- 装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。
- 它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。
- 抽离出大量与函数功能本身无关的雷同代码并继续重用, 装饰器的作用就是为已经存在的函数或对象添加额外的功能
函数
def foo():
return 1
print(foo())
作用域
- 在 Python 函数中会创建一个新的作用域或者称函数自己的命名空间(局部变量/全局变量)。
- 当在函数体中遇到变量时,Python 会首先在该函数的命名空间中寻找变量名。
a_string = "This is a global variable"
def foo():
print(locals())
print(globals()) # doctest: +ELLIPSIS
foo() # 2
# {'foo':... 'a_string': 'This is a global variable',...}
# {}
内建函数 globals 返回一个包含所有 Python 能识别变量的字典。在注释 #2 处,调用了 foo 函数,在函数中打印局部变量的内容。从中可以看到,函数 foo 有自己单独的、此时为空的命名空间。
Python 的作用域规则是, 变量的创建总是会创建一个新的局部变量但是变量的访问(包括修改)在局部作用域查找然后是整个外层作用域来寻找匹配
函数是对象
函数在 Python 中,和其他任何东西一样都是对象(类也是对象)。在 Python 中函数只是常规的值,就像其他任意类型的值一样。这意味着可以将函数当做实参传递给函数,或者在函数中将函数作为返回值返回
>>> def add(x, y):
... return x + y
>>> def sub(x, y):
... return x - y
>>> def apply(func, x, y): # 1
... return func(x, y) # 2
>>> print apply(add, 2, 1) # 3
>>> print apply(sub, 2, 1)
函数可以内嵌函数
Python 允许创建内嵌函数。即可以在函数内部声明函数,并且所有的作用域和生命周期规则仍然适用。
>>> def outer():
... x = 1
... def inner():
... print x # 1
... inner() # 2
>>> outer()
装饰器
- 装饰器其实就是一个以函数作为参数并返回一个替换函数的可执行函数
def outer(func):
def inner():
print("before func")
ret = func() # 1
return ret + 1
return inner
def foo():
return 1
decorated = outer(foo) # 2
print(decorated())
- decorated: 这个变量是对装饰器函数 inner 的引用。它本身是一个函数对象,等价于 inner 函数,可以像调用函数一样使用它,但并没有执行该函数。在这里,decorated 是被装饰函数 foo 的装饰器,它包含了 foo 函数的逻辑,并在其前后执行了额外的操作。
- decorated(): 这是对装饰器函数 inner 的调用
装饰器的作用就是新函数封装旧函数,旧函数在代码不变的情况下,返回加新功能,返回新函数
def hello():
return "hello world!"
增加功能,但是不想改变函数hello
def hello():
return "hello world!"
def makeitalic(fun): # makitalic传了一个新函数
def wrapped(): # 内部函数
return "<i>" + fun() + "</i>" # 要加的新功能
return wrapped # 返回的是wrapped函数功能
# makeitalic里面传入了hello函数,然后内部函数fun()函数也就相当于hello函数了
hello_2 = makeitalic(hello)
# 打印新函数,返回的就是<i>hello world!</i>
print(hello_2())
- 为了增强原函数hello的功能,定义了一个函数,它接收原函数作为参数,并返回一个新的函数,在这个返回的函数中,执行了原函数,并对原函数的功能进行了增强
- makeitalic就是一个装饰器(decorator),它封装了原函数hello,并返回了一个新函数,用于增强原函数的功能,并将其赋值给hello
装饰器提供的@语法糖(Syntactic Sugar),来简化上面的操作
####使用@语法糖
def makeitalic(fun):
def wrapped():
return "<i>" + fun() + "</i>"
return wrapped
@makeitalic # 使用了装饰器可以直接调用,不需要赋值了
def hello():
return "hello world"
print(hello()) # 使用了装饰器可以直接调用,不需要赋值了
- 动态的修改函数(或类的)功能的函数就是装饰器。本质上,它是一个高阶函数,以被装饰的函数(比如上面的hello)为参数,并返回一个包装后的函数(比如上面的wrapped)给被修饰函数(hello)
分类
实践和参考:见参考第一条
普通装饰器
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
@log
def now():
print('2015-3-25')
now()
# call now():
# 2015-3-25
- python解释器在执行now()之前,首先加载环境,加载内存中对应的程序
- 找到后,自上而下执行,于是先执行 @log,即执行: now = log(now)
- 参数func和now 此时指向同一个对象
def now():
print('2015-3-25')
- 解释器继续向下读取程序,直到遇到return返回: 调用的是log函数,log内部的wrapper函数(闭包)
此时并没有被调用,return语句
return wrapper
,log函数将wrapper
返回 - 返回wrapper,然后赋值给now,now变量指向的对象从上面的代码变为了下面的代码
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
- 解释器执行完@log,接下来读取到now()语句
- 调用func,解释器找到func指向的程序
def now():
print('2015-3-25')
- 执行
print('2015-3-25')
- now()执行结束
带参数的装饰器
def logging(flag):
def decorator(fn):
def inner(num1, num2):
if flag == "+":
print("--正在努力加法计算--")
elif flag == "-":
print("--正在努力减法计算--")
result = fn(num1, num2)
return result
return inner
return decorator
@logging("+") # @logging("+") ==> @decorator
def add(a, b):
return a + b
@logging("-") # @logging("-") ==> @decorator
def sub(a, b):
return a - b
result = add(1, 2)
print(result)
result = sub(1, 2)
print(result)
# --正在努力加法计算--
# 3
# --正在努力减法计算--
# -1
多装饰器
离函数最近的装饰器先装饰,然后外面的装饰器再进行装饰,由内到外的装饰过程
def make_div(func):
"""对被装饰的函数的返回值 div标签"""
def inner(*args, **kwargs):
return "<div>" + func() + "</div>"
return inner
def make_p(func):
"""对被装饰的函数的返回值 p标签"""
def inner(*args, **kwargs):
return "<p>" + func() + "</p>"
return inner
@make_div
@make_p
def content():
return "人生苦短"
result = content()
print(result)
# <div><p>人生苦短</p></div>
类装饰器
基于类装饰器的实现,必须实现__call__和__init__两个内置函数
- init :接收被装饰函数
- call :实现装饰逻辑
class Check(object):
def __init__(self, fn):
self.__fn = fn # 初始化操作在此完成
def __call__(self, *args, **kwargs): # 实现__call__方法,表示对象是一个可调用对象,可以像调用函数一样进行调用
print("请登陆...")
self.__fn()
@Check # @Check <===> comment = Check(comment)
def comment():
print("正在发表评论")
comment()
可自定义属性的装饰器
带可选参数的装饰器
装饰器强制的函数的类型检查
from inspect import signature
from functools import wraps
def typeassert(*ty_args, **ty_kwargs):
def decorate(func):
# If in optimized mode, disable type checking
if not __debug__:
return func
# Map function argument names to supplied types
sig = signature(func)
bound_types = sig.bind_partial(*ty_args, **ty_kwargs).arguments
@wraps(func)
def wrapper(*args, **kwargs):
bound_values = sig.bind(*args, **kwargs)
# Enforce type assertions across supplied arguments
for name, value in bound_values.arguments.items():
if name in bound_types:
if not isinstance(value, bound_types[name]):
raise TypeError(
'Argument {} must be {}'.format(name, bound_types[name])
)
return func(*args, **kwargs)
return wrapper
return decorate
@wraps(func) 是一个装饰器,它用于保留被装饰函数的元数据(metadata),包括函数名、文档字符串、参数列表等。在 Python 中,装饰器会导致原始函数的一些元信息丢失,比如函数的名称会变成装饰器函数的名称,文档字符串会变成装饰器函数的文档字符串。为了避免这种情况,可以使用 @wraps 装饰器来解决。
具体来说,@wraps(func) 应用于装饰器内部的 wrapper 函数之前,它会将 wrapper 函数的属性设置为与被装饰函数 func 相同的属性。这样,当调用 wrapper 函数时,它会像被装饰函数一样表现,并且保留了被装饰函数的名称、文档字符串等信息,从而提高了代码的可读性和可维护性。
在给装饰器函数添加 @wraps(func) 装饰器之后,被装饰函数的元数据将会被正确保留,不会被装饰器函数覆盖,这对于调试和文档生成非常有用
参考
9.2 创建装饰器时保留函数元信息 — python3-cookbook 3.0.0 文档 data_structure/python知识记录/python 装饰器的原理.md at master · CANYOUFINDIT/data_structure
评论区