TinyTheBrontosaurus
TinyTheBrontosaurus

Reputation: 4810

Wrapping homogeneous Python objects

I'm looking for a way to have a collection of homogeneous objects, wrap them in another object, but have the wrapper object have the same API as the original and forward the corresponding API call to its object members.

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()

Some possible solutions that have been considered, but are inadequate:

And for anyone curious about the use case, it's actually a collection of several matplotlib axes objects. I don't want to reimplement to entire axes API (it's big), and I don't want to change all the code that makes calls on axes (like plot, step, etc.)

Upvotes: 10

Views: 669

Answers (2)

darksky
darksky

Reputation: 2090

I think you have the right idea here

wrapped_apis = [OriginalApi(), OriginalApi()]
for wrapped_api in wrapped_apis:
    wrapped_api.do_something(1, 2, True)

You can define your wrapper class by inheriting from list and then handle the API calls to its items once it is created.

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

Here, I'm iterating every method in your API class and creating a method in the wrapper class that applies its arguments to all its list items. It then returns a list comprehension consisting of the results.

Needless to say, you should probably validate the type of a new object being appended to this WrapperClass like so,

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)

Upvotes: 1

6502
6502

Reputation: 114539

If you're only implementing methods then a generic __getattr__ can do the trick

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

For example with x = Wrapper([[], [], []]) after calling x.append(12) all the three list objects will have 12 as last element.

Note that the return value will always be None... an option could be collecting return values and returning them as a list but this of course would "break the API".

Upvotes: 7

Related Questions