2.1.1 Python物件導向三大特性

花姐毛毛腿發表於2019-02-28

點選跳轉Python筆記總目錄

Python物件導向三大特性

1,繼承

1,繼承和組合

2,封裝

3,多型


一,python特性之 繼承

1,繼承和組合

1.1、組合

組合:組合指的是,在一個類中以另外一個類的物件(也就是例項)作為資料屬性,稱為類的組合
   也就是說:一個類的屬性是另一個類的物件,就是組合
例子:
  圓環是由兩個圓組成的,圓環的面積就是外圓的面積減去內圓的面積。圓環的周長就是內圓的周長加上外圓的周長,這個時候,我們首先設計一個圓形類,計算一個圓的面積和圓的周長。然後在‘圓環類’組合圓形的例項作為自己的屬性來用(這樣的目的就是為了不用在寫面積和周長的方法了,直接組合圓類的面積和方法去求解。減少了程式碼的重用)
例: 求圓環的面積和周長

from math import pi
class Circle:
    def __init__(self,r):
        self.r=r
    def perimater(self):
        return 2*pi*self.r
    def area(self):
        return pi*self.r*self.r
print(Circle(2).perimater())
print(Circle(3).area())

class Circle_ring: #定義一個圓環類
    def __init__(self,outside_r,inside_r):
        outside_bijiao = max(outside_r,inside_r)
        intside_bijiao = min(outside_r, inside_r)
        self.outsize_circle = Circle(outside_bijiao) #例項化一個大圓形  作為self.outside_circle屬性的值
        self.intsize_circle = Circle(intside_bijiao) #例項化一個小圓環
    def area(self):
        return self.outsize_circle.area()-self.intsize_circle.area()
    def perimater(self):
        return self.intsize_circle.perimater()+self.outsize_circle.perimater()


r1 = Circle_ring(10,20)  #例項化
print(r1.area())
print(r1.perimater())
複製程式碼

組合的兩種方式:
1.在__init__方法裡面組合
2.在外面組合

例1:在__init__裡面組合

class BirthDate:
    def __init__(self,year,month,day):
        self.year=year
        self.month = month
        self.day = day
class Course:
    def __init__(self,name,price,period): #period為週期
        self.name =name
        self.price = price
        self.period = period
class Teacher:
    def __init__(self,name,salary,year,month,day,price,period): #那麼這個裡面也要把該有的屬性傳進去
        self.birth = BirthDate(year,month,day) #在裡面組合(將BirthDate裡面有的屬性傳入進去)
        self.course=Course(name,price,period)
        self.name = name
        self.salary = salary
# 例項化方法一:
egg = Teacher(`egon`,2000,1999,12,2,`6 months`,`15800`)  #也要例項化,Teacher類裡面的屬性都得例項化
print(egg.birth.month)  #當然老師也有生日,就讓egg.birth.month
print(egg.course.period)

# 例項化方法二:
egg.birth=BirthDate(1996,22,4)
print(egg.birth.month)
複製程式碼

例2: 在類外面例項化組合

class BirthDate:
    def __init__(self,year,month,day):
        self.year=year
        self.month = month
        self.day = day
class Course:
    def __init__(self,name,price,period): #period為週期
        self.name =name
        self.price = price
        self.period = period
class Teacher:
    def __init__(self,name,salary,course):
        self.name = name
        self.salary = salary
        self.course = course
#
# #在外面組合。(組合就是一個類的屬性是另一個類的物件)

egg = Teacher(`egon`,2000,`python`)
egg.birth=BirthDate(1996,22,4) #直接給egg一個birth的屬性,
print(egg.birth.year)

egg.course =Course(`python`,`6 months`,15800)
print(egg.course.period)
複製程式碼

1.2,繼承

1.繼承是一種建立新類的方式
2.新建的類可以建立一個或多個父類,父類有稱為基類或者超類
3.新建的類稱為派生類或者子類

