第7章 面向對象編程

面向對象編程(Object-Oriented Programming,OOP)是一種程序設計範式,它將數據和操作數據的方法組織在一起,形成對象。Python作爲一門多範式編程語言,完全支持面向對象編程,並且提供了豐富的面向對象特性。本章將詳細介紹Python中面向對象編程的核心概念和實踐技巧。

本系列文章所使用到的示例源碼:Python從入門到精通示例代碼

7.1 類與對象的概念

面向對象編程簡介

面向對象編程是一種編程範式,它使用”對象”來設計應用程序和計算機程序。與面向過程編程不同,面向對象編程將數據和處理數據的方法封裝在一起,形成一個個獨立的對象。

面向對象 vs 面向過程

面向過程編程關注的是”做什麼”,程序是一系列函數的調用:

# 面向過程的方式
def calculate_area(length, width):
    return length * width

def calculate_perimeter(length, width):
    return 2 * (length + width)

length = 5
width = 3
area = calculate_area(length, width)
perimeter = calculate_perimeter(length, width)

面向對象編程關注的是”誰來做”,程序是一系列對象的交互:

# 面向對象的方式
class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def calculate_area(self):
        return self.length * self.width

    def calculate_perimeter(self):
        return 2 * (self.length + self.width)

rect = Rectangle(5, 3)
area = rect.calculate_area()
perimeter = rect.calculate_perimeter()

OOP的核心思想

面向對象編程有四個核心特性:
1. 封裝(Encapsulation):將數據和方法包裝在一起,隱藏內部實現細節
2. 繼承(Inheritance):子類可以繼承父類的屬性和方法
3. 多態(Polymorphism):同一接口可以有不同的實現
4. 抽象(Abstraction):提取事物的共同特徵,忽略具體細節

類的概念

類是對象的模板

類(Class)是創建對象的藍圖或模板。它定義了對象應該具有的屬性和方法,但本身不是對象。可以把類比作建築圖紙,而對象就是根據圖紙建造的房子。

class Car:
    """汽車類 - 定義汽車的基本屬性和行爲"""
    # 類屬性
    wheels = 4

    def __init__(self, brand, model):
        # 實例屬性
        self.brand = brand
        self.model = model
        self.speed = 0

    def accelerate(self):
        """加速方法"""
        self.speed += 10
        print(f"{self.brand} {self.model} 加速到 {self.speed} km/h")

類的抽象性

類是對現實世界事物的抽象。它提取了事物的共同特徵,忽略了具體的細節。例如,”汽車”這個類抽象了所有汽車的共同特徵:有品牌、型號、速度等屬性,有加速、剎車等行爲。

對象的概念

對象是類的實例

對象(Object)是類的具體實例。如果說類是模板,那麼對象就是根據模板創建的具體實體。每個對象都有自己獨特的屬性值,但共享相同的方法。

# 創建汽車對象
car1 = Car("豐田", "卡羅拉")
car2 = Car("本田", "雅閣")

# 每個對象都有獨立的屬性
print(f"car1: {car1.brand} {car1.model}")
print(f"car2: {car2.brand} {car2.model}")

對象的狀態和行爲

  • 狀態(State):對象的屬性值,描述對象當前的情況
  • 行爲(Behavior):對象的方法,描述對象能夠執行的操作

對象的唯一性

每個對象在內存中都有唯一的標識,即使兩個對象的屬性值完全相同,它們也是不同的對象:

car1 = Car("豐田", "卡羅拉")
car2 = Car("豐田", "卡羅拉")

print(f"car1的id: {id(car1)}")
print(f"car2的id: {id(car2)}")
print(f"car1 is car2: {car1 is car2}")  # False

面向對象的優勢

代碼複用

通過繼承機制,子類可以複用父類的代碼,避免重複編寫相同的功能。

模塊化設計

每個類都是一個獨立的模塊,具有明確的職責和接口,便於團隊協作開發。

易於維護和擴展

面向對象的設計使得代碼結構清晰,修改和擴展功能時影響範圍有限。

7.2 類的定義與實例化

類的定義語法

class關鍵字

Python使用class關鍵字來定義類:

class ClassName:
    """類的文檔字符串"""
    # 類體
    pass

類名命名規範(PascalCase)

