【python入门必学之装饰器,(附例子和详细说明)手把手教分分钟学会】
定义
本质就是函数,功能是装饰其他函数,其实就是给其他函数添加附加功能,
原则
1、不能修改被装饰的函数的源代码
2、不能修改被装饰的函数的调用方式
其实就是当你用装饰器函数装饰其他函数的时候,被装饰的函数被装饰之前的调用方式不会有任何区别
实现装饰器的知识储备
1、函数即“变量”
2、高阶函数
3、嵌套函数
高阶函数+嵌套函数===装饰器
关于函数即变量的理解
上图中描述了函数在内存中存放的原理图,其实test函数名就相当于函数在内存中存放的一个地址,也就是图中所说的房间门牌号
高阶函数
什么是高阶函数?
1、把一个函数名当做实参传给另外一个函数
2、返回值中包含函数名
写一个高阶函数的例子:
123456789101112 | import time def bar(): time.sleep( 3 ) print ( "in the bar" ) def test1(func): start_time = time.time() func() #这里的func()就相当于bar() stop_time = time.time() # 这个打印的时间就是func函数运行的时间,也就是bar函数的运行时间
print ( "the fun runn time is %s" % (stop_time - start_time)) test1(bar) |
上面的代码的例子就是一个高阶函数,满足了把函数名当做实参传给另外一个函数,上述代码中是把bar函数名传递给了test1作为实参,这里就起到了一个装饰的作用,相当于给bar函数增加了一个计算运行时间的功能,但是不能称为装饰器。因为上述我们的原则中说了,装饰器不能修改被装饰器函数的调用方式,我们这里的bar函数调用方式变了,变成了test1(bar),而不是bar(),并且我们的高阶函数还有一个条件没有满足,就是“返回值中包含函数名”
我们接着再写一个高阶函数的例子:
12345678910 | import time def bar(): time.sleep( 3 ) print ( "in the bar" ) def test2(func): print (func) return func bar = test2(bar) bar() |
关于上述代码的一个分析:
当执行test2(bar)的时候,最后返回的其实就是bar函数内存地址,也就是我们所说的门牌号,所以当我们再次给他赋值给bar的时候,执行bar()就会执行bar函数
嵌套函数
嵌套函数就是在一个函数体内容再用def 定义一个函数,例子如下:
1234567 | def foo(): print ( "in the foo" ) def bar(): print ( "int the bar" )
bar() foo() |
上述讲完了关于装饰器的知识储备:
函数即变量
高阶函数
嵌套函数
正式开始理解装饰器
高阶函数+嵌套函数===装饰器
我们写一个例子:
123456789101112131415 | import time def timer(func): def deco():
start_time = time.time() func() #这个时候的func()就是test1() stop_time = time.time() print ( "the func run time is %s" % (stop_time - start_time))
return deco def test1(): time.sleep( 3 ) print ( "in the test1" ) test1 = timer(test1) test1() |
上述代码就是一个简单的装饰器,
通过下面图解对装饰器工作的原理的一个理解:
但是上面代码中的使用装饰器的方法并不是最好的,因为我们是执行了test1=timer(test1)然后执行test1(),其实python中给我们提供了一种方法,即要装饰的函数上面添加@timer,执行@timer其实就是还行test1 = timer(test1)
,并且我们的这种方式,还存在一个问题,就是当被装饰的函数有参数需要传递的时候,就不能用了,所以我们修改后的代码为:
1234567891011121314151617181920 | import time def timer(func): def deco( * args, * * kwargs): start_time = time.time() func( * args, * * kwargs) #这个时候的func()就是test1() stop_time = time.time() print ( "the func run time is %s" % (stop_time - start_time)) return deco @timer #就相当于test1 = timer(teest1) def test1():
time.sleep( 3 ) print ( "in the test1" ) @timer def test2(name):
time.sleep( 2 ) print ( "in the test2 %s" % name) test1() test2( "zhaofan" ) |
但是上面这种代码还不是最完整的,有一种情况上述的代码无法使用,就是在做用户认证的时候,如果有多个系统,需要不同的认证方式,注意:上述代码还有一个小问题就是如果被装饰的函数存在return返回值得时候,也是无法显示返回的数据,即代码应该为:
1234567891011121314151617181920212223242526 | import time user,pwd = "zhaofan" , "123" def auth(auth_type): def outer_wapper(func): def wrapper( * args, * * kwargs): print ( "认证方式为:[31;1m%s[0m" % auth_type)
username = input ( "输入用户名:" ).strip()
password = input ( "输入密码:" ).strip()
if user = = username and pwd = = password:
print ( "[32;1m 认证成功[0m" )
res = func( * args, * * kwargs) return res
else : exit( "[31;1m 认证失败[0m" )
return wrapper return outer_wapper @auth (auth_type = "local" ) def bbs(): print ( "welcome to bbs page" ) @auth (auth_type = "ldap" ) def blog(name): print ( "welcome to blog page" ) print (name) return name bbs() blog( "zhaofan" ) |
通过图解理解上面的代码:
所以整个程序执行的过程如下:
@auth(auth_type=”local”)—–>auth(auth_type=”local”) —–>因为auth(auth_type=”local”)就相当执行auth函数,所以执行结果为outer_wapper,即转换为
@outer_wapper—–>@outer_wapper相当于bbs=outer_wapper(bbs)—–>执行outer_wapper(bbs)的结果为wapper,所以bbs=wapper,也就是说bbs这个时候相当于wapper函数名的内存地址—–>当执行bbs() 就相当于执行了wrapper()
在看闭包问题之前先来看看关于python中作用域的问题
变量作用域

