python - 从装饰器覆盖函数的子函数?

标签 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()

问题是 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 2014slides)。他是 wrapt 模块(Python Package Index ; Git Hub ; Read the Docs )的作者,该模块纠正了所有(?)常见的装饰器不一致问题。它可能会完全解决您的问题(__doc__ 中显示的旧 lib_ 样式名称除外)。

关于python - 从装饰器覆盖函数的子函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33593113/

相关文章:

Python Scrapy 不会重试超时连接

python - 使用 FuncAnimation 在 matplotlib 中动画化等高线图

python GTK : make about dialog without logo

python - 在 Linux 上监听 python 中的全局组合键

python - lambda self : do 是什么意思

python - 排序 sys.path : first virtualenv, then/usr

Python - 使用未知分隔符拆分数值字符串

python - 比较两个 DataFrame 并过滤仅在第二个中列出的项目

python - 使用相同的参数运行相同的函数 10 次并获得结果

python - 在继续循环的同时将值输出到另一个函数