I have this abstract class
class Kuku(ABC):
def __init__(self):
self.a = 4
@property
@abstractmethod
def kaka(self):
pass
kaka is an abstract property, So I would expect python to enforce it being a property in inheritors, But it allows me to create:
class KukuChild(Kuku):
def kaka(self):
return 3
and KukuChild().kaka() returns 3 as if it's not a property. Is this intentional? Pycharm doesn't enforce this either, so why even add the property decorator in the abstract class?
I've come across this problem myself, after looking into the options of how to enforce such behaviour I came up with the idea of implementing a class that has that type checking.
import abc
import inspect
from typing import Generic, Set, TypeVar, get_type_hints
T = TypeVar('T')
class AbstractClassVar(Generic[T]):
pass
class Abstract(abc.ABC):
"""Inherit this class to:
1. Enforce type checking for abstract properties.
2. Check that abstract class members (aka. `AbstractClassVar`) are implemented.
"""
def __init_subclass__(cls) -> None:
def get_abstract_properties(cls) -> Set[str]:
"""Gets a class's abstract properties"""
abstract_properties = set()
if cls is Abstract:
return abstract_properties
for base_cls in cls.__bases__:
abstract_properties.update(get_abstract_properties(base_cls))
abstract_properties.update(
{abstract_property[0] for abstract_property in
inspect.getmembers(cls, lambda a: getattr(a, "__isabstractmethod__", False) and type(a) == property)})
return abstract_properties
def get_non_property_members(cls) -> Set[str]:
"""Gets a class's non property members"""
return {member[0] for member in inspect.getmembers(cls, lambda a: type(a) != property)}
def get_abstract_members(cls) -> Set[str]:
"""Gets a class's abstract members"""
abstract_members = set()
if cls is Abstract:
return abstract_members
for base_cls in cls.__bases__:
abstract_members.update(get_abstract_members(base_cls))
for (member_name, annotation) in get_type_hints(cls).items():
if getattr(annotation, '__origin__', None) is AbstractClassVar:
abstract_members.add(member_name)
return abstract_members
cls_abstract_properties = get_abstract_properties(cls)
cls_non_property_members = get_non_property_members(cls)
# Type checking for abstract properties
if cls_abstract_properties:
for member in cls_non_property_members:
if member in cls_abstract_properties:
raise TypeError(f"Wrong class implementation {cls.__name__} " +
f"with abstract property {member}")
# Implementation checking for abstract class members
if Abstract not in cls.__bases__:
for cls_member in get_abstract_members(cls):
if not hasattr(cls, cls_member):
raise NotImplementedError(f"Wrong class implementation {cls.__name__} " +
f"with abstract class variable {cls_member}")
return super().__init_subclass__()
class Foo(Abstract):
foo_member: AbstractClassVar[str]
@property
@abc.abstractmethod
def a(self):
...
class UpperFoo(Foo):
# Everything should be implemented as intended or else...
...
a or implementing it as anything but a property (type) will result in a TypeErrorfoo_member will result in a NotImplementedError.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With