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)

在學習了“對象”之後,我們就有了一個容器,該容器可以盛放數據與功能,所以我們可以說:對象是把數據與功能整合到一起的產物,或者說”對象“就是一個盛放數據與功能的容器。

# 我們的函數裏面可以存放數據和內置函數,字典、列表、元組、集合等等都可以把變量名、函數名存進去,因此他們都是對象!

​ 在瞭解了對象的基本概念之後,理解面向對象的編程方式就相對簡單很多了,面向對象編程就是要造出一個個的對象,把原本分散開的相關數據與功能整合到一個個的對象裏,這麼做既方便使用,也可以提高程序的解耦合程度,進而提升了程序的可擴展性(需要強調的是,軟件質量屬性包含很多方面,面向對象解決的僅僅只是擴展性問題)
在這裏插入圖片描述

二、類與對象

類即類別/種類,是面向對象分析和設計的基石,對象是類的實例,人是一個種類,但我們自己就是人這個種類的一個實例,一個人就是一個對象。有了類的好處是:我們可以把同一類對象相同的數據與功能存放到類裏,而無需每個對象都重複存一份,這樣每個對象裏只需存自己獨有的數據即可,極大地節省了空間。所以,如果說對象是用來存放數據與功能的容器,那麼類則是用來存放多個對象相同的數據與功能的容器。
在這裏插入圖片描述​ 綜上所述,雖然我們是先介紹對象後介紹類,但是需要強調的是:在程序中,必須要事先定義類,然後再調用類產生對象(調用類拿到的返回值就是對象)。產生對象的類與對象之間存在關聯,這種關聯指的是:對象可以訪問到類中共有的數據與功能,所以類中的內容仍然是屬於對象的,類只不過是一種節省空間、減少代碼冗餘的機制,面向對象編程最終的核心仍然是去使用對象。

三、面向對象編程

我們以開發一個清華大學的選課系統爲例,來簡單介紹基於面向對象的思想如何編寫程序

# 學生1:
    數據:
        學校=清華大學
        姓名=李建剛
        性別=男
        年齡=28
    功能:
        選課

# 學生2:
    數據:
        學校=清華大學
        姓名=王大力
        性別=女
        年齡=18
    功能:
        選課

我們可以總結出一個學生類,用來存放學生們相同的數據與功能

# 學生類
    相同的特徵:
        學校=清華大學
    相同的功能:
        選課

類中只保存相同的,不同的呢?不同的放在類中的init函數裏,如下:

class Student:
    school='清華大學'

    #該方法會在對象產生之後自動執行,專門爲對象進行初始化操作,可以有任意代碼,但一定不能返回非None的值
    def __init__(self,name,sex,age): # 數據值有改變的放在init方法裏,傳值初始化對象
        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),進行空對象stu1的初始化

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}

至此,我們造出了三個對象與一個類,對象存放各自獨有的數據,類中存放對象們共有的內容
在這裏插入圖片描述對象名稱空間產生於對象初始化後,我們都知道py程序執行分爲定義階段和執行階段,函數在定義階段,只檢查語法,不執行代碼。類名稱空間在定義階段就產生了,且注意類在定義階段就會執行類中的代碼

def haha(): # 定義函數
	print('定義階段函數體中的代碼被執行')
# haha() # 調用函數,只要我們不要這句,運行程序,看看會不會打印就可以看出來函數定義階段是否執行了函數體代碼

class heihei(): # 定義類
	def haha():
		print('定義階段類中的函數的函數體代碼被執行')
	print('定義階段類中代碼被執行')
# 一樣的我們只要不實例化類,不執行類中的函數,直接運行上面定義類的代碼,如果...

那麼有人就會問類中的函數在定義階段執行代碼嗎?

1. 函數在定義階段不會執行函數體代碼,在執行階段如果有調用函數的語句,那麼執行階段纔會執行函數體代碼
2. 如果類中有調用函數的語句(但是一般不會出現這種情況),那麼在定義階段函數體代碼也會被執行,如果沒有,那麼定義階段類中的函數的函數體代碼不會執行。

四、屬性訪問

1、類屬性和對象屬性

