剛開始學習python的時候或者其他物件導向的程式語言的時候,難免會對類和物件理解得不太清楚。所以今天和大家分享下python中的類和物件,深入理解下python中的類和物件。
1.鴨子型別
當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子。這個就是鴨子型別的定義,在python中,並不關心這個物件是什麼型別,只關心他的行為。由行為來推斷出該物件所屬於的型別。就比如列表(list)、元組(tuple)、字典(dict)等等,這些類都是可迭代的,所以說他們是可迭代物件。
from collections import Iterable
l = [1, ]
t = (1, )
d = {`d`: 3}
print(isinstance(l, Iterable))
print(isinstance(t, Iterable))
print(isinstance(d, Iterable))
# 結果
True
True
True複製程式碼
2.類變數和例項變數
類變數就是在類內定義的,但是不在方法內定義的,而且字首無self作為引用。例項變數就是有self作為引用的存在類中的變數。類變數是所有物件共享的,在類中修改時,其他的物件也會跟著變。但是需要注意的是,如果是用物件來引用類變數進行修改的話,這裡只是新建了和類變數同名的例項變數,並沒有修改到。下面用程式碼解釋下。
class Student(object):
conutry = `China` # 這個是類變數
def __init__(self, name, sex):
self.name = name # 這個是例項變數,也就是物件變數
self.sex = sex # 物件變數
s1 = Student(`張三`, `man`)
s2 = Student(`里斯`, `woman`)
print(s1.conutry)
print(s2.conutry)
print(Student.conutry)複製程式碼
上面的結果都是三個China,這個很容易知道,用類來引用改變時
Student.conutry = `cn` # 這個是用類引用來進行修改
複製程式碼
修改後列印下三個結果都是修改後的結果。但是下面這個呢?
s1.conutry = `zhongguo` # 用例項來引用進行修改
這次結果就不一樣了,只有s1的類變數變了,其他兩個都是不變的。這是為什麼呢?就如上面所說,用例項引用來修改類變數的時候並不是修改,而是新建了這個變數。又由於python查詢變數是由下往上查詢的,所以會先查詢出新建後的變數。
3.類屬性和例項屬性之間的訪問順序
類屬性就是定義在類中的方法和變數,例項屬性也是一樣的。訪問順序就是由下往上查詢的,用程式碼體會一下。
class A():
name = `A`
def __init__(self):
self.name = `a`
a = A()
print(a.name)
# 結果
a複製程式碼
由於是類變數先載入,再到初始化物件,所以才會執行__init__()方法,所以結果很顯然就是a。這裡由於該類沒有繼承,就沒有很複雜,但是當出現多繼承,幾個類之間就變得很複雜,這個時候的訪問順序就難多了。下面說下這兩種情況,掌握了這兩種情況,其他的基本沒有問題了。
(1.適合深度優先查詢
A繼承了B,C,B,C分別繼承了D,E。深度優先的查詢是先去著A,如果A中沒有該屬性,就去B著,再沒有就去D找。D中找不到了再去C找。這種查詢情況是沒有問題的,但是另一種情況就不合適了。
2)適合廣度優先查詢
這個是A繼承了B,C,B,C都繼承了D。如果這個用深度優先的演算法的話,就會出現一個問題,因為深度優先查詢順序是A->B->D->C。這個是不太合理的,當C中過載了D中的一個方法後,B沒有過載,如果要查詢C中的該方法,用深度優先的演算法就只能找到D中的原始方法,所以說這就不正確了,這時候就需要用廣度優先的 演算法,這個時候查詢順序就是A->B->C->D。但是當遇到上面的情況時又會出錯了。這時怎麼辦?python3就將所有的屬性搜尋演算法統一成了一個演算法:C3演算法,這裡就不展開說這個演算法了,因為太複雜了:)會根據對應情況而實施對應演算法,下面用程式碼來分別體會下以上兩種情況
class E():
pass
class D():
pass
class C(E):
pass
class B(D):
pass
class A(B, C):
pass
print(A.__mro__)
# 結果
(<class `__main__.A`>, <class `__main__.B`>, <class `__main__.D`>, <class `__main__.C`>, <class `__main__.E`>, <class `object`>)複製程式碼
__mro__這個屬性是獲取屬性的查詢順序,可以看到就是和我們上面說的一樣,用了深度優先的演算法。再看另一個
class D():
pass
class C(D):
pass
class B(D):
pass
class A(B, C):
pass
print(A.__mro__)
# 結果
(<class `__main__.A`>, <class `__main__.B`>, <class `__main__.C`>, <class `__main__.D`>, <class `object`>)複製程式碼
這個時候就用了廣度優先演算法,符合與我們剛才所說的,這就是C3演算法了哈。
4.super真的是呼叫父類嗎?
學過Java的都知道,super()這個方法就是在呼叫父類的方法,但是在python中就不一定了。我們先看看super的用法
class A():
def __init__(self):
print(`A`)
class B(A):
def __init__(self):
# python2做法是
# super(A, self).__init__()
super().__init__() # 呼叫父類的初始化方法
print(`B`)
b = B()
# 結果
A
B複製程式碼
上面就是用法了,python2和python3用法不一樣,這裡我們就只用python3了就行操作。接下來看看super真正的呼叫情況。
class A():
def __init__(self):
print(`A`)
class B(A):
def __init__(self):
super().__init__()
print(`B`)
class C(A):
def __init__(self):
super().__init__()
print(`C`)
class D(B, C):
def __init__(self):
super().__init__()
print(`D`)
d = D()複製程式碼
上面這個是我們之前所說的那個適合廣度優先演算法的多繼承,按照我們之前的理解,super呼叫的是父函式,那麼這個結果就會是:
A B C D
顯然是錯誤,結果是這個
是不是覺得很奇怪,但是又很熟悉?是的,這個也是按照剛才的查詢順序一樣執行的,如果不信的話我們列印下__mro__就知道了
是不是剛好倒敘?因為我們是先列印父類的再列印自己的,所以順序倒了。再看看另外一種情況也是可行的
class A():
def __init__(self):
print(`A`)
class B():
def __init__(self):
super().__init__()
print(`B`)
class C(A):
def __init__(self):
super().__init__()
print(`C`)
class D(B):
def __init__(self):
super().__init__()
print(`D`)
class E(D, C):
def __init__(self):
super().__init__()
print(`E`)
e = E()
print(E.__mro__)
# 結果
A
C
B
D
E
(<class `__main__.E`>, <class `__main__.D`>, <class `__main__.B`>, <class `__main__.C`>, <class `__main__.A`>, <class `object`>)複製程式碼
也是和預期一樣的。總的來說,super不一定是呼叫父類,它的呼叫順序也是遵循mro演算法的,就是屬性查詢演算法,和上文說的C3演算法一致。
有任何問題歡迎在留言區提問,或者有不當的地方也歡迎指出
ps:如果覺得文章不錯的話,歡迎隨手點贊轉發支援
日常學python
程式碼不止bug,還有美和樂趣