Python 面向對象1——面向對象編程介紹

  前言:

1、對象可以比作人【(會某些技能,具有某些屬性(特徵)】。

2、每個對象都有不同的屬性(特徵),需要用__init__去定義這些屬性(特徵)。

3、類可以比作一羣人(他們有相似的技能或者相似的特徵)。

4、先定義類,然後調用類(實例化)產生對象。

5、"類" 具有數據屬性(所有對象共享)函數屬性(主要是給對象使用的,並且是綁定到對象的)。

 

創建類的2中方式:

# 方式一
class Foo(object):
    CITY = "bj"

    def func(self,x):
        return x+1

# 方式二
Coo=type("Coo",(object,),{"CITY":"bj","func":lambda self,x:x+1})

 

  一、前言:面向過程編程 ---針對擴展性要求不高的程序,可使用面向過程編程

  1、什麼是面向過程編程?

     核心是過程二字,過程指的是解決問題的步驟,即先幹什麼,在幹什麼,在幹什麼。。。。

     基於面向過程的思想,編寫程序就好比在設計一條流水線,是一種機械式思維方式。

     優點:複雜度的問題流程化,進而簡單化(一個複雜的問題,分成一個個小的步驟去實現,實現小的步驟將會非常簡單)

     缺點:一套流水線或者流程就是用來解決一個問題,生產汽水的流水線無法生產汽車,即便是能,也得是大改,改一個組件,牽一髮而動全身。

應用場景:一旦完成基本很少改變的場景,著名的例子有Linux內核,git,以及Apache HTTP Server等。

面向過程編程小例子:把要做的事情拆分爲一件件小的事情,然後完成。

def login():
    uname=input('pls input uname>>:').strip()
    pwd=input('pls input pwd>>:').strip()
    return uname,pwd
 
def auth(uname,pwd):
    if uname == 'szq' and pwd == '123':
        return True,uname
    else:
        return False,uname
 
def index():
    if res[0]:
        print('歡迎%s登錄' %res[1])
    else:
        print('%s登錄失敗' % res[1])
 
uname,pwd=login()
res=auth(uname,pwd)
index()

 

  二、面向對象編程 ---用在擴展性強的程序上(與用戶交互上)

  1、什麼是面向對象編程?

     核心是 "對象" 二字,對象指的是:特徵(指變量)與技能(指函數)的結合體。

     基於面向對象的思想,編寫程序就好比在創造一個世界,程序員就是這個世界的上帝,因此一切存在的事物皆對象,任何不存在的對象也都可以造出來。

     優點:解決了程序的擴展性。對某一個對象單獨修改,會立刻反映到整個體系中,如對遊戲中一個人物參數的特徵和技能修改都很容易。

     缺點:

    1. 編程的複雜度遠高於面向過程,不瞭解面向對象而立即上手基於它設計程序,極容易出現過度設計的問題。一些擴展性要求低的場景使用面向對象會徒增編程難度,比如管理linux系統的shell腳本就不適合用面向對象去設計,面向過程反而更加適合。

    2. 無法向面向過程的程序設計流水線式的可以很精準的預測問題的處理流程與結果,面向對象的程序一旦開始就由對象之間的交互解決問題,即便是上帝也無法準確地預測最終結果。於是我們經常看到對戰類遊戲,新增一個遊戲人物,在對戰的過程中極容易出現陰霸的技能,一刀砍死3個人,這種情況是無法準確預知的,只有對象之間交互才能準確地知道最終的結果。

應用場景:需求經常變化的軟件,一般需求的變化都集中在用戶層,互聯網應用,企業內部軟件,遊戲等都是面向對象的程序設計大顯身手的好地方

 

三、

    類:一系列對象相似的特徵與技能的結合體

    在程序中:一定要先定義類後調用類 (產生對象)

例子:

對象1:學生
    特徵 
        school='Oldboy'
        name='李泰迪'
        sex='male'
        age=18
    技能 
        選課

對象2:學生
    特徵
        school='Oldboy'
        name='張隨便'
        sex='male'
        age=38
    技能
        選課

對象3:老師
    特徵 
        name='Egon'
        sex='male'
        age=18
        level=10
    技能 
        打分
        點名

總結:

    學生之間相似的特徵:school='Oldboy'
    學生之間相似的技能:選課

    3.1、類的定義  -->類在定義階段就會執行代碼,會產生類的名稱空間

class OldboyStudent:    #類並不是一個真正存在的東西,它是一個抽象的概念(這裏表示school='Oldboy')
    # 公共的特徵: 變量
    school='Oldboy'

    # 技能: 函數
    def choose_course(self):
        print('is choose_course')

print(OldboyStudent.__dict__)        #打印類定義的名稱空間
print(OldboyStudent.__dict__['school'])        #取名稱空間內的"school"對應的值
# >>:Oldboy
print(OldboyStudent.school)          #本質就是取"OldboyStudent.__dict__['school']"的值(OldboyStudent名稱空間內"school"的值)
# >>:Oldboy

OldboyStudent.choose_course(123)     #調用類的容器"choose_course",由於choose_course本身是一個函數,那麼就按照函數的寫法來調用
# >>:is choose_course

    3.2、類的用法:增,刪,改,查

'''類的用法:增刪改查'''
OldboyStudent.country='China'       #增加一條數據
OldboyStudent.school='Oldgirl'      #改數據,把'Oldboy'修改爲'Oldgirl'
print(OldboyStudent.__dict__)       #查看數據
del OldboyStudent.school            #在類(一個容器)裏面把school給刪掉
print(OldboyStudent.__dict__)

  總結類的用途:2種
    1、通過調用類(實例化),可以產生對象。
    2、類本身就是一個容器,裏面有很多名稱空間定義好的名稱,可以直接取。

 

    3.3、python爲類內置的特殊屬性

類名.__name__# 類的名字(字符串)
類名.__doc__# 類的文檔字符串
類名.__base__# 類的第一個父類(在講繼承時會講)
類名.__bases__# 類所有父類構成的元組(在講繼承時會講)
類名.__dict__# 類的字典屬性
類名.__module__# 類定義所在的模塊
類名.__class__# 實例對應的類(僅新式類中)

 

四、對象

    4.1、實例化對象 [object=class()]   -->對象獨有的特徵一定會放在對象自己的名稱空間內。對象共有的特徵可以放在類的名稱空間中。

class OldboyStudent:
    country='China'      #這個相關於對象的公共特徵(即每個對象都有這個特徵)
    def __init__(self,name,sex,age):   #"__init__"是一個內置方法(在滿足某種條件下自動觸發),用來定義對象有哪些屬性(其中self表示對象,對象會自動當做第一個值傳入)
        self.name=name   #特徵1
        self.sex=sex     #特徵2
        self.age=age     #特徵3

    def learn(self):   #定義對象的技能
        print('學習') 

    def eat(self):     #定義對象的技能
        print('吃飯')

    def sleep(self):   #定義對象的技能
        print('睡覺')

# 在實例化時,自動觸發__init__(s1,'張三','male','18')[將對象以及對象要傳入的值一起傳給__init__]
s1=OldboyStudent('張三','male','18')      # 調用類(實例化)產生對象
s2=OldboyStudent('李四','female','28')    # 調用類(實例化)產生對象
s3=OldboyStudent('王二','male','38')      # 調用類(實例化)產生對象

print(s1.__dict__)   #打印對象s1的名稱空間,拿到名稱空間的值。
    # {'name': '張三', 'sex': 'male', 'age': '18'}
print(s2.__dict__)
    # {'name': '李四', 'sex': 'female', 'age': '28'}
print(s3.__dict__)
    # {'name': '王二', 'sex': 'male', 'age': '38'}

總結對象的用途:
    1、對象的屬性查找是:先從對象本身的名稱空間找,找不到則取類的名稱空間中查找,兩者都找不到則報錯。

 

4.2、修改對象名稱空間的值,與修改類名稱空間的值

#一、先定義類
class OldboyStudent:
    school='Oldboy'

    def __init__(self, x, y, z):
        self.name = x
        self.sex = y
        self.age = z

    def choose_course(self):
        print('is choose_course')

#二、後調用類
stu1=OldboyStudent('李泰迪','male',18)
stu2=OldboyStudent('張隨便','famale',38)

# 修改對象下的名稱空間的值只針對對象本身的名稱空間生效,而不會影響到其他對象。
stu1.school='US'
print(stu1.school)
>>:US
print(stu2.school)
>>:Oldboy

# 修改類下的名稱空間的值,那麼引用該類的對象的名稱空間的值都會被修改。
OldboyStudent.school='US'
print(stu1.school)
>>:US
print(stu2.school)
>>:US

 

4.3、對象綁定方法的使用

  綁定方法的特殊點:
     1、綁定給誰就由誰來調用
     2、誰來調用,就會將誰當做第一個參數自動傳入

class OldboyStudent:

    school='Oldboy'
    def __init__(self, x, y, z):
        self.name = x
        self.sex = y
        self.age = z

    def choose_course(self):
        print('%s is choose_course' %self.name)  # 新增self.name

    def eat(self):
        print('%s is eating' %self.name)         # 新增self.name

    def sleep(self):
        print('%s is sleeping' %self.name)       # 新增self.name

stu1=OldboyStudent('李泰迪','male',18)
stu2=OldboyStudent('張隨便','famale',38)

OldboyStudent.choose_course(stu1)
OldboyStudent.choose_course(stu2)
# 李泰迪 is choose_course
# 張隨便 is choose_course

# 強調:綁定到對象的方法的特殊之處在於,綁定給誰就由誰來調用,誰來調用,就會將‘誰’本身當做第一個參數傳給方法,即自動傳值(方法__init__也是一樣的道理)
stu1.choose_course()                # 等同於OldboyStudent.choose_course(stu1)
stu2.choose_course()                # 等同於OldboyStudent.choose_course(stu2)
# 李泰迪 is choose_course
# 張隨便 is choose_course

 

4.4、從代碼級別看面向對象

#1、在沒有學習類這個概念時,數據與功能是分離的
def exc1(host,port,db,charset):
    conn=connect(host,port,db,charset)
    conn.execute(sql)
    return xxx


def exc2(host,port,db,charset,proc_name)
    conn=connect(host,port,db,charset)
    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','存儲過程的名字')



#2、我們能想到的解決方法是,把這些變量都定義成全局變量
HOST=‘127.0.0.1’
PORT=3306
DB=‘db1’
CHARSET=‘utf8’

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


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

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



#3、但是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('存儲過程的名字')

數據與專門操作該數據的功能組合到一起

 

4.5、總結對象的好處

1、可擴展性高的例子

     1):定義類併產生三個對象:s1 ,s2 ,s3

