Python基礎入門(6)- 物件導向程式設計

葛老頭發表於2021-12-20

1.初識物件導向

Python從設計之初就已經是一門物件導向的語言,正因為如此,在Python中建立一個類和物件是很容易的。本篇隨筆將詳細介紹Python的物件導向程式設計。

如果你以前沒有接觸過物件導向的程式語言,那你可能需要先了解一些面嚮物件語言的一些基本特徵,在頭腦裡頭形成一個基本的物件導向的概念,這樣有助於你更容易的學習Python的物件導向程式設計。

1.1.什麼是物件導向程式設計(類)

  • 利用(面向)物件(屬性與方法)去進行編碼的過程
  • 自定義物件資料型別就是物件導向中的類(class)的概念

剛開始接觸程式設計的朋友可能對物件導向上述兩個點可能不是很理解,我們來引入一下程式導向,通過這兩者的對比,以大白話的形式闡述來加深理解

同時物件導向程式設計和麵向過程程式設計也是我們學好一門語言必須要理解透徹的基礎

程式導向(Procedure Oriented 簡稱PO :如C語言):

從名字可以看出它是注重過程的。當解決一個問題的時候,程式導向會把事情拆分成: 一個個函式和資料(用於方法的引數) 。然後按照一定的順序,執行完這些方法(每個方法看作一個過程),等方法執行完了,事情就搞定了。

物件導向(Object Oriented簡稱OO :如C++,JAVA,Python等語言):

看名字它是注重物件的。當解決一個問題的時候,物件導向會把事物抽象成物件的概念,就是說這個問題裡面有哪些物件,然後給物件賦一些屬性和方法,然後讓每個物件去執行自己的方法,問題得到解決。

例子講解

背景:洗衣機洗衣服
程式導向的解決方法 物件導向的解決方法

①執行加洗衣粉方法

①先建立兩個物件:“洗衣機”、“人”

②執行加水方法

②針對物件“洗衣機”加入一些屬性和方法:

    方法:“洗衣服方法”、“漂洗方法”、“烘乾方法”

    屬性:“羽絨服清洗”、“漂洗2次”、“烘乾時間設定120分鐘”

③執行洗衣服方法

③針對物件“人”加入屬性和方法:“加洗衣粉方法”、“加水方法”

    方法:“加洗衣粉方法”、“加水方法”

    屬性:“洗衣粉+消毒液+柔順劑”、“加熱水”

④執行漂洗方法

④然後執行

  物件人:加洗衣粉方法

  物件人:加水方法

  物件洗衣機:洗衣服方法

  物件洗衣機:漂洗方法

  物件洗衣機.:烘乾方法

⑤ 執行烘乾方法 解決同一個問題 ,物件導向程式設計就是先抽象出物件,然後用物件執行方法的方式解決問題

以上就是將解決這個問題的過程拆成一個個方法(是沒有物件去呼叫的),通過一個個方法的執行來解決問題。

程式導向與物件導向的優缺點

程式導向

優點:效能比物件導向好,因為類呼叫時需要例項化,物件導向頻繁呼叫,效能開銷比較大

缺點:沒有物件導向易維護、易複用、易擴充套件

物件導向

優點:易維護、易複用、易擴充套件,由於物件導向有封裝、繼承、多型性的特性,可以設計出低耦合的系統,使系統 更加靈活、更加易於維護

缺點:效能比程式導向低



1.2.類的定義與呼叫

通過關鍵字 class 來定義一個類;class來宣告類,類的名稱首字母大寫,多單詞的情況下,每個單詞的首字母大寫

通過例項化類,進行類的呼叫

 1 # coding:utf-8
 2 
 3 class Person(object):
 4     # 類屬性
 5     name="student"
 6 
 7     def dump(self):
 8         # 要在函式中呼叫類屬性,就要在屬性前新增self進行呼叫
 9         print(f"{self.name} is dumping")
10 
11 student=Person()        #類的例項化
12 print(student.name)     #通過例項化進行屬性的呼叫
13 student.dump()          #通過例項化進行函式呼叫

1.3.self

  • self是類函式中的必傳引數,且必須放在第一個引數位置
  • self代表類的例項,而非類
  • self是一個物件,代表例項化的變數自身
  • self可以直接通過點來定義一個變數
  • self中的變數與含有self引數的函式可以在類中的任何一個函式內隨意呼叫
  • 非函式中定義的變數在定義的時候不用self
  • 類的方法與普通的函式只有一個特別的區別——它們必須有一個額外的第一個引數名稱, 按照慣例它的名稱是 self

self的解析與總結

  1. python中的self代表類的例項。

  2. python中只有針對類來說self才有意義。

  3. self只能用在python類的方法中。

  4. 屬性:

    (1)如果變數定義在類下面而不是類的方法下面,那這個變數即是類的屬性也是類例項的屬性。

    (2)如果變數定義在類的方法下面,如果加了self,那這個變數就是類例項的屬性,不是類的屬性;如果沒加self,這個變數只是這個方法的區域性變數,既不是類的屬性也不是類例項的屬性。

  5. 方法

    (1)如果在類中定義函式時加了self,那這個函式就是類例項的方法,而不是類的方法。

    (2)如果在類中定義函式時沒有加self,那這個函式就只是類的方法,而不是類例項的方法。

  6. 函式中要呼叫類屬性,需要使用 self.類屬性 進行呼叫

