6

我想用相同的包装器在 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

当然,当方法多于aandb时,这会变得一团糟。似乎应该有更简单的东西。显然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()

我考虑过__getattr__在 mixin 中使用,但是当然,因为已经定义了a和之类的方法,所以永远不会调用它。b

我也考虑过使用__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

4 回答 4

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
于 2013-05-22T18:15:01.470 回答
3

您可以使用装饰器和检查来做到这一点:

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
于 2013-05-22T17:34:56.450 回答
2

我可以想到两个一般方向,它们对您的情况有用。

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

编辑:(我想到的实际包装/检查代码与@girasquid 在他的回答中的类似,但连接是使用装饰而不是混合/继承完成的,我认为这更灵活和健壮。)

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

于 2013-05-22T17:33:54.197 回答
1

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

基本概念是装饰器是一个以函数为参数并返回函数的函数:

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
于 2013-05-22T17:06:29.993 回答