在這裏插入圖片描述
上面這個是我小學時代,2010年左右,火的一塌糊塗的地下城與勇士,博主在以前也是地下城骨灰級玩家,這個遊戲博主也很久沒玩過了,60版本是400萬勇士永遠的情懷。好了,廢話少說,上面這些就是人物角色的屬性面板。我們類與對象也有自己的屬性。類的屬性是對象的共有屬性,對象的屬性是對象自己的特有屬性
在這裏插入圖片描述在類中定義的名字,都是類的屬性(對象特有屬性是在類中init函數中定義的,不算是在類中定義的),細說的話,類有兩種屬性:數據屬性和函數屬性,可以通過__dict__訪問屬性的值,比如Student.__dict__[‘school’],但Python提供了專門的屬性訪問語法:

>>> Student.school # 訪問數據屬性,等同於Student.__dict__['school']
'清華大學'
>>> Student.choose # 訪問函數屬性,等同於Student.__dict__['choose']
<function Student.choose at 0x1018a2950>
# 除了查看屬性外,我們還可以使用Student.school=value修改屬性值或新增屬性(類中不存在該屬性即爲新增),用del Student.school刪除類的屬性。

在這裏插入圖片描述操作對象的特有屬性也是一樣(增刪改查)

>>> stu1.course=’python’ # 新增,等同於obj1.__dict__[‘course']='python'
>>> del obj1.course # 刪除,等同於del obj1.__dict__['course']
>>> stu1.age=38 # 修改,等同於obj1.__dict__[‘age']=38
>>> stu1.name # 查看,等同於obj1.__dict__[‘name']
'李建剛'

接下來測試一下你對屬性的是否瞭解(怎樣實現檢測一個類被實例化了多少次?

count = 0
class Student:
    school='清華大學'
    def __init__(self,name,sex,age): 
        global count
        count += 1
        self.name=name
        self.sex=sex
        self.age=age

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)類中定義的函數一般都至少有一個參數,且一般叫self
1. self代表的是傳入一個類的對象,一般類的函數都是給對象用的,因此函數需要知道調用我這個函數的到底是那個對象,因爲函數中可能會用到對象的一些特有屬性
2. 如果類中定義的函數沒有參數,那麼該函數只能被類調用,不能被對象調用。
(3)類使用類中函數的傳參問題

類中定義的函數是類的函數屬性,類可以使用,但必須遵循函數的參數規則,有幾個參數需要傳幾個參數

Student.choose(stu1) # 李建剛 is choosing a course
Student.choose(stu2) # 王大力 is choosing a course
Student.choose(stu3) # 牛嗷嗷 is choosing a course
(4)類的函數屬性與對象的綁定問題

類中定義的函數主要是給對象使用的,而且是綁定給對象的,雖然所有對象指向的都是相同的功能,但是綁定到不同的對象就是不同的綁定方法,內存地址各不相同

# 定義類
class Student:
    school='清華大學'
    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)
print(Student.choose)
print(stu1.choose)
print(stu2.choose)
print(stu3.choose)

print(id(Student.choose))
print(id(stu1.choose))
print(id(stu2.choose))
print(id(stu3.choose))
# 執行結果
<function Student.choose at 0x10c165170>
<bound method Student.choose of <__main__.Student object at 0x10c1ca150>>
<bound method Student.choose of <__main__.Student object at 0x10c1ca190>>
<bound method Student.choose of <__main__.Student object at 0x10c1ca1d0>>
4497756528
4496251472
4496251472
4496251472

綁定到對象的方法特殊之處在於,綁定給誰就應該由誰來調用,誰來調用,就會將’誰’本身當做第一個參數自動傳入(方法__init__也是一樣的道理)

stu1.choose()  # 等同於Student.choose(stu1)
stu2.choose()  # 等同於Student.choose(stu2)
stu3.choose()  # 等同於Student.choose(stu3)

綁定到不同對象的choose技能,雖然都是選課,但李建剛選的課,不會選給王大力,這正是”綁定“二字的精髓所在

#注意:綁定到對象方法的這種自動傳值的特徵,決定了在類中定義的函數都要默認寫一個參數self,self可以是任意名字,但命名爲self是約定俗成的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章