首先,装饰器是Python的一个语法糖(通过@可以对将一个函数作用到另一个函数上),本篇主要围绕函数装饰器进行展开(也有类装饰器)。
文章总览
函数装饰器的特点
* 函数装饰器也是一个`可调用`的函数对象。 * 函数装饰器的参数是一个函数,`一般情况下,返回值也是一个函数`。 * 装饰器在`被装饰函数`导入时(创建时)运行。def timer(func):
print(f"装饰器装饰了{func.__name__}函数")
def clock(*args, **kwargs):
print(f"{func.__name__}开始运行了")
result = func(*args, **kwargs)
print(f"{func.__name__}运行完毕了")
return clock # 注意,返回的是一个函数对象
print(callable(timer)) # 验证装饰器函数是否属于可调用对象
@timer # 通过`@`语法糖实现函数的装饰功能
def hello():
print("你好")
hello()
print("程序运行完毕")
True
装饰器装饰了hello函数
hello开始运行了
你好
hello运行完毕了
程序运行完毕
装饰器概念解答
为什么装饰器装饰后被调用的仍然是原函数
这主要是@语法糖起的作用,它重新绑定了变量。
def timer(func):
print(f"装饰器装饰了{func.__name__}函数")
def clock(*args, **kwargs):
print(f"{func.__name__}开始运行了")
result = func(*args, **kwargs)
print(f"{func.__name__}运行完毕了")
return clock # 注意,返回的是一个函数对象
def hello():
print("你好")
hello()
当我们没有为函数添加装饰器的时候,运行结果是这样子的。

当时当我们为函数加入装饰器后。
@timer
def hello():
print("你好")

