Python:在没有构造函数的情况下将对象重新分配给 Class 不会覆盖字典字段

Python: Reassigning Object to Class with No Constructor Does Not Overwrite Dictionary Field

我正在使用 Python 3.9.1,但对 Python 的默认构造函数的工作方式感到困惑。

我下面的 class 有一个 Dictionary 字段,没有构造函数。

当我填充它的 Dictionary 然后将我的对象重新分配给一个新实例时,字典保留其现有值(这也发生在 List 字段):

from typing import Dict


class MyClass:
    records: Dict[str, str] = {}


if __name__ == '__main__':
    my_class = MyClass()
    print(my_class.records)  # prints `{}`.

    my_class.records['1'] = 'one'
    print(my_class.records)  # prints `{'1': 'one'}`.

    # This does not overwrite `my_class` with a new instance.
    # `records` still contains the above element.
    my_class = MyClass()
    print(my_class.records)  # prints `{'1': 'one'}`.

但是,当我添加构造函数时,它按预期工作:

from typing import Dict


class MyClass:
    records: Dict[str, str] = {}

    def __init__(self) -> None:
        self.records = {}


if __name__ == '__main__':
    my_class = MyClass()
    print(my_class.records)  # prints `{}`.

    my_class.records['1'] = 'one'
    print(my_class.records)  # prints `{'1': 'one'}`.

    # `my_class` is now overwritten and `records` has been set to `{}`.
    my_class = MyClass()
    print(my_class.records)  # prints `{}`

我的印象是 Python 的默认构造函数会使用我在 class 中声明的字段(在本例中为 records: Dict[str, str] = {})来覆盖现有对象。

让我更加困惑的是,当我使用常规字段时(string 在下面的情况下,但它也适用于自定义 classes),我不需要构造函数覆盖字段:

class MyClass:
    field: str = ''


if __name__ == '__main__':
    my_class = MyClass()
    print(my_class.field)  # prints nothing.

    my_class.field = 'some value'
    print(my_class.field)  # prints `some value`.

    # `field` has been reset to '' even though `MyClass` has no constructor.
    my_class = MyClass()
    print(my_class.field)  # prints nothing.

谁能解释一下 Python 在做什么?

Specifically, why is the dictionary field's state being changed instead of it being overwritten?

因为您没有分配给记录。你只是在读它。这个

my_class.records['1'] = 'one'

相当于这个

d = my_class.records
d['1'] = 'one'

此外,这就是添加该构造函数使代码“有效”的原因。因为在其中,您要重新分配 records.

self.records = {}

这是一个很好的话题,在 python 中开始学习基本数据类型时经常遇到它。 你知道嗡嗡声 - 一切都是对象


现在解决方案:

您遇到的问题有两个原因。

  1. 可变性
  2. Class变量

让我们一起探索吧。 在您演示的第一个代码中...让我向您展示(修剪后的代码)

class MyClass:
    records: Dict[str, str] = {}

my_class = MyClass()
print(my_class.records)  # prints `{}`.

这里发生了两件事,首先在 class space 中创建了一个名为 'record' 的变量,其数据类型为 Dict。其次它是一个可变数据类型。

在第二个示例中显示 str 数据类型,它是不可变的。 而且,这就是技巧发生的地方。

python 的工作方式有时会让您感到困惑。首先,这些是不可变和可变类型,它们将分别为您提供相同的行为:

不可变对象: int, float, long, complex, string tuple, bool
Mutable Object: list, dict, set, byte array, user-defined classes


↓ 与其搞得一团糟。考虑这个↓

a = [1, 2, 3]
b = a
a[0] = 111

# Now?
b
>>> [111, 2, 3]
#But
a = (1, 2, 3)
b = a
a = (111, 2, 3)

b
>>> (1, 2, 3)

我想这是众所周知的,但幕后的这个确切原则正在发生在你的案例中。

在这里您还可以检查不可变对象中的 id 是否会更改,但在可变对象中则不会。

如果我们不注意简单的事情,那么 python 有很多事情可以愚弄我们。 阅读更多内容 Amazing article focusing just on this