Python装饰器全解析:从作用域、闭包到高级应用
本文深入解析Python装饰器原理,从全局与局部作用域、高级函数特性入手,详细阐述闭包函数的定义与条件。通过无参、有参及多层装饰器的实例,展示如何在不修改源码和调用方式的前提下扩展函数功能,并解析语法糖@的使用及执行顺序。
一.作用域
在Python中,变量的作用域主要分为两种:全局作用域和局部作用域。
全局作用域指的是在文件层级定义的变量或函数名;而局部作用域则是指定义在函数内部的变量或名称。
理解作用域时,需要掌握以下两个核心要点:
a. 在全局作用域中,无法直接访问局部作用域内定义的变量。
b. 在局部作用域中,可以读取全局作用域定义的变量,但默认情况下不能直接修改全局变量(尽管存在特定方法可以修改)。
接下来通过具体实例进行说明:
x = 1 def funx(): x = 10 print(x) # 打印出10 funx() print(x) # 打印出1
如果局部作用域中未定义变量x,函数内部会遵循从内到外的顺序查找x;若最终未找到,程序将抛出错误。
x = 1
def funx():
print(x)
funx()
print(x) # 打印出1
x = 1
def funx():
def func1():
print(x)
func1()
funx()
print(x) # 打印出1
因此,关于作用域的规则可以总结为两点:
全局变量可以在文件的任何位置被引用,但修改操作必须在全局作用域中进行;如果在局部作用域中未找到所需变量,解释器会向外层作用域查找,若仍找不到则报错。
二.高级函数
在Python中,函数名本质上是指向内存地址的引用。既然函数名是地址,我们就可以利用这一特性来实现更灵活的功能。
def delete(ps):
import os
filename = ps[-1]
delelemetns = ps[1]
with open(filename, encoding='utf-8') as f_read,\
open('tmp.txt', 'w', encoding='utf-8') as f_write:
for line in iter(f_read.readline, ''):
if line != '\n': # 处理非空行
if delelemetns in line:
line = line.replace(delelemetns,'')
f_write.write(line)
os.remove(filename)
os.rename('tmp.txt',filename)
def add(ps):
filename = ps[-1]
addelemetns = ps[1]
with open(filename, 'a', encoding='utf-8') as fp:
fp.write("\n", addelemetns)
def modify(ps):
import os
filename = ps[-1]
modify_elemetns = ps[1]
with open(filename, encoding='utf-8') as f_read, \
open('tmp.txt', 'w', encoding='utf-8') as f_write:
for line in iter(f_read.readline, ''):
if line != '\n': # 处理非空行
if modify_elemetns in line:
line = line.replace(modify_elemetns, '')
f_write.write(line)
os.remove(filename)
os.rename('tmp.txt', filename)
def search(cmd):
filename = cmd[-1]
pattern = cmd[1]
with open(filename, 'r', encoding="utf-8") as f:
for line in f:
if pattern in line:
print(line, end="")
else:
print("没有找到")
dic_func ={'delete': delete, 'add': add, 'modify': modify, 'search': search}
while True:
inp = input("请输入您要进行的操作:").strip()
if not inp:
continue
cmd_1 = inp.split()
cmd = cmd_1[0]
if cmd in dic_func:
dic_func[cmd](cmd_1)
else:
print("Error")
例如,将函数名作为字典的值,可以实现对文本数据的增、删、查、改操作。
b. 函数名可以作为返回值返回。
def outer():
def inner():
pass
return inner
s = outer()
print(s)
######输出结果为#######
<function outer.<locals>.inner at 0x000000D22D8AB8C8>
c. 函数名可以作为参数传递给其他函数。
def index():
print("index func")
def outer(index):
s = index
s()
outer(index)
######输出结果#########
index func
只要满足上述任意一个条件,该函数即可被称为高级函数。
三.闭包函数
闭包函数的定义必须同时满足两个条件:1. 函数内部定义了另一个函数;2. 内部函数引用了外部作用域(非全局作用域)的变量。
下面通过几个实例来具体说明闭包函数的概念:
实例一:以下代码仅在函数内部定义了一个函数,但这并不构成闭包函数。
def outer():
def inner():
print("inner func excuted")
inner() # 调用执行inner()函数
print("outer func excuted")
outer() # 调用执行outer函数
####输出结果为##########
inner func excuted
outer func excuted
实例二:以下代码在函数内部定义了一个函数,并引用了外部变量x,这是否是闭包函数?答案是否定的。
x = 1
def outer():
def inner():
print("x=%s" %x) # 引用了一个非inner函数内部的变量
print("inner func excuted")
inner() # 执行inner函数
print("outer func excuted")
outer()
#####输出结果########
x=1
inner func excuted
outer func excuted
回顾闭包函数的定义,检查是否满足两条标准?细心的读者会发现,这里不满足第二条。因为变量x属于全局变量,而非外部作用域的变量。请查看下面的例子:
def outer():
x = 1
def inner():
print("x=%s" %x)
print("inner func excuted")
inner()
print("outer func excuted")
outer()
#####输出结果#########
x=1
inner func excuted
outer func excuted
显然,上面的实例满足了闭包函数的所有条件。现在你应该清楚,一个闭包函数必须同时满足上述两个条件,缺一不可。不过,通常情况下,我们会给闭包函数返回一个值。至于为什么,将在后续内容中揭示该返回值的用途。
def outer():
x = 1
def inner():
print("x=%s" %x)
print("inner func excuted")
print("outer func excuted")
return inner # 返回内部函数名
outer()
现在我们来抽象地定义闭包函数。它是函数与其相关的引用环境组合而成的实体。在实现深约束时,需要创建一个能显式表示引用环境的对象,并将其与相关的子程序捆绑在一起,这个捆绑体即成为闭包。从上面的实例中可以看出,闭包函数必须包含自身的函数定义以及一个外部变量,才能真正称为闭包函数。如果没有外部变量与其绑定,则该函数不能被称为闭包函数。
那么,如何判断一个闭包函数引用了多少个外部变量呢?请看下面的代码。
def outer():
x = 1
y = 2
def inner():
print("x= %s" %x)
print("y= %s" %y)
print(inner.__closure__)
return inner
outer()
######输出结果#######
(<cell at 0x000000DF9EA965B8: int object at 0x000000006FC2B440>, <cell at 0x000000DF9EA965E8: int object at 0x000000006FC2B460>)
结果表明,在inner函数内部引用了两个外部局部变量。如果引用的是非局部变量(即全局变量),这里输出的结果为None。
闭包函数的特点包括:1. 自带作用域;2. 支持延迟计算。
那么闭包函数有什么实际作用呢?我们知道,闭包函数在定义时一定会绑定一个外部环境,这个整体才构成闭包函数。我们可以利用这种绑定特性来完成某些特殊功能。
实例三:根据传入的URL下载页面源码。
from urllib.request import urlopen
def index(url)
def get()
return urlopen(url).read()
return get
python = index("http://www.python.org") # 返回的是get函数的地址
print(python()) # 执行get函数《并且将返回的结果打印出来
baidu = index("http://www.baidu.com")
print(baidu())
有人可能会质疑,这个例子不满足闭包函数的条件,因为没有引用非全局的外部变量。其实并非如此。之前我们提到,函数内部的变量都属于该函数。在index(url)中,url参数也属于函数内部变量,只是我们省略了显式绑定的步骤。因此,上述函数同样属于闭包函数。
四.装饰器
基于以上基础知识,理解装饰器就变得容易了。
装饰器的定义:外部函数接收被装饰函数的名作为参数,内部函数返回装饰后的函数名。
装饰器的特点:1. 不修改被装饰函数的调用方式;2. 不修改被装饰函数的源代码。
a. 无参装饰器
以下实例中,我们需要计算代码执行的时间。
import time, random
def index():
time.sleep(random.randrange(1, 5))
print("welcome to index page")
根据装饰器的特点,我们不能直接修改index()函数,且调用方式必须保持不变。此时,我们可以使用装饰器来实现上述功能。
import time, random
def outer(func): # 将index的地址传递给func
def inner():
start_time = time.time()
func() # fun = index 即func保存了外部index函数的地址
end_time = time.time()
print("运行时间为%s"%(end_time - start_time))
return inner # 返回inner的地址
def index():
time.sleep(random.randrange(1, 5))
print("welcome to index page")
index = outer(index) # 这里返回的是inner的地址,并重新赋值给index
index()
然而,有些情况下被装饰的函数需要传递参数,而有些函数不需要参数。如何处理这种变参函数的情况?下面来看看有参装饰器的使用示例。
b. 有参装饰器
def outer(func): # 将index的地址传递给func
def inner(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs) # fun = index 即func保存了外部index函数的地址
end_time = time.time()
print("运行时间为%s"%(end_time - start_time))
return inner # 返回inner的地址
下面介绍一些其他情况的实例。
如果被装饰的函数有返回值,该如何处理?
def timmer(func):
def wrapper(*args,**kwargs):
start_time = time.time()
res=func(*args,**kwargs) #res来接收home函数的返回值
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return res
return wrapper
def home(name):
time.sleep(random.randrange(1,3))
print('welecome to %s HOME page' %name)
return 123123123123123123123123123123123123123123
这里补充一点,如果要执行被装饰后的函数,调用方式应如下所示:
home = timmer(home) # 等式右边返回的是wrapper函数的内存地址,再将其赋值给home。此时的home不再是原来的函数,而是被装饰后的新函数。
对于像home = timmer(home)这样的写法,Python提供了一个便捷的方式——语法糖@。
以后,我们只需在被装饰函数的定义前加上@timmer,其效果与home = timmer(home)完全相同。
如果一个函数被多个装饰器装饰,其执行顺序是怎样的?
import time
import random
def timmer(func):
def wrapper():
start_time = time.time()
func()
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return wrapper
def auth(func):
def deco():
name=input('name: ')
password=input('password: ')
if name == 'egon' and password == '123':
print('login successful')
func() #wrapper()
else:
print('login err')
return deco
@auth # index = auth(timmer(index))
@timmer # index = timmer(index)
def index():
time.sleep(3)
print('welecome to index page')
index()
实验结果表明,多个装饰器装饰一个函数时,执行顺序是从下往上。
总结
以上所述是Python装饰器的详细实例解析,希望对大家有所帮助。如果大家有任何疑问,请留言,小编会及时回复。在此也非常感谢大家对本站网站的支持!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