class OldboyStudent:

    def __init__(self,name,sex,age):   #定義對象有哪些特徵
        self.name=name   #特徵1
        self.sex=sex     #特徵2
        self.age=age     #特徵3

s1=OldboyStudent('張三','male','18')      # 調用類(實例化)產生對象
s2=OldboyStudent('李四','female','28')    # 調用類(實例化)產生對象
s3=OldboyStudent('王二','male','38')      # 調用類(實例化)產生對象

    2):新增一個類屬性(特徵),那麼實例化後所有對象都會收到這個屬性。


class OldboyStudent:
    country='China'

    def choose_course(self):
        print('%s 測試綁定方法' %self.name)

    def __init__(self,name,sex,age):   #定義對象有哪些屬性
        self.name=name   #屬性1
        self.sex=sex     #屬性2
        self.age=age     #屬性3

    def learn(self):   #定義對象的特徵
        print('學習')

    def tell_info(self):
        info="""
        國籍:%s
        姓名:%s
        年齡:%s
        性別:%s
        """%(self.country,self.name,self.age,self.sex)
        print(info)

s1=OldboyStudent('張三','male','18')      # 調用類(實例化)產生對象
s2=OldboyStudent('李四','female','28')    # 調用類(實例化)產生對象
s3=OldboyStudent('王二','male','38')      # 調用類(實例化)產生對象

