第十二篇:python中的面向對象

編程範式

編程是程序員用特定的語法+數據結構+算法組成的代碼來告訴計算機如何執行任務的過程,一個程序是程序員爲了獲得一個任務結果而編寫的一組指令的集合,正所謂條條大路通羅馬,實現一個任務的方式有不少種不一樣的方式或風格,對這些不一樣的編程方式的特色進行概括總結得出來的編程方式類別,即爲編程範式。java

面向過程編程

面向過程編程依賴procedures(過程),一個procedure包含一組要被進行計算的步驟,面向過程又被稱爲top-down languages, 就是程序從上到下一步步執行,一步步從上到下,從頭至尾的解決問題 。基本設計思路就是程序一開始是要着手解決一個大的問題,而後把一個大問題分解成不少個小問題或子過程,這些子過程再執行的過程再繼續分解直到小問題足夠簡單到能夠在一個小步驟範圍內解決。python

面向過程:核心是過程,工廠生產同樣的流水線處理方式,機械式地處理,根據業務邏輯從上到下寫壘代碼,關注的是解決問題的過程。
優勢:複雜問題流程化,進而簡單化
缺點:可擴展性差mysql

people1 = {'name':'bjo','age':19,'sex':''}
print(people1['sex'])
print('{} 在跳躍'.format(people1['name']))
print('{} 正在跑步'.format(people1['name']))
people2 = {'name':'anjo','age':18,'sex':''}
print(people2['sex'])
print('{} 在跳躍'.format(people2['name']))
print('{} 正在跑步'.format(people2['name']))

函數式編程

在維基百科中給出了詳細的定義,函數式編程(英語:functional programming)或稱函數程序設計,又稱泛函編程,是一種編程範型,它將電腦運算視爲數學上的函數計算,而且避免使用程序狀態以及易變對象。函數編程語言最重要的基礎是λ演算(lambda calculus)。並且λ演算的函數能夠接受函數看成輸入(引數)和輸出(傳出值)。數學上的函數是從定義域集合A到值域集合B的映射關係。在編程上則是有返回值的子程序屬於函數式編程。linux

def jump(name):
    return '{} 在跳躍'.format(name)
def runing(name):
    return '{} 正在跑步'.format(people1['name'])
people1 = {'name':'bjo','age':19,'sex':''}
print(people1['sex'])
print(jump(people1['name']))
print(runing(people1['name']))
people2 = {'name':'anjo','age':18,'sex':''}
print(people2['sex'])
print(jump(people2['name']))
print(runing(people2['name']))

面向對象編程

面向對象程序設計(英語:Object-oriented programming,縮寫:OOP)是種具備對象概念的程序編程範型,同時也是一種程序開發的方法。它可能包含數據、屬性、代碼與方法。對象則指的是類的實例。它將對象做爲程序的基本單元,將程序和數據封裝其中,以提升軟件的重用性、靈活性和擴展性,對象裏的程序能夠訪問及常常修改對象相關連的數據。在面向對象程序編程裏,計算機程序會被設計成彼此相關的對象。程序員

面向對象:核心就是對象,對象就是特徵與屬性的結合體,關注的是誰能解決問題,而後把問題交給誰,誰即對象,而不關注如何解決問題過程。
優勢:可擴展性強
缺點:編程複雜度高
應該用場景:用戶需求常常變化的場景 遊戲、互聯網應用、企業內部應用等

 面向對象編程示例算法

def Human(name, age, sex):
    def init(name, age, sex):
        sch = {
            'name': name,
            'age': age,
            'sex': sex,
            'jump': jump,
            'running': running,
        }
        return sch

    def jump(people):
        print('%s 在跳躍' % people['name'])

    def running(people):
        print('%s在跑步' % (people['name']))

    return init(name, age, sex)


people1 = Human('bjo', 19, '')
print(people1)
print(people1['name'])

people1['jump'](people1)

people2 = Human('anjo', 18, '')

print(people2)
print(people2['name'], people2['age'], people2['sex'])
people2['running'](people2)

print(id(people1['jump']))
print(id(people2['jump']))

類與對象

類和對象是面向對象編程的2個很是重要的概念。sql

類:類是對象的類型,具備相同屬性和行爲事物的統稱。類是抽象的,在使用的時候一般會找到這個類的一個具體存在。編程

對象:萬物皆對象,對象擁有本身的特徵和行爲。是類中一個具體存在的實例。數據結構

類與對象的關係socket

 類是對象的類型,對象是類的實例。類是抽象的概念,而對象是一個你可以摸得着,看獲得的實體。兩者相輔相成,誰也離不開誰。

現實世界中的類:先有對象,再有類

世界上確定是先出現各類各樣的實際存在的物體,而後隨着人類文明的發展,人類站在不一樣的角度總結出了不一樣的種類,如人類、動物類、植物類等概念

也就說,對象是具體的存在,而類僅僅只是一個概念,並不真實存在。

在程序中:務必保證先定義類,後產生對象

這與函數的使用是相似的,先定義函數,後調用函數,類也是同樣的,在程序中須要先定義類,後調用類

不同的是,調用函數會執行函數體代碼返回的是函數體執行的結果,而調用類會產生對象,返回的是對象

注意: 現實中的類並不徹底等於程序中的類,好比現實中的公司類,在程序中有時須要拆分紅部門類,業務類...... ,有時爲了編程需求,程序中也可能會定義現實中不存在的類,好比策略類,現實中並不存在,可是在程序中倒是一個很常見的類。

