zoukankan      html  css  js  c++  java
  • PythonCookbook第九章(元编程)【持续更新】

    元编程的主要目标是创建函数和类,并用它们来操纵代码(比如修改、生成或者包装已有的代码)。Python中基于这个目的的主要特性包括装饰器、类装饰器以及元类。

    9.1 给函数添加一个包装

    问题

    我们想给函数添加一个包装以添加额外的处理。

    解决方案

    写一个简单的装饰器

    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
        # 返回一个函数
        return wrapper
    
    @timethis
    def countdown(n):
    
        while n > 0:
            n -= 1
    
    if __name__ == '__main__':
        print(countdown(10000))
        print(countdown(10000000))
    

     讨论:

    装饰器是一个函数,它可以接收一个函数作为输入并返回一个新的函数作为输出。

    @timethis
    def countdown(n):

    这个的意思就是countdown = timethis(countdown)

    类里面的内置的@staticmethod, @classmethos, @property都是一样的逻辑

    9.2编写装饰器时如何保存函数的元数据。

    问题:

    我们已经编写好一个装饰器,但是当将它用在一个函数上时,一些重要的元数据比如函数名、文档字符串、函数注释以及调用签名都丢失了。

    解决方案:

    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
        # 返回一个函数
        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))
        # 函数数据类型
        print(countdown.__annotations__)
        # 函数名字
        print(countdown.__name__)
        # 函数文档解释
        print(countdown.__doc__)
    
    countdown 0.0006299018859863281
    0
    countdown 0.5314240455627441
    0
    {'n': <class 'int'>, 'return': <class 'int'>}
    countdown
    this is countdown
    

     讨论:

    如果取消@wraps

    函数的特性都没有了

    countdown 0.0007231235504150391
    0
    countdown 0.5646867752075195
    0
    {}
    wrapper
    None
    
     # 取回原函数
        print(countdown.__wrapped__)
        from inspect import signature
        print(signature(countdown))
        print(signature(countdown.__wrapped__))
    

     可以通过被装饰函数的__wrapped__取回没有被装饰的函数

    9.3 对装饰器进行解包装

    问题:

    取回没有包装过的函数

    解决方案:

    通过__wrapped__属性取回

    讨论:

    只要在装饰器利用了functools.wraps(func)对元数据进行了适当的拷贝,才能用__wrapped__属性取出。

    多个装饰器的时候,看__wrapped__的效果。

    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 )
        # 取回的是decorator2函数
        print(add.__wrapped__(3,4))
        print('=' * 10)
        # 取回的是原来的函数
        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已经修改了这个漏洞,不会直接穿越到元素的函数了。

    但请注意并不是所有的装饰器都使用了@wraps,因此有些装饰器的行为可能与我们预期的有所区别,特别是,由内建的装饰器@staitcmethod和@classmethod创建的描述符对象并不遵循这个约定(相反,它们会把原始函数保存在__func__属性中)。

    9.4定义一个可接收参数的装饰器

    问题:

    编写一个可接收掺乎的装饰器

    解决方案:

    编写一个装饰器工厂,书中用了logging模块,编写装饰器工厂,刚好我也重新复习下logging模块

    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__
            # 获取一个log输出对象流
            log = logging.getLogger(logname)
            logmsg = message if message else  func.__name__
    
            @wraps(func)
            def wrapper(*args, **kwargs):
                # 输出log信息
                # 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
    

     通过装饰器工厂的主要作用就是可以传递参数给内部函数调用,这里传入的是logging的等级

    讨论

    @decorator(x, y, z)

    def func(a, b):

      ...

    其实底层运行的是

    func = decorator(x,y,z)(func)

    decorator(x,y,z)返回的必须是一个可调用对象。

    9.5 定义一个属性可由用户修改的装饰器

    问题

    我们想编写一个装饰器来包装函数,但是可以让用户调整装饰器的属性,这样在运行时能够控制装饰器的行为

    解决方案:

    编写一个访问器函数,通过nonlocal关键字变量来修改装饰器内部的属性。之后把访问器函数作为函数属性附加到包装函数上。

    我自己写的测试,根本不需要访问器函数,直接在包装函数外面定义包装函数的属性为函数。

    from functools import wraps, partial
    import logging
    import time
    
    
    logging.basicConfig(level=logging.DEBUG)
    
    # 访问起函数,是一个简化版的装饰器工厂函数,使用了partial技巧
    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__
            # 获取一个log输出对象流
            log = logging.getLogger(logname)
            logmsg = message if message else  func.__name__
    
            @wraps(func)
            def wrapper(*args, **kwargs):
                # 输出log信息
                # log.setLevel(logging.ERROR)
                # print(log.level)
                log.log(level, logmsg)
                return func(*args, **kwargs)
    
            # 装饰器工厂会直接装饰器工厂函数调用两次,第一次传wrapper参数,第二次调用传func参数
            # 通过这个装饰器工厂给函数赋值属性
            @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
    

     其实我自己在写的过程中发现,不写访问起函数,

    wrapper.get_level = lambda :level
    wrapper.name = 'sidian'
    

     定义内部函数,然后给内部函数添加属性为函数也可以,相对来说就是多一步手工添加,但可以避免写访问器函数。

    讨论:

    装饰器都使用了@functoos.wrap的话,内层函数可以括约多个装饰器层进行传播。

    9.6定义一个能接收可选参数的装饰器

    问题:

    我们想编写一个单独的装饰器,使其即可以像@decorator这样不带参数使用,也可以像@decorator(x,y,z)使用装饰器工厂这么用。

    解决方案:

    定义装饰器函数的传参方式里面有*,通过functools.partail返回一个函数。

    from functools import wraps
    import logging
    import functools
    import time
    
    def logged(func=None, *, level=logging.WARNING, name=None, message=None):
        # 如果没有传入func,就返回这个partial定义好的函数,第二次执行这个函数,并自动传参被装饰的函数
        if func is None:
            return functools.partial(logged, level=level, name=name, message=message)
    
    
        logname = name if name else func.__name__
        # 获取一个log输出对象流
        log = logging.getLogger(logname)
        logmsg = message if message else  func.__name__
    
        @wraps(func)
        def wrapper(*args, **kwargs):
            # 输出log信息
            # 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())
    

     讨论:

    整个重点一点要了解到

    @decorator(x, y, z)

    def func(a, b):

      ...

    其实底层运行的是

    func = decorator(x,y,z)(func)           这个是重中之重

    decorator(x,y,z)返回的必须是一个可调用对象。

     9.7 利用装饰器对函数参数强制执行类型检查(感觉还是非常有用的)

    问题

    我们想为函数参数添加强制性的类型检查功能,将其作为一种断言或者调用者之间的契约。

    解决方法

    from inspect import signature
    from functools import wraps, partial
    
    
    def typeassert(*ty_args, **ty_kwargs):
    
        def decorate(func):
            # 如果为非debug模式,返回函数本身
            if not __debug__:
                return func
            sig = signature(func)
            # print(sig)    # (x, y, z=42) 函数的参数签名信息,<class 'inspect.Signature'>形式
            bound_types = sig.bind_partial(*ty_args, **ty_kwargs).arguments
            # print(bound_types)
            # 通过bind_partial可以给每个参数绑定对象,采用OrderDict形式OrderedDict([('x', <class 'int'>), ('z', <class 'int'>)])
            @wraps(func)
            def wrapper(*args, **kwargs):
                # 因为func如果设置由默认值,则sig对面里面保存着由默认值的信息。
                bound_values = sig.bind(*args, **kwargs)
                # print(bound_values.arguments.items())
                # 通过两个输入参数后,对比有序字典
                for name, value in bound_values.arguments.items():
                    if name in bound_types:
                        if not isinstance(value, bound_types[name]):
                            raise TypeError(
                                'Argument {} must be {}'.format(name, bound_types[name])
                            )
                return func(*args, **kwargs)
            return wrapper
        return decorate
    
    if __name__ == '__main__':
        @typeassert(int, z=int)
        def spam(x, y, z=[]):
            print(x, y, z)
        spam(1, 2, 3)
        spam(1, [])
        spam(1, 2, '')
    
    /usr/local/bin/python3.7 /Users/shijianzhong/study/PythonCookbook/chapter_9/t7_2.py
    Traceback (most recent call last):
      File "/Users/shijianzhong/study/PythonCookbook/chapter_9/t7_2.py", line 38, in <module>
        spam(1, 2, '')
      File "/Users/shijianzhong/study/PythonCookbook/chapter_9/t7_2.py", line 26, in wrapper
        'Argument {} must be {}'.format(name, bound_types[name])
    TypeError: Argument z must be <class 'int'>
    1 2 3
    1 [] []
    
    Process finished with exit code 1
    

     讨论:

    这里主要用到了inspect模块的一些功能

    特别是sig = inspect.signature()

    sig.bind与sig.bindpartail两个方法绑定输入的参数,通过arrgment.items字典形式输出。整个逻辑相对不是很复制,但需要了解很多内置模块的功能。

    9.8在类中定义装饰器

    问题:

    我们想在类中定义一个装饰器,并将其作用于其他的函数或者方法上

    解决方法:

    from functools import wraps
    
    class A:
    
        # 实例装饰器方法
        def decorator1(self, func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                print('Decorator 1')
                return func(*args, **kwargs)
            return wrapper
    
        # 类装饰器函数
        @classmethod
        def decorator2(cls, func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                print('Decorator 2')
                return func(*args, **kwargs)
            return wrapper
    
    a = A()
    @a.decorator1
    def spam():
        ...
    
    @A.decorator2
    def grok():
        ...
    
    spam()
    grok()
    

     讨论

    我们的 propery类就用了这个

    class Person:
    
        # 这个类属性是实例
        first_name = property()
    
        # 调用实例方法的装饰器
        @first_name.getter
        def first_name(self):
            return self._first_name
    
        @first_name.setter
        def first_name(self, value):
            self._first_name = value
    
    
    
    
    if __name__ == '__main__':
        p = Person()
        p.first_name = 'sh'
        print(p.first_name)
    

    9.9把装饰器定义成类

    问题:

    我们想用装饰器来包装函数,但是希望得到的结果是一个可调用的实例。我们需要装饰器既能在类中工作,也可以在类外部使用。

    解决方案:

    import types
    from functools import wraps
    
    class Profiled:
    
        def __init__(self, func):
            wraps(func)(self)
            self.ncalls = 0
    
        def __call__(self, *args, **kwargs):
            self.ncalls += 1
            return self.__wrapped__(*args, **kwargs)
    
        # 定义__get__使的返回的对象可以在调用时,成本绑定对象instance的方法
        def __get__(self, instance, owner):
            # print(instance, owner)
            if instance is None:
                print('123')
                return self
            else:
                # 将自身的__call__方法变成instance的方法
                return types.MethodType(self, instance)
    
    
    @Profiled
    def add(x, y):
        return x+y
    
    class Spam:
        @Profiled
        def bar(self, x ):
            print(self, x)
    
    if __name__ == '__main__':
        print(add(2, 3))
        print(add.ncalls)
        s = Spam()
        s.bar(1)
    

     所以用类写装饰器相对非常的麻烦,一定要定义__get__才能给方法进行装饰,没什么特殊情况,还使通过普通函数写,因为函数默认内置定义好了__get__方法。

    讨论:

    from functools import wraps
    
    # 普通的函数装饰器,方便很多
    def profiled(func):
        ncalls = 0
        @wraps(func)
        def wrapper(*args, **kwargs):
            nonlocal ncalls
            ncalls += 1
            return func(*args, **kwargs)
        # 必须属性赋值使函数,这样才有运行这个函数,或者最新的数据。
        # 这里就像一个读取闭包函数的属性,必须通过函数来读取
        wrapper.ncalls = lambda :ncalls
        return wrapper
    
    
    @profiled
    def add(x, y):
        return x+y
    
    class Spam:
        @profiled
        def bar(self, x ):
            print(self, x)
    
    if __name__ == '__main__':
        print(add(2, 3))
        print(add(3 ,4))
        print(add.ncalls())
        s = Spam()
        s.bar(1)
    

    9.10 把装饰器作用到类和静态方法上

    问题:

    我们想在类或者静态方法上应用装饰器

    解决方案

    直接写上装饰器就可以了,但必须在@classmethod和@staticmethod之前

    from functools import wraps
    import time
    
    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
        # 返回一个函数
        return wrapper
    
    
    
    class Spam:
    
        @timethis
        def instance_method(self, n):
            print(self, n)
            while n > 0:
                n -=1
    
        @classmethod
        @timethis
        def class_method(cls, n):
            print(cls, n)
            while n > 0:
                n -= 1
    
        @staticmethod
        @timethis
        def static_method(n):
            print(n)
            while n > 0:
                n -= 1
    
    
    if __name__ == '__main__':
        s = Spam()
        s.instance_method(1000000)
        Spam.class_method(1000000)
        Spam.static_method(100000)
    

     讨论:

    前面不要搞错位置,@classmethod和@staitcmethod并不会实际创建可直接调用的对象。相反,它们创建的是特殊的描述符对象。

    9.11编写装饰器为被包装的函数添加参数

    问题:

    我们想编写一耳光装饰器为被包装的函数添加额外的参数。但是,添加的参数不能影响到该函数的已有的调用约定。

    解决方案

    通过函数那章学习到的keyword-only参数,额外的参数注入到函数的调用签名。

    from functools import wraps
    
    def optional_debug(func):
        @wraps(func)
        # keyword-only参数传入
        def wrapper(*args, debug=False, **kwargs):
            if debug:
                print('Calling', func.__name__)
            return func(*args, **kwargs)
        return wrapper
    
    
    @optional_debug
    def spam(a, b, c):
        print(a, b, c)
    
    if __name__ == '__main__':
        spam(1, 2, 3, debug=True)
        print('='*20)
        spam(1, 2, 3)
    

     讨论:

    我前面的代码中,如果被装修的函数的参数里面与keyword-only中的参数一样,有可能名称冲突,为了避免,应该增加参数选择。

    from functools import wraps
    import inspect
    
    def optional_debug(func):
        # 对函数的默认变量名参数的变量名进行查寻
        args = inspect.getfullargspec(func).args
        # print(args)
        if 'debug' in args:
            raise TypeError('debug argument already defined')
    
        @wraps(func)
        # keyword-only参数传入
        def wrapper(*args, debug=False, **kwargs):
            if debug:
                print('Calling', func.__name__)
            return func(*args, **kwargs)
        return wrapper
    
    
    @optional_debug
    def spam(a, b, c):
        print(a, b, c)
    
    if __name__ == '__main__':
        spam(1, 2, 3, debug=True)
        print('='*20)
        spam(1, 2, 3)
    
    from functools import wraps
    import inspect
    
    def optional_debug(func):
        # 对函数的默认变量名参数的变量名进行查寻
        args = inspect.getfullargspec(func).args
        # print(args)
        if 'debug' in args:
            raise TypeError('debug argument already defined')
    
        @wraps(func)
        # keyword-only参数传入
        def wrapper(*args, debug=False, **kwargs):
            if debug:
                print('Calling', func.__name__)
            return func(*args, **kwargs)
        
        # 下面使修改被装饰函数的参数签名信息
        sig = inspect.signature(func)
        params = list(sig.parameters.values())
        # print(params)
        params.append(inspect.Parameter('debug',
                                        inspect.Parameter.KEYWORD_ONLY,
                                        default=False))
        wrapper.__signature__ = sig.replace(parameters=params)
    
    
        return wrapper
    
    
    
    @optional_debug
    def spam(a, b, c):
        print(a, b, c)
    
    if __name__ == '__main__':
        spam(1, 2, 3, debug=True)
        print('='*20)
        spam(1, 2, 3)
        print(inspect.signature(spam))
    
    /usr/local/bin/python3.7 /Users/shijianzhong/study/PythonCookbook/chapter_9/t11_3.py
    [<Parameter "a">, <Parameter "b">, <Parameter "c">]
    Calling spam
    1 2 3
    ====================
    1 2 3
    (a, b, c, *, debug=False)
    
    Process finished with exit code 0
    

    9.12 利用装饰器给类打补丁

    
    
  • 相关阅读:
    android openGl视频
    《jQuery权威指南》学习笔记之第2章 jQuery选择器
    RobHess的SIFT源码分析:综述
    building android/ubuntu-touch on 32bit machine
    Android开发(24)---安卓中实现多线程下载(带进度条和百分比)
    创建Sdcard
    POJ 1033 Defragment
    [C++STDlib基础]关于C标准输入输出的操作——C++标准库头文件<cstdio>
    机器学习实战决策树之眼镜男买眼镜
    protubuffer for windows配置指南!
  • 原文地址:https://www.cnblogs.com/sidianok/p/12315501.html
Copyright © 2011-2022 走看看