2014-01-23 60 views
0

我在學習Python,但沒有OOP體驗。我進入IDLE以下行(Python的3.3),和對象的行爲是混淆了我:Python中類的特殊行爲

>>> class IBAN: 
    ISOcode = ' ' 
    checkDigits = '00' 
    class CCC: 
     bankCode = '0000' 
     branchCode = '0000' 
     checkBank = '0' 
     checkAccount = '0' 
     account = '0000000000' 


>>> >>> numberOne = IBAN() 
>>> numberOne.CCC 
<class '__main__.IBAN.CCC'> 
>>> numberOne.CCC.bankCode 
'0000' 
>>> numberOne.ISOcode = 'ES' 
>>> IBAN.ISOcode 
' ' 
>>> numberOne.ISOcode 
'ES' 
>>> IBAN.ISOcode = 'FR' 
>>> numberOne.ISOcode 
'ES' 
>>> IBAN.ISOcode = 'FR' 
>>> IBAN.ISOcode 
'FR' 
>>> numberTwo = IBAN() 
>>> numberTwo.ISOcode 
'FR' 
>>> IBAN.ISOcode = 'IT' 
>>> IBAN.ISOcode 
'IT' 
>>> numberOne.ISOcode 
'ES' 
>>> numberTwo.ISOcode 
'IT' 
>>> IBAN.ISOcode is numberTwo.ISOcode 
True 
>>> 

爲什麼對象numberTwo一樣的IBAN的ISOcode的ISOcode屬性,但不是與對象numberOne的ISO代碼相同?

我希望這樣的事情:

>>> numberOne.ISOcode 
'ES' 
>>> numberTwo.ISOcode 
'FR' 

然而,這不符合我的結果。

我發現a similar question on StackOverflow,這使我想到的結果應該是:

>>> numberOne.ISOcode 
'IT' 
>>> numberTwo.ISOcode 
'IT' 

但是,這並不要麼符合我的結果。

任何人都可以解釋或鏈接到解釋Python中類的行爲的資源嗎?我試圖學習Guido van Rossum的Python教程和「Unifying types and classes in Python 2.2」,但我無法理解他們在說什麼。

謝謝!

回答

4

該類是與該類的實例不同的對象。類具有屬性,而實例具有屬性。每次嘗試訪問實例的屬性時,如果該實例沒有該屬性,則會在該類上查找它。如果在實例上設置了屬性,它總是在實例上設置,即使這意味着它會在該類上隱藏同名的屬性。

在你的類IBAN,你的類中定義的屬性ISOcode。當您執行numberOne.ISOcode = 'ES'時,您在實例上設置了新的屬性。您已將numberOne.ISOcode的值與值IBAN.ISOcode解耦。但是您創建的任何新實例(如numberTwo)仍然沒有自己的實例ISO代碼。當您再設置IBAN.ISOcode時,您正在更改類別屬性。

您可以將class屬性看作「默認」。當您做obj.attr時,如果obj沒有自己的屬性attr,則該值取值爲,此時。如果您更改班級屬性的值,則更改默認值;如果以後嘗試訪問沒有自己屬性的實例的屬性,他們將看到您創建的新默認值。

這裏是一個略微更精簡例如:

>>> class Foo(object): 
...  attr = "Something" 
>>> one = Foo() 
>>> two = Foo() 

起初,都具有相同的價值,因爲我沒有指定任何特定實例的值,因此onetwo無論是從得到他們的價值類。

>>> Foo.attr 
'Something' 
>>> one.attr 
'Something' 
>>> two.attr 
'Something' 

現在我將爲one設置一個新值。這不會影響Foo,並且two仍然從Foo獲得其值,因此two也不受影響。

>>> one.attr = "This is only for #1" 
>>> Foo.attr 
'Something' 
>>> one.attr 
'This is only for #1' 
>>> two.attr 
'Something' 

現在我將爲類Foo設置一個新值。這不會影響one,因爲one有其自己的價值。但由於two不具有其自身的價值,它是從類獲取其價值,因爲我改變了類的值,這改變了什麼樣的價值,我從two得到:

>>> Foo.attr = "This is the class value" 
>>> Foo.attr 
'This is the class value' 
>>> one.attr 
'This is only for #1' 
>>> two.attr 
'This is the class value' 

