python3 cookbook 笔记十四

装饰器

在你写自己的装饰器的时候记得要调用functools库的wraps装饰器,它能保留原始函数的元数据。

import time
from functools import wraps
def timethis(func):
    '''
    Decorator that reports the execution time.
    '''
    @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

wraps是一个便捷函数,调用update_wrapper作为函数装饰器,后者更新一个wrapper函数以使其类似于wrapped函数。

functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)=partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)

functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

import functools
print(functools.WRAPPER_ASSIGNMENTS)
('__module__', '__name__', '__qualname__', '__doc__', '__annotations__')
print(functools.WRAPPER_UPDATES)
('__dict__',)

update_wrapper会把WRAPPER_ASSIGNMENTS这些属性直接赋值过去,然后会更新包装函数的WRAPPER_UPDATES也就是实例字典,添加了一个指向被包装函数的属性__wrapped__。

带参数的装饰器则是通过闭包的原理,在最外层多套了一层函数。

可自定义属性的装饰器则是在带参数的装饰器的基础上,给包装函数添加方法,方法里面通过nonlocal关键字对参数实现更改。

带可选参数的装饰器

from functools import wraps, partial
import logging

def logged(func=None, *, level=logging.DEBUG, name=None, message=None):
    if func is None:
        return partial(logged, level=level, name=name, message=message)
    @wraps(func)
    def wrapper(*args, **kwargs):
    	return func(*args, **kwargs)
    return wrapper

利用装饰器强制函数上的类型检查,用到了inspect库。

将装饰器定义为类的一部分,就是把装饰器放到类里面,定义成实例方法还是类方法只是调用的时候会有区别。看上去有点奇怪,但有些标准库就有很多例子如@property,它其实是个类,里面定义了三个方法getter(), setter(), deleter(),都是装饰器。

将装饰器定义为类,需要实现__init__和__call__方法,特别地,如果你想作用于类定义里面的函数,还需要额外实现__get__方法:

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)

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)

要注意的点是wraps在__init__里的用法,还有__get__方法返回一个绑定方法(给这个方法传递self参数)。

为类和静态方法提供装饰器,要确保装饰器在 @classmethod@staticmethod 之前,因为这两个并不会创建可直接调用的对象, 而是创建特殊的描述器对象,因此当你试着在其他装饰器中将它们当做函数来使用时就会出错。

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

装饰器为被包装函数增加参数,我惊了:

from functools import wraps

def optional_debug(func):
    @wraps(func)
    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)
...
>>> spam(1,2,3)
1 2 3
>>> spam(1,2,3, debug=True)
Calling spam
1 2 3
>>>

使用装饰器扩充类的功能,通过装饰器重写方法,比继承来的更简洁更快。

装饰器的进阶终于讲完了这里贴个基础知识吧。

元类metaclass

《Understanding Python metaclasses》讲的很牛。

断断续续看了一个周才算真正弄懂,太长就不翻译了,随便放两张图吧!
在这里插入图片描述

*args和**kwargs的强制参数签名

对任何涉及到操作函数调用签名的问题,你都应该使用 inspect 模块中的签名特性。 我们最主要关注两个类:Signature 和 Parameter 。下面是一个创建函数前面的交互例子:

>>> from inspect import Signature, Parameter
>>> # Make a signature for a func(x, y=42, *, z=None)
>>> parms = [ Parameter('x', Parameter.POSITIONAL_OR_KEYWORD),
...         Parameter('y', Parameter.POSITIONAL_OR_KEYWORD, default=42),
...         Parameter('z', Parameter.KEYWORD_ONLY, default=None) ]
>>> sig = Signature(parms)
>>> print(sig)
(x, y=42, *, z=None)

上下文管理器

实现一个新的上下文管理器的最简单的方法就是使用 contexlib 模块中的 @contextmanager 装饰器。 下面是一个实现了代码块计时功能的上下文管理器例子:

import time
from contextlib import contextmanager

@contextmanager
def timethis(label):
    start = time.time()
    try:
        yield
    finally:
        end = time.time()
        print('{}: {}'.format(label, end - start))

# Example use
with timethis('counting'):
    n = 10000000
    while n > 0:
        n -= 1

在函数 timethis() 中,yield 之前的代码会在上下文管理器中作为 __enter__() 方法执行, 所有在 yield 之后的代码会作为 __exit__() 方法执行。 如果出现了异常,异常会在yield语句那里抛出。

上面的装饰器适用于函数,如果是对象,那么你就需要单独实现 __enter__() 方法和 __exit__() 方法。

class timethis:
    def __init__(self, label):
        self.label = label

    def __enter__(self):
        self.start = time.time()

    def __exit__(self, exc_ty, exc_val, exc_tb):
        end = time.time()
        print('{}: {}'.format(self.label, end - self.start))

exec执行代码

在局部变量域中会出问题:

# 全局命名空间下
>>> a = 13
>>> exec('b = a + 1')
>>> print(b)
14

# 局部下
>>> def test():
...     a = 13
...     exec('b = a + 1')
...     print(b)
...
>>> test()
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "<stdin>", line 4, in test
NameError: global name 'b' is not defined

默认情况下,exec() 会在调用者局部和全局范围内执行代码。然而,在函数里面, 传递给 exec() 的局部范围是拷贝实际局部变量组成的一个字典。 因此,如果 exec() 如果执行了修改操作,这种修改后的结果对实际局部变量值是没有影响的。

为了修正这样的错误,你需要在调用 exec() 之前使用 locals() 函数来得到一个局部变量字典。 之后你就能从局部字典中获取修改过后的变量值了。例如:

>>> def test():
...     a = 13
...     loc = locals()
...     exec('b = a + 1')
...     b = loc['b']
...     print(b)
...
>>> test()
14

作为 locals() 的一个替代方案,你可以使用你自己的字典,并将它传递给 exec() 。例如:

>>> def test4():
...     a = 13
...     loc = { 'a' : a }
...     glb = { }
...     exec('b = a + 1', glb, loc)
...     b = loc['b']
...     print(b)
...
>>> test4()
14
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值