類名應該使用PascalCase(帕斯卡命名法),即每個單詞的首字母都大寫:

class Person:          # 正確
class BankAccount:     # 正確
class HTTPServer:      # 正確

class person:          # 不推薦
class bank_account:    # 不推薦

類體結構

類體包含類的屬性和方法定義:

class Student:
    """學生類"""
    # 類屬性
    school = "某某大學"

    # 構造方法
    def __init__(self, name, age):
        # 實例屬性
        self.name = name
        self.age = age

    # 實例方法
    def study(self, subject):
        return f"{self.name}正在學習{subject}"

類的基本結構

讓我們通過一個完整的示例來了解類的基本結構:

class Person:
    """人員類 - 演示基本類定義"""
    # 類屬性
    species = "智人"
    count = 0

    def __init__(self, name, age):
        """構造方法 - 初始化對象"""
        self.name = name  # 實例屬性
        self.age = age    # 實例屬性
        Person.count += 1
        print(f"創建了一個人員對象: {self.name}")

    def introduce(self):
        """實例方法 - 自我介紹"""
        return f"我是{self.name},今年{self.age}歲"

    def have_birthday(self):
        """實例方法 - 過生日"""
        self.age += 1
        print(f"{self.name}過生日了,現在{self.age}歲")

    @classmethod
    def get_count(cls):
        """類方法 - 獲取人員總數"""
        return f"目前共有{cls.count}個人員對象"

    @staticmethod
    def is_adult(age):
        """靜態方法 - 判斷是否成年"""
        return age >= 18

運行示例:

# 創建對象實例
person1 = Person("張三", 25)
person2 = Person("李四", 17)

print(f"\n類屬性 species: {Person.species}")
print(f"通過實例訪問類屬性: {person1.species}")

print(f"\n實例方法調用:")
print(person1.introduce())
print(person2.introduce())

print(f"\n類方法調用:")
print(Person.get_count())
print(person1.get_count())  # 也可以通過實例調用

print(f"\n靜態方法調用:")
print(f"張三是否成年: {Person.is_adult(person1.age)}")
print(f"李四是否成年: {Person.is_adult(person2.age)}")

輸出結果:

創建了一個人員對象: 張三
創建了一個人員對象: 李四

類屬性 species: 智人
通過實例訪問類屬性: 智人

實例方法調用:
我是張三,今年25
我是李四,今年17

類方法調用:
目前共有2個人員對象
目前共有2個人員對象

靜態方法調用:
張三是否成年: True
李四是否成年: False

類變量 vs 實例變量

  • 類變量:屬於類本身,被所有實例共享
  • 實例變量:屬於特定的實例,每個實例都有自己的副本
print(f"\n修改實例屬性:")
person1.have_birthday()
print(person1.introduce())

print(f"\n動態添加屬性:")
person1.city = "北京"
print(f"張三的城市: {person1.city}")
# print(f"李四的城市: {person2.city}")  # 這會報錯,因爲李四沒有city屬性

對象的實例化

創建對象實例

使用類名加括號的方式創建對象實例:

# 語法:對象名 = 類名(參數)
person = Person("張三", 25)

內存分配

當創建對象時,Python會在內存中爲對象分配空間,並返回對象的引用。

對象引用

變量存儲的是對象的引用(內存地址),而不是對象本身:

print(f"\n對象的唯一性:")
print(f"person1的id: {id(person1)}")
print(f"person2的id: {id(person2)}")
print(f"person1 == person2: {person1 == person2}")
print(f"person1 is person2: {person1 is person2}")

輸出結果:

對象的唯一性:
person1的id: 1835526299264
person2的id: 1835525126384
person1 == person2: False
person1 is person2: False

類與實例的關係

一個類可以創建多個實例

一個類可以創建任意數量的實例,每個實例都是獨立的對象。

實例之間的獨立性

不同實例的屬性值是獨立的,修改一個實例的屬性不會影響其他實例。

7.3 屬性與方法

實例屬性

實例屬性是屬於特定對象實例的數據,每個實例都有自己的屬性副本。

屬性的定義和訪問

class Student:
    def __init__(self, name, age):
        self.name = name    # 定義實例屬性
        self.age = age

    def show_info(self):
        # 訪問實例屬性
        print(f"姓名: {self.name}, 年齡: {self.age}")

