python入門系列:面向對象
類
經典類 沒有繼承 object的類
新式類 繼承了 object的類
class Money: # 2.x中默認是經典類,3.x中是新式類
pass
class Money(object): # 兼容的一種寫法
pass
Money既是類的name屬性名,又是一個引用該類的變量
print(Money.name) # Money
xxx = Money
print(xxx.name) # Money
對象
one = Money()
print(one) # <main.Money object at 0x000001555E9534A8>
print(one.class) # <class ‘main
屬性相關
對象屬性
class Person:
pass
p = Person()
給 p對象增加屬性, 所有的屬性是以字典的形式組織的
p.age = 18
print(p.age) # 18
print(p.dict) # {‘age‘: 18}
print(p.sex) # AttributeError: ‘Person‘ object has no attribute ‘sex‘
刪除p對象的屬性
del p.age
print(p.age) # AttributeError: ‘Person‘ object has no attribute ‘age‘
類屬性
class Money:
count = 1
type = "rmb"
print(Money.num) # 666
對象查找屬性,先到對象自身去找,若未找到,根據 class找到對應的類,然後去類中查找
one = Money()
print(one.count) # 1
不能通過對象去 修改/刪除 對應類的屬性
one.num = 555 # 實際上是給 one 對象增加了一個屬性
print(Money.num) # 666
print(one.num) # 555
類屬性會被各個對象共享
two = Money()
print(one.num, two.num) # 666 666
Money.num = 555
限制對象的屬性添加
類中的 slots屬性定義了對象可以添加的所有屬性
class Person:
slots = ["age"] # 只允許添加一個 age屬性
p1 = Person()
p1.age = 1
p1.num = 2 # AttributeError: ‘Person‘ object has no attribute ‘num‘
私有化屬性
Python沒有真正的私有化支持,只能用給變量添加下劃線來實現偽私有;通過名字重整機制
屬性的訪問範圍:類的內部-->子類內部-->模塊內的其他位置-->其他模塊
公有屬性 x 的訪問範圍
類的內部
子類內部
模塊內的其他位置
子類內部
受保護屬性 _x 的訪問範圍
類的內部
子類內部
模塊內的其他位置(但不推薦)
子類內部(from ... import xxx 不可以訪問,要指明all變量)
私有屬性 __x 的訪問範圍
類的內部
子類內部
模塊內的其他位置
子類內部(同_x)
保護數據案例
class Person:
def init(self):
self.__age = 18
def set_age(self, age): # 錯誤數據的過濾
if isinstance(age, int) and 0 < age < 150:
self.__age = age
else:
print("Wrong age value")
def get_age():
return self.__age
p = Person()
print(p.get_age()) # 18
p.set_age(22)
print(p.get_age()) # 22
只讀屬性
1. 屬性私有化 + 屬性化 get()方法
class Person(object):
def init(self):
self.__age = 18
可以以使用屬性的方式來使用方法
@property
def age(self):
return self.__age
p = Person()
print(p.age) # 18
p.age = 666 # Attribute Error: can‘t set attribute
2. 通過底層的一些函數
class Person:
通過 屬性 = 值 的方式來給一個對象增加屬性時,底層都會調用這個方法,構成鍵值對,存儲在 dict字典中
可以考慮重寫底層的這個函數,達到只讀屬性的目的
def setattr(self, key, value):
if key == "age" and key in dict:
print("read only attribute")
else:
self.dict[key] = value
方法相關
方法的劃分
實例方法
類方法
靜態方法
class Person:
def instance_fun(self): # self: 調用對象的本身,調用時不用寫,解釋器會傳參
print("instance method", self)
@classmethod
def class_fun(cls): # cls: 類本身
print("class method", cls)
@staticmethod
def static_fun():
print("static method")
所有的方法都存儲在類中,實例中不存儲方法
類方法和靜態方法無法訪問實例屬性
方法的私有化
和變量的私有化思想差不多
class Person:
__age = 18
def __run(self): # 只能在該類中被調用
print("running...")
元類
創建類對象的類(類也是一個對象)
a, s = 8, "123"
print(a.class, s.class) # <class ‘int‘> <class ‘str‘>
print(int.class, str.class) # <class ‘type‘> <class ‘type‘>
type是元類。
通過type元類來創建類,動態創建。也可以用metaclass來指明元類,進行類的創建。
檢測類對象中是否有 metaclass屬性
檢測父類中是否有 metaclass屬性
檢測模塊中是否有 metaclass屬性
通過內置的type來創建類
def run(self):
print("run...")
Dog = type("Dog", (), {"count": 0, "run": run})
print(Dog) #
d = Dog()
print(d.count) # 0
print(d.run()) # run...
更加詳細的內容,在進高級部分的元類編程講解
內置的特殊屬性
內置的特殊方法(魔法函數)
這裏只做一個了解,高級部分會詳細地講解魔法函數。可以理解為在類中實現了這些特殊的函數,類產生的對象可以具有神奇的語法效果。
信息格式化操作
calss Person:
def init(self, n, a):
self.name = n
self.age = a
面向用戶
def str(self):
return "name: %s, age: %d" % (self.name, self.age)
面向開發人員
def repr(self):
todo
一般默認給出對象的類型及地址信息等
打印或進行格式轉換時,先調用 str()函數,若未實現,再調用 repr()函數
p = Person("Rity", 18)
print(p) # name: Rity, age: 18
res = str(p)
print(res) # name: Rity, age: 18
print(repr(p)) # <main.Person object at 0x000001A869BEB470>
調用操作
使得一個對象可以像函數一樣被調用
class PenFactory:
def init(self, type):
self.type = type
def call(self, color):
print("get a new %s, its color is %s" % (self.type, color))
pencil = PenFactory("pencil")
pen = PenFactory("pen")
一下兩種使用方式會調用 call()函數
pencil("red") # get a new pencil, ites color is red
pencil("blue") # get a new pencil, ites color is blue
pen("black") # get a new pen, ites color is black
索引操作
class Person:
def init(self):
self.cache = {}
def setitem(self, key, value):
self.cache[key] = value
def getitem(self, key):
return self.cache[key]
def delitem(self, key):
del self.cache[key]
p = Person()
p["name"] = "MetaTian"
...
比較操作
使得自己定義的類可以按照一定的規則進行比較
import [email protected]_ordering
br/>@functools.total_ordering
def init(self, age, height):
self.age = age
self.height = height
def eq(self, other): # ==
return self.age == other.age
def lt(self, ohter): # <
return self.age < other.age
a1, a2 = A(18, 170), A(19, 178)
因為邏輯具有相反性,當使用 > 時,首先會查找 gt()函數,若未定義,將參數交換後調用 lt()方法
由 == 和 < 可以組合出其他的所有比價邏輯,使用裝飾器可以自動生成其他邏輯對應的函數,簡化代碼
print(a1 < a2) # True
print(a2 > a1) # True
print(a1 >= a2) # False
描述器
描述器是一個對象,用來描述其他對象屬性的操作;作用是對屬性的操作做驗證和過濾。
前面只讀屬性案例中就是用到了描述器。
在對象的內部增加一個描述器,可以接管對象屬性的增刪改查操作。
class Age:
def get(self, instance, owner): # instance是擁有 age 屬性的對象
pass
def set(self, instance, value):
instance.v = value # 將變量的值綁定在 Person 的實例中
def delete(self, instance):
pass
class Person:
age = Age()
age實例是 p1和 p2兩個對象所共享的,所以 Age 對象及實例不應該具有屬性,只單純地提供方法即可
p1 = Person()
p1.age = 19 # 調用 set()
print(p1.age) # 調用 get()
p2 = Person()
p2.age = 20
print(p2.age) # 20
資料描述器和非資料描述器
也可以稱為數據描述器和非數據描述器
資料描述器:實現了get() 和 set()
非資料描述器:僅僅實現了get()
實例屬性和描述器重名時,操作的優先級關系: 資料描述器 > 實例屬性 > 非資料描述器
生命周期
用來表示一個對象從創建到釋放的過程
class Person:
__count = 0
def init(self):
Person.__count += 1
def del(self):
Person.__count -= 1
@classmethod
def log(cls):
print("we have %d people" % cls.__count)
p1 = Person()
Person.log() # we have 1 people
p2 = Person()
Person.log() # we have 2 people
內存管理機制
引言
萬物皆對象,不存在基本數據類型
在 [-5, 正無窮) 範圍內相等的整數和短小的字符串,Python會進行緩存,不會創建多個對象
n1 = 1
n2 = 1
print(id(n1), id(n2)) # 1708655056 1708655056
容器對象:存儲的對象,僅僅是其他對象的引用(列表)
內存回收
引用計數
一個對象會記錄著自身被引用的個數
每增加一個引用,引用數+1,減少一個引用,引用數-1
引用數為0的時候,會被當做垃圾進行回收
會出現兩個對象循環引用的問題
垃圾回收
從經歷過引用計數機制但仍然未被釋放的對象中,進行內存釋放
新增的對象個數 - 消亡對象的個數達到一定閾值時才進行垃圾檢測
分代回收
分代回收是垃圾回收的高效解決方案,不需頻繁地進行垃圾檢測
存活時間越久的對象,越不可能在後面的過程中變成垃圾
設立0, 1, 2三代對象集合,對其中的對象進行不同頻率的檢測
第一次檢測存活下來的,從0代納入1代,0代檢測一定次數後開始檢測1代,以此類推
深拷貝和淺拷貝
淺拷貝
a = [1, 2, 3]
b = a
print(id(a), id(b)) # 2229855665608 2229855665608
深拷貝
import copy
a = [1, 2, 3]
c = copy.deepcopy(a)
print(id(a), id(c)) # 2229855665608 2229861709896
copy和deepcopy的區別
import copy
a = [1, 2, 3]
b = [4, 5, 6]
c = [a, b]
d = copy.deepcopy(c)
e = copy.copy(c)
使用copy拷貝可變類型時,進行單層次的深拷貝,若拷貝的是不可變類型,則進行淺拷貝。
面向對象三大特性
封裝
繼承
非私有的屬性和方法可以被繼承,繼承不是拷貝了資源,而是具有了資源的訪問權,資源的存儲位置在父類中,實現資源重用。
Python中可以使用多繼承。
所有的類都繼承了 object 類
所有的類對象都由 type 實例化出來
class A:
pass
class B:
pass
class C(A, B): # C 類繼承了 A和 B類
pass
print(C.bases) # (<class ‘main.A‘>, <class ‘main.B‘>)
print(int.base) # <class ‘object‘>
print(bool.base) # <class ‘int‘>
幾種繼承的形式
資源查找順序
單繼承鏈:C-->B-->A
無重疊多繼承鏈:按照單繼承鏈深度優先查找(C-->B-->A-->D-->E)
有重疊多繼承鏈:廣度優先查找(C-->B-->D-->A)
資源覆蓋
在優先級較高的類中重新定義了同名的屬性和方法,再次調用時,會調用到優先級較高類中的資源,並不是相關的資源在內存上被覆蓋了,而是調用優先級出現了變化
self 和 cls
誰調用方法,self 和 cls 就是誰
帶著參數去找方法
class A:
def show(self):
print(self)
@classmethod
def tell(cls):
print(cls)
class B(A):
pass
B.tell() # <class ‘main.B‘>
B().show() # <main.B object at 0x027674D0>
資源的累加
class A:
def init(self):
self.x = 2
class B(A):
pass
class C(A):
def init(self):
self.y = 1
class D(A):
def init(self):
self.y = 1
class E(A):
def init(self):
super().init() # 會調用 A的構造函數,參數可以省略
A.init(self) //和上面等價,要傳參
self.y = 1
b = B()
print(b.x) # 2, 調用了父類的構造函數,b 調用,x就是 b的
c = C()
print(c.y) # 1, C有了構造函數,就調用 C的,A的構造函數不會被調用
print(c.x) # 報錯,沒有這個屬性
e = E()
print(e.x, e.y) # 2, 1
多態
Python是動態類型的語言,不需要嚴格意義上的多態
def test(obj):
obj.func()
只要傳入的參數有 func()這個方法,就可以傳入進行執行,不用進行類型檢測。
不需要按照其他靜態語言那樣沿著繼承鏈進行方法調用形成多態
類的設計原則
單一職責原則:一個類只負責一項職責
開放封閉原則:對外擴展開放,對內修改關閉
裏式替換原則:子類所繼承下來的屬性和方法都需能夠合理地使用
接口分離原則:功能一致的方法應該重新組成新的接口/類,進行細分
依賴倒置原則:高層模塊不應該直接依賴低層模塊,核心是面向接口編程
喜歡python + qun:839383765 可以獲取Python各類免費最新入門學習資料!
python入門系列:面向對象