print(s1.country)
# China

s1.choose_course()
# 張三 測試綁定方法

s1.tell_info()
# 國籍: China
# 姓名: 張三
# 年齡: 18
# 性別: male

 

五、面向對象的三大特性:繼承,派生,多態,封裝

 5.1、第一大特性:繼承與派生

 1、什麼是繼承?

   1) 繼承是一種新建類的方式,新建的類稱爲子類或者派生類,被繼承的類稱爲父類或者基類或者超類。

   2) 子類會遺傳父類的一系列屬性,從而解決代碼重用問題。

   3) Python支持多繼承

   4)  在Python3中,如果沒有顯示繼承任何類(使用print(Sub1.__bases__)查看繼承了哪些類),那麼默認繼承object類。

   5)  在Python2中,如果沒有顯示繼承任何類(使用print(Sub1.__bases__)查看繼承了哪些類),也不會繼承object類。

 

2、繼承與抽象

  繼承描述的是子類與父類之間的關係,是一種什麼是什麼的關係。要找出這種關係,必須先抽象再繼承

  抽象即抽取類似或者說比較像的部分。

  抽象分成兩個層次: 

    1.將奧巴馬和梅西這倆對象比較像的部分抽取成類; 

    2.將人,豬,狗這三個類比較像的部分抽取成父類。

  抽象最主要的作用是劃分類別(可以隔離關注點,降低複雜度)

 

  繼承:是基於抽象的結果,通過編程語言去實現它,肯定是先經歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構。

  抽象只是分析和設計的過程中,一個動作或者說一種技巧,通過抽象可以得到類

 

2、爲什麼要用繼承?

  在開發程序的過程中,如果我們定義了一個類A,然後又想新建立另外一個類B,但是類B的大部分內容與類A的相同時。

  我們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。

  通過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的所有屬性(數據屬性和函數屬性),實現代碼重用。例子如下:

==========================第一部分
例如

  貓可以:喵喵叫、吃、喝、拉、撒

  狗可以:汪汪叫、吃、喝、拉、撒

如果我們要分別爲貓和狗創建一個類,那麼就需要爲 貓 和 狗 實現他們所有的功能,僞代碼如下:
 

#貓和狗有大量相同的內容
class 貓:

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

    def 吃(self):
        # do something

    def 喝(self):
        # do something

    def 拉(self):
        # do something

    def 撒(self):
        # do something

class 狗:

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

    def 吃(self):
        # do something

    def 喝(self):
        # do something

    def 拉(self):
        # do something

    def 撒(self):
        # do something



==========================第二部分
上述代碼不難看出,吃、喝、拉、撒是貓和狗都具有的功能,而我們卻分別的貓和狗的類中編寫了兩次。如果使用 繼承 的思想,如下實現:

  動物:吃、喝、拉、撒

     貓:喵喵叫(貓繼承動物的功能)

     狗:汪汪叫(狗繼承動物的功能)

僞代碼如下:
class 動物:

    def 吃(self):
        # do something

    def 喝(self):
        # do something

    def 拉(self):
        # do something

    def 撒(self):
        # do something

# 在類後面括號中寫入另外一個類名,表示當前類繼承另外一個類
class 貓(動物):

    def 喵喵叫(self):
        print '喵喵叫'
        
# 在類後面括號中寫入另外一個類名,表示當前類繼承另外一個類
class 狗(動物):

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




==========================第三部分
#繼承的代碼實現
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('汪汪叫')


# ######### 執行 #########

c1 = Cat('小白家的小黑貓')
c1.eat()

c2 = Cat('小黑的小白貓')
c2.drink()

d1 = Dog('胖子家的小瘦狗')
d1.eat()

使用繼承來重用代碼比較好的例子

 

    提示:用已經有的類建立一個新的類,這樣就重用了已經有的軟件中的一部分設置大部分,大大減少了編程的工作量,這就是常說的軟件重用,不僅可以重用自己的類,也可以繼承別人的,比如標準庫,來定製新的數據類型,這樣就是大大縮短了軟件開發週期,對大型軟件開發來說,意義重大。例子如下:

