第九章 類和對象
內容提要:
- 類是一種數據結構,可以包含數據成員和函數成員。
- 面向對象的程序設計具有三個基本特征:封裝、繼承、多態。
- 可以大大增加程序的可靠性、代碼的可重用性和程序的可維護性,從而提高程序開發效率。
對象
- 對象的定義:
(1)從概念層面來講,就是某種事物的抽象。抽象原則包括(1)數據抽象(2)過程抽象兩個方面。
數據抽象:就是定義對象的屬性
過程抽象:就是定義對象的操作
面向對象的程序設計強調把數據(屬性)和操作(服務)結合為一個不可分的系統單位(即對象),對象的外部只需要知道它做什么,而不必知道它如何做。
(2)從規格層面講,對象是一系列可以被其他對象使用的公共接口(對象交互)。
(3)從語言實現層面來看,對象封裝了數據和代碼(數據和程序)。
封裝
封裝,就是把客觀事物抽象並封裝成對象,即將數據成員、屬性、方法和實踐等集合在一個整體內。通過訪問控制,還可以隱藏內部成員,只允許可行的對象訪問或操作自己的部分數據或方法。
封裝保證了對象的獨立性,可以防止外部程序破壞對象的內部數據,同時便於程序的維護和修改。
繼承
繼承是面向對象的程序設計中代碼重用的主要方法。繼承是允許使用現有類的功能,並在無須重新改寫原來的類的情況下,對這些功能進行擴展。繼承可以避免代碼復制和相關的代碼維護等問題。
多態
子類具有所有父類的非私有數據的行為及子類自己定義的所有其他數據或行為。
即子類具有兩個有效類型:子類的類型及其繼承的父類的類型。
對象可以表示多個類型的能力稱為多態性。
多態性允許每個對象以自己的方式去響應共同的消息,從而允許用戶以更明確的方法建立通用軟件,提高軟件的可維護性。
類對象和實例對象
- 類是一個數據結構,類定義數據類型的數據(屬性)和行為(方法)。對象是類的具體實體,也可以稱為類的實例。
- 在python語言中,類稱為類對象;類的實例稱為實例對象。
類對象:
例: class Person1: #定義類Person1 pass #類體為空語句 p1=Person1() #創建和使用對象 print(p1)
實例對象:
類是抽象的,要使用類定義的功能,就必須實例化,即創建類的對象。創建對象后可以使用“."運算符來調用其成員。
創建類的對象 = 創建類的實例 = 實例化類 ==都說明以類為模板生成了一個對象的操作。
c1=complex(1,2)#創建類complex的實例對象並綁定到變量c1 c1.conjugate()#調用c1的conjugate()方法,返回其共軛值 c1.real
屬性
屬性:類的數據成員實在類中定義的成員變量(域),用來存儲描述類的特征的值,稱為屬性。
屬性可以被該類中定義的方法訪問,也可以通過類對象或實例對象進行訪問。(而在函數體或代碼塊中定義的局部變量,只能在其定義的范圍內進行訪問)
屬性實際上是在類中的變量。
實例屬性:
通過 "self.變量名" 定義的屬性,稱為實例屬性,也稱為實例變量。
類的每個實例都包含該類的實例變量的一個單獨副本。
實例變量在類的內部通過self訪問,在外部通過對象實例訪問。
class Person2: #定義類Person2 def __init__(self,name,age): #__init__方法(構造函數) self.name = name #初始化self.name,即成員變量name(域) self.age = age #初始化self.age,即成員變量age(域) def say_hello(self): #定義類Person2的函數say_hello print("您好,我叫",self.name) #在實例方法中通過self.name讀取成員變量name p1 = Person2('張三',25) #創建對象 p1.say_hello() #調用對象的方法 print(p1.age)
類屬性
python也允許聲明屬於類對象本身的變量,即類屬性,也稱為類變量、靜態屬性。
類屬性屬於整個類,不是特定實例的一部分,而是所有實例之間共享的一個副本。
class Person3: count = 0 #定義類屬性count name = 'Person' #定義類屬性name Person3.count += 1 print(Person3.count)#類名訪問,讀取並顯示類屬性count print(Person3.name)#類名訪問,讀取並顯示類屬性name p1 = Person3() #創建實例對象p1 p2 = Person3() #創建實例對象p2 print((p1.name, p2.name)) #通過實例對象訪問,讀取成員變量的值 Person3.name = '雇員' #通過類名訪問,設置類屬性值 print((p1.name, p2.name)) #讀取成員變量的值 p1.name = '員工' #通過實例對象訪問,設置實例對象成員變量的值 print((p1.name, p2.name))
運行結果:
1 Person ('Person', 'Person') ('雇員', '雇員') ('員工', '雇員')
私有屬性和公有屬性:
約定以兩個下划線開頭,但是不以兩個下划線結束的屬性是私有的,其他為公共的,不能直接訪問私有屬性,但可以在方法中訪問。
class A: __name = 'classA' #定義的__name為私有屬性 def get_name(): print(A.__name) #在類方法中訪問私有類屬性 A.get_name() A.__name #不能直接訪問私有類屬性
@property裝飾器
面向對象編程的封裝性原則要求不直接訪問類中的數據成員。python中可以通過定義私有屬性,然后定義相應的訪問該私有屬性的函數,並使用@property裝飾器裝飾這些函數。程序可以把函數當作屬性訪問,從而提供更友好的訪問方式。
class Person11: def __init__(self,name): self.__name = name @property def name(self): """i'm the 'x' property.""" return self.__name p = Person11("王五") print(p.name)
自定義屬性
對象通過特殊屬性__dict__存儲自定義屬性,即類定義中不存在的屬性。
>>>class C1: pass >>>o = C1 >>>o.name='custom name' >>>o.name >>>o.__dict__
方法
實例方法:
方法是與類相關的函數
- 一般情況下,類方法的第一個參數一般為self,這種方法稱為實例方法。實例方法對類的某個給定的實例進行操作,可以通過self顯式地訪問該實例。
雖然類方法地第一個參數為self,但調用時,用戶不需要也不能給self傳值,事實上,python自動把對象實例傳給self。
例:假設聲明了一個類myclass和類方法my_func(self, p1, p2)
obj1 = myclass() #創建myclass的一個實例obj1
obj1.my_func(p1,p2) #實例對象obj1調用myclass的類方法my_func
python將會自動將語句:obj1.my_func(p1,p2) 轉換為 :obj1.my_func(obj1, p1, p2),即自動把對象實例obj1傳給self
靜態方法
類方法
構造函數__init__
__init__方法即構造函數(構造方法),用於執行類的實例的初始工作。創建完對象后調用,初始化當前對象的實例,無返回值。
析構函數__del__
__del__方法即析構函數,用於實現銷毀類的實例所需的操作,如釋放對象占用的非托管資源(例如:打開的文件、網絡連接等)
默認情況下,當對象不再使用時,執行__del__方法。
class Person3: count = 0#定義類變量count def __init__(self, name, age):#構造函數 self.name = name self.age = age Person3.count += 1 #創建一個實例時,計數加1 def __del__(self): #析構函數 Person3.count -= 1 #刪除一個實例時,計數減1 def say_hi(self): #定義類方法 print("您好,我叫",self.name) def get_count(self): print("總計數為:",Person3.count) print("總計數為:",Person3.count) #類名訪問 P31 =Person3('張三',25) #創建實例化對象 P31.say_hi() #調用對象的方法 Person3.get_count(Person3)# p31 = Person3('李四',28) p31.say_hi() Person3.get_count(Person3) del P31 #刪除對象 p31.get_count() del p31 Person3.get_count(Person3)
運行結果:
總計數為: 0 您好,我叫 張三 總計數為: 1 您好,我叫 李四 總計數為: 2 總計數為: 1 總計數為: 0
私有方法與公有方法:
與私有屬性類似,python約定兩個下划線開頭,但不以兩個下划線結束的方法是私有方法(private),其他為公有方法(public)
不能直接訪問私有方法,但可以在其他方法中訪問。
class book: def __init__(self, name, author, price): self.name = name self.author = author self.price = price def __check_name(self): #定義私有方法,用來判斷name是否為空 if self.name =='': return False else: return True def get_name(self): #定義類方法,在該方法中訪問了私有方法 if self.__check_name():print(self.name,self.author) else:print('no value') b = book('python程序設計教程','江紅',2.0) #創建了一個對象 b.get_name() #對象調用類方法 b.__check_name() #對象直接調用私有方法,(會出錯)
運行結果
python程序設計教程 江紅 Traceback (most recent call last): File "C:/Users/yangx/PycharmProjects/day1/book/part9.py", line 77, in <module> b.__check_name() #對象直接調用私有方法,導致出錯 AttributeError: 'book' object has no attribute '__check_name'
方法重載
在其他程序設計語言中,方法可以重載:即定義多個重名方法,只要保證方法簽名是唯一的。(方法簽名包括三個部分:方法名、參數數量和參數類型)
python語言是動態語言,參數數量可由可選參數和可變參數來控制,所以python對象方法不需要重載。
繼承
派生類(子類)
- python支持多重繼承,即一個派生類(子類)可以繼承多個基類(父類)
- 如果在定義中沒有指定基類,則默認其基類為object。object是所有對象的跟基類,定義了公有方法的默認實現
- 聲明派生類(子類)時,必須在其構造函數中調用基類的構造函數(調用時注意基類的參數寫法)
class Person: #基類(父類) def __init__(self, name, age): self.name = name self.age = age def say_hi(self): #定義基類的方法 print('您好,我叫{0},今年{1}歲了'.format(self.name, self.age)) class student(Person): #派生類(子類) def __init__(self, name, age, stu_id): #派生類的構造函數 Person.__init__(self, name, age) #調用基類的構造函數 self.stu_id = stu_id def say_hi(self): #定義派生類的方法 Person.say_hi(self) print('我是學生,我的學號是{0}'.format(self.stu_id)) p1= Person('張網易',33) p1.say_hi() s1 = student('李遙兒',17,'12333382988') s1.say_hi()
結果:

