繼承順序
''' 一點需要注意 ''' class Father: def f1(self): print("test func followed ==>") self.test() def test(self): print("from Father test") class Son(Father): def test(self): print("from son test") res=Son() res.f1()
結果 >>>: test func followed ==> from son test ''' 子類調用 self.test() 的時候任然要重新從自己開始找 .test()方法。 子類調用父類的屬性,每次調用,都是優先從自己這里開始找,按照mro算法的順序依次找下去,知道找到第一個符合的,所以不能只看定義的時候的單個類的情況。 '''
新式類繼承:廣度優先。
經典類繼承:深度優先。
繼承了object的類以及其子類,都是新式類
沒有繼承object的類以及其子類,都是經典類
Python3中默認繼承object,所以Python3中都是新式類
Python2中不會默認繼承object


class A(object): def test(self): print('from A') class B(A): def test(self): print('from B') class C(A): def test(self): print('from C') class D(B): def test(self): print('from D') class E(C): def test(self): print('from E') class F(D,E): # def test(self): # print('from F') pass f1=F() f1.test() print(F.__mro__) #只有新式才有這個屬性可以查看線性列表,經典類沒有這個屬性 #新式類繼承順序:F->D->B->E->C->A #經典類繼承順序:F->D->B->A->E->C #python3中統一都是新式類 #pyhon2中才分新式類與經典類 繼承順序
繼承原理(python如何實現的繼承)
python到底是如何實現繼承的,對於你定義的每一個類,python會計算出一個方法解析順序(MRO)列表,這個MRO列表就是一個簡單的所有基類的線性順序列表,例如
>>> F.mro() #等同於F.__mro__ [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
為了實現繼承,python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類為止。
而這個MRO列表的構造是通過一個C3線性化算法來實現的。我們不去深究這個算法的數學原理,它實際上就是合並所有父類的MRO列表並遵循如下三條准則:
1.子類會先於父類被檢查
2.多個父類會根據它們在列表中的順序被檢查
3.如果對下一個類存在兩個合法的選擇,選擇第一個父類
子類調用父類的方法(內置函數super)
low版調用方法,還是那個teacher還是那個people:
1 class People:
2 def __init__(self,name,age,sex):
3 self.name=name
4 self.age=age
5 self.sex=sex
6 def foo(self):
7 print('from parent')
8
9 class Teacher(People):
10 def __init__(self,name,age,sex,salary,level):
11 People.__init__(self,name,age,sex) #指名道姓地調用People類的__init__函數
12 self.salary=salary
13 self.level=level
14 def foo(self):
15 print('from child')
16
17 t=Teacher('bob',18,'male',3000,10)
18 print(t.name,t.age,t.sex,t.salary,t.level)
19 t.foo()
low版調用方法,在更改父類的名字之后,需要改動的地方除了子類繼承的父類名字,還要改子類里面調用的父類名,比較麻煩
高端大氣調用方式:只需要改動子類繼承的父類名,即括號里的父類名字
1 class People:
2 def __init__(self,name,age,sex):
3 self.name=name
4 self.age=age
5 self.sex=sex
6 def foo(self):
7 print('from parent')
8
9 class Teacher(People):
10 def __init__(self,name,age,sex,salary,level):
11 #在python3中
12 super().__init__(name,age,sex) #調用父類的__init__的功能,實際上用的是綁定方法,用到了mro表查詢繼承順序,只能調用一個父類的功能
13 #在python2中
14 # super(Teacher,self).__init__(name,age,sex) #super(Teacher,self)是一個死格式
15 self.salary=salary
16 self.level=level
17 def foo(self):
18 super().foo()
19 print('from child')
20
21 t=Teacher('bob',18,'male',3000,10)
22 print(t.name,t.age,t.sex,t.salary,t.level)
23 t.foo()
但是這種方式也有一個缺點,就是當一個子類繼承了多個父類的時候,如果多個父類都包含了相同的屬性名,當要調用該功能的時候,只能調用第一個父類的功能,無法實現多個父類同時調用。多個父類同時調用還是要用low版方法。
訪問限制
在Class內部,可以有屬性和方法,而外部代碼可以通過直接調用實例變量的方法來操作數據,這樣,就隱藏了內部的復雜邏輯。
但是,從前面Student類的定義來看,外部代碼還是可以自由地修改一個實例的name、score屬性:
>>> bart = Student('Bart Simpson', 98) >>> bart.score 98 >>> bart.score = 59 >>> bart.score 59
如果要讓內部屬性不被外部訪問,可以把屬性的名稱前加上兩個下划線__,在Python中,實例的變量名如果以__開頭,就變成了一個私有變量(private),只有內部可以訪問,外部不能訪問,所以,我們把Student類改一改:
class Student(object): def __init__(self, name, score): self.__name = name self.__score = score def print_score(self): print('%s: %s' % (self.__name, self.__score))
改完后,對於外部代碼來說,沒什么變動,但是已經無法從外部訪問實例變量.__name和實例變量.__score了:
>>> bart = Student('Bart Simpson', 98)
>>> bart.__name
Traceback (most recent call last):
File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute '__name'
這樣就確保了外部代碼不能隨意修改對象內部的狀態,這樣通過訪問限制的保護,代碼更加健壯。
需要注意的是,在Python中,變量名類似__xxx__的,也就是以雙下划線開頭,並且以雙下划線結尾的,是特殊變量,特殊變量是可以直接訪問的,不是private變量,所以,不能用__name__、__score__這樣的變量名。
有些時候,你會看到以一個下划線開頭的實例變量名,比如_name,這樣的實例變量外部是可以訪問的,但是,按照約定俗成的規定,當你看到這樣的變量時,意思就是,“雖然我可以被訪問,但是,請把我視為私有變量,不要隨意訪問”。
查看__dict__可以看出,Python實際上是在類、對象,在定義的時候,將這類變量轉換成了類似 _Student__name 的格式

