第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
類方法的應用場景
- 提供替代的構造方法
- 訪問或修改類屬性
- 實現工廠模式
靜態方法¶
靜態方法使用@staticmethod裝飾器定義,不需要self或cls參數。
@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
何時使用靜態方法
- 功能與類相關,但不需要訪問實例或類的數據
- 可以獨立運行的工具函數
- 邏輯上屬於類,但不依賴類的狀態
屬性訪問控制¶
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("程序繼續執行...")
資源清理
析構方法常用於釋放對象佔用的資源,如關閉文件、釋放網絡連接等。
析構方法的注意事項
- 不要依賴析構方法來執行關鍵操作,因爲Python的垃圾回收機制是不確定的
- 對於需要立即釋放的資源,應該使用上下文管理器(with語句)
- 析構方法可能在程序結束時才被調用
對象的生命週期¶
創建、使用、銷燬
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面向對象編程的核心概念和高級特性,包括類與對象、屬性與方法、構造與析構方法、繼承與多態、封裝與訪問控制、特殊方法以及類的高級特性。通過這些知識,你可以設計出更加靈活、可維護和可擴展的程序。
面向對象編程是一種強大的編程範式,它通過抽象、封裝、繼承和多態等特性,幫助我們更好地組織和管理代碼。在實際開發中,合理運用面向對象的思想和技術,可以大大提高代碼的質量和開發效率。