您好,我叫張網易,今年33歲了
您好,我叫李遙兒,今年17歲了
我是學生,我的學號是12333382988
類成員的繼承與重寫
通過繼承,派生類繼承基類中除構造方法之外的所有成員。如果在派生類中重新定義從基類繼承的方法,則派生類中定義的方法覆蓋從基類中繼承的方法。
import math class dimension: def __init__(self, x, y): self.x = x self.y = y def area(self): #基類的方法area() pass class circle(dimension): def __init__(self,r): dimension.__init__(self, r, 0)#注意基類的構造函數的參數形式 def area(self): #派生類重寫了基類的方法area() S1 = math.pi*(int(self.x) ** 2) return S1 class rectangle(dimension): def __init__(self,w,h): dimension.__init__(self, w, h) #注意基類的構造函數的參數形式 def area(self): #派生類重寫了基類的方法area() S2 = (int(self.x) * int(self.y))/2 return S2 d1=circle(2.0) d2 = rectangle(2.0,4.0) print(d1.area(),d2.area())
對象的特殊方法:
對象的淺拷貝和深拷貝
淺拷貝:對象的賦值引用同一個對象,即不拷貝對象。若要拷貝對象,使用如下方法:
(1)切片操作:acc1[:]
(2)對象實例化:list(acc1)
(3)copy模塊的copy函數:copy.copy(acc1)
深拷貝:使用copy.deepcopy函數
上機實踐
29:編寫程序,計算圓的周長、面積和球的表面積、體積

