Переопределение абстрактных методов в Python

При переопределении абстрактного метода Python, есть ли способ переопределить метод с дополнительными параметрами в сигнатуре метода?

например

Абстрактный класс =

Agent(ABC):

    @abstractmethod
    def perceive_world(self, observation):
        pass

Класс наследования:

Dumb_agent(Agent):

    def perceive_world(self, observation):
        print('I see %s' % observation)

Наследование класса с дополнительным параметром в сигнатуре метода:

Clever_Agent(Agent):

    def perceive_world(self, observation, prediction):
        print('I see %s' % observation)
        print('I think I am going to see %s happen next' % prediction)

3 ответа

Решение

То, что вы пытаетесь сделать, просто сработает, но это очень плохая идея.

Как правило, вы не хотите изменять сигнатуру метода несовместимыми способами при переопределении. Это часть принципа подстановки Лискова.

В Python часто есть веские причины нарушать это - наследование не всегда связано с подтипами.

Но когда вы используете ABC для определения интерфейса, это явно относится к подтипам. Это единственная цель ABC подклассы и abstractmethod декораторы, поэтому использование их для обозначения чего-либо еще в лучшем случае вводит в заблуждение.


Более подробно:

Наследуя от AgentВы заявляете, что любой случай Clever_Agent может быть использован, как если бы это было Agent, Это включает в себя возможность звонить my_clever_agent.perceive_world(my_observation), Фактически, это не просто включает это; это все, что это значит! Если этот вызов всегда будет неудачным, то нет Clever_Agent является Agentтак что не должно претендовать на это.

В некоторых языках вам иногда приходится подделывать свой способ проверки интерфейса, чтобы впоследствии вы могли переключать тип и / или "динамически приводить" обратно к фактическому типу. Но в Python это никогда не нужно. Там нет такого понятия, как "список Agents ", просто список всего на свете. (Если вы не используете дополнительную проверку статического типа - но в этом случае, если вам нужно обойти проверку статического типа, не объявляйте статический тип просто для того, чтобы дать себя препятствие обойти.)


В Python вы можете расширить метод за пределы его метода суперкласса, добавив необязательные параметры, и это совершенно правильно, поскольку он по-прежнему совместим с явно объявленным типом. Например, это было бы вполне разумно:

class Clever_Agent(Agent):
    def perceive_world(self, observation, prediction=None):
        print('I see %s' % observation)
        if prediction is None:
            print('I have no predictions about what will happen next')
        else:
            print('I think I am going to see %s happen next' % prediction)

Или даже это может быть разумным:

class Agent(ABC):
    @abstractmethod
    def perceive_world(self, observation, prediction):
        pass

class Dumb_agent(Agent):
    def perceive_world(self, observation, prediction=None):
        print('I see %s' % observation)
        if prediction is not None:
            print('I am too dumb to make a prediction, but I tried anyway')

class Clever_Agent(Agent):
    def perceive_world(self, observation, prediction):
        print('I see %s' % observation)
        print('I think I am going to see %s happen next' % prediction)

Во многих отношениях переопределение абстрактного метода из родительского класса и добавление или изменение сигнатуры метода технически не называется переопределением метода. То, что вы можете эффективно делать - это скрытие метода. Переопределение метода всегда переопределяет конкретную существующую сигнатуру метода в родительском классе.

Вы можете обойти эту проблему, определив вариант абстрактного метода в родительском классе и переопределив его, если необходимо, в своих подклассах.

Вы можете сделать это, просто выполнив то, что вы предложили: вы можете добавить дополнительный параметр в подкласс.

Однако это может привести к нарушению принципа замещения и может привести к ошибкам и проблемам проектирования. В общем, желательно, чтобы подкласс использовался каждый раз, когда суперкласс пригоден для использования. То есть каждый раз, когда метод или функция хочет Agent, вы должны быть в состоянии пройти в CleverAgent, К сожалению, если CleverAgent принимает дополнительные параметры, затем любой код, который вызывает perceive_world на Agent потерпит неудачу, когда дан CleverAgent,

Иногда желательно добавить в подкласс дополнительный параметр (один со значением по умолчанию). Это может быть правильным подходом, если некоторый код будет знать об особом поведении подкласса и если этот код хочет использовать эти знания при взаимодействии с методом.

Кроме того, в некоторых случаях вы используете подклассы и принцип подстановки Лискова на самом деле не является желаемым свойством. Такие случаи обычно означают, что вы используете механизм наследования объектов языка для каких-то целей, кроме простого подтипа. Если вы окажетесь в такой ситуации, это хороший совет, что вы должны проверить свой дизайн тройной проверкой и посмотреть, есть ли подход, который лучше согласован.

Другие вопросы по тегам