FriskySaga
FriskySaga

Reputation: 439

How to override an abstract property method without forgetting to add the property decorator in the child class?

I want the Python interpreter to yell at me if I override an abstract property method, but forget to specify that it's still a property method in the child class.

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)

Is there really no built-in way for Python to do this? Must I really create my own decorator to handle this type of behavior?

Upvotes: 3

Views: 1943

Answers (3)

JL Peyret
JL Peyret

Reputation: 12174

TLDR - not worth the hassle, in my opinion:

Between linting/mypy and unit tests that should cover most of your needs and little tricks around class analysis/metaclasses are likely not worth the extra cognitive load that they bring in. You will only "fail" your decorating once, but will have to read exotic scaffolding code for what you want to do every time.

details - what does actual use get flagged as?

i.e. if badchild1.name.startswith("John"): will fail at runtime and I'd expect mypy or pylint for example to flag on analysis as well, as it's going to be a method object. So will concatenation. The only real candidates for oversight are f-strings, straight out booleans or ==, != equality comparisons that don't care that it's not a string.

pylint has this to say:

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

mypy had no issues however.

However, if I add this:

child = Child()

print("name:" + child.name)

then mypy says:

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

And running the code with the 2 new lines says:

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

Upvotes: 1

Partha Mandal
Partha Mandal

Reputation: 1441

You can use a 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)

This raises a TypeError: Can't instantiate class Child without property name - when @property is commented out!

Upvotes: 4

water_ghosts
water_ghosts

Reputation: 736

You could put a runtime check in the Parent's __init__ method, and raise an exception if name is a method.

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

Upvotes: 1

Related Questions