類別進階用法

Da-Wei Chiang

大綱

  • 類別的屬性
  • 類別的方法
  • 其它修飾器

類別的屬性

  • 實體屬性(Instance Attribute)
  • 類別屬性(Class Attribute)

實體屬性(Instance Attribute)

  • 每個物件各自獨立的屬性, 稱為實體屬性.
    • 主要撰寫於建構子內
In [8]:
## 範例

class Animal():
    def __init__(self, name, obj_type):
        #instance attribute
        self.name = name
        self.obj_type = obj_type
        
John = Animal('John', 'man')
print(John.name)
print(John.obj_type)
Mary = Animal('Mary', 'woman')
print(Mary.name)
print(Mary.obj_type)
print('- - - - - - - -')   
John.obj_type = 'old man' # 修改物件John的屬性, 並不影響Mary的屬性
print(John.name)
print(John.obj_type)
print(Mary.name)
print(Mary.obj_type)   
John
man
Mary
woman
- - - - - - - -
John
old man
Mary
woman

類別屬性(Class Attribute)

  • 建構物件時所共同擁有的屬性, 稱為類別屬性
    • 直接撰寫於類別內
In [3]:
## 範例

class Animal():
    #class attribute
    classification = 'human'
    def __init__(self, name, obj_type):
        #instance attribute
        self.name = name
        self.obj_type = obj_type
    
    
John = Animal('John', 'man')
print(John.name)
print(John.obj_type)
print(John.classification)    ## 透過物件呼叫類別屬性
print('- - - - -')
Mary = Animal('Mary', 'woman')
print(Mary.name)
print(Mary.obj_type)
print(Mary.classification)    ## 透過物件呼叫類別屬性
print('- - - - - - - - - - - - - - - -')
print(Animal.classification)   ## 注意寫法 - 透過類別呼叫類別屬性
Animal.classification = 'person'    ## 注意寫法 - 修改類別屬性
print(Animal.classification, id(Animal.classification))
print(John.classification, id(John.classification))
print(Mary.classification, id(Mary.classification))
print('- - - - - - - - - - - - - - - -')
John.classification = 'boy'   ##注意寫法 - 透過物件更改類別屬性, 等同於產生新的實體屬性
print(Animal.classification, id(Animal.classification))   
print(John.classification, id(John.classification))  
print(Mary.classification, id(Mary.classification))
print('- - - - - - - - - - - - - - - -')
print(Animal.name)   ## 類別無法呼叫實體屬性
John
man
human
- - - - -
Mary
woman
human
- - - - - - - - - - - - - - - -
human
person 4443776304
person 4443776304
person 4443776304
- - - - - - - - - - - - - - - -
person 4443776304
boy 4443811120
person 4443776304
- - - - - - - - - - - - - - - -
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-3-a1019e29f5e9> in <module>
     31 print(Mary.classification, id(Mary.classification))
     32 print('- - - - - - - - - - - - - - - -')
---> 33 print(Animal.name)   ## 類別無法呼叫實體屬性

AttributeError: type object 'Animal' has no attribute 'name'
In [38]:
## 範例 - 當物件屬性與類別屬性相同的狀況(無法使用物件呼叫類別屬性)

class Animal():
    #class attribute
    type_attribute = 'human'
    def __init__(self, name, type_attribute):
        #instance attribute
        self.name = name
        self.type_attribute = type_attribute
    
    
John = Animal('John', 'man')
print(John.name)
print(John.type_attribute)
print('- - - - -')
Mary = Animal('Mary', 'woman')
print(Mary.name)
print(Mary.type_attribute)
print('- - - - - - - - - - - - - - - -')  
print(Animal.type_attribute)
Animal.type_attribute = 'person'
print(Animal.type_attribute, id(Animal.type_attribute))
print(John.type_attribute, id(John.type_attribute))
print(Mary.type_attribute, id(Mary.type_attribute))
John
man
- - - - -
Mary
woman
- - - - - - - - - - - - - - - -
human
person 4576950640
man 4550032688
woman 4576888240

修改類別屬性

  • 除了可以直接透過類別呼叫修改類別屬性之外, 也可透過透過class修改
In [44]:
## 範例 - 透過物件修改類別屬性

class Animal():
    #class attribute
    classification = 'human'
    def __init__(self, name, obj_type):
        #instance attribute
        self.name = name
        self.obj_type = obj_type
    def reset_classVariable_classification(self):
        self.__class__.classification = 'monkey'