student = Student("小明", 18)
print(student.name)     # 直接訪問屬性
student.show_info()     # 通過方法訪問屬性

動態添加屬性

Python允許在運行時爲對象動態添加屬性:

student.grade = "高三"   # 動態添加屬性
print(f"年級: {student.grade}")

屬性的刪除

可以使用del語句刪除對象的屬性:

del student.grade       # 刪除屬性
# print(student.grade)  # 這會報錯,因爲屬性已被刪除

類屬性

類屬性屬於類本身,被所有實例共享。

類屬性 vs 實例屬性

class Counter:
    count = 0  # 類屬性

    def __init__(self, name):
        self.name = name      # 實例屬性
        Counter.count += 1    # 修改類屬性

c1 = Counter("計數器1")
c2 = Counter("計數器2")

print(f"總計數: {Counter.count}")  # 2
print(f"c1的計數: {c1.count}")     # 2
print(f"c2的計數: {c2.count}")     # 2

類屬性的共享性

所有實例共享同一個類屬性:

Counter.count = 10
print(f"c1的計數: {c1.count}")     # 10
print(f"c2的計數: {c2.count}")     # 10

實例方法

實例方法是定義在類中的函數,用於操作實例的數據。

self參數的作用

self參數代表調用方法的實例對象,它必須是實例方法的第一個參數:

class Circle:
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        # self指向調用此方法的實例
        return 3.14159 * self.radius ** 2

    def circumference(self):
        return 2 * 3.14159 * self.radius

circle = Circle(5)
print(f"面積: {circle.area()}")           # self自動傳遞爲circle
print(f"周長: {circle.circumference()}")

類方法

類方法使用@classmethod裝飾器定義,第一個參數是cls,代表類本身。

@classmethod裝飾器

class Person:
    count = 0

    def __init__(self, name):
        self.name = name
        Person.count += 1

    @classmethod
    def get_count(cls):
        """類方法 - 獲取實例總數"""
        return cls.count

    @classmethod
    def create_anonymous(cls):
        """類方法 - 創建匿名對象"""
        return cls("匿名")

# 調用類方法
print(Person.get_count())  # 0

p1 = Person("張三")
p2 = Person.create_anonymous()  # 使用類方法創建對象

print(Person.get_count())  # 2

類方法的應用場景

  1. 提供替代的構造方法
  2. 訪問或修改類屬性
  3. 實現工廠模式

靜態方法

靜態方法使用@staticmethod裝飾器定義,不需要selfcls參數。

@staticmethod裝飾器

class MathUtils:
    @staticmethod
    def add(a, b):
        """靜態方法 - 加法運算"""
        return a + b

    @staticmethod
    def is_even(number):
        """靜態方法 - 判斷是否爲偶數"""
        return number % 2 == 0

# 調用靜態方法
print(MathUtils.add(3, 5))      # 8
print(MathUtils.is_even(4))     # True

# 也可以通過實例調用
utils = MathUtils()
print(utils.add(2, 3))          # 5

何時使用靜態方法

  1. 功能與類相關,但不需要訪問實例或類的數據
  2. 可以獨立運行的工具函數
  3. 邏輯上屬於類,但不依賴類的狀態

屬性訪問控制

Python通過命名約定來實現訪問控制。

公有屬性

默認情況下,所有屬性都是公有的,可以從類的外部直接訪問:

class Student:
    def __init__(self, name):
        self.name = name  # 公有屬性

student = Student("張三")
print(student.name)  # 可以直接訪問

受保護屬性(約定)

以單下劃線開頭的屬性被約定爲受保護的,不應該從類外部直接訪問:

class BankAccount:
    def __init__(self, balance):
        self._balance = balance  # 受保護屬性

    def get_balance(self):
        return self._balance

account = BankAccount(1000)
print(account.get_balance())  # 推薦的訪問方式
print(account._balance)       # 不推薦,但仍然可以訪問

私有屬性(名稱改寫)

以雙下劃線開頭的屬性會被Python進行名稱改寫,變成_ClassName__attribute的形式:

