PythonCookbook Chapter VIII (meta-programming)

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.

 

 

 

 

Guess you like

Origin www.cnblogs.com/sidianok/p/12315501.html