覆盖功能'装饰者的子功能?

时间:2015-11-08 10:49:01

标签: python

让我们考虑这段代码,我想用装饰器动态创建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

这对我来说似乎是最好的解决方案,但这是另一个话题和另一个问题......

1 个答案:

答案 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__ - 样式名称除外。