为什么这个mypy,slots,和抽象类黑客的工作?

2024-10-03 02:32:10 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个比较大的Python项目,为了尽量减少调试时间,我尝试模拟较低级别语言的一些方面。具体来说

  1. 类型转换能力(静态类型)
  2. 防止向类添加动态属性。在

我一直在使用mypy捕捉类型转换错误,并在类实例中定义__slots__,以防止动态添加。在

在某一点上,我需要一个列表,其中包含两个不同的子类(它们有相同的父类),它们具有稍微不同的属性。mypy不喜欢这样一个事实:对一个列表项的属性的调用并没有出现在所有的列表项中。但是,使父对象过于一般化意味着在另一个子对象中动态添加变量并没有被阻止。在

为了解决这个问题,我调试/brute强迫自己使用下面的代码示例:

from abc import ABCMeta
from typing import List

class parentclass(metaclass=ABCMeta):
    __slots__:List[str] = []
    name: None

class withb(parentclass):
    __slots__ = ['b','name']
    def __init__(self):
        self.b: int = 0 
        self.name: str = "john"

class withf(parentclass):
    __slots__ = ['f','name']
    def __init__(self):
        self.name: str = 'harry'
        self.f: int = 123


bar = withb()
foo = withf()

ls: List[parentclass] = [bar, foo]

ls[0].f = 12 ## Needs to fail either in Python or mypy

for i in range(1):
    print(ls[i].name)
    print(ls[i].b) ## This should NOT fail in mypy

这很管用。但我不知道为什么。如果我不初始化父级中的变量(即只将它们设置为None或{}),那么它们似乎不会被带到子级中。但是,如果我在父对象中给它们一个占位符值,例如f:int = 0,那么它们就把它放入子元素中,我的检查就不能再工作了。在

有人能向像我这样的白痴解释这种行为吗?我很想知道,这样我就不会把事情的执行搞得一团糟,也不会引入更多的错误!在

顺便说一句:我确实试过列出[Union[withb,withf]],但那也没用!在


Tags: 对象nameself列表属性动态lslist
1条回答
网友
1楼 · 发布于 2024-10-03 02:32:10

将名称设置为父级中的值将创建类属性。即使实例受到__slots__的限制,类本身也可以有非时隙名称,并且当实例缺少属性时,总是检查其类是否具有类级别的属性(这就是您可以在实例上调用方法的方式)。在

尝试通过实例分配给类属性并不能替换类属性。instance.attr = someval将始终尝试在实例上创建不存在的属性(隐藏class属性)。当层次结构中的所有类都使用__slots__(不带__dict__槽)时,这将失败(因为槽不存在)。在

当您仅仅针对f: None时,您已经注释了名称f,但实际上并没有创建类属性;实际上创建它的是默认值的赋值。当然,在您的例子中,在父类中分配默认值是没有意义的,因为并不是所有子类都有f或{}属性。如果所有子类都必须有一个name,那么它应该是父类的一部分,例如:

class parentclass(metaclass=ABCMeta):
    # Slot for common attribute on parent
    __slots__:List[str] = ['name']
    def __init__(self, name: str):
        # And initializer for parent sets it (annotation on argument covers attribute type)
        self.name = name

class withb(parentclass):
    # Slot for unique attributes on child
    __slots__ = ['b']
    def __init__(self):
        super().__init__("john")  # Parent attribute initialized with super call
        self.b: int = 0  # Child attribute set directly

class withf(parentclass):
    __slots__ = ['f']
    def __init__(self):
        super().__init__('harry')
        self.f: int = 123

如果目标是根据子类的类型动态选择是使用f还是{},那么您可以将使用它的代码更改为:

^{pr2}$

在不需要isinstance的情况下(您知道类型,因为某些索引保证是withf或{}),您可以explicitly ^{} the type,但要知道这会放弃mypy的检查能力;列表是一个同质的数据结构,并且使位置变得重要(la{},作为一个异构容器)被误用。在

相关问题 更多 >