是不是還有點懵,不清楚什麼意思?接著往下看,先了解一下已經有哪些名詞了,然後通過大量程式碼加深映像去理解!


1.4.物件導向中常用術語

  • 類(Class)用來描述具有相同的屬性和方法的物件的集合。它定義了該集合中每個物件所共有的屬性和方法。物件是類的例項;可以理解是一個模版,通過它可以建立出無數個具體例項
  • 方法:類中的所有函式通常稱為方法。不過,和函式所有不同的是,類方法至少要包含一個self引數,類方法無法單獨使用,只能和類的物件一起使用
  • 類變數(屬性):類變數在整個例項化的物件中是公用的。類變數定義在類中且在函式體之外。類變數通常不作為例項變數使用。
  • 資料成員:類變數或者例項變數用於處理類及其例項物件的相關的資料。
  • 方法重寫:如果從父類繼承的方法不能滿足子類的需求,可以對其進行改寫,這個過程叫方法的覆蓋(override),也稱為方法的重寫。
  • 區域性變數(屬性)定義在方法中的變數,只作用於當前例項的類。
  • 例項變數(屬性)在類的宣告中,屬性是用變數來表示的,這種變數就稱為例項變數,例項變數就是一個用 self 修飾的變數。
  • 繼承:即一個派生類(derived class)繼承基類(base class)的欄位和方法。繼承也允許把一個派生類的物件作為一個基類物件對待。例如,有這樣一個設計:一個Dog型別的物件派生自Animal類,這是模擬"是一個(is-a)"關係(例圖,Dog是一個Animal)。
  • 例項化:建立一個類的例項,類的具體物件。
  • 物件:類並不能直接使用,通過類建立出的例項(又稱物件)才能使用。

物件導向最重要的概念就是類和例項,要牢記類是抽象的模版,而例項是根據類建立出來的一個個具體的“物件”,每個物件都擁有相同的方法。

上面標紅的常用術語,是截止此處已經看到或者隨筆中已經記錄的知識點,下面通過大量程式碼加深大家映像:

 1 # 1.例項化後的物件對類屬性進行修改,不會影響模板類中屬性的值 
 2 class Person(object):
 3     # 類屬性
 4     name="student"
 5 
 6     def dump(self):
 7         # 要在函式中呼叫類屬性,就要在屬性前新增self進行呼叫
 8         print(f"{self.name} is dumping")
 9 
10 student=Person()
11 student.name="teacher"
12 print(student.name)     #teacher
13 print(Person.name)      #student
 1 # 2.類方法無法單獨使用,即使是模板類呼叫,必須也只能和例項化後的物件一起使用
 2 class Person(object):
 3     # 類屬性
 4     name="student"
 5 
 6     def dump(self):
 7         # 要在函式中呼叫類屬性,就要在屬性前新增self進行呼叫
 8         print(f"{self.name} is dumping")
 9 
10 student=Person()
11 student.dump()      #student is dumping
12 Person.dump()       #報錯
13 '''
14 Traceback (most recent call last):
15   File "D:/WorkSpace/Python_Study/test03.py", line 12, in <module>
16     Person.dump()
17 TypeError: dump() missing 1 required positional argument: 'self'
18 '''
1 # 3.類屬性,方法中想要呼叫必須使用self,否則無法使用;不在方法中,可以直接呼叫
2 class Person(object):
3     # 類屬性
4     name="student"
5     print(name)
6     
7     def dump(self):
8         print(name)
9         print(self.name)

 1 # 4.類中的方法可以相互呼叫,但是得加self
 2 class Person(object):
 3     name="student"
 4 
 5     def dump(self):
 6         print(f"{self.name} is dumping")
 7 
 8     def run(self):
 9         # 之前有個知識點講過,類中得函式方法,需要和例項一起使用,self本質就是例項
10         self.dump()
11         # 直接呼叫報錯
12         dump()
13 
14 student=Person()
15 student.run()

 1 # 5.方法中可以呼叫其他方法的例項屬性,前提是例項屬性所在的方法要被執行過,才能找到例項屬性
 2 #   因此我們一般將例項屬性放在構造器__init__中,後面會講到
 3 class Person(object):
 4     name="student"
 5 
 6     def dump(self):
 7         #區域性變數,適用方位只有當前這個方法
 8         age=20
 9         #例項變數
10         self.top=180
11         print(f"{self.name} is dumping")
12 
13     def run(self):
14         self.dump()
15         print(self.top)
16 
17 student=Person()
18 student.run()
19 '''
20 student is dumping
21 180
22 '''
 1 # coding:utf-8
 2 
 3 # 6.例項屬性,類别範本無法呼叫訪問;例項化後的物件可以呼叫
 4 #   例項化後的物件,可以通過“物件名.新增變數=新增變數值”的方式來新增變數,一般用的很少,知道即可;也可以通過“類名.新增變數名=新增變數值”的方式來新增變數
 5 class Person(object):
 6     name="student"
 7 
 8     def dump(self):
 9         self.top=180
