The main objective is to create a meta-programming functions and classes, and use them to manipulate the code (such as the revision, the existing code or package). The main characteristic of this Python based decorative purpose include, Class decorator and metaclass.
9.1 to function adds a wrapper
problem
We would like to add a wrapper function to add additional processing.
solution
Write a simple decorator
import time from functools import wraps def timethis(func): ''' :param func: Decorator that reports the execution time :return: func ''' @wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(func.__name__, end - start) return result # Returns a function return wrapper @timethis def countdown(n): while n > 0: n -= 1 if __name__ == '__main__': print(countdown(10000)) print(countdown(10000000))
discuss:
It is a decorative function, a function that may be received as input and returns as output a new function.
@timethis def countdown(n):
This means that countdown = timethis (countdown)
@Staticmethod class built inside, @classmethos, @property logic is the same
9.2 How to save metadata functions when writing decorator.
problem:
We have written a good decorator, but when it is used in a function, a number of important metadata such as the function name, document string, comment, and calls the function signature is lost.
solution:
functools.wraps
import time from functools import wraps def timethis(func): ''' :param func: Decorator that reports the execution time :return: func ''' @wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(func.__name__, end - start) return result # Returns a function return wrapper @timethis def countdown(n: int) -> int: '''this is countdown''' while n > 0: n -= 1 return n if __name__ == '__main__': print(countdown(10000)) print(countdown(10000000)) # Function data types print(countdown.__annotations__) # Function name print(countdown.__name__) # Function documentation explains print(countdown.__doc__)
countdown 0.0006299018859863281 0 countdown 0.5314240455627441 0 {'n': <class 'int'>, 'return': <class 'int'>} countdown this is countdown
discuss:
If you cancel @wraps
Characteristic function are gone
countdown 0.0007231235504150391 0 countdown 0.5646867752075195 0 {} wrapper None
# Retrieve the original function print(countdown.__wrapped__) from inspect import signature print(signature(countdown)) print(signature(countdown.__wrapped__))
Functions can be decorated by __wrapped__ retrieval function is not to be decorated
9.3 pairs decorator be unpacked
problem:
Retrieve the package had no function
solution:
By retrieving the properties __wrapped__
discuss:
As long as the use of the decorator functools.wraps (func) the metadata appropriate copy to remove with __wrapped__ properties.
Multiple decorators of the time, see __wrapped__ effect.
from functools import wraps def decorator1(func): @wraps(func) def wrapper(*args): print('Decorator1') return func(*args) return wrapper def decorator2(func): @wraps(func) def wrapper(*args): print('Decorator2') return func(*args) return wrapper @decorator1 @decorator2 def add(x, y): return x+y if __name__ == '__main__': print(add(2,3)) print('=' * 10 ) # Retrieval function is decorator2 print(add.__wrapped__(3,4)) print('=' * 10) # Original function is retrieved print(add.__wrapped__.__wrapped__(3, 4))
/usr/local/bin/python3.7 /Users/shijianzhong/study/PythonCookbook/chapter_9/t3_3.py Decorator1 Decorator2 5 ========== Decorator2 7 ========== 7 Process finished with exit code 0
Python3.7 have modified this loophole, do not cross directly to the function of the element.
However, please note that not all decorators have used @wraps, so some decorators behavior may differ with our expectations, in particular, the descriptor objects created by the built-in and decorators @staitcmethod and @classmethod Failure to follow this convention (Instead, they will save the original functions in __func__ properties).
9.4 reception parameters may define a decorator
problem:
Receiving a write-doped almost decorators
solution:
Write a decorator factory, the logging module with a book, write a decorator factory, I have just re-logging module under review
from functools import wraps import logging import time def logged(level, name=None, message=None): def decorate(func): logname = name if name else func.__name__ # Obtain a log output target stream log = logging.getLogger(logname) logmsg = message if message else func.__name__ @wraps(func) def wrapper(*args, **kwargs): # Output log information # log.setLevel(logging.ERROR) # print(log.level) log.log(level, logmsg) return func(*args, **kwargs) return wrapper return decorate @logged(logging.DEBUG) def add(x, y): return x+y @logged(logging.CRITICAL, 'example') def spam(): print('Spam!') if __name__ == '__main__': print(add(1, 2)) print('=' * 10) time.sleep(1) print(spam())
/usr/local/bin/python3.7 /Users/shijianzhong/study/PythonCookbook/chapter_9/t4_2.py 3 ========== spam Spam! None Process finished with exit code 0
By major role decorator factory that can pass parameters to a function call inside, here is the logging of incoming grade
discuss
@decorator(x, y, z)
def func(a, b):
...
In fact, the underlying run is
func = decorator(x,y,z)(func)
decorator (x, y, z) must return a call object.
9.5 defines a property that can be modified by the user decorator
problem
We want to write a decorator to wrap function, but allows the user to adjust the properties decorator, so at runtime able to control the behavior of the decorator
solution:
Write a accessor functions to modify the properties of the interior decorator by nonlocal keyword variables. After the accessor functions attached to the package as a function of function attributes.
I wrote the test, do not need accessor functions, defined directly on the outside of the packaging function is a function of the properties as a function.
from functools import wraps, partial import logging import time logging.basicConfig(level=logging.DEBUG) # Access from the function, is a simplified version of the decorator factory function, using a partial tips def attach_wrapper(obj, func=None): if func is None: return partial(attach_wrapper, obj) setattr(obj, func.__name__, func) return func def logged(level, name=None, message=None): def decorate(func): # print(func.__wrapped__) # print(func.__name__) logname = name if name else func.__name__ # Obtain a log output target stream log = logging.getLogger(logname) logmsg = message if message else func.__name__ @wraps(func) def wrapper(*args, **kwargs): # Output log information # log.setLevel(logging.ERROR) # print(log.level) log.log(level, logmsg) return func(*args, **kwargs) # Decorator factory directly decorator factory function called twice, the first pass wrapper parameters, the second call to pass parameters func # Through this decorator factory valued property to the function @attach_wrapper(wrapper) def set_level(newlevel): nonlocal level level = newlevel @attach_wrapper(wrapper) def set_message(newmsg): nonlocal logmsg logmsg = newmsg wrapper.get_level = lambda :level wrapper.name = 'sidian' return wrapper return decorate @logged(logging.DEBUG) def add(x, y): return x+y @logged(logging.CRITICAL, 'example') def spam(): print('Spam!') if __name__ == '__main__': print(add.set_message('Hello World')) print(add(1, 2)) print('=' * 10) time.sleep(1) print(spam())
/usr/local/bin/python3.7 /Users/shijianzhong/study/PythonCookbook/chapter_9/t5_2.py DEBUG:add:Hello World None 3 ========== Spam! None CRITICAL:example:spam
In fact, I found myself in the process of writing, do not write access from the function,
wrapper.get_level = lambda :level wrapper.name = 'sidian'
Definition of internal function, and then add a property to the internal functions can also be, relatively speaking, is one more step to manually add a function, but will prevent write accessor functions.
discuss:
Decorators @ functoos.wrap are used, then the inner sphincter function can be spread more decorative layers.
9.6 can receive a definition of optional parameters decorator
problem:
We want to write a separate decorator, i.e. it can do without parameters like @decorator, may also be used as decorative factory @decorator (x, y, z) so used.
solution:
Parameter passing mode defined inside decorator * function, the function returns a through functools.partail.
from functools import wraps import logging import functools import time def logged(func=None, *, level=logging.WARNING, name=None, message=None): # If no incoming func, and then return the partial-defined function, the second execution of this function, and automatic transfer function parameters to be decorated if func is None: return functools.partial(logged, level=level, name=name, message=message) logname = name if name else func.__name__ # Obtain a log output target stream log = logging.getLogger(logname) logmsg = message if message else func.__name__ @wraps(func) def wrapper(*args, **kwargs): # Output log information # log.setLevel(logging.ERROR) # print(log.level) log.log(level, logmsg) return func(*args, **kwargs) return wrapper @logged def add(x, y): return x+y @logged(level=logging.CRITICAL, name='example') def spam(): print('Spam!') if __name__ == '__main__': print(add(1, 2)) print('=' * 10) time.sleep(1) print(spam())
discuss:
One thing to understand that the entire focus
@decorator(x, y, z)
def func(a, b):
...
In fact, the underlying run is
func = decorator (x, y, z) (func) This is the most important
decorator (x, y, z) must return a call object.