我想用相同的包装器在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
当然,当有比a
和b
更多的方法时,这就变得一团糟。似乎应该有更简单的东西。显然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__
,但是由于已经定义了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__
中的闭包是正确的方法。我很感激你的想法。
答案 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