Python基础第七天——global与nonlocal、闭包函数、装饰器
鸡汤:
突破Python基础第五、六天,算是正式上路了!路漫漫其修远兮 吾将上下而求索!持之以恒才是胜利的关键,加油,奔跑吧小白!
一、拾遗
1、函数的返回值
语法:
在函数内部后面加上:“return 值”
特点:
(1)一次return可以返回一个或多个值,且返回值没有类型限制,返回多个值时以逗号作为分隔符隔开
(2)返回的一个值是字符串的形式,返回多个值是以元组的形式存储
(3)如果不写return,默认返回:None
(4)一个函数中若有多个rentun,只能返回第一个return,函数就终止
2、函数参数的使用
函数的参数分为两大类:形参和实参
(1)形参特点和种类:
<1>特点:
形参在定义阶段定义,调用时需要接收实参传过来的值
<2>种类:
I、位置形参:特点: 必须被传值,不能多传也不能少传
II、默认参数:特点: 在定义阶段已为形参赋值,定义阶段有值,调用阶段可以不传值。 例:def (x,y=1):中的"y=1"就是默认形参,通常情况下把固定不变(或很少变)的参数作为默认参数。
III、*args: 特点:用来接收实参传过来的可变长的位置实参(“*”用来接收实参传过来的可变长按位置定义的参数,然后赋值给变量名args)
IV、**kwargs:特点: 用来接收实参传过来的可变长的关键字实参(“**”)
V、命名关键字参数:
特点:在 “ * ” 后面的参数,这些参数必须要被传值,且实参必须以关键字的形式传值
在“ *args ” 后面的参数也是命名关键字参数,只是“ *args ”可以接收从实参传过来的任意长度的的位置实参。
<3>形参种类在定义时的位置排序:
(位置形参,默认参数,*args,命名关键字参数,**kwargs)
------------------------------------------------------------->
从左至右排序排序
(2)实参特点和种类:
<1>特点:
实参在调用阶段时定义,调用时要传入的值。简单地说,实参就是给形参传值的。
<2>种类:
I、位置实参: 特点:按位置传值,必须与形参一 一对应
II、关键字实参:特点:按关键字以“key=value”的形式传值
<3>实参种类在定义时的位置排序:
(位置实参,关键字实参)
------------------------>
从左至右排序排序
(3)形参与实参的关系:
形参相当于变量名,实参相当于变量的值,在python中变量的定义都没有储值的功能,所以它们两是一种绑定的关系。这种绑定关系在函数调用时生效,函数调用结束后失效
(4)*args和**kwargs的作用:
<1>在定义阶段:
两者一起连用表示能接收从实参传过来的任意长度、任意形式的参数。
*接收任意长度的参数后以元组的形式保存并赋值给变量名:args
**接收任意形式的参数后以字典的形式保存并赋值给变量名:kwargs
<2>在调用阶段:
*args表示将传进去的这些参数全都拆开,拆成按位置的形式,如:(1,2,3,4,5)
**kwargs表示将传进去的这些参数全都拆开,拆成按关键字的形式,如(a=1,b=2,c=3)
3、函数的嵌套
分为两种:
(1)函数的嵌套调用
定义:在函数内部嵌套调用其它函数
(2)函数的嵌套定义
定义:在函数内部使用def关键字再定义一个函数
4、函数对象
定义:函数可以被当作数据去处理。
(1)可以被引用
(2)可以当作参数传入
(3)可以当作函数的返回值
(4)可以当作容器类的元素
5、名称空间与作用域(global和nonlocal)
(1)名称空间
定义:存放名字和值的绑定关系
<1>种类:
内置名称空间:python解释器一启动就会产生。
全局名称空间:python解释器开始执行文件的时候就会产生。
局部名称空间:执行文件的过程中可能会调用到函数,调用函数则会产生局部名称空间,它是临时产生的,调用结束则失效。
<2>三种名称空间按产生顺序排序
内置名称空间——>全局名称空间——>局部名称空间
<3>三种名称空间按查找名字的顺序排序
局部名称空间——>全局名称空间——>内置名称空间
(2)作用域
定义:生效范围
分类:按照每个名称空间的作用范围不同分成了两个区域:
<1>全局作用域:
定义:定义在全局作用域名字叫全局变量。包含内置名称空间和全局名称空间
特点:在文件的任何位置都可以被引用
失效:文件结束完了或del直接干掉了
<2>局部作用域:
定义:定义在局部作用域的名字叫局部变量,局部名称空间
特点:只能在局部使用,不能从全局作用域看局部作用域的名字,但是能在局部作用域看全局作用域的名字
<3>全局作用域和局部作用域按查找顺序排序
局部作用域——>全局作用域
补充:
即使当前位置是全局作用域,也要按照以上的顺序来,因为全局作用域的局部作用域仍然是全局作用域
查看局部作用域的名字:locals
查看全局作用域的名字:globals
二、global与nonlocal的补充
1、global
作用:局部作用域修改名字,会在全局作用域的名字中找
函数内加上global关键字它会一下从最里层跳到最外一层找全局的了
例1:
例2:
要求:在局部作用域中修改全局作用域的名字 (不推荐)
上述编程风格在实际工作中一定要尽可能要避免,因为在局部进行更改会导致全局也跟着改变,这样很容易改乱。
例如,抢票软件。假设一张车票,有几十万人同时在线,大家都发现了这张票,(读一个全局的变量,这几十万人都能读到),于是这几十万人便开始抢,都把这张票加到购物车后付钱,(此时相于每个人的后台都要减去这一张票,这就相当于大家都在改一个值)最快速度付完款的人则能抢到票,但是在最快抢到票的那段时间内,同一时间这几十万人当中可能同时不止一个人抢到了票,可能会多个人同时抢到这张票,这时就会出现一张票被多人抢到手的问题了。(相当于同一时间内有多人改1个值)
为了避免以上问题,解决的方法是:不要使用全局的变量,要使用局部变量
2、nonlocal
作用:局部作用域内修改名字,只在函数内部找
函数内加上nonlocal关键字它会找当前层的上一层。
例1:
例2:若将以上f2函数中“x”注释掉,nonlocal则会继续去再上一层的函数中找变量"x",
本例得到的结果仍然是"from f2 function",那是因为f1函数体中的“x”值只是定义而没有加打印,而且调用了f2函数体中的print('from f2 function',x)这段代码,所以才会得到“from f2 function,xiaobai“
例3:若将函数中所有的变量x都注释掉,则会报错,提示变量"x"未绑定
三、 闭包函数
1、定义:
函数内部定义函数,称为内部函数
该内部函数对外部作用域,而不是对全局作用域名字的引用
闭包函数的格式:
def 外部函数名():
内部函数需要的变量
def 内部函数():
引用外部变量
return 内部函数
例1:
本例中的闭包函数就是返回的foo函数名以及附带的x=2的值,把调用func函数的结果赋值给变量名f,此时变量“f”又是一个全局的变量,也就是此时的func函数能在任意位置被调用了
例2:
补充:即使以下闭包函数中的foo函数不加return也算是闭包函数,只要这个函数是内部函数,且对外部函数的引用就是闭包函数。但是为了调用时不受层级限制,通常函数函数与return一起使用。
将例1中的闭包函数进行调用
2、闭包函数的特点
(1)特性:
闭包函数永远自带着自己的状态,可以被赋值给变量名,调用时不受函数定义时的层级限制。
(2)验证函数是否为装饰函数:__closure__
验证一个函数是否为闭包函数则用“__closure__”来验证。(closure的中文意思就是闭包)
闭包函数中不一定要有return返回值,但是为了调用时不受层级限制,通常要与return一起使用,只需要将这个函数名后加上.__closure__查看结果就可判断是否为闭包函数,如果打印的结果有值,则为闭包函数
(3)取值:
通过“cell_contents”得到闭包中的值。
例1:闭包中附带1个值
例2:闭包中附带多个值
例3:内部函数引用的全局变量则不称为闭包函数,此时用“__closure__”验证得到“None”
以下是非闭包函数
(4)补充:
补充1:凡是在函数内部看到有调用时,一定要看去找这个被调用函数定义时的位置。
例:
补充2:在闭包函数中加上nonlocal关键字时,这个函数仍然是闭包函数,只是这可以在闭包函数内部对外面一层的函数里的值进行修改
例:包两层的情况
3、多层闭包函数
思路:想让一个函数保存一个状态,不断地包它就行了,包一层外面定义一个变量,把这个它包在一个函数内部,想要基于这个函数的基础之上再往里面包内容则再包一层,再把那个函数返回来
例:
4、闭包的应用场景
闭包函数可应用于爬虫项目。
什么是爬虫?
答:爬虫是通过一个链接把网站的内容下载至本地,然后通过一些规则(如正则表达式)提取对自己有用的信息。
例:写一个爬虫的雏形项目
通过urlopen模块可以向网页发起请求将代码下载至本地
from urllib.request import urlopen
(1)爬虫的基本原理
(2)把输入的链接用函数做成一个功能,通过传参的方式统一定义。
(3)为了省去记网址的麻烦,则将程序改为闭包函数。将这种多次访问同一个链接地址称为一种状态。这种状态可以通过闭包的形式保存起来,此时调用函数时不必传参,当要用到时只需要加上括号即可。
(4)以上步骤中的url变量里的网址是写死的,如果要分别爬多个网站,则需要修改源代码,为了避免修改源代码,将以上代码修改为:
四、装饰器
1、定义:
依照目前所学的知识可以将装饰器理解为以下定义:
装饰器本身就是函数且是可以任意调用的对象,被装饰的对象也是任意可调用对象。
(可调用对象:加括号就能运行的函数)
通俗地说装饰器就是闭包函数的一种应用而已。
装饰器的目的是,给被装饰对象添加新功能,并且不让用户察觉感觉到装饰器的存在。
2、特性:
装饰器遵循开放封闭原则 (即不修改被装饰对象的源代码以及调用方式)为被装饰对象添加新功能。
开放封闭原则:对扩展是开放的,对修改是封闭的(所有软件开发都要遵循这个原则)
以上这句话的详细意思是:
例如写一个软件,不可能一次性地把功能都写全了再上线运行,一定会根据客户需要将软件进行功能扩展,这就是对扩展开放。
对软件源代码是不能修改的,因为这些源代码在函数里时,定义阶段可能会被许多位置使用到,这就意味着一旦把某个函数中的源代码改了,影响的可能是全局,引起连锁反应。除非你能保证不会影响全局的情况下才能进行对源代码修改。
例:给一个函数的加上统计运行时间的功能
# 要求:给index函数加上统计index函数的运行时间 import time # 时间模块 import random # 随机模块 # 装饰器 def timmer(func): # func = index def wrapper(): start_time = time.time() # time.time()表示显示当前时间 func() stop_time = time.time() print('run time is %s'%(stop_time - start_time)) return wrapper # 被装饰对象 def index(): time.sleep(random.randrange(1,4)) # 表示随机取1-4中的任意一个数 print('welcome to index page') index = timmer(index) # timmer(index)表示把被装饰对象传给timmer装饰器 index()
图示:
3、装饰器的流程解析:
例:
4、装饰器的语法:
在被装饰对象的正上方单独写一行:@装饰器名
例:将上面的装饰器使用装饰器语法
# 要求:给index函数加上统计index函数的运行时间 import time import random # 装饰器 def timmer(func): # func = index def wrapper(): start_time = time.time() func() stop_time = time.time() print('run time is %s'%(stop_time - start_time)) return wrapper # 被装饰对象1 @timmer # 装饰器语法,相当于 index = timmer(index) def index(): time.sleep(random.randrange(1,4)) print('welcome to index page') # 被装饰对象2 @timmer # 装饰器语法,相当于 home = timmer(home) def home(): time.sleep(random.randrange(1,3)) print('welcome to home page') index() home()
输出结果:
welcome to index page run time is 1.0005803108215332 welcome to home page run time is 1.0001659393310547
5、我自已理解的装饰器原理。
def 装饰器名(形参名): # 装饰器中的形参是用来接收值的,这个传过来的值是被装饰对象的函数名
def 内部函数名():
装饰器功能代码....
被装饰对象名() # 调用被装饰对象,被装饰对象的名字与装饰器括号内的形参名字是一致的。
装饰器功能代码....
return 内部函数名
@ 装饰器名 # 装饰器语法:相当于被装饰对象名 = 装饰器名(被装饰对象名)
def 被装饰对象名():
函数体
......
被装饰对象名() # 调用被装饰对象
6、多个装饰器同时被使用
执行程序的流程是从上往下依次执行的。
而多个装饰器同时使用时,则是从下往上执行的顺序依次计算的。按排序最上面的装饰器先执行,最下面的装饰器先计算。对我们有用的就是执行!
例1:
例2:多个装饰器的执行流程
import time # 1 执行顺序由数字说明 import random # 2 # 装饰器 def timmer(func): # 3 def wrapper(): # 7 start_time = time.time() func() stop_time = time.time() print('run time is %s'%(stop_time - start_time)) return wrapper # 8 def auth(func): # 4 def deco(): # 9 username = input("Please input your username:") # 12 password = input("Please input your password:") if username == 'xiaobai' and password == '123456': print('Login successful!') func() else: print('Login error!') return deco # 10 # 被装饰对象 @auth # 5 将加上wrapper闭包的index当作参数传入auth函数内,返回deco函数,此时得到deco的闭包,再赋值给index @timmer # 6 将index当作参数传入timmer函数,返回wrapper函数,此时得到wrapper的闭包,再赋值给index def index(): time.sleep(3) print('welcome to index page') index() # 11
7、有参装饰器
(1)例:
# 有参装饰器 import time import random #装饰器 def timmer(func): def wrapper(name): start_time = time.time() func(name) stop_time = time.time() print('run time is %s'%(stop_time - start_time)) return wrapper # 装饰器语法 @timmer # 被装饰对象 def home(name): time.sleep(random.randrange(1,3)) print('Welcome to %s home page'%name) # 调用阶段 home('xiaobai')
(2)装饰器接收任意长度的参数
当被装饰对象有多个,且有的是无参函数,有的是有参函数时:
则
在装饰器的接收位置加上*args,**kwargs可接收任意长度的参数,
在装饰器内调用被装饰对象时也要加上*args,**kwargs
例2:
import time import random #装饰器 def timmer(func): def wrapper(*args,**kwargs): # 不管被装饰对象是否有参数,用*args和**kwargs来接收传过来的任意长度的参数 start_time = time.time() func(*args,**kwargs) # 怎么接收的就要怎么给。 stop_time = time.time() print('run time is %s'%(stop_time - start_time)) return wrapper # 装饰器语法 @timmer # 被装饰对象1——有参函数 def home(name): time.sleep(random.randrange(1,3)) print('Welcome to %s home page'%name) # 装饰器语法 @timmer # 被装饰对象2——无参函数 def index(): time.sleep(random.randrange(1,5)) print('welcome to index page') # 调用阶段 home('xiaobai') index()
(3)当被装饰对象有返回值时
例:
import time import random #装饰器 def timmer(func): def wrapper(*args,**kwargs): start_time = time.time() res = func(*args,**kwargs) # 调用被装饰对象并赋值给res stop_time = time.time() print('run time is %s'%(stop_time - start_time)) return res # 返回res,如果被装饰对象中有返回值,则原封不动地返回,如果没有则返回None return wrapper # 装饰器语法 @timmer # 被装饰对象2——无参函数 def index(): time.sleep(random.randrange(1,5)) print('welcome to index page') # 装饰器语法 @timmer # 被装饰对象1——有参函数 def home(name): time.sleep(random.randrange(1,3)) print('Welcome to %s home page'%name) return 'I am King in the world!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' # 调用阶段 res1 = index() # 此时的index不是原始的index,而是调的wrapper函数 print('index return %s'%res1) res2 = home('xiaobai') # 此时的home不是原始的home,而是调的wrapper函数 print('home return %s'%res2)
输出结果:
welcome to index page run time is 2.0005133152008057 index return None Welcome to xiaobai home page run time is 1.0001530647277832 home return I am King in the world!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!