iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 20
2
自我挑戰組

30天學python系列 第 20

[Day20] Python 語言進階 - 4

  • 分享至 

  • xImage
  •  

3.物件導向的相關知識

  • 三大支柱:封裝 (encapsulation)、繼承 (inheritance)、多型 (polymorphism)

範例 - 工資结算系统。之前前面練習有提到。
某公司有是部門經理、程序員和銷售員,根據提供的員工訊息來計算月薪。
部門經理的月薪是每月固定 45000 元
程序員的月薪按本月工作時間計算每小時 200 元
銷售員的月薪是 22000 元的底薪加上銷售額 5% 的加成

from abc import ABCMeta, abstractmethod

class Employee(metaclass = ABCMeta):    # 抽象父類別 (class) 員工

    def __init__ (self, name):
        self.name = name

    @abstractmethod
    def get_salary(self):   # 月薪
        pass

class Manager(Employee):        # 子類別 (class) 部門經理

    def get_salary(self):       # 繼承抽象父類別 (class) 並覆寫
        return 45000.0

class Programmer(Employee):     # 子類別 (class) 程序員
    
    def __init__ (self, name, working_hour = 0):
        self.working_hour = working_hour
        super().__init__(name)  # 繼承抽象父類別 (class)

    def get_salary(self):       # 繼承抽象父類別 (class) 並覆寫
        return 200.0 * self.working_hour

class Salesman(Employee):        # 子類別 (class) 銷售員

    def __init__(self, name, sales = 0.0):
        self.sales = sales
        super().__init__(name)   # 繼承抽象父類別 (class)

    def get_salary(self):        # 繼承抽象父類別 (class) 並覆寫
        return 22000.0 + self.sales * 0.05

class EmployeeFactory():    
    # 創建員工的工廠(透過工廠實現物件使用者和物件之間的解耦合 (decoupling))

    @staticmethod
    def create(emp_type, *args, **kwargs):  # 創建員工
        emp_type = emp_type.upper()
        emp = None
        if emp_type == 'M':
            emp = Manager(*args, **kwargs)
        elif emp_type == 'P':
            emp = Programmer(*args, **kwargs)
        elif emp_type == 'S':
            emp = Salesman(*args, **kwargs)
        return emp

def main():
    """主函数"""
    emps = [
        EmployeeFactory.create('M', 'Alan'), 
        EmployeeFactory.create('P', 'Andy', 120),
        EmployeeFactory.create('P', 'Angle', 85), 
        EmployeeFactory.create('S', 'Alex', 123000),
    ]
    for emp in emps:
        print('%s : %.2f 元' % (emp.name, emp.get_salary()))

if __name__ == '__main__':
    main()

https://ithelp.ithome.com.tw/upload/images/20191001/20121116QvZ8u49zWv.png

  • class 之間的關係
    • is-a 關係:繼承
    • has-a 關係:關聯 / 聚合 / 合成
    • use-a 關係:依賴

範例 - 撲克牌遊戲。之前前面練習有提到。

from enum import Enum, unique
import random

@unique
class Suite(Enum):  # 花色

    SPADE, HEART, CLUB, DIAMOND = range(4)
    def __lt__(self, other):
        return self.value < other.value

