Python [1]

装饰器、上下文管理器、单分发器

Posted by ZYT on November 19, 2018

装饰器

装饰器本质上就是一个函数,这个函数接收其他函数作为参考,并将其以一个新的修改后的函数替代它。

def wrap(f):
    return f

@wrap
def test():
    pass

上面的过程类似于:

f = wrap(test)

简单装饰器

def check_func(func):
    def wrapper(*args, **kwargs):
        if kwargs.get("username") != "admin":
            raise Exception("This user is not allowed!")
        return func(*args, **kwargs)
    return wrapper

@check_func
def get(username="someone"):
    pass

但是装饰器存在问题:新函数缺少很多原函数的属性,比如文档字符串和名字。

>>> print(get.__name__)
'wrapper'

Python 中内置的 functools 模块通过其 update_wrapper 函数解决了这个问题,它会复制这些属性给这个装饰器本身。

>>> get = functools.update_wrapper(check_func, get)
>>> print(get.__name__)
'get'

手动调用 update_wrapper 创建装饰器很不方便,所以提供了 wraps 函数。

from functools import wraps

def check_func(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        if kwargs.get("username") != "admin":
            raise Exception("This user is not allowed!")
        return func(*args, **kwargs)
    return wrapper

@check_func
def get(username="someone"):
    pass


>>> print(get.__name__)
'get'

目前获取被装饰函数的方法比较麻烦,可以采用 inspect 提取函数的签名。

from functools import wraps
import inspect

def check_func(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        func_args = inspect.getcallargs(func, *args, **kwargs)
        if func_args.get("username") != "admin":
            raise Exception("This user is not allowed!")
        return func(*args, **kwargs)
    return wrapper

@check_func
def get(username="someone"):
    pass

带参数的装饰器

from functools import wraps
import inspect

def check_func(level):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            func_args = inspect.getcallargs(func, *args, **kwargs)
            if level == "info":
                if func_args.get("username") != "admin":
                    raise Exception("This user is not allowed!")
            else:
                print(level)
            return func(*args, **kwargs)
        return wrapper
    return decorator

@check_func(level="warn")
def get(username="someone"):
    pass

>>> get(username="someone")
'warn'

类装饰器

装饰器函数其实是这样一个接口约束,它必须接受一个 callable 对象作为参数,然后返回一个 callable 对象。在 Python 中一般 callable 对象都是函数,但也有例外。只要某个对象重载了 __call__() 方法,那么这个对象就是 callable 的。

class Logging(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print(self.func.__name__)
        return self.func(*args, **kwargs)
@Logging
def say(something):
    print("say {}!".format(something))

带参数的类装饰器

from functools import wraps

class Logging(object):
    def __init__(self, level='INFO'):
        self.level = level

    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print("[{level}]: enter function {func}()".format(level=self.level, func=func.__name__))
            return func(*args, **kwargs)
        return wrapper

@Logging(level='INFO')
def say(something):
    print("say {}!".format(something))

装饰器调用顺序:

@a
@b
@c
def f ():
    pass

f = a(b(c(f)))

上下文管理器

上下文对象的简单实现:关键点为 __enter____exit__

class MyContext(object):
    def __enter__(self):
        pass
    def __exit__(self, exc_type, exc_value, traceback):
        pass

上下文管理协议的使用场景:

  1. 调用方法 A
  2. 执行一段代码
  3. 调用方法 B

基于 contextlib.contextmanager 实现

import contextlib

@contextlib.contextmanager
def MyContext():
    yield

可以同时使用多个上下文管理器

with open("file1", "r") as source, open("file2", "w") as desination:
    desination.write(source.read())

单分发器

import functools

class SnareDrum(object): pass
class Cymbal(object): pass
class Stick(object): pass
class Brushes(object): pass

@functools.singledispatch
def play(instrument, accessory):
    raise NotImplementedError("Cannot play these")

@play.register(SnareDrum)
def _(instrument, accessory):
    if isinstance(accessory, Stick):
        return "POC!"
    if isinstance(accessory, Brushes):
        return "SHHHH!"
    raise NotImplementedError("Cannot play these")

print(play(SnareDrum(), Stick()))