class BankAccount:
    def __init__(self, account_number, initial_balance=0):
        self.account_number = account_number  # 公有屬性
        self._balance = initial_balance       # 受保護屬性(約定)
        self.__pin = "1234"                  # 私有屬性

    def deposit(self, amount):
        """存款"""
        if amount > 0:
            self._balance += amount
            print(f"存款{amount}元,餘額: {self._balance}元")
        else:
            print("存款金額必須大於0")

    def withdraw(self, amount, pin):
        """取款"""
        if pin != self.__pin:
            print("密碼錯誤")
            return False
        if amount > self._balance:
            print("餘額不足")
            return False
        self._balance -= amount
        print(f"取款{amount}元,餘額: {self._balance}元")
        return True

    def get_balance(self):
        """獲取餘額"""
        return self._balance

account = BankAccount("123456789", 1000)
print(f"賬戶號碼: {account.account_number}")
print(f"初始餘額: {account.get_balance()}元")

account.deposit(500)
account.withdraw(200, "1234")
account.withdraw(200, "0000")  # 密碼錯誤

print(f"\n訪問受保護屬性(不推薦): {account._balance}")
print(f"嘗試訪問私有屬性:")
try:
    print(account.__pin)
except AttributeError as e:
    print(f"錯誤: {e}")

print(f"通過名稱改寫訪問私有屬性: {account._BankAccount__pin}")

輸出結果:

賬戶號碼: 123456789
初始餘額: 1000
存款500元,餘額: 1500
取款200元,餘額: 1300
密碼錯誤

訪問受保護屬性(不推薦): 1300
嘗試訪問私有屬性:
錯誤: 'BankAccount' object has no attribute '__pin'
通過名稱改寫訪問私有屬性: 1234

7.4 構造方法與析構方法

構造方法__init__()

構造方法__init__()是一個特殊方法,當創建類的新實例時自動調用。它用於初始化對象的屬性。

對象初始化

class Person:
    def __init__(self, name, age):
        """構造方法 - 初始化對象屬性"""
        self.name = name
        self.age = age
        print(f"創建了一個Person對象: {self.name}")

# 創建對象時自動調用__init__方法
person = Person("張三", 25)

參數傳遞

構造方法可以接受任意數量的參數,用於初始化對象:

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width
        self.area = length * width

rect = Rectangle(5, 3)
print(f"矩形面積: {rect.area}")

構造方法的重載(通過默認參數)

Python不支持方法重載,但可以通過默認參數實現類似效果:

class Person:
    def __init__(self, name, age=18, city=None):
        self.name = name
        self.age = age
        self.city = city

# 不同方式創建對象
person1 = Person("張三")
person2 = Person("李四", 25)
person3 = Person("王五", 30, "北京")

print(f"person1: {person1.name}, {person1.age}, {person1.city}")
print(f"person2: {person2.name}, {person2.age}, {person2.city}")
print(f"person3: {person3.name}, {person3.age}, {person3.city}")

析構方法__del__()

析構方法__del__()是一個特殊方法,當對象被銷燬時自動調用。它用於執行清理操作。

對象銷燬時調用

class FileHandler:
    def __init__(self, filename):
        self.filename = filename
        self.file = open(filename, "w")
        print(f"文件 {filename} 已打開")

    def write(self, text):
        self.file.write(text)

    def __del__(self):
        """析構方法 - 對象銷燬時調用"""
        self.file.close()
        print(f"文件 {self.filename} 已關閉")

# 創建對象
handler = FileHandler("test.txt")
handler.write("Hello, World!")

# 刪除對象引用,觸發垃圾回收
print("刪除對象引用...")
del handler
print("程序繼續執行...")

資源清理

析構方法常用於釋放對象佔用的資源,如關閉文件、釋放網絡連接等。

析構方法的注意事項

  1. 不要依賴析構方法來執行關鍵操作,因爲Python的垃圾回收機制是不確定的
  2. 對於需要立即釋放的資源,應該使用上下文管理器(with語句)
  3. 析構方法可能在程序結束時才被調用

對象的生命週期

創建、使用、銷燬

Python對象的生命週期包括三個階段:
1. 創建:調用類創建實例,執行__init__方法
2. 使用:訪問和修改對象的屬性,調用對象的方法
3. 銷燬:對象不再被引用,垃圾回收器回收內存,執行__del__方法

引用計數

