当前位置: 首页 > news >正文

Python装饰器

什么是python装饰器?

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

为什么要使用装饰器?

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

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

装饰器

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

基本定义

装饰器的定义格式如下:

def decorator_function(original_function):def wrapper_function(*args, **kwargs):# 在调用原函数之前的额外操作result = original_function(*args, **kwargs)# 在调用原函数之后的额外操作return resultreturn 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 wrapTheFunctiondef 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 resultreturn 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 resultreturn 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 resultreturn 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_hellosay_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 resultreturn wrapperdef 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 resultreturn 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 resultreturn wrapperreturn 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 wrapsclass logit(object):def __init__(self, logfile='out.log'):self.logfile = logfiledef __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_functiondef notify(self):# logit只打日志,不做别的pass@logit()   #将类实例化
def myfunc1():passmyfunc1()

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

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

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

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

常见用法

类的装饰器

from dataclasses import dataclass@dataclass
class PlayingCard:rank: strsuit: str

类方法装饰器

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

@property

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

class House:def __init__(self, price):self._price = price@propertydef price(self):return self._price@price.setterdef price(self, new_price):if new_price > 0 and isinstance(new_price, float):self._price = new_priceelse:print("Please enter a valid price")@price.deleterdef price(self):del self._price

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

  • @property 获取属性。
  • @price.setter 属性设定,可以用来限制属性的范围等等。
  • @price.deleter 定义属性的删除,在del house.price时被执行。

@abstractmethod

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

@classmethod

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

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

@staticmethoed

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

记录状态的装饰器

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

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

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

import functoolsclass CountCalls:def __init__(self, func):functools.update_wrapper(self, func)self.func = funcself.num_calls = 0def __call__(self, *args, **kwargs):self.num_calls += 1print(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 timedef timer(func):"""Print the runtime of the decorated function"""@functools.wraps(func)def wrapper_timer(*args, **kwargs):start_time = time.perf_counter()    # 1value = func(*args, **kwargs)end_time = time.perf_counter()      # 2run_time = end_time - start_time    # 3print(f"Finished {func.__name__!r} in {run_time:.4f} secs")return valuereturn 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 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]                      # 1kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]  # 2signature = ", ".join(args_repr + kwargs_repr)           # 3print(f"Calling {func.__name__}({signature})")value = func(*args, **kwargs)print(f"{func.__name__!r} returned {value!r}")           # 4return valuereturn 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 wrapsdef requires_auth(f):@wraps(f)def decorated(*args, **kwargs):auth = request.authorizationif not auth or not check_auth(auth.username, auth.password):authenticate()return f(*args, **kwargs)return decorated

日志

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

rom functools import wrapsdef 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 + xresult = 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 levellevel = newlevel
​@wrapper_property(wrapper)def set_message(newmsg):nonlocal logmsglogmsg = newmsg
​return wrapper
​return decorate
​
​
@logger_info(logging.WARNING)
def main(x, y):return x + ymain(3, 3)
​
# 输出
# WARNING:Test:main
# 6main.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__] = funcreturn 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 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.instancewrapper_singleton.instance = Nonereturn wrapper_singleton@singleton
class TheOne:pass

实现缓存和记忆机制

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

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

添加额外的信息

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

def set_unit(unit):"""Register a unit on a function"""def decorator_set_unit(func):func.unit = unitreturn funcreturn 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'

http://www.lryc.cn/news/147459.html

相关文章:

  • 【Spring】使用自定义注解方式实现AOP鉴权
  • Go几种读取配置文件的方式
  • 每日一题(反转链表)
  • 某人事系统架构搭建设计记录
  • uniapp 实现切换tab锚点定位到指定位置
  • 华纳云:ssh登录22号端口拒绝连接Ubuntu?
  • python conda实践 sanic框架gitee webhook实践
  • LeetCode——无重复的最长子串(中等)
  • 【SQL】关系模型与查询和更新数据
  • 【Centos8_配置单节点伪分布式Spark环境】
  • 【软考】系统集成项目管理工程师(三)信息系统集成专业技术知识①【16分】
  • 揭秘特权账号潜在风险,你中招了吗?
  • 线性代数的学习和整理13: 定义域,值域,到达域 和单射,满射,双射,反函数,逆矩阵
  • 深入MaxCompute -第十一弹 -QUALIFY
  • Mysql定时备份事件
  • 探索ClickHouse——安装和测试
  • 常用的css样式
  • 小兔鲜儿 - 微信登录
  • C++ Primer阅读笔记--对象移动(右值引用、移动迭代器和引用限定符的使用)
  • 【办公类-16-01-02】2023年度上学期“机动班下午代班的排班表——跳过周三、节日和周末”(python 排班表系列)
  • ChatGPT HTML JS Echarts实现热力图展示
  • JavaScript七小知
  • Ubuntu【系统环境下】【编译安装OpenCV】【C++调用系统opencv库】
  • AR界安卓在中国,Rokid引爆空间计算狂潮
  • 在 React 中如何使用定时器
  • Unity记录4.6-存储-第四阶段总结
  • 【Python】从入门到上头— 使用包、模块、安装第三方模块(7)
  • flutter和原生利用pigeon建立通道
  • TCP连接分析:探寻TCP的三次握手
  • gitHooks使用教程