Reputation: 439
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
Reputation: 12174
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.
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
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
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