python基礎-面向物件程式設計
一 物件的概念
面向過程:
核心是"過程"二字
過程的終極奧義就是將程式流程化
過程是"流水線",用來分步驟解決問題的
面向物件:
核心是"物件"二字
物件的終極奧義就是將程式"整合"
物件是"容器",用來盛放資料與功能的
類也是"容器",該容器用來存放同類物件共有的資料與功能
”面向物件“的核心是“物件”二字,而物件的精髓在於“整合“,什麼意思?
所有的程式都是由”資料”與“功能“組成,因而編寫程式的本質就是定義出一系列的資料,然後定義出一系列的功能來對資料進行操作。在學習”物件“之前,程式中的資料與功能是分離開的,如下
#資料:name、age、sex name='lili' age=18 sex='female' # 功能:tell_info def tell_info(name,age,sex): print('<%s:%s:%s>' %(name,age,sex)) # 此時若想執行檢視個人資訊的功能,需要同時拿來兩樣東西,一類是功能tell_info,另外一類則是多個數據name、age、sex,然後才能執行,非常麻煩 tell_info(name,age,sex)
# 學生的功能 def tell_stu_info(stu_obj): print('學生資訊:名字:%s 年齡:%s 性別:%s' % ( stu_obj['stu_name'], stu_obj['stu_age'], stu_obj['stu_gender'] )) def set_info(stu_obj, x, y, z): stu_obj['stu_name'] = x stu_obj['stu_age'] = y stu_obj['stu_gender'] = z stu_obj = { 'stu_school': 'oldboy', 'stu_name': 'egon', 'stu_age': 18,'stu_gender': 'male', 'tell_stu_info': tell_stu_info, 'set_info': set_info } stu1_obj = { 'stu_school': 'oldboy', 'stu_name': 'lili', 'stu_age': 19, 'stu_gender': 'female', 'tell_stu_info': tell_stu_info, 'set_info': set_info } # 課程的資料 course_name = 'python' course_period = '6mons' course_score = 10 # 課程的功能 def tell_coure_info(): print('課程資訊:名字:%s 週期:%s 學分:%s' % (course_name, course_period, course_score))
在學習了“物件”之後,我們就有了一個容器,該容器可以盛放資料與功能,所以我們可以說:物件是把資料與功能整合到一起的產物,或者說”物件“就是一個盛放資料與功能的容器/箱子/盒子。
如果把”資料“比喻為”睫毛膏“、”眼影“、”脣彩“等化妝所需要的原材料;把”功能“比喻為眼線筆、眉筆等化妝所需要的工具,那麼”物件“就是一個彩妝盒,彩妝盒可以把”原材料“與”工具“都裝到一起
如果我們把”化妝“比喻為要執行的業務邏輯,此時只需要拿來一樣東西即可,那就是彩妝盒,因為彩妝盒裡整合了化妝所需的所有原材料與功能,這比起你分別拿來原材料與功能才能執行,要方便的多。
在瞭解了物件的基本概念之後,理解面向物件的程式設計方式就相對簡單很多了,面向物件程式設計就是要造出一個個的物件,把原本分散開的相關資料與功能整合到一個個的物件裡,這麼做既方便使用,也可以提高程式的解耦合程度,進而提升了程式的可擴充套件性(需要強調的是,軟體質量屬性包含很多方面,面向物件解決的僅僅只是擴充套件性問題)
二 類與物件
類即類別/種類,是面向物件分析和設計的基石,如果多個物件有相似的資料與功能,那麼該多個物件就屬於同一種類。有了類的好處是:我們可以把同一類物件相同的資料與功能存放到類裡,而無需每個物件都重複存一份,這樣每個物件裡只需存自己獨有的資料即可,極大地節省了空間。所以,如果說物件是用來存放資料與功能的容器,那麼類則是用來存放多個物件相同的資料與功能的容器。
綜上所述,雖然我們是先介紹物件後介紹類,但是需要強調的是:在程式中,必須要事先定義類,然後再呼叫類產生物件(呼叫類拿到的返回值就是物件)。產生物件的類與物件之間存在關聯,這種關聯指的是:物件可以訪問到類中共有的資料與功能,所以類中的內容仍然是屬於物件的,類只不過是一種節省空間、減少程式碼冗餘的機制,面向物件程式設計最終的核心仍然是去使用物件。
在瞭解了類與物件這兩大核心概念之後,我們就可以來介紹一下面向物件程式設計啦。
三 面向物件程式設計
3.1 類的定義與例項化
我們以開發一個清華大學的選課系統為例,來簡單介紹基於面向物件的思想如何編寫程式
面向物件的基本思路就是把程式中要用到的、相關聯的資料與功能整合到物件裡,然後再去使用,但程式中要用到的資料以及功能那麼多,如何找到相關連的呢?我需要先提取選課系統裡的角色:學生、老師、課程等,然後顯而易見的是:學生有學生相關的資料於功能,老師有老師相關的資料與功能,我們單以學生為例,
# 學生的資料有
學校
名字
年齡
性別
# 學生的功能有
選課
詳細的
# 學生1:
資料:
學校=清華大學
姓名=李建剛
性別=男
年齡=28
功能:
選課
# 學生2:
資料:
學校=清華大學
姓名=王大力
性別=女
年齡=18
功能:
選課
# 學生3:
資料:
學校=清華大學
姓名=牛嗷嗷
性別=男
年齡=38
功能:
選課
我們可以總結出一個學生類,用來存放學生們相同的資料與功能
# 學生類
相同的特徵:
學校=清華大學
相同的功能:
選課
基於上述分析的結果,我們接下來需要做的就是在程式中定義出類,然後呼叫類產生物件
class Student: # 類的命名應該使用“駝峰體” school='清華大學' # 資料 def choose(self): # 功能 print('%s is choosing a course' %self.name)
類體最常見的是變數的定義和函式的定義,但其實類體可以包含任意Python程式碼,類體的程式碼在類定義階段就會執行,因而會產生新的名稱空間用來存放類中定義的名字,可以列印Student.__dict__來檢視類這個容器內盛放的東西
>>> print(Student.__dict__) {..., 'school': '清華大學', 'choose': <function Student.choose at 0x1018a2950>, ...}
呼叫類的過程稱為將類例項化,拿到的返回值就是程式中的物件,或稱為一個例項
>>> stu1=Student() # 每例項化一次Student類就得到一個學生物件 >>> stu2=Student() >>> stu3=Student()
如此stu1、stu2、stu3全都一樣了(只有類中共有的內容,而沒有各自獨有的資料),想在例項化的過程中就為三位學生定製各自獨有的資料:姓名,性別,年齡,需要我們在類內部新增一個__init__方法,如下
class Student: school='清華大學' #該方法會在物件產生之後自動執行,專門為物件進行初始化操作,可以有任意程式碼,但一定不能返回非None的值 def __init__(self,name,sex,age): self.name=name self.sex=sex self.age=age def choose(self): print('%s is choosing a course' %self.name)
然後我們重新例項出三位學生
>>> stu1=Student('李建剛','男',28) >>> stu2=Student('王大力','女',18) >>> stu3=Student('牛嗷嗷','男',38)
單拿stu1的產生過程來分析,呼叫類會先產生一個空物件stu1,然後將stu1連同呼叫類時括號內的引數一起傳給Student.__init__(stu1,’李建剛’,’男’,28)
def __init__(self, name, sex, age): self.name = name # stu1.name = '李建剛' self.sex = sex # stu1.sex = '男' self.age = age # stu1.age = 28
會產生物件的名稱空間,同樣可以用__dict__檢視
>>> stu1.__dict__ {'name': '李建剛', 'sex': '男', 'age': 28}
至此,我們造出了三個物件與一個類,物件存放各自獨有的資料,類中存放物件們共有的內容
# 注意:類體程式碼是在類定義階段就會立即執行,會產生類的名稱空間 class Student: # 1、變數的定義 stu_school='oldboy' # 2、功能的定義 def tell_stu_info(stu_obj): print('學生資訊:名字:%s 年齡:%s 性別:%s' %( stu_obj['stu_name'], stu_obj['stu_age'], stu_obj['stu_gender'] )) def set_info(stu_obj,x,y,z): stu_obj['stu_name']=x stu_obj['stu_age']=y stu_obj['stu_gender']=z # print('========>') # print(Student.__dict__) # 屬性訪問的語法 # 1、訪問資料屬性 # print(Student.stu_school) # Student.__dict__['stu_school'] # 2、訪問函式屬性 # print(Student.set_info) # Student.__dict__['set_info'] # Student.x=1111 #Student.__dict__['x]=111 # print(Student.__dict__) # 二:再呼叫類產生物件 stu1_obj=Student() stu2_obj=Student() stu3_obj=Student() # print(stu1_obj.__dict__) # print(stu2_obj.__dict__) # print(stu3_obj.__dict__) # 為物件定製自己獨有的屬性 # 問題1:程式碼重複 # 問題2:屬性的查詢順序 # stu1_obj.stu_name='egon' # stu1_obj.__dict__['stu_name']='egon' # stu1_obj.stu_age=18 # stu1_obj.__dict__['stu_age']=18 # stu1_obj.stu_gender='male' # stu1_obj.__dict__['stu_gender']='male' # # print(stu1_obj.__dict__) # # stu2_obj.stu_name='lili' # stu2_obj.stu_age=19 # stu2_obj.stu_gender='female' # # print(stu2_obj.__dict__) # # stu3_obj.stu_name='jack' # stu3_obj.stu_age=20 # stu3_obj.stu_gender='male' # # print(stu2_obj.__dict__) # 解決問題一: # 解決方案一: # def init(obj,x,y,z): # obj.stu_name=x # obj.stu_age=y # obj.stu_gender=z # # init(stu1_obj,'egon',18,'male') # init(stu2_obj,'lili',19,'female') # init(stu2_obj,'jack',20,'male') # 解決方案二: # 一:先定義類 class Student: # 1、變數的定義 stu_school='oldboy' # 空物件,'egon',18,'male' def __init__(obj,x,y,z): obj.stu_name=x # 空物件.stu_name='egon' obj.stu_age=y # 空物件.stu_age=18 obj.stu_gender=z # 空物件.stu_gender='male' # return None # 2、功能的定義 def tell_stu_info(stu_obj): print('學生資訊:名字:%s 年齡:%s 性別:%s' %( stu_obj['stu_name'], stu_obj['stu_age'], stu_obj['stu_gender'] )) def set_info(stu_obj,x,y,z): stu_obj['stu_name']=x stu_obj['stu_age']=y stu_obj['stu_gender']=z # print('========>') # 二:再呼叫類產生物件 # 呼叫類的過程又稱之為例項化,發生了三件事 # 1、先產生一個空物件 # 2、python會自動呼叫類中的__init__方法然將空物件已經呼叫類時括號內傳入的引數一同傳給__init__方法 # 3、返回初始完的物件 stu1_obj=Student('egon',18,'male') # Student.__init__(空物件,'egon',18,'male') # stu2_obj=Student('lili',19,'female') # stu3_obj=Student('jack',20,'male') # print(stu1_obj.__dict__) # print(stu2_obj.__dict__) # print(stu3_obj.__dict__) # 總結__init__方法 # 1、會在呼叫類時自動觸發執行,用來為物件初始化自己獨有的資料 # 2、__init__內應該存放是為物件初始化屬性的功能,但是是可以存放任意其他程式碼,想要在類呼叫時就立刻執行的程式碼都可以放到該方法內 # 3、__init__方法必須返回None
3.2 屬性訪問
3.2.1 類屬性與物件屬性
在類中定義的名字,都是類的屬性,細說的話,類有兩種屬性:資料屬性和函式屬性,可以通過__dict__訪問屬性的值,比如Student.__dict__[‘school’],但Python提供了專門的屬性訪問語法
>>> Student.school # 訪問資料屬性,等同於Student.__dict__['school'] '清華大學' >>> Student.choose # 訪問函式屬性,等同於Student.__dict__['choose'] <function Student.choose at 0x1018a2950> # 除了檢視屬性外,我們還可以使用Student.attrib=value(修改或新增屬性),用del Student.attrib刪除屬性。
操作物件的屬性也是一樣
>>> stu1.name # 檢視,等同於obj1.__dict__[‘name'] '李建剛' >>> stu1.course=’python’ # 新增,等同於obj1.__dict__[‘course']='python' >>> stu1.age=38 # 修改,等同於obj1.__dict__[‘age']=38 >>> del obj1.course # 刪除,等同於del obj1.__dict__['course']
3.2.2 屬性查詢順序與繫結方法
物件的名稱空間裡只存放著物件獨有的屬性,而物件們相似的屬性是存放於類中的。物件在訪問屬性時,會優先從物件本身的__dict__中查詢,未找到,則去類的__dict__中查詢
1、類中定義的變數是類的資料屬性,是共享給所有物件用的,指向相同的記憶體地址
# id都一樣 print(id(Student.school)) # 4301108704 print(id(stu1.school)) # 4301108704 print(id(stu2.school)) # 4301108704 print(id(stu3.school)) # 4301108704
2、類中定義的函式是類的函式屬性,類可以使用,但必須遵循函式的引數規則,有幾個引數需要傳幾個引數
Student.choose(stu1) # 李建剛 is choosing a course Student.choose(stu2) # 王大力 is choosing a course Student.choose(stu3) # 牛嗷嗷 is choosing a course
但其實類中定義的函式主要是給物件使用的,而且是繫結給物件的,雖然所有物件指向的都是相同的功能,但是繫結到不同的物件就是不同的繫結方法,記憶體地址各不相同
print(id(Student.choose)) # 4335426280 print(id(stu1.choose)) # 4300433608 print(id(stu2.choose)) # 4300433608 print(id(stu3.choose)) # 4300433608
繫結到物件的方法特殊之處在於,繫結給誰就應該由誰來呼叫,誰來呼叫,就會將’誰’本身當做第一個引數自動傳入(方法__init__也是一樣的道理)
stu1.choose() # 等同於Student.choose(stu1) stu2.choose() # 等同於Student.choose(stu2) stu3.choose() # 等同於Student.choose(stu3)
繫結到不同物件的choose技能,雖然都是選課,但李建剛選的課,不會選給王大力,這正是”繫結“二字的精髓所在。
#注意:繫結到物件方法的這種自動傳值的特徵,決定了在類中定義的函式都要預設寫一個引數self,self可以是任意名字,但命名為self是約定俗成的。
Python中一切皆為物件,且Python3中類與型別是一個概念,因而繫結方法我們早就接觸過
#型別list就是類 >>> list <class 'list'> #例項化的到3個物件l1,l2,l3 >>> l1=list([1,2,3]) >>> l2=list(['a','b','c']) >>> l3=list(['x','y']) #三個物件都有繫結方法append,是相同的功能,但記憶體地址不同 >>> l1.append <built-in method append of list object at 0x10b482b48> >>> l2.append <built-in method append of list object at 0x10b482b88> >>> l3.append <built-in method append of list object at 0x10b482bc8> #操作繫結方法l1.append(4),就是在往l1新增4,絕對不會將4新增到l2或l3 >>> l1.append(4) #等同於list.append(l1,4) >>> l1 [1,2,3,4] >>> l2 ['a','b','c'] >>> l3 ['x','y']
3.3.3 小結
在上述介紹類與物件的使用過程中,我們更多的是站在底層原理的角度去介紹類與物件之間的關聯關係,如果只是站在使用的角度,我們無需考慮語法“物件.屬性"中”屬性“到底源自於哪裡,只需要知道是通過物件獲取到的就可以了,所以說,物件是一個高度整合的產物,有了物件,我們只需要使用”物件.xxx“的語法就可以得到跟這個物件相關的所有資料與功能,十分方便且解耦合程度極高。