множественное наследование классов Python - PullRequest
0 голосов
/ 26 апреля 2018

Я пытаюсь понять методы наследования классов Python, и у меня возникают проблемы с выяснением того, как сделать следующее:

Как я могу наследовать метод от класса , условного от ребенкаinput?

Я пробовал следующий код без особого успеха.

class A(object):
    def __init__(self, path):
        self.path = path

    def something(self):
        print("Function %s" % self.path)   


class B(object):
    def __init__(self, path):
        self.path = path
        self.c = 'something'

    def something(self):
        print('%s function with %s' % (self.path, self.c))


class C(A, B):
    def __init__(self, path):
        # super(C, self).__init__(path)

        if path=='A':
            A.__init__(self, path)
        if path=='B':
            B.__init__(self, path)
        print('class: %s' % self.path)


if __name__ == '__main__':
    C('A')
    out = C('B')
    out.something()

Я получаю следующий вывод:

class: A
class: B
Function B

Хотя я хотел бы видеть:

class: A
class: B
B function with something

Я предполагаю, что причина использования A.something() (вместо B.something()) связана с MRO питона.

Ответы [ 3 ]

0 голосов
/ 26 апреля 2018

Ответ Мартин объяснил, как выбрать объект, наследуемый от одного из двух классов.Python также позволяет легко пересылать метод в другой класс:

>>> class C:
    parents = { 'A': A, 'B': B }
    def __init__(self, path):
        self.parent = C.parents[path]
        self.parent.__init__(self, path)                 # forward object initialization
    def something(self):
        self.parent.something(self)                      # forward something method


>>> ca = C('A')
>>> cb = C('B')
>>> ca.something()
Function A
>>> cb.something()
B function with something
>>> ca.path
'A'
>>> cb.path
'B'
>>> cb.c
'something'
>>> ca.c
Traceback (most recent call last):
  File "<pyshell#46>", line 1, in <module>
    ca.c
AttributeError: 'C' object has no attribute 'c'
>>> 

Но здесь класс C не наследует от A или B:

>>> C.__mro__
(<class '__main__.C'>, <class 'object'>)

Ниже приведен мой оригиналРешение, использующее исправление обезьян :

>>> class C:
    parents = { 'A': A, 'B': B }
    def __init__(self, path):
        parent = C.parents[path]
        parent.__init__(self, path)                      # forward object initialization
        self.something = lambda : parent.something(self) # "borrow" something method

, оно избегает атрибута parent в классе C, но менее читабельно ...

0 голосов
/ 26 апреля 2018

Хотя ответ Мартина (как обычно) близок к идеальному, я просто хотел бы отметить, что при проектном POV наследование здесь не тот инструмент.

Помните, что наследование реализации на самом деле является статическим и каким-то образом ограниченным видом композиции / делегирования, поэтому, как только вы захотите чего-то более динамичного, правильный дизайн - отказаться от наследования и перейти к полной компоновке / делегированию, каноническими примерами являютсяГосударство и Стратегия.Применительно к вашему примеру это может выглядеть примерно так:

class C(object):
    def __init__(self, strategy):
        self.strategy = strategy

    def something(self):
        return self.strategy.something(self)

class AStrategy(object):
    def something(self, owner):
        print("Function A")

class BStrategy(object):
    def __init__(self):
        self.c = "something"

    def something(self, owner):
        print("B function with %s" % self.c)


if __name__ == '__main__':
    a = C(AStrategy())
    a.something()
    b = C(BStrategy())
    b.something()

Затем, если вам нужно разрешить пользователю указывать стратегию по имени (в виде строки), вы можете добавить фабричный шаблон в решение * 1006.*

STRATEGIES = {
    "A": AStrategy,
    "B": BStrategy, 
    }

def cfactory(strategy_name):
  try:
      strategy_class = STRATEGIES[strategy_name]
  except KeyError:
      raise ValueError("'%s' is not a valid strategy" % strategy_name)
  return C(strategy_class())

if __name__ == '__main__':
    a = cfactory("A")
    a.something()
    b = cfactory("B")
    b.something()
0 голосов
/ 26 апреля 2018

Вызов __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

Это, однако, становится очень унылым и уродливым.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...