2017-05-03 66 views
2

我是一个自学成才的程序员,我最近一直在学习python。我遇到了一个奇怪的问题,但我想这只是我不知道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

回答

3

你刚落不是一个,而是两个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代码 - 包括表情,无论是在模块级别还是在类体内部,或者在函数或方法声明中处理一次 - 当该模块首次加载时。

这意味着您在课程主体中创建的空字典以及__init__级别的默认参数,此时会创建为字典,并在每次类实例化时重新使用。

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

第二个问题是函数/方法参数的默认值与函数代码一起保存 - 所以您在每个方法调用后声明为空的字典都是相同的 - 并且在您的类的所有实例之间共享。

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

至于我在我的答案self.__tags = tags or {}提出了or关键字 - 它从旧Python中常见的模式乞求(我们之前有一个inode if)但仍然有用,其中“或”运算符快捷键和obj1 or obj2等表达式中的 返回第一个操作数(如果它评估为“truish”值),或者返回第二个属性(如果它不是非常,无所谓,第二个参数的真值无论如何都是重要的)。使用内联“if”表达式的相同表达式应为:self.__tags = tags if tags else {}

此外,很高兴地提到,虽然前置两个__属性名称的模式,以旧的教程中提到的“私有”属性,这不是一个好的编程模式,应该避免。 Python实际上并未实现私有或受保护的属性访问 - 我们所使用的约定是,如果某个属性,方法或函数名以_(单个下划线)开头,那么它就是私有用于编码它的人,在未来版本的控制这些属性的代码中更改或调用这些属性可能会有未曾行为的行为 - 但代码中的任何内容都不会阻止您这样做。

对于双下划线前缀,但是,有一个实际工作中的副作用:在编译时间,与__前缀class属性被重新命名,并且__xxx被重命名为_<classname>__xxx - 类主体中的所有ocurrences中被重命名同样的方式,以及类体外的代码可以正常访问它,只需编写完整的名称即可。这个特性的目的是允许基类拥有属性和方法,这些属性和方法不会在子类中被重写,或者是由于错误或易于使用属性名称(但不是为了“安全”目的)。

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

+0

你介意我问这是怎么改变输出的,我以前没见过这个或者是关键字的用法? –

+0

这实际上为我清除了一些东西。首先,我遇到了“'NoneType'对象不支持项目分配的问题”(设置'tags:dict = None')。其次,我遇到了我的问题中所描述的问题。这清除了两者,'或'很有意义!谢谢!在8分钟内将接受这个答案。 – nwilging

+0

三,如果你包括“东西必须是私人的,我会把双下划线到处”! – jonrsharpe