Windless
订阅/Feed
稗田千秋(i@wind.moe)

Python元编程 装饰器篇

稗田千秋
Nov.28 2016 code

最近在 readthedocs 上阅读 Python3-Cookbook 的中文版,深有收获。因为C++的TMP,一直对元编程有着敬而远之的态度,但是在用Python的过程中,却感到了元编程是一种切实的生产力工具。本文为元编程其一,专门介绍装饰器,类装饰器和元类日后再书。

0x00 装饰器能干什么

个人认为装饰器其实和 Monkey Patch 有异曲同工之妙,都可以给函数执行前后执行额外代码,而不必修改函数本身。

我们先来看一个简单的装饰器

def decorator(func):
    # 定义一个wrapper,对原始函数进行包装
    def wrapper():
        print("明明是我先来的...")
        func()  # 执行原始函数
    return wrapper  # 需要再返回这个wrapper这个包装函数,代码才能执行

@decorator
def wa():
    print("为什么会变成这样呢")

运行结果可以看到在 wa 函数运行前先执行了 wrapper 里在函数前写的的代码,其实装饰器的执行也可以写作

decorator(wa)()

也就是类似与函数的存在,将另一个函数作为对象传入。但是可以注意到,这样的装饰会导致函数的元信息,比如名字,文档字符串,注解和参数签名等都丢失了。

print(wa.__name__)
# 未使用装饰器的输出:wa
# 使用装饰器的输出:wrapper

虽然影响可能不是很大,但是可以使用 functools 库中的 @wraps 装饰器来注解包装函数,比如我们将上文的 decorator 稍作改动

import functools

def decorator(func):
    @functools.wraps(func)
    def wrapper():
        print("明明是我先来的...")
        func()
    return wrapper
        
@decorator
def wa():
    print("为什么会变成这样呢")

print(wa.__name__)
# 未使用wraps的输出:wa
# 使用wraps的输出:wa

@wraps 可以通过属性 __wrapped__ 直接访问原函数,也保留了部分元信息(__name__,__doc__,__annotations__等)。

0x01 传递参数

先来向被装饰函数传递参数,args和kwarg这里就先不做解释了,这里本质上就是多次传递参数而已。

import functools

def decorator(func):
    @functools.wraps(func)
    def wrapper(*args):
        print("明明是我先来的...")
        func(*args)

    return wrapper

@decorator
def wa(arg):
    print("为什么会变成这样呢")
    print(arg)

wa("两件快乐事情重合在一起")

接下来是向装饰器传递参数,这个就稍微复杂了一点,先看实现

import functools

def decorator(sentence):
    def wrapper(func):
        @functools.wraps(func)
        def wrapped(*args, **kwargs):
            print("明明是我先来的...")
            func(*args)
        return wrapped
    return wrapper

@decorator("本该是像梦境一般幸福的时间")
def wa(arg):
    print("为什么会变成这样呢")
    print(arg)

# 这里的装饰器也等于如下调用
wa = decorator("本该是像梦境一般幸福的时间")(wa)

wa("两件快乐事情重合在一起")

乍看实现起来有点复杂,但是有了上文的基础,也就不难理解了。最外层的 decorator 接受参数并将参数作用于内部的装饰器函数上, 内层的函数 wrapper 接受一个参数并将其包装起来,也就类似无参数的 decorator 装饰器, 然后返回即可。

0X02 应用示例

if isinstance(args[-1], AbstractView):

的作用是由于装饰的对象不同,传递而进的参数也不同,因此需要判断一下传进来的是视图类还是视图函数,咋最后返回函数即可。

--END--
文章创建于 2016-11-28 15:42:04,最后更新 2016-11-28 15:42:04
Comment
尝试加载Disqus评论, 失败则会使用基础模式.
    • play_arrow

    About this site

    version:1.02 Alpha
    博客主题: Lime
    联系方式: i@wind.moe
    写作语言: zh_CN & en_US
    博客遵循 CC BY-NC-SA 4.0许可进行创作

    此外,本博客会基于访客的Request Headers记录部分匿名数据用于统计(Logger的源码见Github),包含Referer, User-Agent & IP Address.个人绝不会主动将数据泄露给第三方