John = Animal('John', 'boy')
print(John.classification)
John.reset_classVariable_classification()  #透過物件實體修改類別屬性
print(John.classification, id(John.classification))
print(Animal.classification, id(Animal.classification))
human
monkey 4550202992
monkey 4550202992

練習

  • 請自行練習物件屬性與類別屬性
In [ ]:
## 猜猜以下執行結果

class Animal():
    father_attribute = 'animal'
    def __init__(self, name):
        self.name = name
class Human(Animal):
    son_attribute = 'Human'
    def __init__(self, name):
        self.name = name

print(Animal.father_attribute)
print(Human.son_attribute)
print('- '*5)
John = Human('John')
print(John.son_attribute)
print(John.father_attribute)
print('- '*5)
Bird = Animal('Bird')
print(Bird.father_attribute)
print(Bird.son_attribute)
In [ ]:
## 猜猜以下執行結果

class Animal():
    __father_attribute = 'animal'
    def __init__(self, name):
        self.name = name
    def get_attribute(self):
        return self.__class__.__father_attribute

print(Animal.__father_attribute)
Bird = Animal('Bird')
print(Bird.get_attribute())

練習

  • 請撰寫一個台灣大學的學生類別.
    • 每個學生有自己的姓名、學號(隨機產生)、科系.
    • 並具備查看學生所有資訊的功能
  • 並建立物件, 測試上述功能是否正常

類別的方法(Class Method)

  • 實體方法(Instance Method)
  • 類別方法(Class Method)
  • 靜態方法(Static Method)

實體方法(Instance Method)

  • 物件呼叫、使用的方法, 稱為實體方法
  • 實體方法必須包含一個self參數
In [47]:
## 範例

class Animal():
    #class attribute
    classification = 'human'
    def __init__(self, name, obj_type):
        #instance attribute
        self.name = name
        self.obj_type = obj_type
    #instance method
    def get_name(self):
        return self.name
    def set_name(self, name):
        self.name = name
John = Animal('John', 'man')
print(John.get_name())
John.set_name('Jimmy')
print(John.get_name())
John
Jimmy

類別方法(Class Method)

  • 撰寫類別方法需要使用修飾詞@classmethod, 並帶入屬性cls
    • 將修飾詞@classmethod撰寫於方法之前, 表示所撰寫的為類別方法
    • 撰寫類別方法需於第一個參數中寫入cls
In [15]:
## 範例

class Animal():
    #class attribute
    classification = 'human'
    def __init__(self, name, obj_type):
        #instance attribute
        self.name = name
        self.obj_type = obj_type
    #instance method
    def get_name(self):
        print(self)
        return self.name
    def set_name(self, name):
        self.name = name
    #class method
    @classmethod
    def eatting(cls):
        print(cls)
        print('%s 具備eatting技能' %(cls.classification))


John = Animal('John', 'man')
print(John.get_name())  
print('- - - - - - - ')
John.eatting()   #物件呼叫類別方法
print('- - - - - - - ')
Animal.eatting()   #類別呼叫類別方法
<__main__.Animal object at 0x108ec0a10>
John
- - - - - - - 
<class '__main__.Animal'>
human 具備eatting技能
- - - - - - - 
<class '__main__.Animal'>
human 具備eatting技能
In [27]:
## 範例 - 透過類別方法產生物件

class Animal():
    #class attribute
    classification = 'human'
    def __init__(self, name, obj_type):
        #instance attribute
        self.name = name
        self.obj_type = obj_type
    #instance method
    def get_name(self):
        print(self)
        return self.name
    def set_name(self, name):
        self.name = name
    #class method
    @classmethod
    def Woman(cls, name):
        return cls(name, 'woman')   #建構Animal物件
    @classmethod
    def Man(cls, name):
        return cls(name, 'man')    #建構Animal物件

Mary = Animal.Woman('Mary')
print(Mary.name)
print(Mary.obj_type)
print(Mary.get_name())
print('- - - - - - - ')
John = Animal.Man('John')
print(John.name)
print(John.obj_type)
print(John.get_name())
Mary
woman
<__main__.Animal object at 0x10731b310>
Mary
- - - - - - - 
John
man
<__main__.Animal object at 0x108ec0710>
John

練習

  • 請自行練習使用類別方法
