0%

python中装饰器的作用

1. 函数

1.1 函数是python中的一等对象

函数可以作为别的函数的参数、函数的返回值,赋值给变量或存储在数据结构中。

函数作为参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def say_hello(name):
return f"Hello {name}"

def be_awesome(name):
return f"Yo {name}, together we are the awesomest!"

def greet_bob(greeter_func):
return greeter_func("Bob")

>>>greet_bob(say_hello)
'Hello Bob'

>>> greet_bob(be_awesome)
'Yo Bob, together we are the awesomest!'

函数作为返回值

1
2
3
4
5
6
7
8
9
10
11
def parent(num):
def first_child():
return "Hi, I am Emma"

def second_child():
return "Call me Liam"

if num == 1:
return first_child
else:
return second_child

1.2 内置函数

在一个函数里面定义的函数称为内置函数(inner Functions)。内置函数只能在父函数内使用,不能在函数外使用。

1
2
3
4
5
6
7
8
9
10
11
def parent():
print("Printing from the parent() function")

def first_child():
print("Printing from the first_child() function")

def second_child():
print("Printing from the second_child() function")

second_child()
first_child()

2. 装饰器

2.1 装饰器实现

如下是一个简单的装饰器实现代码,函数my_decorator传入一个函数func再返回函数wrapperwrapperfunc的功能进行了增加。通过代码say_whee = my_decorator(say_whee)进行了装饰。所以简单来说,装饰器对一个函数进行包装,来修改他的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper

def say_whee():
print("Whee!")

say_whee = my_decorator(say_whee)

>>> say_whee()
Something is happening before the function is called.
Whee!
Something is happening after the function is called.

2.2 语法糖(Syntactic Sugar)

通过say_whee = my_decorator(say_whee)进行装饰有一点麻烦,所以在函数定义时通过@进行修饰,如下。

1
2
3
4
5
6
7
8
9
10
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper

@my_decorator
def say_whee():
print("Whee!")

@my_decorator可以看作say_whee = my_decorator(say_whee)

2.2.3 带参数的装饰器

内置装饰函数wrapper_do_twice使用*args, **kwargs获取任意数量的参数,再传给func

1
2
3
4
5
6
7
8
9
10
11
12
def do_twice(func):
def wrapper_do_twice(*args, **kwargs):
func(*args, **kwargs)
func(*args, **kwargs)
return wrapper_do_twice
>>> say_whee()
Whee!
Whee!

>>> greet("World")
Hello World
Hello World

2.2.4 装饰器的返回值

以上定义的修饰器wrapper_do_twice并没有返回值,所以如果装饰的函数有返回值就无法得到返回的结果。通过一下修改使修饰后的函数返回值和之前的一致。

1
2
3
4
5
6
7
8
9
def do_twice(func):
def wrapper_do_twice(*args, **kwargs):
func(*args, **kwargs)
return func(*args, **kwargs)
return wrapper_do_twice
>>> return_greeting("Adam")
Creating greeting
Creating greeting
'Hi Adam'

2.2.5 保留函数的信息

由于包装后的函数是wrapper_do_twice,函数名的等信息都修改为wrapper_do_twice的信息。

1
2
3
4
5
6
7
8
9
10
>>> say_whee
<function do_twice.<locals>.wrapper_do_twice at 0x7f43700e52f0>

>>> say_whee.__name__
'wrapper_do_twice'

>>> help(say_whee)
Help on function wrapper_do_twice in module decorators:

wrapper_do_twice()

为了解决这个问题,使用functools.wraps修饰修饰器,这将保留有关原始功能的信息。

1
import functoolsdef do_twice(func):    @functools.wraps(func)    def wrapper_do_twice(*args, **kwargs):        func(*args, **kwargs)        return func(*args, **kwargs)    return wrapper_do_twice

2. 装饰器的高级使用方法

2.1 类中的装饰器

一、类中修饰器的一种用法是装饰类中的函数,有一些常用的python内置的修饰器。

