【Python之路】特别篇--Python装饰器
前情提要
1. 作用域
在python中,函数会创建一个新的作用域。python开发者可能会说函数有自己的命名空间,差不多一个意思。这意味着在函数内部碰到一个变量的时候函数会优先在自己的命名空间里面去寻找。
python中的作用域分4种情况:
L:local,局部作用域,即函数中定义的变量;
E:enclosing,嵌套的父级函数的局部作用域,即包含此函数的上级函数的局部作用域,但不是全局的;
G:globa,全局变量,就是模块级别定义的变量;
B:built-in,系统固定模块里面的变量,比如int, bytearray等。
搜索变量的优先级顺序依次是:局部作用域 > 外层作用域 > 当前模块中的全局 > python内置作用域,也就是LEGB。
当然,local和enclosing是相对的,enclosing变量相对上层来说也是local。
x = int(2.9) # int built-in g_count = 0 # global
def outer():
o_count = 1 # enclosing
def inner():
i_count = 2 # local
print(o_count)
# print(i_count) 找不到
inner()
outer() # print(o_count) #找不到
在Python中,只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如if、try、for等)是不会引入新的作用域的。
让我们写一个简单的函数看一下 global 和 local 有什么不同:
>>> a_string = "This is a global variable"
>>> def foo():
... print locals()
>>> print globals()
{..., 'a_string': 'This is a global variable'}
>>> foo() # 2
{}
内置的函数globals
返回一个包含所有python解释器知道的变量名称的字典。
在#2我调用了函数 foo
把函数内部本地作用域
里面的内容打印出来。
我们能够看到,函数foo
有自己独立的命名空间,虽然暂时命名空间里面什么都还没有。
global关键字
当内部作用域想修改外部作用域的变量时,就要用到global和nonlocal关键字了,当修改的变量是在全局作用域(global作用域)上的,就要使用global先声明一下,代码如下:
count = 10
def outer():
global count
print(count)
count = 100
print(count)
outer()
#10
#100
nonlocal关键字
global关键字声明的变量必须在全局作用域上,不能嵌套作用域上,当要修改嵌套作用域(enclosing作用域,外层非全局作用域)中的变量怎么办呢,这时就需要nonlocal关键字了
def outer():
count = 10
def inner():
nonlocal count
count = 20
print(count)
inner()
print(count)
outer()
#20
#20
2. 变量解析规则
当然这并不是说我们在函数里面就不能访问外面的全局变量。
在python的作用域规则里面,创建变量一定会一定会在当前作用域里创建一个变量,
但是访问或者修改变量时会先在当前作用域查找变量,没有找到匹配变量的话会依次向上在闭合的作用域里面进行查看找。
所以如果我们修改函数foo
的实现让它打印全局的作用域里的变量也是可以的:
>>> a_string = "This is a global variable"
>>> def foo():
... print a_string # 1
>>> foo()
This is a global variable
在#1处,python解释器会尝试查找变量a_string
,当然在函数的本地作用域
里面是找不到的,所以接着会去上层的作用域里面去查找。
但是另一方面,假如我们在函数内部给全局变量赋值,结果却和我们想的不一样:
>>> a_string = "This is a global variable"
>>> def foo():
... a_string = "test" # 1
... print locals()
>>> foo()
{'a_string': 'test'}
>>> a_string # 2
'This is a global variable'
我们能够看到,全局变量能够被访问到(如果是可变数据类型(像list,dict这些)甚至能够被更改)但是赋值不行。
在函数内部的#1处,我们实际上新创建
了一个局部变量,隐藏
全局作用域中的同名变量。
我们可以通过打印出局部命名空间中的内容得出这个结论。
我们也能看到在#2处打印出来的变量a_string
的值并没有改变。
3. 变量生存周期
值得注意的一个点是,变量不仅是生存在一个个的命名空间内,他们都有自己的生存周期,请看下面这个例子:
>>> def foo():
... x = 1
>>> foo()
>>> print x # 1
Traceback (most recent call last):
...
NameError: name 'x' is not defined
#1处发生的错误不仅仅是因为作用域规则
导致的(尽管这是抛出了NameError的错误的原因)
它还和python以及其它很多编程语言中函数调用实现的机制有关。
在这个地方这个执行时间点并没有什么有效的语法让我们能够获取变量x
的值,因为它这个时候压根不存在!
函数foo
的命名空间随着函数调用开始而开始,结束而销毁。
4. 嵌套函数
Python允许创建嵌套函数。这意味着我们可以在函数里面定义函数而且现有的作用域和变量生存周期依旧适用。
>>> def outer():
... x = 1
... def inner():
... print x # 1
... inner() # 2
...
>>> outer()
1
python解释器需找一个叫x
的本地变量,查找失败之后会继续在上层的作用域里面寻找。
这个上层的作用域定义在另外一个函数里面。
对函数outer
来说,变量x
是一个本地变量,但是如先前提到的一样,
函数inner
可以访问封闭的作用域(至少可以读和修改)。
在#2处,我们调用函数inner
,非常重要的一点是:
inner
也仅仅是一个遵循python变量解析规则的变量名,python解释器会优先在outer
的作用域里面对变量名inner
查找匹配的变量.
5. 闭包
我们先不急着定义什么是闭包,先来看看一段代码:
>>> def outer():
... x = 1
... def inner():
... print x # 1
... return inner
>>> foo = outer()
>>> foo.func_closure
(<cell at 0x...: int object at 0x...>,)
inner
作为一个函数被outer
返回,保存在一个变量foo
,并且我们能够对它进行调用foo()
。
不过它会正常的运行吗?我们先来看看作用域规则。
所有的东西都在python的作用域规则下进行工作:“x
是函数outer
里的一个局部变量。
当函数inner
在#1处打印x
的时候,python解释器会在inner
内部查找相应的变量,当然会找不到,
所以接着会到封闭作用域里面查找,并且会找到匹配。
但是从变量的生存周期来看,该怎么理解呢?
我们的变量x
是函数outer
的一个本地变量,这意味着只有当函数outer
正在运行的时候才会存在。
根据我们已知的python运行模式,我们没法在函数outer
返回之后继续调用函数inner
,在函数inner
被调用的时候,变量x
早已不复存在,可能会发生一个运行时错误。
万万没想到,返回的函数inner
居然能够正常工作。
Python支持一个叫做函数闭包
的特性,用人话来讲就是:
嵌套定义在非全局作用域
里面的函数能够记住它在被定义的时候它所处的封闭命名空间。
这能够通过查看函数的func_closure
属性得出结论,这个属性里面包含封闭作用域里面的值
(只会包含被捕捉到的值,比如x
,如果在outer
里面还定义了其他的值,封闭作用域里面是不会有的)
记住,每次函数outer
被调用的时候,函数inner
都会被重新定义。
闭包用途:
# 用途1:当闭包执行完后,仍然能够保持住当前的运行环境。
# 比如说,如果你希望函数的每次执行结果,都是基于这个函数上次的运行结果。我以一个类似棋盘游戏的例子
# 来说明。假设棋盘大小为50*50,左上角为坐标系原点(0,0),我需要一个函数,接收2个参数,分别为方向
# (direction),步长(step),该函数控制棋子的运动。棋子运动的新的坐标除了依赖于方向和步长以外,
# 当然还要根据原来所处的坐标点,用闭包就可以保持住这个棋子原来所处的坐标。 origin = [0, 0] # 坐标系统原点
legal_x = [0, 50] # x轴方向的合法坐标
legal_y = [0, 50] # y轴方向的合法坐标
def create(pos=origin):
def player(direction,step):
# 这里应该首先判断参数direction,step的合法性,比如direction不能斜着走,step不能为负等
# 然后还要对新生成的x,y坐标的合法性进行判断处理,这里主要是想介绍闭包,就不详细写了。
new_x = pos[0] + direction[0]*step
new_y = pos[1] + direction[1]*step
pos[0] = new_x
pos[1] = new_y
#注意!此处不能写成 pos = [new_x, new_y],原因在上文有说过
return pos
return player player = create() # 创建棋子player,起点为原点
print (player([1,0],10)) # 向x轴正方向移动10步
print (player([0,1],20)) # 向y轴正方向移动20步
print (player([-1,0],10)) # 向x轴负方向移动10步
用途1
# 用途2:闭包可以根据外部作用域的局部变量来得到不同的结果,这有点像一种类似配置功能的作用,我们可以
# 修改外部的变量,闭包根据这个变量展现出不同的功能。比如有时我们需要对某些文件的特殊行进行分析,先
# 要提取出这些特殊行。 def make_filter(keep):
def the_filter(file_name):
file = open(file_name)
lines = file.readlines()
file.close()
filter_doc = [i for i in lines if keep in i]
return filter_doc
return the_filter # 如果我们需要取得文件"result.txt"中含有"pass"关键字的行,则可以这样使用例子程序
filter = make_filter("pass")
filter_result = filter("result.txt")
用途2
装饰器需要掌握知识:
- 作用域知识(LEGB)
函数知识
函数名可以作为参数输入
- 函数名可以作为返回值
- 闭包
- 在内部函数里,对外部作用域的变量引用,那么内部函数就是一个闭包
写代码要遵循开发封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:
封闭:已实现的功能代码块
开放:对扩展开发
如果需要在 函数执行前 额外执行其他功能的话,我们就可以用到装饰器来实现~
def index():
print("Hello !")
return True index()
如果我们需要在函数 f1执行前先输出一句 Start ,函数执行后输出一句 End ,那么我们可以这样做:
def outer(func):
def inner():
print("Start")
result = func()
print("End")
return result
return inner @outer # index = outer(index)
def index():
print("Hello !")
return True result = index() # Start
# Hello !
# End
当写完这段代码后(函数未被执行、未被执行、未被执行),python解释器就会从上到下解释代码,步骤如下:
1.先把 def outer(func) 函数加载到内存
2.执行@outer
执行@outer 时 , 先把 index 函数 加载到内存 ! 并且内部会执行如下操作:
1.执行 outer 函数,将 index 作为参数传递 ( 此时 func = index )
2.将 outer 函数返回值 ( return inner ),重新赋值给 index (index = inner)
然后执行下面的 result = index() => 相当于 执行了 inner() 函数!!!
问题:如果被装饰的函数如果有参数呢?
def outer(func):
def inner(a1):
print("start")
result = func(a1)
print("end")
return result
return inner def index(a1):
print(a1)
return True result = index(1)
print(result)
一个参数
def outer(func):
def inner(a1,a2):
print("start")
result = func(a1,a2)
print("end")
return result
return inner def index(a1,a2):
a3 = a1 + a2
print(a3)
return True result = index(1,2)
print(result)
两个参数
问题:可以装饰具有处理n个参数的函数的装饰器吗?
def outer(func):
def inner(*args,**kwargs):
print("start")
result = func(*args,**kwargs)
print("end")
return result
return inner @outer
def index1(a1,):
print(a1)
return True @outer
def index2(a1,a2):
print(a1,a2)
return True @outer
def index3(a1,a2,a3):
print(a1,a2,a3)
return True index1(5)
index2(5,6)
index3(5,6,7)
多层装饰器
问题:一个函数可以被多个装饰器装饰吗?
def outer_0(func):
def inner(*args,**kwargs):
print("0.5")
result = func(*args,**kwargs)
return result
return inner def outer_1(func):
def inner(*args,**kwargs):
print("123")
result = func(*args,**kwargs)
print("456")
return result
return inner @outer_0
@outer_1
def index(a1,a2):
print(a1,a2)
return True index(1,2)
1.先把 outer_0 、outer_1 和 index 加载到内存
2.执行 @outer_0 时, func = @outer_1 和 index 函数的结合体
3.然后执行 @outer_0 的 inner , 其中包含了 @outer_1 和index => 所以先执行 @outer_1
4.当执行 @outer_1 时, func = index , 并执行 inner -> 再执行 func 即 index 函数
(原理同 只有一个装饰器一样 , 可以 把 @outer_1 和 def index 看成一个 结成的函数 => 当作只有一个 装饰器@outer_0)
带参数的装饰器
装饰器还有更大的灵活性,例如带参数的装饰器:在上面的装饰器调用中,比如@show_time,
该装饰器唯一的参数就是执行业务的函数。
装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。
import time
def time_logger(flag=0): def show_time(func): def wrapper(*args,**kwargs):
start_time=time.time()
func(*args,**kwargs)
end_time=time.time()
print('spend %s'%(end_time-start_time)) if flag:
print('将这个操作的时间记录到日志中') return wrapper return show_time @time_logger(3)
def add(*args,**kwargs):
time.sleep(1)
sum=0
for i in args:
sum+=i
print(sum) add(2,7,5)
@time_logger(3) 做了两件事:
(1)time_logger(3):得到闭包函数show_time,里面保存环境变量flag
(2)@show_time :add=show_time(add)
上面的time_logger是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器(一个含有参数的闭包函数)。
当我们使用@time_logger(3)调用的时候,Python能够发现这一层的封装,并把参数传递到装饰器的环境中。
类装饰器
再来看看类装饰器,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。
使用类装饰器还可以依靠类内部的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。
import time
class Foo(object):
def __init__(self, func):
self._func = func def __call__(self):
start_time=time.time()
self._func()
end_time=time.time()
print('spend %s'%(end_time-start_time)) @Foo #bar=Foo(bar)
def bar():
print ('bar')
time.sleep(2) bar() #bar=Foo(bar)()>>>>>>>没有嵌套关系了,直接active Foo的 __call__方法
注意 :
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,:
我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。
from functools import wraps def logged(func): @wraps(func)
def wrapper(*args, **kwargs):
print (func.__name__ + " was called")
return func(*args, **kwargs)
return wrapper @logged
def cal(x):
return x + x * x print(cal.__name__) #cal
【Python之路】特别篇--Python装饰器的更多相关文章
- python之路(7)装饰器
前言 装饰器:为函数添加附属功能,本质为函数 原则:不修改被修饰函数的源代码 不修改被修饰函数的调用方式 装饰器=高阶函数+函数嵌套+闭包 使用场景演示 定义下面函数 def cal(l): res ...
- Python进阶【第九篇】装饰器
什么是装饰器 装饰器本身就是函数,并且为其他函数添加附加功能 装饰器的原则:1.不修改被装饰对象的源代码 2.不修改被装饰对象的调用方式装饰器=高阶函数+函数嵌套+闭包 # res=timmer(t ...
- python之路《八》装饰器
装饰器是个好东西啊 那么装饰器是个什么样的东西呢,他又能做些什么呢? 1.为什么装饰器 当我们一个程序已经构建完成,并且已经发布出去了,但是现在需要增加一个活动,例如淘宝给你发送一个今日优惠,或者开启 ...
- Python之路(第九篇)Python文件操作
一.文件的操作 文件句柄 = open('文件路径+文件名', '模式') 例子 f = open("test.txt","r",encoding = “utf ...
- Python菜鸟之路:Python基础-逼格提升利器:装饰器Decorator
一.装饰器 装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理等. 装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身 ...
- Python之命名空间、闭包、装饰器
一.命名空间 1. 命名空间 命名空间是一个字典,key是变量名(包括函数.模块.变量等),value是变量的值. 2. 命名空间的种类和查找顺序 - 局部命名空间:当前函数 - 全局命名空间:当前模 ...
- Python基础(七) python自带的三个装饰器
说到装饰器,就不得不说python自带的三个装饰器: 1.@property 将某函数,做为属性使用 @property 修饰,就是将方法,变成一个属性来使用. class A(): @prope ...
- Python之面向对象:闭包和装饰器
一.闭包 1. 如果一个函数定义在另一个函数的作用域内,并且引用了外层函数的变量,则该函数称为闭包. def outter(): name='python' def inner(): print na ...
- Python进阶(七)----带参数的装饰器,多个装饰器修饰同一个函数和递归简单案例(斐波那契数列)
Python进阶(七)----带参数的装饰器,多个装饰器修饰同一个函数和递归简单案例(斐波那契数列) 一丶带参数的装饰器 def wrapper_out(pt): def wrapper(func): ...
随机推荐
- Android控制UI界面
⒈使用XML布局文件控制UI界面[推荐] Android推荐使用XML布局文件来控制视图,这样不仅简单.明了,而且可以将应用的视图控制逻辑从Java或Kotlin代码中分离出来,放入XML文件中控制, ...
- Qt5笔记(一)
1. 只要指定父对象,直接或间接继承于QObect,那么子对象如果是动态分配空间,不需要手动释放内存,系统会自动释放.( Qt的内存回收机制) 2. 想要查看某个函数,可以F1进入帮助文档,连 ...
- (0)c++入门——认识指针与数组——指针即是内存中地址。
初识指针 首先需要了解一个概念,计算机的内存(或者说是寄存器)都是有地址的. <c++ primer plus>一书P37中提到这样一个概念:为把信息存储在计算机中,程序必须记录3个基本属 ...
- Redis服务端相关
全局命令: 查看所有键: keys * 键总数: dbsize 检查键是否存在: exists key 删除键: del key [key...] 键过期: expire key seconds 键的 ...
- 关于vs code文本编辑器的快捷键
另一篇编辑器Sublime Text下载.使用教程.插件推荐说明.全套快捷键 基础编辑 快捷键 作用 Ctrl+X 剪切 Ctrl+C 复制 Ctrl+Shift+K 删除当前行 Ctrl+Enter ...
- 空格 ACSII码 160 32
ascii160和ascii32都表示空格,但是在IE里,160就不是显示空格,firefox里会显示空格,32不管是firefox里,还是IE里都显示空格.
- C# List.sort排序(多权重,升序降序)
很多人可能喜欢Linq的orderBy排序,可惜U3D里面linq在Ios上会报错,所以就必须使用list的排序. 其实理解了并不难 升序降序比较 sort有三种结果 1,-1,0分别是大,小,相等. ...
- Linux Permission denied 问题
Linux Permission denied 问题 来源 https://www.cnblogs.com/sparkdev/p/10287164.html 如果当前用户没有某个文件的写权限,又要通 ...
- Laravel 查询或写入Enum字段出错的bug解决办法
查询: if($request->filled('type')){ $where[] = ['type', strval(intval($request->input('type')))] ...
- Laravel 实现多级控制器(实现Api区分版本)
路由: Route::get('', 'v1\\UserController@index'); 文件夹分层 User控制器命名空间: namespace App\Http\Controllers\v1 ...