Python使用引用計數來跟蹤對象的引用情況。當對象的引用計數降爲0時,對象將被垃圾回收器回收。

import sys

class Demo:
    def __init__(self, name):
        self.name = name
        print(f"{self.name} 被創建")

    def __del__(self):
        print(f"{self.name} 被銷燬")

# 創建對象
obj = Demo("測試對象")
print(f"引用計數: {sys.getrefcount(obj) - 1}")  # 減1是因爲getrefcount函數本身會創建一個臨時引用

# 創建另一個引用
obj2 = obj
print(f"引用計數: {sys.getrefcount(obj) - 1}")

# 刪除一個引用
del obj
print("第一個引用已刪除")

# 刪除最後一個引用
print("刪除最後一個引用...")
del obj2
print("程序結束")

7.5 繼承與多態

繼承的概念

繼承是面向對象編程的核心特性之一,它允許創建一個新類(子類),繼承現有類(父類)的屬性和方法。

父類(基類、超類)與子類(派生類)

# 父類
class Animal:
    def __init__(self, name, species):
        self.name = name
        self.species = species

    def make_sound(self):
        print("一些聲音")

    def info(self):
        print(f"動物: {self.name}, 物種: {self.species}")

# 子類
class Dog(Animal):
    def __init__(self, name, breed):
        # 調用父類的構造方法
        super().__init__(name, "犬科")
        self.breed = breed

    def make_sound(self):
        print("汪汪!")

    def fetch(self):
        print(f"{self.name}在撿球")

# 創建子類對象
dog = Dog("旺財", "金毛")
dog.info()          # 繼承自父類的方法
dog.make_sound()    # 重寫的方法
dog.fetch()         # 子類特有的方法

is-a關係

繼承表示”is-a”(是一個)關係。例如,狗是一種動物,所以Dog類繼承自Animal類。

繼承的語法

單繼承

class 子類名(父類名):
    # 子類體
    pass

多繼承

Python支持多繼承,一個類可以繼承多個父類:

class Flyable:
    def fly(self):
        print("飛行中...")

class Swimmable:
    def swim(self):
        print("游泳中...")

class Duck(Animal, Flyable, Swimmable):
    def __init__(self, name):
        super().__init__(name, "鴨科")

    def make_sound(self):
        print("嘎嘎!")

duck = Duck("唐老鴨")
duck.info()       # 來自Animal
duck.make_sound() # 重寫的方法
duck.fly()        # 來自Flyable
duck.swim()       # 來自Swimmable

方法重寫(Override)

子類可以重寫(覆蓋)父類的方法,提供特定的實現。

重寫父類方法

class Cat(Animal):
    def __init__(self, name, color):
        super().__init__(name, "貓科")
        self.color = color

    def make_sound(self):
        print("喵喵!")

    def info(self):
        # 重寫父類的info方法
        print(f"貓咪: {self.name}, 顏色: {self.color}, 物種: {self.species}")

cat = Cat("咪咪", "橘色")
cat.info()
cat.make_sound()

super()函數的使用

super()函數用於調用父類的方法:

class Bird(Animal):
    def __init__(self, name, wingspan):
        super().__init__(name, "鳥類")
        self.wingspan = wingspan

    def info(self):
        # 先調用父類的info方法
        super().info()
        # 再添加子類特有的信息
        print(f"翼展: {self.wingspan}釐米")

bird = Bird("小鳥", 15)
bird.info()

方法解析順序(MRO)

方法解析順序決定了多繼承時方法的查找順序:

print(f"Duck的MRO: {Duck.__mro__}")

輸出結果:

Duck的MRO: (<class '__main__.Duck'>, <class '__main__.Animal'>, <class '__main__.Flyable'>, <class '__main__.Swimmable'>, <class 'object'>)

多態的概念

多態是指同一個操作作用於不同的對象,可以有不同的解釋和執行方式。

同一接口,不同實現

def make_speak(animal):
    """多態函數 - 接受任何有make_sound方法的對象"""
    animal.make_sound()

# 不同類型的對象
dog = Dog("旺財", "金毛")
cat = Cat("咪咪", "橘色")
bird = Bird("小鳥", 15)

# 同一個函數作用於不同對象
make_speak(dog)  # 輸出: 汪汪!
make_speak(cat)  # 輸出: 喵喵!
make_speak(bird) # 輸出: 一些聲音