建立類

語法:class 類名:

      定義變量

      定義函數

#python中的類
class Fruit():
    generate = '植物上長的果子'
    def grow_up(self):
        print('果子在成長中')

#python中的對象
f1 = Fruit()  #調用類或稱爲實例化,獲得對象。即f1是Fruit類實例化的對象

操做屬性

python中一切都是對象,類自己也是對象,有本身的屬性,能夠經過 類名.__dict__查找。類中能夠有任意python代碼,這些代碼在類定義階段便會執行,於是會產生新的名稱空間,用來存放類的變量名與函數名,而這些在類中定義的名字,都是類的屬性,均可以經過類名訪問,類名加點(.)是訪問屬性的語法。全部類的屬性都存儲到類的__dict__字典裏,也能夠經過字典的形式訪問。

 

#python中的類
class Fruit():
    generate = '植物上長的果子'
    def grow_up(self):
        print('果子在成長中')

#查看類的全部屬性
print(Fruit.__dict__)
# . :專門用來訪問屬性,本質操做的就是__dict__
print(Fruit.generate) #等於 Fruit.__dict__['generate']
Fruit.generate = 18 #等於 Fruit.__dict__['generate'] = 18
Fruit.x = 2 #等於 Fruit.__dict__['generate'] = 2
del Fruit.generate #等於 del Fruit.__dict__['generate']

類屬性有兩種:數據屬性(變量名)和函數屬性(函數名)

數據屬性:即類變量,類變量在整個實例化的對象中是公用的,在對象與類沒有同名的屬性前提下,實例化的對象能夠訪問類變量,但不能修改類變量類變量定義在類中且在函數體以外。若是須要用在類的方法中,使用 類名.類屬性.。

#python中的類
class Fruit():
    generate = '植物上長的果子'
    def grow_up(self):
        print(Fruit.generate) #訪問類變量
        print('果子在成長中')

# #python中的對象
f1 = Fruit()  #f1是Fruit類實例化的對象
f2 = Fruit()  #f1是Fruit類實例化的對象
print(f1.generate)
print((f2.generate))
##類的數據屬性是全部對象共享的,id都同樣
print(id(f1.generate))
print(id(f1.generate))

#對象不能修改類變量
f1.generate = 29 #實質是對象f1爲本身添加generate屬性
f2.generate = 28
print('類屬性 ======>',Fruit.generate)
print('對象f1的屬性 ======>',f1.generate) #若對象與類有同名屬性,對象訪問該屬性,則是訪問本身的屬性。
print('對象f2的屬性 ======>',f2.generate)
print(id(Fruit.generate))
print(id(f1.generate))
print(id(f2.generate))

函數屬性:類中定義的函數(沒有被任何裝飾器裝飾的)是類的函數屬性,也叫普通方法。外部用實例調用,類可使用,但必須遵循函數的參數規則,有幾個參數須要傳幾個參數,其實普通方法主要是給對象使用的,並且是綁定到對象的,雖然全部對象指向的都是相同的功能,可是綁定到不一樣的對象就是不一樣的綁定方法,綁定到對象的方法的特殊之處在於,綁定給誰就由誰來調用,誰來調用,就會將‘誰’自己當作第一個參數傳給方法,即自動傳值(方法__init__也是同樣的道理),綁定到對象的方法的這種自動傳值的特徵,決定了在類中定義的普通方法都要默認寫一個參數self,而且是普通方法的第一個位置參數。self能夠是任意名字,可是約定俗成地寫出self。

#python中的類
class Fruit():
    generate = '植物上長的果子'
    def grow_up(self):
        print('果子在成長中')

#類的函數屬性是綁定給對象使用的,obj.method稱爲綁定方法,內存地址都不同,id是python的實現機制,並不能真實反映內存地址,若是有內存地址,仍是之內存地址爲準。
f1 = Fruit()
f2 = Fruit()
f3 = Fruit()
print(Fruit.grow_up)
print(f1.grow_up)
print(f2.grow_up)
print(f3.grow_up)

python的自動傳值:對象調用類的普通方法,則python自動把對象自己傳給該方法的第一個位置參數。

#python中的類
class Fruit():
    generate = '植物上長的果子'
    def grow_up(self):
        # print('果子在成長中')
        print('參數self是:{}'.format(self))

f1 = Fruit()
f2 = Fruit()

print('對象f1:%s'%f1)
f1.grow_up() #等同於 Fruit.grow_up(f1)

print('對象f2:%s'%f2)
f2.grow_up() #等同於 Fruit.grow_up(f2)
#python爲類內置的特殊屬性
類名.__name__# 類的名字(字符串)
類名.__doc__# 類的文檔字符串
類名.__base__# 類的第一個父類(在講繼承時會講)
類名.__bases__# 類全部父類構成的元組(在講繼承時會講)
類名.__dict__# 類的字典屬性
類名.__module__# 類定義所在的模塊
類名.__class__# 實例對應的類(僅新式類中)
類的特殊屬性(類內置屬性)

實例屬性:同類的對象,除了類似的屬性以外還各類不一樣的屬性,定義在類的普通函數中,而且以普通函數的第一個位置參數作前綴的變量,叫作實例屬性。能夠爲類實例化的對象修改或刪除對象屬性的,上面說到普通函數的第一個位置參數傳入的其實就是一個對象,當該函數被調用時,函數裏的代碼被執行。實例屬性就會被操做到對應的對象屬性中,能夠經過對象的__dict__屬性查看對象的屬性。經過對象屬性能夠體現對象與對象在類中的獨特之處。

