Как переопределить метод абстрактного свойства, не забыв добавить декоратор свойства в дочерний класс?

#python #python-3.x #properties #abstract-class

#python #python-3.x #свойства #abstract-class

Вопрос:

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

 class Parent(metaclass=ABCMeta):
  @property
  @abstractmethod
  def name(self) -> str:
    pass

class Child(Parent):
  @property # If I forget this, I want Python to yell at me.
  def name(self) -> str:
    return 'The Name'

if __name__ == '__main__':
  print(Child().name)
  

Действительно ли в Python нет встроенного способа сделать это? Должен ли я действительно создавать свой собственный декоратор для обработки такого типа поведения?

Ответ №1:

Вы можете использовать metaclass :

 class Parent(type):
  def __new__(cls, name, bases, body):
    if 'name' not in body.keys() or body['name'].__class__.__name__ != 'property':
      raise TypeError(f"Can't instantiate class {name} without property name")
    return super().__new__(cls, name, bases, body)


class Child(metaclass=Parent):
  @property # If I forget this, raise TypeError
  def name(self) -> str: # also, name must be implemented
    return 'The Name'

if __name__ == '__main__':
  print(Child().name)
  

Это вызывает TypeError: Can't instantiate class Child without property name — когда @property закомментировано!

Комментарии:

1. Спасибо, что ответили альтернативным решением; это было очень проницательно. Поддержано.

Ответ №2:

Вы могли бы поместить проверку времени выполнения в __init__ метод родительского элемента и вызвать исключение, если name это метод.

 class Parent(metaclass=ABCMeta):
  def __init__(self):
    assert not callable(self.name)

  @abstractmethod
  def name(self) -> str:
    pass


class GoodChild(Parent):
  @property
  def name(self) -> str:
    return 'The Name'


class BadChild(Parent):
  def name(self) -> str:
    return 'Whoops, not a property'


if __name__ == '__main__':
  good_child = GoodChild()
  print(good_child.name)   # Prints 'The Name'
  bad_child = BadChild()   # Raises an AssertionError when initialized
  

Комментарии:

1. ИМО, это самый простой ответ, поэтому я отмечу как лучший ответ. Спасибо.

Ответ №3:

TLDR — не стоит хлопот, на мой взгляд:

Сочетание linting / mypy и модульных тестов, которые должны удовлетворять большинству ваших потребностей, и небольшие хитрости, связанные с анализом классов / метаклассами, вероятно, не стоят дополнительной когнитивной нагрузки, которую они вносят. Вы «подведете» оформление только один раз, но каждый раз вам придется читать экзотический код scaffolding для того, что вы хотите сделать.

подробности — как помечается фактическое использование?

т. е. if badchild1.name.startswith("John"): произойдет сбой во время выполнения, и я бы ожидал, что mypy или pylint, например, также будут помечены при анализе, поскольку это будет объект метода. То же самое будет с конкатенацией. Единственными реальными кандидатами на надзор являются f-строки, прямые логические значения или == , != сравнения на равенство, которым все равно, что это не строка.

в pylint есть это, чтобы сказать:

 test_171_prop.py:11 Method 'name' was expected to be 'property', found it instead as 'method' (invalid-overridden-method)
  

однако у mypy не было проблем.

Однако, если я добавлю это:

 child = Child()

print("name:"   child.name)
  

затем mypy говорит:

 test_171_prop.py:16: error: Unsupported operand types for   ("str" and "Callable[[], str]")
Found 1 error in 1 file (checked 1 source file)
  

И запуск кода с двумя новыми строками говорит:

 TypeError: can only concatenate str (not "method") to str
  

Комментарии:

1. Мне действительно нравится этот ответ, но он не всегда применим к людям, которые, возможно, не смогут использовать эти инструменты на работе.

2.@FriskySaga нет проблем. приношу извинения за аспект «не делай этого» в моем ответе, который, как правило, раздражает меня самого в Stackoverflow. хотя я не могу говорить о вашем контексте, если бы я присматривал за кучей пользователей, подключающихся к коду фреймворка, я бы ожидал, что будет больше ошибок, чем просто @property отсутствующих декораторов. Моя обработка будет зависеть от а) насколько частыми и постоянными были ошибки, б) сколько сбоев они вызывают — очевидно, если щенок умирает каждый раз, когда забывается @prop, что отличается от 2 минут настройки. Я бы, вероятно, исправил больше с помощью документации / поддержки, чтобы