鴨子類型(Duck Typing)

Python的多態基於”鴨子類型”:如果它走起來像鴨子,叫起來像鴨子,那麼它就是鴨子。

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

    def make_sound(self):
        print("機器人: 嘟嘟嘟!")

# Robot類不是Animal的子類,但有make_sound方法
robot = Robot("機器人")
make_speak(robot)  # 輸出: 機器人: 嘟嘟嘟!

isinstance()和issubclass()

類型檢查

isinstance()函數用於檢查對象是否是指定類或其子類的實例:

print(f"dog是Animal的實例? {isinstance(dog, Animal)}")  # True
print(f"cat是Dog的實例? {isinstance(cat, Dog)}")        # False
print(f"robot是Animal的實例? {isinstance(robot, Animal)}")  # False

繼承關係檢查

issubclass()函數用於檢查一個類是否是另一個類的子類:

print(f"Dog是Animal的子類? {issubclass(Dog, Animal)}")  # True
print(f"Cat是Dog的子類? {issubclass(Cat, Dog)}")        # False
print(f"Duck是Flyable的子類? {issubclass(Duck, Flyable)}")  # True

7.6 封裝與訪問控制

封裝的概念

封裝是面向對象編程的核心特性之一,它將數據和操作數據的方法綁定在一起,對外部隱藏實現細節。

數據隱藏

封裝通過限制對對象內部數據的直接訪問,提供了數據的安全性。

接口與實現分離

封裝將對象的接口(公共方法)與實現(內部數據和私有方法)分離,使得代碼更易於維護。

Python中的訪問控制

前面我們已經介紹了Python中的訪問控制約定:
- 公有成員:普通命名,如name
- 受保護成員:單下劃線前綴,如_balance
- 私有成員:雙下劃線前綴,如__pin

屬性的getter和setter

Python提供了@property裝飾器,用於創建屬性的getter和setter方法。

@property裝飾器

class Temperature:
    def __init__(self, celsius=0):
        self._celsius = celsius

    @property
    def celsius(self):
        """攝氏度 - getter方法"""
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        """攝氏度 - setter方法"""
        if value < -273.15:
            raise ValueError("溫度不能低於絕對零度")
        self._celsius = value

    @property
    def fahrenheit(self):
        """華氏度 - 只讀屬性"""
        return self._celsius * 9/5 + 32

    @property
    def kelvin(self):
        """開爾文溫度 - 只讀屬性"""
        return self._celsius + 273.15

# 使用屬性
temp = Temperature(25)
print(f"攝氏度: {temp.celsius}°C")
print(f"華氏度: {temp.fahrenheit}°F")
print(f"開爾文: {temp.kelvin}K")

# 修改屬性
temp.celsius = 30
print(f"新攝氏度: {temp.celsius}°C")
print(f"新華氏度: {temp.fahrenheit}°F")

# 嘗試設置無效值
try:
    temp.celsius = -300
except ValueError as e:
    print(f"錯誤: {e}")

# 嘗試修改只讀屬性
try:
    temp.fahrenheit = 100
except AttributeError as e:
    print(f"錯誤: {e}")

輸出結果:

攝氏度: 25°C
華氏度: 77.0°F
開爾文: 298.15K
新攝氏度: 30°C
新華氏度: 86.0°F
錯誤: 溫度不能低於絕對零度
錯誤: can't set attribute 'fahrenheit'

屬性的讀取控制

@property裝飾器將方法轉換爲只讀屬性:

class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        return self._radius

    @property
    def area(self):
        return 3.14159 * self._radius ** 2

    @property
    def circumference(self):
        return 2 * 3.14159 * self._radius

circle = Circle(5)
print(f"半徑: {circle.radius}")
print(f"面積: {circle.area}")
print(f"周長: {circle.circumference}")

屬性的設置控制

@property.setter裝飾器用於定義屬性的setter方法:

class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if not isinstance(value, str):
            raise TypeError("名字必須是字符串")
        if len(value) < 2:
            raise ValueError("名字長度至少爲2")
        self._name = value

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if not isinstance(value, int):
            raise TypeError("年齡必須是整數")
        if value < 0 or value > 150:
            raise ValueError("年齡必須在0到150之間")
        self._age = value