#python中的類
class Fruit():
    generate = '植物上長的果子'
    def grow_up(self):
        self.name = '蘋果'
        print('果子在成長中')

f1 = Fruit()
f2 = Fruit()
#查看對象的屬性
print('對象f1的屬性字典:%s'%f1.__dict__) #返回{}空字典,由於對象f1被實例化後沒有添加任何屬性。
print('對象f2的屬性字典:%s'%f2.__dict__,end='\n'*2) #返回{}空字典,由於對象f2被實例化後沒有添加任何屬性。

f1.name = '香蕉' #給對象添加屬性name,等於 f1.__dict__['name'] = '香蕉'
f2.colour = '紅色'
print('對象f1的屬性字典:%s'%f1.__dict__)
print('對象f2的屬性字典:%s'%f2.__dict__,end='\n'*2)

f2.colour = '藍色' #修改對象屬性
print('對象f2的屬性字典:%s'%f2.__dict__)

#調用有定義實例屬性的函數
f1.grow_up() #等同於 Fruit.grow_up(f1)
f2.grow_up() #等同於 Fruit.grow_up(f2)
print('對象f1的屬性字典:%s'%f1.__dict__)
print('對象f2的屬性字典:%s'%f2.__dict__,end='\n'*2)

__init__()構造方法

__init__()是一個特殊的方法屬於類的專有方法,被稱爲類的構造函數或初始化方法,方法的前面和後面都有兩個下劃線。這是爲了不Python默認方法和普通方法發生名稱的衝突。每當建立類的實例化對象的時候,__init__()方法都會默認被運行。做用就是初始化已實例化後的對象,即該方法是在對象產生以後纔會執行,只用來爲對象進行初始化操做,能夠有任意代碼,但必定不能有返回值。實例屬性通常在該函數中定義。

class Fruit():
    generate = '植物上長的果子'
    def __init__(self,name,colour):
        self.name = name #定義實例屬性,經過傳參給實例化的對象初始化屬性。
        self.colour = colour
        print('__init__函數被調用')
        print('{}是{}的'.format(self.name,self.colour))
    def grow_up(self):
        self.name = '蘋果'
        print('果子在成長中')

f1 = Fruit('蘋果','紅色') #建立一個新對象後,調用Fruit類的__init__()方法,把對象和參數傳給函數,而後返回這個對象給變量f1引用。
f2 = Fruit('香蕉','黃色')
#查看對象的屬性
print('對象f1的屬性字典:%s'%f1.__dict__)
print('對象f2的屬性字典:%s'%f2.__dict__)
#方式一
class Fruit():
    generate = '植物上長的果子'
    def grow_up(self):
        self.name = '蘋果'
        print('果子在成長中')

# 實例化出兩個個空對象
obj1=Fruit()
obj2=Fruit()

# 爲對象定製本身獨有的特徵
obj1.name='蘋果'
obj1.colour='紅色'

obj2.name='香蕉'
obj2.colour='黃色'

print(obj1.__dict__)
print(obj2.__dict__)
print(Fruit.__dict__)


#方式二
class Fruit():
    generate = '植物上長的果子'
    def grow_up(self):
        self.name = '蘋果'
        print('果子在成長中')

# 實例化出兩個空對象
obj1=Fruit()
obj2=Fruit()

# 爲對象定製本身獨有的特徵
def initialize(obj, x, y):
    obj.name = x
    obj.age = y

initialize(obj1,'蘋果','紅色')
initialize(obj2,'香蕉','黃色')

print(obj1.__dict__)
print(obj2.__dict__)
print(Fruit.__dict__)


#方式三
class Fruit():
    generate = '植物上長的果子'
    def initialize(self,name,colour):
        self.name = name #經過傳參給實例化的對象初始化屬性。
        self.colour = colour
        print('__init__函數被調用')
        print('{}是{}的'.format(self.name,self.colour))
    def grow_up(self):
        self.name = '蘋果'
        print('果子在成長中')


obj1=Fruit()
obj1.initialize('蘋果','紅色') #Fruit.initialize(obj1,'蘋果','紅色')

obj2=Fruit()
obj2.initialize('香蕉','黃色') #Fruit.initialize(obj2,'香蕉','黃色')

print(obj1.__dict__)
print(obj2.__dict__)
print(Fruit.__dict__)


# 方式四
class Fruit():
    generate = '植物上長的果子'
    def __init__(self,name,colour):
        self.name = name #經過傳參給實例化的對象初始化屬性。
        self.colour = colour
        print('__init__函數被調用')
        print('{}是{}的'.format(self.name,self.colour))
    def grow_up(self):
        self.name = '蘋果'
        print('果子在成長中')

obj1=Fruit('蘋果','紅色') #Fruit.__init__(obj1,'蘋果','紅色')
obj2=Fruit('香蕉','黃色') #Fruit.__init__(obj2,'香蕉','黃色')

print(obj1.__dict__)
print(obj2.__dict__)
print(Fruit.__dict__)
爲對象初始化本身獨有特徵方式
from pymysql import connect
#一、在沒有學習類這個概念時,數據與功能是分離的
def exc1(host,port,db,charset,sql):
    conn=connect(host,port,db,charset)
    xxx = conn.execute(sql)
    return xxx

def exc2(host,port,db,charset,sql):
    conn=connect(host,port,db,charset)
    xxx = conn.call_proc(sql)
    return xxx

#每次調用都須要重複傳入一堆參數
exc1('127.0.0.1',3306,'db1','utf8','select * from tb1;')
exc2('127.0.0.1',3306,'db1','utf8','存儲過程的名字')




#二、咱們能想到的解決方法是,把這些變量都定義成全局變量
HOST='127.0.0.1'
PORT=3306
DB='db1'
CHARSET='utf-8'

def exc1(host,port,db,charset,sql):
    conn=connect(host,port,db,charset)
    xxx = conn.execute(sql)
    return xxx


def exc2(host,port,db,charset,sql):
    conn=connect(host,port,db,charset)
    xxx = conn.call_proc(sql)
    return xxx

exc1(HOST,PORT,DB,CHARSET,'select * from tb1;')
exc2(HOST,PORT,DB,CHARSET,'存儲過程的名字')


#三、可是2的解決方法也是有問題的,按照2的思路,咱們將會定義一大堆全局變量,這些全局變量並無作任何區分,即可以被全部功能使用,然而事實上只有HOST,PORT,DB,CHARSET是給exc1和exc2這兩個功能用的。言外之意:咱們必須找出一種可以將數據與操做數據的方法組合到一塊兒的解決方法,這就是咱們說的類了

class MySQLHandler:
    def __init__(self,host,port,db,charset='utf8'):
        self.host=host
        self.port=port
        self.db=db
        self.charset=charset
    def exc1(self,sql):
        conn=connect(self.host,self.port,self.db,self.charset)
        res=conn.execute(sql)
        return res


    def exc2(self,sql):
        conn=connect(self.host,self.port,self.db,self.charset)
        res=conn.call_proc(sql)
        return res


obj=MySQLHandler('127.0.0.1',3306,'db1')
obj.exc1('select * from tb1;')
obj.exc2('存儲過程的名字')


#改進
class MySQLHandler:
    def __init__(self,host,port,db,charset='utf8'):
        self.host=host
        self.port=port
        self.db=db
        self.charset=charset
        self.conn=connect(self.host,self.port,self.db,self.charset)
    def exc1(self,sql):
        return self.conn.execute(sql)

    def exc2(self,sql):
        return self.conn.call_proc(sql)


obj=MySQLHandler('127.0.0.1',3306,'db1')
obj.exc1('select * from tb1;')
obj.exc2('存儲過程的名字')
從代碼級別看面向對象

 靜態屬性、類方法、靜態方法

靜態屬性

定義在類中,經過裝飾器 @property裝飾的方法,而且只有一個參數。只容許實例對象調用。

class Fruit():
    generate = '植物上長的果子'
    def __init__(self,name,colour):
        self.name = name #經過傳參給實例化的對象初始化屬性。
        self.colour = colour
        print('__init__函數被調用')
        print('{}是{}的'.format(self.name,self.colour))
    @property
    def grow_up(self):
        print('果子在成長中')
        return self.name,Fruit.generate
f1 = Fruit('蘋果','紅色')
print(f1.grow_up)

類方法

定義在類中,經過裝飾器 @classmethod裝飾的方法,此方法是專門綁定給類調用的,固然實例對象也能夠調用,但方法裏不能訪問實例屬性。同時必須有一個位置參數cls,而且用此來調用類屬性:cls.類屬性名。與對象調用普通方法同樣,調用此函數時,python會把調用者自己傳給第一個位置參數。

class Fruit():
    generate = '植物上長的果子'
    def __init__(self,name,colour):
        self.name = name #經過傳參給實例化的對象初始化屬性。
        self.colour = colour

    @classmethod
    def grow_up(cls):
        print('grow_up被執行')
        cls.weight = 39 #設置類屬性,即便是實例對象調用,也是設置爲實例對象的類屬性,非實例對象的獨特屬性。
        return cls.generate

f1 = Fruit('蘋果','紅色')
print(f1.grow_up())
print(f1.__dict__)
print(Fruit.__dict__,end='\n'*2)

print(Fruit.grow_up())
print(f1.__dict__)
print(Fruit.__dict__)

靜態方法

定義在類中,經過裝飾器 @staticmethod裝飾的方法,不能訪問實例屬性 ,與類相關可是不依賴類與實例對象,能夠經過類或者實例來調用。python對此方法沒有默認自動傳值,不與類或對象綁定,不需必須有參數,能夠是無參函數。

class Fruit():
    generate = '植物上長的果子'
    def __init__(self,name,colour):
        self.name = name #經過傳參給實例化的對象初始化屬性。
        self.colour = colour

    @staticmethod
    def grow_up(cls):
        print('grow_up被執行')
        Fruit.generate += cls
        return Fruit.generate

print(Fruit.__dict__,end='\n'*2)
f1 = Fruit('蘋果','紅色')
print(f1.grow_up('39'))
print(f1.__dict__)
print(Fruit.__dict__,end='\n'*2)

print(Fruit.grow_up(' 39'))
print(f1.__dict__)
print(Fruit.__dict__)

面向對象的三大特性

繼承/派生/繼承結構

