3

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?

FriskySaga
  • 409
  • 1
  • 8
  • 19

3 Answers3

4

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!

Partha Mandal
  • 1,391
  • 8
  • 14
1

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
water_ghosts
  • 716
  • 5
  • 12
1

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
JL Peyret
  • 10,917
  • 2
  • 54
  • 73
  • I really like this answer, but it's not always applicable to people who may not be able to use these tools at work. – FriskySaga Aug 28 '20 at 01:43
  • 1
    @FriskySaga no problem. apologies for the "dont do this" aspect to my answer, that tends to irritate me on Stackoverflow myself. while I cant speak to your context, if I were looking after a bunch of users plugging into framework code, I'd expect there'd be more goofs than *just* `@property` missing decorators. My handling would depend on a) how frequent and persistent the goofs were, b) how much disruption they cause - obviously if a puppy dies each time @prop is forgotten that is different than 2 mins of adjustment. I'd *probably* fix more via the documentation/support end of things tho – JL Peyret Sep 01 '20 at 19:46