class Hero:
    def __init__(self,nickname,aggressivity,life_value):
        self.nickname=nickname
        self.aggressivity=aggressivity
        self.life_value=life_value

    def move_forward(self):
        print('%s move forward' %self.nickname)

    def move_backward(self):
        print('%s move backward' %self.nickname)

    def move_left(self):
        print('%s move forward' %self.nickname)

    def move_right(self):
        print('%s move forward' %self.nickname)

    def attack(self,enemy):
        enemy.life_value-=self.aggressivity
class Garen(Hero):
    pass

class Riven(Hero):
    pass

g1=Garen('草叢倫',100,300)
r1=Riven('銳雯雯',57,200)

print(g1.life_value)
r1.attack(g1)
print(g1.life_value)

'''
運行結果
'''

  注意:像g1.life_value之類的屬性引用,會先從實例中找life_value然後去類中找,然後再去父類中找...直到最頂級的父類。

 

  1) 減少代碼冗餘例子 --- 這裏使用到了對象的繼承派生

  例子:定義學生類和老師類,但是學生類和老師類大部分代碼相同,這裏就可以使用類的繼承來減少代碼冗餘。

#修改前:
import pickle

class OldboyStudent:
    school='oldboy'

    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex

    def choose_course(self,course):
        self.course=course
        print('%s is choose course:%s' %(self.name,self.course))

    def save(self):
        with open('%s.stu' %self.name,'wb') as f:
            pickle.dump(self,f)

class OldboyTeacher:
    school = 'oldboy'

    def __init__(self,name,age,sex,level):
        self.name = name
        self.age = age
        self.sex = sex
        self.level = level

    def score(self,stu):
        print('%s is score %s' %(self.name,stu.name))

    def save(self):
        with open('%s.stu' %self.name,'wb') as f:
            pickle.dump(self,f)

stu1=OldboyStudent('alex',20,'male')
stu1.save()

tea1=OldboyTeacher('egon',18,'male','10')
tea1.save()

#####################################分割線####################################

#修改後:  使用繼承與派生的方式減少代碼冗餘。
import pickle

class OldboyPeople:
    school='oldboy'

    def save(self):
        with open('%s.stu' %self.name,'wb') as f:
            pickle.dump(self,f)

    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex

class OldboyStudent(OldboyPeople):
    def choose_course(self,course):
        self.course=course
        print('%s is choose course:%s' %(self.name,self.course))

# 在子類中派生出新方法中重用父類的功能:
# 方式一:指名道姓的訪問父類的函數
class OldboyTeacher(OldboyPeople):                
    def __init__(self,name,age,sex,level):        # 在子類派生出的新方法中重用父類的功能。[定義了與父類名字相同的"__init__"]
        OldboyPeople.__init__(self,name,age,sex)  # 指名道姓的訪問父類的函數"__init__",把子類[def __init__(self,name,age,sex,level)]裏面的self,name,age,sex 通過[ OldboyPeople.__init__(self,name,age,sex)]傳值給父類OldboyPeople。
        self.level = level                        # 在子類中新增一個功能level 

    def score(self,stu):
        print('%s is score %s' %(self.name,stu.name))

stu1=OldboyStudent('alex',20,'male')
print(stu1.name)
print(stu1.school)

# alex
# oldboy

tea1=OldboyTeacher('egon',18,'male','10')
print(tea1.name)
print(tea1.level)

# egon
# 10

 

3、如何用繼承      繼承的優點:減少類與類之間的代碼冗餘

子類與父類的繼承關係例子:

class Parent1:   # 父類
    pass

class Parent2:   # 父類
    pass

class Sub1(Parent1):         # 子類
    pass

class Sub2(Parent1,Parent2): # 子類
    pass

print(Sub1.__bases__)        # 查看子類Sub1繼承的父類有哪些
# (<class '__main__.Parent1'>,)

print(Sub2.__bases__)        # 查看子類Sub2繼承的父類有哪些
# (<class '__main__.Parent1'>, <class '__main__.Parent2'>)

 

4、在Python中類分爲兩種

     新式類:==>Python3

                但凡繼承object的類,以及該類的子類都是新式類。

                在Python3中所有的類都是新式類(Python3中所有類的父類都是object)。

     經典類:==>Python2

                沒有繼承object類,以該類的子類都是經典類。

                只有在Python2中才有存在經典類,因爲在python2中沒有顯示繼承任何類,也不會繼承object類。

 

 5、繼承實現的原理 -- 子類重用父類共的兩種方式

  5.1.1、屬性的查找關係,對象先查找自己的名稱空間,如果沒有則去類的名稱空間查找

  例子1:單繼承下,屬性的查找關係

class Foo:
    def f1(self):
        print('Foo f1')

    def f2(self):       # self = obj
        print('Foo f2')
        self.f1()       # self.f1() == obj.f1() ,obj這個對象本身是沒有f1()這個函數的,它需要去類中查找,即Bar(Foo)中函數f1()

class Bar(Foo):
    def B1(self):
        print('Bar B1')

obj=Bar()
obj.f2()

# Foo f2
# Bar f1

 

  5.1.2、多繼承下的繼承順序

   Python中子類可以同時繼承多個父類,如A(B,C,D)

   如果繼承關係爲非菱形結構,則會按照先找B這一條分支,然後再找C這一條分支,最後找D這一條分支的順序直到找到我們想要的屬性

   如果繼承關係爲菱形結構,那麼屬性的查找方式有兩種,分別是:深度優先廣度優先

 

