通过共享对象的__dict__属性创建视图

Rém*_*net 6 python python-3.x

我目前正在寻找一种在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
Run Code Online (Sandbox Code Playgroud)

这使得代码相当短,并且修改A1,A2等类不需要对补丁进行任何更改,这非常好。但是,我显然担心__dict__多个类之间共享的含义。我的问题是:

  • 您是否通过其他方式略微改进了上述方法或完全不同的方法来实现我的目标?(请注意,在不对补丁程序进行任何更改的情况下,允许进行类修改/添加是很困难的)
  • 当多个对象共享同一对象时,应该注意哪些陷阱__dict__
  • 通过显式提供the __dict__和the ,创建对象的最佳方式(减少不良程度)是__class__什么?
  • 奖励:如以上示例所示,我需要在包装器上附加一条额外的信息。因为我无法将其添加到对象中__dict__,所以我被迫将其作为类成员或“捕获”变量添加到类本身。我还有其他位置吗?理想情况下,我希望避免为每个名称实例创建一个新类(我只想为每个原始类动态创建一个新类)

其他考虑的解决方案:

代理对象(请参阅juanpa.arrivillaga答案)几乎是一种完美的解决方案,但是当修补函数被另一个函数在内部调用时,它就不够用了。具体来说,在上面发布的代码中,对该meeting函数的最终调用将使用原始实现而不是已修补的实现。有关示例,请参见https://repl.it/repls/OrneryLongField

jua*_*aga 2

在我看来,您想要一个代理对象,这就是视图通常的样子。简而言之,该模式可以像这样简单(对于只读代理):

class View:
    def __init__(self, obj):
        self._obj = obj
    def __getattr__(self, attr):
        return getattr(self._obj, attr)
Run Code Online (Sandbox Code Playgroud)

好处__getattr__是它仅在未找到属性时调用。如果你想要写访问,那么你需要更加小心,并实现__setattribute__总是被调用的,并且很容易无意中触发无限递归。

请注意,因为我们正在被代理的对象上使用getattr,所以我们不必管理重新创建接口!方法解析、描述符协议(so property)、继承等都由通常的机制处理:

In [1]: class View:
   ...:     def __init__(self, obj):
   ...:         self._obj = obj
   ...:     def __getattr__(self, attr):
   ...:         return getattr(self._obj, attr)
   ...:

In [2]: class UrFoo:
   ...:     def __init__(self, value):
   ...:         self.value = value
   ...:     def foo(self):
   ...:         return self.value
   ...:

In [3]: class Foo(UrFoo):
   ...:     def frognicate(self):
   ...:         return self.value * 42
   ...:     @property
   ...:     def baz(self):
   ...:         return 0
   ...:

In [4]: foo = Foo(8)

In [5]: view = View(foo)

In [6]: view.foo()
Out[6]: 8

In [7]: view.frognicate()
Out[7]: 336

In [8]: view.baz
Out[8]: 0
Run Code Online (Sandbox Code Playgroud)