包装同类Python对象

时间:2019-03-17 19:46:09

标签: 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
    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)
    
  • 使用列表和for循环。这会更改对象上的API。就是说,这是我无法找到更优雅的解决方案。在这种情况下,WrappedApi类将不存在。

    代码示例:

    wrapped_apis = [OriginalApi(), OriginalApi()]
    for wrapped_api in wrapped_apis:
      wrapped_api.do_something(1, 2, True)
    
  • 我尝试使用 Python Object Wrapper ,但我看不到如何用相同的参数调用多个子对象。

对于对用例感到好奇的人,它实际上是几个matplotlib axes对象的集合。我不想重新实现整个axes API(它很大),也不想更改所有在轴上进行调用的代码(例如plotstep,等)

2 个答案:

答案 0 :(得分:7)

如果您仅实现方法,则通用__getattr__可以解决问题

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 = Wrapper([[], [], []])之后使用x.append(12),所有三个列表对象的最后一个元素将为12。

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

答案 1 :(得分:1)

我认为您在这里有正确的想法

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)