在python中類的繼承分為:單繼承 多繼承

class ParentClass1: #定義父類
    pass
 
class ParentClass2: #定義父類
    pass
 
class SubClass1(ParentClass1): 
	#單繼承,基類是ParentClass1,派生類是SubClass
    pass
 
class SubClass2(ParentClass1,ParentClass2):
	#多繼承,用逗號分隔開多個繼承的類
    pass
複製程式碼

4.檢視所有繼承的父類
__base __
只檢視從左到右繼承的第一個子類

class ParentClass1:  # 定義父類
    pass


class ParentClass2:  # 定義父類
    pass


class SubClass1(ParentClass1, ParentClass2):
    pass


print(SubClass1.__base__)
#輸出:
<class `__main__.ParentClass1`>
複製程式碼

__bases __
檢視所有繼承的父類

class ParentClass1:  # 定義父類
    pass


class ParentClass2:  # 定義父類
    pass


class SubClass1(ParentClass1, ParentClass2):
    pass


print(SubClass1.__bases__)
#輸出:
(<class `__main__.ParentClass1`>, <class `__main__.ParentClass2`>)
複製程式碼

如果沒有指定父類,python會預設繼承object類,object是所有python的父類.

經典類: 在python2中,class Dad: 不會繼承object,這樣的類叫做經典類(它叫經典類,不是因為它經典,而是因為它比較老)
新式類: 在python3中,python會預設繼承object類(一切皆物件)class Dad 就相當於python2中的 class Dad(object) #新式類
而且python3中沒有經典類了

5.繼承與抽象(先抽象後繼承)

抽象即抽取類似或者說比較像的部分。
抽象分成兩個層次:

  1. 將奧巴馬和梅西這倆物件比較像的部分抽取成類;
  2. 將人,豬,狗這三個類比較像的部分抽取成父類。

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

在這裡插入圖片描述

繼承: 是基於抽象的結果,通過程式語言去實現它,肯定是先經歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構。
抽象只是分析和設計的過程中,一個動作或者說一種技巧,通過抽象可以得到類

在這裡插入圖片描述

使用繼承來解決程式碼重用的例子:

第一部分
例如

&emsp;&emsp;貓可以:吃、喝、爬樹

&emsp;&emsp;狗可以:吃、喝、看家

如果我們要分別為貓和狗建立一個類,那麼就需要為 貓 和 狗 實現他們所有的功能,虛擬碼如下:


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

    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


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

&emsp;&emsp;動物:吃、喝

&emsp;&emsp;   貓:爬樹(貓繼承動物的功能)

&emsp;&emsp;   狗:看家(狗繼承動物的功能)

虛擬碼如下:
class 動物:

    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)

class Cat(Animal):

    def __init__(self, name):
        self.name = name
        self.breed = `貓`

    def climb(self):
        print(`爬樹`)

class Dog(Animal):

    def __init__(self, name):
        self.name = name
        self.breed=`狗`

    def look_after_house(self):
        print(`汪汪叫`)


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

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

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

d1 = Dog(`胖子家的小瘦狗`)
d1.eat()
複製程式碼

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

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

通過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的所有屬性(資料屬性和函式屬性),實現程式碼重用

class Animal:
    ```
    人和狗都是動物,所以創造一個Animal基類
    ```
    def __init__(self, name, aggressivity, life_value):
        self.name = name  # 人和狗都有自己的暱稱;
        self.aggressivity = aggressivity  # 人和狗都有自己的攻擊力;
        self.life_value = life_value  # 人和狗都有自己的生命值;

    def eat(self):
        print(`%s is eating`%self.name)

class Dog(Animal):
    pass

class Person(Animal):
    pass

egg = Person(`maotui`,10,1000)
ha2 = Dog(`二愣子`,50,1000)
egg.eat()
ha2.eat()
複製程式碼

