python装饰器教程

Functions

下面是一个十分简单的函数,给定输入,返回对应的值

def add_one(number):
    return number + 1

>>> add_one(2)
3

First-class objects

在python中,函数也可以当成是一个对象,那就意味着函数可以当作参数进行传递,就像整型,字符型,列表类型的对象一样,参考下面的例子:

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!'

say_hello()和be_awesome()都是常规的函数,greet_bob()则需要一个函数作为输入。
本质上来说,对于greet_bob(say_hello),say_hello是没有括号的,这代表仅仅是函数的引用进行传递,不会执行say_hello函数,而greet_bob()函数带括号,所以它会执行

Inner Functions

在一个函数内部再定义一个函数也是可行的,看如下一个例子

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

需要注意的是inner function定义的顺序并不重要,而且innner function只在其父函数parrent()中有效。

return functions from functions

python允许将函数作为返回值,看下面这个例子

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
<function __main__.parent.<locals>.first_child()>

>>> second
<function __main__.parent.<locals>.second_child()>

>>> first()
'Hi, I am Emma'

>>> second()
'Call me Liam'

Simple Decorators

有了上面的基础知识以后,下面来学习下装饰器的基本用法

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.

>>> say_whee
<function __main__.my_decorator.<locals>.wrapper()>

实际上,say_whee指向的是wrapper()函数,因为在调用my_decorator的时候返回的wrapper函数

在看下面一个例子,因为wrapper()是一个常规的Python函数,装饰器修改函数的方式可以动态改变。

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)

>>>say_whee()
 Whee!
    

syntatic sugar

Python允许使用@符号以更简单的方式使用装饰器,看如下一个例子

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!")

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

所以, @my_decorator等价于 say_whee = my_decorator(say_whee).
这就是装饰器的使用方式

Reusing Decorators

装饰器本质上也只是一个常规的python函数,我们可以重复使用它方便开发。看如下一个例子

def do_twice(func):
    def warpper_do_twice():
        func()
        func()
    return warpper_do_twice

@do_twice
def say_whee():
    print("whee")

>>> say_whee()
whee
whee

Decorating Functions with Arguments

对一个带有参数的函数进行装饰,可以在inner wrapper functions中使用args和**kwargs ,用于接收任意位置的位置参数和关键字参数

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

@do_twice
def greet(name):
    print(f"Hello {name}")
    
>>>greet('world')
Hello world
Hello world

returning values from decorated functions

当被装饰的函数中有返回值该如何处理呢,这取决于装饰器的写法,看如下的例子

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

>>> hi_adam = return_greeting('Adam')
Creating greeting
Creating greeting

>>> print(hi_adam)
None
    

从结果上看,装饰器吞噬了函数的返回值。由于do_twice_warpper()没有显示的返回值,所以return_greeting(‘Adam’)返回的是空值。
为了解决这个问题,需要保证,wrapper function返回了被装饰函数的返回值。看如下的例子

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}"
    
>>> return_greeting("Adam")
Creating greeting
Creating greeting
'Hi Adam'

who are you, really

打印出函数的名称和相关文档

>>> print
<function print>

>>> print.__name__
>'print'

>>> help(print)
    Help on built-in function print in module builtins:
    
    print(...)
        print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
        
        Prints the values to a stream, or to sys.stdout by default.
        Optional keyword arguments:
        file:  a file-like object (stream); defaults to the current sys.stdout.
        sep:   string inserted between values, default a space.
        end:   string appended after the last value, default a newline.
        flush: whether to forcibly flush the stream.
    
>>> say_whee
 <function __main__.do_twice.<locals>.warpper_do_twice()>

>>> say_whee.__name__
'warpper_do_twice'

>>> help(say_whee)
Help on function warpper_do_twice in module __main__:
warpper_do_twice()
    

see_whee函数经过装饰之后,see_whee的函数名称发生了改变。它现在报告是do_twice()装饰器中的wrapper_do_twice()内部函数。虽然技术上是正确的,但这不是非常有用的信息。
为了解决这问题,装饰器应该使用@ functools.wraps装饰器,它将保留有关原始功能的信息

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 say_whee():
    print("whee")

>>> say_whee
 <function __main__.say_whee()>

>>> say_whee.__name__ 
'say_whee'