不能直接訪問__name是因為Python解釋器對外把__name變量改成了_Student__name,所以,仍然可以通過_Student__name來訪問__name變量:
>>> bart._Student__name 'Bart Simpson'
但是強烈建議你不要這么干,因為不同版本的Python解釋器可能會把__name改成不同的變量名。
總的來說就是,Python本身沒有任何機制阻止你干壞事,一切全靠自覺。
最后注意下面的這種錯誤寫法:
>>> bart = Student('Bart Simpson', 98) >>> bart.get_name() 'Bart Simpson' >>> bart.__name = 'New Name' # 設置__name變量! >>> bart.__name 'New Name'
表面上看,外部代碼“成功”地設置了__name變量,但實際上這個__name變量和class內部的__name變量不是一個變量!內部的__name變量已經被Python解釋器自動改成了_Student__name,而外部代碼給bart新增了一個__name變量。不信試試:
>>> bart.get_name() # get_name()內部返回self.__name 'Bart Simpson'
但是如果外部代碼要獲取name和score怎么辦?可以給Student類增加get_name和get_score這樣的方法:
class Student(object): ... def get_name(self): return self.__name def get_score(self): return self.__score
如果又要允許外部代碼修改score怎么辦?可以再給Student類增加set_score方法:
class Student(object): ... def set_score(self, score): self.__score = score
你也許會問,原先那種直接通過bart.score = 59也可以修改啊,為什么要定義一個方法大費周折?因為在方法中,可以對參數做檢查,避免傳入無效的參數:
class Student(object): ... def set_score(self, score): if 0 <= score <= 100: self.__score = score else: raise ValueError('bad score')
但是這樣來更改一個屬性還是顯得比較詭異,因為一個原本 self.name=[名字] 的操作,變成了一個函數來操作。
但是你任然可以將隱藏的屬性偽裝成一個正常屬性,類的內部對這個變量的操作進行限制,或者參數檢查等功能。
class People: def __init__(self,name,permission=False): self.__name=name self.permission=permission @property #將 self.name() 變成了 self.name def name(self): return self.__name @name.setter #讓 self.name 可以像正常屬性一樣可以用‘=’操作設置屬性的新值 self.name='[新值]' def name(self,val): if not isinstance(val,str): raise TypeError('must be str') #raise 定義錯誤信息 self.__name=val @name.deleter #可以 del self.name 刪除屬性 def name(self): if not self.permission: raise PermissionError('不讓刪') del self.__name snow=People('***snow***') print(snow.name) print(snow.permission) snow.permission=True del snow.name print(snow.name)
class Foo: def __init__(self,val): self.__NAME=val #將所有的數據屬性都隱藏起來 def getname(self): return self.__NAME #obj.name訪問的是self.__NAME(這也是真實值的存放位置) def setname(self,value): if not isinstance(value,str): #在設定值之前進行類型檢查 raise TypeError('%s must be str' %value) self.__NAME=value #通過類型檢查后,將值value存放到真實的位置self.__NAME def delname(self): raise TypeError('Can not delete') name=property(getname,setname,delname) #不如裝飾器的方式清晰 一種property的古老用法
綁定方法與非綁定方法
類中定義的函數分成兩大類:
一:綁定方法(綁定給誰,誰來調用就自動將它本身當作第一個參數傳入):
1. 綁定到類的方法:用classmethod裝飾器裝飾的方法。
為類量身定制
類.boud_method(),自動將類當作第一個參數傳入
(其實對象也可調用,但仍將類當作第一個參數傳入)
2. 綁定到對象的方法:沒有被任何裝飾器裝飾的方法。
為對象量身定制
對象.boud_method(),自動將對象當作第一個參數傳入
(屬於類的函數,類可以調用,但是必須按照函數的規則來,沒有自動傳值那么一說)
二:非綁定方法:用staticmethod裝飾器裝飾的方法
1. 不與類或對象綁定,類和對象都可以調用,但是沒有自動傳值那么一說。就是一個普通工具而已
注意:與綁定到對象方法區分開,在類中直接定義的函數,沒有被任何裝飾器裝飾的,都是綁定到對象的方法,可不是普通函數,對象調用該方法會自動傳值,而staticmethod裝飾的方法,不管誰來調用,都沒有自動傳值一說
1 staticmethod
statimethod不與類或對象綁定,誰都可以調用,沒有自動傳值效果,python為我們內置了函數staticmethod來把類中的函數定義成靜態方法
import hashlib
import time
class MySQL:
def __init__(self,host,port):
self.id=self.create_id()
self.host=host
self.port=port
@staticmethod
def create_id(): #就是一個普通工具
m=hashlib.md5(str(time.clock()).encode('utf-8'))
return m.hexdigest()
print(MySQL.create_id) #<function MySQL.create_id at 0x0000000001E6B9D8> #查看結果為普通函數
conn=MySQL('127.0.0.1',3306)
print(conn.create_id) #<function MySQL.create_id at 0x00000000026FB9D8> #查看結果為普通函數
2 classmethod
classmehtod是給類用的,即綁定到類,類在使用時會將類本身當做參數傳給類方法的第一個參數(即便是對象來調用也會將類當作第一個參數傳入),python為我們內置了函數classmethod來把類中的函數定義成類方法
import settings
import hashlib
import time
class MySQL:
def __init__(self,host,port):
self.host=host
self.port=port
@classmethod
def from_conf(cls):
print(cls)
return cls(settings.HOST,settings.PORT)
print(MySQL.from_conf) #<bound method MySQL.from_conf of <class '__main__.MySQL'>>
conn=MySQL.from_conf()
print(conn.host,conn.port)
conn.from_conf() #對象也可以調用,但是默認傳的第一個參數仍然是類

