我目前正在寻找一种可维护且易于使用的方式,在 Python 对象上创建“视图”。具体而言,我有一个非常大的类集合,它们共享几个常见方法,我想包装这些类的实例以修改这些方法的行为。
当然,我可以为每个类创建一个新类,遵循包装器模式,重新定义整个接口并将每个方法重定向到原始对象,除了我希望覆盖的那些方法。由于需要大量的代码和在任何类更改时所需的维护,这是不切实际的。
一些尝试表明,我可以通过大量使用元类和内省来生成包装器,以“重新创建”包装器对象中的接口,但这被证明是相当可怕的,特别是如果 A 具有属性(未包含代码)。
第二次尝试表明,可以通过共享__dict__
属性并覆盖__class__
实现较少的代码。这导致以下代码 (https://repl.it/repls/InfantileAshamedProjector):
##############################
# Existing code
##############################
class A1:
def __init__(self, eggs):
self.eggs = eggs
# Lots of complicated functions and members
def hello(self):
print ("hello, you have %d eggs" % self.eggs)
def meeting(self):
self.hello()
print ("goodbye")
# Lots of complicated functions calling hello.
# Lots of A2, A3, A4 with the same pattern
##############################
# "Magic" code for view generation
##############################
class FutureView:
pass
def create_view(obj, name):
class View(obj.__class__):
def hello(self):
print ("hello %s, you have %d eggs" % (name, self.eggs))
view = FutureView()
view.__dict__ = obj.__dict__
view.__class__ = View
return view
##############################
# Sample of use
##############################
a = A1(3)
a.hello() # Prints hello, you have 3 eggs
v = create_view(a, "Bob")
v.hello() # Prints hello Bob, you have 3 eggs
a.eggs = 5
a.hello() # Prints hello, you have 5 eggs
v.hello() # Prints hello Bob, you have 5 eggs
a.meeting() # Prints hello, you have 5 eggs. Goodbye
v.meeting() # Prints hello Bob, you have 5 eggs. Goodbye
这样可以使代码非常简短,修改A1,A2等类不需要对补丁进行任何更改,这非常好。然而,我显然担心在多个类之间共享__dict__
的影响。我的问题是:
- 您是否看到其他实现目标的方法,无论是稍微改进上述方法还是完全不同的方法?(请注意,允许修改/添加类而不需要对修补机制进行任何更改是一项困难要求)
- 当我有多个对象共享相同的
__dict__
时,应该注意哪些陷阱? - 通过显式提供
__dict__
和__class__
,创建对象的最佳(少坏一点?)方式是什么? - 奖励:如上例所示,我需要将一个额外的信息附加到我的包装器中。因为我不能将其添加到对象的
__dict__
中,所以我被迫将其添加到类本身中,无论是作为类成员还是“捕获”变量。还有其他位置可以放置它吗?理想情况下,我想避免为每个名称实例都创建一个新类(我只想为每个原始类动态创建新类)。
其他考虑的解决方案:
代理对象(请参见juanpa.arrivillaga答案)是一种几乎完美的解决方案,但在补丁函数被另一个函数内部调用时,它表现不佳。具体来说,在上面发布的代码中,对meeting
函数的最终调用将使用原始实现而不是已打补丁的实现。有关示例,请参见https://repl.it/repls/OrneryLongField。
__dict__
可能并不重要,因为没有任何改变。但如果它们会改变,这可能会导致意想不到的行为(在各种可能的解决方案下)。 - BrenBarnwith
块时简单地修改方法,然后在结束时将原始版本放回去。这会使按照你在示例中所做的方式交替调用方法变得麻烦(即先调用a,然后v,然后a,然后v等,而不是a,a,a,a,v,v,v,v),但它会在某种程度上隔离修改后的行为,这似乎更加清晰。 - BrenBarnA1
及其同类)应该被修改以支持这一点;这样你就可以通过向__init__
提供参数来提供所有相关状态,而不必通过共享__dict__
来复制所有状态。在我看来,你的解决方案基本上是对类没有足够能力复制和访问相关内部状态的问题的一种解决方法。 - BrenBarn