10         print(f"{self.name} is dumping")
11 
12 student=Person()
13 student.dump()  #例項變數在該方法中,執行該方法,使例項變數生效
14 
15 student.abc=123
16 print(student.abc,student.top)  # 123 180
17 print(Person.top)       # 報錯
18 '''
19 Traceback (most recent call last):
20   File "D:/WorkSpace/Python_Study/test03.py", line 17, in <module>
21     print(Person.top)
22 AttributeError: type object 'Person' has no attribute 'top'
23 '''

上面標紅術語的基本使用上述程式碼已經解釋了各自概念中的含義,下面針對例項變數和類變數單獨細緻再區分一下:

  • 類變數:定義在類裡面,通過類名或物件名引用,如果是通過物件名引用,會先找有沒有這個同名的例項變數,如果沒有,引用到的才是類變數,類變數的更新,只能通過類名,比如 Person.name =“李四” ;通過物件來更新類屬性只對當前物件生效,前面有相關程式碼說明
  • 例項變數: 定義在方法裡面的變數,一般在構造器__init__裡面,只能通過物件名進行引用;例項變數的增加、更新形式,比如self.top = 180

例項變數(相當於Java中的靜態屬性)

Python基礎入門(6)- 物件導向程式設計
建立方式:self.例項化變數名=變數值
使用方式:例項化後的物件.例項變數名
建立+使用方式
  • 例項變數是建構函式下(一般放在建構函式__init__)的變數帶self.變數
  • 例項變數為每個例項本身獨有,不可相互呼叫、新增、修改、刪除,不可被類呼叫、新增、修改、刪除
  • 可以訪問類變數
  • 如果同時有類變數和例項變數,程式執行時,先訪問例項變數,例項變數存在,會使用例項變數,例項變數不存在,會使用類變數
  • 例項改類變數,不可修改,實際是在例項記憶體裡建立了例項變數
  • 新增、修改、刪除例項變數n,不會影響到類變數n
  • a例項不能呼叫b例項的變數;相當於class A ,例項化物件a對self.name賦值為“張三”,然後例項化物件b對self.name賦值為“李四”。a物件中還是張三,是獨立的例項變數
  • 例項變數可修改、新增、刪除
  • 當例項變數與類變數重名時,優先呼叫例項變數

類變數:

Python基礎入門(6)- 物件導向程式設計
建立方式:類變數名=變數值
使用方式:
①類.類變數名
②例項化後的物件.類變數名
注意:如果重新賦值等操作,例項化後的物件只對當前物件生效;而類的方式呼叫修改直接將模板屬性改了,其他物件跟著變
建立+使用方式
  • 類變數在class內,但不在class的方法內,存在類的記憶體裡
  • 類變數是該類所有例項共享的變數,但是例項物件只能訪問,不可修改,每個例項物件去訪問同一個類變數都將得到相同結果【例項名.類變數名】;如果有物件將類屬性值改了,再次訪問,值變了,而其他物件依舊是原先的值,實際是在當前物件在記憶體中建立了自己的例項變數,本質上訪問的是例項變數,而並非類變數
  • 新增、修改、刪除類變數n,不會影響到例項變數n
  • 類無權訪問例項名
  • 類變數可修改、新增、刪除

程式碼檢驗:

 1 # 實驗證明
 2 # 1、例項變數為每個例項獨有,不可相互呼叫、新增、修改、刪除,不可被類呼叫、新增、修改、刪除
 3 # 2、如果同時有類變數和例項變數,程式執行時,先訪問例項變數,例項變數存在,會使用例項變數,例項變數不存在,會使用類變數
 4 # 3、類無法訪問例項變數
 5 class Test(object):
 6     name = '類的姓名'  # 類變數
 7     address = '類的地址'
 8 
 9     def __init__(self, name, age, sex):
10         self.name = name  # 例項變數
11         self.age = age
12         self.sex = sex
13 
14     def test1(self):
15         print(self.name, Test.address)
16 
17     def test2(self):
18         pass
19 
20 
21 Test1 = Test('test1例項的姓名', 22, '')
22 Test2 = Test('test2例項的姓名', 33, '')
23 print(Test1.name, Test1.address)#test1例項的姓名 類的地址    當例項屬性name和類屬性name重名時,優先呼叫例項屬性
24 print(Test2.name, Test2.address)#test2例項的姓名 類的地址    當例項屬性name和類屬性name重名時,優先呼叫例項屬性
25 print(Test.name)     #類的姓名      類呼叫的name是類屬性,而並非例項屬性
26 print(Test.age)      #報錯        類呼叫例項屬性報錯,例項屬性需要和例項物件在一起使用
27 '''
28 Traceback (most recent call last):
29   File "D:/WorkSpace/Python_Study/test03.py", line 26, in <module>
30     print(Test.age)
31 AttributeError: type object 'Test' has no attribute 'age'
32 '''
 1 # 實驗證明
 2 # 1、例項變數可修改、新增、刪除
 3 # 2、類變數可修改、新增、刪除
 4 # 3、新增、修改、刪除例項變數n,不會影響到類變數n
 5 # 4、新增、修改、刪除類變數n,不會影響到例項變數n
 6 class Test(object):
 7     name = '類的姓名'  # 類變數
 8     address = '類的地址'
 9 