提示: 用已經有的類建立一個新的類,這樣就重用了已經有的軟體中的一部分設定大部分,大大生了程式設計工作量,這就是常說的軟體重用,不僅可以重用自己的類,也可以繼承別人的,比如標準庫,來定製新的資料型別,這樣就是大大縮短了軟體開發週期,對大型軟體開發來說,意義重大.

6.派生:(相對論)

子類也可以新增自己新的屬性或者在自己這裡重新定義這些屬性(不會影響到父類),需要注意的是,一旦重新定義了自己的屬性且與父類重名,那麼呼叫新增的屬性時,就以自己為準了。

class Animal:
    ```
    人和狗都是動物,所以創造一個Animal基類
    ```
    def __init__(self, name, aggressivity, life_value):
        self.name = name  # 人和狗都有自己的暱稱;
        self.aggressivity = aggressivity  # 人和狗都有自己的攻擊力;
        self.life_value = life_value  # 人和狗都有自己的生命值;

    def eat(self):
        print(`%s is eating`%self.name)

class Dog(Animal):
    ```
    狗類,繼承Animal類
    ```
    def bite(self, people):
        ```
        派生:狗有咬人的技能
        :param people:  
        ```
        people.life_value -= self.aggressivity

class Person(Animal):
    ```
    人類,繼承Animal
    ```
    def attack(self, dog):
        ```
        派生:人有攻擊的技能
        :param dog: 
        ```
        dog.life_value -= self.aggressivity

egg = Person(`maotui`,10,1000)
ha2 = Dog(`二愣子`,50,1000)
print(ha2.life_value)
print(egg.attack(ha2))
print(ha2.life_value)
複製程式碼

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

在子類中,新建的重名的函式屬性,在編輯函式內功能的時候,有可能需要重用父類中重名的那個函式功能,應該是用呼叫普通函式的方式,即:類名.func(),此時就與呼叫普通函式無異了,因此即便是self引數也要為其傳值

在python3中,子類執行父類的方法也可以直接用super方法.
幫你瞭解super

class A:
    def hahaha(self):
        print(`A`)

class B(A):
    def hahaha(self):
        super().hahaha()
        #super(B,self).hahaha()
        #A.hahaha(self)
        print(`B`)

a = A()
b = B()
b.hahaha()
super(B,b).hahaha()
複製程式碼
class Animal:
    ```
    人和狗都是動物,所以創造一個Animal基類
    ```
    def __init__(self, name, aggressivity, life_value):
        self.name = name  # 人和狗都有自己的暱稱;
        self.aggressivity = aggressivity  # 人和狗都有自己的攻擊力;
        self.life_value = life_value  # 人和狗都有自己的生命值;

    def eat(self):
        print(`%s is eating`%self.name)

class Dog(Animal):
    ```
    狗類,繼承Animal類
    ```
    def __init__(self,name,breed,aggressivity,life_value):
        super().__init__(name, aggressivity, life_value) #執行父類Animal的init方法
        self.breed = breed  #派生出了新的屬性

    def bite(self, people):
        ```
        派生出了新的技能:狗有咬人的技能
        :param people:  
        ```
        people.life_value -= self.aggressivity

    def eat(self):
        # Animal.eat(self)
        #super().eat()
        print(`from Dog`)

class Person(Animal):
    ```
    人類,繼承Animal
    ```
    def __init__(self,name,aggressivity, life_value,money):
        #Animal.__init__(self, name, aggressivity, life_value)
        #super(Person, self).__init__(name, aggressivity, life_value)
        super().__init__(name,aggressivity, life_value)  #執行父類的init方法
        self.money = money   #派生出了新的屬性

    def attack(self, dog):
        ```
        派生出了新的技能:人有攻擊的技能
        :param dog: 
        ```
        dog.life_value -= self.aggressivity

    def eat(self):
        #super().eat()
        Animal.eat(self)
        print(`from Person`)

