Вызов __init__
в любом родительском классе не меняет структуру наследования ваших классов, нет. Вы только изменяете, какой метод инициализатора запускается в дополнение к C.__init__
при создании экземпляра. C
наследуется как от A
, так и от B
, и все методы B
затеняются теми, что на A
, в соответствии с порядком наследования.
Если вам нужно изменить наследование классов на основе значения в конструкторе, создайте два отдельных класса с разными структурами. Затем предоставьте другой вызываемый объект в качестве API для создания экземпляра:
class CA(A):
# just inherit __init__, no need to override
class CB(B):
# just inherit __init__, no need to override
def C(path):
# create an instance of a class based on the value of path
class_map = {'A': CA, 'B': CB}
return class_map[path](path)
Пользователь вашего API по-прежнему имеет имя C()
для вызова; C('A')
создает экземпляр класса, отличного от C('B')
, но они оба реализуют один и тот же интерфейс, поэтому для вызывающей стороны это не имеет значения.
Если у вас есть , чтобы иметь общий класс 'C' для использования в isinstance()
или issubclass()
тестах, вы можете смешать один и использовать __new__
метод переопределить возвращаемый подкласс:
class C:
def __new__(cls, path):
if cls is not C:
# for inherited classes, not C itself
return super().__new__(cls)
class_map = {'A': CA, 'B': CB}
cls = class_map[path]
# this is a subclass of C, so __init__ will be called on it
return cls.__new__(cls, path)
class CA(C, A):
# just inherit __init__, no need to override
pass
class CB(C, B):
# just inherit __init__, no need to override
pass
__new__
вызывается для создания нового экземпляра объекта; если метод __new__
возвращает экземпляр класса (или его подкласс), тогда __init__
будет автоматически вызываться для этого нового объекта экземпляра. Вот почему C.__new__()
возвращает результат CA.__new__()
или CB.__new__()
; __init__
будет вызван для вас.
Демо из последних:
>>> C('A').something()
Function A
>>> C('B').something()
B function with something
>>> isinstance(C('A'), C)
True
>>> isinstance(C('B'), C)
True
>>> isinstance(C('A'), A)
True
>>> isinstance(C('A'), B)
False
Если ни один из этих параметров не подходит для вашего конкретного варианта использования, вам придется добавить больше маршрутизации в новую реализацию somemethod()
для C
, которая затем вызывает A.something(self)
или B.something(self)
на основе self.path
. Это становится громоздким очень быстро, когда вам нужно сделать это для каждого метода , но декоратор может помочь:
from functools import wraps
def pathrouted(f):
@wraps
def wrapped(self, *args, **kwargs):
# call the wrapped version first, ignore return value, in case this
# sets self.path or has other side effects
f(self, *args, **kwargs)
# then pick the class from the MRO as named by path, and call the
# original version
cls = next(c for c in type(self).__mro__ if c.__name__ == self.path)
return getattr(cls, f.__name__)(self, *args, **kwargs)
return wrapped
затем используйте это для пустых методов в вашем классе:
class C(A, B):
@pathrouted
def __init__(self, path):
self.path = path
# either A.__init__ or B.__init__ will be called next
@pathrouted
def something(self):
pass # doesn't matter, A.something or B.something is called too
Это, однако, становится очень унылым и уродливым.