如果我有例如:
class Parent(object):
@property
@abc.abstractmethod
def asdf(self) -> str:
""" Must be implemented by child """
@dataclass
class Children(Parent):
asdf = "1234"
def some_method(self):
self.asdf = "5678"
我从 mypy 得到一个错误,说我在
some_method
中隐藏类属性。这是可以理解的,因为 asdf
是在 Parent
中定义的,因此成为类属性。但是,如果我这样做:
class Parent(object):
asdf: str
mypy 没有产生错误,但是没有强制执行对 asdf 的赋值。而且,
asdf
仍然是一个类属性,对吗?一般而言,类属性是否不应该被子类的方法覆盖?我怎样才能创建一个强制其子级具有特定实例属性(而不是类属性)的父类?
如果我看看 C# 等其他语言,我认为这些“类属性”在某种程度上等同于属性,但我们可以自定义其获取和设置行为。为什么 Python 不是这样?
@property
装饰器的目的。通常它用于一个 function 应该被访问/“感觉”是外部代码的常量。
Python 不会不费吹灰之力就对变量 进行严格的类型检查。 (它确实在运行时限制了 values 的types,因此变量的类型不会微妙地/意外地改变。但是,它通常允许在名义上期望整数的任何地方传递/返回字符串 - 反之亦然反之亦然。在运行时传递的值将继续使用,无需转换为其原始类型,也不会抛出错误 - 直到 & 除非需要转换且未提供。例如,类型提示将变量标记为string 不会阻止将 float 分配给该变量。只有在进行显式类型检查时才会抛出错误,或者如果您尝试调用为 strings 而不是 floats 定义的方法 - 例如
str.join()
。)
其中的基础假设是“其他开发人员和当前开发人员一样了解他们在做什么”,因此您必须采取一些变通办法来严格执行您在 C#、Java、Scala 中看到的类型等等。将“type hints”想象成更像是 linters 和 IDE 的文档和帮助,而不是严格的类型强制执行。
这个观点给出了几个备选方案,看你想从中得到什么
Children.asdf
:检查应该有多严格?您显示的类常量字符串是您要查找的内容,还是您想要通常与 @property
装饰器关联的功能?
初稿,作为一个非常不严格的常量字符串,非常符合Python的EAFP传统:
class Parent:
ASDF: str = None # Subclasses are expected to define a string for ASDF.
class Children(Parent):
ASDF: 'MyChildrenASDF'
如果您想(某种程度上)严格执行该行为,您可以这样做:
class Parent:
ASDF: str = None # # Subclasses are required to define a string for ASDF.
def __init__(self):
if not isinstance(self.ASDF, str):
# This uses the class name of Children or whatever
# subclasses Parent in the error message, which makes
# debugging easier if you have many subclasses and multiple
# developers.
raise TypeError(f'{self.__class__.__name__}.ASDF must be a string.')
class Children(Parent):
ASDF: 'MyChildrenASDF'
def __init__(self):
# This approach does assume the person writing the subclass
# remembers to call super().__init__(). That's not enforced
# automatically.
super().__init__()
第二个选项和我个人一样严格,除了在极少数情况下。如果您需要更强的执行力,您可以编写一个单元测试,它 遍历所有
Parent.__subclasses__()
,并在每次运行测试时执行检查。
或者,您可以定义一个 Python 元类。请注意,元类在 Python 中是一个“高级主题”,一般的经验法则是“如果您不知道是否需要元类,或者不知道元类是什么,则不应使用元类” .基本上,元类让你破解类定义过程:你可以注入动态自动定义的类属性,如果东西没有定义则抛出错误,以及其他各种古怪的技巧......但它是一个很深的兔子洞,对于大多数用例来说可能有点矫枉过正。 如果你想要的东西实际上是一个函数,但使用了
@property
装饰器,所以它感觉像一个实例属性,你可以这样做:
class Parent:
@property
def asdf(self) -> str:
prepared = self._calculate_asdf()
if not isinstance(prepared):
raise TypeError(f'{self.__class__.__name__}._calculate_asdf() must return a string.')
def _calculate_asdf(self) -> str:
raise NotImplementedError(f'{self.__class__.__name__}._calculate_asdf() must return a string.')
class Children(Parent):
def _calculate_asdf(self) -> str:
# I needed a function here to show what the `@property` could
# do. This one seemed convenient. Any function returning a
# string would be fine.
return self.__class__.__name__.reverse()