Python 3.x 在_post_init中设置可选数据类参数时如何避免检查无__

Python 3.x 在_post_init中设置可选数据类参数时如何避免检查无__,python-3.x,type-hinting,mypy,python-dataclasses,Python 3.x,Type Hinting,Mypy,Python Dataclasses,考虑一个参数具有可变默认值的数据类。为了能够使用新的默认值而不是共享的可变对象实例化对象,我们可以执行以下操作: @dataclass class ClassWithState: name: str items: Optional[List[str]] = None def __post_init__(self) -> None: if self.items is None: self.items = [] 这正如预期的那

考虑一个参数具有可变默认值的数据类。为了能够使用新的默认值而不是共享的可变对象实例化对象,我们可以执行以下操作:

@dataclass
class ClassWithState:
    name: str
    items: Optional[List[str]] = None

    def __post_init__(self) -> None:
        if self.items is None:
            self.items = []
这正如预期的那样有效。然而,每当我在这个类的某些实例中引用
items
时,mypy就会警告说
items
可能没有。例如:

c = ClassWithState("object name")
c.items.append("item1")
MyPy会抱怨如下:

@dataclass
class ClassWithState:
    name: str
    items: Optional[List[str]] = None

    def __post_init__(self) -> None:
        if self.items is None:
            self.items = []
“可选[List[str]]”的“None”项没有属性“append”

我不想每次提到
项目时都要添加不必要的检查,例如

assert c.items is not None
我提到的所有
项目
。如何使mypy相信
永远不会为零?

我将使用
默认值\u工厂
选项集:

from dataclasses import dataclass, field
from typing import List


@dataclass
class ClassWithState:
    name: str
    items: List[str] = field(default_factory=list)

>>> ClassWithState("Hello")
ClassWithState(name='Hello', items=[])
问题(如果我们需要更多的灵活性怎么办?) 问题是,我们没有任何方法告诉mypy,
项在
\uuuu post\uuuu init\uuuuu
之前是可选的,但在之后不是

当期望的默认初始化不依赖于初始值设定项的其他参数时,Carcigenicate将处理这种情况。但是,假设您需要查看
名称
,以了解如何默认初始化

对于这种情况,如果有一个类似于
default\u factory
的方法,将参数作为参数引入到部分初始化的对象中,那就太好了,但不幸的是。其他看起来相关但不起作用的事情:

  • init=False
    字段选项允许在
    \uuuu post\u init\uuuu
    中初始化字段,但删除用户指定显式值的选项
  • 使用
    InitVar
    泛型类型的作用与我们在这里想要的相反:使值可用于初始值设定项(和
    \uuuuuu post\u init\uuuuu
    ),而不将其作为dataclass对象的字段
使用非None sentinel值 但是,作为一种解决方法,您可以指定一个特殊的对象值来表示需要替换字段默认值的
\uuu post\u init\uuu
方法。对于大多数类型,只需创建特定类型的唯一虚拟对象就很容易了,您可以将其存储为类变量,并从字段default_factory返回(如果它是像
list
这样的可变类型,dataclass不允许您直接将其指定为默认值)。对于像
str
int
这样的类型,除非您使用一个“change\u me”值,而您知道该值不是该字段的合法显式值,否则不能保证它按预期工作

from dataclasses import dataclass, field
from typing import ClassVar, List


@dataclass
class ClassWithState:
    name: str
    __uninitialized_items: ClassVar[List[str]] = list()
    items: List[str] = field(default_factory=lambda: ClassWithState.__uninitialized_items)

    def __post_init__(self) -> None:
        if self.items is self.__uninitialized_items:
            self.items = [str(i) for i in range(len(self.name))]


print(ClassWithState("testing", ["one", "two", "three"]))
print(ClassWithState("testing"))
print(ClassWithState("testing", []))
输出:

ClassWithState(name='testing', items=['one', 'two', 'three'])
ClassWithState(name='testing', items=['0', '1', '2', '3', '4', '5', '6'])
ClassWithState(name='testing', items=[])
如果字段可以有稍微不同的名称。。。 使用属性 如果您不需要按名称传递显式初始化(或者即使您可以简单地让参数的名称与您在断言非None时使用的名称稍有不同),那么这是一个更灵活的选项。 其想法是让可选字段成为一个单独的(甚至可能是“私有”)成员,同时让属性访问自动强制转换的版本。我遇到这个解决方案时,每当访问对象时,我都需要应用额外的转换,而强制转换只是一个特例(使属性为只读的功能也很好)。(如果对象引用永远不会更改,您可以考虑<代码> CaseDeXialPix) 下面是一个例子:

从数据类导入数据类
从输入导入列表,可选,强制转换
@数据类
类别类别与状态:
姓名:str
_项目:可选[列表[str]]=无
@财产
def项目(自身)->列表[str]:
返回类型(列表[str],自我项目)
@项目设置器
def项(自身,值:列表[str])->无:
self.\u项=值
定义后初始化(自)->无:
如果self.\u项目为无:
self.\u items=[str(i)表示范围内的i(len(self.name))]
打印(ClassWithState(“测试”,_items=[“一”,“二”,“三]))
打印(ClassWithState(“测试”、“一”、“二”、“三”))
打印(ClassWithState(“测试“,[]))
打印(ClassWithState(“测试”))
obj=带有状态的类(“测试”)
打印(obj)
obj.items.append('test')
打印(obj)
obj.items=['other','one']
打印(obj)
打印(对象项)
以及输出:

ClassWithState(name='testing', _items=['one', 'two', 'three'])
ClassWithState(name='testing', _items=['one', 'two', 'three'])
ClassWithState(name='testing', _items=[])
ClassWithState(name='testing', _items=['0', '1', '2', '3', '4', '5', '6'])
ClassWithState(name='testing', _items=['0', '1', '2', '3', '4', '5', '6'])
ClassWithState(name='testing', _items=['0', '1', '2', '3', '4', '5', '6', 'test'])
ClassWithState(name='testing', _items=['another', 'one'])
['another', 'one']
创建一个
InitVar[可选[…]
字段,并使用
\uuuu post\uu init\uuu
设置真字段 如果可以处理不同的名称,另一种选择是使用
InitVar
指定可选版本只是
\uuuuu init\uuuuuu
(和
\uuuuu post\u init\uuuuuu
)的一个参数,然后在
\uuuuu post\u init\uuuuuuuu
中设置不同的非可选成员变量。这避免了需要执行任何强制转换,不需要设置属性,允许表示使用目标名称而不是代理名称,并且不会出现没有合理的sentinel值的问题,但是,仅当您可以使用与访问字段不同的名称处理初始值设定项参数时,此方法才有效,并且不如属性方法灵活:

从数据类导入InitVar、数据类、字段
输入导入列表,可选
@数据类
类别类别与状态:
姓名:str
_项目:InitVar[可选[列表[str]]]=None
项目:列表[str]=字段(init=False,默认工厂=List)
定义后初始化(self,items:Optional[List[str]])->无:
如果项目为无:
items=[str(i)表示范围内的i(len(self.name))]
self.items=项目

用法与属性方法相同,并且输出看起来也相同,只是表示形式在
项前面没有下划线

此模型是否将默认值设置为空列表,而不是无?如果希望默认值为“无”,而不是空列表,该怎么办?我很好奇如何处理这个问题,因为<代码