niko
niko

Reputation: 1168

How to dynamically inherit at initialization time?

I have the following class structure:

class Base:
  def z(self):
    raise NotImplementedError()

class A(Base):
  def z(self):
    self._x()
    return self._z()

  def _x(self):
    # do stuff

  def _a(self):
    raise NotImplementedError()

class B(Base)
  def z(self):
    self._x()
    return self._z()

  def _x(self):
    # do stuff

  def _z(self):
    raise NotImplementedError()


class C(A):
  def _z(self):
    print(5)

class D(B):
  def _z(self):
    print(5)

The implementation of C(A) and D(B) is exactly the same and does not really care which class it inherits from. The conceptual difference is only in A and B (and these need to be kept as separate classes). Instead of writing separate definitions for C and D, I want to be able to dynamically inherit from A or B based on an argument provided at time of creating an instance of C/D (eventually C and D must be the same name).

It seems that metaclasses might work, but I am not sure how to pass an __init__ argument to the metaclass __new__ (and whether this will actually work). I would really prefer a solution which resolves the problem inside the class.

Upvotes: 2

Views: 64

Answers (1)

DeepSpace
DeepSpace

Reputation: 81594

Have you considered using composition instead of inheritance? It seems like it is much more suitable for this use case. See the bottom of the answer for details.

Anyway,

  • class C(A): ......... class C(B): ..... is not even valid, and will result with only class C(B) getting defined.

  • I'm not sure a metaclass will be able to help you here. I believe the best way would be to use type but I'd love to be corrected.

A solution using type (and probably misusing locals() but that's not the point here)

class A:
    def __init__(self):
        print('Inherited from A')

class B:
    def __init__(self):
        print('Inherited from B')

class_to_inherit = input()  # 'A' or 'B"

C = type('C', (locals()[class_to_inherit],), {})

C()

'A' or 'B'
>> A
Inherited from A

'A' or 'B'
>> B
Inherited from B


Composition

Tracking back to the question in the beginning of my answer, you state yourself that the implementation of both "C(A)" and "C(B)" is identical and they don't actually care about A or B. It seems more correct to me to use composition. Then you can do something along the lines of:

class A: pass

class B: pass

class C:
    def __init__(self, obj):  # obj is either A or B instance, or A or B themselves
        self.obj = obj  # or self.obj = obj() if obj is A or B themselves

c = C(A())  #  or c = C(A) 

In case C should expose the same API as A or B, C can overwrite __getattr__:

class A:
    def foo(self):
        print('foo')

class C:
    def __init__(self, obj):
        self.obj = obj

    def __getattr__(self, item):
        return getattr(self.obj, item)

C(A()).foo()
# foo

Upvotes: 1

Related Questions