Python装饰器

什么是python装饰器?

顾名思义,从字面意思就可以理解,它是用来"装饰"Python的工具,使得代码更具有Python简洁的风格。换句话说,它是一种函数的函数,因为装饰器传入的参数就是一个函数,然后通过实现各种功能来对这个函数的功能进行增强。

为什么要使用装饰器?

装饰器是通过某种方式来增强函数的功能。当然,我们可以通过很多方式来增强函数的功能,只是装饰器有一个无法替代的优势--简洁。只需要在每个函数上方加一个@就可以对这个函数进行增强。

装饰器(Decorator)是Python中一个重要部分,它本质上是一个函数,不同于普通函数,装饰器的返回值是一个函数对象。通过利用装饰器,我们可以让其他函数在不做任何代码改动的情况下增加额外的功能,同时也能够让代码更加简洁。

装饰器

装饰器是Python的一种高级函数,它可以接受一个函数作为参数,并返回一个新的函数。通过使用装饰器,我们可以在不修改原函数代码的情况下,为函数添加额外的功能或行为。

基本定义

装饰器的定义格式如下:

def decorator_function(original_function):
    def wrapper_function(*args, **kwargs):
        # 在调用原函数之前的额外操作
        result = original_function(*args, **kwargs)
        # 在调用原函数之后的额外操作
        return result
    return wrapper_function

在上述示例中,decorator_function是装饰器函数,它接受一个原函数original_function作为参数,并返回一个新的函数wrapper_function。wrapper_function内部可以执行一些在调用原函数之前或之后的额外操作。‍

def a_new_decorator(a_func):
    def wrapTheFunction():
        print("I am doing some boring work before executing a_func()")
        a_func()
        print("I am doing some boring work after executing a_func()")
 
    return wrapTheFunction
 
def a_function_requiring_decoration():
    print("I am the function which needs some decoration to remove my foul smell")
 
a_function_requiring_decoration()
#outputs: "I am the function which needs some decoration to remove my foul smell"
 
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
#now a_function_requiring_decoration is wrapped by wrapTheFunction()
 
a_function_requiring_decoration()
#outputs:I am doing some boring work before executing a_func()
#        I am the function which needs some decoration to remove my foul smell
#        I am doing some boring work after executing a_func()

上面的例子演示了装饰器的工作过程,Python提供了语法糖来简化装饰器,在函数定义时加上@装饰器,在函数被调用时,自动会调用装饰器返回的函数

@a_new_decorator
def a_function_requiring_decoration():
    """Hey you! Decorate me!"""
    print("I am the function which needs some decoration to "
          "remove my foul smell")
 
a_function_requiring_decoration()
#outputs: I am doing some boring work before executing a_func()
#         I am the function which needs some decoration to remove my foul smell
#         I am doing some boring work after executing a_func()
 
#the @a_new_decorator is just a short way of saying:
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)

原函数参数

如果被装饰器包装的函数带有参数,需要通过*args, **kwargs来传递参数,*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 say_hello(name):
    print(f'Hello {name}!')

