【python入门必学之装饰器,(附例子和详细说明)手把手教分分钟学会】

Python与机器学习 徐 自远 550℃

【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 timedef bar(): time.sleep(3) print("in the bar")deftest2(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 timedef 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 timedef timer(func): defdeco(*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)) returndeco @timer #就相当于test1 = timer(teest1)def test1():time.sleep(3) print("in the test1")@timerdef test2(name):time.sleep(2) print("in the test2 %s"%name) test1()test2("zhaofan")

但是上面这种代码还不是最完整的,有一种情况上述的代码无法使用,就是在做用户认证的时候,如果有多个系统,需要不同的认证方式,注意:上述代码还有一个小问题就是如果被装饰的函数存在return返回值得时候,也是无法显示返回的数据,即代码应该为:

1234567891011121314151617181920212223242526 import timeuser,pwd = "zhaofan","123"defauth(auth_type): defouter_wapper(func): defwrapper(*args,**kwargs): print("认证方式为:%s" %auth_type)username = input("输入用户名:").strip()password = input("输入密码:").strip()if user == username and pwd == password:print(" 认证成功")res=func(*args,**kwargs) return reselse: exit(" 认证失败")return wrapper returnouter_wapper@auth(auth_type="local")defbbs(): print("welcome to bbs page")@auth(auth_type="ldap")defblog(name): print("welcome to blog page") print(name) returnnamebbs()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所在的层级相同,

通过下面代码进行理解:

这个例子中我们在def func1函数内可以调用fun2,但是我们在外面是无法调用到func2的,所以结果为看到如下:

闭包

关于闭包主要有下面两种说法:

  • 闭包是符合一定条件的函数,定义为:闭包是在其词法上下文中引用了自由变量的函数
  • 闭包是由函数与其相关的引用环境组合而成的实体。定义为:在实现绑定时,需要创建一个能显示表示引用环境的东西,并将它与相关的子程序捆绑在一起,这样捆绑起来的整体称为闭包

个人觉得第二种说法更准确,闭包只是在形式上表现像函数,实际不是函数。

我们对函数的定义是:一些可执行的代码,这些代码在函数定义后就确定了,不会在执行时发生变化,所以一个函数只有一个实例。

闭包在运行的时候可以有多个实例,不同的引用环境和相同的环境组合可以产生不同的实例。

这里有一个词:引用环境,其实引用环境就是在执行运行的某个时间点,所有处于活跃状态的变量所组成的集合,这里的变量是指变量的名字和其所代表的对象之间的联系。

可以使用闭包语言的特点:

  • 函数可以作为另外一个函数的返回值或者参数,还可以作为一个变量的值。
  • 函数可以嵌套使用

而认为闭包是函数的有一句话是:

闭包是指延伸了作用域的函数,其中包含函数定义体中引用。但是不在定义体中定义的非全局变量。

上面这种说法个人觉得也是一种理解方式

相信看了这些概念也还是不好理解,还是通过下面例子更好理解:

先实现一种计算平均值的方法:

从结果我们可以看出这里保存了每次的历史值

换一种方法实现:

实现了第一种相同的效果,对这种方法分析:

通常我们会认为我们调用avg(10)的时候make_averager函数已经返回了,而它的本地作用域也一去不复返,但这里其实series是自由变量,是指未在本地作用域绑定的变量

我们可以通过print(dir(avg)),看到如下结果:

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

series的绑定在返回的avg函数的__closure__属性中这或许就是有的人会认为闭包一种函数。闭包会保留定义函数时存在的自由变量的绑定,这样调用函数时虽然定义作用域不能用了,但是仍能使用那些绑定

关于nonlocal

刚开始了解闭包之后,如果尝试使用这种编程方式容易出现以下错误使用例子:

先来看一下错误提示:

这个例子中和我们上面使用的不同之处是:这里的count和total是数字,是不可变类型,而之前的例子中series是一个列表是可变类型

所以这里重新回到了最开始说的作用域问题了,当我们在averager中使用

count += 1的时候其实就是count = count + 1,这样就是在averager函数定义体中对count进行赋值,count就变成了局部变量。

问题小结:当时数字,字符串,元组等不可变类型时,只能读取不能更新,如果使用类似count += 1就会隐式的把count变成局部变量,所以开始例子中使用series,我们后面的操作是append并且列表还是可变对象

不过python3引入了一个新的关键词nonlocal,通过它把变量标记为自由变量,这样我们把上面这个错误的例子简单更改:

到这里装饰器的前奏就说完了,下面就是装饰器,我个人觉得装饰器只是闭包的一种应用,闭包在很多情况下都是一种非常好的变成技巧。

以上是全部代码,只是善于分享,不足之处请包涵!爬虫基本的原理就是,获取源码,进而获取网页内容。一般来说,只要你给一个入口,通过分析,可以找到无限个其他相关的你需要的资源,进而进行爬取。

http://m.toutiaocdn.net/group/6489962366774542862/?iid=17686046122&app=news_article&tt_from=android_share&utm_medium=toutiao_android&utm_campaign=client_share

 

转载请注明:徐自远的乱七八糟小站 » 【python入门必学之装饰器,(附例子和详细说明)手把手教分分钟学会】

喜欢 (0)

苏ICP备18041234号-1 bei_an 苏公网安备 32021402001397号