Python(面向對象編程4——繼承順序、封裝)


繼承順序


'''
一點需要注意
'''
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中才分新式類與經典類

繼承順序
View Code

繼承原理(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類的定義來看,外部代碼還是可以自由地修改一個實例的namescore屬性:

>>> 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_nameget_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的古老用法
一種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() #對象也可以調用,但是默認傳的第一個參數仍然是類


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM