在black magic上操作的装饰器实用工具
项目描述
在black magic上操作的元编程模块!
目前只有一个模块可用。然而,我完全欢迎有趣的想法。
black_magic.decorator
此模块允许创建看起来和表现与原始函数相同的包装函数。这对于装饰器特别有用。
模块的一部分复制了知名 decorator 模块的功能,但它是基于直接创建AST节点而不是组合和编译字符串。
此外,此模块使创建具有修改签名的包装器成为可能。目前,唯一专门为此目的而明确指定的专用函数是 partial。如果您对进行更复杂的修改感兴趣,可以将动态创建的 Signature 传递给 wraps。如果您创建了一些有用的功能,请考虑将您的功能贡献给此模块。
.wraps()
wraps 可以像标准 functools.wraps 函数一样使用。然而,它返回一个真正的函数,即在使用 help() 或其他元编程工具检查时将具有有用签名的函数。此外,它知道如何精确地复制签名,甚至记得默认参数和注释的对象标识符。
>>> from black_magic.decorator import wraps
>>> def real(a=[]):
... pass
>>> @wraps(real)
... def fake(*args, **kwargs):
... return args
>>> fake()
([],)
>>> fake(1)
(1,)
>>> fake(a=2)
(2,)
>>> fake()[0] is real()
True
如果你想玩得更大胆,甚至可以使用ast.expr的
>>> import ast
>>> fake = wraps(real)(ast.Num(n=1))
>>> fake(0)
1
警告:在使用此模块中的任何函数之前,请务必阅读以下警告!
.partial()
这类似于functools.partial函数。
>>> from black_magic.decorator import partial
>>> def real(arg):
... print(arg)
>>> partial(real, arg=0)()
0
尽管有一些区别
此函数返回一个看起来像输入函数的对象,除了修改后的参数。
所有重写的参数都将从签名中完全删除。在functools.partial中,这只对按位置绑定的参数有效。
首先移除**kwargs,然后是*args
>>> partial(lambda a,b,c: (a,b,c), 2, a=1)(3) (1, 2, 3)
通过留出第一个参数为空,partial可以作为装饰器使用
>>> @partial(None, 1, bar=0) ... def foo(bar, lum): ... return bar, lum >>> foo() (0, 1)
注意,由partial(None, ...)返回的函数就像partial一样:它可以绑定额外的参数,你仍然可以省略第一个参数。这有一些奇怪的性质,不应在生产代码中使用,但我认为这可以增加一些额外的脑洞,看看它将走向何方。
注意:迭代调用partial(第一个参数为None)不会像将partial应用于函数那样隐藏参数,即你可以在后续调用中将绑定的参数向右移动。在代码中
>>> partial(None, 1)(a=0)(lambda a, b: (a, b))()
(0, 1)
.metapartial()
返回的值可以像partial一样绑定函数到此处提供的参数。实际上,partial = metapartial()。
通过返回的函数绑定更多的关键字参数将覆盖先前绑定的相同名称的关键字参数。
>>> @metapartial(1, a=0, c=3)
... def func(a, b, *args, **kwargs):
... return (a, b, args, kwargs)
>>> func(2)
(0, 1, (2,), {'c': 3})
.decorator()
这是创建装饰器的典范工具
>>> from black_magic.decorator import decorator
>>> @decorator
... def plus_one(fn):
... def fake(*args, **kwargs):
... return 1 + fn(*args, **kwargs)
... return fake
>>> @plus_one
... def mul_plus_one(a, b):
... return a * b
>>> mul_plus_one(2, 3)
7
.flatorator()
flatorator模仿了著名decorator模块的功能。
>>> from black_magic.decorator import flatorator
>>> @flatorator
... def times_two(fn, *args, **kwargs):
... return 2 * fn(*args, **kwargs)
>>> @times_two
... def add_times_two(a, b):
... return a + b
>>> add_times_two(1, 2)
6
底层实现
问题:这使用了丑陋的str连接和eval代码,对吧?
答案:不,它使用了丑陋的抽象语法树代码来进行动态代码生成。
问题:但代码仍然很丑陋,对吧?
答案:是的。
警告:性能下降即将到来
使用此模块中的工具装饰函数是一个相当昂贵的操作,所以不要经常这样做!另一方面,调用包装器没有问题。
警告:functools.partial很邪恶
当将functools.partial对象传递给.wraps或任何更普遍的黑魔法函数时,请务必小心。 functools.partial对按关键字绑定的参数的处理非常不合理。这些以及所有后续参数都成为关键字参数。考虑以下示例
>>> import functools
>>> def func(a, b, *args, **kwargs):
... return (a, b, args, kwargs)
>>> part = functools.partial(func, a=0)
>>> part(1)
Traceback (most recent call last):
...
TypeError: func() got multiple values for argument 'a'
此外,请注意,*args参数将永远无法访问!
为了兼容不同版本的Python以及易于使用,我选择将functools.partial对象处理成如果你实际上使用了具有相同参数的black_magic.decorator.partial一样,即
>>> wrap = wraps(part)(part)
>>> wrap(1, 2, c=3)
(0, 1, (2,), {'c':3})
注意,由.wraps(functools.partial(f))施加的签名与.wraps(.partial(f))的签名等效,这可能会让人意外。
测试
此模块已通过Travis CI在python{2.6, 2.7, 3.3, 3.4, 3.5}和PyPy1.9上进行了测试,并使用python 3.2进行了本地测试。
更改日志
0.0.12
日期:2015年12月26日
修复源分发中缺失的UNLICENSE文件
一些内部维护
0.0.11
日期:2015年12月25日
添加对Python 3.5的支持
0.0.10
添加与Python 3.4的兼容性
0.0.9
修复black_magic.decorator.partial的签名:移除可以防止将同名参数绑定到**kwargs的命名参数func
partial的返回值现在可以在函数未关闭的情况下用于进一步的参数(重新)绑定。请注意:这可能会出现意外的特性,例如在后续调用中将位置参数向右移动。
添加metapartial函数,它只接受要绑定的*args, **kwargs,而要使用的函数只能在第二步中指定。
略微提高了black_magic.decorator的性能。现在与decorator模块的性能大致相同。
0.0.8
使用相同的参数将所有functools.partial转换为black_magic.partial。
0.0.7
对functools.partial提供部分支持
0.0.6
修复了在black_magic.decorator.wraps()中模拟的functools.update_wrapper
0.0.5
添加black_magic.decorator.partial
0.0.4
支持将任何可调用对象传递给ASTorator.decorate
将README转换为.rst格式
项目详情
black-magic-0.0.12.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 4d433fb9ebebf0a62ddf8108c1d21af467066de50a934a92f875058f52f44071 |
|
MD5 | ebcf1a75bd636ac5e0f623c95db3a114 |
|
BLAKE2b-256 | bd3bde3f4022b111afae930424e96cf731754b948e4ce3d1dea83825423b2c8f |