python 抽象实例属性替代

问题描述 投票:0回答:1

如果我有例如:

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 不是这样?

python mypy
1个回答
0
投票

您可能误解了

@property
装饰器的目的。通常它用于一个 function 应该被访问/“感觉”是外部代码的常量。

Python 不会不费吹灰之力就对变量 进行严格的类型检查。 (它确实在运行时限制了 valuestypes,因此变量的类型不会微妙地/意外地改变。但是,它通常允许在名义上期望整数的任何地方传递/返回字符串 - 反之亦然反之亦然。在运行时传递的值将继续使用,无需转换为其原始类型,也不会抛出错误 - 直到 & 除非需要转换且未提供。例如,类型提示将变量标记为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()

© www.soinside.com 2019 - 2024. All rights reserved.