要記住的一點是,每次查找屬性時,都會動態計算出來。創建實例不會自動將類屬性從類複製到實例。當你訪問一個實例的屬性時,這些實例對自己說:「我有我自己的屬性這個名字嗎?如果是,返回該值,否則返回類的值。」它每次詢問自己你試圖訪問一個屬性;沒有什麼時候該實例變成了「獨立」的類。

+0

非常感謝@BrenBarn!這清楚了我對Python屬性處理方面的很多想法。 – Trimax

0

ISOcode在您的例子定義是什麼所謂的類屬性,而不是實例屬性

您應該設置self.ISOcode類的__init__方法內,使其獨特的每個實例。否則,該屬性將存儲在該類中。

class IBAN: 
    def __init__(self): 
     self.ISOcode = ' ' 
+0

Thanks @mhlester。我想爲每個實例默認屬性值,但不是類屬性。 – Trimax

0

您正在通過類屬性設置實例屬性來做一些令人迷惑的名稱遮蔽。

>>> class A(): 
    stuff = '' 

>>> a = A() 
>>> a.stuff 
'' 
>>> a.stuff = 'thing' # sets an instance attribute 
>>> a.stuff 
'thing' 
>>> a.__class__.stuff 
'' 
>>> a.__class__.stuff = 'blah' 
>>> a.stuff # instance attributes are checked FIRST 
'thing' 
>>> aa = A() 
>>> aa.stuff 
'blah' 

基本上,不要這樣做。如果您打算將屬性設置爲實例屬性,請將其設置爲__init__

+0

我認爲問題是他OP沒有意識到正在使用的類屬性。 – brianmearns

+0

_'name shadowing__?他們在不同的命名空間嗎?我以爲它們在同一個命名空間中,所以我們通過用它們的對象名稱加前綴來稱呼它們。 – Trimax

+0

@Trimax它不是名稱空間的問題(實例和類屬性被一起填充到單個名稱空間中),而是屬性查找優先級的問題 - 首先檢查實例的屬性,然後是類。 – roippi

0

Python有類屬性和實例屬性。當您指定一個值時,如

x.ISOcode = 'b' 

它爲該實例創建一個成員屬性。當你首先訪問一個對象中的一個屬性時,它會在成員屬性中進行搜索,如果沒有找到,它會在類屬性中進行搜索

請看下面的例子

class IBAN(object): 
    ISOcode = 'a' 

print IBAN.ISOcode // outputs a 

x = new IBAN() 
print x.ISOcode // outputs "a" 
x.ISOcode = 'b' // creates a member variable for instance x instead of modifying class variable 

print IBAN.ISOcode // outputs "a" 
print x.ISOcode // outputs "b" 
print x.__class__.ISOcode // outputs "a" 

y = new IBAN() 
print y.ISOcode // outputs "a" 

IBAN.ISOcode = 'c' 

print IBAN.ISOcode // outputs "c" 
print x.ISOcode // outputs "b" because member variable is set 
print x.__class__.ISOcode // outputs "c" 
print y.ISOcode // outputs "c" because it still uses class variable. no instance variable set for this instance 
+0

謝謝@ZubaiR。一個很好的例子,很好的解釋。 – Trimax

0

你看到的行爲是因爲對Python對象成員訪問的備用行爲。 numberOneIBAN類的一個實例,當您嘗試訪問它的成員(例如numberOne.ISOCode)時,它將檢查對象本身是否有名爲ISOCode的成員。如果是這樣,它會使用它。如果沒有,那麼它會回退到對象的類型,並檢查該類型是否具有該名稱的成員,並使用它。

但是,當您在對象上設置成員時(例如numberOne.ISOCode = 'ES'),它不執行回退行爲,它使用該對象的現有成員,或者如果沒有此成員,它將創建它。

這可能並不重要,因爲你從來沒有做過OOP,但是Python做的事情與其他語言有所不同。您不需要在Python類中聲明實例成員,因此當您在IBAN類的正下方設置ISOCode時,就會使其成爲IBAN類對象的成員,而不是該類的單個實例。如果您希望該類的所有實例都有一個字段,請將其分配給該類的構造函數__init__。例如:

#!/bin/python 

class IBAN: 
    def __init__(self): 
     self.ISOCode = ' ' 

這將確保創建一個實例時,都會有它自己的ISOCode成員。

+0

謝謝@ sh1ftst0rm!術語:OOP中的_'member'='attribute'_?另一方面,我認爲P.沒有一個類構造函數。 – Trimax