person = Person("張三", 25)
print(f"姓名: {person.name}, 年齡: {person.age}")

# 修改屬性
person.name = "李四"
person.age = 30
print(f"新姓名: {person.name}, 新年齡: {person.age}")

# 嘗試設置無效值
try:
    person.name = "A"  # 名字太短
except ValueError as e:
    print(f"錯誤: {e}")

try:
    person.age = 200  # 年齡超出範圍
except ValueError as e:
    print(f"錯誤: {e}")

7.7 特殊方法(魔術方法)

特殊方法概述

特殊方法(也稱爲魔術方法)是Python中以雙下劃線開頭和結尾的方法,如__init____str__等。這些方法爲類提供了特殊的行爲,如運算符重載、對象表示等。

常用特殊方法

str()和__repr__()

__str__()__repr__()方法用於提供對象的字符串表示:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        """返回對象的字符串表示,用於str()和print()"""
        return f"Point({self.x}, {self.y})"

    def __repr__(self):
        """返回對象的官方字符串表示,用於repr()和交互式環境"""
        return f"Point(x={self.x}, y={self.y})"

point = Point(3, 4)
print(f"str(point): {str(point)}")
print(f"repr(point): {repr(point)}")

len()

__len__()方法使對象支持len()函數:

class MyList:
    def __init__(self, items):
        self.items = items

    def __len__(self):
        """返回對象的長度"""
        return len(self.items)

my_list = MyList([1, 2, 3, 4, 5])
print(f"len(my_list): {len(my_list)}")

getitem()和__setitem__()

__getitem__()__setitem__()方法使對象支持索引訪問:

class MyDict:
    def __init__(self):
        self.data = {}

    def __getitem__(self, key):
        """獲取指定鍵的值"""
        return self.data.get(key, None)

    def __setitem__(self, key, value):
        """設置指定鍵的值"""
        self.data[key] = value

my_dict = MyDict()
my_dict["name"] = "張三"
my_dict["age"] = 25

print(f"my_dict['name']: {my_dict['name']}")
print(f"my_dict['age']: {my_dict['age']}")

call()

__call__()方法使對象可調用,像函數一樣:

class Adder:
    def __init__(self, n):
        self.n = n

    def __call__(self, x):
        """使對象可調用"""
        return self.n + x

add5 = Adder(5)
print(f"add5(10): {add5(10)}")
print(f"add5(20): {add5(20)}")

eq()、lt()等比較方法

這些方法用於重載比較運算符:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __eq__(self, other):
        """重載==運算符"""
        if not isinstance(other, Person):
            return False
        return self.name == other.name and self.age == other.age

    def __lt__(self, other):
        """重載<運算符"""
        if not isinstance(other, Person):
            return NotImplemented
        return self.age < other.age

person1 = Person("張三", 25)
person2 = Person("張三", 25)
person3 = Person("李四", 30)

print(f"person1 == person2: {person1 == person2}")
print(f"person1 == person3: {person1 == person3}")
print(f"person1 < person3: {person1 < person3}")

add()、sub()等算術方法

