Python:使用pre和post方法包装方法调用

时间:2008-11-03 08:23:10

标签: python metaprogramming

我正在实例化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()

6 个答案:

答案 0 :(得分:6)

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

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对象进行包装。之后,继续使用该对象,就好像它是包装对象的实例一样。使用此解决方案,您可以基于每个实例进行包装。

答案 1 :(得分:1)

no-whistles-or-bells解决方案是为A类编写一个包装类来实现这一点。

答案 2 :(得分:1)

您可以修改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'

答案 3 :(得分:1)

我最近刚刚阅读了python中的装饰器,我还没有理解它们,但在我看来它们可以解决你的问题。请参阅布鲁斯·埃克尔介绍装饰者: http://www.artima.com/weblogs/viewpost.jsp?thread=240808

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

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

答案 4 :(得分:1)

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

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

答案 5 :(得分:0)

这是我在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