Python类实例变量隔离

Python类实例变量隔离,python,python-3.x,oop,Python,Python 3.x,Oop,我是一名自学成才的程序员,最近一直在学习python。我遇到了一个奇怪的问题,但我想这只是我不知道python语法和/或程序流的结果 我有一个名为Test的类,它位于文件TestClass.py中。 ` 我正在一个包含过程代码的文件中测试这个类的功能,test.py import TestClass t1 = TestClass.Test() t1.setTag('test1', 'value1') t1.setField('testfield', 'fieldvalue') t2 = Te

我是一名自学成才的程序员,最近一直在学习python。我遇到了一个奇怪的问题,但我想这只是我不知道python语法和/或程序流的结果

我有一个名为
Test
的类,它位于文件
TestClass.py
中。 `

我正在一个包含过程代码的文件中测试这个类的功能,
test.py

import TestClass

t1 = TestClass.Test()
t1.setTag('test1', 'value1')
t1.setField('testfield', 'fieldvalue')

t2 = TestClass.Test()
t2.setTag('test2', 'value2')

print(t1.getAll())
print(t2.getAll())
print
语句是事情变得奇怪的地方。输出应为:

[{'tags': {'test1': 'value1'}, 'fields': {'testfield': 'fieldvalue'}}]
[{'tags': {'test2': 'value2'}, 'fields': {}}]
但实际产出是

[{'tags': {'test2': 'value2', 'test1': 'value1'}, 'fields': {'testfield': 'fieldvalue'}}]
[{'tags': {'test2': 'value2', 'test1': 'value1'}, 'fields': {'testfield': 'fieldvalue'}}]
为什么呢


编辑:Python3.5

您不是落入一个Python陷阱,而是落入了两个为新来者所熟知的Python“陷阱”

此行为是预期的,若要修复此行为,应将类声明的开头更改为:

from typing import Optional 


class Test:
    def __init__(self, tags: Optional(dict)=None, fields: Optional(dict)=None):
        self.__tags = tags or {}
        self.__fields = fields or {}
        ...
    ...
现在理解“为什么会这样?”:
当第一次加载模块时,Python代码(包括表达式,出现在模块级别、类主体内部或函数或方法声明中)只处理一次

这意味着您在类主体中以及在
\uuuu init\uuu
级别的默认参数上创建的空词典,此时在该级别创建为词典,并在每次实例化该类时重复使用

第一部分是Python中直接在类主体上声明的属性是类属性——这意味着它们将在该类的所有实例中共享。如果在方法中使用
self.attribute=XXX
指定属性,则创建实例属性

第二个问题是函数/方法参数的默认值与函数代码一起保存,因此在每次方法调用后声明为空的字典都是相同的,并在类的所有实例中共享

避免这种情况的通常模式是将默认参数设置为
None
或选择的其他sentinel值,并在要测试的函数体中:如果没有向这些参数发送值,只需创建一个新的字典(或其他可变对象)实例。这是在函数实际执行时创建的,并且对于该运行是唯一的。(并且,如果您将它们分配给具有
self.attr={}
的实例属性,则它们当然是该实例的唯一属性)

至于我在回答中提出的
关键字
self.\uu tags=tags或{}
——它源自旧Python中常见的一种模式(在我们有inine
if
之前),但仍然很有用,其中“or”操作符的快捷方式 在表达式(如
obj1或obj2
)中,如果第一个操作数的计算结果为“truish”值,则返回第一个操作数,或者返回第二个属性(如果不是truish,则无所谓,第二个参数的真值才是最重要的)。使用内联“if”表达式的同一表达式是:
self.\uu tags=tags if tags else{}

另外,值得一提的是,虽然在属性名前加两个
\uuuuu
的模式是为了拥有旧教程中提到的“私有”属性,但这不是一种好的编程模式,应该避免。Python实际上并没有实现私有或受保护的属性访问—我们使用的是一种约定,即如果某个属性、方法或函数名以
\uuu
开头(一条下划线),则它意味着供在那里编码的人私有使用,在控制这些属性的未来版本的代码中,更改或调用这些属性可能会有未被执行的行为,但代码中没有任何内容实际上阻止您这样做

但是,对于双下划线前缀,有一个实际的副作用:在编译时,重命名以
\uuuuuu
为前缀的类属性,并将
\uuuuuuuxxx
重命名为
\uuuuuuuuuuuuxxx
——类主体内的所有引用都以相同的方式重命名,并且类主体外的代码可以正常访问它,只是写下完整的名字。此功能旨在允许基类保存不会在子类中被重写的属性和方法,无论是由于错误还是易于使用属性名(但不是出于“安全”目的)


旧的语言教程和文本通常将此功能解释为在Python中执行“私有属性”的一种方式——这些实际上是不正确的

你介意我问一下这是如何改变输出的吗?我以前从未见过像这样使用or关键字?这实际上为我澄清了一些事情。首先,我遇到了“非类型”对象不支持项分配的问题(设置
标记:dict=None
)。第二,我遇到了我的问题中描述的问题。这将清除这两种情况,
非常有意义!非常感谢。我会在8分钟内接受这个答案。第三,如果你包括“事情必须是私人的,我会在所有地方加上双下划线”@jonrsharpe我现在经历了一个转变:)类C语言中不常见使用
的模式,它们总是生成布尔整数0或1,而不是选择一个参数值。在引入
if/else
表达式之前,它在Python中非常方便,如本例所示,可以更加清晰。
from typing import Optional 


class Test:
    def __init__(self, tags: Optional(dict)=None, fields: Optional(dict)=None):
        self.__tags = tags or {}
        self.__fields = fields or {}
        ...
    ...