TAG : **PYTHON**
STATE : **FINISHED**

1. 函数

Reference: https://www.yuque.com/petwan/toolchain/data-classes

在理解装饰器之前,先看一个函数的实例

  1. def add_one(number):
  2. return number + 1
  3. add_one(2)

在python中,万物都是对象,函数也是,函数作为对象可以赋值给一个变量,可以作为元素添加到集合对象中,也可以作为参数传递给其他函数,同时还可以当作函数的返回值,以上这些特性就是第一类对象所特有的。

1.1 函数是对象

函数作为一个对象,拥有对象模型的三个通用属性:

  • id
  • type
  • value ```python def add_one(number): return number + 1

id(add_one) # id number type(add_one) #

foo = add_one # 赋值给另一个变量,函数并不会被调用,仅仅是在函数对象上绑定了一个新的名字而已

foo(2) 等同于 add_one(2)

  1. <a name="Sj1j3"></a>
  2. ## 1.2 高阶函数
  3. 函数接受一个或多个函数作为输入/输出时,我们称之为高阶函数,典型的高阶函数是map函数。
  4. ```python
  5. def foo(text):
  6. return len(text)
  7. lens = map(foo, ['hello', 'world!!!!'])
  8. print(list(lens))
  9. # [5, 9]
  10. # ----------------------------
  11. # map函数的作用相当于
  12. # [foo(i) for i in ["hello","world"]]

1.3 函数嵌套

Python允许函数嵌套,但是嵌套的函数不能在函数外部访问,只能在函数内部使用。

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()
parent()
# ----------------
Printing from the parent() function
Printing from the second_child() function
Printing from the first_child() function

1.4 返回函数

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
first = parent(1)
second = parent(2)
first()
# "Hi, I am Emma"
second()
# "Call me Liam"

1.5 实现call的类

对于自定义的类,如果实现了call方法,那么该类的实例对象的行为就是一个函数,是一个可以被调用callable的对象

class Add:
    def __init__(self, n):
        self.n = n
    def __call__(self):
        return self.n + x
add = Add(1)
add(4)
# 5

2. 简单的装饰器

下面先实现一个简单的装饰器

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_where()
# ---------------------
Something is happening before the function is called.
Whee!
Something is happening after the function is called.

可以看到,利用my_decorator函数实现了对某个func的调用。

接下来,我们再看一个实例

from datetime import datetime

def not_during_the_night(func):
    def wrapper():
        if 7 <= datetime.now().hour < 22:
            func()
        else:
            pass  # Hush, the neighbors are asleep
    return wrapper

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

say_whee = not_during_the_night(say_whee)

如果在22点到7点之间调用函数,什么都不执行。

2.1 Syntactic Sugar

上面的例子中,如果要实现对say_whee()函数的调用,我们需要在对应的函数中加入say_whee作为函数输入。
为了简化,我们利用@语法进行改写:

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)
可以将自定义的装饰器统一放到一个module中,通过import的形式进行使用。

# decorators.py
def do_twice(func):
    def wrapper_do_twice():
        func()
        func()
    return wrapper_do_twice
from decorators import do_twice

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

say_whee()
# -----------------
Whee!
Whee!

2.2 含参装饰函数

如果某个函数需要参数输入,对应的装饰函数如何写?
借助args, *kwargs实现

# decorators.py
def do_twice(func):
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        func(*args, **kwargs)
    return wrapper_do_twice
from decorators import do_twice

@do_twice
def say_whee(name):
    print("Hello {}".format(name))

say_whee("world")
# -------------------
Hello World
Hello World

2.3 从装饰函数返回值

如果想要让装饰函数返回值,如何实现?

def do_twice(func):
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        return func(*args, **kwargs)
    return wrapper_do_twice

@do_twice
def return_greeting(name):
    print("Creating greeting")
    return f"Hi {name}"

a = return_greeting("world")
print(a)
# ----------------------------
Creating greeting
Creating greeting
Hi world

2.4 函数元信息

使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了

def do_twice(func):
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        return func(*args, **kwargs)
    return wrapper_do_twice

@do_twice
def return_greeting(name):
    print("Creating greeting")
    return f"Hi {name}"