1、@property

修饰函数可以作为属性使用,与所定义的属性配合使用,这样可以防止属性被修改。

1
class House:	def __init__(self, price):		self._price = price	@property	def price(self):		return self._price		@price.setter	def price(self, new_price):		if new_price > 0 and isinstance(new_price, float):			self._price = new_price		else:			print("Please enter a valid price")	@price.deleter	def price(self):		del self._price

按照惯例,在python中当在变量名前加一个下划线时,意味着告诉其他开发人员不应直接在类外访问或者修改改变量。

  • @property 获取属性。

  • @price.setter 属性设定,可以用来限制属性的范围等等。

  • @price.deleter 定义属性的删除,在del house.price时被执行。

2、@abstractmethod

抽象方法表示基类的一个方法,没有实现,所以基类不能实例化,子类实现了该抽象方法才能被实例化

3、@classmethod

classmethod声明方法为类方法,直接通过 类||实例.类方法()调用。经过@classmethod修饰的方法,不需要self参数,但是需要一个标识类本身的cls参数。

1
class T:    @classmethod    def class_test(cls):#必须有cls参数        print "i am a class method"if __name__ == "__main__":    T.class_test()    T().class_test()

4、@staticmethoed

声明方法为静态方法,直接通过 类||实例.静态方法()调用。经过@staticmethod修饰的方法,不需要self参数,其使用方法和直接调用函数一样。

二、另一种用法是装饰整个类,装饰器接收的是一个类而不是一个函数,如下装饰器等同于PlayingCard = dataclass(PlayingCard)

1
from dataclasses import dataclass@dataclassclass PlayingCard:    rank: str    suit: str

2.2 多个装饰器装饰一个函数

可以将几个装饰器堆叠在一起,将它们叠在一起,从而将它们应用到一个函数上。

1
from decorators import debug, do_twice@debug@do_twicedef greet(name):    print(f"Hello {name}")

2.3 带参数的装饰器

能将参数传递给装饰器是很有用的,比如我们可以给@do_twice扩展为@repeat(num_times)。注意代码多内置了一层函数传参,可以理解为repeat(num_times)返回的函数再来装饰func

1
def repeat(num_times):    def decorator_repeat(func):        @functools.wraps(func)        def wrapper_repeat(*args, **kwargs):            for _ in range(num_times):                value = func(*args, **kwargs)            return value        return wrapper_repeat    return decorator_repeat@repeat(num_times=4)def greet(name):    print(f"Hello {name}")

2.4 带参数和不带参数共存的装饰器

为了让装饰器既可以带参数,又可以不带参数。通过*号使除了func以外的所有参数都仅限为关键字。

1
def name(_func=None, *, kw1=val1, kw2=val2, ...):  # 1    def decorator_name(func):        ...  # Create and return a wrapper function.    if _func is None:        return decorator_name                      # 2    else:        return decorator_name(_func)               # 3
1
def repeat(_func=None, *, num_times=2):    def decorator_repeat(func):        @functools.wraps(func)        def wrapper_repeat(*args, **kwargs):            for _ in range(num_times):                value = func(*args, **kwargs)            return value        return wrapper_repeat    if _func is None:        return decorator_repeat    else:        return decorator_repeat(_func)

2.5 记录状态的装饰器

装饰器可以跟踪函数的状态,如下是一个记录函数调用次数的装饰器,通过函数属性来计数。

1
import functoolsdef count_calls(func):    @functools.wraps(func)    def wrapper_count_calls(*args, **kwargs):        wrapper_count_calls.num_calls += 1        print(f"Call {wrapper_count_calls.num_calls} of {func.__name__!r}")        return func(*args, **kwargs)    wrapper_count_calls.num_calls = 0    return wrapper_count_calls@count_callsdef say_whee():    print("Whee!")

记录状态的最好的方式是使用类作为装饰器,只需要实现__init__()__call__()。使用functools.update_wrapper(self, func)保留信息。

1
import functoolsclass CountCalls:    def __init__(self, func):        functools.update_wrapper(self, func)        self.func = func        self.num_calls = 0    def __call__(self, *args, **kwargs):        self.num_calls += 1        print(f"Call {self.num_calls} of {self.func.__name__!r}")        return self.func(*args, **kwargs)@CountCallsdef say_whee():    print("Whee!")

3. 修饰器的一些实际应用

修饰器的模板

1
import functoolsdef decorator(func):    @functools.wraps(func)    def wrapper_decorator(*args, **kwargs):        # Do something before        value = func(*args, **kwargs)        # Do something after        return value    return wrapper_decorator

3.1 Timing Functions

@timer装饰器,记录函数执行的时间。

1
import functoolsimport timedef timer(func):    """Print the runtime of the decorated function"""    @functools.wraps(func)    def wrapper_timer(*args, **kwargs):        start_time = time.perf_counter()    # 1        value = func(*args, **kwargs)        end_time = time.perf_counter()      # 2        run_time = end_time - start_time    # 3        print(f"Finished {func.__name__!r} in {run_time:.4f} secs")        return value    return wrapper_timer@timerdef waste_some_time(num_times):    for _ in range(num_times):        sum([i**2 for i in range(10000)])>>> waste_some_time(999)Finished 'waste_some_time' in 0.3260 secs

3.2 Debugging Code

@debug装饰器,输出函数的输入和输出。

1
import functoolsdef debug(func):    """Print the function signature and return value"""    @functools.wraps(func)    def wrapper_debug(*args, **kwargs):        args_repr = [repr(a) for a in args]                      # 1        kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]  # 2        signature = ", ".join(args_repr + kwargs_repr)           # 3        print(f"Calling {func.__name__}({signature})")        value = func(*args, **kwargs)        print(f"{func.__name__!r} returned {value!r}")           # 4        return value    return wrapper_debug@debugdef make_greeting(name, age=None):    if age is None:        return f"Howdy {name}!"    else:        return f"Whoa {name}! {age} already, you are growing up!">>> make_greeting("Benjamin")Calling make_greeting('Benjamin')'make_greeting' returned 'Howdy Benjamin!''Howdy Benjamin!'

3.3 Registering Plugins

装饰器不必包装他们正在装饰的功能,它们还可以简单地注册一个函数的存在,并将其解封返回。如下是创建轻量级插件架构的代码。

1
import randomPLUGINS = dict()def register(func):    """Register a function as a plug-in"""    PLUGINS[func.__name__] = func    return func@registerdef say_hello(name):    return f"Hello {name}"@registerdef be_awesome(name):    return f"Yo {name}, together we are the awesomest!"def randomly_greet(name):    greeter, greeter_func = random.choice(list(PLUGINS.items()))    print(f"Using {greeter!r}")    return greeter_func(name)

3.4 创建单例(Singletons)

单例是一种设计模式,单例模式保证了在程序的不同位置都可以且仅可以取到同一个对象实例。通过保存实例使得每次返回的是同一个对象实例。

1
import functoolsdef singleton(cls):    """Make a class a Singleton class (only one instance)"""    @functools.wraps(cls)    def wrapper_singleton(*args, **kwargs):        if not wrapper_singleton.instance:            wrapper_singleton.instance = cls(*args, **kwargs)        return wrapper_singleton.instance    wrapper_singleton.instance = None    return wrapper_singleton@singletonclass TheOne:    pass

3.4 实现缓存和记忆机制

通过记录状态来保存之前的计算结果。

1
import functoolsfrom decorators import count_callsdef cache(func):    """Keep a cache of previous function calls"""    @functools.wraps(func)    def wrapper_cache(*args, **kwargs):        cache_key = args + tuple(kwargs.items())        if cache_key not in wrapper_cache.cache:            wrapper_cache.cache[cache_key] = func(*args, **kwargs)        return wrapper_cache.cache[cache_key]    wrapper_cache.cache = dict()    return wrapper_cache@cache@count_callsdef fibonacci(num):    if num < 2:        return num    return fibonacci(num - 1) + fibonacci(num - 2)