10     def __init__(self, name, age):
11         self.name = name  # 例項變數
12         self.age = age
13 
14     def test1(self):
15         print(self.name, self.address)
16 
17     def test2(self):
18         pass
19 
20 Test1 = Test('test1例項的姓名', 22)
21 Test1.address = 'test1例項的地址'  # 看上去是訪問類屬性,實際是新增例項變數
22 print(Test1.address)    #test1例項的地址
23 print(Test.address)     #類的地址
24 print(Test1.age)        #22
25 Test1.age = 11
26 print(Test1.age)        #11
27 Test.age = 30  # 新增類變數
28 print(Test1.age)        #11
29 print(Test.age)         #30
30 print(Test.address)     #類的地址
31 Test.address = '中國'
32 print(Test.address)     #中國

1.5.類的建構函式

類的建構函式(構造器):類中的一種預設函式,用來將類例項化的同時,將引數傳入類中;相當於起到這個類初始化的作用

 1 # coding:utf-8
 2 
 3 class Test(object):
 4 
 5     def __init__(self,a,b): #注意:這邊除了self預設的,你定義了幾個,例項化該類的時候,你就要傳遞幾個引數
 6         a = a
 7         self.b=b
 8         print(f"建構函式執行了,需要傳遞兩個引數,{a}是區域性變數,適用範圍是建構函式內;{self.b}是例項變數,適用於整個類")
 9 
10 test01=Test(a="A",b="B")    #建構函式執行了,需要傳遞兩個引數,A是區域性變數,適用範圍是建構函式內;B是例項變數,適用於整個類
11 test02=Test("C","D")        #建構函式執行了,需要傳遞兩個引數,C是區域性變數,適用範圍是建構函式內;D是例項變數,適用於整個類

1.6.物件的生命週期

簡單瞭解下,後面進階篇章會有詳細講解,此處,只需要瞭解圖示即可,例項化一個類的時候,物件的生命就開始誕生,程式碼執行完或者物件不在用時,python的__del__會自動對物件進行回收

 

2.類中的私有函式和私有變數

什麼是私有函式私有變數:

  • 無法被例項化後的物件呼叫的類中的函式和變數
  • 只在類的內部可以呼叫私有函式和變數
  • 只希望類內部業務呼叫使用,不希望被使用者呼叫的場景

私有函式與私有變數的定義方法:

定義方法:在變數或函式前新增__(兩個下橫線),即為私有變數或函式

 1 # coding:utf-8
 2 
 3 class Person(object):
 4     def __init__(self,name):
 5         self.name=name
 6         self.__age=20
 7     def dump(self):
 8         print(self.name,self.__age)
 9     def __cry(self):
10         print(self.name+"is crying")
11 
12 student=Person("張三")
13 student.dump()        #張三 20
14 print(student.__age)  #報錯:AttributeError: 'Person' object has no attribute '__age'
15 student.__cry()     #報錯:AttributeError: 'Person' object has no attribute '__cry'

發現例項化後的物件無法呼叫私有函式與私有變數,但是我就是想呼叫怎麼辦?格式: 物件._類名__方法()  物件_類名__屬性

 1 # coding:utf-8
 2 
 3 class Person(object):
 4     def __init__(self,name):
 5         self.name=name
 6         self.__age=20
 7     def dump(self):
 8         print(self.name,self.__age)
 9     def __cry(self):
10         print(self.name+"is crying")
11 
12 student=Person("張三")
13 student.dump()        #張三 20
14 print(dir(student))     #列印student物件,可以操作哪些變數和方法  '_Person__age', '_Person__cry', '__class__', '__delattr__',
15 student._Person__cry()  #呼叫私有方法
16 print(student._Person__age)     #訪問私有屬性

私有函式和私有屬性的應用:Python中的封裝

  • python 中的封裝:將不對外的私有屬性或方法通過可對外使用的函式而使用(類中定義私有的,只有類內部使用,外部無法訪問)
  • 這樣做的主要原因:保護私隱,明確區分內外
 1 # coding:utf-8
 2 
 3 class Parent(object):
 4     def __hello(self,data):
 5         print("hello %s" % data)
 6 
 7     def helloworld(self):
 8         self.__hello("world")
 9 
10 if __name__=="__main__":
11     p=Parent()
12     p.helloworld()      #hello world

 

3.裝飾器與類的裝飾器

裝飾器的作用:可以使我們更加靈活的使用函式,應用場景中最常見的記錄日誌,就會經常使用裝飾器功能;還有遮蔽一些不合法的程式執行,保證程式碼的安全等等

3.1.什麼是裝飾器

裝飾器本身就是一個函式,它只不過是對另外一個函式進行了一次封裝,它能夠把那個函式的功能進行一個強化

  • 也是一種函式

  • 可以接收函式作為引數

  • 可以返回函式

  • 接收一個函式,內部對其處理,然後返回一個新的函式,動態的增強函式功能

  • 將C函式在a函式中執行,在a函式中可以選擇執行或不執行c函式,也可以對c函式的結果進行二次加工處理

    Python基礎入門(6)- 物件導向程式設計
    # coding:utf-8
    
    def a():
        def c():
            print("Hello World")
        return c()
    
    a() #Hello World
    c() #在外部無法呼叫,報錯
    View Code

3.2.裝飾器的定義

1     def out(func_args):      #外圍函式
2 
3             def inter(*args, **kwargs):   #內嵌函式
4 
5                     return func_args(*args, **kwargs)
6 
7               return inter    #外圍函式返回內嵌函式,注意這裡的inter函式是不執行的,因為沒有()