print(return_greeting.__name__)
# 显示结果不再是 return_greeting,而是 wrapper_do_twice

借助 functools 保留函数的元信息

import functools

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

@do_twice
def return_greeting(name):
    print("Creating greeting")
    return f"Hi {name}"

print(return_greeting.__name__)
# return_greeting

3. 举个例子

这里给出一个简单的实例

import functools

def 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 计时装饰函数

我们创建一个计时的装饰器,用于计算程序的耗时,并打印在console中。

import functools
import time

def 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

@timer
def waste_some_time(num_times):
    for _ in range(num_times):
        sum([i**2 for i in range(10000)])

waste_some_time(1)

上面的装饰器实现了计算程序耗时,这里仅作提示,实际上可以借助timeit模块进行程序耗时查看。

3.2 调试代码

import functools

def 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
@debug
def 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!'

也可以将该装饰函数应用到标准函数中

import math

# Apply a decorator to a standard library function
math.factorial = debug(math.factorial)

def approximate_e(terms=18):
    return sum(1 / math.factorial(n) for n in range(terms))

3.3 注册插件

import random
PLUGINS = dict()

def register(func):
    """
    Register a function as a plug-in
    """
    PLUGINS[func.__name__] = func
    return func

@register
def say_hello(name):
    return f"Hello {name}"

@register
def 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)

print(randomly_greet("Alice"))

这种方法的好处在于用户无需维护插件列表,仅需要使用@register进行注册即可。

3.4 嵌套

可以对某个函数进行多层装饰,按照从代码上到下的顺序进行嵌套

from decorators import debug, do_twice

@debug
@do_twice
def greet(name):
    print(f"Hello {name}")
greet("Eva")
# --------------
Calling greet('Eva')
Hello Eva
Hello Eva
'greet' returned None

如果修改嵌套顺序

from decorators import debug, do_twice

@do_twice
@debug
def greet(name):
    print(f"Hello {name}")

执行结果如下:

greet("Eva")
# ---------------
Calling greet('Eva')
Hello Eva
'greet' returned None
Calling greet('Eva')
Hello Eva
'greet' returned None

4. 类装饰器

要对类进行装饰,通常有两种方法:

  1. 装饰类中的某个方法
  2. 装饰整个类

一些常见的装饰器如 classmethodstaticmethodproperty。这里给一个例子:

class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value >= 0:
            self._radius = value
        else:
            raise ValueError("Raisus must be positive")

    @property
    def area(self):
        return self.pi() * self.radius ** 2

    def cylinder_volume(self, height):
        """Calculate volume of cylinder with circle as base"""
        return self.area * height

    @classmethod
    def unit_circle(cls):
        """Factory method creating a circle with radius 1"""
        return cls(1)

    @staticmethod
    def pi():
        return 3.1415926
c = Circle(5)
c.radius    # 5
c.area        # 78.5398163375
c.radius = 2
c.cylinder_volume(height=4)    # 50.2654816
Circle.unit_circle().raius    # 1

装饰某个方法

import functools
import time

def 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

class TimeWaster:
    def __init__(self, max_num):
        self.max_num = max_num

    @timer
    def waste_time(self, num_times):
        for _ in range(num_times):
            sum([i**2 for i in range(self.max_num)])
tw = TimeWaster(100)
tw.waste_time(999)
# Finished 'waste_time' in 0.0268 secs

装饰整个类

另一种方法是对整个类进行装饰,例如使用Python3.7中的dataclasses模块对类进行装饰。

from dataclasses import dataclass

@dataclass
class PlayingCard:
    rank: str
    suit: str

对类进行装饰和对函数进行装饰比较类似,只是装饰器接收的是一个类,而不是函数。
需要注意的是,对整个类进行装饰,仅在实例化这个类的时候进行装饰,而不装饰其他方法。

import functools
import time

def 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

@timer
class TimeWaster:
    def __init__(self, max_num):
        self.max_num = max_num

    def waste_time(self, num_times):
        for _ in range(num_times):
            sum([i**2 for i in range(self.max_num)])
tw = TimeWaster(100)
# ------------------
Finished 'TimeWaster' in 0.0000 secs

tw.waste_time(999)
# No timing consuming display