網(wǎng)站前端設(shè)計(jì)網(wǎng)絡(luò)營銷案例分析報告
一、類和對象
1. 類和對象基礎(chǔ)
類(Class)的概念
類是對一類事物的抽象描述,定義了這類事物的屬性(數(shù)據(jù))和方法(行為)。
- 屬性:類的特征,如 “人” 的姓名、年齡。
- 方法:類的行為,如 “人” 的說話、跑步。
類比現(xiàn)實(shí):
- 類 = 設(shè)計(jì)圖紙(如 “汽車” 的設(shè)計(jì)圖)。
- 對象 = 根據(jù)圖紙制造的具體實(shí)例(如 “一輛紅色的特斯拉汽車”)。
2、定義類的語法
class ClassName:# 類屬性(可選,屬于類本身)class_attribute = "類屬性"# 構(gòu)造方法(初始化對象時自動調(diào)用)def __init__(self, param1, param2):# 實(shí)例屬性(屬于對象)self.attribute1 = param1 # 通過self綁定到對象self.attribute2 = param2# 實(shí)例方法(需通過對象調(diào)用,第一個參數(shù)為self)def instance_method(self, arg):print(f"實(shí)例方法:{self.attribute1}, 參數(shù):{arg}")# 類方法(需裝飾器@classmethod,第一個參數(shù)為cls)@classmethoddef class_method(cls):print(f"類方法:{cls.class_attribute}")# 靜態(tài)方法(無需綁定類或?qū)ο?#xff0c;通過@staticmethod裝飾)@staticmethoddef static_method():print("靜態(tài)方法")
3、對象(實(shí)例)的創(chuàng)建與使用
1. 創(chuàng)建對象(實(shí)例化類)
obj = ClassName("值1", "值2") # 調(diào)用__init__方法初始化對象
2. 訪問屬性和方法
# 訪問實(shí)例屬性
print(obj.attribute1) # 輸出:值1# 調(diào)用實(shí)例方法
obj.instance_method("參數(shù)") # 輸出:實(shí)例方法:值1, 參數(shù):參數(shù)# 訪問類屬性(通過類或?qū)ο?#xff09;
print(ClassName.class_attribute) # 輸出:類屬性
print(obj.class_attribute) # 輸出:類屬性# 調(diào)用類方法和靜態(tài)方法(通過類調(diào)用)
ClassName.class_method() # 輸出:類方法:類屬性
ClassName.static_method() # 輸出:靜態(tài)方法
4.關(guān)鍵概念解析
1.self
?的作用
- 實(shí)例方法的第一個參數(shù)必須是
self
,代表當(dāng)前對象本身。- 通過
self
可以訪問對象的屬性和方法。
class Person:def __init__(self, name):self.name = name # 將參數(shù)name賦值給對象的name屬性def say_hello(self):print(f"Hello, {self.name}!") # 通過self訪問對象的name屬性
2. 類屬性 vs 實(shí)例屬性
類屬性:屬于類本身,所有對象共享,通過類名直接訪問。
class Dog:species = "犬科" # 類屬性dog1 = Dog()
print(Dog.species) # 輸出:犬科(通過類訪問)
print(dog1.species) # 輸出:犬科(通過對象訪問)
實(shí)例屬性:屬于每個對象,通過self
在構(gòu)造方法中定義,每個對象獨(dú)立存在。
class Dog:def __init__(self, name):self.name = name # 實(shí)例屬性(每個狗的名字不同)dog1 = Dog("旺財")
dog2 = Dog("小白")
print(dog1.name) # 輸出:旺財
print(dog2.name) # 輸出:小白
3. 方法類型
方法類型 | 裝飾器 | 參數(shù)特點(diǎn) | 訪問方式 |
---|---|---|---|
實(shí)例方法 | 無 | 第一個參數(shù)為 self | 通過對象調(diào)用 |
類方法 | @classmethod | 第一個參數(shù)為 cls | 通過類調(diào)用 |
靜態(tài)方法 | @staticmethod | 無特殊參數(shù) | 通過類調(diào)用 |
5. 示例:定義 “學(xué)生” 類
class Student:# 類屬性:所有學(xué)生共享的學(xué)校名稱school = "XX中學(xué)"# 構(gòu)造方法:初始化學(xué)生的姓名和年級def __init__(self, name, grade):self.name = name # 實(shí)例屬性:姓名self.grade = grade # 實(shí)例屬性:年級# 實(shí)例方法:打印學(xué)生信息def show_info(self):print(f"姓名:{self.name},年級:{self.grade},學(xué)校:{self.school}")# 類方法:修改學(xué)校名稱@classmethoddef change_school(cls, new_school):cls.school = new_school# 靜態(tài)方法:判斷是否為高年級(示例邏輯)@staticmethoddef is_senior(grade):return grade >= 3 # 假設(shè)3年級及以上為高年級# 創(chuàng)建學(xué)生對象
stu1 = Student("張三", 2)
stu2 = Student("李四", 4)# 調(diào)用實(shí)例方法
stu1.show_info() # 輸出:姓名:張三,年級:2,學(xué)校:XX中學(xué)
stu2.show_info() # 輸出:姓名:李四,年級:4,學(xué)校:XX中學(xué)# 調(diào)用類方法修改學(xué)校名稱
Student.change_school("實(shí)驗(yàn)中學(xué)")
print(Student.school) # 輸出:實(shí)驗(yàn)中學(xué)# 調(diào)用靜態(tài)方法
print(Student.is_senior(3)) # 輸出:True
二. 繼承和多態(tài)
2.1繼承(Inheritance)
繼承是面向?qū)ο缶幊痰暮诵母拍钪?#xff0c;允許一個類(子類)繼承另一個類(父類)的屬性和方法,實(shí)現(xiàn)代碼復(fù)用和層次化設(shè)計(jì)。
1. 基本語法與概念
class ParentClass:def parent_method(self):print("這是父類的方法")class ChildClass(ParentClass): # 子類繼承父類def child_method(self):print("這是子類的方法")# 創(chuàng)建子類對象
child = ChildClass()
child.parent_method() # 調(diào)用父類方法
child.child_method() # 調(diào)用子類方法
關(guān)鍵點(diǎn):
- 子類通過括號內(nèi)指定父類名稱實(shí)現(xiàn)繼承。
- 子類自動獲得父類的所有非私有屬性和方法。
- 子類可新增自己的屬性和方法,或重寫父類方法。
2. 方法重寫(Override)
子類可重新定義父類的方法,覆蓋其實(shí)現(xiàn):
class Animal:def speak(self):return "動物發(fā)出聲音"class Dog(Animal):def speak(self): # 重寫父類方法return "汪汪汪"class Cat(Animal):def speak(self): # 重寫父類方法return "喵喵喵"# 測試
dog = Dog()
cat = Cat()
print(dog.speak()) # 輸出:汪汪汪
print(cat.speak()) # 輸出:喵喵喵
3. 多重繼承
Python 支持一個子類繼承多個父類:
class A:def method_a(self):print("A類的方法")class B:def method_b(self):print("B類的方法")class C(A, B): # 繼承自A和Bpassc = C()
c.method_a() # 輸出:A類的方法
c.method_b() # 輸出:B類的方法
注意:多重繼承可能導(dǎo)致 “菱形繼承問題”,需通過 MRO(方法解析順序)解決。
4. 訪問父類方法
通過super()
調(diào)用父類的方法:
class Parent:def greet(self):print("Hello from Parent")class Child(Parent):def greet(self):super().greet() # 調(diào)用父類的greet方法print("Hello from Child")child = Child()
child.greet()
# 輸出:
# Hello from Parent
# Hello from Child
2.2 多態(tài)(Polymorphism)
多態(tài)允許不同類的對象對同一方法做出不同響應(yīng),提高代碼靈活性和可擴(kuò)展性。
1. 基于繼承的多態(tài)
通過方法重寫實(shí)現(xiàn):
class Shape:def area(self):return 0 # 默認(rèn)實(shí)現(xiàn)class Rectangle(Shape):def __init__(self, width, height):self.width = widthself.height = heightdef area(self): # 重寫area方法return self.width * self.heightclass Circle(Shape):def __init__(self, radius):self.radius = radiusdef area(self): # 重寫area方法return 3.14 * self.radius ** 2# 多態(tài)調(diào)用
shapes = [Rectangle(2, 3), Circle(5)]
for shape in shapes:print(f"面積: {shape.area()}") # 自動調(diào)用對應(yīng)子類的area方法
2. 鴨子類型(Duck Typing)
Python 的多態(tài)不依賴?yán)^承,只要對象具有相同方法即可調(diào)用:
class Dog:def speak(self):return "汪汪汪"class Cat:def speak(self):return "喵喵喵"class Car:def speak(self): # 不繼承任何類,但有相同方法名return "嘟嘟嘟"# 多態(tài)調(diào)用
def animal_speak(obj):print(obj.speak())dog = Dog()
cat = Cat()
car = Car()animal_speak(dog) # 輸出:汪汪汪
animal_speak(cat) # 輸出:喵喵喵
animal_speak(car) # 輸出:嘟嘟嘟(Car類與動物無關(guān),但仍可調(diào)用)
3. 抽象基類(Abstract Base Class)
強(qiáng)制子類實(shí)現(xiàn)特定方法:
from abc import ABC, abstractmethodclass Animal(ABC): # 抽象基類@abstractmethod # 抽象方法,子類必須實(shí)現(xiàn)def speak(self):passclass Dog(Animal):def speak(self): # 實(shí)現(xiàn)抽象方法return "汪汪汪"class Cat(Animal):def speak(self): # 實(shí)現(xiàn)抽象方法return "喵喵喵"# 無法實(shí)例化抽象基類
# animal = Animal() # 報錯:TypeError# 合法的子類實(shí)例
dog = Dog()
print(dog.speak()) # 輸出:汪汪汪
4.代碼示例:員工管理系統(tǒng)
from abc import ABC, abstractmethodclass Employee(ABC): # 抽象基類def __init__(self, name, salary):self.name = nameself.salary = salary@abstractmethoddef calculate_bonus(self):passdef get_info(self):return f"姓名: {self.name}, 工資: {self.salary}, 獎金: {self.calculate_bonus()}"class FullTimeEmployee(Employee):def calculate_bonus(self): # 實(shí)現(xiàn)抽象方法return self.salary * 0.2 # 20%獎金class PartTimeEmployee(Employee):def calculate_bonus(self): # 實(shí)現(xiàn)抽象方法return self.salary * 0.1 # 10%獎金# 多態(tài)調(diào)用
employees = [FullTimeEmployee("張三", 8000),PartTimeEmployee("李四", 3000)
]for emp in employees:print(emp.get_info()) # 自動調(diào)用對應(yīng)子類的calculate_bonus方法# 輸出:
# 姓名: 張三, 工資: 8000, 獎金: 1600.0
# 姓名: 李四, 工資: 3000, 獎金: 300.0
5. 繼承與多態(tài)的優(yōu)勢
- 代碼復(fù)用:通過繼承減少重復(fù)代碼。
- 可擴(kuò)展性:新增子類不影響現(xiàn)有代碼(開閉原則)。
- 靈活性:通過多態(tài)統(tǒng)一接口,不同實(shí)現(xiàn)動態(tài)切換。
- 可維護(hù)性:通過抽象基類明確接口規(guī)范,降低耦合度。
三. 特殊方法(魔術(shù)方法)
3.1、什么是特殊方法?
????????特殊方法(Magic Methods)也稱為魔術(shù)方法,是 Python 中預(yù)定義的、以雙下劃線(__
)開頭和結(jié)尾的方法。它們用于實(shí)現(xiàn)類的內(nèi)置行為(如初始化、運(yùn)算符重載、迭代等),無需顯式調(diào)用,而是由特定語法或內(nèi)置函數(shù)觸發(fā)。
常見用途:
- 對象初始化(
__init__
)- 字符串表示(
__str__
,?__repr__
)- 算術(shù)運(yùn)算符(
__add__
,?__sub__
)- 比較運(yùn)算符(
__eq__
,?__lt__
)- 容器操作(
__len__
,?__getitem__
)- 上下文管理器(
__enter__
,?__exit__
)
3.2、常用特殊方法分類
1. 對象創(chuàng)建與銷毀
__init__(self, ...)
:初始化對象,創(chuàng)建實(shí)例時自動調(diào)用。__new__(cls, ...)
:創(chuàng)建對象實(shí)例的靜態(tài)方法,先于__init__
執(zhí)行。__del__(self)
:對象被垃圾回收時調(diào)用。
示例:
class Person:def __init__(self, name, age):self.name = nameself.age = ageprint(f"創(chuàng)建了{(lán)self.name}")def __del__(self):print(f"銷毀了{(lán)self.name}")p = Person("張三", 20) # 輸出:創(chuàng)建了張三
del p # 輸出:銷毀了張三
2. 字符串表示
__str__(self)
:返回對象的用戶友好字符串表示(str(obj)
或print(obj)
時調(diào)用)。__repr__(self)
:返回對象的開發(fā)者友好字符串表示(交互式環(huán)境或repr(obj)
時調(diào)用)。
示例:
class Point:def __init__(self, x, y):self.x = xself.y = ydef __str__(self):return f"({self.x}, {self.y})"def __repr__(self):return f"Point({self.x}, {self.y})"p = Point(3, 4)
print(p) # 輸出:(3, 4)(調(diào)用__str__)
print(repr(p)) # 輸出:Point(3, 4)(調(diào)用__repr__)
3. 算術(shù)運(yùn)算符重載
__add__(self, other)
:定義加法(+
)行為。__sub__(self, other)
:定義減法(-
)行為。__mul__(self, other)
:定義乘法(*
)行為。__truediv__(self, other)
:定義除法(/
)行為。__floordiv__(self, other)
:定義整除(//
)行為。
示例:
class Vector:def __init__(self, x, y):self.x = xself.y = ydef __add__(self, other):return Vector(self.x + other.x, self.y + other.y)def __str__(self):return f"Vector({self.x}, {self.y})"v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1 + v2 # 調(diào)用__add__
print(v3) # 輸出:Vector(4, 6)
4. 比較運(yùn)算符重載
__eq__(self, other)
:定義等于(==
)行為。__ne__(self, other)
:定義不等于(!=
)行為。__lt__(self, other)
:定義小于(<
)行為。__gt__(self, other)
:定義大于(>
)行為。
示例:
class Person:def __init__(self, age):self.age = agedef __eq__(self, other):return self.age == other.agedef __lt__(self, other):return self.age < other.agep1 = Person(20)
p2 = Person(25)
print(p1 == p2) # 輸出:False(調(diào)用__eq__)
print(p1 < p2) # 輸出:True(調(diào)用__lt__)
5. 容器類方法
__len__(self)
:返回容器長度(len(obj)
時調(diào)用)。__getitem__(self, key)
:獲取容器元素(obj[key]
時調(diào)用)。__setitem__(self, key, value)
:設(shè)置容器元素(obj[key] = value
時調(diào)用)。__contains__(self, item)
:判斷元素是否存在(item in obj
時調(diào)用)。
示例:
class MyList:def __init__(self, *items):self.items = list(items)def __len__(self):return len(self.items)def __getitem__(self, index):return self.items[index]my_list = MyList(1, 2, 3)
print(len(my_list)) # 輸出:3(調(diào)用__len__)
print(my_list[1]) # 輸出:2(調(diào)用__getitem__)
6. 上下文管理器(with 語句)
__enter__(self)
:進(jìn)入上下文時調(diào)用,返回值綁定到as
后的變量。__exit__(self, exc_type, exc_value, traceback)
:退出上下文時調(diào)用,處理異常。
示例:
class FileHandler:def __init__(self, filename, mode):self.filename = filenameself.mode = modedef __enter__(self):self.file = open(self.filename, self.mode)return self.filedef __exit__(self, exc_type, exc_value, traceback):self.file.close()return True # 異常已處理,不再傳播with FileHandler("test.txt", "w") as f:f.write("Hello, World!") # 自動調(diào)用__enter__和__exit__
3.3、自定義類的特殊方法實(shí)戰(zhàn)
下面是一個綜合示例,展示如何通過特殊方法創(chuàng)建一個支持多種操作的自定義類:
class MyNumber:def __init__(self, value):self.value = value# 算術(shù)運(yùn)算def __add__(self, other):return MyNumber(self.value + other.value)def __sub__(self, other):return MyNumber(self.value - other.value)# 比較運(yùn)算def __eq__(self, other):return self.value == other.valuedef __gt__(self, other):return self.value > other.value# 字符串表示def __str__(self):return f"數(shù)值: {self.value}"def __repr__(self):return f"MyNumber({self.value})"# 類型轉(zhuǎn)換def __int__(self):return int(self.value)def __float__(self):return float(self.value)# 使用示例
a = MyNumber(5)
b = MyNumber(10)# 算術(shù)運(yùn)算
c = a + b
print(c) # 輸出:數(shù)值: 15# 比較運(yùn)算
print(a > b) # 輸出:False
print(a == b) # 輸出:False# 類型轉(zhuǎn)換
print(int(a)) # 輸出:5
print(float(a)) # 輸出:5.0
3.4、特殊方法總結(jié)
????????特殊方法是 Python 面向?qū)ο缶幊痰膹?qiáng)大工具,通過合理實(shí)現(xiàn)這些方法,可以讓自定義類具有與內(nèi)置類型相似的行為,提高代碼的可讀性和可維護(hù)性。
常見用途總結(jié):
類別 | 常用方法 |
---|---|
對象創(chuàng)建 / 銷毀 | __init__ ,?__new__ ,?__del__ |
字符串表示 | __str__ ,?__repr__ |
算術(shù)運(yùn)算符 | __add__ ,?__sub__ ,?__mul__ |
比較運(yùn)算符 | __eq__ ,?__lt__ ,?__gt__ |
容器操作 | __len__ ,?__getitem__ |
上下文管理器 | __enter__ ,?__exit__ |
類型轉(zhuǎn)換 | __int__ ,?__float__ |
????????通過深入理解和使用特殊方法,可以編寫出更加 Pythonic、靈活且高效的代碼。
四. 描述符和屬性
4.1、描述符(Descriptor)
????????描述符是一種實(shí)現(xiàn)了特定協(xié)議(__get__
、__set__
、__delete__
)的類,用于控制類中屬性的訪問行為。它是 Python 實(shí)現(xiàn)@property
、classmethod
、staticmethod
等功能的底層機(jī)制,也是自定義屬性邏輯的強(qiáng)大工具。
1. 描述符協(xié)議的三個方法
方法名稱 | 觸發(fā)時機(jī) | 參數(shù)說明 |
---|---|---|
__get__(self, instance, owner) | 當(dāng)訪問屬性時調(diào)用 | instance :對象實(shí)例(若無則為None )owner :所屬類 |
__set__(self, instance, value) | 當(dāng)設(shè)置屬性值時調(diào)用 | instance :對象實(shí)例value :要設(shè)置的值 |
__delete__(self, instance) | 當(dāng)刪除屬性時調(diào)用 | instance :對象實(shí)例 |
2. 描述符的類型
根據(jù)是否實(shí)現(xiàn)
__set__
方法,描述符分為兩類:
- 數(shù)據(jù)描述符(Data Descriptor):實(shí)現(xiàn)了
__set__
和__get__
,可完全控制屬性的讀寫。- 非數(shù)據(jù)描述符(Non-Data Descriptor):僅實(shí)現(xiàn)
__get__
,屬性為只讀(如@property
裝飾的方法)。
3. 數(shù)據(jù)描述符示例:限制屬性類型
class TypedAttribute:def __init__(self, expected_type):self.expected_type = expected_typeself.name = None # 存儲屬性名(通過__set_name__綁定)def __set_name__(self, owner, name):"""在類定義時自動調(diào)用,綁定屬性名"""self.name = namedef __get__(self, instance, owner):"""獲取屬性值"""if instance is None:return self # 通過類訪問描述符時返回自身return instance.__dict__[self.name] # 從實(shí)例字典獲取值def __set__(self, instance, value):"""設(shè)置屬性值,校驗(yàn)類型"""if not isinstance(value, self.expected_type):raise TypeError(f"{self.name}必須是{self.expected_type.__name__}類型")instance.__dict__[self.name] = value # 存儲到實(shí)例字典# 使用描述符的類
class Person:name = TypedAttribute(str) # 數(shù)據(jù)描述符:限制為str類型age = TypedAttribute(int) # 數(shù)據(jù)描述符:限制為int類型# 測試
p = Person()
p.name = "張三" # 合法
# p.age = "20" # 報錯:TypeError: age必須是int類型
4. 非數(shù)據(jù)描述符示例:只讀屬性
class ReadOnlyDescriptor:def __init__(self, value):self.value = valuedef __get__(self, instance, owner):return self.valueclass Config:VERSION = ReadOnlyDescriptor("1.0.0") # 非數(shù)據(jù)描述符:只讀print(Config.VERSION) # 輸出:1.0.0
# Config.VERSION = "2.0.0" # 報錯:AttributeError(無__set__方法)
5. 描述符的優(yōu)先級
描述符在類中的優(yōu)先級由以下規(guī)則決定(從高到低):
- 實(shí)例字典(
__dict__
):實(shí)例直接賦值的屬性會覆蓋描述符。p.name = "李四" # 直接修改實(shí)例字典,繞過描述符的__set__
- 數(shù)據(jù)描述符:優(yōu)先于實(shí)例字典。
- 非數(shù)據(jù)描述符:低于實(shí)例字典。
- 普通屬性:無描述符時,直接訪問實(shí)例字典。
4.2、屬性(Property)
????????**@property
** 是 Python 提供的語法糖,用于將類中的方法轉(zhuǎn)換為 “屬性”,簡化數(shù)據(jù)描述符的使用。它本質(zhì)上是一個非數(shù)據(jù)描述符。
1.?@property
基礎(chǔ)用法
class Circle:def __init__(self, radius):self._radius = radius # 私有屬性,通過property訪問@propertydef radius(self):"""獲取半徑(屬性 getter)"""return self._radius@radius.setterdef radius(self, value):"""設(shè)置半徑(屬性 setter)"""if value < 0:raise ValueError("半徑不能為負(fù)數(shù)")self._radius = value@propertydef area(self):"""計(jì)算面積(只讀屬性)"""return 3.14 * self._radius ** 2# 使用示例
c = Circle(5)
print(c.radius) # 輸出:5(調(diào)用@property)
c.radius = 6 # 調(diào)用@radius.setter
print(c.area) # 輸出:113.04(只讀屬性)
# c.area = 100 # 報錯:AttributeError(無setter)
2.?property
的參數(shù)形式
不使用裝飾器時,可通過property(fget, fset, fdel, doc)
創(chuàng)建屬性:
class Circle:def __init__(self, radius):self._radius = radiusdef get_radius(self):return self._radiusdef set_radius(self, value):if value < 0:raise ValueError("半徑不能為負(fù)數(shù)")self._radius = valueradius = property(get_radius, set_radius) # 定義屬性area = property(lambda self: 3.14 * self._radius ** 2) # 只讀屬性
3. 屬性的優(yōu)勢
- 封裝性:隱藏屬性的存儲細(xì)節(jié),通過方法控制訪問。
- 驗(yàn)證邏輯:在
setter
中添加數(shù)據(jù)校驗(yàn)(如類型、范圍檢查)。 - 計(jì)算屬性:動態(tài)生成屬性值(如
area
無需存儲,實(shí)時計(jì)算)。
4、描述符 vs 屬性
特性 | 描述符 | 屬性(@property ) |
---|---|---|
實(shí)現(xiàn)方式 | 自定義類,實(shí)現(xiàn)協(xié)議方法 | 裝飾器或property 函數(shù) |
靈活性 | 高(可復(fù)用,支持多個屬性) | 低(每個屬性需單獨(dú)定義) |
適用場景 | 多個屬性共享相同邏輯(如類型校驗(yàn)) | 單個屬性的讀寫控制 |
數(shù)據(jù)描述符 / 非數(shù)據(jù)描述符 | 可自定義(實(shí)現(xiàn)__set__ 即數(shù)據(jù)描述符) | 非數(shù)據(jù)描述符(默認(rèn)只讀,需@setter 才為數(shù)據(jù)描述符) |
5、實(shí)戰(zhàn):用描述符實(shí)現(xiàn)緩存屬性
class CacheDescriptor:def __init__(self, func):self.func = funcself.cache = {} # 緩存字典def __get__(self, instance, owner):if instance is None:return selfkey = id(instance)if key not in self.cache:self.cache[key] = self.func(instance) # 首次調(diào)用時計(jì)算并緩存return self.cache[key]class HeavyCalculation:def __init__(self, data):self.data = data@CacheDescriptor # 使用描述符裝飾方法def result(self):print("執(zhí)行耗時計(jì)算...")return sum(self.data) * 0.5 # 模擬耗時操作# 測試
obj1 = HeavyCalculation([1, 2, 3, 4, 5])
print(obj1.result) # 輸出:執(zhí)行耗時計(jì)算... 7.5(首次計(jì)算)
print(obj1.result) # 直接從緩存獲取,不重復(fù)計(jì)算
6、總結(jié)
- 描述符是 Python 的高級特性,用于實(shí)現(xiàn)屬性的復(fù)雜控制邏輯,是
@property
、classmethod
等的底層機(jī)制。- **
@property
** 是描述符的簡化用法,適合單個屬性的讀寫控制,常用于數(shù)據(jù)驗(yàn)證和計(jì)算屬性。- 合理使用描述符和屬性,可以讓代碼更具封裝性和可維護(hù)性,避免直接操作屬性帶來的安全隱患。
????????理解描述符和屬性的工作原理,有助于深入掌握 Python 的面向?qū)ο缶幊?#xff0c;并在需要時實(shí)現(xiàn)高度定制化的屬性行為。
五. 靜態(tài)方法和類方法
5.1、基本概念
類型 | 綁定對象 | 裝飾器 | 第一個參數(shù) | 調(diào)用方式 |
---|---|---|---|---|
實(shí)例方法 | 實(shí)例 | 無 | self (實(shí)例本身) | obj.method() |
類方法 | 類 | @classmethod | cls (類本身) | Class.method() |
靜態(tài)方法 | 無 | @staticmethod | 無特殊參數(shù) | Class.method() |
5.2、靜態(tài)方法(Static Method)
靜態(tài)方法屬于類,但不綁定類或?qū)嵗?#xff0c;類似于普通函數(shù)。它不能訪問類或?qū)嵗膶傩?#xff0c;僅用于執(zhí)行與類相關(guān)的獨(dú)立功能。
1. 定義與使用
class Calculator:@staticmethoddef add(a, b):return a + b@staticmethoddef multiply(a, b):return a * b# 調(diào)用方式
print(Calculator.add(3, 5)) # 輸出:8
print(Calculator.multiply(4, 2)) # 輸出:8# 也可通過實(shí)例調(diào)用(不推薦)
calc = Calculator()
print(calc.add(3, 5)) # 輸出:8(但實(shí)例狀態(tài)不會被使用)
2. 核心特點(diǎn)
- 不依賴實(shí)例狀態(tài):無法訪問或修改實(shí)例屬性。
- 不依賴類狀態(tài):無法訪問或修改類屬性(如類變量)。
- 用途:封裝與類相關(guān)的工具函數(shù),提高代碼組織性。
5.3、類方法(Class Method)
類方法綁定到類而非實(shí)例,通過第一個參數(shù)cls
訪問類屬性和方法,常用于創(chuàng)建工廠方法或修改類狀態(tài)。
1. 定義與使用
class Person:count = 0 # 類變量:記錄實(shí)例數(shù)量def __init__(self, name):self.name = namePerson.count += 1@classmethoddef get_count(cls):"""獲取類的實(shí)例數(shù)量"""return cls.count@classmethoddef create_anonymous(cls):"""工廠方法:創(chuàng)建匿名實(shí)例"""return cls("匿名用戶")# 使用示例
p1 = Person("張三")
p2 = Person("李四")print(Person.get_count()) # 輸出:2(通過類調(diào)用)
print(p1.get_count()) # 輸出:2(通過實(shí)例調(diào)用,仍綁定類)anon = Person.create_anonymous()
print(anon.name) # 輸出:匿名用戶
2. 核心特點(diǎn)
- 訪問類屬性:通過
cls
參數(shù)訪問類變量(如cls.count
)。- 修改類狀態(tài):可修改類變量或調(diào)用其他類方法。
- 工廠方法:創(chuàng)建實(shí)例的替代構(gòu)造函數(shù)(如
create_anonymous
)。
5.4、靜態(tài)方法 vs 類方法
特性 | 靜態(tài)方法 | 類方法 |
---|---|---|
第一個參數(shù) | 無特殊參數(shù) | cls (類本身) |
訪問類屬性 | ? 無法直接訪問 | ? 通過cls 訪問 |
修改類狀態(tài) | ? 無法修改 | ? 可修改類變量 |
工廠方法 | ? 不適用 | ? 常用于創(chuàng)建實(shí)例的替代方式 |
適用場景 | 與類相關(guān)的工具函數(shù)(如驗(yàn)證、計(jì)算) | 與類狀態(tài)相關(guān)的操作(如計(jì)數(shù)器、工廠) |
5.5、實(shí)戰(zhàn)對比
1. 靜態(tài)方法示例:日期驗(yàn)證工具
class Date:def __init__(self, year, month, day):self.year = yearself.month = monthself.day = day@staticmethoddef is_valid_date(date_str):"""驗(yàn)證日期字符串是否合法"""year, month, day = map(int, date_str.split('-'))return 1 <= month <= 12 and 1 <= day <= 31# 使用示例
print(Date.is_valid_date("2023-10-15")) # 輸出:True
print(Date.is_valid_date("2023-13-40")) # 輸出:False
2. 類方法示例:工廠模式
class Pizza:def __init__(self, ingredients):self.ingredients = ingredients@classmethoddef margherita(cls):"""創(chuàng)建瑪格麗特披薩(固定配料)"""return cls(["番茄", "馬蘇里拉芝士", "羅勒"])@classmethoddef pepperoni(cls):"""創(chuàng)建意式香腸披薩(固定配料)"""return cls(["番茄", "馬蘇里拉芝士", "香腸"])# 使用示例
m = Pizza.margherita()
p = Pizza.pepperoni()print(m.ingredients) # 輸出:['番茄', '馬蘇里拉芝士', '羅勒']
print(p.ingredients) # 輸出:['番茄', '馬蘇里拉芝士', '香腸']
5.6、常見問題
1. 何時使用靜態(tài)方法?
- 函數(shù)邏輯與類相關(guān),但不依賴類或?qū)嵗隣顟B(tài)(如工具函數(shù))。
- 提高代碼可讀性,將工具函數(shù)封裝在類內(nèi)部。
2. 何時使用類方法?
- 需要訪問或修改類變量(如計(jì)數(shù)器、配置)。
- 創(chuàng)建工廠方法,提供多種實(shí)例化方式。
- 實(shí)現(xiàn)繼承時,確保子類調(diào)用正確的類方法(
cls
會自動綁定到子類)。
3. 能否通過實(shí)例調(diào)用類方法 / 靜態(tài)方法?
可以,但不推薦。雖然實(shí)例可以調(diào)用類方法和靜態(tài)方法,但它們的第一個參數(shù)仍綁定類(cls
)或無特殊綁定,不會使用實(shí)例狀態(tài)。
5.7、總結(jié)
- 靜態(tài)方法是類的工具函數(shù),不依賴類或?qū)嵗隣顟B(tài),用于封裝獨(dú)立功能。
- 類方法綁定類,通過
cls
訪問類屬性,常用于工廠方法或類狀態(tài)操作。 - 合理使用兩者可提高代碼的組織性和可維護(hù)性,避免濫用全局函數(shù)。
理解靜態(tài)方法和類方法的區(qū)別,有助于設(shè)計(jì)更清晰、更符合面向?qū)ο笤瓌t的 Python 類.
六. 封裝和私有屬性
6.1、封裝的概念
????????封裝(Encapsulation)?是面向?qū)ο缶幊痰娜筇匦灾?#xff08;另外兩個是繼承和多態(tài)),它指的是將數(shù)據(jù)(屬性)和操作數(shù)據(jù)的方法(行為)捆綁在一起,并通過訪問控制隱藏內(nèi)部實(shí)現(xiàn)細(xì)節(jié),僅對外提供必要的接口。
封裝的核心目標(biāo):
- 數(shù)據(jù)保護(hù):防止外部直接修改內(nèi)部數(shù)據(jù),避免意外破壞。
- 接口簡化:隱藏復(fù)雜的內(nèi)部實(shí)現(xiàn),只暴露高層接口,降低使用難度。
- 可維護(hù)性:內(nèi)部實(shí)現(xiàn)可以自由修改,只要接口不變,外部代碼不受影響。
6.2、Python 的私有屬性與方法
Python 通過命名約定和特殊語法實(shí)現(xiàn)封裝,而非強(qiáng)制訪問控制。
1. 單下劃線(_
):弱私有(約定)
- 含義:表示 “私有”,但僅是約定,外部仍可訪問。
- 用途:提示開發(fā)者該屬性或方法不建議直接使用,可能在未來版本中變化。
示例:
class BankAccount:def __init__(self, balance):self._balance = balance # 單下劃線表示私有屬性def deposit(self, amount):self._balance += amountdef _calculate_interest(self): # 單下劃線表示私有方法return self._balance * 0.05# 外部仍可訪問,但不建議
account = BankAccount(1000)
print(account._balance) # 輸出:1000(可以訪問,但違反約定)
2. 雙下劃線(__
):名稱修飾(Name Mangling)
- 含義:強(qiáng)制私有,Python 會自動將其重命名為
_類名__屬性名
,外部無法直接訪問。- 用途:防止子類意外覆蓋父類的屬性或方法。
示例:
class Parent:def __init__(self):self.__private_attr = 42 # 雙下劃線屬性def __private_method(self): # 雙下劃線方法return "私有方法"class Child(Parent):passp = Parent()
print(p._Parent__private_attr) # 輸出:42(通過重命名后的名稱訪問)
# print(p.__private_attr) # 報錯:AttributeErrorc = Child()
# print(c.__private_attr) # 報錯:AttributeError(子類無法直接訪問)
3. 雙下劃線結(jié)尾(__
):特殊方法(避免使用)
- 含義:Python 的特殊方法(如
__init__
、__str__
),用于實(shí)現(xiàn)特定協(xié)議。- 注意:自定義屬性或方法應(yīng)避免使用雙下劃線結(jié)尾,防止與 Python 內(nèi)置名稱沖突。
6.3、封裝的最佳實(shí)踐
1. 使用屬性(@property
)控制訪問
通過@property
裝飾器實(shí)現(xiàn)對私有屬性的訪問控制,隱藏內(nèi)部實(shí)現(xiàn):
class Person:def __init__(self, age):self._age = age # 私有屬性@propertydef age(self):"""獲取年齡(只讀)"""return self._age@age.setterdef age(self, value):"""設(shè)置年齡,添加驗(yàn)證邏輯"""if value < 0:raise ValueError("年齡不能為負(fù)數(shù)")self._age = value# 使用示例
p = Person(25)
print(p.age) # 輸出:25(通過@property訪問)
p.age = 30 # 通過@age.setter設(shè)置
# p.age = -5 # 報錯:ValueError
2. 封裝內(nèi)部實(shí)現(xiàn)細(xì)節(jié)
將不對外公開的邏輯封裝為私有方法,只暴露高層接口:
class DataProcessor:def __init__(self, data):self._data = datadef process(self):"""公開的處理接口"""self._clean_data()self._analyze_data()return self._generate_report()def _clean_data(self): # 私有方法self._data = [x for x in self._data if x is not None]def _analyze_data(self): # 私有方法self._stats = {"mean": sum(self._data) / len(self._data)}def _generate_report(self): # 私有方法return f"分析結(jié)果:平均值 = {self._stats['mean']}"# 使用示例
processor = DataProcessor([1, 2, 3, None, 5])
print(processor.process()) # 輸出:分析結(jié)果:平均值 = 2.75
3. 防止子類意外覆蓋
使用雙下劃線方法避免子類覆蓋父類的核心邏輯:
class Base:def __init__(self):self.__initialize() # 強(qiáng)制私有方法,子類無法覆蓋def __initialize(self): # 雙下劃線方法print("初始化基類")class Sub(Base):def __initialize(self): # 這是一個新方法,不會覆蓋父類的__initializeprint("初始化子類") # 不會被調(diào)用s = Sub() # 輸出:初始化基類
4、封裝的優(yōu)勢
-
數(shù)據(jù)安全:通過訪問控制避免外部直接修改敏感數(shù)據(jù)。
# 錯誤示例:直接修改內(nèi)部狀態(tài) account.balance = -1000 # 可能導(dǎo)致賬戶余額異常# 正確示例:通過方法控制修改 account.deposit(100) # 經(jīng)過驗(yàn)證的操作
-
代碼可維護(hù)性:內(nèi)部實(shí)現(xiàn)可以自由修改,不影響外部調(diào)用。
# 原實(shí)現(xiàn):直接存儲平均值 self._mean = sum(data) / len(data)# 新實(shí)現(xiàn):改為動態(tài)計(jì)算(接口不變) @property def mean(self):return sum(self._data) / len(self._data)
-
簡化接口:隱藏復(fù)雜細(xì)節(jié),提供簡潔的 API。
# 用戶只需調(diào)用高層方法,無需關(guān)心內(nèi)部步驟 processor.process() # 而非手動調(diào)用多個方法
5、常見誤區(qū)
-
過度使用雙下劃線:
- 雙下劃線主要用于防止子類覆蓋,而非完全禁止外部訪問。
- 大多數(shù)情況下,單下劃線約定已足夠。
-
認(rèn)為雙下劃線是 “真正私有”:
- Python 沒有真正的私有屬性,雙下劃線只是名稱修飾,仍可通過
_類名__屬性名
訪問。
- Python 沒有真正的私有屬性,雙下劃線只是名稱修飾,仍可通過
-
忽略屬性的驗(yàn)證邏輯:
- 直接使用公共屬性(如
self.age
)而不添加驗(yàn)證,可能導(dǎo)致數(shù)據(jù)不一致。
- 直接使用公共屬性(如
6、總結(jié)
- 封裝是將數(shù)據(jù)和操作捆綁,并控制訪問的機(jī)制,提高代碼安全性和可維護(hù)性。
- 單下劃線(
_
)?是約定的私有標(biāo)識,提示外部不要直接訪問。- 雙下劃線(
__
)?通過名稱修飾實(shí)現(xiàn)更強(qiáng)的封裝,防止子類意外覆蓋。- 屬性(
@property
)?是實(shí)現(xiàn)封裝的最佳方式,允許對屬性訪問添加驗(yàn)證和邏輯。
合理使用封裝,能夠設(shè)計(jì)出更加健壯、靈活且易于維護(hù)的 Python 類。
七. 抽象基類
7.1、抽象基類的概念
????????抽象基類(ABC)?是一種特殊的類,它定義了一組必須被子類實(shí)現(xiàn)的方法(抽象方法),但自身不能被實(shí)例化。抽象基類用于強(qiáng)制子類遵循特定的接口規(guī)范,確保多態(tài)性的正確實(shí)現(xiàn),是 Python 實(shí)現(xiàn) “接口繼承” 的核心機(jī)制。
7.2、如何定義抽象基類
在 Python 中,通過abc
模塊的ABC
類和@abstractmethod
裝飾器定義抽象基類和抽象方法:
from abc import ABC, abstractmethodclass Animal(ABC): # 繼承自ABC@abstractmethod # 標(biāo)記為抽象方法def speak(self):"""子類必須實(shí)現(xiàn)的方法"""pass@abstractmethoddef move(self):pass
關(guān)鍵點(diǎn):
- 抽象基類必須繼承自
abc.ABC
。- 抽象方法使用
@abstractmethod
裝飾,子類必須實(shí)現(xiàn)這些方法,否則無法實(shí)例化。- 抽象基類可以包含具體方法(非抽象方法),供子類直接繼承。
7.3、強(qiáng)制子類實(shí)現(xiàn)接口
如果子類未實(shí)現(xiàn)抽象基類的所有抽象方法,實(shí)例化時會拋出TypeError
:
正確實(shí)現(xiàn)示例:
class Dog(Animal):def speak(self):return "汪汪汪"def move(self): # 實(shí)現(xiàn)move方法return "四條腿跑"dog = Dog()
print(dog.speak()) # 輸出:汪汪汪
7.4、抽象基類的具體方法
抽象基類可以提供默認(rèn)實(shí)現(xiàn)的具體方法,供子類繼承或重寫:
from abc import ABC, abstractmethodclass FileHandler(ABC):def __init__(self, filename):self.filename = filename@abstractmethoddef read(self):pass@abstractmethoddef write(self, data):passdef close(self): # 具體方法(非抽象)print(f"關(guān)閉文件 {self.filename}")# 子類繼承并實(shí)現(xiàn)抽象方法
class TextFile(FileHandler):def read(self):with open(self.filename, 'r') as f:return f.read()def write(self, data):with open(self.filename, 'w') as f:f.write(data)txt = TextFile("test.txt")
txt.write("Hello, ABC!")
txt.close() # 調(diào)用基類的close方法
7.5、抽象基類的應(yīng)用場景
1. 定義接口規(guī)范
確保不同子類遵循統(tǒng)一的方法命名和參數(shù)列表,例如定義 “支付接口”:
from abc import ABC, abstractmethodclass Payment(ABC):@abstractmethoddef pay(self, amount):"""支付指定金額"""passclass Alipay(Payment):def pay(self, amount):print(f"支付寶支付{amount}元")class WeChatPay(Payment):def pay(self, amount):print(f"微信支付{amount}元")# 多態(tài)調(diào)用
def make_payment(payment: Payment, amount):payment.pay(amount)make_payment(Alipay(), 100) # 輸出:支付寶支付100元
make_payment(WeChatPay(), 200) # 輸出:微信支付200元
2. 類型檢查
使用isinstance()
或issubclass()
判斷對象或類是否符合抽象基類的接口:
class VirtualPayment(Payment): # 未實(shí)現(xiàn)pay方法(故意錯誤)passprint(issubclass(Alipay, Payment)) # 輸出:True
print(isinstance(Alipay(), Payment)) # 輸出:True# 未實(shí)現(xiàn)抽象方法的類不被視為子類
print(issubclass(VirtualPayment, Payment)) # 輸出:False
3. 插件系統(tǒng)設(shè)計(jì)
允許動態(tài)加載符合抽象基類接口的插件,提高系統(tǒng)擴(kuò)展性:
# 基類(插件接口)
class Plugin(ABC):@abstractmethoddef run(self):pass# 插件實(shí)現(xiàn)
class DataPlugin(Plugin):def run(self):print("數(shù)據(jù)處理插件運(yùn)行")# 插件管理器
class PluginManager:def __init__(self):self.plugins = []def add_plugin(self, plugin):if isinstance(plugin, Plugin): # 檢查是否符合接口self.plugins.append(plugin)else:raise TypeError("插件必須實(shí)現(xiàn)Plugin接口")
7.6、抽象基類 vs 接口繼承
- 抽象基類:通過
abc
模塊顯式定義,允許包含抽象方法和具體方法,子類需顯式繼承。- 鴨子類型(Duck Typing):不依賴?yán)^承,只要對象具有相同方法即視為符合接口(如 Python 的
list
和str
都支持__len__
方法)。
選擇建議:
- 需要強(qiáng)制子類實(shí)現(xiàn)接口時,使用抽象基類。
- 追求靈活性和簡潔性時,優(yōu)先使用鴨子類型(如
len(obj)
不關(guān)心obj
是否繼承自某個基類)。
7.7、注意事項(xiàng)
-
抽象基類不能實(shí)例化:
animal = Animal() # 報錯:TypeError: Can't instantiate abstract class Animal with abstract methods speak, move
-
子類可部分實(shí)現(xiàn)抽象方法:
- 若子類未實(shí)現(xiàn)所有抽象方法,則子類仍為抽象基類,無法實(shí)例化。
-
抽象方法可以有默認(rèn)實(shí)現(xiàn):
class Animal(ABC):@abstractmethoddef speak(self):return "默認(rèn)聲音" # 子類可選擇重寫或使用默認(rèn)
7.8、總結(jié)
- 抽象基類是 Python 實(shí)現(xiàn)接口規(guī)范的重要工具,通過
@abstractmethod
強(qiáng)制子類實(shí)現(xiàn)特定方法。- 適用于需要嚴(yán)格控制子類接口的場景(如框架設(shè)計(jì)、插件系統(tǒng))。
- 結(jié)合多態(tài)性,可實(shí)現(xiàn)統(tǒng)一接口調(diào)用不同實(shí)現(xiàn)的靈活架構(gòu)。
????????合理使用抽象基類,能夠提高代碼的可維護(hù)性和可擴(kuò)展性,避免因子類接口不一致導(dǎo)致的問題。
八、裝飾器本質(zhì)
8.1、裝飾器本質(zhì)
????????在 Python 中,@staticmethod
?和?@classmethod
?是內(nèi)置的裝飾器類,用于修改方法的綁定行為。它們的本質(zhì)是將普通方法轉(zhuǎn)換為特殊的描述符(Descriptor)對象,從而改變方法的調(diào)用邏輯。
8.2、@staticmethod
?的實(shí)現(xiàn)原理
1. 工作機(jī)制
? ? @staticmethod
?將方法轉(zhuǎn)換為一個不綁定類或?qū)嵗?/strong>的函數(shù),調(diào)用時不會自動傳遞任何參數(shù)(如?self
?或?cls
)。
示例代碼:
class MyClass:@staticmethoddef static_method(x, y):return x + y# 等價于手動實(shí)現(xiàn)
def static_method(x, y):return x + yclass MyClass:static_method = staticmethod(static_method) # 使用staticmethod類包裝
2. 底層實(shí)現(xiàn)
???staticmethod
?是一個描述符類,實(shí)現(xiàn)了?__get__
?方法,返回原始函數(shù)本身:
class staticmethod:def __init__(self, func):self.func = funcdef __get__(self, instance, owner=None):return self.func # 直接返回原始函數(shù),不綁定任何對象
調(diào)用過程:
obj = MyClass()
obj.static_method(1, 2) # 等價于調(diào)用 MyClass.static_method(1, 2)
8.3、@classmethod
?的實(shí)現(xiàn)原理
1. 工作機(jī)制
@classmethod
?將方法轉(zhuǎn)換為一個綁定到類的方法,調(diào)用時自動傳遞類本身作為第一個參數(shù)(cls
)。
示例代碼:
class MyClass:@classmethoddef class_method(cls, x, y):return cls(x, y) # 創(chuàng)建類的實(shí)例# 等價于手動實(shí)現(xiàn)
def class_method(cls, x, y):return cls(x, y)class MyClass:class_method = classmethod(class_method) # 使用classmethod類包裝
2. 底層實(shí)現(xiàn)
classmethod
?也是一個描述符類,其?__get__
?方法返回一個綁定了類的函數(shù):
調(diào)用過程:
obj = MyClass()
obj.class_method(1, 2) # 等價于調(diào)用 MyClass.class_method(MyClass, 1, 2)
8.4、對比與驗(yàn)證
1. 驗(yàn)證描述符行為
通過查看方法的類型驗(yàn)證它們是描述符:
class MyClass:def instance_method(self):pass@staticmethoddef static_method():pass@classmethoddef class_method(cls):passprint(type(MyClass.instance_method)) # <class 'function'>(普通函數(shù),是描述符)
print(type(MyClass.static_method)) # <class 'function'>(靜態(tài)方法,是描述符)
print(type(MyClass.class_method)) # <class 'method'>(類方法,已綁定)obj = MyClass()
print(type(obj.instance_method)) # <class 'method'>(實(shí)例方法,已綁定)
print(type(obj.static_method)) # <class 'function'>(靜態(tài)方法,未綁定)
print(type(obj.class_method)) # <class 'method'>(類方法,已綁定)
2. 手動實(shí)現(xiàn)裝飾器
使用自定義描述符模擬?@classmethod
?的行為:
class MyClassMethod:def __init__(self, func):self.func = funcdef __get__(self, instance, owner=None):if owner is None:owner = type(instance)def wrapper(*args, **kwargs):return self.func(owner, *args, **kwargs)return wrapper# 使用自定義裝飾器
class MyClass:@MyClassMethod # 等價于 @classmethoddef create(cls, value):return cls(value)obj = MyClass.create(42) # 自動傳遞 MyClass 作為第一個參數(shù)
8.5、應(yīng)用場景與優(yōu)勢
1.?@staticmethod
?的優(yōu)勢
- 減少命名空間污染:將工具函數(shù)封裝在類內(nèi)部,避免全局函數(shù)。
- 提高代碼內(nèi)聚性:相關(guān)功能集中在類中,便于維護(hù)。
示例1:
class MathUtils:@staticmethoddef is_prime(n):if n < 2:return Falsefor i in range(2, int(n**0.5) + 1):if n % i == 0:return Falsereturn True
示例2:
class StaticMethod:def __init__(self, func):self.func = func # 存儲原始函數(shù)def __get__(self, instance, owner=None):"""描述符協(xié)議:返回原始函數(shù),不綁定任何對象"""return self.func # 直接返回原始函數(shù),不傳遞self或cls
2.?@classmethod
?的優(yōu)勢
- 工廠方法:創(chuàng)建實(shí)例的替代構(gòu)造函數(shù)(如從配置文件創(chuàng)建)。
- 修改類狀態(tài):直接操作類變量,不依賴實(shí)例。
示例1:
class Person:count = 0def __init__(self, name):self.name = namePerson.count += 1@classmethoddef get_count(cls):return cls.count@classmethoddef create_from_dict(cls, data):return cls(data["name"])
示例2:
class ClassMethod:def __init__(self, func):self.func = func # 存儲原始函數(shù)def __get__(self, instance, owner=None):"""描述符協(xié)議:返回綁定類的方法"""if owner is None:owner = type(instance)def bound_method(*args, **kwargs):"""綁定類的方法,自動將類作為第一個參數(shù)傳遞"""return self.func(owner, *args, **kwargs)return bound_method # 返回綁定類的方法
8.6、總結(jié)
@staticmethod
?通過描述符機(jī)制將方法轉(zhuǎn)換為普通函數(shù),調(diào)用時不傳遞任何隱式參數(shù)。@classmethod
?通過描述符機(jī)制將方法綁定到類,調(diào)用時自動傳遞類作為第一個參數(shù)。- 兩者本質(zhì)上都是通過描述符(Descriptor)協(xié)議實(shí)現(xiàn)的,是 Python 元編程的基礎(chǔ)工具之一。
8.6.?@property的實(shí)現(xiàn)原理
1、@property
?的本質(zhì)
? @property
?是 Python 內(nèi)置的數(shù)據(jù)描述符,用于將方法轉(zhuǎn)換為可像屬性一樣訪問的特殊對象。它允許定義只讀屬性、讀寫屬性和刪除屬性,并在訪問時自動執(zhí)行相應(yīng)的方法。
核心功能:
- 攔截屬性的訪問(
getter
)、設(shè)置(setter
)和刪除(deleter
)操作。- 隱藏內(nèi)部實(shí)現(xiàn)細(xì)節(jié),提供簡潔的屬性接口。
2、描述符協(xié)議與?@property
@property
?的實(shí)現(xiàn)基于描述符協(xié)議的三個核心方法:
__get__(self, instance, owner)
:獲取屬性值時調(diào)用。__set__(self, instance, value)
:設(shè)置屬性值時調(diào)用。__delete__(self, instance)
:刪除屬性時調(diào)用。
簡化版?property
?描述符實(shí)現(xiàn):
class Property:def __init__(self, fget=None, fset=None, fdel=None, doc=None):self.fget = fget # 獲取屬性的方法self.fset = fset # 設(shè)置屬性的方法self.fdel = fdel # 刪除屬性的方法self.__doc__ = doc # 文檔字符串def __get__(self, instance, owner=None):if instance is None:return self # 通過類訪問時返回描述符本身if self.fget is None:raise AttributeError("屬性不可讀")return self.fget(instance) # 調(diào)用獲取方法,傳遞實(shí)例def __set__(self, instance, value):if self.fset is None:raise AttributeError("屬性不可寫")self.fset(instance, value) # 調(diào)用設(shè)置方法,傳遞實(shí)例和值def __delete__(self, instance):if self.fdel is None:raise AttributeError("屬性不可刪除")self.fdel(instance) # 調(diào)用刪除方法,傳遞實(shí)例def getter(self, fget):"""創(chuàng)建一個新的property實(shí)例,更新fget方法"""return type(self)(fget, self.fset, self.fdel, self.__doc__)def setter(self, fset):"""創(chuàng)建一個新的property實(shí)例,更新fset方法"""return type(self)(self.fget, fset, self.fdel, self.__doc__)def deleter(self, fdel):"""創(chuàng)建一個新的property實(shí)例,更新fdel方法"""return type(self)(self.fget, self.fset, fdel, self.__doc__)
3、@property
?的工作流程
1. 基本用法
class Person:def __init__(self, age):self._age = age # 私有屬性@propertydef age(self):"""獲取年齡的方法(getter)"""return self._age@age.setterdef age(self, value):"""設(shè)置年齡的方法(setter)"""if value < 0:raise ValueError("年齡不能為負(fù)數(shù)")self._age = value
2. 等價的手動實(shí)現(xiàn)
class Person:def __init__(self, age):self._age = agedef get_age(self):return self._agedef set_age(self, value):if value < 0:raise ValueError("年齡不能為負(fù)數(shù)")self._age = value# 使用自定義Property描述符age = Property(get_age, set_age)
4、描述符如何攔截屬性訪問
????????當(dāng)通過實(shí)例訪問被?@property
?裝飾的屬性時,Python 會自動觸發(fā)描述符的?__get__
、__set__
?或?__delete__
?方法:
1. 獲取屬性
p = Person(25)
print(p.age) # 觸發(fā) Property.__get__(p, Person)
2. 設(shè)置屬性
p.age = 30 # 觸發(fā) Property.__set__(p, 30)
3. 刪除屬性
del p.age # 觸發(fā) Property.__delete__(p)
5、驗(yàn)證描述符行為
通過自定義描述符驗(yàn)證?@property
?的行為:
class DebugProperty:def __init__(self, fget=None):self.fget = fgetdef __get__(self, instance, owner=None):print(f"獲取屬性(instance={instance}, owner={owner})")return self.fget(instance) if instance else selfdef setter(self, fset):self.fset = fsetreturn selfdef __set__(self, instance, value):print(f"設(shè)置屬性為 {value}")if not hasattr(self, 'fset'):raise AttributeError("屬性不可寫")self.fset(instance, value)# 使用自定義描述符
class Circle:def __init__(self, radius):self._radius = radius@DebugProperty # 等價于@propertydef radius(self):return self._radius@radius.setterdef radius(self, value):self._radius = value# 驗(yàn)證
c = Circle(5)
print(c.radius) # 輸出:獲取屬性(instance=<__main__.Circle object...>, owner=<class '__main__.Circle'>) 5
c.radius = 10 # 輸出:設(shè)置屬性為 10
6、@property
?的優(yōu)勢
-
接口一致性:通過屬性語法訪問,隱藏方法調(diào)用的復(fù)雜性。
# 無@property print(p.get_age()) # 方法調(diào)用# 有@property print(p.age) # 屬性訪問
-
數(shù)據(jù)驗(yàn)證:在 setter 中添加邏輯,確保數(shù)據(jù)合法性。
@age.setter def age(self, value):if value < 0:raise ValueError("年齡不能為負(fù)數(shù)")self._age = value
-
計(jì)算屬性:動態(tài)生成屬性值,無需存儲中間結(jié)果。
@property def area(self):return 3.14 * self._radius ** 2 # 每次訪問時計(jì)算
7、總結(jié)
@property
?是數(shù)據(jù)描述符:通過實(shí)現(xiàn)?__get__
、__set__
?和?__delete__
?方法,攔截屬性的訪問、設(shè)置和刪除操作。- 裝飾器語法糖:
@property
?和?@attr.setter
?實(shí)際上是創(chuàng)建和修改描述符實(shí)例的過程。- 描述符優(yōu)先級:數(shù)據(jù)描述符(如?
@property
)的優(yōu)先級高于實(shí)例字典,確保屬性訪問被正確攔截。
????????這些示例涵蓋了Python面向?qū)ο缶幊痰暮诵母拍?#xff0c;包括類定義、繼承、多態(tài)、特殊方法、屬性訪問控制、靜態(tài)和類方法,以及抽象基類等。