1. 程式人生 > >Python 全棧開發:python面向對象三大特征

Python 全棧開發:python面向對象三大特征

之前 instance strac 存放位置 多功能 bst bee 經典 位置

目錄:

  • 繼承

  • 封裝

  • 多態

一、繼承

1.什麽繼承

繼承是一種創建新類的方式,新建的類可以繼承一個或多個父類(python支持多繼承),父類又可稱為基類或超類,新建的類稱為派生類或子類。

2.為什麽要有繼承

子類會“”遺傳”父類的屬性,從而解決代碼重用問題,減少代碼的冗余

3.怎麽應用繼承

eg:

class ParentClass1: # 定義父類1
    pass

class ParentClass2: # 定義父類2
    pass

class Subclass1(ParentClass1): # 單繼承 父類1
    pass

class Subclass2(ParentClass1,ParentClass2):  #
多繼承多個父類 父類1 父類2 pass print(Subclass1.__bases__) # 查看所有父類信息 print(Subclass2.__bases__) # 查看所有父類信息

結果:以元組的形式返回
(<class ‘__main__.ParentClass1‘>,)
(<class ‘__main__.ParentClass1‘>, <class ‘__main__.ParentClass2‘>)

4.繼承與抽象(先抽象再繼承)

先抽象:抽取對象之間相似之處得到了類,在總結類與類之間的相似得到父類

再繼承:(子類繼承父類,子類可以遺傳父類屬性)是基於抽象的結果,通過編程語言去實現它,肯定是先經歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構。

5.派生與重用

派生:子類定義自己新的屬性,如果與父類同名,以子類自己的為準

技術分享圖片
# 父類
class People:
    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex

    def func1(self):
        print(People.func1)


# 子類 派生自己的屬性
class Teacher(People):
    def __init__(self, name, age, sex, level, salary):
        self.name 
= name self.age = age self.sex = sex self.level = level self.salary = salary def func1(self): print(Teacher.func1) # 實例化 tea1 = Teacher(fixd, 18, male, 9, 3.1) print(tea1.name, tea1.age, tea1.sex, tea1.level, tea1.salary) # 結果 fixd 18 male 9 3.1
示例代碼

重用:在子類派生出的新方法中重用父類的功能

方式一:指名道姓地調用(其實與繼承沒有什麽關系的)

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


# 子類 "指名道姓" 調用父類的屬性
class Teacher(People):
    def __init__(self, name, age, sex, level, salary):
        People.__init__(self, name, age, sex)
        self.level = level
        self.salary = salary

方式二、super()調用(嚴格依賴於繼承)

  ps:super()的返回值是一個特殊的對象,該對象專門用來調用父類中的屬性

  了解:在python2中,需要super(自己的類名,self)

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


# 子類 super() 調用父類的屬性
class Teacher(People):
    def __init__(self, name, age, sex, level, salary):       
        super().__init__(name, age, sex)
        self.level = level
        self.salary = salary 

註意:以上兩種方式都可以使用,在實際的編碼工作中,推薦使用統一的一種方式

技術分享圖片
#super()會嚴格按照mro列表從當前查找到的位置繼續往後查找
class A:
    def test(self):
        print(A.test)
        super().f1()
class B:
    def f1(self):
        print(from B)
class C(A,B):
    pass

c=C()
print(C.mro()) #C->A->B->object


c.test()
mro列表

5.屬性查找

‘‘‘
1、新式類:
    繼承object的類,以及該類的子類,都是新式類

    在python3中,如果一個類沒有指定繼承的父類,默認就繼承object
    所以說python3中所有的類都是新式類

2、經典類(只有在python2才區分經典類與新式類):
    沒有繼承object的類,以及該類的子類,都是經典類
‘‘‘

單繼承名稱空間的查找順序:

對象自身-------->>當前類-------->>父類-------->>object # 查找不到,報錯

多繼承名稱空間的查找順序:

技術分享圖片

技術分享圖片

在菱形繼承的背景下,查找屬性
1、經典類:深度優先
2、新式類:廣度優先

二、封裝

1.什麽是封裝

字面上的意思就是,把東西隱藏起來了。

在python中的封裝就是把 類中的屬性(變量、函數)隱藏起來

2.為什麽要用封裝

封裝的真諦在於明確內外

對外隱藏(類的外部只能通過我們提供的接口對類內部的隱藏屬性就行訪問)

對內開放(在類的內部可以直接使用隱藏屬性)

封裝數據(變量)將數據隱藏並不是我們的目的,可以通過接口的方式將數據暴露給類外面使用,在接口中我們可以對數據進行限制,完成對數據的控制

技術分享圖片
class Teacher:
    def __init__(self,name,age):
        # self.__name=name
        # self.__age=age
        self.set_info(name,age)

    def tell_info(self):
        print(姓名:%s,年齡:%s %(self.__name,self.__age))
    def set_info(self,name,age):
        if not isinstance(name,str):
            raise TypeError(姓名必須是字符串類型)
        if not isinstance(age,int):
            raise TypeError(年齡必須是整型)
        self.__name=name
        self.__age=age


t=Teacher(egon,18)
t.tell_info()

t.set_info(egon,19)
t.tell_info()
示例代碼

封裝方法(函數)主要目的隔離復雜度,將類中多個函數組合,提供一個對外封裝好的接口,供使用者調用,而調用者無需考慮接口內復雜的實現過程,簡化調用

技術分享圖片
#取款是功能,而這個功能有很多功能組成:插卡、密碼認證、輸入金額、打印賬單、取錢
#對使用者來說,只需要知道取款這個功能即可,其余功能我們都可以隱藏起來,很明顯這麽做
#隔離了復雜度,同時也提升了安全性

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.怎麽用封裝

在python中用雙下劃線開頭的方式將屬性隱藏起來(設置成私有的)

#其實這僅僅這是一種變形操作且僅僅只在類定義階段發生變形
#類中所有雙下劃線開頭的名稱如__x都會在類定義時自動變形成:_類名__x的形式:

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

#A._A__N是可以訪問到的,
#這種,在外部是無法通過__x這個名字訪問到。

PS:

1.這種機制也並沒有真正意義上限制我們從外部直接訪問屬性,知道了類名和屬性名就可以拼出名字:_類名__屬性,然後就可以訪問了,如a._A__N,即這種操作並不是嚴格意義上的限制外部訪問,僅僅只是一種語法意義上的變形,主要用來限制外部的直接訪問。

2.變形的過程只在類的定義時發生一次,在定義後的賦值操作,不會變形

技術分享圖片

3.在繼承中,父類如果不想讓子類覆蓋自己的方法,可以將方法定義為私有的

技術分享圖片
#正常情況
>>> class A:
...     def fa(self):
...         print(from A)
...     def test(self):
...         self.fa()
... 
>>> class B(A):
...     def fa(self):
...         print(from B)
... 
>>> b=B()
>>> b.test()
from B
 

#把fa定義成私有的,即__fa
>>> 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()
from A
示例代碼

4.特性(property)

什麽是特性

property是一種特殊的屬性,訪問它時會執行一段功能(函數)然後返回值

eg:

技術分享圖片
BMI指數(bmi是計算而來的,但很明顯它聽起來像是一個屬性而非方法,如果我們將其做成一個屬性,更便於理解)

成人的BMI數值:
過輕:低於18.5
正常:18.5-23.9
過重:24-27
肥胖:28-32
非常肥胖, 高於32
  體質指數(BMI)=體重(kg)÷身高^2(m)
  EX:70kg÷(1.75×1.75)=22.86
功能描述 技術分享圖片
class People:
    def __init__(self,name,weight,height):
        self.name=name
        self.weight=weight
        self.height=height

    @property
    def bmi(self):
        return self.weight / (self.height * self.height)

# egon=People(‘egon‘,75,1.80)
#
# egon.bmi=egon.weight / (egon.height * egon.height)
# print(egon.bmi)
#
# yl=People(‘yangli‘,85,1.74)
# yl.bmi=yl.weight / (yl.height * yl.height)
# print(yl.bmi)


# 首先需要明確。bmi是算出來的,不是一個固定死的值,也就說我們必須編寫一個功能,每次調用該功能
#都會立即計算一個值
egon=People(egon,75,1.80)
yl=People(yangli,85,1.74)

# 但很明顯人的bmi值聽起來更像一個名詞而非動詞
# print(egon.bmi())
# print(yl.bmi())


# 於是我們需要為bmi這個函數添加裝飾器,將其偽裝成一個數據屬性
# egon.weight=70
# print(egon.bmi) #21.604938271604937,調用egon.bmi本質就是觸發函數bmi的執行,從而拿到其返回值
# print(yl.bmi)
示例代碼

為什麽要用特性

將一個類的函數定義成特性以後,對象再去使用的時候obj.name,根本無法察覺自己的name是執行了一個函數然後計算出來的,這種特性的使用方式遵循了統一訪問的原則

PS:

【public】
這種其實就是不封裝,是對外公開的
【protected】
這種封裝方式對外不公開,但對朋友(friend)或者子類(形象的說法是“兒子”,
但我不知道為什麽大家 不說“女兒”,就像“parent”本來是“父母”的意思,但中文都是叫“父類”)公開 【private】 這種封裝對誰都不公開

python並沒有在語法上把它們三個內建到自己的class機制中,可以通過property方法實現

技術分享圖片
class Foo:
    def __init__(self,val):
        self.__NAME=val #將所有的數據屬性都隱藏起來

    @property
    def name(self):
        return self.__NAME #obj.name訪問的是self.__NAME(這也是真實值的存放位置)

    @name.setter
    def name(self,value):
        if not isinstance(value,str):  #在設定值之前進行類型檢查
            raise TypeError(%s must be str %value)
        self.__NAME=value #通過類型檢查後,將值value存放到真實的位置self.__NAME

    @name.deleter
    def name(self):
        raise TypeError(Can not delete)

f=Foo(egon)
print(f.name)
# f.name=10 #拋出異常‘TypeError: 10 must be str‘
del f.name #拋出異常‘TypeError: Can not delete‘
示例代碼

三、多態

1、什麽是多態

多態指的是同一種事物多種形態

2、為什麽要用多態

用基類創建一套統一的規則,強制子類去遵循(使用抽象類實現),這樣便可以
在不用考慮對象具體類型的前提下而直接使用對象下的方法

3、如何用多態

動物有多種形態:人,狗,豬

技術分享圖片
import abc
class Animal(metaclass=abc.ABCMeta): #同一類事物:動物
    @abc.abstractmethod
    def talk(self):
        pass

class People(Animal): #動物的形態之一:人
    def talk(self):
        print(say hello)

class Dog(Animal): #動物的形態之二:狗
    def talk(self):
        print(say wangwang)

class Pig(Animal): #動物的形態之三:豬
    def talk(self):
        print(say aoao)
Animal

文件有多種形態:文本文件,可執行文件

技術分享圖片
import abc
class File(metaclass=abc.ABCMeta): #同一類事物:文件
    @abc.abstractmethod
    def click(self):
        pass

class Text(File): #文件的形態之一:文本文件
    def click(self):
        print(open file)

class ExeFile(File): #文件的形態之二:可執行文件
    def click(self):
        print(execute file)
File

PS:

1.抽象基類是不能實例化的

2.繼承抽象基類的類,必須重寫其抽象父類中的抽象方法

4、多態性

1.什麽是多態動態綁定(在繼承的背景下使用時,又稱為多態性)

多態性是指在不考慮實力類型的情況下使用實例

技術分享圖片
在面向對象方法中一般是這樣表述多態性:向不同的對象發送同一條消息(!!!obj.func():是調用了obj的方法func,又稱為向obj發送了一條消息func),不同的對象在接收時會產生不同的行為(即方法)。也就是說,每個對象可以用自己的方式去響應共同的消息。所謂消息,就是調用函數,不同的行為就是指不同的實現,即執行不同的函數。

比如:老師.下課鈴響了(),學生.下課鈴響了(),老師執行的是下班操作,學生執行的是放學操作,雖然二者消息一樣,但是執行的效果不同
解釋

2.為什麽要用多態性(多態性的好處)

  • 增加了程序的靈活性
以不變應萬變,不論對象千變萬化,使用者都是同一種形式去調用
  • 增加了程序的可擴展性
通過繼承animal類創建了一個新的類,使用者無需更改自己的代碼,還是用func(animal)去調用 
技術分享圖片
>>> class Cat(Animal): #屬於動物的另外一種形態:貓
...     def talk(self):
...         print(say miao)
... 
>>> def func(animal): #對於使用者來說,自己的代碼根本無需改動
...     animal.talk()
... 
>>> cat1=Cat() #實例出一只貓
>>> func(cat1) #甚至連調用方式也無需改變,就能調用貓的talk功能
say miao

‘‘‘
這樣我們新增了一個形態Cat,由Cat類產生的實例cat1,使用者可以在完全不需要修改自己代碼的情況下。使用和人、狗、豬一樣的方式調用cat1的talk方法,即func(cat1)
‘‘‘
示例代碼

3.鴨子類型

Python中崇尚鴨子類型(如果看起來像,叫聲像而且走起路像鴨子,那麽它就是鴨子)

通過繼承實現的多態性,具有強耦合性

鴨子類型,通過創建一個外觀和行為像,但與原類型毫無關系的全新對象,具有松耦合性

技術分享圖片
#其實大家一直在享受著多態性帶來的好處,
#比如Python的序列類型有多種形態:字符串,列表,元組,多態性的體現
#
#str,list,tuple都是序列類型
s=str(hello)
l=list([1,2,3])
t=tuple((4,5,6))

#我們可以在不考慮三者類型的前提下使用s,l,t
s.__len__()
l.__len__()
t.__len__()

len(s)
len(l)
len(t)
示例代碼

Python 全棧開發:python面向對象三大特征