@实际上执行的是这样的一组操作。
def timer(func):
print(f"装饰器装饰了{func.__name__}函数")
def clock(*args, **kwargs):
print(f"{func.__name__}开始运行了")
result = func(*args, **kwargs)
print(f"{func.__name__}运行完毕了")
return clock # 注意,返回的是一个函数对象
def hello():
print("你好")
hello = timer(hello) # time() 相当于 @timer
hello()
所以,我们后面执行hello对象是,其实操作的并不是原始的函数,而是clock(*args, **kwargs)
装饰器的参数是如何决定的
从上一小节中我们说到,@改变了原始变量锁绑定的函数对象。
def timer(func):
def clock(x , y, z):
print(f"{func.__name__}开始运行了")
result = func(x, y)
print(f"{func.__name__}运行完毕了")
return result
return clock # 注意,返回的是一个函数对象
@timer
def hello(x, y):
return x + y
try:
hello(5, 6)
except Exception as e:
print(e)
hello(5, 6, 7)
装饰器装饰了hello函数
timer.<locals>.clock() missing 1 required positional argument: 'z'
hello开始运行了
hello运行完毕了
通过这段代码,我们可以清楚的看到hello变量实际指向的对象是clock(x , y, z),所以要以这个参数列表为准。
闭包
要想真正掌握解释器,我们就不能不掌握`闭包的作用`。 * 什么是闭包 > 闭包就是创造了一个位于全局作用域和函数局部作用域中间的自由变量 * 什么是自由变量? > 是人们给这个内容起的一个`术语`,作用是既不在局部作用域,也不处于全局作用域,可以起到函数运行时能够操作它,且可以避免在某些地方被修改导致的函数运行时出现BUG。 ```python def add(name): classes = [] """将某个人添加到班级中""" if name not in classes: classes.append(name) return "添加成功" else: return "学生已经存在" print(add("张三")) print(add("张三")) ``` 假设我们存在这样一个需求,你会发现,不论运行多少次`add("张三")`,结果永远是`添加成功`,这是因为此时`classes`这个变量是一个局部变量,它会在`add`函数开始运行时进行创建,运行完毕后就被销毁了,导致`无法持久化保存`。 那么如何解决这个问题呢?你可能会想到`全局变量`(很多时候我们也确实是这样做的),但是这并不是一种特别保险的做法,例如下面这样:classes = []
def add(name):
"""将某个人添加到班级中"""
if name not in classes:
classes.append(name)
return "添加成功"
else:
return "学生已经存在"
print(add("张三"))
print(add("张三"))
print(add("李四"))
classes = {} # 如果某个人在某段代码中更改了全局变量
try:
print(add("张三"))
except Exception as e:
print(e)
添加成功
学生已经存在
添加成功
'dict' object has no attribute 'append'
全局变量的优势在于这个变量在程序任何地方都是可用的,可以被操作的,但是这也在某些情况下称为了缺点,有些时候,如果对全局变量没有进行沟通,而直接更改,就很容易出现程序异常。
接下来就轮到我们本节的重磅任务登场了,它就是闭包。
classes = []
def clock():
classes = [] # 这里的classes就被称为自由变量
def add(name):
"""将某个人添加到班级中"""
if name not in classes:
classes.append(name)
return "添加成功"
else:
return "学生已经存在"
return add
A = clock()
print(A("张三"))
print(A("李四"))
classes = {}
print(A("张三"))
这个时候可以看到,无论我们是否改变全局变量,都不会影响到程序的正常运行,因为函数用的是自由变量(因为自由变量更靠近函数)。
__code__
classes = []
def clock():
classes = [] # 这里的classes就被称为自由变量
def add(name):
"""将某个人添加到班级中"""
if name not in classes:
classes.append(name)
return "添加成功"
else:
return "学生已经存在"
return add
A = clock()
print(A.__code__.co_varnames) # 保存的是返回函数中的局部变量
print(A.__code__.co_freevars) # 保存的是自由变量
__closure__
import random
def clock():
classes = [] # 这里的classes就被称为自由变量
students = {}
def add(name):
"""将某个人添加到班级中"""
if name not in classes:
classes.append(name)
students[name] = random.randint(1, 100)
return "添加成功"
else:
return "学生已经存在"
return add
A = clock()
A("张三")
A("李四")
print(A.__code__.co_freevars)
print(A.__closure__[0]) # 获取co_freevars中的第一个元素
print(A.__closure__[0].cell_contents) # 获取co_freevars中第一个元素的具体内容
print(A.__closure__[1].cell_contents) # 获取co_freevars中第二个元素的具体内容
global与nonlocal
当函数通过global引入全局变量时,函数内部可以修改全局变量。
global自然不用说,是将变量从全局进行引入,为什么要有global呢?因为全局变量是在任何地方都可以使用的,为什么还要引入呢?这就不得不提这样一段代码了。
num = 10
def add():
num = 100
return num
def count():
x = num + 10
return x
def error():
x = num + 10
num = x
return num
T = add()
print(T)
T1 = count()
print(T1)
try:
T2 = error()
except Exception as e:
print(e)
100
20
local variable 'num' referenced before assignment
error函数运行为什么会出错的,如果我们按着一步一步执行去分析,发现似乎是没有问题的,但是当我们把函数看为一个整体的话,就可以发现问题所在,函数在构造时会先将局部变量全部定义出来,而定义局部变量的标准就是变量 = xxx,所以,在error函数中,因为有num = x的存在,将num标记为了全局变量,但是当代码执行到x = num + 10时,num在函数内部是没有具体值的,由于将num标记为了局部变量,我们也没办法在去使用全局变量num,导致函数异常。
由此引发的问题还有,当我们在函数内部使用num = xxx的时候,Python会将num标记为局部变量,这也就导致了我们无法通过函数去改变全局变量的值,这个时候,我们就需要golbal登场了。
lis = []
def add():
global num
x = num + 100
num = x
return x
T = add()
print(T)
print(num)
nonlocal
我们一直global可以在函数内部修改全局变量,那么nonlocal的作用是什么呢?这里还需要再次引入闭包的内容。
def func():
num = 10
def count():
return num + 10
def error():
num += 100
return num
# return count
return error
f = func()
try:
print(f())
except Exception as e:
print(e)
# output: local variable 'num' referenced before assignment
可以看到,在闭包函数内部,只能使用自由变量,不能修改全局变量,如果我们想要修改自由变量,用global显然是不行的,因为global操作的是全局变量,这个时候我们就需要用到nonloacl,它的作用是:操作上一级出现的变量,error函数的上一级是func,则func函数中定义的变量是可以导入的。
def func():
num = 10
def count():
return num + 10
def error():
nonlocal num
num += 100
return num
# return count
return error
f = func()
print(f())
测验:
请问这样以下两段代码能否成功执行?
# 片段1
def func():
num = 10
def count():
def error():
nonlocal num
num += 10
return num
return error
return count
f = func()
print(f()())
# 片段2
num = 10
def func():
def count():
def error():
nonlocal num
num += 10
return num
return error
return count
f = func()
print(f()())
带参数的装饰器
假如我们希望给我们的装饰器设置一些默认的参数,来提升装饰函数的作用,我们该如何实现呢?import time
def run(func, sheep=3):
def runner(*args, **kwargs):
print("函数开始执行...")
start = time.time()
time.sleep(sheep)
result = func(*args, **kwargs)
print("函数执行用时:", int(time.time() - start))
print("函数运行结束")
return result
return runner
@run(sheep=5)
def PrintHello():
print("hello")
PrintHello()
可以尝试运行这样一段代码,TypeError: run() missing 1 required positional argument: 'func',结果应该是这样的,显示缺少一个func函数,让我们来尝试分析一下原因:
@语法糖可以自动将某个函数作为参数传入到另外一个函数中,但是当另外一个函数存在多余参数时,@语法糖并不知道哪个参数时函数,所以也就没办法直接进行传递了。
所以如果我们想要实现带参数的装饰器,就还需要在外面在包裹一层。
import time
def Start(sheep = 3):
def run(func):
def runner(*args, **kwargs):
print("函数开始执行...")
start = time.time()
time.sleep(sheep)
result = func(*args, **kwargs)
print("函数执行用时:", int(time.time() - start))
print("函数运行结束")
return result
return runner
return run
@Start(sheep=5)
def PrintHello():
print("hello")
PrintHello()
函数开始执行...
hello
函数执行用时: 5
函数运行结束
类装饰器
如前面带参数的装饰器,在使用起来其实也是不太方便的?一方面需要三层结构,`也会影响可读性`。所以我们直接通过类装饰器来修改其实现。import time
class Start:
def __init__(self, sheep = 3):
self.sheep = sheep
def __call__(self, func):
def runner(*args, **kwargs):
print("函数开始执行...")
start = time.time()
time.sleep(self.sheep)
result = func(*args, **kwargs)
print("函数执行用时:", int(time.time() - start))
print("函数运行结束")
return result
return runner
@Start(sheep=5)
def PrintHello():
print("hello")
PrintHello()