2026年8月31日 星期一

Python一下:用類別打造智慧養殖場域與感測器物件

《Python一下:從風土資料到智慧生活》第 12 篇

用類別打造智慧養殖場域與感測器物件

把場域資料與操作規則放在一起,讓程式更接近真實世界的設備與管理工作。

CH8 類別物件導向智慧養殖
學習目標
完成本篇後,你能建立類別與物件,理解 self、建構子、實例屬性、類別屬性與方法;能使用屬性驗證、類別方法、組合及繼承,建立多場域感測資料模型。

一、只用字典有什麼問題?

字典很適合保存一筆資料,但資料與規則分散後,容易寫錯鍵名,也難以確保每座場域都具有相同欄位。

site = {
    "name": "示範池 A",
    "temperature": 27.2,
    "ph": 7.4,
}

if 24.0 <= site["temperature"] <= 30.0:
    print(site["name"], "水溫在示意範圍內")

若每次檢查都重新寫判斷式,規則很可能不一致。類別可以定義共同藍圖,物件則是依藍圖建立的個別場域。

概念智慧養殖比喻Python 表示
類別 class場域資料的設計規格AquacultureSite
物件 object依規格建立的某座場域site_a
屬性 attribute名稱、水溫、pHsite_a.temperature
方法 method更新讀值、產生摘要site_a.summary()
場域安全提醒:本篇所有門檻與資料均為程式教學示意。不同養殖物種、成長階段、季節與設備會有不同需求,真實控制系統必須加入校正、人工覆核、故障保護與專業設定。

二、第一個場域類別

__init__ 是建立物件時自動執行的建構方法;self 代表目前正在操作的物件。

class AquacultureSite:
    def __init__(self, name, temperature, ph):
        self.name = name
        self.temperature = temperature
        self.ph = ph

    def summary(self):
        return (
            f"{self.name}|水溫 {self.temperature}°C"
            f"|pH {self.ph}"
        )


site_a = AquacultureSite("示範池 A", 27.2, 7.4)
site_b = AquacultureSite("示範池 B", 28.1, 7.1)

print(site_a.summary())
print(site_b.summary())

site_asite_b 各自保存一份實例屬性。呼叫 site_a.summary() 時,Python 會把 site_a 自動傳給 self

三、讓物件負責自己的資料更新

不要任由外部程式到處修改屬性,可建立方法統一驗證流程。

class SensorReading:
    def __init__(self, sensor_id, value, unit):
        self.sensor_id = sensor_id
        self.unit = unit
        self.update(value)

    def update(self, value):
        if not isinstance(value, (int, float)):
            raise TypeError("感測值必須是數字")
        self.value = float(value)

    def display(self):
        return f"{self.sensor_id}: {self.value}{self.unit}"


temperature = SensorReading("TEMP-01", 27.2, "°C")
temperature.update(27.6)
print(temperature.display())

raise 會主動產生例外,阻止不合理資料進入物件。下一篇將完整學習如何捕捉與處理例外。

四、使用 property 驗證屬性

property 讓外部仍以屬性方式讀寫,但實際上會經過驗證方法。前置底線 _value 表示這是內部使用的慣例名稱,並非真正禁止存取。

class PHSensor:
    def __init__(self, sensor_id, value):
        self.sensor_id = sensor_id
        self.value = value

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, new_value):
        new_value = float(new_value)
        if not 0.0 <= new_value <= 14.0:
            raise ValueError("pH 必須介於 0 到 14")
        self._value = new_value


sensor = PHSensor("PH-01", 7.4)
sensor.value = 7.2
print(sensor.value)

五、實例屬性與類別屬性

實例屬性屬於個別物件;類別屬性由所有物件共享,適合記錄共同版本或物件數量。

class Device:
    platform_name = "水井村智慧巡查教學平台"
    device_count = 0

    def __init__(self, device_id):
        self.device_id = device_id
        Device.device_count += 1


device_a = Device("TEMP-01")
device_b = Device("PH-01")

print(Device.platform_name)
print("裝置數量:", Device.device_count)
判斷方法:每個物件可能不同的資料放在 self;整個類別共同使用的資料才放成類別屬性。可變的串列或字典若設成類別屬性,所有物件會共用同一份,須特別小心。

六、類別方法:從字典建立物件

@classmethod 接收類別本身 cls,常用於提供另一種建立物件的方式。

class AquacultureSite:
    def __init__(self, name, temperature, ph):
        self.name = name
        self.temperature = float(temperature)
        self.ph = float(ph)

    @classmethod
    def from_dict(cls, data):
        return cls(
            data["場域"],
            data["水溫"],
            data["pH"],
        )

    def summary(self):
        return f"{self.name}|{self.temperature}°C|pH {self.ph}"


data = {"場域": "示範池 C", "水溫": 26.8, "pH": 7.3}
site_c = AquacultureSite.from_dict(data)
print(site_c.summary())