講解:

  • 有一個函式func_args和一個裝飾器(簡單理解為加工作坊)
  • 裝飾器分為外圍函式和內嵌函式,內嵌函式一般負責加工,加工好後將加工結果返回,注意返回是在與內嵌函式同級返回,內嵌函式裡面那個return只是加工過程,我換成print什麼的都是可以的;加工結果返回內嵌函式,不需要加(),不用再執行一遍inter再返回,因為已經執行過了,直接返回即可;
  • 將func_args函式(方法)給out裝飾器加工,out扔給實際加工的內嵌函式執行加工過程
  • 因為,任何函式和方法都可以給裝飾器加工,所以,裝飾器不知道你讓我加工的函式方法有幾個引數,因此,內嵌函式定義了不確定引數,一個為陣列一個為字典使用者接收需要加工的函式引數

3.3.裝飾器的用法

  • 將被呼叫的函式直接作為引數傳入裝飾器的外圍函式括弧

    Python基礎入門(6)- 物件導向程式設計
    # 裝飾器
    def a(func):
        def b(*args,**kwargs):
            print(f"需要加工的是:{func(*args,**kwargs)}")
        return b
    
    # 需要加工的函式
    def c (name):
        return name
    
    a(c)("zhangsan")  #需要加工的是:zhangsan
    View Code
  • 將裝飾器與被呼叫的函式繫結在一起

  • @符號+裝飾器函式放在被呼叫函式的上一行,被呼叫的函式正常定義,只需要直接呼叫被執行函式即可

    Python基礎入門(6)- 物件導向程式設計
     1 # 裝飾器
     2 def a(func):
     3     def b(*args,**kwargs):
     4         print(f"需要加工的是:{func(*args,**kwargs)}")
     5     return b
     6 
     7 # 需要加工的函式,@裝飾器名,不用加(),這個是常用的,掌握
     8 @a
     9 def c (name):
    10     return name
    11 
    12 c("zhangsan")   #需要加工的是:zhangsan
    View Code
 1 # coding:utf-8
 2 
 3 def check_str(func):
 4     def inner(*args,**kwargs):
 5         print("args:",args,kwargs)
 6         result=func(*args,**kwargs)
 7         if result=='ok':
 8             return 'result is %s' % result
 9         else:
10             return 'result is %s' % result
11     return inner
12 # 裝飾器位置要放在需要呼叫裝飾器函式前面,因為python是自上而下執行的
13 @check_str
14 def test(data):
15     return data
16 
17 print(test("ok"))
18 # 引數以元組傳給裝飾器
19 # args: ('ok',) {}
20 # result is ok
21 print(test(data="no"))
22 # 引數以字典傳給裝飾器
23 # args: () {'data': 'no'}
24 # result is no

3.4.類的常用裝飾器

@classmethod:使類函式可不經例項化而直接被呼叫

 1 # coding:utf-8
 2 
 3 class Test(object):
 4     def run(self):
 5         print("Test is runing")
 6 
 7     @classmethod
 8     def jump(cls):
 9         print("Test is junming")
10 
11 Test.jump()     #Test is junming,使用了裝飾器@classmethod,類可以呼叫類方法
12 Test.run()      #報錯,類無法呼叫self例項化函式
13 '''
14 Traceback (most recent call last):
15   File "D:/WorkSpace/Python_Study/test01.py", line 12, in <module>
16     Test.run()
17 TypeError: run() missing 1 required positional argument: 'self'
18 
19 '''
 1 # coding:utf-8
 2 
 3 # 1:類的例項化函式中可以呼叫@classmethod裝飾器修飾的類函式;類的例項化物件也是可以呼叫@classmethod修飾的方法,下面程式碼未展示,大家可以自己試一下。test.jump()
 4 class Test(object):
 5     name="張三"
 6 
 7     def run(self):
 8         print(f"{self.name} is runing")
 9         self.jump()
10 
11     @classmethod
12     def jump(cls):
13         print(f"{cls.name} is junming")
14 
15 test=Test()
16 test.run()
17 '''
18 執行結果:
19 張三 is runing
20 張三 is junming
21 '''
 1 # coding:utf-8
 2 
 3 # 2:@classmethod裝飾器修飾的類函式中無法使用類的例項化函式
 4 class Test(object):
 5     name="張三"
 6 
 7     def run(self):
 8         print("Test is runing")
 9 
10     @classmethod
11     def jump(cls):
12         print(f"{cls.name} is junming")
13         cls.run()
14 
15 Test.jump()
16 '''
17 執行結果:
18 張三 is junming
19 Traceback (most recent call last):
20   File "D:/WorkSpace/Python_Study/test01.py", line 14, in <module>
21     Test.jump()
22   File "D:/WorkSpace/Python_Study/test01.py", line 11, in jump
23     cls.run()
24 TypeError: run() missing 1 required positional argument: 'self'
25 '''

