我是一名自学成才的程序员,我最近一直在学习python。我遇到了一个奇怪的问题,但我想这只是因为我不知道python语法和/或程序流程。
我有一个名为Test
的类,它位于文件TestClass.py
中。
`
class Test:
__tags = {}
__fields = {}
def __init__(self, tags: dict={}, fields: dict={}):
self.__tags = tags
self.__fields = fields
def setTag(self, key, value):
self.__tags[key] = value
def getTag(self, key):
return self.__tags[key]
def setField(self, key, value):
self.__fields[key] = value
def getField(self, key):
return self.__fields[key]
def getAll(self):
return [
{
'tags': self.__tags,
'fields': self.__fields
}
]
我正在包含过程代码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'}}]
为什么呢?
编辑: Python 3.5
答案 0 :(得分:3)
你只是陷入了一个,但在两个众所周知的Python#34;陷阱"对于新人。
此行为是预期的,要修复它,您应该将类声明的开头更改为:
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代码 - 包括表达式,出现在模块级别,或者在类体内部,或者在函数或方法声明中,只处理一次 - 当该模块首次加载时。
这意味着您在类体中创建的空字典以及此时创建为字典的__init__
级别的默认参数,并在每次实例化类时重复使用。
第一部分是直接在Python类体上声明的属性是 class 属性 - 这意味着它们将在该类的所有实例之间共享。如果在方法内部分配了self.attribute = XXX
的属性,则创建实例属性。
第二个问题是函数/方法参数的默认值与函数代码一起保存 - 因此在每个方法调用之后,您声明为空的字典都是相同的 - 并且在您的类的所有实例之间共享。
避免这种情况的通常模式是将默认参数设置为None
或其他选择的sentinel值,并在函数体内进行测试:如果没有值发送到这些参数,只需创建一个新的字典(或其他可变对象)实例。这是在实际执行该函数时创建的,并且对于该运行是唯一的。 (并且,如果您将它们分配给具有self.attr = {}
的实例属性,当然对该实例是唯一的)
至于我在答案or
中提出的self.__tags = tags or {}
关键字 - 它来自旧Python中常见的模式(在我们有一个inine if
之前)但仍然有用,其中"或"运算符快捷方式,以及
在像obj1 or obj2
这样的表达中,如果它评估为" truish"则返回第一个操作数。值,或返回第二个属性(如果它不是真实的,无关紧要,第二个参数的真值无论如何都是重要的)。使用内联的相同表达式" if"表达式为:self.__tags = tags if tags else {}
。
另外,很高兴提到虽然将两个__
添加到属性名称的模式,以便将旧教程中提到的内容称为" private"属性,这不是一个好的编程模式,应该避免。 Python实际上并没有实现私有或受保护的属性访问 - 我们使用的是一种约定,如果某个属性,方法或函数名称以_
(单个下划线)开头,则它适用于任何人的私人使用在那里编码,并且在控制这些属性的代码的未来版本中更改或调用那些可能具有未被捕获的行为 - 但代码中没有任何内容实际上阻止您这样做。
但是,对于双下划线前缀,有一个实际的副作用:在编译时,重命名前缀为__
的类属性,并重命名__xxx
到_<classname>__xxx
- 类体内的所有事件都以相同的方式重命名,类体外部的代码可以正常访问它,只需编写完整的受损名称。此功能旨在允许基类保存属性和方法,这些属性和方法不会在子类中被覆盖,无论是错误还是易于使用属性名称,(但不是出于&#34;安全&#34;目的) 。
旧语言教程和文本通常会将此功能作为一种方式来解释私有属性&#34;在Python中 - 那些实际上是不正确的。