Python基礎入門(6)- 面向對象編程

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中的靜態屬性)

創建方式:self.實例化變數名=變數值
使用方式:實例化後的對象.實例變數名

創建+使用方式

  • 實例變數是構造函數下(一般放在構造函數__init__)的變數帶self.變數
  • 實例變數為每個實例本身獨有,不可相互調用、新增、修改、刪除,不可被類調用、新增、修改、刪除
  • 可以訪問類變數
  • 如果同時有類變數和實例變數,程式執行時,先訪問實例變數,實例變數存在,會使用實例變數,實例變數不存在,會使用類變數
  • 實例改類變數,不可修改,實際是在實例記憶體里創建了實例變數
  • 新增、修改、刪除實例變數n,不會影響到類變數n
  • a實例不能調用b實例的變數;相當於class A ,實例化對象a對self.name賦值為「張三」,然後實例化對象b對self.name賦值為「李四」。a對象中還是張三,是獨立的實例變數
  • 實例變數可修改、新增、刪除
  • 當實例變數與類變數重名時,優先調用實例變數

類變數:

創建方式:類變數名=變數值
使用方式:
①類.類變數名
②實例化後的對象.類變數名
注意:如果重新賦值等操作,實例化後的對象只對當前對象生效;而類的方式調用修改直接將模板屬性改了,其他對象跟著變

創建+使用方式

  • 類變數在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函數的結果進行二次加工處理

    # 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.裝飾器的用法

  • 將被調用的函數直接作為參數傳入裝飾器的外圍函數括弧

    # 裝飾器
    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

  • 將裝飾器與被調用的函數綁定在一起

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

     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 '''