@staticmethod:使類函式可以不經過例項化而直接被呼叫,呼叫該裝飾器的函式無需傳遞self或cls引數,且無法在函式內呼叫其他類函式或類變數(因為沒有self或者cls代表當前類)

 1 # coding:utf-8
 2 
 3 '''
 4 1.例項化(self)函式和用@classmethod裝飾的函式中可以呼叫@staticmethod修飾的函式
 5 2.@staticmethod修飾的函式中無法呼叫類中的其他函式以及類屬性
 6 3.@staticmethod修飾的函式和@classmethod裝飾的函式一樣可以通過(類.方法名)或者(例項化物件.方法名)執行
 7 '''
 8 class Test(object):
 9     name="張三"
10 
11     def run(self):
12         print(f"{self.name} is runing")
13         self.jump()
14         self.run()
15 
16     @classmethod
17     def jump(cls):
18         print(f"{cls.name} is junming")
19         cls.cry()
20 
21     @staticmethod
22     def cry():
23         # print(f"{name} is crying") 無法呼叫類屬性
24         print("Test is crying")
25 
26         # 無法呼叫其他類函式
27         # jump()
28         # self.run()
29 
30 Test.jump()
31 '''
32 張三 is junming
33 Test is crying
34 '''
35 Test.cry()      #Test is crying
36 
37 test=Test()
38 test.jump()
39 '''
40 張三 is junming
41 Test is crying
42 '''
43 test.cry()      #Test is crying

@property:將類函式的執行免去括弧,類似於呼叫屬性(變數)

 沒有了括弧,那我想給@property修飾的函式傳參怎麼辦???再新增一個同名不同參方法函式,使用@方法名.setter進行裝飾

# coding:utf-8

#@property修飾的還是self例項函式,因此具有的功能同例項函式,只是物件呼叫的時候不用加(),對其傳參改值的時候要做特殊處理
class Test(object):

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

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

    @name.setter
    def name(self,newname):
        self.__name=newname
        print ("newname: %s" % self.__name)

t1=Test(name="張三")
t1.name     #張三

# 如果我想要修改name的值怎麼辦,採取類似java方法過載的方式
# 簡單講下方法過載,我的java隨筆裡面有,大家可以去詳細瞭解下:
#   就是方法名相同的時候,根據引數的個數來呼叫方法
t1.name="李四"    #newname: 李四
t1.name          #李四

@property修飾的方法函式要傳遞多個引數的時候怎麼辦,可以傳tuple,list等,@property方法內部去處理

 1 # coding:utf-8
 2 
 3 #@property修飾的方法傳遞設定多個引數
 4 class Test(object):
 5 
 6     def __init__(self,name):
 7         self.__name=name
 8 
 9     @property
10     def name(self):
11         print (self.__name)
12 
13     @name.setter
14     def name(self,data):
15 
16         # 根據實際需要將傳進來的引數進行處理
17         if str(type(data))=="<class 'list'>":
18             for i in range(len(data)):
19                 print(data[i])
20         elif str(type(data))=="<class 'tuple'>":
21             for i in range(len(data)):
22                 print(data[i])
23 
24 
25 t1=Test(name="張三")
26 # 可以傳tuple、list等型別資料
27 t1.name=["李四","lisi"]
28 '''
29 輸出結果:
30 李四
31 lisi
32 '''
33 t1.name=(6,4,2,3,223,4)
34 '''
35 輸出結果:
36 6
37 4
38 2
39 3
40 223
41 4
42 '''

 

4.類的繼承與多型

4.1.什麼是繼承

  • 通過繼承基類來得到基類的功能
  • 所以我們把繼承的類稱為父類或者基類,繼承者被稱作子類
  • 程式碼的重用

4.2.父類與子類的關係

  • 子類擁有父類的所有屬性和方法
  • 父類不具備子類自有的屬性和方法

4.3.python中類的繼承

  • 定義子類時,將父類傳入子類引數內
  • 子類例項化後可以呼叫自己與父類的函式與變數
  • 父類無法呼叫子類的函式與變數

 1 # coding:utf-8
 2 
 3 class Parent(object):
 4     def __init__(self,name,sex):
 5         self.name=name
 6         self.sex=sex
 7 
 8     def talk(self):
 9         return f'{self.name} are walking'
10 
11     def is_sex(self):
12         if self.sex=='boy':
13             return f'{self.name} is a boy'
14         else:
15             return f'{self.name} is a girl'
16 
17 class ChildOne(Parent):
18     def play_football(self):
19         return f'{self.name} are playing football'
20 
21 class ChildTwo(Parent):
22     def play_pingpong(self):
23         return f'{self.name} are playing pingpong'
24 
25 c_one=ChildOne(name="張三",sex="boy")
26 print(c_one.talk())     #張三 are walking
27 print(c_one.play_football())    #張三 are playing football
28 
29 c_two=ChildTwo("王五","girl")
30 print(c_two.is_sex())           #王五 is a girl
31 print(c_two.play_pingpong())    #王五 are playing pingpong
32 
33 p=Parent("李四","boy")
34 print(p.is_sex())               #李四 is a boy
35 print(p.play_football())        #報錯,父類無法呼叫子類的方法
36 '''
37 Traceback (most recent call last):
38   File "D:/WorkSpace/Python_Study/test01.py", line 35, in <module>
39     print(p.play_football())
40 AttributeError: 'Parent' object has no attribute 'play_football'
41 '''

