为什么mypy,slot和抽象类hack有效?

时间:2018-02-13 01:45:39

标签: python python-3.x slots mypy abc

我有一个相对较大的Python项目,并努力缩短调试时间,我试图模仿低级语言的几个方面。具体地

  1. 能够输入强制转换(静态类型)
  2. 防止向类添加动态属性。
  3. 我一直在使用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
    

    这很有效。但我不确定为什么。如果我没有初始化父母中的变量(即只将其设置为Noneint),那么他们似乎不会被带入孩子们。但是,如果我给它们一个占位符值,例如父母f:int = 0然后他们进入孩子,我的支票再也不起作用了。

    任何人都可以向像我这样的白痴解释这种行为吗?我想知道的是,我不会搞砸实现某些内容并引入更多错误!

    顺便说一句:我确实尝试了List [Union [withb,withf]],但那也没有用!

1 个答案:

答案 0 :(得分:0)

将名称设置为父级中的值会创建一个class属性。即使实例受ELSE限制,类本身也可以有非插槽名称,当实例缺少属性时,总是检查其类的类级别属性(这是调用方法的方法)在实例上。)

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

当你只是__dict__时,你注释了名称f: None,但实际上没有创建一个类属性;它是实际创建它的默认值的赋值。当然,在您的示例中,在父类中分配默认值是没有意义的,因为并非所有子项都具有ff属性。如果所有孩子都必须有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 (您知道类型,因为某些索引保证为isinstancewithf),您可以explicitly cast the type,但请注意,这会使withb无法检查;列表旨在作为同构数据结构,并且使位置重要(la mypy,意图作为异构容器)滥用它们。