面向對象基礎 編程範式 所謂的面向對象編程,指的就是一種編程範式,那麼什麼是編程範式呢?就是 按照某種語法風格加上數據結構加上演算法來編寫程式 。 數據結構:列表、字典、集合 演算法:編寫程式的邏輯或者解決問題的流程 一個程式是程式員為了得到一個任務結果而編寫的一組指令的集合,正所謂跳跳大陸通羅馬,實現 ...
面向對象基礎
編程範式
所謂的面向對象編程,指的就是一種編程範式,那麼什麼是編程範式呢?就是按照某種語法風格加上數據結構加上演算法來編寫程式。
- 數據結構:列表、字典、集合
- 演算法:編寫程式的邏輯或者解決問題的流程
一個程式是程式員為了得到一個任務結果而編寫的一組指令的集合,正所謂跳跳大陸通羅馬,實現一個任務的方式有很多種不同的方式,對這些不同的編程方式的特點進行歸納總結得出來的編程方式類別,即為編程範式。
不同的編程範式本質上代表對各種類型的任務採取的不同的解決問題的思路,大多數語言只支持一種編程範式,當然也有些語言可以同時支持多種編程範式。兩種最重要的編程範式分別是面向過程編程和面向對象編程。
面向過程編程
面向過程編程,核心就是過程二字,過程指的是解決問題的步驟,就是先乾什麼,然後乾什麼,最後乾什麼?實際上就相當於設計了一條流水線,就是機械式的思維方式,把一個大問題拆分成若幹個小問題,然後一個個小問題再細化,然後再把這些小問題組合起來,就解決了一個大問題。
下麵來看下麵向過程的編程,讓用戶輸入註冊信息:
import json
import re
def interactive(): # 接收用戶輸入
name = input('>>>').strip()
pwd = input('>>').strip()
return {
'name':name,
'pwd':pwd
}
def check(user_info): # 檢測
is_valid = True
if len(user_info['name']) == 0:
print('用戶名不能為空!')
is_valid = False
if len(user_info['pwd']) < 6:
print('密碼不能少於6位!')
is_valid = False
return {
'is_valid':is_valid,
'user_info':user_info
}
def register(check_info): # 寫入資料庫中
if check_info['is_valid']: # 如果為真
with open('db.json','w',encoding='utf-8') as f:
json.dump(check_info['user_info'],f)
def main(): # 主函數
user_info = interactive()
check_info = check(user_info)
register(check_info)
if __name__ == '__main__':
main()
那麼這段程式的運行結果就是提示輸入用戶名和密碼,然後對用戶名和密碼進行檢測,如果符合規則,那麼就將寫入資料庫中。
那麼此時,如果我想讓用戶再註冊信息的時候,再加上郵箱,代碼是怎麼寫的呢?
import json
import re
def interactive(): # 接收用戶輸入
name = input('>>>').strip()
pwd = input('>>').strip()
email = input('>>').strip()
return {
'name':name,
'pwd':pwd,
'email':email
}
def check(user_info): # 檢測
is_valid = True
if len(user_info['name']) == 0:
print('用戶名不能為空!')
is_valid = False
if len(user_info['pwd']) < 6:
print('密碼不能少於6位!')
is_valid = False
if not re.search(r'@.*?\.com$',user_info['email']):
print('郵箱格式不合法!')
is_valid = False
return {
'is_valid':is_valid,
'user_info':user_info
}
def register(check_info): # 寫入資料庫中
if check_info['is_valid']: # 如果為真
with open('db.json','w',encoding='utf-8') as f:
json.dump(check_info['user_info'],f)
def main(): # 主函數
user_info = interactive()
check_info = check(user_info)
register(check_info)
if __name__ == '__main__':
main()
如果我們還要添加來自國家、性別、年齡這些信息,這整個代碼流程不都要繼續修改嗎?那麼我們現在就能看出面向過程的優缺點了:
優點:把複雜的問題流程化,進而簡單化
缺點:可擴展性差
應用場景:自動部署、系統監控腳本
面向對象編程
面向對象編程,核心就是對象二字,就是站在上帝的視角:所有存在的事物都是i對象
舉個例子說明:在西游記中,如來就是上帝,他想要解決的問題就是如何把經書傳入大唐,如果是面向過程來說,如來就要想這條路要怎麼走,碰到事情的時候應該怎麼處理,就是要把取經的過程全部都要考慮到,然而換成面向對象呢?他找來了唐僧、孫悟空等人,妖魔鬼怪,一大堆神仙開始了取經之路...
我和小明的特征都是對象,我們的特征都是有鼻子有眼睛、兩隻手兩條腿,技能就是吃飯、學習、睡覺,所以我們可以被稱為對象。
為什麼你不會覺得你面前的筆記本不是孫悟空呢?因為筆記本有顯示器、鍵盤、滑鼠,而孫悟空有毛臉雷公嘴、會七十二變、有火眼金晶,所以你不會這樣覺得。
所謂的對象:就是特征和技能的結合體
面向過程:設計流程化
面向過程:站在上帝的視角來模擬世界
優點:可擴展性高
缺點:編程複雜度高
應用場景:用戶需求經常變更:互聯網應用,游戲,企業內部應用
面向對象程式設計
在軟體質量屬性上,成本、性能、安全性、可擴展性都是必須要有的,但是可擴展性占的比例還是比較高的。
類
類即類別、種類,是面向對象設計最重要的概念。剛剛我們知道對象是特征和技能的結合體,那麼類就是一系列對象相似的特征與技能的結合體。
那麼問題來了,先有的一個個具體存在的對象(比如一個具體存在的人),還是先有人類這個概念,這個問題需要分兩種情況去看
- 在現實世界中,肯定是先有的對象,然後才有的類
世界上肯定是先出現各種各樣的實際存在的物體,然後隨著人類文明的發展,人類站在不同的角度總結出了不同的種類,比如人類、動物類、植物類等概念。也就說,對象是具體的存在,而類僅僅只是一個概念,並不真實存在,比如你無法告訴我人類具體指的是哪一個人。
- 在程式中,務必先保證先定義類,後產生對象
與函數的使用是類似的:先定義函數,後調用函數,類也是一樣的:在程式中需要先定義類,後調用類。不一樣的是:調用函數會執行函數體代碼返回的是函數體執行的結果,而調用類會產生對象,返回的是對象
定義類
按照上面步驟,我們來先定義一個類(站在學校的角度,我們都是學生)
- 在現實世界中,先有對象,然後才有的對象
對象1:李坦克
特征:
學校:zhcpt
姓名:李坦克
性別:男
年齡:18
技能:
學習
吃飯
睡覺
對象2:王大炮
特征:
學校:zhcpt
姓名:王大炮
性別:男
年齡:23
技能:
學習
吃飯
睡覺
對象3:張小麗
特征:
學校:zhcpt
姓名:張小麗
性別:女
年齡:16
技能:
學習
吃飯
睡覺
現實中的學生類:
相似的特征:
學校 = zhcpt
相似的技能:
學習
吃飯
睡覺
- 在程式中,務必保證:先定義類,然後使用類(產生對象)
# 在python中程式中的類用class關鍵字定義。而在程式中特征用變數標識,技能用函數表示,因而類中最常見的無非是:變數和函數的定義
# 先定義類
class zhcptStundet:
school = 'zhcpt' # 相同的特征
def learn(self): # 相同的屬性
print('is learning')
def eat(self):
print('is eatting')
def sleep(self):
print('is sleeping')
# 後產生對象
zhcptStudent()
產生一個對象,不是去執行類體,而是執行這個類體後會得到一個返回值。這個返回值就是對象,在這個過程中也被稱為實例化。所以應該是這樣的:
stu1 = zhcptStudent() # 類的實例化
print(stu1) # <__main__.zhcptStundet object at 0x0000013557F00438>
print(stu1)則是列印zhcptStudent這個類產生的對象,是這個對象的記憶體地址
註意:
- 類中可以有任意的python代碼,這些代碼在類定義階段便會執行,因而會產生新的名稱空間,用來存放類的變數名和函數,可以通過zhcptStundet.__dict__方法去查看
- 類中定義的名字,都是類的屬性,點是訪問屬性的語法
- 對於經典類來說,我們可以通過該字典去操作類名稱空間的名字,但新式類有限制
# 定義類
class zhcptStudent:
school = 'zhcpt' # 類的數據屬性,定義新的名字,新的命名空間
def learn(self): # 類的函數屬性,定義了函數,產生了函數局部名稱空間,把函數內部定義的名字放進去
print('is learning')
def eat(self):
print('is eatting')
def sleep(self):
print('is sleeping')
print('----------run-----------')
# 查看類的名稱空間
print(zhcptStudent.__init__)
我們剛剛說類在定義階段時候便會執行,因而會產生新的名稱空間,用來存放類的變數名和函數,可以通過方法去查看,運行結果為:
----------run-----------
{'__module__': '__main__', 'school': 'zhcpt', 'learn': <function zhcptStundet.learn at 0x0000022B584A0C80>, 'eat': <function zhcptStundet.eat at 0x0000022B584A0D08>, 'sleep': <function zhcptStundet.sleep at 0x0000022B584C90D0>, '__dict__': <attribute '__dict__' of 'zhcptStundet' objects>, '__weakref__': <attribute '__weakref__' of 'zhcptStundet' objects>, '__doc__': None}
所以類和函數就有了區別:函數在定義完成之後不調用的話,那麼這段函數就不會執行,而類在定義階段就會執行
針對屬性,python提供了專門的屬性訪問語法,現在我們來查看訪問下變數屬性的值、函數屬性的值
print(zhcptStudent.school)
print(zhcptStudent.learn)
運行結果為:
zhcpt
<function zhcptStudent.learn at 0x000001D94F020C80> # 列印的是這個類下的learn函數記憶體地址
剛剛我們說,可以通過字典的方式去操作類名稱空間的名字,讓我們來看一下:
print(zhcptStudent.__dict__['school'])
print(zhcptStudent.__dict__['learn'])
運行結果和上面是一樣的:
zhcpt
<function zhcptStudent.learn at 0x0000023D2EAB0C80>
其實,我們很早之前就接觸過類了,還記得我們之前學過了list、dict嗎?記得time嗎?
In [2]: print(list)
<class 'list'>
In [3]: print(dict)
<class 'dict'>
# time本身也是一個類
import time
time.sleep # 其中time是類,sleep是類里定義的函數屬性
類的操作
class zhcptStudent:
school = 'zhcpt'
def learn(self):
print('is learning')
def eat(self):
print('is eatting')
def sleep(self):
print('is sleeping')
print('----------run-----------')
增加
zhcptStudent.country = 'CN'
print(zhcptStudent.__dict__)
print(zhcptStudent.country)
# 運行結果
----------run-----------
{'__module__': '__main__', 'school': 'zhcpt', 'learn': <function zhcptStudent.learn at 0x00000233B8D10C80>, 'eat': <function zhcptStudent.eat at 0x00000233B8D10D08>, 'sleep': <function zhcptStudent.sleep at 0x00000233B8D390D0>, '__dict__': <attribute '__dict__' of 'zhcptStudent' objects>, '__weakref__': <attribute '__weakref__' of 'zhcptStudent' objects>, '__doc__': None, 'country': 'CN'}
CN
刪除
del zhcptStudent.country
print(zhcptStudent.__dict__)
# 運行結果,已經找不到country這個k了
{'__module__': '__main__', 'school': 'zhcpt', 'learn': <function zhcptStudent.learn at 0x000001EEF1380C80>, 'eat': <function zhcptStudent.eat at 0x000001EEF1380D08>, 'sleep': <function zhcptStudent.sleep at 0x000001EEF13A90D0>, '__dict__': <attribute '__dict__' of 'zhcptStudent' objects>, '__weakref__': <attribute '__weakref__' of 'zhcptStudent' objects>, '__doc__': None}
修改
zhcptStudent.school = '珠海城市職業技術學院'
print(zhcptStudent.school)
運行結果:
珠海城市職業技術學院
初始化方法
首先先定義一個zhcptStudent類
class zhcptStudent:
school = 'zhcpt' # 相同的特征
def learn(self): # 相同的屬性
print('is learning')
def eat(self):
print('is eatting')
def sleep(self):
print('is sleeping')
print('----------run-----------')
stu1 = zhcptStudent()
stu2 = zhcptStudent()
stu1.learn()
stu2.eat()
# 運行結果是
----------run-----------
is learning
is eatting
上面說過,類是具有一系列對象相似的技能和屬性的結合體,對象特征與技能的結合體。
那麼,實例化的對象,總有不同的地方,比如李坦克是男的,而張小麗是女的,所以我們需要為對象定製對象自己獨有的特征:
class Student:
school = 'zhcpt'
def __init__(self,name,age,sex):
self.Name = name
self.Age = age
self.Sex = sex
def learn(self):
print('is learning')
def eat(self):
print('is eatting')
stu1 = Student() # TypeError: __init__() missing 3 required positional arguments: 'name', 'age', and 'sex'
這個時候就會報錯,提示缺少三個參數,但是我們卻沒有調用__init__方法,為什麼呢?因為python會自動調用,什麼時候會調用呢?創建對象、調用類、實例化的時候就會調用。
stu1 = Student('李坦克',18,'女')
那麼這個時候,就相當於把 ('李坦克','女',18) 三個參數傳遞進去了,即
self.Name = '李坦克'
self.Age = 18
self.Sex = '女'
加上__init__方法後實例化的步驟:
1.先產生一個空對象
2.觸發Student.init(stu1,'李坦克',18,'女')
為什麼觸發就會把參數傳遞進去呢?
print(Student.__init__)
結果為:
<function Student.__init__ at 0x000001CFC9AE0C80> # 原來__init__方法是一個函數,那麼函數就必須要傳入參數
類的操作
class Student:
school = 'zhcpt'
def __init__(self,name,age,sex):
self.Name = name
self.Age = age
self.Sex = sex
def learn(self):
print('is learning')
def eat(self):
print('is eatting')
查找
stu1 = Student('張鐵牛',22,'男')
print(stu1.Name)
print(stu1.Age)
print(stu1.Sex)
# 運行結果為:
張鐵牛
22
男
改
stu1 = Student('張鐵牛',22,'男')
stu1.Name = '李鐵蛋'
print(stu1.Name)
# 運行結果為:
李鐵蛋
刪除
stu1 = Student('張鐵牛',22,'男')
del stu1.Name
print(stu1.__dict__) # 查看對象的名稱空間
# 運行結果如下:
{'Age': 22, 'Sex': '男'}
增加
stu1 = Student('張鐵牛',22,'男')
stu1.class_name = 'python開發'
print(stu1.__dict__)
# 運行結果如下:
{'Name': '張鐵牛', 'Age': 22, 'Sex': '男', 'class_name': 'python開發'}
補充說明:
- 站的角度不同,定義出的類是截然不同的
- 現實中的類並不完全等於程式中的類,比如現實中的公司類,在程式中有時需要拆分成部門類、業務類
- 有時為了編程需求,程式中也可能會定義出現實中不存在的類,比如策略類,現實中並不存在,但是在程式中卻是一個很常見的事
屬性查找與綁定方法
類有兩種屬性:數據屬性和函數屬性
1、類的數據屬性是所有對象共用的
class Student:
school = 'zhcpt'
def __init__(self,name,age,sex):
self.Name = name # 就相當於往類的名稱空間添加一個名字,是stu1的名稱空間
self.Age = age
self.Sex = sex
def learn(self):
print('is learning')
def eat(self):
print('is eatting')
stu1 = Student('張全蛋',18,'男') # 都有著相同的數據屬性,school = 'zhcpt'
stu2 = Student('王小麗',16,'女')
stu3 = Student('李黑',23,'男')
他們三個對象都定製了私有的特征:
print(stu1.__dict__)
print(stu2.__dict__)
print(stu3.__dict__)
# 運行結果如下:
{'Name': '張全蛋', 'Age': 18, 'Sex': '男'}
{'Name': '王小麗', 'Age': 16, 'Sex': '女'}
{'Name': '李黑', 'Age': 23, 'Sex': '男'}
訪問類中的數據屬性:是所有對象共有的
print(Student.school,id(Student.school))
print(stu1.school,id(stu1.school))
print(stu2.school,id(stu2.school))
print(stu3.school,id(stu3.school))
# 運行結果如下:
zhcpt 2347297903872
zhcpt 2347297903872
zhcpt 2347297903872
zhcpt 2347297903872
2、類的函數屬性是綁定給對象使用的,稱為對象的綁定方法
class Student:
school = 'zhcpt'
def __init__(self,name,age,sex):
self.Name = name # 就相當於往類的名稱空間添加一個名字,是stu1的名稱空間
self.Age = age
self.Sex = sex
def learn(self):
print('%s is learning',self.Name)
def eat(self):
print('%s is eatting',self.Name)
stu1 = Student('張全蛋',18,'男') # 都有著相同的數據屬性,school = 'zhcpt'
stu2 = Student('王小麗',16,'女')
stu3 = Student('李黑',23,'男')
訪問類中的函數屬性:是綁定給對象使用的,綁定到不同的對象是不同的綁定方法,對象調用綁定方法,就會把對象本身當作第一個參數傳遞給self
# 綁定方法,類的函數的記憶體地址,和對象的函數記憶體地址不一樣
print(stu1.learn) # <bound method Student.learn of <__main__.Student object at 0x0000027BFF0680B8>>
# 對象訪問類中的函數屬性,則將本身作為第一個參數傳遞給self
print(stu1.learn())
# 運行結果
張全蛋 is learning
綁定方法的意思就是調用的都是同一種功能,但是大家執行的都是各自不同的方法
stu1.learn()
stu2.learn()
stu3.learn()
# 運行結果如下:
張全蛋 is learning
王小麗 is learning
李黑 is learning
類中定義的函數屬性,沒有任何處理的話,不是給類用的,實際上是綁定給對象使用的,誰來調用就是誰在使用這個功能
類即類型
python中一起都是對象,且python3中類與類型是一個概念,類型就是類
# 類型就是類
In [1]: print(list)
<class 'list'>
# 實例化3個對象
In [2]: l1 = list()
In [3]: l2 = list()
In [4]: l3 = list()
# 每個對象的綁定方法,都具有著相同的功能,但是記憶體地址不同
In [5]: print(l1.append)
<built-in method append of list object at 0x7ff344053e88>
In [6]: print(l2.append)
<built-in method append of list object at 0x7ff344035fc8>
In [7]: print(l3.append)
<built-in method append of list object at 0x7ff34431c5c8>
# 操作綁定方法l1.append(3),就是往l1里添加3,絕不會把3添加到l2或l3中
In [8]: l1.append(3)
In [9]: l1
Out[9]: [3]
In [10]: l2
Out[10]: []
In [11]: l3
Out[11]: []
# 調用類list.append(l3,111),實際上相當於:l3.append(111)
In [14]: list.append(l3,111)
In [15]: l3
Out[15]: [111]
從代碼級別看面向對象
1、在沒有學習類這個概念的時候,數據和功能是分開的
def exc1(host,port,db,charset):
conn=connect(host,port,db,charset)
conn.execute(sql)
return xxx
def exc2(host,port,db,charset,proc_name)
conn=connect(host,port,db,charset)
conn.call_proc(sql)
return xxx
#每次調用都需要重覆傳入一堆參數
exc1('127.0.0.1',3306,'db1','utf8','select * from tb1;')
exc2('127.0.0.1',3306,'db1','utf8','存儲過程的名字')
2、我們就想到瞭解決方法是,把這些變數定義為全局變數
HOST=‘127.0.0.1’
PORT=3306
DB=‘db1’
CHARSET=‘utf8’
def exc1(host,port,db,charset):
conn=connect(host,port,db,charset)
conn.execute(sql)
return xxx
def exc2(host,port,db,charset,proc_name)
conn=connect(host,port,db,charset)
conn.call_proc(sql)
return xxx
exc1(HOST,PORT,DB,CHARSET,'select * from tb1;')
exc2(HOST,PORT,DB,CHARSET,'存儲過程的名字')
3、但是2的解決方法也是有問題的,按照2的思路,我們將會定義一大堆全局變數,這些全局變數並沒有做任何區分,即能夠被所有功能使用,然而事實上只有HOST,PORT,DB,CHARSET是給exc1和exc2這兩個功能用的。言外之意:我們必須找出一種能夠將數據與操作數據的方法組合到一起的解決方法,這就是我們說的類了
class MySQLHandler:
def __init__(self,host,port,db,charset='utf8'):
self.host=host
self.port=port
self.db=db
self.charset=charset
self.conn=connect(self.host,self.port,self.db,self.charset)
def exc1(self,sql):
return self.conn.execute(sql)
def exc2(self,sql):
return self.conn.call_proc(sql)
obj=MySQLHandler('127.0.0.1',3306,'db1')
obj.exc1('select * from tb1;')
obj.exc2('存儲過程的名字')
總結使用類可以:將數據和專門操作該數據的功能整合到一起
可擴展性高
定義類並產生3個對象
class Chinese:
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
p1=Chinese('egon',18,'male')
p2=Chinese('alex',38,'female')
p3=Chinese('wpq',48,'female')
如果我們新增一個類屬性,將會立馬反應給所有對象,而對象卻無需修改
class Chinese:
country='China'
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
def tell_info(self):
info='''
國籍:%s
姓名:%s
年齡:%s
性別:%s
''' %(self.country,self.name,self.age,self.sex)
print(info)
p1=Chinese('egon',18,'male')
p2=Chinese('alex',38,'female')
p3=Chinese('wpq',48,'female')
print(p1.country)
p1.tell_info()
小節練習
編寫一個學生類,產生一堆學生對象
要求:有一個計數器,統計總共實例化了多少對象
class Student:
def __init__(self,name,sex,age):
self.Name = name
self.Sex = sex
self.Age = age
Student.count += 1
stu1 = Student('小明','男',14)
stu2 = Student('老王','男',38)
stu3 = Student('小麗','女',18)
這時候我們要考慮一個問題,對象的屬性是對象自己的, 所以....類的屬性是大家共有的
class Student:
count = 0
def __init__(self,name,sex,age):
self.Name = name
self.Sex = sex
self.Age = age
Student.count += 1
stu1 = Student('小明','男',14)
stu2 = Student('老王','男',38)
stu3 = Student('小麗','女',18)
print(Student.count)