Python必知必會(huì):令人相見(jiàn)恨晚的十個(gè)Python類技巧
前路漫漫,我愛(ài)Python。Hello,大家好!今天筆者將向大家分享10個(gè)關(guān)于Python類的關(guān)鍵技巧,早點(diǎn)了解這些技巧有助于你寫(xiě)出更加優(yōu)雅、高效和Pythonic的代碼!
1. 繼承(Inheritance)VS 組合(Composition)
有時(shí)候我們應(yīng)該使用繼承,有時(shí)應(yīng)該使用組合。實(shí)際應(yīng)用中應(yīng)該如何選擇呢?一起來(lái)看看。
繼承應(yīng)該用在 IS-A 關(guān)系中。比如,猴子是一種動(dòng)物,圓是一種形狀,小轎車是一種機(jī)動(dòng)車。在Python面向?qū)ο缶幊讨?,可以像下面這樣表達(dá) IS-A 關(guān)系:
- Monkey 繼承自 Animal 類
- Circle 類繼承自 Shape 類
- Car 類繼承自 Vehicle 類
圖片
組合應(yīng)該用在 HAS-A (或HAS-MANY) 關(guān)系中。比如,一只猴子有一個(gè)主人,一張轎車有一個(gè)引擎,一個(gè)公司有一個(gè)或多個(gè)員工。
相反,一只猴子不是一個(gè)主人,一張轎車不是一個(gè)引擎,一個(gè)公司不是一個(gè)員工。并且,從常識(shí)來(lái)看,這樣表達(dá)也不合理。我們不能使用繼承來(lái)表示這些關(guān)系——而是應(yīng)該使用組合。
圖片
請(qǐng)注意,此處并沒(méi)有繼承關(guān)系。相反,Monkey 對(duì)象包含了 Owner 對(duì)象,Car 對(duì)象包含了 Engine 對(duì)象,Company 對(duì)象包含了 Employee 對(duì)象。
對(duì)于繼承和組合的選擇需要格外謹(jǐn)慎,因?yàn)殄e(cuò)誤的選擇可能會(huì)給你的項(xiàng)目帶來(lái)意想不到的麻煩。
2. super()方法及其用途
super() —— 當(dāng)用在類中時(shí),它允許我們?cè)L問(wèn)父類的方法。
假設(shè)我們有2個(gè)類:矩形(Rectangle)和正方形(Square)。我們都知道,正方形其實(shí)是一種特殊的矩形(即長(zhǎng)寬相等)。因此,我們可以通過(guò)繼承 Rectangle 類來(lái)創(chuàng)建 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
接下來(lái),我們定義 Square 類,它繼承自 Rectangle 類。注意,為了盡可能復(fù)用現(xiàn)有的方法(來(lái)自父類),我們就會(huì)用到 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__ 實(shí)際上就是在調(diào)用父類 Rectangle 的 __init__ 方法。
我們給 super().__init__ 方法傳遞了 (length, length) 參數(shù),因?yàn)檎叫蔚拈L(zhǎng)寬相等。同時(shí),我們可以調(diào)用父類計(jì)算面積和周長(zhǎng)的方法,因?yàn)樽宇惪梢栽L問(wèn)父類的方法。這樣,不僅提升了代碼的復(fù)用性,并且代碼更簡(jiǎn)潔。
3. 實(shí)例方法 VS 類方法 VS 靜態(tài)方法
實(shí)例方法(Instance methods)屬于類(對(duì)象)的實(shí)例,它可以訪問(wèn)實(shí)例的屬性。
例如,在下面的代碼片段中,intro 就是 Dog 類的實(shí)例方法。
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)屬于類(而不是實(shí)例),并且類方法只能訪問(wèn)類屬性,而不能訪問(wèn)實(shí)例屬性。
比如,在下面的示例中,get_employee_count 就是 Employee 類的一個(gè)類方法:
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
關(guān)于類方法:
- 我們使用 @classmethod 裝飾器來(lái)表示定義類方法。
- 類方法接受的是 cls 參數(shù)而不是 self, cls 表示類本身(這里為 Employee)。
- 類方法(get_employee_count)只能訪問(wèn)類屬性(如employee_count),而不能訪問(wèn)實(shí)例屬性(如name, salary)。
靜態(tài)方法(Static methods)屬于類,它無(wú)法訪問(wèn)任何屬性。
例如,在下面的示例中,description 就是 Employee 類的靜態(tài)方法:
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.
關(guān)于靜態(tài)方法:
- 我們通過(guò) @staticmethod 裝飾器來(lái)定義靜態(tài)方法。
- 請(qǐng)注意,靜態(tài)方法不接受 cls 或 self 參數(shù)。
- 靜態(tài)方法不能訪問(wèn)任何屬性,包括類屬性和實(shí)例屬性。
4. 數(shù)據(jù)類(Dataclasses)
當(dāng)我們需要?jiǎng)?chuàng)建具有許多簡(jiǎn)單屬性的類時(shí),使用數(shù)據(jù)類(dataclass)會(huì)尤其有用。比如,
from dataclasses import dataclass
@dataclass
class Employee:
name: str # 姓名
age: int # 年齡
gender: str # 性別
education: str # 學(xué)歷
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())
圖片
注意,我們不需要在數(shù)據(jù)類中編寫(xiě) __init__ 方法,因?yàn)樗呀?jīng)為我們自動(dòng)編寫(xiě)該方法。
此外,請(qǐng)注意,如果你需要在 __init__ 方法中執(zhí)行一些特定的操作,那么可能使用數(shù)據(jù)類并不合適。
5.__dict__屬性
當(dāng)我們創(chuàng)建類的實(shí)例后,實(shí)際上實(shí)例對(duì)象的屬性在底層存在一個(gè)特殊變量中(即__dict__),我們可以使用它來(lái)獲取實(shí)例的屬性信息。
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}
即使是動(dòng)態(tài)添加的屬性,也可以通過(guò)對(duì)象的 __dict__ 屬性獲?。?/p>
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}
如果我們希望調(diào)試和檢查具有許多屬性的復(fù)雜對(duì)象和類,這非常有用。