物件變數與類別變數
物件變數是存在於每個實例出來的物件之內, 比如實例出 p1, p2二隻神奇寶貝, p1 有自已的level, p2也有自已的level. 物件變數就如每個人的手, 你的手斷了, 不干我的事, 我的手依然在.
類別變數則是存在於類別之內, 每個物件共用的變數, 比如count, 為p1, p2共同擁有. 類別變數就好比太陽, 我的太陽跟你的太陽, 都是同一顆. 你的太陽被射下來了, 我的太陽也跟著沒了.
物件變數
請先注意下面程式碼藍色的地方, self.name=name, 此行會建立name 物件變數, 而 p1.level=100 此行也是在建立 level物件變數, 二者都是物件變數.
也就是說, 物件變數不一定要在類別裏建立, 也可以在外部隨意的創建. 在外部創建物件變數, 大陸用語為動態綁定屬性. 此點跟Java/C#不一樣, 強型語言的物件變數, 一定要在類別裏先建好. 這是熟悉強型語言的人特別容易搞混的地方.
那麼在類別裏建立物件變數, 跟在外部建立物件變數, 有什麼差別呢? 如果在外部建立 p1.w=50, 那麼p2是沒有 w 的. 也就是說, 在類別內建立的物件變數, 每個物件都會有. 但在外部建立的, 不一定每個物件都有, 都要足一的建立.
class Pokemon(object): def __init__(self, name): self.name=name p1=Pokemon("Eve") p1.level=100 print("%s的level : %d" % (p1.name, p1.level))
物件變數生成時機
在Java, C#等語言中, 物件變數定義在類別內, 所以只要一new出物件, 物件變數就存在了. 但在Python裏卻不一樣, 這也是熟強型語言的人易混亂的地方, 請看如下代碼
1 class Pokemon(object):
2 def setLevel(self, l):
3 self.level=l
4 p1=Pokemon();
5 p1.setLevel(100) #此行若不執行, 下一行列印將會出錯
6 print(p1.level)
在上述代碼中, 第4行實例出p1後, p1是沒有level這個物件變數的. 一直要到執行第5行後, level才會出現. 也就是說, 如果第5行註解掉不執行的話, 那第6行將出跳出未定義level的錯誤訊息.
因此, 若希望實例化物件後, 就有物件變數的話, 就必需在 __init__()中初始化
類別變數
類別變數是直接標示在class之內,方法之外。如果在類別內的方法或是建構子要存取類別變數,一定要加上類別名稱,如Pokemon.count。那如果是在類別之外要存取類別變數,也一樣要加上類別名稱。
class Pokemon(object): count=0 #類別變數 def __init__(self, name, level=0): self.name=name self.level=level Pokemon.count+=1 p1=Pokemon("Eve") print("第%d隻 Pokemon出生,name: %s, level : %d" % (Pokemon.count, p1.name, p1.level)) p2=Pokemon("Pikachu", 100) print("第%d隻 Pokemon出生,name: %s, level : %d" % (Pokemon.count, p2.name, p2.level)) p3=Pokemon("Frog") print("第%d隻 Pokemon出生,name: %s, level : %d" % (Pokemon.count, p3.name, p3.level))
物件與類別變數名稱衝突
當物件變數與類別變數的名稱相同時, 就會造成衝突, 類別變數就會被遮蔽掉. 這也是熟悉強型語言的人常常搞混的地方.
如下程式碼所示, 在類別中標示了tag=”類別變數” 的類別變數. 但在類別外, 卻有另一個 p1.tag= “物件變數”, 此行是要創建一個新的物件變數.
這下好玩了, 這個物件同時擁有類別變數tag, 也擁有物件變數tag, 那麼要印出 p1.tag時, 類別變數tag會被遮蔽掉, 只能顯示物件變數. 此時如果要印出類別變數, 那麼就需使用 Pokemon.tag.
class Pokemon(object): tag="類別變數" def __init__(self, name="", level=0): self.name=name self.level=level p1=Pokemon(); p1.tag="物件變數" #創建新的物件變數 print("p1的tag : %s, Pokemon的tag : %s" % (p1.tag, Pokemon.tag)) 結果 : p1的tag : 物件變數, Pokemon的tag : 類別變數
上述類別變數被遮蔽時, 如果下達 del p1.tag, 物件變數就會消失掉而失去遮蔽效應. 這時候再使用 p1.tag, 類別變數就又跑出來了
class Pokemon(object): tag="類別變數" def __init__(self, name="", level=0): self.name=name self.level=level p1=Pokemon(); p1.tag="物件變數"#創建新的物件變數 print("p1的tag : %s, Pokemon的tag : %s" % (p1.tag, Pokemon.tag)) del p1.tag print("p1的tag : %s, Pokemon的tag : %s" % (p1.tag, Pokemon.tag)) 結果: p1的tag : 物件變數, Pokemon的tag : 類別變數 p1的tag : 類別變數, Pokemon的tag : 類別變數
物件取得類別變數
上述之所以這麼複雜, 全是因為可以從物件名稱取得類別變數所引起的. 說實在的, “你的太陽”~~你以為你是誰啊, 憑什麼說太陽是你的!!!
當初在設計物件導向時, p1.count 這種由物件名稱取得類別變數的設計, 是錯誤有缺陷的. 所以盡可能使用類別名稱取得類別變數(Pokemon.count).
這錯誤的設計, 在Java依然存在著. 也就是說在Java 仍可使用p1.count. 不過在C#中, 是被禁止的.
另外, 在設計程式時, 物件變數及類別變數也盡可能避開使用相同的名字, 不然日後維護時, 會發現拿石頭砸自已的腳是很痛der