In [ ]:
## 猜猜以下執行結果

class Animal():
    father_attribute = 'animal'
    def __init__(self, name):
        self.name = name
    def get_info(self):
        return Animal.get_attribute_info()
    @classmethod
    def get_attribute_info(cls):
        return cls.father_attribute
Bird = Animal('Bird')
print(Bird.get_info())
In [ ]:
## 猜猜以下執行結果

class Animal():
    father_attribute = 'animal'
    def __init__(self, name):
        self.name = name
    @classmethod
    def get_animal_info(cls):
        return cls.father_attribute
class Human(Animal):
    son_attribute = 'Human'
    def __init__(self, name):
        self.name = name
    @classmethod
    def get_human_info(cls):
        return cls.son_attribute
    
print('- '*5)
John = Human('John')
print(John.get_animal_info())
print(John.get_human_info())
In [ ]:
## 猜猜以下執行結果

class Animal():
    father_attribute = 'animal'
    def __init__(self, name):
        self.name = name
    @classmethod
    def get_attribute_info(cls):
        return cls.father_attribute
class Human(Animal):
    son_attribute = 'Human'
    __son_gender = 'man'
    def __init__(self, name):
        self.name = name
    @classmethod
    def get_attribute_info(cls):
        return cls.son_attribute
    @classmethod
    def get_gender(cls):
        return cls.__son_gender
John = Human('John')
print(John.get_attribute_info())
print(John.get_gender())
In [ ]:
## 猜猜以下執行結果

class Animal():
    __private_father_attribute = 'animal'
    def __init__(self, name):
        self.name = name
    @classmethod
    def __class_father_private_method(cls):
        return 'this is class private method'
    @classmethod
    def class_father_public_method(cls):
        return cls.__class_father_private_method();
class Human(Animal):
    __private_son_attribute = 'human'
    def __init__(self, name):
        self.name = name
    @classmethod
    def get_father_private_method(cls):
        return Animal.__class_father_private_method()
    def son_instance_methon_get_father_private_method(self):
        return self.__class__.__class_father_private_method()
    @classmethod
    def class_son_public_method(cls):
        return cls.__class_father_private_method();
John = Human('John')
print(John.get_father_private_method())
print(John.__class_father_private_method())
print(John.class_father_public_method())
print(John.son_instance_methon_get_father_private_method())
print(John.class_son_public_method())
print(Human.__class_father_private_method())
print(Human.class_father_public_method())

靜態方法

  • 撰寫靜態方法需要使用修飾詞@staticmethod, 靜態方法不需先寫任何參數(self or cls)
    • 將修飾詞@staticmethod撰寫於方法之前, 表示所撰寫的為靜態方法
  • 靜態方法既不屬於實體方法也不屬於類別方法. 故無法透過靜態方法來修改類別、實體屬性
In [49]:
## 範例

class Animal():
    #class attribute
    classification = 'human'
    def __init__(self, name, obj_type):
        #instance attribute
        self.name = name
        self.obj_type = obj_type
    #instance method
    def get_name(self):
        return self.name
    def set_name(self, name):
        self.name = name
    #staticmethod
    @staticmethod
    def Playing_Time():
        return '今天玩了一個下午'

John = Animal('John', 'boy')
print(John.get_name())
print(John.Playing_Time()) #物件呼叫靜態方法
print(Animal.Playing_Time())  #類別呼叫靜態方法
John
今天玩了一個下午
今天玩了一個下午

練習

  • 請練習撰寫靜態方法
In [ ]:
## 猜猜以下執行結果

class Animal():
    classification = 'animal'
    def __init__(self, obj_type):
        self.obj_type = obj_type
    @staticmethod
    def Animal_static():
        return Animal.classification

animal = Animal('animal')
animal.Animal_static()
In [ ]:
## 猜猜以下執行結果

class Animal():
    classification = 'animal'
    def __init__(self, obj_type):
        self.obj_type = obj_type
    @staticmethod
    def Animal_static():
        return Animal.classification

class Humal(Animal):
    def __init__(self, obj_type):
        self.obj_type = obj_type
    @staticmethod
    def get_father_static_method():
        return Animal_static()
    
John = Humal('Human')
print(John.Animal_static())
print(John.get_father_static_method())

其他修飾器

@property  #將方法轉換為「只能讀取」的屬性(透過此修飾器達到封裝效果)
In [5]:
## 範例

