2017-05-03 101 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