say_hello('Jack’)

‘’’
Hello Jack!
Hello Jack!
’‘’

装饰器返回值

装饰器如果要返回原函数的返回值,需要用一个返回变量接收原函数的返回值

def log_foo(func):
    def wrapper(*args, **kwargs):
        print(f'func() start...')
        result = func(*args, **kwargs)
        print(f'func() end.')
        return result
    return wrapper

@log_foo
def say_hello(name):
    print(f'Hello {name}!')

say_hello('Jack’)

‘’'
func() start...
Hello Jack!
func() end.
‘''

保留原函数信息

函数携带的一些基本信息,例如函数名、函数文档等,我们可以通过func.__name__获取函数名、可以通过func.__doc__获取函数的文档信息,用户也可以通过注解等方式为函数添加元信息。

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

def log_foo(func):
    def wrapper(*args, **kwargs):
        '''
        自定义日志装饰器
        '''
        print(f'func() start...')
        result = func(*args, **kwargs)
        print(f'func() end.')
        return result
    return wrapper

@log_foo
def say_hello(name):
    '''
    say_hello函数
    '''
    print(f'Hello {name}!')

print(say_hello.__name__) 
print(say_hello.__doc__) 
print(help(say_hello)) 

‘’’
wrapper

        自定义日志装饰器
        
Help on function wrapper in module __main__:

wrapper(*args, **kwargs)
    自定义日志装饰器

None
’‘’

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

import functools
def log_foo(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        '''
        自定义日志装饰器
        '''
        print(f'func() start...')
        result = func(*args, **kwargs)
        print(f'func() end.')
        return result
    return wrapper

@log_foo
def say_hello(name):
    '''
    say_hello函数
    '''
    print(f'Hello {name}!')

print(say_hello.__name__)
print(say_hello.__doc__)
print(help(say_hello))

‘’’
say_hello

    say_hello函数
    
Help on function say_hello in module __main__:

say_hello(name)
    say_hello函数

None
’‘’

多个装饰器装饰

可以多个装饰器同时装饰一个函数

def dec_foo1(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f'--dec_foo1() start--')
        result = func(*args, **kwargs)
        print(f'--dec_foo1() end--')
        return result

    return wrapper


def dec_foo2(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f'--dec_foo2() start--')
        result = func(*args, **kwargs)
        print(f'--dec_foo2() end--')
        return result

    return wrapper

@dec_foo1
@dec_foo2
def say_hello(name):
    '''
    say_hello函数
    '''
    print(f'Hello {name}!')

say_hello('Rose’)

‘’'
--dec_foo1() start--
--dec_foo2() start--
Hello Rose!
--dec_foo2() end--
--dec_foo1() end--
‘''

装饰器函数包装的顺序是从上往下。

 

带参数的装饰器

能将参数传递给装饰器是很有用的,比如我们可以给@dec_foo1()扩展为@dec_foo1(num_times)

注意代码多内置了一层函数传参(需要嵌套3层函数,前面的都是2层),通过闭包的形式将参数num_times传入内层函数,可以理解为dec_foo1(num_times)返回的函数再来装饰func

def dec_foo1(num_times):
    def decorator_repeat(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print(f'--dec_foo1() start--')
            for _ in range(num_times):
                result = func(*args, **kwargs)
            print(f'--dec_foo1() end--')
            return result
        return wrapper
    return decorator_repeat

@dec_foo1(4)
def say_hello(name):
    print(f'Hello {name}!')

say_hello('Rose’)

‘’'
--dec_foo1() start--
Hello Rose!
Hello Rose!
Hello Rose!
Hello Rose!
--dec_foo1() end--
‘''

装饰器类

前面介绍类对象只要实现了__call__也可以作为函数对象被调用,函数对象也可以用来作为装饰器。

类的装饰器在@声明的时候需要使用cls()来声明,因为这样才能正确的生成一个函数对象,不能简单的使用类名来声明装饰器,那样会出错TypeError

from functools import wraps
 
class logit(object):
    def __init__(self, logfile='out.log'):
        self.logfile = logfile
 
    def __call__(self, func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called"
            print(log_string)
            # 打开logfile并写入
            with open(self.logfile, 'a') as opened_file:
                # 现在将日志打到指定的文件
                opened_file.write(log_string + '\n')
            # 现在,发送一个通知
            self.notify()
            return func(*args, **kwargs)
        return wrapped_function
 
    def notify(self):
        # logit只打日志,不做别的
        pass

@logit()   #将类实例化
def myfunc1():
    pass

myfunc1()

并且通过函数对象实现装饰器,有一个优势,便是可以提供更多的预置的变量。

给 logit 创建子类,来添加 email 的功能。

class email_logit(logit):
    '''
    一个logit的实现版本,可以在函数调用时发送email给管理员
    '''
    def __init__(self, email='[email protected]', *args, **kwargs):
        self.email = email
        super(email_logit, self).__init__(*args, **kwargs)
 
    def notify(self):
        # 发送一封email到self.email
        # 这里就不做实现了
        pass

@email_logit() 将会和 @logit() 产生同样的效果,但是在打日志的基础上,还会多发送一封邮件给管理员。

常见用法

类的装饰器

from dataclasses import dataclass

@dataclass
class PlayingCard:
    rank: str
    suit: str

类方法装饰器

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

@property

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

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时被执行。

@abstractmethod

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

@classmethod

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

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

@staticmethoed

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

记录状态的装饰器

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

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

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

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

@timer

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

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(999)
#Finished 'waste_some_time' in 1.6487 secs

@debug

@debug装饰器,输出调试信息,帮助程序员分析问题。

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

授权

装饰器能有助于检查某个人是否被授权去使用一个web应用的端点(endpoint)。它们被大量使用于Flask和Django web框架中。这里是一个例子来使用基于装饰器的授权:

from functools import wraps
 
def requires_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if not auth or not check_auth(auth.username, auth.password):
            authenticate()
        return f(*args, **kwargs)
    return decorated

日志

日志是装饰器运用的另一个亮点

rom functools import wraps
 
def logit(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging
 
@logit
def addition_func(x):
   """Do some math."""
   return x + x
 
 
result = addition_func(4)
# Output: addition_func was called

另外一个例子:

import logging
from functools import partial
​
def wrapper_property(obj, func=None):
    if func is None:
        return partial(wrapper_property, obj)
    setattr(obj, func.__name__, func)
    return func
​
def logger_info(level, name=None, message=None):
    def decorate(func):
        logmsg = message if message else func.__name__
        def wrapper(*args, **kwargs):
            log.log(level, logmsg)
            return func(*args, **kwargs)
​
        @wrapper_property(wrapper)
        def set_level(newlevel):
            nonlocal level
            level = newlevel
​
        @wrapper_property(wrapper)
        def set_message(newmsg):
            nonlocal logmsg
            logmsg = newmsg
​
        return wrapper
​
    return decorate
​
​
@logger_info(logging.WARNING)
def main(x, y):
    return x + y

main(3, 3)
​
# 输出
# WARNING:Test:main
# 6

main.set_level(logging.ERROR)
main(5, 5)

# 输出
# ERROR:Test:main
# 10

这里面最重要的是wrapper_property这个函数,它的功能是把一个函数func变成一个对象obj的属性,然后通过调用wrapper_property,给装饰器添加了两个属性set_messageset_level,分别用于改变输出日志的内容和改变输出日志的等级。

 

Registering Plugins

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

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)

创建单例(Singletons)

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

import functools

def 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

@singleton
class TheOne:
    pass

实现缓存和记忆机制

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

import functools
from decorators import count_calls

def 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_calls
def fibonacci(num):
    if num < 2:
        return num
    return fibonacci(num - 1) + fibonacci(num - 2)

添加额外的信息

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

def set_unit(unit):
    """Register a unit on a function"""
    def decorator_set_unit(func):
        func.unit = unit
        return func
    return decorator_set_unit

import math

@set_unit("cm^3")
def volume(radius, height):
    return math.pi * radius**2 * height

>>> volume(3, 5)
141.3716694115407

>>> volume.unit
'cm^3'

你可能感兴趣的:(python,开发语言)