day 10 - 1 函数进阶
函数进阶
命名空间和作用域
命名空间
命名空间 有三种
内置命名空间 —— python解释器
就是python解释器一启动就可以使用的名字存储在内置命名空间中
内置的名字在启动解释器的时候被加载进内存里
全局命名空间 —— 我们写的代码但不是函数中的代码
是在程序从上到下被执行的过程中依次加载进内存的
放置了我们设置的所有变量名和函数名
局部命名空间 —— 函数
就是函数内部定义的名字
当调用函数时 才会产生这个名称空间 随着函数执行的结束 这个命名空间随之消失
在局部:可以使用全局、内置命名空间中的名字
在全局:可以使用内置命名空间中的名字,但是不能用局部中使用
在内置:不能使用局部和全局的名字的
如下面的例子
#局部命名空间的名字 不可以在全局空间中被调用 def func(): a = 1 func() print(a) #报错——NameError: name 'a' is not defined
命名空间的使用原则:
1、在正常情况下,直接使用内置的名字
2、当我们在全局定义了和内置命名空间中同名的名字时,会使用全局的名字
3、在嵌套中,当自己的这一级中有所需的名字时,就不去找上一级找了
4、在嵌套中,如果自己没有 就找上一级要 上一级没有再找上一级 如果内置的名字空间也没有 就报错
5、多个函数应该拥有多个独立的局部名字空间,不互相共享
作用域
作用域 有两种
全局作用域 —— 作用在全局 —— 内置和全局命名空间中的名字都属于全局作用域 —— globals()
局部作用域 —— 作用在局部 —— 函数(局部命名空间中的名字属于局部作用域) —— locals()
我们接着来看下面 global 的例子
#global 声明过得变量,让在函数外部的 print() 可以进行打印 a = 1 def func(): global a #使用 global 进行声明 a = 2 func() print(a) #输出结果为 2 b=1 def func(): #global b b = 2 func() print(b) #此时结果还是 1,因为在内部的任何操作 不影响外部的
对于不可变数据类型 在局部可以查看全局作用域中的变量,但是不能直接修改
如果想要修改,需要在程序的一开始添加 global 声明
如果在一个局部(函数)内声明了一个 global 变量,那么这个变量在局部的所有操作将对全局的变量有效
接着我们对比着 global() 变量来看 locals() 变量
#可以看出两个打印的内容一个为局部一个为全局 a = 1 b = 2 def func(): x = 'aaa' y = 'bbb' print(locals()) #输出局部的变量,根据 locals 所在的位置 print(globals()) #永远打印全局的名字,可以看到打印出了好多内容 func()
你一定会觉得 globals 函数在很多时候很好用(我也是这么觉得着)
可以 globals 这个函数涉及到代码的安全性 不推荐使用
可以通过传参和接收返回值 来完成原本使用 global 完成的事情
#通过传参和接收返回值来时实现调用全局变量 a = 1 def func(): global a a = 3 func() print(a) #改为: a = 2 def func(a): a += 1 return a print(func(a)) #此时返回 3
函数的嵌套与调用
#求两个数字的最大值 def max(a,b): return a if a > b else b print(max(15,98))
#求三个数的最大值 def the_max(a,b,c): e = max(a,b) #把其中的两个值 丢给 max 去解决了 return max(e,c) print(the_max(56,85,65))
不能直接调用函数内层的函数名
def outer(): def inner(): print('inner') #outer() #没有结果 #inner() #报错 #outer(inner()) #报错
可以使用 nonlocal 实现函数嵌套调用
# nonlocal # 对全局无效 # 对局部 也只是对 最近的 一层 有影响 def outer(): a = 1 def inner(): b = 2 print(a) #内部函数可以使用外部函数的变量 print('inner') def inner2(): print(a,b) print('inner2') nonlocal a #声明了上面第一次出现 a 局部变量,没找到 则报错(不常用) #global #在此处使用不能达到要求,因为此时 a = 1 为局部变量 a += 1 #在这里操作是想修改上面 a = 1 的这个值(不可变数据类型的修改) inner2() #函数必须是先定义后调用 print('局部变量 a:',a) inner() outer() #输出结果 ''' 1 inner 1 2 inner2 局部变量 a: 2 #这里可以看到外部函数的变量在内部函数中被修改了 '''
函数的赋值
函数名可以用于:赋值、容器类型的元素、返回值、参数
#函数名的赋值与作为容器类型的元素使用 def func(): print(123) #func() #函数名就是内存地址 func2 = func #函数名可以用于赋值 func2() #所以结果为 123 l = [func,func2] #函数名可以作为容器类型的元素 如下面 for 循环 print(l) #可以看出赋值后的内存地址是一致的 for i in l: i()
#函数名作为返回值与参数 def func(): print(123) def func2(f): f() return f #函数名可以作为函数的返回值 func2(func) #函数名可以作为函数的参数 func3 = func2(func) #用于接收返回值 func3()
闭包
定义:
必须符合:是嵌套函数,且内部函数调用外部函数的变量
def outer(): a = 1 def inner(): print(a) #到这里就是一个闭包了 print(inner.__closure__) #可以使用 __closure__ 来检测是否是闭包 #返回 (<cell at : int object at ,) 这些就表示是一个闭包了 inner() outer()
闭包常用形式:接收返回值
在一个函数的内部去使用它外部的变量
#不过下面并不是闭包常用的形式 def outer(): a = 1 def inner(): print(a) inner() outer() #而下面的才是(采用接收返回值的形式) def outer(): a = 1 def inner(): print(a) return inner inn = outer() inn() #在这里 a = 1 不会因为 outer() 函数的结束而消失,因为 inn() 后面可能会用到,所以才不会消失 #使用闭包的好处在于 我保护了 a = 1 这个变量,它既不是全局变量,我又可以在使用它时,去使用它 #延长了 a = 1 的声命周期 节省了创建和删除变量 a = 1 的时间
闭包的应用
##获取网站信息 #现在已经可以拿到网页了,但是我们一般都要把它封装成一个函数 from urllib.request import urlopen ret = urlopen('https://fanyi.baidu.com/translate#en/zh/inter').read()print(ret) print(ret) #我们使用闭包来封装它 from urllib.request import urlopen def get_url(): url = 'https://fanyi.baidu.com/translate#en/zh/inter' def inner(): ret = urlopen(url).read() print(ret) return inner get_func = get_url() get_func() #这样的好处在于 就算在外部调用了 n 次 get_func() 而创建 n 次 url 这个变量