包装调用Python类的方法

时间:2013-05-22 16:57:50

标签: python python-2.7

我想用相同的包装器在Python中包装许多类方法。

从概念上讲,它在最简单的场景中看起来像这样:

x = 0 # some arbitrary context

class Base(object):
    def a(self):
       print "a x: %s" % x

    def b(self):
       print "b x: %s" % x

 class MixinWithX(Base):
     """Wrap"""
     def a(self):
         global x
         x = 1
         super(MixinWithX, self).a()
         x = 0

     def b(self):
         global x
         x = 1
         super(MixinWithX, self).a()
         x = 0

当然,当有比ab更多的方法时,这就变得一团糟。似乎应该有更简单的东西。显然x可以在装饰器中进行修改但是仍然有一个很长的垃圾列表,而不是上面的代码:

 from functools import wraps
 def withx(f):
     @wraps(f) # good practice
     def wrapped(*args, **kwargs):
         global x
         x = 1
         f(*args, **kwargs)
         x = 0
     return wrapped

 class MixinWithX(Base):
     """Wrap"""
     @withx
     def a(self):
         super(MixinWithX, self).a()

     @withx
     def b(self):
         super(MixinWithX, self).b()

我考虑过在mixin中使用__getattr__,但是由于已经定义了ab等方法,因此永远不会调用它。

我还考虑过使用__getattribute__,但它返回属性,而不是包装调用。我想__getattribute__可以返回一个闭包(下面的例子),但我不确定设计是多么合理。这是一个例子:

 class MixinWithX(Base):
    # a list of the methods of our parent class (Base) that are wrapped
    wrapped = ['a', 'b']

    # application of the wrapper around the methods specified
    def __getattribute__(self, name):
       original = object.__getattribute__(self, name)
       if name in wrapped:
          def wrapped(self, *args, **kwargs):
              global x
              x = 1 # in this example, a context manager would be handy.
              ret = original(*args, **kwargs)
              x = 0
              return ret
          return wrapped
       return original

在我看来,Python中可能存在一些内容,可能会减少手动复制要包装的父类的每个方法的需要。或许__getattribute__中的闭包是正确的方法。我很感激你的想法。

4 个答案:

答案 0 :(得分:4)

这是我的尝试,它允许更简洁的语法...

x = 0 # some arbitrary context

# Define a simple function to return a wrapped class
def wrap_class(base, towrap):
    class ClassWrapper(base):
        def __getattribute__(self, name):
            original = base.__getattribute__(self, name)
            if name in towrap:
                def func_wrapper(*args, **kwargs):
                    global x
                    x = 1
                    try:
                        return original(*args, **kwargs)
                    finally:
                        x = 0
                return func_wrapper
            return original
    return ClassWrapper


# Our existing base class
class Base(object):
    def a(self):
       print "a x: %s" % x

    def b(self):
       print "b x: %s" % x


# Create a wrapped class in one line, without needing to define a new class
# for each class you want to wrap.
Wrapped = wrap_class(Base, ('a',))

# Now use it
m = Wrapped()
m.a()
m.b()

# ...or do it in one line...
m = wrap_class(Base, ('a',))()

...输出......

a x: 1
b x: 0

答案 1 :(得分:3)

您可以使用装饰器和inspect执行此操作:

from functools import wraps
import inspect

def withx(f):
    @wraps(f)
    def wrapped(*args, **kwargs):
        print "decorator"
        x = 1
        f(*args, **kwargs)
        x = 0
    return wrapped

class MyDecoratingBaseClass(object):
    def __init__(self, *args, **kwargs):
        for member in inspect.getmembers(self, predicate=inspect.ismethod):
            if member[0] in self.wrapped_methods:
                setattr(self, member[0], withx(member[1]))

class MyDecoratedSubClass(MyDecoratingBaseClass):
    wrapped_methods = ['a', 'b']
    def a(self):
        print 'a'

    def b(self):
        print 'b'

    def c(self):
        print 'c'   

if __name__ == '__main__':
    my_instance = MyDecoratedSubClass()
    my_instance.a()
    my_instance.b()
    my_instance.c()

输出:

decorator
a
decorator
b
c

答案 2 :(得分:2)

我可以想到两个在你的情况下有用的一般方向。

一个是使用类装饰器。编写一个接受类的函数,并返回一个具有相同方法集的类,进行修饰(通过调用type(...)创建一个新类,或者通过更改输入类)。

编辑:(我想到的实际包装/检查代码类似于 @girasquid在他的回答中有什么,但连接是使用装饰而不是mixin / inheritance完成的,我认为它更灵活,更健壮。)

这让我想到了第二个选项,即使用元类,它可能更干净(如果你不习惯使用元类,那就更难了)。如果您无权访问原始类的定义,或者不想更改原始定义,则可以对原始类进行子类化,在派生类上设置元类。

答案 3 :(得分:1)

有一个解决方案,它被称为装饰器。谷歌“蟒蛇装饰者”获取大量信息。

基本概念是装饰器是一个函数,它将函数作为参数,并返回一个函数:

def decorate_with_x(f)
    def inner(self):
         self.x = 1 #you must always use self to refer to member variables, even if you're not decorating
         f(self)
         self.x = 0
    return inner

class Foo(object):

     @decorate_with_x # @-syntax passes the function defined on next line
                      # to the function named s.t. it is equivalent to 
                      # foo_func = decorate_with_x(foo_func)
     def foo_func(self):
         pass