Python Object Oriented Programming (OOP) Note

2023-12-21

筆記如何使用 Python 進行物件導向開發與設計常見的語法 property, setter, __str__, __init__, __iter__

logo

說明

app.py

from Car import Car

car1 = Car("Red", 100)
car2 = Car("Black", 60)

car1.fuel = 50
print(car1) # 50
car1.refuel(25)
print(car1) # 75
car1.__fuel = 10
print(car1) # 75 😮

for wheel in car1:
    wheel.inflate(5)
    print(wheel)

透過 self.__attributeName 的方式可以簡單的隱藏 attribute,但其實 python 只是 rename 成 _ClassName__attribute

而 python 的 getter 與 setter,pythonic 的方式是透過 @property 來修飾 method 作為 getter,並透過 @propertyName.setter 的方式來修飾 method 來構成 setter。

class Car:
    def __init__(self, color, fuel):
        self.__fuel = fuel 

    @property
    def fuel(self):
        return self.__fuel

    @fuel.setter
    def fuel(self, gallons):
        if int(gallons) > 0:
            self.__fuel = gallons
        else:
            raise ValueError("Invalid fuel gallons")

Class Field 直接在 Class 下定義,Class Method 則透過 @classmethod 來修飾:

class Car:
    # Class field (shared by all instances of the class)
    cars = []

    @classmethod
    def get_cars(cls):
        # A class method to access the class variable 'cars'
        return len(cls.cars)

如果想要透過 del 來消滅物件,可能無法隨心所欲,python 有自己的物件 GC 機制,即使移除了物件的指向 car1 實際的 Car instance 仍會存在。保險的方式是明確的透過 method 的呼叫去進行移除。

car1 = Car('black', 100)
car2 = Car('white', 90)

del car1
print(Car.get_cars()) # 2 😮

Car.py

from Tire import Tire

class Car:
    # Class field (shared by all instances of the class)
    wheels = 4
    cars = []

    def __init__(self, color, fuel):
        # Instance fields (unique to each instance)
        self.color = color
        self.__fuel = fuel  # Intended as a private attribute
        self.wheels = []
        
        for _ in range(Car.wheels):
            self.wheels.append(Tire(brand="Michelin", size="205/55R16", pressure=32))
        
        Car.cars.append(self)

    @property
    def fuel(self):
        # Property: getter method for the __fuel attribute
        return self.__fuel

    @fuel.setter
    def fuel(self, gallons):
        # Property: setter method for the __fuel attribute
        if int(gallons) > 0:
            self.__fuel = gallons
        else:
            raise ValueError("Invalid fuel gallons")

    def refuel(self, gallons):
        if int(gallons) > 0:
            self.__fuel += gallons
        else:
            raise ValueError("Invalid fuel gallons")        

    def __str__(self):
        return f'A {self.color} car with {self.__fuel} gallons.'
    
    def __iter__(self):
        for wheel in self.wheels:
            yield wheel

    def remove_car(self):
        # Method to explicitly remove a car
        if self in Car.cars:
            Car.cars.remove(self)

    @classmethod
    def get_cars(cls):
        # A class method to access the class variable 'cars'
        return len(cls.cars)

Tire.py

class Tire:
    def __init__(self, brand, size, pressure):
        self.brand = brand   # Brand of the tire (e.g., Michelin, Goodyear)
        self.size = size     # Size of the tire (e.g., 205/55R16)
        self._pressure = pressure  # Private variable to store tire pressure in PSI

    @property
    def pressure(self):
        """
        Get the current tire pressure.
        """
        return self._pressure

    @pressure.setter
    def pressure(self, value):
        """
        Set the tire pressure, but ensure it's within a valid range.
        """
        if value >= 0:
            self._pressure = value
        else:
            raise ValueError("Tire pressure must be a non-negative value")

    def inflate(self, amount):
        """
        Inflate the tire by a specified amount of PSI.
        """
        if amount > 0:
            self.pressure += amount
            print(f"Inflated {self.brand} tire to {self.pressure} PSI")
        else:
            print("Invalid inflation amount. Amount must be greater than 0.")

    def deflate(self, amount):
        """
        Deflate the tire by a specified amount of PSI.
        """
        if amount > 0 and self.pressure - amount >= 0:
            self.pressure -= amount
            print(f"Deflated {self.brand} tire to {self.pressure} PSI")
        else:
            print("Invalid deflation amount or tire pressure too low.")

    def __str__(self):
        """
        Return a string representation of the tire.
        """
        return f"{self.brand} Tire ({self.size}) - Pressure: {self.pressure} PSI"