4.4.類的多型

  • 什麼是多型:同一個功能的多狀態化
  • 多型的背景:為了保留子類中某個和父類名稱一樣的函式的功能,這時候,我們就用到了類的多型,可以幫助我們保留子類中的函式功能。
  • 多型的用法:子類中重寫父類的方法
 1 # coding:utf-8
 2 
 3 #書寫一個父類
 4 class XiaomingFather(object):
 5    def talk(self):
 6        print('小明爸爸說了一句話...')
 7 
 8 # 2 書寫一個子類,並且繼承一個父類
 9 class XiaomingBrother(XiaomingFather):
10    def run(self):
11        print('小明的哥哥在奔跑著..')
12 
13    def talk(self):
14        print('小明哥哥在說話...')
15 
16 class Xiaoming(XiaomingFather):
17    def talk(self):
18        print('哈哈  小明也可以說自己的觀點...')
19 
20 #為什麼要去多型
21 #為什麼要去繼承父類
22 #答案: 為了使用已經寫好的類中的函式
23 # 為了保留子類中某個和父類名稱一樣的函式功能, 這時候, 我們就用到了類的多型.
24 # 可以幫助我們保留子類中的函式的功能
25 if __name__ == '__main__':
26    Xiaoming_borther = XiaomingBrother()
27    Xiaoming_borther.talk()      #小明哥哥在說話...
28    father = XiaomingFather()
29    father.talk()                #小明爸爸說了一句話...
30    Xiaoming = Xiaoming()
31    Xiaoming.talk()              #哈哈  小明也可以說自己的觀點...

 


4.5.類的super函式

  • super是子類繼承父類方法使用的關鍵字,當子類繼承父類後,就可以使用super來呼叫父類的方法
  • super函式:在類中呼叫父類的方法,而不是重寫,與多型區分開
  • super() 函式是用於呼叫父類(超類)的一個方法
  • Python3.x 和 Python2.x 的一個區別是: Python 3 可以使用直接使用 super().xxx 代替 super(Class, self).xxx 
 1 # coding: UTF-8
 2 
 3 class Parent(object):
 4     def __init__(self, p):
 5         print('Hello i am parent', p)
 6 
 7     def talk(self):
 8         print("Parent is talking")
 9 
10 
11 class Child(Parent):
12     def __init__(self, c):
13         print('Hello i am child', c)
14         super(Child,self).talk()
15         super().talk()
16         super(Child,self).__init__(c)
17         super().__init__(c)
18 
19     def talk(self):
20         print("Child is talking")
21 
22 if __name__ == '__main__':
23     child=Child("小明")
24 
25 '''
26 Hello i am child 小明
27 Parent is talking
28 Parent is talking
29 Hello i am parent 小明
30 Hello i am parent 小明
31 '''

 

5.類的多重繼承

5.1.什麼是多重繼承

可以繼承多個基(父)類


5.2.多重繼承的方法

 1 # coding: UTF-8
 2 
 3 class Tool(object):
 4     def work(self):
 5         return 'tool work'
 6 
 7     def car(self):
 8         return 'car will run'
 9 
10 class Food(object):
11     def work(self):
12         return 'food work'
13 
14     def cake(self):
15         return 'i like cake'
16 
17 # 繼承父類的子類
18 class Person(Tool,Food):
19     #繼承多個父類時,存在相同方法,會先執行自己類的,如果沒有,根據繼承的順序從左至右依次呼叫
20     #自己類中的>Tool>Food
21     #可以通過python內建函式__mro__檢視這個類繼承鏈的順序
22     pass
23 
24 if __name__=='__main__':
25     p=Person()
26     print(p.car())          #car will run
27     print(p.cake())         #i like cake
28     print(p.work())         #Tool在Food左邊先被繼承,因此執行Tool類中同名的work方法
29     print(Person.__mro__)   #(<class '__main__.Person'>, <class '__main__.Tool'>, <class '__main__.Food'>, <class 'object'>)

 擴充: __mro__ 檢視類繼承鏈的屬性

多繼承中多個父類存在同名方法需要呼叫指定類的同名方法怎麼辦???

 1 # coding:utf-8
 2 
 3 class A(object):
 4     def working(self):
 5         print('Enter A')
 6 
 7 
 8 
 9 class B(object):
10     def working(self):
11         print('Enter B')
12 
13 
14 class C(A,B):
15     def working(self):
16         A.working(self)
17         B.working(self)
18         print('Enter C')
19 
20 
21 if __name__ == '__main__':
22     c = C()
23     c.working()
24 '''
25 Enter A
26 Enter B
27 Enter C
28 '''

5.3.類的多重繼承中super的使用方法

super物件是可以作為一個類代理物件,在多重繼承時,如何使用super來建立自己需要的父類代理物件呢?

super構造時有三種傳參方式:

  • super() 是super(__class__,self)的懶人模式,實際上還是super(Type , obj)的形式

  • super(Type, obj) 

這個方式要傳入兩個常數,第一個引數type必須是一個類名,第二個引數是一個該類的例項化物件,不過可以不是直接的例項化物件,該類的子類的例項化物件也行。

super會按照某個類的__ mro__屬性中的順序去查詢方法,super(Type, obj)兩個引數中Type作用是定義在__ mro__陣列中的那個位置開始找,obj定義的是用obj所屬的類的__ mro__屬性

 1 # coding:utf-8
 2 
 3 class A(object):
 4     def __init__(self):
 5         print('Enter A')
 6         print('Leave A')
 7 
 8 
 9 class B(A):