5.1..3、繼承原理 (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.如果對下一個類存在兩個合法的選擇,選擇第一個父類

 

5.1.4、子類中調用父類的方法 -- 多繼承要謹慎使用,多繼承相當於程序的強耦合。

方法一:指名道姓,即父類名.父類方法()

class Vehicle:  # 定義交通工具類
    Country = 'China'

    def __init__(self, name, speed, load, power):
        self.name = name
        self.speed = speed
        self.load = load
        self.power = power

    def run(self):
        print('開動啦...')

class Subway(Vehicle):  # 地鐵
    def __init__(self, name, speed, load, power, line):
        Vehicle.__init__(self, name, speed, load, power)  # 派生方式1--指名道姓的使用"Vehicle"類定義的屬性
        self.line = line

    def run(self):
        print('地鐵%s號線歡迎您' % self.line)
        super(Subway, self).run()

line13 = Subway('中國地鐵', '180m/s', '1000人/箱', '電', 13)
line13.run()

 

方法二:super()    super(自己的類名,self).父類中的方法名()

class Vehicle:  # 定義交通工具類
    Country = 'China'

    def __init__(self, name, speed, load, power):
        self.name = name
        self.speed = speed
        self.load = load
        self.power = power

    def run(self):
        print('開動啦...')

class Subway(Vehicle):  # 地鐵
    def __init__(self, name, speed, load, power, line):
        # 在python3中: super() 等同於 super(Subway,self)
        super(Subway,self).__init__(name, speed, load, power)      # 派生方式2-super(),可以把"super()"理解爲父類,那麼就可以通過“super”直接調用父類裏面的功能了。
        self.line = line

    def run(self):
        print('地鐵%s號線歡迎您' % self.line)
        super(Subway, self).run()

line13 = Subway('中國地鐵', '180m/s', '1000人/箱', '電', 13)
line13.run()

  調用super會得到一個特殊的對象,該對象是專門用來引用父類中的方法的 (該對象會嚴格按照當前類的MRO列表從當前類的父類中依次查找屬性)

  證明:調用super()時,是如何按照MRO列表依次查找屬性的

class A:
    def f1(self):
        print("A")
        super().f2()   # super() 會基於當前所在的查找位置繼續往後查找。 
                       # 從下面的MRO列表中可以看出:super() 會基於<class '__main__.B'>開始查找。
class B:
    def f2(self):
        print("B")

class C(A,B):
    def f2(self):
        print("C")

obj=C()
print(C.mro())
# MRO列表: [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

obj.f1()
# A
# B

強調:指名道姓或者super()二者使用哪一種都可以,但最好不要混合使用

 

5.1.5、類之間的組合使用

1、什麼是組合?

   一個對象屬性的值是來自於另外一個類的對象,這就叫做類的組合使用。

2、爲什麼要用組合?

    組合是用來減少類與類之間的代碼冗餘(這一點和繼承是相同的,只不過繼承是:多個子類屬於一個父類,屬於一個從屬關係,這種的話都可以使用繼承的方式來減少代碼冗餘。但是對於沒有從屬關係的類,想要實現代碼冗餘:這個時候就需要用到組合。)

組合使用例子:OldboyStudent類下的"tell_course_info"對象(函數)裏面的for循環取到的值來自於Course類下面的"tel_info"對象(函數)

# 人類
class OldboyPeople:
    school='Oldboy'
    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age


# 學生類
class OldboyStudent(OldboyPeople):
    def __init__(self,name, sex, age):
        OldboyPeople.__init__(self,name, sex, age)
        self.course=[]   # 課程爲空

    def choose_course(self,course):
        print('%s is choose_course %s' %(self.name,course))

    def tell_course_info(self):     # 循環取出self.course裏面的值,並打印信息
        for course in self.course:  # 在stu1對象裏面,循環取出將python對象和linux對象
            course.tel_info()       # course拿到的就是[python對象和linux對象],然後調用python對象和linux對象下面的tel_info方法

# 課程類
class Course:
    def __init__(self,name,price,period):
        self.name=name
        self.price=price
        self.period=period

    def tel_info(self):
        print("""
        名稱: %s,
        價格: %s,
        週期: %s,
        """ % (self.name,self.price,self.period))
python=Course('Python','8000','5mons')  # 調用課程類拿到一個對象(python)
linux=Course('Linux','10000','3mons')   # 調用課程類拿到一個對象(linux)

# stu1對象的course屬性的值,來自於Course類下面的python對象和linux對象
stu1=OldboyStudent('李三',18,'male')    # 調用學生類拿到一個對象(stu1)

stu1.course.append(python)   # 將python[課程類實例化的對象]這個對象放入到stu1對象內。
stu1.course.append(linux)    # linux[課程類實例化的對象]這個對象放入到stu1對象內。
stu1.tell_course_info()      # 調用學生類裏面的方法,把學生所選的課程信息全部打印出來。

  名稱: Python,
  價格: 8000,
  週期: 5mons,
        
  名稱: Linux,
  價格: 10000,
  週期: 3mons,

 

 5.2、第二大特性:多態與多態性

1、什麼是多態?
    同一種事務的多種形態。

2、爲何要用多態
    多態的特性:可以在不用考慮對象具體類型的情況下,直接調用對象的一些方法。

3、如何用多態

     解釋多態的例子:

import abc

# "Animal"類在使用了"metaclass=abc.ABCMeta"之後無法被實例化(抽象基類不能被實例化)
class Animal(metaclass=abc.ABCMeta):  # 父類存在的意義就是用來定義規範
    @abc.abstractmethod               # 在使用"abc.abstractmethod"方法之後:
    def talk(self):                   # 所有繼承了"Animal"的子類,裏面必須定義一個相同的talk函數,否則報錯。
        pass

    @abc.abstractmethod
    def eat(self):
        print("eat")

class People(Animal):
    def talk(self):
        print("hello")

    def eat(self):
        print("eat")

class Dog(Animal):
    def talk(self):
        print("旺旺")

    def eat(self):
        print("eat")

class Pig(Animal):
    def talk(self):
        print("henhen")

    def eat(self):
        print("eat")


peo1=People()
dog1=Dog()
pig1=Pig()

peo1.talk()
dog1.talk()
pig1.talk()

peo1.eat()
dog1.eat()
pig1.eat()

peo1.eat()
dog1.eat()
pig1.eat()

3.1、Python推崇的是鴨子類型(走起路來像鴨子,叫聲像鴨子,那麼就是鴨子)

class Animal(metaclass=abc.ABCMeta):
    def talk(self):
        pass

# 這裏不用繼承Animal類了,自己在每一個類裏面都定義一個叫talk的方法:
class People:
    def talk(self):
        print("hello")

class Dog:
    def talk(self):
        print("旺旺")

class Pig:
    def talk(self):
        print("henhen")


peo1=People()
dog1=Dog()
pig1=Pig()

peo1.talk()
dog1.talk()
pig1.talk()

 

5.3、第三大特性:封裝

1、什麼是封裝?
    "封"的意思就是藏起來,在內部可以看到,但是對外部是隱藏的。
    "裝"的意思是往一個容器中放入一系列屬性

2、爲何要用封裝 --- 將數據屬性隱藏起來,從而讓類的使用者無法直接操作該數據屬性,只能間接使用[ 通過調用類內部提供的接口的方式來使用, 而接口則由類的開發人員設計。]

   2.1、封裝數據屬性:

     將數據屬性隱藏起來,從而讓類的使用者無法直接操作該數據屬性。
     需要類的設計者在類的內部開闢接口,讓類的使用者用接口來間接地操作數據。
     類的設計者可以在接口上附加任意邏輯,從而嚴格控制類的使用者對類屬性的操作。

    將數據隱藏起來這不是目的。隱藏起來然後對外提供操作該數據的接口,然後我們可以在接口附加上對該數據操作的限制,以此完成對數據屬性操作的嚴格控制。

class People():
    def __init__(self,name,age):
        self.__name=name
        self.__age=age

    def tell_info(self):
        print("<%s:%s>" %(self.__name,self.__age))

peo1=People("sudaa",18)
# print(peo1.__name)  #在外部,無法通過調用對象的方式來查看封裝後的屬性值

peo1.tell_info()   # 只能通過類內部的方法[在類內部可以調用和查看__開頭的屬性],查看屬性的值
<sudaa:18>

# 數據隱藏之後無法調用
print(peo1.name)
print(peo1.__name)

 

   2.2:封裝方法:目的是隔離複雜度

封裝方法舉例: 

  1. 你的身體沒有一處不體現着封裝的概念:你的身體把膀胱尿道等等這些尿的功能隱藏了起來,然後爲你提供一個尿的接口就可以了(接口就是你的。。。,),你總不能把膀胱掛在身體外面,上廁所的時候就跟別人炫耀:hi,man,你瞅我的膀胱,看看我是怎麼尿的。

  2. 電視機本身是一個黑盒子,隱藏了所有細節,但是一定會對外提供了一堆按鈕,這些按鈕也正是接口的概念,所以說,封裝並不是單純意義的隱藏!!!

  3. 快門就是傻瓜相機爲傻瓜們提供的方法,該方法將內部複雜的照相功能都隱藏起來了

提示:在編程語言裏,對外提供的接口(接口可理解爲了一個入口),可以是函數,稱爲接口函數,這與接口的概念還不一樣,接口代表一組接口函數的集合體。

# 取款是功能,而這個功能有很多功能組成:插卡、密碼認證、輸入金額、打印賬單、取錢
# 對使用者來說,只需要知道取款這個功能即可,其餘功能我們都可以隱藏起來,很明顯這麼做
# 隔離了複雜度,同時也提升了安全性
class ATM:
    def __card(self):
        print('插卡')
    def __auth(self):
        print('用戶認證')
    def __input(self):
        print('輸入取款金額')
    def __print_bill(self):
        print('打印賬單')
    def __take_money(self):
        print('取款')

    def withdraw(self):
        self.__card()
        self.__auth()
        self.__input()
        self.__print_bill()
        self.__take_money()

a=ATM()
a.withdraw()

 

3、如何用封裝
    但凡是雙下劃線開頭" __test "(不能是雙下劃線結尾)的屬性,都會被隱藏起來,類內部可以直接使用而類外部無法直接使用封裝是對外不對內的(外部非要訪問的話,通過res._Foo__f1()的方式照樣可以獲取到值)
    這種隱藏的特點: 
        1、只是一種語法上的變形,會將__開頭的屬性變形爲:_自己的類名__屬性名
        2、該變形只在類定義階段發生一次,在類定義階段之後新增的__開頭的屬性並不會發生變形
        3、這種隱藏是對外不對內的
--見例子1
        4、在繼承中,父類如果不想讓子類覆蓋自己的同名方法,可以將方法(函數)定義爲私有的(即給函數加上__開頭)。--見例子3

 例子1:封裝的例子,封裝之後,類裏面定義的函數,在類的外面是無法直接調用的。

class Foo():
    __n=1

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

    def __f1(self):
        print('f1')

res=Foo('szq')

# res.__f1()    # 使用此方法無法訪問對象下的__f1方法

# 問題?如何才能訪問到對象下的__f1方法:
# 1.首先查看類的名稱空間
# print(Foo.__dict__)  # 通過查看Foo類的名稱空間可以看出'_Foo__n'與_Foo__f1'
# {'__module__': '__main__', '_Foo__n': 1, '__init__': <function Foo.__init__ at 0x0000023ECC1EAA60>, '_Foo__f1': <function Foo.__f1 at 0x0000023ECC1EAAE8>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}

# 2.根據名稱空間來訪問對象下的__f1方法
# res._Foo__f1()   # 那麼就需要使用這種方式來獲取類內部的一些屬性和值
# f1

例子2:封裝之後,外部如何調用? --通過在類的內部新建接口的形式!

class Foo():
    __n=1

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

    def __f1(self):
        print('f1')

    def f2(self):     # 類的內部新增一個接口函數f2,通過f2函數來調用"__f1"函數
        self.__f1()

res=Foo('szq')
res.f2()
f1

例子3:在繼承中,父類如果不想讓子類覆蓋自己的同名方法,如何實現?

class Foo:
    def __f1(self):
        print("Foo.f1")

    def f2(self):
        print("Foo.f2")
        self.__f1()

class Bar(Foo):
    def __f1(self):
        print("Bar.f1")

obj=Bar()
obj.f2()

# 沒加__時
# Foo.f2
# Bar.f1

# 加__之後
# Foo.f2
# Foo.f1

 

 4、封裝之property ,類裏面的裝飾器如何使用?

 通過使用property裝飾器:可以將對象的"技能"(函數)僞裝成一個"屬性"(變量),並且可以對這個屬性(變量)做"增,刪,改"的操作。

 例子1:將一個對象的技能(函數)僞裝成一個屬性(變量)

僞裝之前:想要訪問People類下面的tell_name時,只能通過peo1.tell_name()的方式

class People():
    def __init__(self,name):
        self.__name=name

    def tell_name(self):
        return self.__name

peo1=People('sudada')
print(peo1.tell_name())

sudada


僞裝之後:想要訪問People類下面的name時,就可以直接通過peo1.name的方式去訪問,和訪問屬性(變量)的方式是一樣的。
class People():
    def __init__(self,name):
        self.__name=name

    @property
    def name(self):
        return self.__name

peo1=People('sudada')
print(peo1.name)

sudada

  例子2:將一個對象的技能(函數)僞裝成一個屬性(變量),並且做 "增,刪,改" 的操作

class People():
    def __init__(self,name):
        self.__name=name

    @property              # 將技能(函數)僞裝爲一個屬性(變量),可以進行查看
    def name(self):
        return self.__name

    @name.setter           # 由於name已經被僞裝爲一個屬性(變量),那麼就得有修改這個屬性(變量)值的方法。就是"@name.setter"
    def name(self,val):
        if type(val) is not str:
            raise TypeError('名字的值必須爲str類型')
        self.__name=val

    @name.deleter          # 刪除僞裝後的屬性(變量)的方法"name.deleter"
    def name(self):
        print('無法刪除!')

peo1=People('sudada')
peo1.name='szq'            # 在使用"@name.setter"裝飾器之後,就可以通過peo1.name='szq'的方式修改name的值了。
print(peo1.name)
# szq

del peo1.name    # 刪除操作,觸發"name.deleter",並拿到返回值
# 無法刪除!

 

5、綁定方法與非綁定方法

1、綁定方法  --  classmethod
    特點:綁定給誰,就應該有誰來調用,誰來調用就會將誰當做第一個參數傳入。
    
    1.綁定到對象的方法:
        在類中定義的函數,在沒有被任何裝飾器裝飾的情況下,默認都是綁定給對象的。
    
    2.綁定到類的方法:
        在類中定義的函數,在被裝飾器classmethod裝飾的情況下,該方法是綁定類的,由類來調用

  綁定到類的例子:最常用的應用場景--提供額外的實例化方式 

import settings   # 導入配置文件
'''
settings配置文件內容如下:
HOST='127.0.0.1'
PORT=3306
'''


class MySQL():
    def __init__(self,host,port):
        self.host=host
        self.port=port

    @classmethod          # 提供一種額外的實例化方式,從配置文件中讀取配置的方式來完成傳參的一種方式
    def from_conf(cls):   # cls是自動補全的,和self一樣。這裏的cls補全的是類(MySQL)。[哪個類去調用就傳誰入哪個了類]
        return cls(settings.HOST,settings.PORT)     
        # cls(settings.HOST,settings.PORT) == MySQL(settings.HOST,settings.PORT)就是一次實例化,拿到的是一個對象。

# 方式一:使用類的綁定方法拿到的對象,MySQL.from_conf()獲取到的是一個對象,這個對象是通過"cls(settings.HOST,settings.PORT)"(實例化)得到的。
conn1=MySQL.from_conf()
print(conn1.host,conn1.port)
# 127.0.0.1 3306

# 方式二:實例化類,獲取到對象的方式
conn=MySQL('1.1.1.1',3306)
print(conn.host,conn.port)
# 1.1.1.1 3306

  2、與非綁定方法(得到的就是一個普通的函數,誰都可以調用)  --  staticmethod
    特點:即不與類綁定也不與對象綁定,沒有任何自動傳值的效果,因爲函數體根本也不需要。

import hashlib
import time

class Mysql:
    def __init__(self,host,port):
        self.host=host
        self.port=port

    @staticmethod
    def create_id():        # 這裏不需要傳入self,也就是不需要綁定任何類或者對象。
        m=hashlib.md5()
        m.update(str(time.clock()).encode("utf-8"))
        return m.hexdigest()

obj=Mysql("1.1.1.1",3306)
print(obj.create_id())        # 對象可以調用create_id函數
print(Mysql.create_id())      # 類也可以調用create_id函數

 

六、內置方法之反射

1、什麼是反射?

   指的是通過字符串來操作類或者對象的屬性。

例子:反射涉及到的四個內置函數

# 先定義類
class People():
    country='China'
    def __init__(self,name):
        self.name=name
    def foo(self):
        print("from foo")
peo=People("sudada")

涉及到的四個內置函數

一、hasattr   # 查看一個類是否存在某個屬性
# 方法一:通過查看"People"類的名稱空間的方式查看"country"這個屬性是否存在
print("country" in People.__dict__)
# 方法二:本質是通過方式一得來的
print(hasattr(People,'country'))


二、getattr  # 獲取一個類某個屬性的值
# 方法一:通過查看"People"類的名稱空間的方式獲取"country"這個屬性的值
print(People.__dict__['country'])
# 方法二:
print(getattr(People,'country',None))   # 返回變量值
print(getattr(People,"foo"))            # 返回函數的內存地址
# People類存在country這個屬性的話,則返回屬性對應的值(變量就返回值,函數則返回函數的內存地址),否則返回None


三、setattr  # 設置一個類某個屬性的值
setattr(People,'x',111)
print(People.x)
# 111


四、delattr  # 刪除一個類某個屬性
delattr(People,'country')
print(People.__dict__)

  2、反射的應用場景: -- 通過輸入命令的方式獲取類裏面某個屬性的值

class FTP():
    def get(self):
        print('get...')

    def put(self):
        print('put...')

    def run(self):
        while True:
            cmd=input('>>: ').strip()   # 用戶輸入命令
            if hasattr(self,cmd):       # 判斷輸入的"命令"是否存在於FTP這個類的屬性裏面
                res=getattr(self,cmd)   # 如果輸入的"命令"存在的話,那麼就通過getattr的方法拿到FTP這個類對應的屬性,並拿到返回結果
                res()                   # 把拿到的結果加括號運行,從而得到屬性的值
            else:
                print('命令不存在')

obj=FTP()
obj.run()

 

七、內置方法

  內置方法一: __str__  當對象被打印時(print(對象名)),顯示__str__函數的返回值。

class People():
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex

    def __str__(self):      # 在實例化對象後,只要使用"print(對象)",就會觸發這個函數,並且會執行這個函數。
        return '["%s",%s,"%s"]' %(self.name,self.age,self.sex)

peo1=People('sudaa',18,'male')
print(peo1)
# ["sudaa",18,"male"]

 

  內置方法二: __del__   當對象被刪除之前(當前程序代碼結束運行之前),會自動觸發

   哪些地方會用到__del__:當一個Python變量關聯了系統變量時,涉及到系統回收資源的情況下,會用到__del__。

class Foo():
    def __init__(self,x):
        self.x=x

    def __del__(self):   # 當一個對象的代碼執行完畢之後,就會觸發這個規則(執行這個函數)
        print('del...')

obj=Foo(111)
print('====>')   # 先執行這個代碼,然後執行__del__代碼
# ====>
# del...


# 僞代碼:當MySQL服務器調用完畢後,關閉系統資源
class MySQL():
    def __init__(self,host,port):
        self.host=host
        self.port=port
        self.conn=connect(host,port)

    def __del__(self):
        self.conn.close

obj=MySQL('1.1.1.',3306)

 

八、內置函數補充  --  詳見第8接15講

 

九、面向對象相關補充:metaclass(一)

  9.1、創建類的2中方式

# 方式一
class Foo(object):
    CITY = "bj"

    def func(self,x):
        return x+1

# 方式二
Coo=type("Coo",(object,),{"CITY":"bj","func":lambda self,x:x+1})

  9.2、類由自定義的type創建,通過metaclass可以指定當前的類由哪一個type創建。 

class MyType(type):
    def __init__(self,*args,**kwargs):
        print("創建類之前")
        super().__init__(*args,**kwargs)
        print("創建類之後")


class Foo(object,metaclass=MyType):  # 當前類,由type類創建(每個類默認metaclass=type)
    CITY = "bj"

    def func(self,x):
        return x+1

# 右鍵運行後的效果如下,雖然沒有實例化對象,但是metaclass=MyType時,會創建"MyType"這個類
# 創建類之前
# 創建類之後

# 執行過程:
1.MyType的__init__方法
2.MyType的__call__方法
3.Foo的__new__方法
4.Foo的__init__方法

  9.3、總結metaclass

     1.默認類都是由type實例化創建。

     2.某個類指定mrtaclass=MyType,那麼當前類的所有派生類(子類)都由MyType創建。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章