为什么数据类的类属性声明中不能有可变的默认值?

Why can't dataclasses have mutable defaults in their class attributes declaration?

提问人:Graham 提问时间:12/5/2018 最后编辑:colllinGraham 更新时间:9/2/2023 访问量:105077

问:

这似乎是以前可能被问过的问题,但一个小时左右的搜索没有结果。将默认列表参数传递给数据类看起来很有希望,但这并不完全是我想要的。

问题来了:当尝试为类属性分配可变值时,会出现错误:

@dataclass
class Foo:
    bar: list = []

# ValueError: mutable default <class 'list'> for field a is not allowed: use default_factory

我从错误消息中收集到我应该改用以下内容:

from dataclasses import field

@dataclass
class Foo:
    bar: list = field(default_factory=list)

但是为什么不允许可变默认值呢?是为了强制避免可变的默认参数问题吗?

蟒蛇 python-3.x

评论

14赞 DeepSpace 12/5/2018
“是为了强制避免可变的默认参数问题吗”是的。想象一下,对一个实例的更改会更改曾经创建的所有实例。如果这是一个人想要的行为,他们应该使用类属性。
2赞 shmee 12/5/2018
PEP 557 的相关部分解释了此设计。
4赞 Mr. Developerdude 12/11/2019
你的问题回答了我的问题,显然你比我聪明。接受这个点赞!
0赞 Tyomik_mnemonic 2/11/2021
我认为这 youtrack.jetbrains.com/issue/PY-42319
13赞 Nickolay 6/19/2021
由于我仍然设法在问题中错过了这个解决方案,我将在此处复制正确的语法:bar: list = dataclasses.field(default_factory=list)

答:

145赞 2 revs, 2 users 99%Graham #1

看起来我的问题在文档中得到了非常清楚的回答(正如 shmee 所提到的,它源自 PEP 557):

Python 将默认成员变量值存储在类属性中。请考虑以下示例,不使用数据类:

class C:
    x = []
    def add(self, element):
        self.x.append(element)

o1 = C()
o2 = C()
o1.add(1)
o2.add(2)
assert o1.x == [1, 2]
assert o1.x is o2.x

请注意,class 的两个实例共享相同的类变量 ,正如预期的那样。Cx

使用数据类,如果此代码有效:

@dataclass
class D:
    x: List = []
    def add(self, element):
        self.x += element

它将生成类似于以下内容的代码:

class D:
    x = []
    def __init__(self, x=x):
        self.x = x
    def add(self, element):
        self.x += element

这与使用 class 的原始示例存在相同的问题。也就是说,在创建类实例时未指定值的两个类实例将共享 的同一副本。由于数据类仅使用普通的 Python 类创建,因此它们也共享此行为。数据类没有通用的方法来检测这种情况。相反,如果数据类检测到 、 或 类型的默认参数,则它将引发 。这是一个部分解决方案,但它确实可以防止许多常见错误。CDxxValueErrorlistdictset

29赞 Shizzy 11/3/2022 #2

以上答案不正确。可变默认值(如空列表)可以在数据类中使用 定义。default_factory

    @dataclass
    class D:
        x: list = field(default_factory=list) 

使用默认工厂函数是创建>可变类型的新实例作为字段默认值的一种方式:

   @dataclass
   class D:
       x: list = field(default_factory=list)

   assert D().x is not D().x

链接在这里

评论

1赞 rv.kvetch 11/3/2022
据我所知,上述答案没有错,尽管如前所述,我同意它似乎有些不完整。此处突出显示了重要的部分:“使用数据类,如果此代码有效 [...]”
0赞 Shizzy 11/4/2022
@rv.kvetch 我不能完全同意,因为如果你在这两节中检查他们的断言条件,很明显,使用default_factory不会给你带来它试图避免的问题。 ''' @dataclass类 D: x: list = field(default_factory=list) 断言 D().x 不是 D().x ```
7赞 bfontaine 1/18/2023
您应该写“@username的答案”而不是“上面的答案”,因为答案的显示顺序会随着时间的推移而变化。现在,你的答案刚好低于你三周后写的答案。
8赞 Sadegh Pouriyan 11/21/2022 #3

导入字段,如 dataclass。

from dataclasses import dataclass, field

并将其用于列表:

@dataclass
class Foo:
    bar: list = field(default_factory=list)

评论

3赞 Karl Knechtel 1/4/2023
问题不在于写什么。问题是为什么不允许以明显的方式做事。
8赞 Metalstorm 3/9/2023 #4

只需在default_factory中使用可调用对象:

from dataclasses import dataclass, field

@dataclass
class SomeClass:
    """
    """

    some_list: list = field(default_factory=lambda: ["your_values"])

如果您希望所有实例都改变同一个列表:

from dataclasses import dataclass, field

SHARED_LIST = ["your_values"]
    
@dataclass
class SomeClass:
    """
    """
    
    some_list: list = field(default_factory=lambda: SHARED_LIST)
3赞 NicoHood 5/7/2023 #5

我偶然发现了这个问题,因为我确实希望有一个静态列表作为类变量。这可以使用 ClassVar 注解来完成:

from typing import ClassVar

@dataclass
class Foo:
    bar: ClassVar[list[str]] = ['hello', 'world']