【问题标题】:Python: wrapping method invocations with pre and post methodsPython:使用 pre 和 post 方法包装方法调用
【发布时间】:2008-11-03 08:23:10
【问题描述】:

我正在实例化一个 A 类(我从某人那里导入 否则,所以我不能修改它)到我的 X 类中。

有没有办法可以拦截或包装对 A 中方法的调用? 即,在下面的代码中我可以调用

x.a.p1()

得到输出

X.pre
A.p1
X.post

许多 TIA!

class A:
    # in my real application, this is an imported class
    # that I cannot modify
    def p1(self): print 'A.p1'

class X:
    def __init__(self):
        self.a=A()
    def pre(self): print 'X.pre'
    def post(self): print 'X.post'

x=X()
x.a.p1()

【问题讨论】:

    标签: python metaprogramming


    【解决方案1】:

    这是我和我的同事提出的解决方案:

    from types import MethodType
    
    class PrePostCaller:
        def __init__(self, other):
            self.other = other
    
        def pre(self): print 'pre'
        def post(self): print 'post'
    
        def __getattr__(self, name):
            if hasattr(self.other, name):
                func = getattr(self.other, name)
                return lambda *args, **kwargs: self._wrap(func, args, kwargs)
            raise AttributeError(name)
    
        def _wrap(self, func, args, kwargs):
            self.pre()
            if type(func) == MethodType:
                result = func( *args, **kwargs)
            else:
                result = func(self.other, *args, **kwargs)
            self.post()
            return result
    
    #Examples of use
    class Foo:
        def stuff(self):
            print 'stuff'
    
    a = PrePostCaller(Foo())
    a.stuff()
    
    a = PrePostCaller([1,2,3])
    print a.count()
    

    给予:

    pre
    stuff
    post
    pre
    post
    0
    

    因此,在创建对象实例时,请使用 PrePostCaller 对象包装它。之后,您继续使用该对象,就好像它是包装对象的实例一样。使用此解决方案,您可以在每个实例的基础上进行包装。

    【讨论】:

      【解决方案2】:

      无声无息的解决方案是为 A 类编写一个包装类来实现这一点。

      【讨论】:

      • 是的,但我想避免,因为我的真实程序包含的类实例比我想包装的要多。
      • 好吧,同样调用包装器,但将其放在您自己的命名空间中。它将有效地用作替代品,您不必更改代码。
      【解决方案3】:

      您可以只修改 A 实例并将 p1 函数替换为包装函数:

      def wrapped(pre, post, f):
          def wrapper(*args, **kwargs):
              pre()
              retval = f(*args, **kwargs)
              post()
              return retval
          return wrapper
      
      class Y:
          def __init__(self):
              self.a=A()
              self.a.p1 = wrapped(self.pre, self.post, self.a.p1)
      
          def pre(self): print 'X.pre'
          def post(self): print 'X.post'
      

      【讨论】:

        【解决方案4】:

        正如其他人所提到的,包装器/装饰器解决方案可能是最简单的解决方案。出于您指出的相同原因,我不建议修改包装类本身。

        如果您有许多外部类,您可以编写代码生成器来为您生成包装类。由于您在 Python 中执行此操作,您甚至可以将生成器作为程序的一部分来实现,在启动时生成包装器,或其他方式。

        【讨论】:

          【解决方案5】:

          我最近刚刚阅读了有关 python 中的装饰器的信息,我还不了解它们,但在我看来它们可以解决您的问题。请参阅 Bruce Eckel 对装饰器的介绍: http://www.artima.com/weblogs/viewpost.jsp?thread=240808

          他在那里还有一些关于该主题的帖子。

          编辑:三天后我偶然发现了这篇文章,它展示了如何在没有装饰器的情况下完成类似的任务,它有什么问题,然后介绍了装饰器并开发了一个非常完整的解决方案: http://wordaligned.org/articles/echo

          【讨论】:

            【解决方案6】:

            这是我在 comp.lang.python 上从 Steven D'Aprano 那里收到的内容。

            # Define two decorator factories.
            def precall(pre):
                def decorator(f):
                    def newf(*args, **kwargs):
                        pre()
                        return f(*args, **kwargs)
                    return newf
                return decorator
            
            def postcall(post):
                def decorator(f):
                    def newf(*args, **kwargs):
                        x = f(*args, **kwargs)
                        post()
                        return x
                    return newf
                return decorator
            

            现在您可以根据需要修改 A 类补丁。这可能不是一个伟大的 在生产代码中执行此操作的想法,因为它会在任何地方影响 A 类。 [这对我的应用程序来说没问题,因为它基本上是一个协议转换器,并且每个类都有一个正在处理的实例。]

            class A:
                # in my real application, this is an imported class
                # that I cannot modify
                def p1(self): print 'A.p1'
            
            class X:
                def __init__(self):
                    self.a=A()
                    A.p1 = precall(self.pre)(postcall(self.post)(A.p1))
                def pre(self): print 'X.pre'
                def post(self): print 'X.post'
            
            
            x=X()
            x.a.p1()
            

            给出想要的结果。

            X.pre
            A.p1
            X.post
            

            【讨论】:

            • 不修补类,只修补实例:self.a.p1 = precall(self.pre)(postcall(self.post)(self.a.p1))
            猜你喜欢
            • 2019-02-21
            • 2015-11-10
            • 1970-01-01
            • 2012-08-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多