数据类 - 为什么属性的处理方式与正常处理方式不同 类?

Dataclass - why attributes are treated differently from normal classes?

我理解 dataclass 作为创建 __init____repr__ 以及其他函数的装饰器 'automatically'.

但我注意到了一些出乎我意料的事情,我想知道这是否是预期的事情,因为我在官方文档中找不到任何相关内容(至少不是我相关的)

第一个例子:

from dataclasses import dataclass

class CustomObj():
    def __init__(self, x):
        self.x = x
        print(f'called custom obj with {x}')

class Normal():
    i : int
    o : CustomObj
    f : float = 100.
    s : str = 'this is a string'

@dataclass
class Data():
    i : int
    o : CustomObj
    f : float = 100.
    s : str = 'this is a string'


object_1 = Normal()
object_2 = Data(i = 1., o = CustomObj('custom_from_2'))

try:
    object_1.i
except AttributeError:
    print('it is ok, detecting an expected attribute error')

assert object_2.i == 1.
print('it is ok, because dataclass makes us set i value')

assert object_1.f == object_2.f
print('it is ok, because it is an "native" value')

try:
    object_1.o
except AttributeError:
    print('it is ok, detecting an expected attribute error, we didnt set o for object_1')

assert Normal.f == Data.f
print('it is ok, both classes have the same values and we did mess with it yet')

object_1.o = CustomObj('custom_from_1')
print('we set a customObj for obj_1 here')

Normal.f = 222.
Data.f = 222.

assert Normal.f == Data.f
print('it is ok, we set both values to same thing')

assert object_1.f == 222 and object_2.f == 100.
print(f'1: {object_1.f} 2: {object_2.f}')
print('By setting Normal.f we set object_1.f to 222 but object_2.f still 100')

object_1.s = 'changing object_1.s to something else'
object_2.s = 'changing object_2.s to something else'

Normal.s = 'changing Normal.s to something else'
Data.s = 'changing Data.s to something else'

print(object_1.s, object_2.s)
print(Normal.s, Data.s)

object_3 = Normal()
object_4 = Data(i = 4., o = CustomObj('custom_from_4'))

assert object_3.s == 'changing Normal.s to something else'
print('it is expected to have new value for the class definitions of Normal here')

print(f'Normal.s: {Normal.s}')
print(f'Data.s: {Data.s}')
print(f'object_1.s: {object_1.s}')
print(f'object_2.s: {object_2.s}')
print(f'object_3.s: {object_3.s}')
print(f'object_4.s: {object_4.s}')

输出为:

called custom obj with custom_from_2
it is ok, detecting an expected attribute error
it is ok, because dataclass makes us set i value
it is ok, because it is an "native" value
it is ok, detecting an expected attribute error, we didnt set o for object_1
it is ok, both classes have the same values and we did mess with it yet
called custom obj with custom_from_1
we set a customObj for obj_1 here
it is ok, we set both values to same thing
1: 222.0 2: 100.0
By setting Normal.f we set object_1.f to 222 but object_2.f still 100
changing object_1.s to something else changing object_2.s to something else
changing Normal.s to something else changing Data.s to something else
called custom obj with custom_from_4
it is expected to have new value for the class definitions of Normal here
Normal.s: changing Normal.s to something else
Data.s: changing Data.s to something else
object_1.s: changing object_1.s to something else
object_2.s: changing object_2.s to something else
object_3.s: changing Normal.s to something else
object_4.s: this is a string

我的三个问题是:

  1. 为什么在检查object_1.iobject_2.i
  2. 时更改Normal.i与更改Data.i不同
  3. 为什么 Data.s 变了而 object_4.s 没有变
  4. 此行为在文档中的什么位置?

我的猜测是,使用装饰器会返回对某种 __new__ 运算符的引用,并且应该以这种方式更改定义中的值。 但是我在文档中找不到它的说明,所以我很困惑。

有人知道吗?

简而言之,@dataclass 装饰器通过从类型注释中提取变量来转换 class 的定义。当您找不到文档时,了解发生了什么的最好方法是查看源代码。

我们可以先去definition of dataclass and see that it returns a class processed by _process_class(). Inside the function, you can find that it gives a new initializer到正在装修的class,基本就是你猜到的

正如@juanpa.arrivillaga 所指出的,您的 Normal.iData.i 不同的原因是因为 Data.i@dataclass object 属性,而你的 Normal.i 是 class 属性。这也是设置 Data.s 对您的 object_4.s.

没有影响的原因

最后,此行为在文档本身中并未详细阐述,但在链接 PEP557 中,其中说明了添加 @dataclass.

的确切效果