0

I have a class A with dozen of functions. Now I want to extend those functions in class B but apply to a list of A. See the example below.

class A:
  def __init__(self):
      # initiate self.data

  def func1(self, *args, **kargs):
      # do things with self.data
  
  # there are a lot more functions like func1

class B:
  def __init__(self, n):
      self.list_of_A = [A() for i in range n]

  def func1(self, *args, **kargs):
      # do something here

      for a in self.list_of_A:
          a.func1(*args, **kargs)

      # do something here

The requirements are:

  1. Because A has a lot of functions and it is impractical to rewrite all of them in B, I would like to have B.func1 to be generated automatically (e.g. through some wrapper function mechanism).
  2. B.func1 can have the same docstring as A.func1.

Can someones suggest me how to do this? Thanks!

lcb
  • 999
  • 1
  • 6
  • 10
  • 2
    how about the use of `super` in class inheritance? [post1](https://stackoverflow.com/questions/3277367/how-does-pythons-super-work-with-multiple-inheritance) or [more recent post](https://stackoverflow.com/questions/45898310/python-class-methods-and-inheritance) – willwrighteng Sep 13 '21 at 22:40
  • 1
    Your questions are getting closed because your descriptions are totally unclear. You are using terminology incorrectly. There *is no inheritance here*. Inheritance is a relationship between classes. Another example, "a set of". There are no sets here. You have a list. This may all sound nitpicky, but when you use standard terminology in a non-standard way, it makes communication difficult. – juanpa.arrivillaga Sep 13 '21 at 22:45
  • thanks. updated the description to make it clearer. – lcb Sep 13 '21 at 23:16
  • @martineau I think you closed this question but the answer that you pointed out does not solve this problem. Specifically, in this case, I don't have access to `A` class so I cannot wrap it. Also even if I do so, it still does not solve the problem stated in this ticket. Pls help open this. Thanks. – lcb Sep 14 '21 at 13:47
  • I don't really agree but will vote to reopen your question. – martineau Sep 14 '21 at 14:03
  • I now regret reopening your question, because it really **is** a duplicate of [How to wrap every method of a class?](https://stackoverflow.com/questions/11349183/how-to-wrap-every-method-of-a-class?noredirect=1&lq=1) as [my answer](https://stackoverflow.com/a/69184616/355230) below illustrates. – martineau Sep 15 '21 at 13:30

2 Answers2

1

There are many ways you could approach this. Here's one approach that I would hesitate to use because it highly couples both of these classes, but I'm showing it to you because it is an example of generating methods dynamically:

class B:
    def __init__(self, n):
        self.list_of_A = [A() for i in range(n)]

     
    def _apply_a_method(self, method_name, *args, **kargs):
        # do something here

        for a in self.list_of_A:
            getattr(a, method_name)(*args, **kargs)
        
        # do something here
   
    @classmethod
    def build_api(cls, method_names):
        def _make_wrapper(method_name):
            def _wrapper(self, *args, **kwargs):
                return self._apply_a_method(method_name, *args, **kwargs)
            return _wrapper

        for method_name in method_names:
            setattr(cls, method_name, _make_wrapper(method_name))


B.build_api(["func1", "func2", "func3"]) # choose the functions you want

Note, this has nothing to do with inheritance. Inheritance is something that happens when you subclass from another class. That isn't what you are doing here. You are basically asking "how do I dynamically wrap various methods from class A into methods in class B".

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
0

Here's proof that not only is your question a duplicate like I said it was of that other one, but also that it indeed does solve the problem.

A metaclass approach is totally fitting because the essence of what you want to do is create new classes, and the instances of a metaclass are classes. The fact that these class instance are based on an existing ones means that it can be done "extending" or "wrapping" the methods of the existing one is an additional detail which making the answer to the duplicate question that much more suitable.

Also note that this technique is more generic and "automatic" than @juanpa.arrivillaga's answer in the sense that you don't have list the methods you want (since it does them all). And that it doesn't require changing class A as you claimed in a comment — although I did change it from what's in your question in order to have something more suitable for development and testing proposes.

Also note that the classes created are subclasses of the the base class, A in the example code, in terms of inheritance (not the metaclass).

from functools import wraps
from types import FunctionType


def wrapper(method):
    @wraps(method)
    def wrapped(self, *args, **kwargs):
        for instance in self.base_instances:
            method(instance, *args, **kwargs)

    return wrapped


class MyMetaClass(type):
    def __new__(meta, classname, bases, classdict):
        assert len(bases) == 1, 'A single base class is required'

        # Define an `__init__()` method.
        Base = bases[0]
        def __init__(self, n):
            self.base_instances = [Base() for _ in range(n)]

        # Create methods by wrapping those of base class.
        new_classdict = {key: wrapper(value) if isinstance(value, FunctionType) else value
                            for key, value in vars(Base).items()
                                if isinstance(value, FunctionType)}
        new_classdict['__init__'] = __init__  # Replace.

        # Construct the class using new class dictionary.
        return type.__new__(meta, classname, bases, new_classdict)

class A:
    def __init__(self):
        self.data = list(range(1, 5))  # Initialize self.data

    def func1(self, *args, **kargs):
        print(', '.join(str(i) for i in self.data))  # Do things with self.data

    def func2(self, *args, **kargs):
        print(f'sum: {sum(self.data)}')  # Do things with self.data

    # There are lots more functions like func1 & 2
    ...

class B(A, metaclass=MyMetaClass):
    pass

b = B(3)
b.func1()
b.func2()
martineau
  • 119,623
  • 25
  • 170
  • 301