Python必知必會:令人相見恨晚的十個Python類技巧
前路漫漫,我愛Python。Hello,大家好!今天筆者將向大家分享10個關于Python類的關鍵技巧,早點了解這些技巧有助于你寫出更加優雅、高效和Pythonic的代碼!
1. 繼承(Inheritance)VS 組合(Composition)
有時候我們應該使用繼承,有時應該使用組合。實際應用中應該如何選擇呢?一起來看看。
繼承應該用在 IS-A 關系中。比如,猴子是一種動物,圓是一種形狀,小轎車是一種機動車。在Python面向對象編程中,可以像下面這樣表達 IS-A 關系:
- Monkey 繼承自 Animal 類
- Circle 類繼承自 Shape 類
- Car 類繼承自 Vehicle 類
圖片
組合應該用在 HAS-A (或HAS-MANY) 關系中。比如,一只猴子有一個主人,一張轎車有一個引擎,一個公司有一個或多個員工。
相反,一只猴子不是一個主人,一張轎車不是一個引擎,一個公司不是一個員工。并且,從常識來看,這樣表達也不合理。我們不能使用繼承來表示這些關系——而是應該使用組合。
圖片
請注意,此處并沒有繼承關系。相反,Monkey 對象包含了 Owner 對象,Car 對象包含了 Engine 對象,Company 對象包含了 Employee 對象。
對于繼承和組合的選擇需要格外謹慎,因為錯誤的選擇可能會給你的項目帶來意想不到的麻煩。
2. super()方法及其用途
super() —— 當用在類中時,它允許我們訪問父類的方法。
假設我們有2個類:矩形(Rectangle)和正方形(Square)。我們都知道,正方形其實是一種特殊的矩形(即長寬相等)。因此,我們可以通過繼承 Rectangle 類來創建 Square 類。這樣,Rectangle 類是父類,而 Square 類則是子類。我們首先定義 Rectangle 類:
class Rectangle:
def __init__(self, length: float, width: float):
self.length = length
self.width = width
def area(self):
return self.length * self.width
def perimeter(self):
return 2 * (self.length + self.width)
if __name__ == '__main__':
r = Rectangle(length=10.5, width=6.4)
print(r.area()) # 67.2
print(r.perimeter()) # 33.8
接下來,我們定義 Square 類,它繼承自 Rectangle 類。注意,為了盡可能復用現有的方法(來自父類),我們就會用到 super() 方法。
class Square(Rectangle):
def __init__(self, length: float):
super().__init__(length, length)
if __name__ == '__main__':
s = Square(5)
print(s.area()) # 25
print(s.perimeter()) # 20
這里,super() 指的是父類(即 Rectangle)。因此,super().__init__ 實際上就是在調用父類 Rectangle 的 __init__ 方法。
我們給 super().__init__ 方法傳遞了 (length, length) 參數,因為正方形的長寬相等。同時,我們可以調用父類計算面積和周長的方法,因為子類可以訪問父類的方法。這樣,不僅提升了代碼的復用性,并且代碼更簡潔。
3. 實例方法 VS 類方法 VS 靜態方法
實例方法(Instance methods)屬于類(對象)的實例,它可以訪問實例的屬性。
例如,在下面的代碼片段中,intro 就是 Dog 類的實例方法。
class Dog:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
def introduce(self):
print(f'My name is {self.name}!')
if __name__ == '__main__':
rocky = Dog(name='Rocky', age=20)
rocky.introduce() # My name is Rocky!
類方法(Class methods)屬于類(而不是實例),并且類方法只能訪問類屬性,而不能訪問實例屬性。
比如,在下面的示例中,get_employee_count 就是 Employee 類的一個類方法:
class Employee:
employee_count: int = 0
@classmethod
def get_employee_count(cls) -> int:
return cls.employee_count
def __init__(self, name: str, salary: int) -> None:
self.name = name
self.salary = salary
Employee.employee_count += 1
if __name__ == '__main__':
emp1 = Employee(name='John', salary=5000)
emp2 = Employee(name='Jack', salary=10000)
emp3 = Employee(name='Stefan', salary=8000)
print(Employee.get_employee_count()) # 3
關于類方法:
- 我們使用 @classmethod 裝飾器來表示定義類方法。
- 類方法接受的是 cls 參數而不是 self, cls 表示類本身(這里為 Employee)。
- 類方法(get_employee_count)只能訪問類屬性(如employee_count),而不能訪問實例屬性(如name, salary)。
靜態方法(Static methods)屬于類,它無法訪問任何屬性。
例如,在下面的示例中,description 就是 Employee 類的靜態方法:
class Employee:
def __init__(self, name: str, salary: int) -> None:
self.name = name
self.salary = salary
@staticmethod
def description() -> str:
return 'Employees are the most basic and important resources for company.'
if __name__ == '__main__':
print(Employee.description())
# Employees are the most basic and important resources for company.
關于靜態方法:
- 我們通過 @staticmethod 裝飾器來定義靜態方法。
- 請注意,靜態方法不接受 cls 或 self 參數。
- 靜態方法不能訪問任何屬性,包括類屬性和實例屬性。
4. 數據類(Dataclasses)
當我們需要創建具有許多簡單屬性的類時,使用數據類(dataclass)會尤其有用。比如,
from dataclasses import dataclass
@dataclass
class Employee:
name: str # 姓名
age: int # 年齡
gender: str # 性別
education: str # 學歷
telphone: str # 電話
email: str # 郵箱
position: str # 職位
salary: int # 薪資
seniority: int # 工齡
def description(self) -> str:
return f"""The description of employee:
Name: {self.name}
Age: {self.age}
Gender: {self.gender}
Education: {self.education}
Telephone: {self.telphone}
Email: {self.email}
Position: {self.position}
Salary: {self.salary}
Seniority: {self.seniority}
"""
if __name__ == '__main__':
emp_info: list[str | int] = ['Jack', 29, 'Male', 'master', '188******666',
'jackzhang@example.com', 'manager', 10000, 5]
emp = Employee(*emp_info)
print(emp.description())
圖片
注意,我們不需要在數據類中編寫 __init__ 方法,因為它已經為我們自動編寫該方法。
此外,請注意,如果你需要在 __init__ 方法中執行一些特定的操作,那么可能使用數據類并不合適。
5.__dict__屬性
當我們創建類的實例后,實際上實例對象的屬性在底層存在一個特殊變量中(即__dict__),我們可以使用它來獲取實例的屬性信息。
class Employee:
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = age
if __name__ == '__main__':
emp = Employee(name="John", age=20)
print(emp.__dict__)
# {'name': 'John', 'age': 20}
即使是動態添加的屬性,也可以通過對象的 __dict__ 屬性獲取:
class Employee:
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = age
if __name__ == '__main__':
emp = Employee(name="John", age=20)
print(emp.__dict__)
# {'name': 'John', 'age': 20}
emp.salary = 10000
print(emp.__dict__)
# {'name': 'John', 'age': 20, 'salary': 10000}
如果我們希望調試和檢查具有許多屬性的復雜對象和類,這非常有用。