七、組合:一座場域擁有多個感測器

「場域擁有感測器」適合使用組合,而不是繼承。場域物件內保存感測器物件,讓兩種責任保持清楚。

class Sensor:
    def __init__(self, sensor_id, kind, value, unit):
        self.sensor_id = sensor_id
        self.kind = kind
        self.value = float(value)
        self.unit = unit

    def display(self):
        return f"{self.kind}: {self.value}{self.unit}"


class Site:
    def __init__(self, name):
        self.name = name
        self.sensors = []

    def add_sensor(self, sensor):
        if not isinstance(sensor, Sensor):
            raise TypeError("只能加入 Sensor 物件")
        self.sensors.append(sensor)

    def report(self):
        lines = [f"【{self.name}】"]
        lines.extend(sensor.display() for sensor in self.sensors)
        return "\n".join(lines)


site = Site("示範池 A")
site.add_sensor(Sensor("TEMP-01", "水溫", 27.2, "°C"))
site.add_sensor(Sensor("PH-01", "pH", 7.4, ""))
print(site.report())

八、繼承:建立不同種類的感測器

若多種感測器都具有編號、數值與顯示功能,可把共同部分放在父類別,再由子類別補充差異。

class Sensor:
    def __init__(self, sensor_id, value):
        self.sensor_id = sensor_id
        self.value = float(value)

    def status(self):
        return "尚未定義判斷規則"


class TemperatureSensor(Sensor):
    def status(self):
        return "讀值已收到,請依場域標準判讀"


class PHSensor(Sensor):
    def status(self):
        if 0.0 <= self.value <= 14.0:
            return "讀值格式合理,仍須依場域標準判讀"
        return "讀值超出 pH 理論範圍,請檢查設備"


sensors = [
    TemperatureSensor("TEMP-01", 27.2),
    PHSensor("PH-01", 7.4),
]

for sensor in sensors:
    print(sensor.sensor_id, sensor.status())

兩個子類別都能呼叫 status(),卻可有不同實作,這稱為多型。外部程式不必用大量 if 判斷感測器種類。

九、組合還是繼承?

關係設計方式例子
「是一種」is-a可考慮繼承pH 感測器是一種感測器
「擁有」has-a優先考慮組合養殖場域擁有多個感測器

不要為了使用類別而建立很深的繼承層次。若兩個物件只是合作關係,組合通常更容易替換與測試。

十、物件如何轉回可儲存資料?

物件不能直接假設可以存進 JSON。先提供 to_dict(),把需要公開的欄位轉成基本資料型別。

class Sensor:
    def __init__(self, sensor_id, kind, value, unit):
        self.sensor_id = sensor_id
        self.kind = kind
        self.value = float(value)
        self.unit = unit

    def to_dict(self):
        return {
            "sensor_id": self.sensor_id,
            "kind": self.kind,
            "value": self.value,
            "unit": self.unit,
        }


sensor = Sensor("TEMP-01", "水溫", 27.2, "°C")
print(sensor.to_dict())
資料治理提醒:公開資料前應移除可辨識養殖戶、精確位置、設備憑證及未授權營運資訊。類別封裝方便程式設計,但不會自動完成個資或資安保護。

十一、常見錯誤除錯表

現象可能原因修正方式
方法缺少參數錯誤忘記在實例方法第一個參數寫 self補上 self,並由物件呼叫
所有物件共用同一串列把可變串列設成類別屬性__init__ 建立實例串列
建立物件時立即出錯建構子參數數量或型別不符檢查類別介面並加入清楚驗證
屬性名稱打錯AttributeError統一命名,必要時提供 property
繼承後父類別資料不存在子類別未呼叫父類別建構子使用 super().__init__(...)

十二、USR 實作挑戰

挑戰 A|七座場域物件
建立七個匿名教學場域物件,每座加入水溫與 pH 感測器,再輸出巡查摘要。公開展示時不要放入養殖戶姓名或精確位置。
挑戰 B|設備狀態
替 Sensor 加入最後更新時間與連線狀態,建立 is_stale() 方法判斷資料是否過期。
挑戰 C|共同定義規則
與養殖戶共同討論規則應放在哪個物件,區分「感測器讀值是否合理」與「養殖管理是否需要處理」。

十三、與生成式 AI 協作

請擔任 Python 物件導向設計助教。
我要建立智慧養殖教學系統,包含場域、感測器與巡查報告。
請先用「是一種」與「擁有」分析類別關係,再提出簡潔的類別設計。
要求:
1. 不建立過深的繼承;
2. 感測資料必須驗證型別;
3. 不自行設定真實養殖安全門檻;
4. 提供 to_dict() 供後續存檔;
5. 說明哪些資料不宜公開。
本篇小結
類別是資料與操作規則的共同藍圖,物件則代表個別場域或設備。先釐清責任,再選擇組合或繼承,才能讓程式隨場域擴充而不失去可讀性。

沒有留言:

張貼留言