3.5 添加额外的信息

通过在装饰函数时给函数附加属性来添加信息。

1
def set_unit(unit):    """Register a unit on a function"""    def decorator_set_unit(func):        func.unit = unit        return func    return decorator_set_unitimport math@set_unit("cm^3")def volume(radius, height):    return math.pi * radius**2 * height>>> volume(3, 5)141.3716694115407>>> volume.unit'cm^3'

4. 补充知识

1、语法糖

Syntactic sugar,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。

2、闭包

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是在支持头等函数的编程语言中实现词法绑定的一种技术。闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。环境里是若干对符号和值的对应关系,它既要包括约束变量(该函数内部绑定的符号),也要包括自由变量(在函数外部定义但在函数内被引用),有些函数也可能没有自由变量。闭包跟函数最大的不同在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即便脱离了捕捉时的上下文,它也能照常运行。捕捉时对于值的处理可以是值拷贝,也可以是名称引用,这通常由语言设计者决定,也可能由用户自行指定(如C++)。

3、python中的一等对象

头等函数(first-class function)是指在程序设计语言中,函数被当作头等公民。这意味着,函数可以作为别的函数的参数、函数的返回值,赋值给变量或存储在数据结构中。

4、面向切面编程

面向切面的程序设计(Aspect-oriented programming,AOP,又译作面向方面的程序设计、剖面导向程序设计)是计算机科学中的一种程序设计思想,旨在将横切关注点与业务主体进行进一步分离,以提高程序代码的模块化程度。通过在现有代码基础上增加额外的通知(Advice)机制,能够对被声明为“切点(Pointcut)”的代码块进行统一管理与装饰,如“对所有方法名以‘set*’开头的方法添加后台日志”。该思想使得开发人员能够将与代码核心业务逻辑关系不那么密切的功能(如日志功能)添加至程序中,同时又不降低业务代码的可读性。面向切面的程序设计思想也是面向切面软件开发的基础。

5、高阶函数(Higher-order function)

如果返回的结果也是函数的函数。

6、*args, **kwargs

*args接受参数,**kwargs接收键值对的参数。

7、单例模式

A singleton is a class with only one instance.

8、实例方法、类方法、静态方法

实例方法需要传入self,类方法需要传入cls参数,静态方法无需传入self参数或者是cls参数。

1
class MyClass(object):    # 实例方法    def instance_method(self):        print('instance method called', self)        # 类方法    @classmethod    def class_method(cls):        print('class method called', cls)        # 静态方法    @staticmethod    def static_method():        print('static method called')
  • 实例方法,self参数指向的是实例化出的实例对象。

  • 类方法,cls参数指向的是一开始定义的类对象,不是实例对象。
  • 静态方法,调用时并不需要传递类或者实例,不会与类或者实例绑定。

Reference

[1] Python装饰器详解 - 知乎 (zhihu.com)

[2] 理解Python装饰器(Decorator) - 简书 (jianshu.com)

[3] 装饰器 - 廖雪峰的官方网站 (liaoxuefeng.com)

[4] 闭包 (计算机科学) - 维基百科,自由的百科全书 (wikipedia.org)

[5] 面向切面的程序设计 - 维基百科,自由的百科全书 (wikipedia.org)

[6] [The @property Decorator in Python: Its Use Cases, Advantages, and Syntax (freecodecamp.org)](https://www.freecodecamp.org/news/python-property-decorator/)

[7] Primer on Python Decorators – Real Python

[8] Higher-order function - Wikipedia

[9] Python 常用装饰器 - 知乎 (zhihu.com)

[10] [Python]实例方法、类方法、静态方法 - 知乎 (zhihu.com)

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

欢迎关注我的其它发布渠道