An adorner is typically a named object that does not allow a lambda expression, accepts a single argument when called by (an adornment function), and returns another callable object. The callable object here includes not only functions and methods, but also classes. Any callable object (any object that implements the call method can be called) can be used as an adorner, and the object they return is not a simple function, but a more complex class instance that implements its own call method.
@some_decoratordef decorated_function(): pass# 以上写法总是可以替换为显式的装饰器调用和函数的重新赋值:decorated_function = some_decorator(decorated_function)
1. Adorner definition/Use Method 1.1 general pattern: as a function
def mydecorator(function): def wrapped(*args, **kwargs): # 在函数调用之前, 做点什么 result = function(*args, **kwargs) # 在函数调用之后, 做点什么 # 返回结果 return result # 返回 wrapper 作为装饰函数 return wrapped
1.2 implementation
PagerMethod: As a class
The generic pattern used by a non-parameterized adorner as a class is as follows:
class DecoratorAsClass: def __init__(self, function): self.function = function def __call__(self, *args, **kw): # 在调用原始函数之前, 做点什么 result = self.function(*args, **kwargs) # 在调用原始函数之后, 做点什么 # 返回结果 return result
1.3 Parametric Adorner: Implementing a second layer of packaging
def repeat(number=3): """ 多次重复执行装饰函数, 返回最后一次原始函数调用的值作为结果. : param number: 重复次数, 默认值为 3 """ def actual_decorator(function): def wrapped(*args, **kwargs): result = None for _ in range(number): result = function(*args, **kwargs) return result return wrapped return actual_decorator@repeat(2)def foo(): print("foo")
An adorner with parameters can always be changed as follows:
foo = repeat(number=3)(foo)
Even if the parameter of the parameterized adorner has a default value, parentheses must be appended to the name
@repeat()def bar(): print("bar")
1.4 Preserving the introspective adorner
The common disadvantage of using adorners is that the function metadata (primarily the document string and the original function name) is not saved when using adorners. The adorner combination creates a new function and returns a new object, completely without regard to the original function's flags. This will make it more difficult to debug the adorner's decorated functions, and it will also destroy most of the tools that can be used to automate the production of documents, and should be inaccessible to the original document string and function signatures.
The way to solve this problem is to use functools the decorator built into the module wraps() .
from functools import wrapsdef preserving_decorator(function): @wraps(function) def wrapped(*args, **kwargs): """包装函数内部文档""" return function(*args, **kwargs) return wrapped@preserving_decoratordef function_with_important_docstring(): """这是我们想要保存的文档字符串""" passprint(function_with_important_docstring.__name__)print(function_with_important_docstring.__doc__)
2. Decorator Common Example 2.1 parameter check
Checking the parameters that a function accepts or returns may be useful when executed in a specific context.
# Adorner Code rpc_info = {} # when actually read, this class definition populates the Rpc_info dictionary and is used to check the parameter type in a specific environment. Def XMLRPC (in_= (), out= (Type (None),): Def _xml RPC (function): # Register Signature func_name = function.__name__ Rpc_info[func_name] = (In_, out) def _CHEC K_types (elements, types): "" "is used to check the type of the child function" "If Len (elements)! = Len (types): Raise type Error ("Argumen count is wrong") typed = enumerate (Zip (elements, types)) for index, couple in typed: arg, Of_the_right_type = couple if isinstance (ARG, Of_the_right_type): Co Ntinue Raise TypeError ("Arg #%d should be%s"% (index, of_the_right_type)) def __xmlrpc (*args): # No allowed keywords # check what's entered if function.__class__ = = "Method": Checkable_args = args[1:] # class method, remove Self Else:checkable_args = args[:] # normal function _check_types (Checkable_args, I N_) # Run function res = function (*args) # Check for input if not type (res) in (tuple, list): Checkable_ res = (res,) Else:checkable_res = Res _check_types (checkable_res, out) # Function machine Type Check success return res return __XMLRPC return _xmlrpc# Use example class Rpcview: @xmlrpc ((int, int)) # both INT--and None def meth1 (self, Int1, Int2): Print ("Received%d and%d"% (Int1, Int2)) @xmlrpc ((str,) , (int,)) #----int def meth2 (self, phrase): Print ("received%s"% phrase) return 12# call output PR int (rpc_info) # output: # {' Meth1 ': ((<class ' int '), <class ' int ' >), (<class ' Nonetype '), ' Meth2 ': ((< Class ' Str ';,), (<class ' int '),}my = Rpcview () my.meth1 (1, 2) # Output: Type check succeeded # received 1 and 2my.meth2 (2) # Output: type check Failed # file "D:\VBoxShare\Work\Documents\PyProject\PyCookbook\test.py", line page, in <module># my.meth2 (2) # File "D:\VBoxShare\Work\Documents\pyproject\pycookbook\test.py ", line __xmlrpc# _check_types (Checkable_args, In_) # File" D:\VBoxShare\Work \documents\pyproject\pycookbook\test.py ", line A, in _check_types# raise TypeError (" Arg #%d should is%s "% (index, O F_the_right_type) # Typeerror:arg #0 should be <class ' str ' >
2.2 Cache
The cache adorner is very similar to a parameter check, but his focus is on the function that the content state does not affect the input, and each set of parameters can be linked to a unique result. Therefore, the cache adorner can put the output with the parameters required by the calculation method, and return him directly in subsequent calls (this behavior becomes memoizing).
Import Timeimport Hashlibimport Picklecache = {}def is_obsolete (entry, duration): Return time.time ()-entry["Time"] &G T Durationdef Compute_key (function, args, kw): "" uses the sorted parameters to build the SHA hash key and saves the result in a global dictionary. Use pickle to create a hash, which is a shortcut to freeze all the state of an object passed in as a parameter to ensure that all parameters are satisfied. "" "Key = Pickle.dumps ((function.__name__, args, kw)) return Hashlib.sha1 (key). Hexdigest () def memoize (duration=10): def _memoize (function): Def __memoize (*args, **kw): key = Compute_key (function, args, kw) # Have you got it already? if (key in caches and not Is_obsolete (Cache[key], duration)): print ("We got a winner.") return cache[key]["value"] # Calculate result = function (*args, **kw) # Save Results Cache[ke Y] = {"value": result, "Time": Time.time ()} return result ret Urn __memoize return _memoize@memoize () def func_1 (A, B): Return a + bprint (Func_1 (2, 2) # 4print (Func_1 (2, 2)) # Print, 4@memoize (1) def func_2 (A, B): Return a + bprint (func_2 (2, 2)) # 4TIME.S Leep (1) Print (func_2 (2, 2)) # 4
Cached values can also be bound to the function itself to manage its scope and life cycle, instead of a centralized dictionary. However, in any case, a more efficient adorner uses a dedicated cache library based on advanced caching algorithms.
2.3 Agents
The proxy adorner uses the global proxy to mark and register functions. For example, a security layer that protects code access based on the current user can be implemented using the centralized inspector and the permissions required by the associated callable object.
class User: def __init__(self, roles): self.roles = rolesclass Unauthorized(Exception): passdef protect(role): def _protect(function): def __protect(*args, **kw): user = globals().get("user") if user is None or role not in user.roles: raise Unauthorized("I won't tell you.") return function(*args, **kw) return __protect return _protecttarek = User(("admin", "user")) bill = User(("user",))class MySecrets: @protect("admin") def waffle_recipe(self): print("use tons of butter") these_are = MySecrets()user = tarekthese_are.waffle_recipe() # use tons of butteruser = billthese_are.waffle_recipe() # __main__.Unauthorized: I won't tell you.
The above models are commonly used in Python WEB frameworks (permission validation) to define the security of a published class. For example, Django provides adorners to protect the security of function access.
2.4 Context Providers
The context decorator ensures that the function can run in the correct context, or run some code before and after the function, in other words, he sets and resets a particular execution environment.
For example, when a data item needs to be shared among multiple threads, a lock is used to protect her from multiple accesses, which can be written in an adorner.
from threading import RLocklock = RLock()def synchronized(function): def _synchronized(*args, **kw): lock.acquire() try: return function(*args, **kw) finally: lock.release() return _synchronized@synchronizeddef thread_safe(): # 确保锁定资源 pass
The upper and lower adorners are usually replaced by the context Manager (with).
Python Decorator Summary