egg = Person(`egon`,10,1000,600)
ha2 = Dog(`二愣子`,`哈士奇`,10,1000)
print(egg.name)
print(ha2.name)
egg.eat()
複製程式碼

通過繼承建立了派生類與基類之間的關係,它是一種`是`的關係,比如白馬是馬,人是動物。
當類之間有很多相同的功能,提取這些共同的功能做成基類,用繼承比較好,比如教授是老師

class Teacher:
    def __init__(self,name,gender):
        self.name=name
        self.gender=gender
    def teach(self):
        print(`teaching`)


class Professor(Teacher):
    pass

p1=Professor(`egon`,`male`)
p1.teach()

複製程式碼

7.抽象類與介面類:
介面類
繼承有兩種用途:

  1. 繼承基類的方法,並且做出自己的改變或者擴充套件(程式碼重用)

  2. 宣告某個子類相容於某基類,定義一個介面類Interface,介面類中定義了一些介面名(就是函式名)且並未實現介面的功能,子類繼承介面類,並且實現介面中的功能

class Alipay:
    ```
    支付寶支付
    ```
    def pay(self,money):
        print(`支付寶支付了%s元`%money)

class Applepay:
    ```
    apple pay支付
    ```
    def pay(self,money):
        print(`apple pay支付了%s元`%money)


def pay(payment,money):
    ```
    支付函式,總體負責支付
    對應支付的物件和要支付的金額
    ```
    payment.pay(money)


p = Alipay()
pay(p,200)
複製程式碼

開發中容易出現的問題

class Alipay:
    ```
    支付寶支付
    ```
    def pay(self,money):
        print(`支付寶支付了%s元`%money)

class Applepay:
    ```
    apple pay支付
    ```
    def pay(self,money):
        print(`apple pay支付了%s元`%money)

class Wechatpay:
    def fuqian(self,money):
        ```
        實現了pay的功能,但是名字不一樣
        ```
        print(`微信支付了%s元`%money)

def pay(payment,money):
    ```
    支付函式,總體負責支付
    對應支付的物件和要支付的金額
    ```
    payment.pay(money)


p = Wechatpay()
pay(p,200)   #執行會報錯
複製程式碼

介面初成:手動報異常:NotImplementedError來解決開發中遇到的問題

class Payment:
    def pay(self):
        raise NotImplementedError

class Wechatpay(Payment):
    def fuqian(self,money):
        print(`微信支付了%s元`%money)


p = Wechatpay()  #這裡不報錯
pay(p,200)      #這裡報錯了
複製程式碼

借用abc模組來實現介面

from abc import ABCMeta,abstractmethod

class Payment(metaclass=ABCMeta):
    @abstractmethod
    def pay(self,money):
        pass


class Wechatpay(Payment):
    def fuqian(self,money):
        print(`微信支付了%s元`%money)

p = Wechatpay() #不調就報錯了
複製程式碼

實踐中,繼承的第一種含義意義並不很大,甚至常常是有害的。因為它使得子類與基類出現強耦合。

繼承的第二種含義非常重要。它又叫“介面繼承”。
介面繼承實質上是要求“做出一個良好的抽象,這個抽象規定了一個相容介面,使得外部呼叫者無需關心具體細節,可一視同仁的處理實現了特定介面的所有物件”——這在程式設計上,叫做歸一化。

歸一化使得高層的外部使用者可以不加區分的處理所有介面相容的物件集合——就好象linux的泛檔案概念一樣,所有東西都可以當檔案處理,不必關心它是記憶體、磁碟、網路還是螢幕(當然,對底層設計者,當然也可以區分出“字元裝置”和“塊裝置”,然後做出針對性的設計:細緻到什麼程度,視需求而定)。
為何要用介面
介面提取了一群類共同的函式,可以把介面當做一個函式的集合。
然後讓子類去實現介面中的函式。
這麼做的意義在於歸一化,什麼叫歸一化,就是隻要是基於同一個介面實現的類,那麼所有的這些類產生的物件在使用時,從用法上來說都一樣。
歸一化,讓使用者無需關心物件的類是什麼,只需要的知道這些物件都具備某些功能就可以了,這極大地降低了使用者的使用難度。
比如:我們定義一個動物介面,介面裡定義了有跑、吃、呼吸等介面函式,這樣老鼠的類去實現了該介面,松鼠的類也去實現了該介面,由二者分別產生一隻老鼠和一隻松鼠送到你面前,即便是你分別不到底哪隻是什麼鼠你肯定知道他倆都會跑,都會吃,都能呼吸。
再比如:我們有一個汽車介面,裡面定義了汽車所有的功能,然後由本田汽車的類,奧迪汽車的類,大眾汽車的類,他們都實現了汽車介面,這樣就好辦了,大家只需要學會了怎麼開汽車,那麼無論是本田,還是奧迪,還是大眾我們都會開了,開的時候根本無需關心我開的是哪一類車,操作手法(函式呼叫)都一樣

抽象類
什麼是抽象類

  • 與java一樣,python也有抽象類的概念但是同樣需要藉助模組實現,抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被例項化

為什麼要有抽象類

  • 如果說類是從一堆物件中抽取相同的內容而來的,那麼抽象類就是從一堆類中抽取相同的內容而來的,內容包括資料屬性和函式屬性。

  • 比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你吃水果時,要麼是吃一個具體的香蕉,要麼是吃一個具體的桃子。。。。。。你永遠無法吃到一個叫做水果的東西。

  • 從設計角度去看,如果類是從現實物件抽象而來的,那麼抽象類就是基於類抽象而來的。

  • 從實現角度來看,抽象類與普通類的不同之處在於:抽象類中有抽象方法,該類不能被例項化,只能被繼承,且子類必須實現抽象方法。這一點與介面有點類似,但其實是不同的,即將揭曉答案

在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(`硬碟資料的讀取方法`)

class Process(All_file): #子類繼承抽象類,但是必須定義read和write方法
    def read(self):
        print(`程式資料的讀取方法`)

    def write(self):
        print(`程式資料的讀取方法`)

wenbenwenjian=Txt()

yingpanwenjian=Sata()

jinchengwenjian=Process()

#這樣大家都是被歸一化了,也就是一切皆檔案的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()

print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)
複製程式碼

二,python特性之 封裝

【好處】
隱藏物件的屬性和實現細節,僅對外提供公共訪問方式。

【好處】

  1. 將變化隔離;
  2. 便於使用;
  3. 提高複用性;
  4. 提高安全性;

【封裝原則】
1. 將不需要對外提供的內容都隱藏起來;
2. 把屬性都隱藏,提供公共方法對其訪問。

2.0 私有變數和私有方法

在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是可以訪問到的,即這種操作並不是嚴格意義上的限制外部訪問,僅僅只是一種語法意義上的變形
複製程式碼

這種自動變形的特點:

1.類中定義的__x只能在內部使用,如self.__x,引用的就是變形的結果。

2.這種變形其實正是針對外部的變形,在外部是無法通過__x這個名字訪問到的。

3.在子類定義的__x不會覆蓋在父類定義的__x,因為子類中變形成了:_子類名__x,而父類中變形成了:_父類名__x,即雙下滑線開頭的屬性在繼承給子類時,子類是無法覆蓋的。

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

1.這種機制也並沒有真正意義上限制我們從外部直接訪問屬性,知道了類名和屬性名就可以拼出名字:_類名__屬性,然後就可以訪問了,如a._A__N

2.變形的過程只在類的內部生效,在定義後的賦值操作,不會變形

在這裡插入圖片描述
  • 私有方法
    在繼承中,父類如果不想讓子類覆蓋自己的方法,可以將方法定義為私有的
class A:
    ...

    def fa(self):
        pass

    print(`from A`)


def test(self):
    self.fa()


class B(A):

    def fa(self):
        pass

    print(`from B`)


b = B()
b.fa()


class A:
    ...

    def __fa(self):  # 在定義時就變形為_A__fa

        ...

    print(`from A`)


def test(self):
    self.__fa()  # 只會與自己所在的類為準,即呼叫_A__fa


class B(A):

    def __fa(self):
        pass

    print(`from B`)

複製程式碼

2.1封裝與擴充套件性

封裝在於明確區分內外,使得類實現者可以修改封裝內的東西而不影響外部呼叫者的程式碼;而外部使用用者只知道一個介面(函式),只要介面(函式)名、引數不變,使用者的程式碼永遠無需改變。這就提供一個良好的合作基礎——或者說,只要介面這個基礎約定不變,則程式碼改變不足為慮。

#類的設計者
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #對外提供的介面,隱藏了內部的實現細節,此時我們想求的是面積
        return self.__width * self.__length


#使用者
r1=Room(`臥室`,`egon`,20,20,20)
r1.tell_area() #使用者呼叫介面tell_area


#類的設計者,輕鬆的擴充套件了功能,而類的使用者完全不需要改變自己的程式碼
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #對外提供的介面,隱藏內部實現,此時我們想求的是體積,內部邏輯變了,只需求修該下列一行就可以很簡答的實現,而且外部呼叫感知不到,仍然使用該方法,但是功能已經變了
        return self.__width * self.__length * self.__high


#對於仍然在使用tell_area介面的人來說,根本無需改動自己的程式碼,就可以用上新功能
r1.tell_area()
複製程式碼

2.2 property屬性

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

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

成人的BMI數值:
過輕:低於18.5
正常:18.5-23.9
過重:24-27
肥胖:28-32
非常肥胖, 高於32
&emsp;&emsp;體質指數(BMI)=體重(kg)÷身高^2(m)
&emsp;&emsp;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**2)

p1=People(`egon`,75,1.85)
print(p1.bmi)
複製程式碼

例二:圓的周長和麵積

import math
class Circle:
    def __init__(self,radius): #圓的半徑radius
        self.radius=radius

    @property
    def area(self):
        return math.pi * self.radius**2 #計算面積

    @property
    def perimeter(self):
        return 2*math.pi*self.radius #計算周長

c=Circle(10)
print(c.radius)
print(c.area) #可以向訪問資料屬性一樣去訪問area,會觸發一個函式的執行,動態計算出一個值
print(c.perimeter) #同上
```
輸出結果:
314.1592653589793
62.83185307179586
```
複製程式碼

為什麼要用property

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

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

python並沒有在語法上把它們三個內建到自己的class機制中,在C++裡一般會將所有的所有的資料都設定為私有的,然後提供set和get方法(介面)去設定和獲取,在python中通過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`
複製程式碼

一個靜態屬性property本質就是實現了get,set,delete三種方法

class Foo:
    @property
    def AAA(self):
        print(`get的時候執行我啊`)

    @AAA.setter
    def AAA(self,value):
        print(`set的時候執行我啊`)

    @AAA.deleter
    def AAA(self):
        print(`delete的時候執行我啊`)

#只有在屬性AAA定義property後才能定義AAA.setter,AAA.deleter
f1=Foo()
f1.AAA
f1.AAA=`aaa`
del f1.AAA
複製程式碼
class Foo:
    def get_AAA(self):
        print(`get的時候執行我啊`)

    def set_AAA(self,value):
        print(`set的時候執行我啊`)

    def delete_AAA(self):
        print(`delete的時候執行我啊`)
    AAA=property(get_AAA,set_AAA,delete_AAA) #內建property三個引數與get,set,delete一一對應

f1=Foo()
f1.AAA
f1.AAA=`aaa`
del f1.AAA
複製程式碼

怎麼用?

class Goods:

    def __init__(self):
        # 原價
        self.original_price = 100
        # 折扣
        self.discount = 0.8

    @property
    def price(self):
        # 實際價格 = 原價 * 折扣
        new_price = self.original_price * self.discount
        return new_price

    @price.setter
    def price(self, value):
        self.original_price = value

    @price.deleter
    def price(self):
        del self.original_price


obj = Goods()
obj.price         # 獲取商品價格
obj.price = 200   # 修改商品原價
print(obj.price)
del obj.price     # 刪除商品原價
複製程式碼

2.3 classmethod 類方法

class Classmethod_Demo():
    role = `dog`

    @classmethod
    def func(cls):
        print(cls.role)


Classmethod_Demo.func()
複製程式碼

2.4 staticmethod 靜態方法

class Staticmethod_Demo():
    role = `dog`

    @staticmethod
    def func():
        print("當普通方法用")

Staticmethod_Demo.func()
複製程式碼

練習1:

class Foo:
    def func(self):
        print(`in father`)


class Son(Foo):
    def func(self):
        print(`in son`)

s = Son()
s.func()
# 請說出上面一段程式碼的輸出並解釋原因?

複製程式碼

練習2

class A:
    __role = `CHINA`
    @classmethod
    def show_role(cls):
        print(cls.__role)

    @staticmethod
    def get_role():
        return A.__role

    @property
    def role(self):
        return self.__role

a = A()
print(a.role)
print(a.get_role())
a.show_role()
# __role在類中有哪些身份?
# 以上程式碼分別輸出哪些內容?
# 這三個裝飾器分別起了什麼作用?有哪些區別?

複製程式碼

三,python特性之 多型

3.0多型

多型指的是一類事物有多種形態
動物有多種形態:人,狗,豬

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`)
複製程式碼

檔案有多種形態:文字檔案,可執行檔案

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`)
複製程式碼

3.1多型性

一 什麼是多型動態繫結(在繼承的背景下使用時,有時也稱為多型性)
多型性是指在不考慮例項型別的情況下使用例項

在物件導向方法中一般是這樣表述多型性:
向不同的物件傳送同一條訊息(!!!obj.func():是呼叫了obj的方法func,又稱為向obj傳送了一條訊息func),不同的物件在接收時會產生不同的行為(即方法)。
也就是說,每個物件可以用自己的方式去響應共同的訊息。所謂訊息,就是呼叫函式,不同的行為就是指不同的實現,即執行不同的函式。

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

多型性

peo=People()
dog=Dog()
pig=Pig()

#peo、dog、pig都是動物,只要是動物肯定有talk方法
#於是我們可以不用考慮它們三者的具體是什麼型別,而直接使用
peo.talk()
dog.talk()
pig.talk()

#更進一步,我們可以定義一個統一的介面來使用
def func(obj):
    obj.talk()
複製程式碼

鴨子型別

逗比時刻:
  Python崇尚鴨子型別,即‘如果看起來像、叫聲像而且走起路來像鴨子,那麼它就是鴨子’
python程式設計師通常根據這種行為來編寫程式。例如,如果想編寫現有物件的自定義版本,可以繼承該物件
也可以建立一個外觀和行為像,但與它無任何關係的全新物件,後者通常用於儲存程式元件的鬆耦合度。
例1: 利用標準庫中定義的各種‘與檔案類似’的物件,儘管這些物件的工作方式像檔案,但他們沒有繼承內建檔案物件的方法
例2: 序列型別有多種形態:字串,列表,元組,但他們直接沒有直接的繼承關係

#二者都像鴨子,二者看起來都像檔案,因而就可以當檔案一樣去用
class TxtFile:
    def read(self):
        pass

    def write(self):
        pass

class DiskFile:
    def read(self):
        pass
    def write(self):
        pass
複製程式碼

相關文章