文章目录
乱七八糟Summary
类作为装饰器时, 被装饰方法名就是类的示例, 比如#5 里面的say, 就是logger类的实例
参考(很多关于装饰器的理解和整理来自一篇文章搞懂装饰器所有用法)
一篇文章搞懂装饰器所有用法
Python 工匠:使用装饰器的技巧
01. 装饰器语法糖
如果你接触 Python 有一段时间了的话,想必你对 @ 符号一定不陌生了,没错@
符号就是装饰器的语法糖
。
它放在一个函数开始定义的地方,它就像一顶帽子一样戴在这个函数的头上。和这个函数绑定在一起。在我们调用这个函数的时候,第一件事并不是执行这个函数,而是将这个函数做为参数传入它头顶上这顶帽子,这顶帽子我们称之为装饰函数 或 装饰器。
你要问我装饰器可以实现什么功能?我只能说你的脑洞有多大,装饰器就有多强大。
装饰器的使用方法很固定:
先定义一个装饰函数(帽子)(也可以用类、偏函数实现)
再定义你的业务函数、或者类(人)
最后把这顶帽子带在这个人头上
装饰器的简单的用法有很多,这里举两个常见的。
- 日志打印器 时间计时器
- 入门用法:日志打印器#
02.首先是日志打印器。
实现的功能:
- 在函数执行前,先打印一行日志告知一下主人,我要执行函数了。
- 在函数执行完,也不能拍拍屁股就走人了,咱可是有礼貌的代码,再打印一行日志告知下主人,我执行完啦。
Python 装饰器是一个非常强大的工具,它可以用于各种场景。让我们来看看一些常见的使用场景及其代码示例:
# 这是装饰函数
def logger(func):
def wrapper(*args, **kw):
print('我准备开始计算:{} 函数了:'.format(func.__name__))
# 真正执行的是这行。
func(*args, **kw)
print('啊哈,我计算完啦。给自己加个鸡腿!!')
return wrapper
假如,我的业务函数是,计算两个数之和。写好后,直接给它带上帽子。
@logger
def add(x, y):
print('{} + {} = {}'.format(x, y, x+y))
add(200, 50)
输出如下:
我准备开始计算:add 函数了:
200 + 50 = 250
啊哈,我计算完啦。给自己加个鸡腿!
03. 进阶用法:带参数的函数装饰器
通过上面简单的入门,你大概已经感受到了装饰的神奇魅力了。
不过,装饰器的用法远不止如此。我们今天就要把这个知识点讲透。
上面的例子,装饰器是不能接收参数的。其用法,只能适用于一些简单的场景。不传参的装饰器,只能对被装饰函数,执行固定逻辑。
如果你有经验,你一定经常在项目中,看到有的装饰器是带有参数的。
装饰器本身是一个函数,既然做为一个函数都不能携带函数,那这个函数的功能就很受限。只能执行固定的逻辑。这无疑是非常不合理的。而如果我们要用到两个内容大体一致,只是某些地方不同的逻辑。不传参的话,我们就要写两个装饰器。小明觉得这不能忍。
那么装饰器如何实现传参呢,会比较复杂,需要两层嵌套。
同样,我们也来举个例子。
我们要在这两个函数的执行的时候,分别根据其国籍,来说出一段打招呼的话。
def chinese():
print("我来自中国。")
def usa():
print("I am from America.")
在给他们俩戴上装饰器的时候,就要跟装饰器说,这个人是哪国人,然后装饰器就会做出判断,打出对应的招呼。
戴上帽子后,是这样的。
@say_hello("china")
def chinese():
print("我来自中国。")
@say_hello("america")
def usa():
print("I am from America.")
定义帽子
def say_hello(contry):
def wrapper(func):
def deco(*args, **kwargs):
if contry == "china":
print("你好!")
elif contry == "america":
print('hello.')
else:
return
# 真正执行函数的地方
func(*args, **kwargs)
return deco
return wrapper
执行代码
american()
print("------------")
chinese()
#你好!
#我来自中国。
#------------
#hello.
#I am from America
04. 高阶用法:不带参数的类装饰器
以上都是基于函数实现的装饰器,在阅读别人代码时,还可以时常发现还有基于类实现的装饰器。
基于类装饰器的实现,必须实现__call__
和 __init__
两个内置函数。
- init :接收被装饰函数
- call :实现装饰逻辑。
class logger(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("[INFO]: the function {func}() is running..."\
.format(func=self.func.__name__))
return self.func(*args, **kwargs)
@logger
def say(something):
print("say {}!".format(something))
say("hello")
#输出如下
#[INFO]: the function say() is running...
#say hello!
05. 高阶用法:带参数的类装饰器
上面不带参数的例子,你发现没有,只能打印INFO级别的日志,正常情况下,我们还需要打印DEBUG WARNING等级别的日志。 这就需要给类装饰器传入参数,给这个函数指定级别了。
带参数和不带参数的类装饰器有很大的不同。
- init :不再接收被装饰函数,而是接收传入参数。
- call :接收被装饰函数,实现装饰逻辑。
class logger(object):
def __init__(self, level='INFO'):
self.level = level
def __call__(self, func): # 接受函数
def wrapper(*args, **kwargs):
print("[{level}]: the function {func}() is running..."\
.format(level=self.level, func=func.__name__))
func(*args, **kwargs)
return wrapper #返回函数
@logger(level='WARNING')
def say(something):
print("say {}!".format(something))
say("hello")
#输出如下:
#[WARNING]: the function say() is running...
#say hello!
06. 使用偏函数与类实现装饰器#
绝大多数装饰器都是基于函数和闭包实现的,但这并非制造装饰器的唯一方式。
事实上,Python 对某个对象是否能通过装饰器( @decorator)形式使用只有一个要求:decorator 必须是一个“可被调用(callable)的对象
。
对于这个 callable 对象,我们最熟悉的就是函数了。
除函数之外,类也可以是 callable 对象,只要实现了__call__ 函数(上面几个盒子已经接触过了),还有比较少人使用的偏函数也是 callable 对象。
接下来就来说说,如何使用 类和偏函数结合实现一个与众不同的装饰器。
如下所示,DelayFunc 是一个实现了 call 的类,delay 返回一个偏函数,在这里 delay 就可以做为一个装饰器。(以下代码摘自 Python工匠:使用装饰器的小技巧)
import time
import functools
class DelayFunc:
def __init__(self, duration, func):
self.duration = duration
self.func = func
def __call__(self, *args, **kwargs):
print(f'Wait for {self.duration} seconds...')
time.sleep(self.duration)
return self.func(*args, **kwargs)
def eager_call(self, *args, **kwargs):
print('Call without delay')
return self.func(*args, **kwargs)
def delay(duration):
"""
装饰器:推迟某个函数的执行。
同时提供 .eager_call 方法立即执行
"""
# 此处为了避免定义额外函数,
# 直接使用 functools.partial 帮助构造 DelayFunc 实例
return functools.partial(DelayFunc, duration)
业务代码和调用
@delay(duration=2)
def add(a, b):
return a+b
print(add)
#<__main__.DelayFunc object at 0x107bd0be0>
print(add(1, 2))
#Wait for 2 seconds...
#8
print(add.func)
#<function add at 0x107bef1e0>
print(add.eager_call(1, 2))
#8
07.类装饰器
#单例
instances = {}
def singleton(cls):
def get_instance(*args, **kw):
cls_name = cls.__name__
print('===== 1 ====')
if not cls_name in instances:
print('===== 2 ====')
instance = cls(*args, **kw)
instances[cls_name] = instance
return instances[cls_name]
return get_instance
@singleton
class User:
_instance = None
def __init__(self, name):
print('===== 3 ====')
self.name = name
08.内置装饰器:property
property用法和描述符用法请参考如下文章
Python property
Python 描述符
09. 各种代码示例
-
函数缓存:
使用装饰器实现函数结果缓存,避免重复计算。from functools import lru_cache @lru_cache(maxsize=128) def fibonacci(n): if n <= 1: return n else: return (fibonacci(n-1) + fibonacci(n-2)) print(fibonacci(100)) # Output: 354224848179261915075
-
日志记录:
使用装饰器记录函数的调用信息。def log_function_call(func): def wrapper(*args, **kwargs): print(f"Calling function: {func.__name__}") result = func(*args, **kwargs) print(f"Function {func.__name__} returned: {result}") return result return wrapper @log_function_call def add_numbers(a, b): return a + b add_numbers(2, 3) # Output: # Calling function: add_numbers # Function add_numbers returned: 5
-
权限控制:
使用装饰器实现函数的访问权限控制。def require_admin(func): def wrapper(*args, **kwargs): if not is_admin(): print("Access denied. You must be an admin.") return return func(*args, **kwargs) return wrapper @require_admin def delete_user(user_id): # Delete the user from the system pass delete_user(123) # Will only work if the user is an admin
-
性能测量:
使用装饰器测量函数的执行时间。import time def measure_time(func): def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) end_time = time.time() print(f"Function {func.__name__} took {end_time - start_time:.6f} seconds to execute.") return result return wrapper @measure_time def long_running_task(): # Perform a long-running task time.sleep(2) long_running_task() # Output: Function long_running_task took 2.000000 seconds to execute.
-
参数验证:
使用装饰器验证函数的输入参数。def validate_input(func): def wrapper(a, b): if not isinstance(a, (int, float)) or not isinstance(b, (int, float)): raise ValueError("Both inputs must be numbers.") return func(a, b) return wrapper @validate_input def add_numbers(a, b): return a + b print(add_numbers(2, 3)) # Output: 5 print(add_numbers(2, "3")) # ValueError: Both inputs must be numbers.
-
单例模式:
使用装饰器实现单例模式。def singleton(cls): instances = {} def wrapper(*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return wrapper @singleton class MyClass: def __init__(self, value): self.value = value obj1 = MyClass(42) obj2 = MyClass(24) print(obj1 is obj2) # Output: True
-
异常处理:
使用装饰器处理函数抛出的异常。
好的,我们来补充一下7.异常处理的代码示例:
def handle_exceptions(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except TypeError as e:
print(f"TypeError occurred: {e}")
return None
except ValueError as e:
print(f"ValueError occurred: {e}")
return None
except Exception as e:
print(f"An unexpected error occurred: {e}")
return None
return wrapper
@handle_exceptions
def divide(a, b):
return a / b
print(divide(10, 2)) # Output: 5.0
print(divide(10, 0)) # Output: ValueError occurred: division by zero
print(divide(10, "2")) # Output: TypeError occurred: unsupported operand type(s) for /: 'int' and 'str'
在这个例子中,handle_exceptions
装饰器捕获了 TypeError
、ValueError
和其他未知异常,并打印出错误信息。如果函数执行成功,它会返回函数的结果,否则返回 None
。
这种异常处理装饰器可以用于统一处理多个函数的异常,避免在每个函数中重复编写异常处理代码。
你也可以根据需要修改装饰器的行为,比如记录异常信息到日志文件、发送警报、重试等操作。
-
类方法和静态方法:
使用装饰器定义类方法和静态方法。class MyClass: def __init__(self, value): self.value = value @classmethod def class_method(cls, param): print(f"Class method called with param: {param}") @staticmethod def static_method(a, b): return a + b MyClass.class_method("hello") # Output: Class method called with param: hello print(MyClass.static_method(2, 3)) # Output: 5
-
属性管理:
使用装饰器实现属性的 getter、setter 和 deleter 方法。class Person: def __init__(self, name): self._name = name @property def name(self): return self._name @name.setter def name(self, new_name): self._name = new_name @name.deleter def name(self): del self._name person = Person("John") print(person.name) # Output: John person.name = "Jane" print(person.name) # Output: Jane del person.name
-
上下文管理器:
使用装饰器实现自定义的上下文管理器。from contextlib import contextmanager @contextmanager def custom_context_manager(param): print(f"Entering custom context manager with param: {param}") try: yield finally: print("Exiting custom context manager") with custom_context_manager("hello"): print("Inside the context block") # Output: # Entering custom context manager with param: hello # Inside the context block # Exiting custom context manager
-
事件处理:
使用装饰器实现事件的注册和触发。class EventManager: def __init__(self): self.events = {} def on(self, event_name): def decorator(func): if event_name not in self.events: self.events[event_name] = [] self.events[event_name].append(func) return func return decorator def trigger(self, event_name, *args, **kwargs): if event_name in self.events: for func in self.events[event_name]: func(*args, **kwargs) event_manager = EventManager() @event_manager.on("my_event") def handle_my_event(param): print(f"Handling 'my_event' with param: {param}") event_manager.trigger("my_event", "hello") # Output: Handling 'my_event' with param: hello
这些只是 Python 装饰器的一些常见使用场景,实际上装饰器的应用范围非常广泛,可以用于优化、扩展和重构代码,提高代码的可读性和可维护性。
是的,Python 中的类装饰器可以用来取代 mixin 类、多重继承以及对 super()
函数的使用。让我们来看一些具体的代码示例:
使用类装饰器取代 mixin 类
假设我们有一个 LoggableMixin
类,它提供了日志记录功能:
class LoggableMixin:
def log(self, message):
print(f"[{self.__class__.__name__}] {message}")
我们可以使用类装饰器来实现同样的功能:
def loggable(cls):
class LoggedClass(cls):
def log(self, message):
print(f"[{self.__class__.__name__}] {message}")
return LoggedClass
@loggable
class MyClass:
def __init__(self, name):
self.name = name
obj = MyClass("John")
obj.log("Hello, world!") # Output: [MyClass] Hello, world!
在这个例子中,loggable
装饰器创建了一个新的类 LoggedClass
,它继承了原始的 MyClass
,并添加了 log
方法。这样就避免了使用 mixin 类的需要。
使用类装饰器取代多重继承
假设我们有两个基类 A
和 B
,我们想要创建一个新类 C
,它同时继承自 A
和 B
:
class A:
def a_method(self):
print("A's method")
class B:
def b_method(self):
print("B's method")
class C(A, B):
pass
obj = C()
obj.a_method() # Output: A's method
obj.b_method() # Output: B's method
我们可以使用类装饰器来实现同样的功能:
def combine_classes(cls_a, cls_b):
class CombinedClass(cls_a, cls_b):
pass
return CombinedClass
@combine_classes(A, B)
class C:
pass
obj = C()
obj.a_method() # Output: A's method
obj.b_method() # Output: B's method
在这个例子中,combine_classes
装饰器创建了一个新的类 CombinedClass
,它同时继承自 A
和 B
。这样就避免了使用多重继承的需要。
使用类装饰器取代 super()
函数
假设我们有一个基类 Base
和一个派生类 Derived
,我们想要在 Derived
中覆盖基类的方法,并在覆盖方法中调用基类的方法:
class Base:
def method(self):
print("Base's method")
class Derived(Base):
def method(self):
print("Derived's method")
super().method()
obj = Derived()
obj.method() # Output: Derived's method
# Base's method
我们可以使用类装饰器来实现同样的功能:
def override_method(method):
def wrapper(self):
print("Derived's method")
method(self)
return wrapper
class Derived(Base):
@override_method
def method(self):
super().method()
obj = Derived()
obj.method() # Output: Derived's method
# Base's method
在这个例子中,override_method
装饰器创建了一个新的方法 wrapper
,它在调用原始方法 method
之前打印了一条