Python常用装饰器汇总
本节将介绍一些比较常用的装饰器。
1、类装饰器
下面的代码演示了 staticmethod 和 classmethod 两个类装饰器的用法,分别用来装饰类成员函数,用来表示某个方法是静态方法或类方法。class DemoClass1(object): attr1 = 1 # 类属性 @staticmethod # 静态方法 def static_func1(): print(DemoClass1.attr1) DemoClass1.attr1 += 1 @classmethod # 类方法 def class_func1(cls): print(cls.attr1) cls.attr1 += 2 DemoClass1.static_func1() DemoClass1.class_func1() DemoClass1.static_func1() DemoClass1.class_func1()运行后输出结果如下:
$ python staticclassdemo1.py
1
2
4
5
另外一个常见的装饰器是 property,该装饰器表明被装饰函数会在读取与函数同名的属性时被调用,并且得到的是被装饰函数的返回值。注意,该属性是只读的,不能被赋值。下面是一个例子。
>>> class PropertyDemo1(object): ... def __init__(self): ... self.field1 = 0 ... self.field2 = 0 ... @property ... def count(self): ... return self.field1 ... # 类定义结束 >>> obj1 = PropertyDemo1() >>> obj1.count 0 >>> obj1.count = 4 # 只读属性,不能被赋值 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: can't set attribute如果希望可读也可写,那么需要先定义一个被 property 装饰的函数,然后定义一个被 setter 装饰的函数。下面是一个例子。
>>> class GetterSetter(object): ... def __init__(self): ... self.field1 = 0 ... @property # 表明是一个属性,默认是只读的 ... def count(self): ... return self.field1 ... @count.setter # 添加设置写操作函数 ... def count(self, value): ... if not isinstance(value, int): ... raise ValueError('count must be an integer!') ... self.field1 = value ... # 类定义结束 >>> obj1 = GetterSetter() # 创建一个对象 >>> obj1.count = 3 # 赋值 >>> obj1.count # 读出 3
2、退出时执行的函数
接下来介绍的装饰器用来装饰在程序退出时执行的函数。演示例子如下:import time def onexit(f): # 装饰器 import atexit atexit.register(f) # 将被装饰函数注册到系统中 return f @onexit def on_exit_func1(): print("on_exit_func1() is running") @onexit def on_exit_func2(): print("on_exit_func2() is running") print("Starting running") # 开始执行脚本 time.sleep(3) print("Ending") # 脚本执行完毕运行后的输出结果如下:
$ python onexit.py
Starting running
Ending
on_exit_func2() is running
on_exit_func1() is running
3、单例模式
这时装饰器装饰的类,表示该类最多只能有一个实例对象。演示例子如下:>>> def singleton(cls): # 装饰器,不过传入的是一个类对象而不是函数对象 ... instances = {} ... def getinstance(): ... if cls not in instances: ... instances[cls] = cls() # 生成一个对象 ... return instances[cls] ... return getinstance ... # 装饰器定义结束 >>> @singleton ... class DemoClass1: ... def __init__(self): ... self.field1 = 8 ... self.field2 = "python" ... # 被装饰的类定义结束 >>> obj1 = DemoClass1() # 创建两个对象 >>> obj2 = DemoClass1() # 第二个对象 >>> obj1 is obj2 # 这两个对象是否是同一个对象 True # 是同一个对象
4、执行时间限制
有时需要限定某个函数的执行时间,如果超出该时间,需要强制结束运行。提供这种装饰器的 Python 包很多,这里仅介绍其中一个,即 timeout_decorator。使用该 timeout_decorator 之前,需要自行安装,方法是运行下面的命令行:
pip install timeout-decorator
下面是一个使用该装饰器的例子。import time import timeout_decorator # 引入装饰器 @timeout_decorator.timeout(5) # 不能超时5秒 def func_with_time_limit(): print("Start") for i in range(1,10): # 循环10次 time.sleep(1) print("{} seconds have passed".format(i)) if __name__ == '__main__': func_with_time_limit()运行该脚本,输出如下:
$ python timeoutdemo1.py
Start
1 seconds have passed
2 seconds have passed
3 seconds have passed
4 seconds have passed
Traceback (most recent call last): # 打印出来调用栈
File "timeoutdemo1.py", line 12, in <module>
func_with_time_limit()
File
"/anaconda3/lib/python3.7/site-packages/timeout_decorator/timeout_
decorator.py",
line 81, in new_function
return function(*args, **kwargs)
File "timeoutdemo1.py", line 8, in func_with_time_limit
time.sleep(1)
File
"/anaconda3/lib/python3.7/site-packages/timeout_decorator/timeout_
decorator.py",
line 72, in handler
_raise_exception(timeout_exception, exception_message)
File
"/anaconda3/lib/python3.7/site-packages/timeout_decorator/timeout_
decorator.py",
line 45, in _raise_exception
raise exception()
timeout_decorator.timeout_decorator.TimeoutError: 'Timed Out'
可以发现,在超时后它会自动抛出异常,我们可以捕捉该异常。下面是修改后的代码:
import time import timeout_decorator @timeout_decorator.timeout(5) # 不能超时5秒 def func_with_time_limit(): print("Start") for i in range(1,10): time.sleep(1) print("{} seconds have passed".format(i)) if __name__ == '__main__': try: func_with_time_limit() except: print("Time-out")运行后的输出如下:
$ python timeoutdemo2.py
Start
1 seconds have passed
2 seconds have passed
3 seconds have passed
4 seconds have passed
Time-out
5、执行时间标定
有时需要统计出某个函数每次执行的时间,此时既可以使用在函数执行之前和之后获取系统时间,然后通过计算得到实际运行时间,也可以使用现成的装饰器来达到这个目的。这里介绍的装饰器是 benchmark-decorator,这个装饰器也是需要读者自行安装的。安装命令如下:
pip install benchmark-decorator
下面是一个使用benchmark-decorator装饰器的例子。
import time from benchmark import Benchmark # 引入标定模块 @Benchmark() # 标定该函数的执行时间 def do_real_work1(): time.sleep(0.1) return 1 @Benchmark() # 标定该函数的执行时间 def do_real_work2(): time.sleep(0.2) return 2 @Benchmark() # 标定该函数的执行时间 def func_benchmarked(): print("Start") for i in range(1,10): do_real_work1() do_real_work2() if __name__ == '__main__': func_benchmarked ()运行该脚本后会发现,在当前目录下多了一个文件 bench.log,标定数据都记录在该文件中了。下面是该文件的内容:
2019-07-28 06:10:05,921 - BENCHMARK - INFO - do_real_work1: 0.10025286674499512
2019-07-28 06:10:06,123 - BENCHMARK - INFO - do_real_work2: 0.20151209831237793
2019-07-28 06:10:06,227 - BENCHMARK - INFO - do_real_work1: 0.10357093811035156
2019-07-28 06:10:06,431 - BENCHMARK - INFO - do_real_work2: 0.2039330005645752
......
6、自动重新运行
如果执行过程中抛出了异常,那么可以尝试让 retry() 函数再次运行。该软件包也需要我们自行安装,安装命令如下:pip install retry_decorator
下面是一个使用该装饰器的例子:from retry_decorator import * # 引入重试模块 @retry(Exception, tries=3, timeout_secs=0.3) # 自动重试3次,间隔0.3秒 def retry_demo(): # 如果抛出了任意类型的异常 import sys,time print('working now') raise Exception('Testing retry') if __name__ == '__main__': try: retry_demo() except Exception as e: print('Received the last exception')运行后的输出如下:
$ python retrydemo1.py
working now #第5行代码的输出
ERROR:root:Retrying in 0.32 seconds ... # 会在0.32秒后再次尝试运行
Traceback (most recent call last):
File "/anaconda3/lib/python3.7/site-packages/retry_decorator/retry_decorator.py",
line 26, in f_retry
return f(*args, **kwargs)
File "retrydemo1.py", line 7, in retry_demo
raise Exception('Testing retry')
Exception: Testing retry
working now # 第2轮运行,第5行代码的输出
ERROR:root:Retrying in 0.60 seconds ...
Traceback (most recent call last):
File "/anaconda3/lib/python3.7/site-packages/retry_decorator/retry_
decorator.py",
line 26, in f_retry
return f(*args, **kwargs)
File "retrydemo1.py", line 7, in retry_demo
raise Exception('Testing retry')
Exception: Testing retry
working now # 第3轮运行
Received the last exception
7、状态机
对于一个状态机来说,需要定义状态和状态迁移函数,在某个事件发生时,会调用对应的状态迁移函数,如果迁移函数执行失败,其会调用用户定义的失败处理函数。下面是 pysta temachine 包的源代码:
import functools import inspect class InvalidStateTransition(Exception): pass class StateInfo(object): @staticmethod def get_states(cls): if not inspect.isclass(cls): raise TypeError('"{0}" is no class object!'.format(cls)) if not hasattr(cls, '___pystatemachine_cls_states'): states = tuple(state for _, state in inspect.getmembers(cls, lambda member: isinstance(member, State))) setattr(cls, '___pystatemachine_cls_states', states) return getattr(cls, '___pystatemachine_cls_states') @staticmethod def get_initial_state(cls): if not inspect.isclass(cls): raise TypeError('"{0}" is no class object!'.format(cls)) if not hasattr(cls, '___pystatemachine_cls_initial_state'): states = StateInfo.get_states(cls) initial_states = [state for state in states if state.is_ initial] assert initial_states assert len(initial_states) == 1 initial_state = initial_states[0] setattr(cls, '___pystatemachine_cls_initial_state', initial_ state) return getattr(cls, '___pystatemachine_cls_initial_state') @staticmethod def get_current_state(obj): if not hasattr(obj, '___pystatemachine_obj_current_state'): initial_state = StateInfo.get_initial_state(obj.__class__) setattr(obj, '___pystatemachine_obj_current_state', initial_ state) return getattr(obj, '___pystatemachine_obj_current_state') @staticmethod def set_current_state(obj, state): assert isinstance(state, State), 'invalid state type!' setattr(obj, '___pystatemachine_obj_current_state', state) class State(object): # 定义状态类 def __init__(self, name, initial=False): super(State, self).__init__() self.is_initial = True if initial else False self.name = name.upper() def __str__(self): return '<{0}.State[{1}] object at 0x{2:X}>'.format(__name__, self.name, id(self)) def event(from_states=None, to_state=None): # 装饰器 """ a decorator for transitioning from certain states to a target state. must be used on bound methods of a class instance, only. """ from_states_tuple = (from_states, ) if isinstance(from_states, State) \ else tuple(from_states or []) if not len(from_states_tuple) >= 1: raise ValueError() if not all(isinstance(state, State) for state in from_states_tuple): raise TypeError() if not isinstance(to_state, State): raise TypeError() def wrapper(wrapped): @functools.wraps(wrapped) def transition(instance, *a, **kw): if instance.current_state not in from_states_tuple: raise InvalidStateTransition() try: result = wrapped(instance, *a, **kw) except Exception as error: error_handlers = getattr(instance, '___pystatemachine_transition_failure_handlers', []) for error_handler in error_handlers: error_handler(instance, wrapped, instance.current_state, to_state, error) if not error_handlers: raise error else: StateInfo.set_current_state(instance, to_state) return result return transition return wrapper def transition_failure_handler(calling_sequence=0): def wrapper(wrapped): setattr(wrapped, '___pystatemachine_is_transition_failure_ handler', True) setattr(wrapped, '___pystatemachine_transition_failure_handler_calling_ sequence', int(calling_sequence)) return wrapped return wrapper def acts_as_state_machine(cls): """ a decorator which sets two properties on a class: * the 'current_state' property: a read-only property, returning the state machine's current state, as 'State' object * the 'states' property: a tuple of all valid state machine states, as 'State' objects class objects may use current_state and states freely :param cls: :return: """ assert not hasattr(cls, 'current_state') assert not hasattr(cls, 'states'), '{0} already has a "states" attribute!'.format(cls) def get_states(obj): return StateInfo.get_states(obj.__class__) def is_transition_failure_handler(obj): return all([ any([ inspect.ismethod(obj), # Python 2 inspect.isfunction(obj), # python3 ]), getattr(obj, '___pystatemachine_is_transition_failure_ handler', False), ]) transition_failure_handlers = sorted( [value for name, value in inspect.getmembers(cls, is_transition_failure_handler)], key=lambda m: getattr(m, '___pystatemachine_transition_failure_handler_calling_ sequence', 0), ) setattr(cls, '___pystatemachine_transition_failure_handlers', transition_failure_handlers) cls.current_state = property(fget=StateInfo.get_current_state) cls.states = property(fget=get_states) return cls下面是使用该状态机装饰器的例子,该例子描述了闸机的状态。闸机只有两个状态,一个是锁死,一个是可以推动的非锁死状态。事件也有两个,一个是让闸机锁死,一个是让闸机解锁。状态转换函数应该有4个,但有两个可以和别的共享,所以实际上只有两个状态转换函数,一个是解锁处理函数,一个是锁定处理函数。
由于任意状态转换的过程中可能发生异常情况,所以需要定义异常,该函数会在转换函数抛出异常时被调用。下面是一个例子:
@acts_as_state_machine class Turnstile(object): # 这个类对应一个状态机 locked = State('locked', initial=True) # 定义两个状态 unlocked = State('unlocked') @event(from_states=(locked, unlocked), to_state=unlocked) def coin(self): # 解锁的处理函数 assert random.random() > .5, 'failing for demonstration purposes, only ..' print('*blingbling* .. unlocked!') @event(from_states=(locked, unlocked), to_state=locked) def push(self): # 锁定处理函数 print('*push* .. locked!') @transition_failure_handler(calling_sequence=2) def turnstile_malfunction(self, method, from_state, to_state, error): print(" *** turnstile_malfunction() is Running") @transition_failure_handler(calling_sequence=1) def before_turnstile_malfunction(self, method, from_state, to_state, error): print(" *** before_turnstile_malfunction() is Running") import random turnstile = Turnstile() # 创建状态机 for _ in range(10): # 产生10个事件 handler = random.choice([turnstile.coin, turnstile.push]) handler() # 触发事件 print("======================="). # 分隔符运行后的输出如下:
$ python statemachine.py
*blingbling* .. unlocked!
=======================
*push* .. locked!
=======================
*** before_turnstile_malfunction() is Running
*** turnstile_malfunction() is Running
=======================
*** before_turnstile_malfunction() is Running
*** turnstile_malfunction() is Running
=======================
*** before_turnstile_malfunction() is Running
*** turnstile_malfunction() is Running
=======================
*** before_turnstile_malfunction() is Running
*** turnstile_malfunction() is Running
=======================
*push* .. locked!
=======================
*push* .. locked!
=======================
*push* .. locked!
=======================
*push* .. locked!
=======================
声明:《Python系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。