如何制作合适的函数包装器



我用朴素的方法编写了一个包装器。获取所有*args**kwargs并将它们传递给封闭函数。但是出了问题。所以我简化了一个例子来说明我的问题。

# simplies wrapper possible: just pass the args
def wraps(f):
def call(*argv, **kw):
# add some meaningful manipulations later
return f(*argv, **kw)
return call
# check the wrapper behaves identically
class M:
def __init__(this, param):
this.param = param
M.__new__ = M.__new__
m1 = M(1)
M.__new__ = wraps(M.__new__)
m2 = M(2)

m1正常实例化,但m2失败,错误描述如下

TypeError: object.__new__() takes exactly one argument (the type to instantiate)

问题是如何正确定义wrapscall函数,使它们的行为与被包装的函数相同,而不管被包装的函数是什么。

这显然不是最终目标,因为原语lambda x: x就足够了。这是一个起点,从这里我可以介绍更多的复杂问题。

简短回答:不可能的。你无法在python中定义一个完美的包装器(在许多其他语言中也是如此)。

稍长版本. Python函数是一级对象,对象可接受的所有操作都可以用函数执行。所以你不能假设一些复杂的过程会限制自己只调用作为参数传递的函数,而不会以其他不明显的方式使用函数对象

使用示例进行更详细的推测

只在部分域中定义的函数是很常见的

def half(i):
if i < 0:
raise ValueError
if i & 1:
raise ValueError
return i / 2

很直。不,我们可以更混乱一点:

class Veggy:
def __init__(this, kind):
this.kind = kind
def pr(this):
print(this.kind)
def assess(v):
if v.kind in ['tomato', 'carrot']:
raise ValueError
v.pr()

这里Veggy用作函数代理,但也有公共属性kind,评估函数在执行前检查。

函数对象也可以做同样的事情,因为它除了调用之外还有其他属性。

def test(x):
return x + x
def assess4(f, *argv, **kw):
if f.__name__ != 'test':
raise ValueError
if f.__module__ != '__main__':
raise ValueError
if len(f.__code__.co_code) % 8 == 4:
raise ValueError
return f(*argv, **kw)

编写正确的包装成为一个挑战。这个挑战可能会更加复杂:

def assess0(f, *argv, **kw):
if len(f.__code__.co_code) % 8 == 0:
kw['arg'] = True
return f(*argv[1:], kw)
else
kw['arg'] = False
return f(*argv[:-1], **kw)

通用包装器应该正确处理assess0assess4,这几乎是不可能的。我们还没有接触id魔法。检查id会将可接受的函数转换为石头。

编码礼仪

所以您不能编写包装器。为什么有人不厌其烦地写一个呢?当函数不能保证行为等价并且可能在代码流中引入重要的更改时,为什么函数如此普遍?

简单的答案是编码约定。著名的代换原理。当某个对象被另一个相同类型的对象替换时,代码应该保留行为属性。Python很少关注类型提名和强制执行。严格的类型系统不是必须的,你可以像python语言那样通过文档和类型注释来建立api和协议。

程序必须是为人们阅读而编写的,并且只能偶然地为机器执行。OOP约定都在人们的脑海中。因此,python开发人员打破惯例,要求重写object方法时使用一些非标准行为。这种非常规的OOP处理使得不可能使用修饰符来转换__init____new__方法。

最终解

如果python认为__new__如此特殊,那么通用包装器也应该这样做。

# simplest wrapper possible: just pass the args
def wraps(f):
def call(*argv, **kw):
# add some meaningful manipulations later
return f(*argv, **kw)
def call_new(*argv, **kw):
# add some meaningful manipulations later
return f(argv[0])
if f is object.__new__:
return call_new
# elif other_special_case: pass
else:
return call

现在它可以成功通过测试

# check the wrapper behaves identically
class M:
def __init__(this, param):
this.param = param
M.__new__ = M.__new__
m1 = M(1)
M.__new__ = wraps(M.__new__)
m2 = M(2)

缺点除了__new__之外,您应该为任何其他违反约定的函数实现不同的解决方案,以使您的函数包装器在通用上下文中半适用。但这是你能从python中得到的最好的。

最新更新