import math class MyMath: def __init__(self,x): self.x = x # self.y = y def area(self): pass def Length(self): pass def vact(self): pass class circle(MyMath): def __init__(self,r): MyMath.__init__(self,r) def area(self): Area = math.pi * (self.x ** 2) print("圓的面積 = {0:1.2f}".format(Area) ) return Area def Length(self): lengths = math.pi * (self.x * 2) print("圓的周長 = {0:1.2f}".format(lengths)) return lengths class ball(MyMath): def __init__(self,r): MyMath.__init__(self,r) def area(self): Area = 4 * math.pi * (self.x ** 2) print("球的表面積 = {0:1.2f}".format(Area)) return Area def vact(self): V = (4/3) * math.pi * (self.x ** 3) print("球的體積 = {0:1.2f}".format(V)) return V #測試代碼 r = int(input("請輸入半徑:")) circle1 = circle(r) circle1.Length() circle1.area() ball1 = ball(r) ball1.area() ball1.vact()
30 編寫程序,實現華氏溫度和攝氏溫度的轉換

class Temperature: def __init__(self,number): self.number = number def ToFahrenheit(self): fahrenhiet = self.number + 56 print("攝氏溫度 = {0:1.1f},華氏溫度 = {1:1.1f}".format(self.number,fahrenhiet)) def ToCelsius(self): celsius = self.number - 56 print("華氏溫度 = {0:1.1f},攝氏溫度 = {1:1.1f}".format(self.number,celsius)) #測試代碼 T1 = int(input("請輸入攝氏溫度:")) temperture1 = Temperature(T1) temperture1.ToFahrenheit() T2 = int(input("請輸入華氏溫度:")) temperture2 = Temperature(T2) temperture2.ToCelsius()