很好! 现在 say_whee()在经过装饰之后仍然是它本身.

A Few Real World Examples

下面是一个通用装饰器的一个模板

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

下面来看几个装饰器的典型例子

Timing Functions

# example1
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)
Finished 'waste_some_time' in 0.0057 secs

>>> waste_some_time(99)
Finished 'waste_some_time' in 0.7334 secs

Debugging functions

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('bob')
Calling make_greeting('bob')
'make_greeting' returned 'Howdy bob!'
'Howdy bob!'

Slowing Down Code

import functools
import time

def slow_down(func):
    """sleep 1 second before calling function"""
    def warpper_slow_down(*args, **kwargs):
        time.sleep(1)
        return func(*args, **kwargs)
    return warpper_slow_down

@slow_down
def countdown(from_number):
    if from_number < 1:
        print("Liftoff!")
    else:
        print(from_number)
        countdown(from_number - 1)

>>> countdown(3)
3
2
1
Liftoff!

Fancy Decorators

Decorating Classes

可以通过两种不同的方式对类进行装饰。
第一种方式和上文用装饰器装饰函数十分类似。我们可以装饰类的方法。
在Python中内置了一些常用的装饰器, 例如@classmethod, @staticmethod和@property,
@classmethod和@staticmethod装饰器用于定义类名称空间内未连接到该类的特定实例方法
@property装饰器用户为类属性自定义getter和setter

具体用法如下:

class TimeWaster:
    @debug
    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(1000)
Calling __init__(<__main__.TimeWaster object at 0x000001FCD67DE208>, 1000)
'__init__' returned None
    
>>> tw.waste_time(99)
Finished 'waste_time' in 0.0671 secs    

在类上使用装饰器的另一种方法是装饰整个类

@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(99)

对一个类进行装饰并不会装饰它的方法,回想下,装饰器的本质@timer只是
TimeWaster = timer(TimeWaste)的缩写

Nesting Decorators

可以通过将多个装饰器堆叠在一起来应用他们

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

>>>greet('Johnson')
Calling greet('Johnson')
Hello Johnson
Hello Johnson
'greet' returned None
    

装饰器按它们列出的顺序执行。换句话说,@ debug调用@do_twice,调用greet()或debug(do_twice(greet()))

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

>>> greet("Johnson")
Calling greet('Johnson')
Hello Johnson
'greet' returned None
Calling greet('Johnson')
Hello Johnson
'greet' returned None

Decorators with Arguments

有时,将参数传递给装饰器很有用。例如,@do_twice可以扩展为@repeat(num_times)装饰器。然后可以将执行修饰函数的次数作为参数

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}")

>>> greet('Johnson')
Hello Johnson
Hello Johnson
Hello Johnson
Hello Johnson
    

repeat装饰器看起来有些混乱,但是为了处理装饰器的参数,只需要用一个额外的def进行处理即可。

Stateful Decorators

import functools

def 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_calls
def say_whee():
    print("Whee!")

>>> say_whee()
Call 1 of 'say_whee'
Whee!

>>> say_whee()
Call 2 of 'say_whee'
Whee!

Classes as Decorators

还有一种方式是可以用类装饰器来保存状态。回想下之前的内容,@my_decorator等价于func=my_decorator(func), 因此,如果my_decorator是一个类的话,需要在_init_()方法中添加func作为参数,此外还需要_call_()方法,这样该类可以被调用,起到装饰功能

class Counter:
    def __init__(self, start=0):
        self.count = start

    def __call__(self):
        self.count += 1
        print(f"Current count is {self.count}")

counter = Counter()
>>> counter()
Current count is 1
    
>>> counter()
Current count is 2
import functools

class 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)

@CountCalls
def say_whee():
    print("Whee!")
    
>>>say_whee()
Call 1 of 'say_whee'
Whee!
    
>>> say_whee()
Call 2 of 'say_whee'
Whee!

_init_()方法必须存储对函数的引用,并且可以执行任何其他必要的初始化.
调用_call_()方法而不是装饰函数。它与我们前面示例中的wrapper()函数基本相同
需要使用functools.update_wrapper()函数而不是@ functools.wraps

references

https://realpython.com/primer-on-python-decorators/#both-please-but-never-mind-the-bread

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值