包装同构Python对象

包装同构Python对象,python,Python,我正在寻找一种方法来拥有一个同质对象的集合,将它们包装在另一个对象中,但是让包装器对象具有与原始对象相同的API,并将相应的API调用转发给其对象成员 class OriginalApi: def __init__(self): self.a = 1 self.b = "bee" def do_something(self, new_a, new_b, put_them_together=None): self.a = new_a or self.a s

我正在寻找一种方法来拥有一个同质对象的集合,将它们包装在另一个对象中,但是让包装器对象具有与原始对象相同的API,并将相应的API调用转发给其对象成员

class OriginalApi:
  def __init__(self):
    self.a = 1
    self.b = "bee"

  def do_something(self, new_a, new_b, put_them_together=None):
    self.a = new_a or self.a
    self.b = new_b or self.b

    if put_them_together is not None:
      self.b = "{}{}".format(self.a, self.b)

  # etc.

class WrappedApi:
  def __init__(self):
    self.example_1 = OriginalApi()
    self.example_2 = OriginalApi()
已经考虑了一些可能的解决方案,但不充分:

  • 重写整个API为什么不?不充分,因为API相当大且正在扩展。必须在多个地点维护API是不现实的

    代码示例:

    class WrappedApi:
      def __init__(self):
        self.example_1 = OriginalApi()
        self.example_2 = OriginalApi()
    
      def do_something(self, new_a, new_b, put_them_together=None):
        self.example_1.do_something(new_a, new_b, put_them_together)
        self.example_2.do_something(new_a, new_b, put_them_together)
    
    wrapped_apis = [OriginalApi(), OriginalApi()]
    for wrapped_api in wrapped_apis:
      wrapped_api.do_something(1, 2, True)
    
  • 使用列表和for循环这会更改对象上的API。也就是说,如果我找不到更优雅的东西,这就是备份解决方案。在这种情况下,
    WrappedApi
    类将不存在

    代码示例:

    class WrappedApi:
      def __init__(self):
        self.example_1 = OriginalApi()
        self.example_2 = OriginalApi()
    
      def do_something(self, new_a, new_b, put_them_together=None):
        self.example_1.do_something(new_a, new_b, put_them_together)
        self.example_2.do_something(new_a, new_b, put_them_together)
    
    wrapped_apis = [OriginalApi(), OriginalApi()]
    for wrapped_api in wrapped_apis:
      wrapped_api.do_something(1, 2, True)
    
  • 我试着用 ,但我不知道如何让它调用具有相同参数的多个子对象


对于任何对用例感兴趣的人来说,它实际上是几个matplotlib
对象的集合。我不想重新实现整个
axes
API(它很大),也不想更改在axes上调用的所有代码(比如
plot
step
,等等)

如果您只实现方法,那么通用的
\ugetattr\uuuuu
就可以完成这个任务

class Wrapper: 
    def __init__(self, x): 
        self.x = x 
    def __getattr__(self, name): 
        def f(*args, **kwargs): 
            for y in self.x: 
                getattr(y, name)(*args, **kwargs) 
        return f
例如,在调用
x.append(12)
之后,使用
x=Wrapper([],[],[],[])
,所有三个列表对象都将12作为最后一个元素


请注意,返回值将始终为
None
。。。一个选项可以是收集返回值并将其作为列表返回,但这当然会“破坏API”。

我认为您的想法是正确的

wrapped_apis = [OriginalApi(), OriginalApi()]
for wrapped_api in wrapped_apis:
    wrapped_api.do_something(1, 2, True)
您可以通过继承
list
来定义包装器类,然后在创建包装器类后处理对其项的API调用

class WrapperClass(list):
    def __init__(self, api_type):
        self.api_type = api_type

        for func in dir(api_type):
            if callable(getattr(api_type, func)) and not func.startswith("__"):
                setattr(self, func, lambda *args, **kwargs: 
                    [getattr(o, func)(*args, **kwargs) for o in self])

w = WrapperClass(OriginalApi)
o1, o2 = [OriginalApi()]*2
w.append(o1)
w.append(o2)
print(w.do_something(1, 2, True))
# [None, None]
print(w[0].b)
# 12
print(w[1].b)
# 12
print(o1.b)
# 12
在这里,我将迭代API类中的每个方法,并在包装器类中创建一个将其参数应用于其所有列表项的方法。然后返回一个包含结果的列表

不用说,您可能应该验证附加到此
WrapperClass
的新对象的类型

def append(self, item):
    if not isinstance(item, self.api_type):
        raise TypeError('Wrong API type. Expected %s'.format(self.api_type))
    super(WrapperClass, self).append(item)

您希望如何处理序列协议?对包装进行索引是应该为您提供其中一个包装项目,还是为所有包装项目进行索引的结果?这在一般情况下会很复杂。这是指
def\uuu getitem\uuuu(self,i):
?如果是这样的话,我没有考虑过这一点,但我认为这两种方法都适用于我的情况。不过,这只适用于方法和其他可调用属性。将结果收集到一个列表中是很简单的,但是很难区分哪些情况是您有一个
None
s列表并应该返回列表,或者一个
None
s列表并应该返回
None
@jornsharpe:当然可以,但是,在不改变API的情况下,很难复用需要读取的协议。如果包含的对象的答案不相同,那么
len(x)
应该返回什么?好吧,非常好!正如我上面所说,在一般情况下,这会变得非常复杂。关于返回值,这是一个很好的观点。我希望我使用的
matplotlib
能够忽略大部分返回值。如果没有呢?然后。。。好。。。这会变得更加困难。如果是这样的话,我会问一个新问题。