10     def __init__(self):
11         print('Enter B')
12         print('Leave B')
13 
14 
15 class C(A):
16     def __init__(self):
17         print('Enter C')
18         print('Leave C')
19 
20 
21 class D(B, C):
22     def __init__(self):
23         print('Enter D')
24         super(D, self).__init__()
25         super(B, self).__init__()
26         super(C, self).__init__()
27         print('Leave D')
28 
29 if __name__ == '__main__':
30     d = D()
31     print(D.mro())
32     print(C.mro())
33     print(B.mro())
34 
35 '''
36 Enter D
37 Enter B
38 Leave B
39 Enter C
40 Leave C
41 Enter A
42 Leave A
43 Leave D
44 [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
45 [<class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
46 [<class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
47 '''

5.4.菱形繼承(鑽石繼承)

在多層繼承和多繼承同時使用的情況下,就會出現複雜的繼承關係,多重多繼承。

其中,就會出現菱形繼承。如下圖所示:

在這種結構中,在呼叫順序上就出現了疑惑,呼叫順序究竟是以下哪一種順序呢?

  • D->B->A->C(深度優先)
  • D->B->C->A(廣度優先)

下面我們來解答下這個問題,舉個例子來看下:

 1 class A():
 2  def __init__(self):
 3    print('init A...')
 4    print('end A...')
 5  
 6 class B(A):
 7  def __init__(self):
 8    print('init B...')
 9    A.__init__(self)
10    print('end B...')
11  
12 class C(A):
13  def __init__(self):
14    print('init C...')
15    A.__init__(self)
16    print('end C...')
17  
18 class D(B, C):
19  def __init__(self):
20    print('init D...')
21    B.__init__(self)
22    C.__init__(self)
23    print('end D...')
24  
25 if __name__ == '__main__':
26    D()

輸出結果:

 1 init D...
 2 init B...
 3 init A...
 4 end A...
 5 end B...
 6 init C...
 7 init A...
 8 end A...
 9 end C...
10 end D...
  • 從輸出結果中看,呼叫順序為:D->B->A->C->A。可以看到,B、C共同繼承於A,A被呼叫了兩次。A沒必要重複呼叫兩次。
  • 其實,上面問題的根源都跟MRO有關,MRO(Method Resolution Order)也叫方法解析順序,主要用於在多重繼承時判斷調的屬性來自於哪個類,其使用了一種叫做C3的演算法,其基本思想時在避免同一類被呼叫多次的前提下,使用廣度優先和從左到右的原則去尋找需要的屬性和方法。
  • 那麼如何避免頂層父類中的某個方法被多次呼叫呢,此時就需要super()來發揮作用了,super本質上是一個類,內部記錄著MRO資訊,由於C3演算法確保同一個類只會被搜尋一次,這樣就避免了頂層父類中的方法被多次執行了,上面程式碼可以改為:
 1 class A():
 2  def __init__(self):
 3    print('init A...')
 4    print('end A...')
 5  
 6 class B(A):
 7  def __init__(self):
 8    print('init B...')
 9    super(B, self).__init__()
10    print('end B...')
11  
12 class C(A):
13  def __init__(self):
14    print('init C...')
15    super(C, self).__init__()
16    print('end C...')
17  
18 class D(B, C):
19  def __init__(self):
20    print('init D...')
21    super(D, self).__init__()
22    print('end D...')
23  
24 if __name__ == '__main__':
25    D()

輸出結果:

1 init D...
2 init B...
3 init C...
4 init A...
5 end A...
6 end C...
7 end B...
8 end D...

可以看出,此時的呼叫順序是D->B->C->A。即採用是廣度優先的遍歷方式。

補充內容

  • Python類分為兩種,一種叫經典類,一種叫新式類。都支援多繼承,但繼承順序不同。
  • 新式類:從object繼承來的類。(如:class A(object)),採用廣度優先搜尋的方式繼承(即先水平搜尋,再向上搜尋)。
  • 經典類:不從object繼承來的類。(如:class A()),採用深度優先搜尋的方式繼承(即先深入繼承樹的左側,再返回,再找右側)。
  • Python2.x中類的是有經典類和新式類兩種。Python3.x中都是新式類。

 

6.類的高階函式

6.1.__str__

若定義了該函式,當print當前例項化物件的時候,會返回該函式的return資訊


6.2.__getattr__

當呼叫的屬性或者方法不存在時,會返回該方法定義的資訊


6.3.__setattr__

用於攔截當前類中不存在的屬性與值


6.4.__call__

用於將一個例項化後的類變成一個函式使用

6.5.例項

 1 # coding:utf-8
 2 
 3 # t.a.b.c鏈式操作
 4 class Test(object):
 5     def __init__(self,attr=None):
 6         self.__attr=attr
 7 
 8     def __call__(self,name):
 9         return name
10 
11     def __getattr__(self, key):
12         if self.__attr:
13             key='{}.{}'.format(self.__attr,key)
14         else:
15             key=key
16         print(key)
17         return Test(key)
18 
19 t=Test()
20 t1=t.a.b.c.d("zhangsan")
21 print(t1)
22 '''
23 a
24 a.b
25 a.b.c
26 a.b.c.d
27 zhangsan
28 '''

相關文章