对于上述代码中出现错误,肯定没什么疑问了,毕竟b并没有定义和赋值,当我们把代码更改如下后:

再看一个例子:

首先这个错误已经非常明显:说在赋值之前引用了局部变量b
可能很多人觉得会打印10然后打印6,其实这里就是涉及到变量作用域的问题
当Python编译函数的的定义体的时候,它判断b是局部变量,毕竟在函数中有b = 9表示给b赋值了,所以python会从本地环境获取b,当我们调用方法执行的时候,定义体会获取并打印变量a的值,但是当尝试获取b的值的时候发现b没有绑定值,所以要想让上述代码运行还可以把b设置为全局变量,或者把b赋值放到调用之前


函数对象的作用域
python中一切皆对象,同其他对象一样,函数对象也有其使用的范围即函数对象的作用域。
在python中我们通过def定义函数,函数对象的作用域与def所在的层级相同,
通过下面代码进行理解:
1 |
def func1(): def func2(x): return 2*x print(func2(5))func1()print(func2(5)) |
这个例子中我们在def func1函数内可以调用fun2,但是我们在外面是无法调用到func2的,所以结果为看到如下:

闭包
关于闭包主要有下面两种说法:
- 闭包是符合一定条件的函数,定义为:闭包是在其词法上下文中引用了自由变量的函数
- 闭包是由函数与其相关的引用环境组合而成的实体。定义为:在实现绑定时,需要创建一个能显示表示引用环境的东西,并将它与相关的子程序捆绑在一起,这样捆绑起来的整体称为闭包
个人觉得第二种说法更准确,闭包只是在形式上表现像函数,实际不是函数。
我们对函数的定义是:一些可执行的代码,这些代码在函数定义后就确定了,不会在执行时发生变化,所以一个函数只有一个实例。
闭包在运行的时候可以有多个实例,不同的引用环境和相同的环境组合可以产生不同的实例。
这里有一个词:引用环境,其实引用环境就是在执行运行的某个时间点,所有处于活跃状态的变量所组成的集合,这里的变量是指变量的名字和其所代表的对象之间的联系。
可以使用闭包语言的特点:
- 函数可以作为另外一个函数的返回值或者参数,还可以作为一个变量的值。
- 函数可以嵌套使用
而认为闭包是函数的有一句话是:
闭包是指延伸了作用域的函数,其中包含函数定义体中引用。但是不在定义体中定义的非全局变量。
上面这种说法个人觉得也是一种理解方式
相信看了这些概念也还是不好理解,还是通过下面例子更好理解:
先实现一种计算平均值的方法:

从结果我们可以看出这里保存了每次的历史值
换一种方法实现:

实现了第一种相同的效果,对这种方法分析:
通常我们会认为我们调用avg(10)的时候make_averager函数已经返回了,而它的本地作用域也一去不复返,但这里其实series是自由变量,是指未在本地作用域绑定的变量
我们可以通过print(dir(avg)),看到如下结果:

其实这里面保存着均布变量和自由变量的名称,我们可以通过下面方法查看:

series的绑定在返回的avg函数的__closure__属性中这或许就是有的人会认为闭包一种函数。闭包会保留定义函数时存在的自由变量的绑定,这样调用函数时虽然定义作用域不能用了,但是仍能使用那些绑定
关于nonlocal
刚开始了解闭包之后,如果尝试使用这种编程方式容易出现以下错误使用例子:
1 |
def make_averager(): count = 0 total = 0 def averager(new_value): count += 1 total += new_value return total / count return averager |
先来看一下错误提示:

这个例子中和我们上面使用的不同之处是:这里的count和total是数字,是不可变类型,而之前的例子中series是一个列表是可变类型
所以这里重新回到了最开始说的作用域问题了,当我们在averager中使用
count += 1的时候其实就是count = count + 1,这样就是在averager函数定义体中对count进行赋值,count就变成了局部变量。
问题小结:当时数字,字符串,元组等不可变类型时,只能读取不能更新,如果使用类似count += 1就会隐式的把count变成局部变量,所以开始例子中使用series,我们后面的操作是append并且列表还是可变对象
不过python3引入了一个新的关键词nonlocal,通过它把变量标记为自由变量,这样我们把上面这个错误的例子简单更改:
1 |
def make_averager(): count = 0 total = 0 def averager(new_value): nonlocal count,total count += 1 total += new_value return total / count return averager |
到这里装饰器的前奏就说完了,下面就是装饰器,我个人觉得装饰器只是闭包的一种应用,闭包在很多情况下都是一种非常好的变成技巧。
以上是全部代码,只是善于分享,不足之处请包涵!爬虫基本的原理就是,获取源码,进而获取网页内容。一般来说,只要你给一个入口,通过分析,可以找到无限个其他相关的你需要的资源,进而进行爬取。