繼承是一種建立新類的方式,新建的類能夠繼承一個或多個父類(python支持多繼承),父類又可稱爲基類或超類,新建的類稱爲派生類或子類。子類會「」遺傳」父類的屬性,從而解決代碼重用問題。若是沒有指定基類,python的類會默認繼承object類,object是全部python類的基類,它提供了一些常見方法(如__str__)的實現。

 在開發程序的過程當中,若是咱們定義了一個類A,而後又想新創建另一個類B,可是類B的大部份內容與類A的相同時。咱們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。經過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的全部屬性(數據屬性和函數屬性),實現代碼重用。不只能夠重用本身的類,也能夠繼承別人的,好比標準庫,來定製新的數據類型,這樣就是大大縮短了軟件開發週期,對大型軟件開發來講,意義重大.。

python中類的繼承分爲:單繼承和多繼承

class ParentClass1: #定義父類
    pass

class ParentClass2: #定義父類
    pass

class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass
    pass

class SubClass2(SubClass1,ParentClass2): #python支持多繼承,用逗號分隔開多個繼承的類
    pass

#查看父類,__bases__則是查看全部繼承的父類,注意,不包括父類的父類。
print(ParentClass1.__bases__)
print(ParentClass2.__bases__)
print(SubClass1.__bases__)
print(SubClass2.__bases__)

#判斷繼承關係
print(issubclass(SubClass2,ParentClass2))
print(issubclass(SubClass2,ParentClass1)) #無論幾代的繼承,不管是直接繼承仍是間接繼承,只要存在繼承關係,就返回True。

重用性與繼承

繼承能夠實現代碼重用,咱們把類與類之間類似或者說比較像的部分抽取成類,這個過程稱之爲抽象,因此在作繼承以前,必須先抽象。抽象指對現實世界問題和實體的本質表現,行爲和特徵建模,創建一個相關的子集,能夠用於 繪程序結構,從而實現這種模型。抽象不只包括這種模型的數據屬性,還定義了這些數據的接口。

#不用不使用繼承寫的貓類和狗類
class Dog:
    def __init__(self,name):
        self.name = name

    def cry(self):
        print('汪汪叫')

    def eat(self):
        print("%s 吃 " %self.name)

    def drink(self):
        print ("%s 喝 " %self.name)

    def shit(self):
        print ("%s 拉 " %self.name)

    def pee(self):
        print ("%s 撒 " %self.name)


class Cat:
    def __init__(self,name):
        self.name = name

    def cry(self):
        print('喵喵叫')

    def eat(self):
        print("%s 吃 " %self.name)

    def drink(self):
        print ("%s 喝 " %self.name)

    def shit(self):
        print ("%s 拉 " %self.name)

    def pee(self):
        print ("%s 撒 " %self.name)

'''
從上面代碼能夠看出貓類和狗類有大量相同的內容,而咱們卻分別在貓和狗的類中編寫了兩次。
若是使用 繼承 的思想,把類似功能的代碼都寫到另外一類中,貓和狗都是動物,因此咱們能夠寫
一個動物類,而後繼承,以下實現:
'''
class Animal:

    def eat(self):
        print("%s 吃 " %self.name)

    def drink(self):
        print ("%s 喝 " %self.name)

    def shit(self):
        print ("%s 拉 " %self.name)

    def pee(self):
        print ("%s 撒 " %self.name)


class Cat(Animal):

    def __init__(self, name):
        self.name = name
        self.breed = ''

    def cry(self):
        print('喵喵叫')

class Dog(Animal):

    def __init__(self, name):
        self.name = name
        self.breed=''

    def cry(self):
        print('汪汪叫')
View Code

屬性查找順序(繼承順序)

python究竟是如何實現繼承的,對於你定義的每個類,python會計算出一個方法解析順序(MRO)列表,這個MRO列表就是一個簡單的全部基類的線性順序列表,經過 類名.__mro__ 能夠查看MRO列表。爲了實現繼承,python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類爲止。而這個MRO列表的構造是經過一個C3線性化算法來實現的。咱們不去深究這個算法的數學原理,它實際上就是合併全部父類的MRO列表並遵循以下三條準則:

1.子類會先於父類被檢查
2.多個父類會根據它們在列表中的順序被檢查
3.若是對下一個類存在兩個合法的選擇,選擇第一個父類

在多繼承中,若是繼承關係爲菱形結構,那麼屬性的查找方式有兩種,分別是:深度優先和廣度優先。新式類使用深度優先的查找順序,經典類(沒有繼承object的新建類)使用廣度優先。python3中統一都是新式類,pyhon2中才分新式類與經典類。

class A(object):
    a = 0
    def test(self):
        print('from A')

class B(A):
    a = 1
    def test(self):
        print('from B')

class C(A):
    a = 2
    def test(self):
        print('from C')

class D(B):
    a = 3
    def test(self):
        print('from D')

class E(C):
    a = 5
    def test(self):
        print('from E')

class F(D,E):
    a = 8
    # def __init__(self,a):
    #     self.a = a
    def test(self):
        print('from F')
    pass

print(F.__mro__)
f=F()
# f=F(9)
print(f.a)
f.test()
View Code

子類中調用父類的方法

方法一:指名道姓,即父類名.父類方法(),這種類調用的方法,函數有多少個參數,就須要傳入多少給參數。

方法二:使用函數super(),該函數會自動傳入實例對象。

當你使用super()函數時,Python會在MRO列表上繼續搜索下一個類。即便沒有直接繼承關係,super仍然會按照mro繼續日後查找,只要每一個重定義的方法統一使用super()並只調用它一次,那麼控制流最終會遍歷完整個MRO列表,每一個方法也只會被調用一次(注意注意注意:使用super調用的全部屬性,都是從MRO列表當前的位置日後找,千萬不要經過看代碼去找繼承關係,必定要看MRO列表

class A:
    def __init__(self,x,y):
        self.A_x = x
        self.A_y = y

class B:
    def __init__(self,x,y):
        self.B_x = x
        self.B_y = y

class C(A,B):
    def __init__(self,x,y):
        # B.__init__(self,x,y) #當繼承的類中有一樣屬性,指名道姓可指定哪一個類的屬性,

        super().__init__(x,y) #按照mro列表順序查找,一旦找到就會中止查找
        # super(__class__,self).__init__(x,y)
        # super(C,self).__init__(x,y)

c = C('abc',9)
print(c.__dict__)

派生與方法重寫

固然子類也能夠添加本身新的屬性或者在本身這裏從新定義繼承的屬性(不會影響到父類),函數屬性被從新定義則叫方法重寫。須要注意的是,一旦從新定義了本身的屬性且與父類重名,那麼調用新增的屬性時,就以本身爲準了。在子類中,新建的重名的函數屬性,在編輯函數內功能的時候,有可能須要重用父類中重名的那個函數功能,應該是用調用普通函數的方式,即:類名.func(),此時就與調用普通函數無異了,所以即使是self參數也要爲其傳值。

class A:
    def __init__(self,x,y):
        self.A_x = x
        self.A_y = y

class B(A):
    def __init__(self,x,y,i,j): #方法重寫
        self.B_x = x #派生屬性
        self.B_y = y #派生屬性
        A.__init__(self,i,j) #調用父類方法

b = B('a','b','c','d')
print(b.__dict__,end='\n'*2)

b.__init__(1,2,8,9)
print(b.__dict__,end='\n'*2) #實例對象調用該類函數屬性

A.__init__(b,'ab','cd') #調用父類方法
print(b.__dict__)

接口與歸一化設計

接口是一種將對象標記爲符合給定API或約定的機制,定義了一羣類共同的函數,但未實現,而是讓子類去實現接口中的函數。這麼作的意義在於歸一化,什麼叫歸一化,就是隻要是基於同一個接口實現的類,那麼全部的這些類產生的對象在使用時,從用法上來講都同樣。 歸一化讓使用者無需關心對象的類是什麼,只須要的知道這些對象都具有某些功能就能夠了,這極大地下降了使用者的使用難度。使得高層的外部使用者能夠不加區分的處理全部接口兼容的對象集合,好比,有一個汽車接口,裏面定義了汽車全部的功能,而後由本田汽車的類,奧迪汽車的類,大衆汽車的類,他們都實現了汽車接口,這樣就好辦了,你們只須要學會了怎麼開汽車,那麼不管是本田,仍是奧迪,仍是大衆咱們都會開了,開的時候根本無需關心我開的是哪一類車,操做手法(函數調用)都同樣,不過,在python中沒有提供實現接口的功能,若是非要去模仿接口的概念,能夠藉助第三方模塊:http://pypi.python.org/pypi/zope.interface

基於繼承咱們能夠實現模仿接口,定義一個接口類(模仿java的Interface),接口類中定義了一些接口名(就是函數名)且並未實現接口的功能,子類繼承接口類,而且實現接口中的功能。

class Interface:#定義接口Interface類來模仿接口的概念,python中壓根就沒有interface關鍵字來定義一個接口。
    def read(self): #定接口函數read
        pass

    def write(self): #定義接口函數write
        pass


class Txt(Interface): #文本,具體實現read和write
    def read(self):
        print('文本數據的讀取方法')

    def write(self):
        print('文本數據的讀取方法')

class Sata(Interface): #磁盤,具體實現read和write
    def read(self):
        print('硬盤數據的讀取方法')

    def write(self):
        print('硬盤數據的讀取方法')
View Code

上面的代碼只是看起來像接口,其實並無起到接口的做用,子類徹底能夠不用去實現接口 ,這就用到了抽象類。

抽象類

與java同樣,python也有抽象類的概念可是一樣須要藉助模塊實現,抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被實例化,抽象類的本質仍是類,指的是一組類的類似性,包括數據屬性(如all_type)和函數屬性(如read、write),而接口只強調函數屬性的類似性。抽象類是一個介於類和接口直接的一個概念,同時具有類和接口的部分特性,能夠用來實現歸一化設計 。若是說類是從一堆對象中抽取相同的內容而來的,那麼抽象類是從一堆中抽取相同的內容而來的,內容包括數據屬性和函數屬性。好比咱們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你吃水果時,要麼是吃一個具體的香蕉,要麼是吃一個具體的桃子。。。。。。你永遠沒法吃到一個叫作水果的東西。

在python中實現抽象類

#一切皆文件
import abc #利用abc模塊實現抽象類

class All_file(metaclass=abc.ABCMeta):
    all_type='file'
    @abc.abstractmethod #定義抽象方法,無需實現功能
    def read(self):
        '子類必須定義讀功能'
        pass

    @abc.abstractmethod #定義抽象方法,無需實現功能
    def write(self):
        '子類必須定義寫功能'
        pass

# class Txt(All_file):
#     pass
#
# t1=Txt() #報錯,子類沒有定義抽象方法

class Txt(All_file): #子類繼承抽象類,可是必須定義read和write方法
    def read(self):
        print('文本數據的讀取方法')

    def write(self):
        print('文本數據的讀取方法')

class Sata(All_file): #子類繼承抽象類,可是必須定義read和write方法
    def read(self):
        print('硬盤數據的讀取方法')

    def write(self):
        print('硬盤數據的讀取方法')
View Code

繼承結構:表示多「代」派生,能夠述成一個「族譜」,連續的子類,與祖先類都有關係。

泛化/特化

基於繼承
泛化表示全部子類與其父類及祖先類有同樣的特色。
特化描述全部子類的自定義,也就是,什麼屬性讓它與其祖先類不一樣。

多態與多態性

多態指的是一類事務的多種形態,好比水有固態、液態、氣態,Python的序列類型有多種形態:字符串,列表,元組,字典。

多態性指在不考慮實例類型的狀況下使用實例的方法。在Python中不少地方均可以體現多態的特性,好比內置函數len(object),len函數不只能夠計算字符串的長度,還能夠計算列表、元組等對象中的數據個數,這裏在運行時經過參數類型肯定其具體的計算過程。多態性的好處是:增長了程序的靈活性(以不變應萬變,不論對象變幻無窮,使用者都是同一種形式去調用,如func(animal))、增長了程序額可擴展性。

'''不一樣類型上的實例對象體現的多態性'''
class Water:
    def __init__(self,bottle):
        self.bottle = bottle
    def temperature(self,centigrade):
        if centigrade <= 0:
            print('{}是液態水,常溫下大於零'.format(self.bottle))
        else:
            print('{}的溫度是:{} 攝氏度'.format(self.bottle,centigrade))

class Ice:
    def __init__(self,bottle):
        self.bottle = bottle
    def temperature(self,centigrade):
        if centigrade >= 0:
            print('{}是固態冰,常溫下小於零攝氏度'.format(self.bottle))
        else:
            print('{}的溫度是:{} 攝氏度'.format(self.bottle,centigrade))

b1 = Water('一號瓶子')
b2 = Ice('二號瓶子')

#不一樣類的實例對象調用相同的方法
b1.temperature(30)
b2.temperature(30)
b2.temperature(-20)


'''繼承上體現的多態性'''
class Water:
    def __init__(self,bottle):
        self.bottle = bottle
    def temperature(self,centigrade):
        if centigrade <= 0:
            print('{}是液態水,常溫下大於零'.format(self.bottle))
        else:
            print('{}的溫度是:{} 攝氏度'.format(self.bottle,centigrade))

class Ice(Water):
    def __init__(self,bottle):
        super().__init__(bottle)
    def temperature(self,centigrade):
        if centigrade >= 0:
            print('{}是固態冰,常溫下小於零攝氏度'.format(self.bottle))
        else:
            print('{}的溫度是:{} 攝氏度'.format(self.bottle,centigrade))

b1 = Water('一號瓶子')
b2 = Ice('二號瓶子')

#子類與父類的實例對象調用相同的方法
b1.temperature(30)
b2.temperature(30)
b2.temperature(-20)
View Code

封裝

封裝描述了對數據/信息進行隱藏的觀念,它對數據屬性提供接口和訪問函數。經過任何客戶端直接對數據的訪問,無視接口,與封裝性都是背道而馳的,除非程序員容許這些操做。做爲實現的 一部分,客戶端根本就不須要知道在封裝以後,數據屬性是如何組織的。在Python中,全部的類屬性都是公開的,但名字可能被「混淆」了,以阻止未經受權的訪問,但僅此而已,再沒有其餘預防措施了。這就須要在設計時,對數據提供相應的接口,以避免客戶程序經過不規範的操做來存取封裝的數據屬性。

封裝其實分爲兩個層面,但不管哪一種層面的封裝,都要對外界提供好訪問你內部隱藏內容的接口(接口能夠理解爲入口,有了這個入口,使用者無需且不可以直接訪問到內部隱藏的細節,只能走接口,而且咱們能夠在接口的實現上附加更多的處理邏輯,從而嚴格控制使用者的訪問)

第一個層面的封裝(什麼都不用作):建立類和對象會分別建立兩者的名稱空間,咱們只能用類名.或者obj.的方式去訪問裏面的名字,這自己就是一種封裝。

'''定義類自己就是一種封裝方式'''
class A:
    N=0
    def __init__(self):
        self.X=10
    def foo(self):
        print('from A')
a = A()
print(a.N)
a.foo()

第二個層面的封裝:類中把某些屬性和方法隱藏起來(或者說定義成私有的),只在類的內部使用、外部沒法訪問,或者留下少許接口(函數)供外部訪問。

在python中用雙下劃線的方式實現隱藏屬性(設置成私有的)

類中全部雙下劃線開頭的名稱如__x都會自動變造成:_類名__x的形式:

這種自動變形的特色:

  1. 類中定義的__x只能在內部使用,如self.__x,引用的就是變形的結果。
  2. 這種變形其實正是針對外部的變形,在外部是沒法經過__x這個名字訪問到的。
  3. 在子類定義的__x不會覆蓋在父類定義的__x,由於子類中變造成了:_子類名__x,而父類中變造成了:_父類名__x,即雙下滑線開頭的屬性在繼承給子類時,子類是沒法覆蓋的。

注意:對於這一層面的封裝(隱藏),咱們須要在類中定義一個函數(接口函數)在它內部訪問被隱藏的屬性,而後外部就可使用了

也能夠經過j@property特性來實現函數隱藏。property是一種特殊的屬性,訪問它時會執行一段功能(函數)而後返回值。將一個類的函數定義成此特性之後,對象再去使用的時候obj.name,根本沒法察覺本身的name是執行了一個函數而後計算出來的,這種特性的使用方式遵循了統一訪問的原則

class A:
    __N=0 #類的數據屬性就應該是共享的,可是語法上是能夠把類的數據屬性設置成私有的如__N,會變形爲_A__N
    def __init__(self):
        self.__X=10 #變形爲self._A__X
    def __foo(self): #變形爲_A__foo
        print('from A')
    @property
    def bar(self):
        self.__foo() #只有在類內部才能夠經過__foo的形式訪問到.
        return 8

這種變形須要注意的問題是:

這種機制也並無真正意義上限制咱們從外部直接訪問屬性,知道了類名和屬性名就能夠拼出名字:_類名__屬性,而後就能夠訪問了,如a._A__N。即這種操做並非嚴格意義上的限制外部訪問,僅僅只是一種語法意義上的變形,主要用來限制外部的直接訪問。

class A:
    __N=0 #類的數據屬性就應該是共享的,可是語法上是能夠把類的數據屬性設置成私有的如__N,會變形爲_A__N
    def __init__(self):
        self.__X=10 #變形爲self._A__X
    def __foo(self): #變形爲_A__foo
        print('from A')
    @property
    def bar(self):
        self.__foo() #只有在類內部才能夠經過__foo的形式訪問到.
        return 8

print(A._A__N)
a = A()
print(a._A__X)
print(a.__dict__)
print(A.__dict__)

'''變形的過程只在類的定義是發生一次,在定義後的賦值操做,不會變形'''
a.__Y = 20
A.__Z = 90
print(a.__dict__)
print(a.__Y)
print(A.__dict__)
print(A.__Z)

在繼承中,父類若是不想讓子類覆蓋本身的方法,能夠將方法定義爲私有的。

class A:
    def __fa(self):  # 在定義時就變形爲_A__fa
        print('from A')
    def test(self):
        self.__fa()  # 只會與本身所在的類爲準,即調用_A__fa

class B(A):
    def __fa(self):
        print('from B')

b = B()
b.test()

python並不會真的阻止你訪問私有的屬性,模塊也遵循這種約定,若是模塊名以單下劃線開頭,那麼from module import *時不能被導入,可是你from module import _private_module依然是能夠導入的其實不少時候你去調用一個模塊的功能時會遇到單下劃線開頭的(socket._socket,sys._home,sys._clear_type_cache),這些都是私有的,原則上是供內部調用的,做爲外部的你,獨斷獨行也是能夠用的,只不過顯得稍微傻逼一點點python要想與其餘編程語言同樣,嚴格控制屬性的訪問權限,只能藉助內置方法如__getattr__。

合成

軟件重用的重要方式除了繼承以外還有另一種方式,即:組合

組合指的是,在一個類中以另一個類的對象做爲數據屬性,稱爲類的組合

class Equip:  # 武器裝備類
    def fire(self):
        print('release Fire skill')

class Riven:  # 英雄Riven的類,一個英雄須要有裝備,於是須要組合Equip類
    camp = 'Noxus'
    def __init__(self, nickname):
        self.nickname = nickname
        self.equip = Equip()  # 用Equip類產生一個裝備,賦值給實例的equip屬性
        
r1 = Riven('關雨竹')
r1.equip.fire()  # 可使用組合的類產生的對象所持有的方法

組合與繼承都是有效地利用已有類的資源的重要方式。可是兩者的概念和使用場景皆不一樣:

1.繼承的方式

經過繼承創建了派生類與基類之間的關係,它是一種'是'的關係,好比白馬是馬,人是動物。

當類之間有不少相同的功能,提取這些共同的功能作成基類,用繼承比較好,好比老師是人,學生是人

2.組合的方式

用組合的方式創建了類與組合的類之間的關係,它是一種‘有’的關係,好比教授有生日,教授教python和linux課程,教授有學生s一、s二、s3...

當類之間有顯著不一樣,而且較小的類是較大的類所須要的組件時,用組合比較好。

自省(反射)

反射的概念是由Smith在1982年首次提出的,主要是指程序能夠訪問、檢測和修改它自己狀態或行爲的一種能力(自省)。這一律唸的提出很快引起了計算機科學領域關於應用反射性的研究。它首先被程序語言的設計領域所採用,並在Lisp和麪向對象方面取得了成績。

python提供了四個能夠實現自省的函數。適用於類和對象(一切皆對象,類自己也是一個對象)。

class Foo(object):
    X = 30

    def __init__(self):
        self.y = 20

    def func(self):
        return 'func'

    @staticmethod
    def bar():
        return 'bar'

f = Foo()

#檢測是否含有某屬性
print(hasattr(f,'y'))
print(hasattr(Foo,'X'))

#獲取屬性,若屬性不存在則報錯。可設置第三個參數爲報錯時返回的信息。
print(getattr(f,'y'))
print(getattr(f,'x','屬性不存在'))
print(getattr(Foo,'X'))

#設置屬性
setattr(f,'x',15) #添加
setattr(Foo,'X',90) #修改
print(f.__dict__)

#刪除屬性
delattr(f,'y')
delattr(Foo,'X')
print(f.__dict__)

 

 python的魔術方法參考連接:http://www.javashuo.com/article/p-kudivpto-d.html

相關文章
相關標籤/搜索