這些方法用於重載算術運算符:

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f"Vector({self.x}, {self.y})"

    def __add__(self, other):
        """重載+運算符"""
        if not isinstance(other, Vector):
            return NotImplemented
        return Vector(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        """重載-運算符"""
        if not isinstance(other, Vector):
            return NotImplemented
        return Vector(self.x - other.x, self.y - other.y)

    def __mul__(self, scalar):
        """重載*運算符"""
        if not isinstance(scalar, (int, float)):
            return NotImplemented
        return Vector(self.x * scalar, self.y * scalar)

v1 = Vector(3, 4)
v2 = Vector(1, 2)

print(f"v1: {v1}")
print(f"v2: {v2}")
print(f"v1 + v2: {v1 + v2}")
print(f"v1 - v2: {v1 - v2}")
print(f"v1 * 2: {v1 * 2}")

enter()和__exit__()

__enter__()__exit__()方法實現上下文管理器協議,支持with語句:

class FileManager:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None

    def __enter__(self):
        """進入上下文時調用"""
        self.file = open(self.filename, self.mode)
        print(f"打開文件: {self.filename}")
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        """退出上下文時調用"""
        if self.file:
            self.file.close()
            print(f"關閉文件: {self.filename}")
        # 返回True表示異常已處理,False表示異常需要向外傳播
        return False

# 使用自定義上下文管理器
with FileManager("test.txt", "w") as f:
    f.write("Hello, World!")
    print("寫入文件")

print("上下文已退出")

實際運行結果:

打開文件: test.txt
寫入文件
關閉文件: test.txt
上下文已退出

7.8 類的高級特性

類裝飾器

類裝飾器是應用於類定義的裝飾器,用於修改類的行爲。

def add_str_method(cls):
    """類裝飾器 - 添加__str__方法"""
    def __str__(self):
        attrs = ", ".join(f"{k}={v}" for k, v in self.__dict__.items())
        return f"{cls.__name__}({attrs})"

    cls.__str__ = __str__
    return cls

@add_str_method
class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

student = Student("張三", 20)
print(student)  # 輸出: Student(name=張三, age=20)

描述符

描述符是實現了__get____set____delete__方法的類,用於控制屬性的訪問。

class Descriptor:
    """描述符類 - 驗證字符串屬性"""
    def __init__(self, name=None):
        self.name = name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__.get(self.name, None)

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise TypeError(f"{self.name}必須是字符串")
        if len(value) < 2:
            raise ValueError(f"{self.name}長度至少爲2")
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        if self.name in instance.__dict__:
            del instance.__dict__[self.name]

class Person:
    name = Descriptor("name")

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

# 使用描述符
person = Person("張三")
print(f"姓名: {person.name}")

# 嘗試設置無效值
try:
    person.name = "A"  # 名字太短
except ValueError as e:
    print(f"錯誤: {e}")

try:
    person.name = 123  # 不是字符串
except TypeError as e:
    print(f"錯誤: {e}")

數據類(Python 3.7+)

數據類是Python 3.7引入的特性,用於簡化創建主要用於存儲數據的類。

from dataclasses import dataclass

@dataclass
class Book:
    title: str
    author: str
    pages: int
    price: float = 0.0

    def price_per_page(self):
        return self.price / self.pages if self.pages > 0 else 0

# 創建數據類對象
book1 = Book("Python編程", "張三", 300, 59.9)
book2 = Book("Python編程", "張三", 300, 59.9)
book3 = Book("Java編程", "李四", 400, 69.9)

print(f"book1: {book1}")
print(f"book1 == book2: {book1 == book2}")
print(f"book1 == book3: {book1 == book3}")
print(f"每頁價格: {book1.price_per_page():.3f}元")

輸出結果:

book1: Book(title='Python編程', author='張三', pages=300, price=59.9)
book1 == book2: True
book1 == book3: False
每頁價格: 0.200

抽象基類

抽象基類(ABC)用於定義接口,強制子類實現特定的方法。

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        """計算面積"""
        pass

    @abstractmethod
    def perimeter(self):
        """計算周長"""
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14159 * self.radius ** 2

    def perimeter(self):
        return 2 * 3.14159 * self.radius

# 嘗試實例化抽象類
try:
    shape = Shape()
except TypeError as e:
    print(f"錯誤: {e}")

# 實例化具體子類
rect = Rectangle(5, 3)
circle = Circle(4)

print(f"矩形面積: {rect.area()}")
print(f"矩形周長: {rect.perimeter()}")
print(f"圓形面積: {circle.area()}")
print(f"圓形周長: {circle.perimeter()}")

輸出結果:

錯誤: Can't instantiate abstract class Shape with abstract methods area, perimeter
矩形面積: 15
矩形周長: 16
圓形面積: 50.26544
圓形周長: 25.13272

總結

本章我們詳細介紹了Python面向對象編程的核心概念和高級特性,包括類與對象、屬性與方法、構造與析構方法、繼承與多態、封裝與訪問控制、特殊方法以及類的高級特性。通過這些知識,你可以設計出更加靈活、可維護和可擴展的程序。

面向對象編程是一種強大的編程範式,它通過抽象、封裝、繼承和多態等特性,幫助我們更好地組織和管理代碼。在實際開發中,合理運用面向對象的思想和技術,可以大大提高代碼的質量和開發效率。

小夜