'enforcement for abstract properties in python3
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?
Solution 1:[1]
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__()
Usage:
class Foo(Abstract):
foo_member: AbstractClassVar[str]
@property
@abc.abstractmethod
def a(self):
...
class UpperFoo(Foo):
# Everything should be implemented as intended or else...
...
- Not implementing the abstract property
a
or implementing it as anything but aproperty
(type) will result in a TypeError - The trick with the class member is quite different and uses a more complex approach with annotations, but the result is almost the same.
Not Implementing the abstract class member
foo_member
will result in a NotImplementedError.
Solution 2:[2]
You are overriding the kaka
property in the child class. You must also use @property
to decorate the overridden methods in the child:
from abc import ABC, abstractmethod
class Kuku(ABC):
def __init__(self):
self.a = 4
@property
@abstractmethod
def kaka(self):
pass
class KukuChild(Kuku):
@property
def kaka(self):
return 3
KukuChild().kaka # <-- use as an attribute, not a method call.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Reblochon Masque |