文章目录
名称空间
什么是名称空间
在Python中名称空间是用存储对象和名字绑定关系的地方,
那么问题来了,什么是对象,什么是名字,什么是绑定关系?
明白了名称空间是用于存储对象和名字绑定关系的地方,
那么接下来就可以细致了解一下名称空间可以分为哪几类了:
名称空间的意义
名称空间最大的作用就是防止名字重复造成的引用不当,
我们可以在全局名称空间中定义一个a = 6
同时也可以在局部名称空间中定义一个a = 7
,这两者之间是不会产生任何冲突的,
这就是名称空间最大的作用,防止名字重复造成的引用不当。
名称空间的查找顺序
知道了名称空间的意义,那么肯定会有读者意识到,我在全局定义一个a = 6
,在局部定义一个a = 7
,那么接下来调用a这个名字的时候,Python究竟会从哪个空间开始寻找a所对应的对象呢?
我只能说,这位读者你很上道,我们将以实例解答这个问题;
a = 6 # 在全局名称空间中定义一个a
b = 8 # 在全局名称空间中定义一个b,为了测验调用函数时能否找到全局中的b
def test():
a = 7 # 在局部名称空间中定义一个a
return a,b
print(test())
print(a)
从以上我们的测验中,调用函数test
时输出的a将会是7,而当直接使用print(a)
时输出的a将会是6。
所以我们可以大胆的下结论:
局部名称空间详解
在局部名称空间中有一个非常神奇的事情,
因为函数是可以相互嵌套的,在一个函数中嵌套另外一个函数是很正常的现象:
def test_1(): # 定义一个函数
def test_2(): # 在test_1中定义一个嵌套函数
print('球球好心人给个赞吧')
# 这是最简单的函数嵌套,
# 但也是最不规范的函数嵌套,
# 因为如果不改进的话,则无法使用嵌套的test_2函数
以上就是最简单形式的函数嵌套,
那么问题接踵而至,上文中说过局部命中空间是在函数中产生的,
那么如果我在一个函数中定义一个嵌套函数,是不是意味着我在局部名称空间中创建了一个局部名称空间?
对头!
但是在术语上我们会称test_2
为最内部名称空间,而test_1
则是被我们称为附属函数名称空间;
我们可以如此反复俄罗斯套娃:
def test_1(): # 定义一个函数
def test_2(): # 在test_1中定义一个嵌套函数
def test_3(): # 在内嵌函数test_2中再定义一个嵌套函数
# 省略一万层....
print('球球好心人给个赞吧')
print('球球好心人给个赞吧')
嵌套函数中的查找顺序
在前文中已经介绍过了关于嵌套函数所产生的附属函数名称空间、内部名称空间,
那么如果在附属函数名称空间和内部名称空间都定义一个相同名字,那么查找顺序是如何呢?
b = 10 # 在全局定义一个b
def test_1(): # 定义一个函数
def test_2(): # 在test_1中定义一个嵌套函数
a = 6 # 在内部名称空间中定义一个a
return a,b
a = 7 # 在附属名称空间中定义一个a
b = 8 # 在附属名称空间中定义一个b
print(test_1()) # 调用函数
b = 10 # 在全局定义一个b
def test_1(): # 定义一个函数
def test_2(): # 在test_1中定义一个嵌套函数
a = 6 # 在内部名称空间中定义一个a
return a,b
a = 7 # 在附属名称空间中定义一个a
b = 8 # 在附属名称空间中定义一个b
return test_2
print(test_1()()) # 调用函数
按照修改后,我们所得到的结果将会是6,
当我们调用嵌套函数的时候,嵌套函数会从自身的局部空间中开始寻找是否有该名称
就像调用嵌套函数test_2
一般,它从自己的局部名称空间开始寻找,找到了a = 6
后就停止寻找
所以我们又可以下结论了:
关于嵌套函数的使用
b = 10
def test_1():
def test_2():
a = 6
return a,b
a = 7
b = 8
return test_2
print(test_1()()) # 仔细看一下调用函数的过程
为什么调用函数过程中需要写两个括号test_1()()
,而不是直接test_1()
呢?
我们仔细看一下test_1
函数的返回值,test_1
的返回值是一个函数对象test_2
,
所以我们如果调用函数的话只写一个括号将会得到一个函数对象,也就是test_2
来实例示范一下;
b = 10
def test_1():
def test_2():
a = 6
return a,b
a = 7
b = 8
return test_2
print(test_1(), type(test_1())) # 打印输出一下结果
可能有读者想要唱反调了,我就是想直接写一个test_1()
就能够直接得到想要的结果该怎么办呢?
我只能说,这位看官你很有成为天才的潜力,因为懒才是人类进步的基石,
这个需求可以实现,但是我们要这样改代码:
b = 10
def test_1():
def test_2():
a = 6
return a,b
a = 7
b = 8
return test_2()
print(test_1()) # 调用函数
我们的确得到了想要的结果,仔细想一下为什么呢?
还是因为返回值,函数test_1
的返回值是test_2()
,也就是说返回的结果是函数test_2
运行后的结果,
又开始俄罗斯套娃了,
我拿到的返回值是另外一个函数的返回值!?
我只能说没错,是这样的。
但是用这个方法需要注意一点,那就是内嵌函数必须是无参数的!
b = 10
def test_1():
def test_2(c): # 随便定义一个参数
a = 6
return a,b
a = 7
b = 8
return test_2()
print(test_1()) # 调用函数
以上就是关于俄罗斯套娃的名称空间的讲解,接下来我们要介绍一下作用域了,
如果能够将名称空间中的知识点李姐,
那么作用域也不过尔尔。
作用域
什么是作用域
作用域是根据名称空间所产生的,意思就是名字的作用范围;
在上文之中我们其实已经或多或少涉及到了作用域了。
b = 10 # 这个全局变量的作用域就是该模块中的全部范围
def test_1():
def test_2():
a = 6
return a,b # 正因为全局变量的作用于是全部范围,才能够返回b
a = 7
b = 8
return test_2
print(test_1()())
或多或少读者对于这个作用域已经有些许了解,
我直接将结论摆出:
可能正是因为作用域的不同,所以查找顺序也会不同,作用域越大的名称空间反而查找的优先级越低,
正如上文中的,即使全局和局部中都有a
这个名字,调用函数的时候也会先从局部开始。
global
语句
不同的名称空间可以定义相同的名字,这样不会有任何冲突,
可这也意味着,
当我们在局部名称空间的时候是无法修改全局中的名字绑定关系,
于是Python提供了一个方法去解决这个问题:
a = 10 # 定义一个全局语句
def test():
global a # 使用global语句,声明我是用的名字a全局名称空间中的那个a
a = 5
return a
print(a) # 先打印输出一下没有调用函数前的a是什么
print(test()) # 输出一下函数中的结果
print(a) # 看一下全局中的a是否发生了改变
所以我们可以知道,使用global
语句后,我们使用的名字都将会是全局名称空间的
nonlocal
语句
既然在局部可以修改全局名称空间中的名字绑定关系,
那么在内部名称空间是否可以修改附属函数名称空间中的绑定关系呢?
答案显然是可以的,但是需要使用到nonlocal
语句。
def test_1():
def test_2(): # 在test_1里定义一个嵌套函数test_2
a = 15
def test_3(): # 俄罗斯套娃一波
nonlocal a # 使用nonlocal,声明接下来使用的a是附属名称空间的a,所以究竟是test_2中的还是test_1中的?
a = 5
print('调用test_3对a这个名字的绑定关系进行更改')
test_3()
print(f'输出附属函数名称空间中的a:{a}')
a = 10
test_2()
print(f'输出最外部函数的a值:{a}')
test_1()
由此我们可以知道,在调用nonlocal
声明使用的a
是函数test_2
中的,而不是test_1
中的
因此我们可以得出的结论是:
题目题目
还是老规矩,写一个题目对上述内容进行测试
def discount(price,rate):
final_price = price * rate
old_price = 6
print('old_price的值',old_price)
return final_price
old_price = float(input('请输入价格'))
rate = float(input('请输入折扣率'))
print(discount(old_price,rate))
print('old_price的值',old_price)
在上述代码中,假设我输入的old_price
是100,rate
是0.6
那么请问两个问题
能够看到这里的都是一条汉子!