我有一个相对较大的Python项目,并努力缩短调试时间,我试图模仿低级语言的几个方面。具体地
我一直在使用mypy来捕捉类型转换错误,并且我已经在我的类实例中定义__slots__
以防止动态添加。
有一次,我需要一个List,它填充了两个不同的子类(它们具有相同的父类),它们具有稍微不同的属性。 mypy并不喜欢这样的事实,即列表项的属性调用并不存在于所有列表项中。但是,使父对象过于笼统意味着不会阻止另一个孩子中存在的变量的动态添加。
为了解决这个问题,我调试/强行强迫自己使用以下代码示例,该示例似乎有效:
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
或int
),那么他们似乎不会被带入孩子们。但是,如果我给它们一个占位符值,例如父母f:int = 0
然后他们进入孩子,我的支票再也不起作用了。
任何人都可以向像我这样的白痴解释这种行为吗?我想知道的是,我不会搞砸实现某些内容并引入更多错误!
顺便说一句:我确实尝试了List [Union [withb,withf]],但那也没有用!
答案 0 :(得分:0)
将名称设置为父级中的值会创建一个class属性。即使实例受ELSE
限制,类本身也可以有非插槽名称,当实例缺少属性时,总是检查其类的类级别属性(这是调用方法的方法)在实例上。)
尝试通过实例分配类属性不会替换class属性。 __slots__
将始终尝试在实例上创建属性(如果它不存在(隐藏类属性))。当层次结构中的所有类都使用instance.attr = someval
(没有__slots__
个插槽)时,这将失败(因为插槽不存在)。
当你只是__dict__
时,你注释了名称f: None
,但实际上没有创建一个类属性;它是实际创建它的默认值的赋值。当然,在您的示例中,在父类中分配默认值是没有意义的,因为并非所有子项都具有f
或f
属性。如果所有孩子都必须有b
,那么它应该是父类的一部分,例如:
name
如果目标是根据子类的类型mypy
understands isinstance
checks动态选择是使用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
,那么您可以使用它来更改代码: / p>
b
如果不需要if isinstance(ls[0], withf): # Added to ensure `ls[0]` is withf before using it
ls[0].f = 12 ## Needs to fail either in Python or mypy
for x in ls:
print(x.name)
if isinstance(x, withb): # Added to only print b for withb instances in ls
print(x.b) ## This should NOT fail in mypy
(您知道类型,因为某些索引保证为isinstance
或withf
),您可以explicitly cast
the type,但请注意,这会使withb
无法检查;列表旨在作为同构数据结构,并且使位置重要(la mypy
,意图作为异构容器)滥用它们。