与公认的答案相比,一个可能更好的解决方案:
from better_abc import ABCMeta, abstract_attribute # see below
class AbstractFoo(metaclass=ABCMeta):
@abstract_attribute
def bar(self):
pass
class Foo(AbstractFoo):
def __init__(self):
self.bar = 3
class BadFoo(AbstractFoo):
def __init__(self):
pass
它的行为如下:
Foo() # ok
BadFoo() # will raise: NotImplementedError: Can't instantiate abstract class BadFoo
# with abstract attributes: bar
此答案使用与接受的答案相同的方法,但与内置 ABC 很好地集成,并且不需要 check_bar() 助手的样板。
这是better_abc.py的内容:
from abc import ABCMeta as NativeABCMeta
class DummyAttribute:
pass
def abstract_attribute(obj=None):
if obj is None:
obj = DummyAttribute()
obj.__is_abstract_attribute__ = True
return obj
class ABCMeta(NativeABCMeta):
def __call__(cls, *args, **kwargs):
instance = NativeABCMeta.__call__(cls, *args, **kwargs)
abstract_attributes = {
name
for name in dir(instance)
if getattr(getattr(instance, name), '__is_abstract_attribute__', False)
}
if abstract_attributes:
raise NotImplementedError(
"Can't instantiate abstract class {} with"
" abstract attributes: {}".format(
cls.__name__,
', '.join(abstract_attributes)
)
)
return instance
好消息是你可以做到:
class AbstractFoo(metaclass=ABCMeta):
bar = abstract_attribute()
它的工作原理和上面一样。
也可以使用:
class ABC(ABCMeta):
pass
定义自定义 ABC 助手。 PS。我认为这段代码是 CC0。
这可以通过使用 AST 解析器通过扫描__init__ 代码来提高(在类声明时),但现在看来有点矫枉过正(除非有人愿意实现)。
2021:打字支持
你可以使用:
from typing import cast, Any, Callable, TypeVar
R = TypeVar('R')
def abstract_attribute(obj: Callable[[Any], R] = None) -> R:
_obj = cast(Any, obj)
if obj is None:
_obj = DummyAttribute()
_obj.__is_abstract_attribute__ = True
return cast(R, _obj)
这将使 mypy 突出显示一些打字问题
class AbstractFooTyped(metaclass=ABCMeta):
@abstract_attribute
def bar(self) -> int:
pass
class FooTyped(AbstractFooTyped):
def __init__(self):
# skipping assignment (which is required!) to demonstrate
# that it works independent of when the assignment is made
pass
f_typed = FooTyped()
_ = f_typed.bar + 'test' # Mypy: Unsupported operand types for + ("int" and "str")
FooTyped.bar = 'test' # Mypy: Incompatible types in assignment (expression has type "str", variable has type "int")
FooTyped.bar + 'test' # Mypy: Unsupported operand types for + ("int" and "str")
对于速记符号,正如@SMiller 在 cmets 中所建议的那样:
class AbstractFooTypedShorthand(metaclass=ABCMeta):
bar: int = abstract_attribute()
AbstractFooTypedShorthand.bar += 'test' # Mypy: Unsupported operand types for + ("int" and "str")