定义抽象实例属性而不是属性的最佳实践是什么?
我想写这样的东西:
class AbstractFoo(metaclass=ABCMeta):
@property
@abstractmethod
def bar(self):
pass
class Foo(AbstractFoo):
def __init__(self):
self.bar = 3
代替:
class Foo(AbstractFoo):
def __init__(self):
self._bar = 3
@property
def bar(self):
return self._bar
@bar.setter
def setbar(self, bar):
self._bar = bar
@bar.deleter
def delbar(self):
del self._bar
属性很方便,但对于不需要计算的简单属性,它们是多余的。这对于将由用户进行子类化和实现的抽象类尤其重要(我不想强制某人使用 @property
,因为他本来可以编写 self.foo = foo
在 __init__
)。
Abstract attributes in Python问题建议作为使用 @property
和 @abstractmethod
的唯一答案:它没有回答我的问题。
通过 AbstractAttribute
获得抽象类属性的 ActiveState 配方可能是正确的方法,但我不确定。它也只适用于类属性而不是实例属性。
最佳答案
与公认的答案相比,一个可能更好的解决方案:
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 在评论中所建议的那样:
class AbstractFooTypedShorthand(metaclass=ABCMeta):
bar: int = abstract_attribute()
AbstractFooTypedShorthand.bar += 'test' # Mypy: Unsupported operand types for + ("int" and "str")
关于python - 抽象属性(不是属性)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23831510/