让我们考虑这段代码,我想用装饰器动态创建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()
问题是在某些情况下呼叫可能会有不同的行为。某些库函数不会返回error_code,因此更容易编写它 这个:
def foo():
call(check_status=True)
或者我认为好多了(这是我开始考虑装饰器的地方):
@LibFunc(check_status=True)
def foo():
call()
在最后一个例子中,我应该在call
内声明foo
作为由装饰器本身动态创建的子函数。
想法是使用这样的东西:
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
不幸的是,这不是一个选项,因为其他事情应该在call
之前和之后完成:
@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
这对我来说似乎是最好的解决方案,但这是另一个话题和另一个问题......
答案 0 :(得分:1)
你嫁给装饰师的想法吗?因为如果你的目标是一堆模块级函数,每个函数都包含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')
请注意包装器&#34;功能&#34;在导入模块时分配。它们位于顶级模块名称空间中,并且不被任何if __name__ == '__main__'
测试隐藏。
它们在大多数情况下都会起到类似功能的作用,但会有细微差别。例如,我为每个实例提供了一个与__name__
匹配的名称,而不是lib_
- libtowrap
中使用的前缀名称...但我复制了原始实例__doc__
,可能会引用lib_some_other_function
这样的前缀名称。此外,使用isinstance
对它们进行测试可能会让人感到惊讶。
有关装饰者的更多信息,以及许多更令人烦恼的小差异,如我上面提到的那些,请参阅Graham Dumpleton的半小时讲座&#34; 高级方法创建装饰器&#34; (PyCon US 2014; slides)。他是 wrapt
模块(Python Package Index; Git Hub; Read the Docs)的作者,它修正了所有(?)常见的装饰器不一致性。它可能完全解决您的问题(lib_
)中显示的旧__doc__
- 样式名称除外。