物件與類別變數

      在〈物件與類別變數〉中尚無留言

物件變數與類別變數

物件變數是存在於每個實例出來的物件之內, 比如實例出 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

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *