让我们考虑这段代码,我想在其中使用装饰器动态创建 bar
def foo():
def bar():
print "I am bar from foo"
print bar()
def baz():
def bar():
print "I am bar from baz"
print bar()
我想我可以用装饰器从外面创建酒吧:
def bar2():
print "I am super bar from foo"
setattr(foo, 'bar', bar2)
但结果不是我所期望的(我想得到I am super bar from foo
:
>>> foo()
I am bar from foo
是否可以使用装饰器覆盖现有函数的子函数?
实际用例
我正在为一个库编写一个包装器,为了避免样板代码,我想简化我的工作。
每个库函数都有一个前缀lib_
并返回一个错误代码。我想将前缀添加到当前函数并处理错误代码。这可以像这样简单:
def call():
fname = __libprefix__ + inspect.stack()[1][3]
return_code = getattr(__lib__, fname)(*args)
if return_code < 0: raise LibError(fname, return_code)
def foo():
call()
问题是 call 在某些情况下可能会有不同的行为。一些库函数不返回 error_code 所以这样写会更容易 这个:
def foo():
call(check_status=True)
或者在我看来更好(这是我开始考虑装饰器的地方):
@LibFunc(check_status=True)
def foo():
call()
在最后一个示例中,我应该将 foo
中的 call
声明为装饰器本身动态创建的子函数。
我的想法是使用这样的东西:
class LibFunc(object):
def __init__(self,**kwargs):
self.kwargs = kwargs
def __call__(self, original_func):
decorator_self = self
def wrappee( *args, **kwargs):
def call(*args):
fname = __libprefix__ + original_func.__name__
return_code = getattr(__lib__, fname)(*args)
if return_code < 0: raise LibError(fname, return_code)
print original_func
print call
# <<<< The part that does not work
setattr(original_func, 'call', call)
# <<<<
original_func(*args,**kwargs)
return wrappee
最初我很想在装饰器内部调用 call
以尽量减少编写:
@LibFunc():
foo(): pass
不幸的是,这不是一个选项,因为有时在调用
之前和之后应该做其他事情:
@LibFunc():
foo(a,b):
value = c_float()
call(a, pointer(value), b)
return value.value
我考虑过的另一个选择是使用 SWIG
,但这同样不是一个选项,因为我需要使用 SWIG 包装函数重建现有库。
最后但并非最不重要的一点是,我可能会从 SWIG
类型映射中获得灵感并将我的包装器声明为:
@LibFunc(check_exit = true, map = ('<a', '>c_float', '<c_int(b)')):
foo(a,b): pass
这对我来说似乎是最好的解决方案,但这是另一个话题和另一个问题......
最佳答案
您是否接受了装饰师的想法?因为如果您的目标是一堆模块级函数,每个函数都包装 somelib.lib_somefunctionname
,我不明白您为什么需要一个。
那些模块级名称不必是函数,它们只需要是可调用的。它们可以是一堆类实例,只要它们具有 __call__
方法即可。
我使用了两个不同的子类来确定如何处理返回值:
#!/usr/bin/env python3
import libtowrap # Replace with the real library name.
class Wrapper(object):
'''
Parent class for all wrapped functions in libtowrap.
'''
def __init__(self, name):
self.__name__ = str(name)
self.wrapped_name = 'lib_' + self.__name__
self.wrapped_func = getattr(libtowrap, self.wrapped_name)
self.__doc__ = self.wrapped_func.__doc__
return
class CheckedWrapper(Wrapper):
'''
Wraps functions in libtowrap that return an error code that must
be checked. Negative return values indicate an error, and will
raise a LibError. Successful calls return None.
'''
def __call__(self, *args, **kwargs):
error_code = self.wrapped_func(*args, **kwargs)
if error_code < 0:
raise LibError(self.__name__, error_code)
return
class UncheckedWrapper(Wrapper):
'''
Wraps functions in libtowrap that return a useful value, as
opposed to an error code.
'''
def __call__(self, *args, **kwargs):
return self.wrapped_func(*args, **kwargs)
strict = CheckedWrapper('strict')
negative_means_failure = CheckedWrapper('negative_means_failure')
whatever = UncheckedWrapper('whatever')
negative_is_ok = UncheckedWrapper('negative_is_ok')
请注意,包装器“功能”是在导入模块时分配的。它们位于顶级模块命名空间中,不被任何 if __name__ == '__main__'
测试隐藏。
在大多数情况下,它们的行为类似于函数,但会有细微差别。例如,我给每个实例一个 __name__
匹配它们分配给的名称,而不是 libtowrap
中使用的 lib_
前缀名称。 .. 但我复制了原始的 __doc__
,它可能引用了一个前缀名称,如 lib_some_other_function
。此外,使用 isinstance
测试它们可能会让人们大吃一惊。
有关装饰器的更多信息,以及像我上面提到的那些许多更烦人的小差异,请参阅 Graham Dumpleton 的半小时讲座“创建装饰器的高级方法” (PyCon US 2014;slides)。他是 wrapt
模块(Python Package Index ; Git Hub ; Read the Docs )的作者,该模块纠正了所有(?)常见的装饰器不一致问题。它可能会完全解决您的问题(__doc__
中显示的旧 lib_
样式名称除外)。
关于python - 从装饰器覆盖函数的子函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33593113/