class Card():       # 每張牌

    def __init__(self, suite, face): # 初始化(花色,數字)
        self.suite = suite
        self.face = face

    def show(self): # 顯示牌面
        suites = ['♠️', '♥️', '♣️', '♦️']
        faces = ['', 'A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
        return f'{suites[self.suite.value]} {faces[self.face]}'

    def __str__(self):
        return self.show()

    def __repr__(self):
        return self.show()

class Poker():  # 一副牌

    def __init__(self):
        self.index = 0
        self.cards = [Card(suite, face)
                      for suite in Suite
                      for face in range(1, 14)]

    def shuffle(self):  # 洗牌(隨機)
        random.shuffle(self.cards)
        self.index = 0

    def deal(self):     # 發牌
        card = self.cards[self.index]
        self.index += 1
        return card

    @property
    def has_more(self): # 還有沒有牌
        return self.index < len(self.cards)

class Player():         # 玩家

    def __init__(self, name):
        self.name = name
        self.cards = []

    def get_one(self, card): # 拿牌
        self.cards.append(card)

    def sort(self, comp = lambda card: (card.suite, card.face)):
         # 整理牌
        self.cards.sort(key = comp)

def main():
    poker = Poker()
    poker.shuffle()
    players = [Player('pA'), Player('pB'), Player('pC'), Player('pD')]
    while poker.has_more:
        for player in players:
                player.get_one(poker.deal())
    for player in players:
        player.sort()
        print(player.name, end = ': ')
        print(player.cards)

if __name__ == '__main__':
    main()

https://ithelp.ithome.com.tw/upload/images/20191001/20121116NSTcunGLU3.png

  • 物件的複制(深度複製 / 深拷貝 / 深度克隆和淺度複製 / 淺拷貝 / 影子克隆)
    深度複製(Deep Copy):不僅複製物件的基本型別,同時也複製原物件中的物件.完全產生新物件。
    淺度複製(Shallow Copy):只複製物件的基本型別,物件型別,仍屬於原來的引用。

  • 垃圾回收、循環引用和弱引用
    指不能確保其引用的對象不會被垃圾回收器回收的引用。

    以下情況會導致垃圾回收:

    • 調用 gc.collect()
    • gc 模組的計數器達到閥值
    • 程序退出

    Python 使用自動化內存管理,以引用計數為基礎,也引入標記 - 清除分代收集兩種機制為輔。

    導致引用計數 +1 的情況:

    • 物件被創建,例如 a = 23
    • 物件被引用,例如 b = a
    • 物件被作為參數,傳入到一個函數中,例如 f(a)
    • 物件作為一個元素,存儲在容器中,例如 list1 = [a, a]

    導致引用計數 -1 的情況:

    • 物件的別名被顯式銷毀,例如 del a
    • 物件的別名被賦予新的對象,例如 a = 24
    • 一個物件離開它的作用域,例如 f 函數中的區域變數執行完畢時(全域變數不會)
    • 物件所在的容器被銷毀,或從容器中刪除物件

    引用計數可能會導致循環引用問題,而循環引用會導致內存洩露。

    list1 = []
    list2 = []
    list1.append(list2)
    list2.append(list1)
    

    為了解決這個問題,Python 中引入'標記 - 清除'和'分代收集'。
    在創建一個物件的時,物件被放在第一代中,如果在第一代的垃圾檢查中,物件存活下來,該物件就會被放到第二代 中,同理在第二代的垃圾檢查中對象存活下來,該物件就會被放到第三代中。
    如果不想造成循環引用可以使用 weakref 模組構造弱引用。

  • 混入 (Mixin)

範例 - 自定義字典限制只有在指定的 key 不存在時才能在字典中設置鍵值對。

class SetOnceMappingMixin(): # 定義混入 (Mixin) 類別
    __slots__ = ()

    def __setitem__(self, key, value):
        if key in self:
            raise KeyError(str(key) + ' already set')
        return super().__setitem__(key, value)

class SetOnceDict(SetOnceMappingMixin, dict):
    # 定義字典
    pass

my_dict = SetOnceDict()
try:
    my_dict['username'] = 'jackfrued'
    my_dict['username'] = 'hellokitty'
except KeyError:
    pass

print(my_dict)

https://ithelp.ithome.com.tw/upload/images/20191001/20121116Ab1ACyejz2.png

  • 元編程和元類別

範例 - 用元類別實現單例模式。

import threading

class SingletonMeta(type):   # 定義元類別

    def __init__(cls, *args, **kwargs):
        cls.__instance = None
        cls.__lock = threading.Lock()
        super().__init__(*args, **kwargs)

    def __call__(cls, *args, **kwargs):
        if cls.__instance is None:
            with cls.__lock:
                if cls.__instance is None:
                    cls.__instance = super().__call__(*args, **kwargs)
        return cls.__instance

class President (metaclass = SingletonMeta):
    # 單例類別
    pass
    
t1 = President()
t2 = President()
print(t1)
print(t2)

https://ithelp.ithome.com.tw/upload/images/20191001/20121116jnS1WZW0JT.png

  • 物件導向設計原則 (SOLID)

    • 單一功能原則 (SRP) - 物件應該具有一種單一功能的概念。
    • 開閉原則 (OCP) - 軟體應該是對於擴充開放的,但是對於修改封閉的。
    • 里氏替換原則 (LSP) - 任何時候可以用子類別物件替換掉父別物件。
    • 介面隔離原則 (ISP) - 接口要小而專不要大而全。
    • 依賴反轉原則 (DIP) - 抽象介面不應該依賴於具體實現,而具體實現則應該依賴於抽象介面。
    • 合成聚合複用原則 (CARP) - 使用物件組合,而不是繼承來達到複用的目的。
    • 最少知識原則 (迪米特法則,LoD) - 不要給沒有必然聯繫的物件發消息。
  • GoF 設計模式

    • 創建型模式:單例、工廠、建造者、原型
    • 結構型模式:適配器、外觀、代理
    • 行為型模式:迭代器、觀察者、狀態、策略

範例 - 找出文件的雜湊函式 (Hash function)。
要先下載 Python-3.7.1.tgz 此檔案才能測試程式,其他檔案也行。

class StreamHasher():	# 雜湊函數生成器 (策略模式)

    def __init__(self, alg='md5', size=4096):
        self.size = size
        alg = alg.lower()
        self.hasher = getattr(__import__('hashlib'), alg.lower())()

    def __call__(self, stream):
        return self.to_digest(stream)

    def to_digest(self, stream):	# 生成十六進制形式
        for buf in iter(lambda: stream.read(self.size), b''):
            self.hasher.update(buf)
        return self.hasher.hexdigest()

def main():
    hasher1 = StreamHasher()
    with open('Python-3.7.1.tgz', 'rb') as stream:
        print(hasher1.to_digest(stream))
    hasher2 = StreamHasher('sha1')
    with open('Python-3.7.1.tgz', 'rb') as stream:
        print(hasher2(stream))

if __name__ == '__main__':
    main()

https://ithelp.ithome.com.tw/upload/images/20191002/20121116AjgEhXnY3o.png


上一篇
[Day19] Python 語言進階 - 3
下一篇
[Day21] Python 語言進階 - 5
系列文
30天學python30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言