class Bank():
    def __init__(self, name):
        self.name = name
    @property
    def password(self):
        return 'my_password'

Richard = Bank('Richard')
print(Richard.password)   #注意因為property將方法轉為屬性, 故不需要括號()
Richard.password = 'Change Passwd'   #只可讀取不可修改
my_password
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-5-3b24134b3e96> in <module>
     10 Richard = Bank('Richard')
     11 print(Richard.password)   #注意因為property將方法轉為屬性, 故不需要括號()
---> 12 Richard.password = 'Change Passwd'   #只可讀取不可修改

AttributeError: can't set attribute

練習

  • 請練習使用@property
In [ ]:
## 猜猜以下執行結果

class Bank():
    def __init__(self, name):
        self.name = name
    @property
    @staticmethod
    def password():
        return 'my_password'

Richard = Bank('Richard')
print(Richard.password)   

@property轉屬性後搭配使用

_________.setter   #___________為轉換的屬性名稱

_________.deleter  #___________為轉換的屬性名稱
In [6]:
## 範例

class Bank():
    def __init__(self, name):
        self.name = name
        self.word = 'My password'
    @property
    def password(self):
        return self.word
    
    @password.setter    #注意寫法
    def password(self, value):
        self.word = value
    
    @password.deleter   #注意寫法
    def password(self):
        del self.word
        print('delete self.password')

Richard = Bank('Richard')
print(Richard.password)   #注意因為property將方法轉為屬性, 故不需要括號()
Richard.password = "change password"    #透過setter可重新定義password
print(Richard.password)
del Richard.password
print(Richard.password)
My password
change password
delete self.password
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-6-a8466536abca> in <module>
     23 print(Richard.password)
     24 del Richard.password
---> 25 print(Richard.password)

<ipython-input-6-a8466536abca> in password(self)
      7     @property
      8     def password(self):
----> 9         return self.word
     10 
     11     @password.setter    #注意寫法

AttributeError: 'Bank' object has no attribute 'word'

注意 - 透過@property所轉換的屬性不可先定義於建構子內

In [9]:
## 範例

class Bank():
    def __init__(self, name):
        self.name = name
        self.password = 'My password'
    @property
    def password(self):
        return self.password

Richard = Bank('Richard')
print(Richard.password)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-9-eb46ea11c6ad> in <module>
      9         return self.password
     10 
---> 11 Richard = Bank('Richard')
     12 print(Richard.password)

<ipython-input-9-eb46ea11c6ad> in __init__(self, name)
      4     def __init__(self, name):
      5         self.name = name
----> 6         self.password = 'My password'
      7     @property
      8     def password(self):

AttributeError: can't set attribute

練習

  • 請練習使用@property修飾器

關於__name__

  • 確認目前所執行的程式屬於哪一支

練習

  • 建構一個類別, 類別中除了建構子之外在建立兩個方法getshow.
    • get印出get文字即可
    • show印出show文字即可
  • 建構完class後印出__name__.並建構物件執行此方法.

練習

  • 將上述程式整個複製併貼到name.py的檔案中.
  • 透過呼叫這支模組並使用show方法.
  • 觀察執行結果

上述練習我們發現. 呼叫show方法時. 該類別中建構物件及其方法的結果也會一併印出.

這是因為當引用這個模組時. python直譯器會去讀取並執行每一行.

因此我們需要修改name.py的程式. 將不屬於主程式執行的部分加入條件判斷.如下所示

In [ ]:
## 範例

class Example():
    def __init__(self):
        pass
    def get(self):
        print('get')
    def show(self):
        print('show')
        
if(__name__ == '__main__'):   #加入此行進行判斷
    print('__name__ :', __name__)
    e = Example()
    e.get()
    e.show()

練習

  • 請自行練習__name__

總練習 - 請設計遊戲類別

  • 網路遊戲中有兩種職業, 分別為劍士與法師
    • 劍士
      • 劍士除了具備玩家輸入的基本屬性外, 還具備跑步及騎乘寵物的技能
      • 劍士的攻擊技能, 玩家可自行選擇學習「神聖劍」或「二刀流」
    • 法師
      • 法師除了具備玩家輸入的基本屬性外, 還具備飛行及瞬移的技能
      • 劍士的攻擊技能, 玩家可自行選擇學習「火系魔法」或「冰系魔法」
  • 請以上述描述練習建構類別.