Переопределение абстрактных методов в 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 это никогда не нужно. Там нет такого понятия, как "список Agent
s ", просто список всего на свете. (Если вы не используете дополнительную проверку статического типа - но в этом случае, если вам нужно обойти проверку статического типа, не объявляйте статический тип просто для того, чтобы дать себя препятствие обойти.)
В 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
,
Иногда желательно добавить в подкласс дополнительный параметр (один со значением по умолчанию). Это может быть правильным подходом, если некоторый код будет знать об особом поведении подкласса и если этот код хочет использовать эти знания при взаимодействии с методом.
Кроме того, в некоторых случаях вы используете подклассы и принцип подстановки Лискова на самом деле не является желаемым свойством. Такие случаи обычно означают, что вы используете механизм наследования объектов языка для каких-то целей, кроме простого подтипа. Если вы окажетесь в такой ситуации, это хороший совет, что вы должны проверить свой дизайн тройной проверкой и посмотреть, есть ли подход, который лучше согласован.