pythdon day 10 2019/10/13 學習資料來自老男孩教育 [TOC] 1. 反射補充 反射模擬網路瀏覽器登錄 16. 面向對象 16.1 面向對象初步介紹 面對對象(object oriented programming,OOP)編程的思想主要是針對大型軟體設計而來的。面向對象編程 ...
目錄
- pythdon day 10
- 1. 反射補充
- 16. 面向對象
- 17. 類class
- 17.1 類的定義
- 17.2 __init__構造方法和__new__方法
- 17.3 實例屬性和實例方法
- 17.4 類對象/類屬性/類方法/靜態方法
- 17.5 del()方法(析構函數)和垃圾回收機制
- 17.6 call()方法和可調用對象
- 17.7 方法沒有重載/方法的動態性
- 17.8 私有屬性和私有方法(實現封裝)
- 17.9 @property裝飾器
- 17.10 屬性和方法命名總結/類編程風格
- 17.11 繼承(inheritance)
- 17.12 object根類
- 17.13 多重繼承
- 17.14 super()方法獲得父類定義
- 17.15 多態(polymorphism)
- 17.16 特殊方法和特性屬性
- 17.17 對象的淺拷貝和深拷貝
- 17.18 組合
- 18. 面向對象三大特征
- 19. 設計模式:工廠模式與單例模式
pythdon day 10
2019/10/13
學習資料來自老男孩與尚學堂
1. 反射補充
反射模擬網路瀏覽器登錄
# 初級版
'''
from lib import account
url = input('please input the website address:>>> ')
if url.endswith('login'):
r = account.login()
print(r)
elif url.endswith('logout'):
r = account.logout()
print(r)
else:
print('404')
'''
# 中級版
'''
url = input('please input the website address:>>> ')
inp = url.split('/')[1] # ['www.baidu.com','login'][1] == 'login'
if hasattr(account, inp): # 判斷某個模塊中是否有指定對象
target_func = getattr(account, inp, None) # 找到模塊中的指定對象
r = target_func()
print(r)
else:
print('404')
'''
# 高級版
# from lib import account as lat # 從lib包裡面導入account模塊
# print(lat.__name__)
# m = __import__('lib.account', fromlist=True) # 本質上是調用了__import__方法
# print(m.__name__)
inp = input('module/function: ').strip()
module_name, func_name = inp.split('/') # 將列表中的索引值為0與1分別賦值給前面的兩個變數
m = __import__('lib.'+module_name, fromlist=True) # 因為__import__接收字元串作為參數,
if hasattr(m, func_name):
target_func = getattr(m, func_name)
r = target_func()
print(r)
else:
print('404')
16. 面向對象
16.1 面向對象初步介紹
面對對象(object oriented programming,OOP)編程的思想主要是針對大型軟體設計而來的。面向對象編程命名程式的擴展性更強/可讀性更好,使得編程可以像搭積木一樣簡單。
面向對象編程將數據和操作數據相關的方法封裝到對象中,組織代碼和數據的方式更加接近人的思維,從而大大提高了編程的效率。
python完全採用了面向對象的思想,是真正面向對象的編程語言,完全支持面向對象的基本功能,例如:繼承/多態/封裝等。
python中,一切對象。前面學習的數據類型,函數等,都是對象。
python支持面向過程/面向對象/函數式編程等多種編程範式。
16.2 面向對象和麵向過程區別
面向過程(procedure oriented)思維
面向過程編程更加關註的是程式的邏輯流程,是一種“執行者”思維,適合編寫小規模的程式。
面向過程思想思考問題時,我們首先思考“怎麼按步驟實現?”,並將步驟對應成方法,一步一步,最終完成。這個適合簡單任務,不需要過多協作的情況下。- 面向對象(object oriented)思維
面向對象更加關註的是“軟體中對象之間的關係”,是一種“設計者”思維,適合編寫大規模的程式。(
面向對象思想更契合人的思維模式。我們首先思考的是“怎麼設計這個事物?” 。比如,思考造車,就會先思考,“車怎麼設計?”,而不是“怎麼按步驟造車的問題”。這就是思維方式的轉變。
面向對象方式思考造車,發現車由如下對象組成:- 輪胎
- 發動機
- 車殼
- 座椅
- 擋風玻璃
為了便於協作,我們找輪胎廠完成製造輪胎的步驟,發動機廠完成製造發動機的步驟,這樣,發現大家可以同時進行車的製造,最終進行組裝,大大提高了效率。但是,具體到輪胎廠的一個流水線操作,仍然是有步驟的,不是離不開面向過程思想。
因此,面向對象可以幫助我們從巨集觀上把握/從整體上分析整個系統。但是,具體到實現部分的微觀操作(就是一個個方法),仍然需要面向過程的思路去處理。
面向對象和麵向過程是相輔相成的,面向對象離不開面向過程。
面向對象思考方式
遇到複雜問題,先從問題中找名詞(面向過程更多的是找動詞),然後確立這些名詞哪些可以作為類,再根據問題需求確定類的屬性和方法,確定類之間的關係。- 面向對象和麵向過程的總結:
- [x] 都是解決問題的思維方式,都是代碼組織的方式。
- [x] 解決簡單問題可以使用面向過程。
[x] 解決複雜問題:巨集觀上使用面向對象把握,微觀處理上仍然是面向過程。
16.3 對象的進化
隨著編程面臨的問題越來越複雜,編程語言本身也在進化,從主要處理簡單數據開始,隨著數據變多進化“數組”;數據類型變複雜,進化出了“結構體”;處理數據的方式和邏輯變複雜,進化出了“對象”。
- 簡單數據:像30,40,50.2等這些數字,就是簡單的數據。最初的電腦編程,都是像這樣的數字。
- 數組:將同類型的數據放到一起。比如,整數數組[20,30,40],字元串數組['aa','bb','cc']等
- 結構體:將不同類型的數據放到一起,是C語言中的數據結構。比如:
struct resume{int age;char name[10],double salary;};
等 - 對象:將不同類型的數據/方法(即函數)放到一起,就是對象。比如:
class Student:#舊式類寫法
company = 'SXT' #類屬性
conut = 0 #類屬性
def __init__(self,name,score):
self.name = name #實例屬性
self.score = score
Student.count = Student.count + 1
def say_score(self): #實例方法
print('我的公司是:', Student.company)
print(self.name,'我的分數是:',self.score)
17. 類class
17.1 類的定義
可以將對象比作一個“餅干”,類就是製造這個餅干的“模具”。
通過類定義數據類型的屬性(數據)和方法(行為),也就是說,“類將行為和狀態打包在一起”。
對象是類的具體實體,一般稱為“類的實例”。類看做餅干模具,對象就是根據這個模具製造出的餅干。
從一個類創建對象時,每個對象都會共用這個類的行為(即類中定義的方法),但會有自己的屬性值(不共用狀態)。更具體一點:“方法代碼是共用的,屬性數據不共用”。
python中,一切皆對象。類也稱為"類對象",類的實例也稱為“實例對象”。
定義類的語法格式如下:
class 類名:
類體
要點如下:
- 類名必須符合"標識符"的規則;一般規定,首字母大字,多個單詞使用“駝峰原則”,即每個單詞首字母大寫。
- 類體中可以定性屬性和方法。
- 屬性用來描述數據,方法(即函數)用來描述這些數據相關的操作。
# 一個典型的類的定義
class Student(object):#新式類寫法
def __init__(self,name,score):#__init__是構造函數
self.name = name #實例屬性
self.score = score
def say_score(self): #實例方法
print(self.name,'我的分數是:',self.score)
s1 = Student('張三',80)
#s1是實例對象,實際是Student.__init__(s1,'張三',80)
s1.say_score() #實際是Student.say_score(s1)
17.2 __init__構造方法和__new__方法
類是抽象的,也稱為為“對象的模板”。我們需要通過類這個模板,創建類的實例對象,然後才能使用類定義的功能。
一個python對象包含三個部分:id(identity識別碼)/type(對象類型)/value(對象的值)
現在,可以更進一步的說,一個python對象包含如下部分:
1. id(identity識別碼)
2. type(對象類型)
3. value(對象的值)
(1) 屬性(attribute)
(2) 方法(method)
創建對象,我們需要定義構造函數__init__()方法。構造方法用於執行"實例對象的初始化工作",即對象創建後,初始化當前對象的相關屬性,無返回值。
init()的要點如下:
- 名稱固定,必須為:init():
- 第一個參數固定,必須為self.self指的就是剛剛創建好的實例對象。
- 構造函數通常用來初始化實例對象的實例屬性。
- 通過“類名(參數列表)”來調用構造函數。調用後,將創建後的對象返回給相應的變數。比如:s1 = Student('張三',80)
- init()方法:初始化創建好的對象,初始化指的是:給實例屬性賦值
- new()方法:用於創建對象,但我們一般無需重定義該方法。
- 如果我們不定義__init__()方法,系統會提供一個預設的__init__方法。如果我們定義了帶參的__init__方法,系統不創建預設的__init__方法。
註:
1. python中的self相當於C++中的self指針,JAVA和C#中的this關鍵字。python中,self必須為構造函數的第一個參數,名字可以任意修改。但一般遵守慣例,都叫做self。
17.3 實例屬性和實例方法
17.3.1 實例屬性(實例變數)
實例屬性是從屬於實例對象的屬性,也稱為“實例變數”。他的使用有如下幾個要點:
- 實例屬性一般在__init__()方法中通過如下代碼定義:
self.實例屬性名 = 初始值
- 在本類的其他實例方法中,也是通過self 進行訪問:
self.實例屬性名
- 創建實例對象後,通過實例對象訪問:
obj01 = 類名() #創建對象,調用__init__()初始化屬性
obj01.實例屬性名 = 值 #可以給已有屬性賦值,也可以新加屬性
17.3.2 實例方法
實例方法是從屬於實例對象的方法。實例方法的定義格式如下:
def 方法名(self [, 形參列表]):
函數體
方法的調用格式如下:
對象.方法名([實參列表])
要點:
- 定義實例方法時,第一個參數必須為self。和前面一樣,self 指當前的實例對象。 2. 調用實例方法時,不需要也不能給self 傳參。self 由解釋器自動傳參。
- 函數和方法的區別
- 都是用來完成一個功能的語句塊,本質一樣。
- 方法調用時,通過對象來調用。方法從屬於特定實例對象,普通函數沒有這個特點。
- 直觀上看,方法定義時需要傳遞self,函數不需要。
class Student(object):#類名一般首字母大寫,多個單詞每個單詞首字母大寫
'''定義類的測試'''
def __init__(self,name,score):#構造函數的第一個參數必須是self
self.name = name
self.score = score
def say_score(self):#對象的方法,第一個參數也必須是self。
print('{0}的分數是{1}'.format(self.name,self.score))
s1 = Student('小藍',90) ## Student.__init(s1,'小藍',90)
s1.say_score() ## Student.say_score(s1)
s1.age = 28
s1.salary = 3800
print(s1.salary)
print(s1.say_score)
s2 = Student('小張',20) #s2並不會有salary,age屬性。
print(dir(s1)) #獲得對象的的所有屬性和方法
print(s1.__dict__) #對象的屬性,將屬性以字典形式返回
print(isinstance(s1,list)) #判斷“對象”是不是“指定類型”
小藍的分數是90
3800
<bound method Student.say_score of <__main__.Student object at 0x000001CBEBADF9B0>>
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__re
duce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'ag
e', 'name', 'salary', 'say_score', 'score']
{'name': '小藍', 'score': 90, 'age': 28, 'salary': 3800}
False
17.4 類對象/類屬性/類方法/靜態方法
17.4.1 類對象
當解釋器執行class語句時,就會創建一個類對象。
class Student(object):
'''測試類之二'''
pass #空語句,占位符
print(type(Student),'\r\n*******\r\n',id(Student))
Stu2 = Student #實際就是生成了一個變數名就是類名“Student”的對象。也可以賦值給新變數Stu2.
s1 = Stu2()
print(s1)
17.4.2 類屬性(類變數)
類屬性是從屬於"類對象"的屬性,也稱為"類變數"。由於類屬性從屬於類對象,可以被所有實例對象共用。
類屬性的定義方式:
class 類名:
類變數名 = 初始值
在類中或者類的外面,我們可以通過:"類名.類變數名"來讀寫。
class Student(object):
'''類屬性的使用測試'''
company = 'SXT' #類屬性
count = 0 #類屬性
def __init__(self,name,score):
self.name = name #實例屬性
self.score = score
Student.count += 1
def say_score(self): #實例方法
print('我的公司是:',Student.company)
print(self.name,'的分數是:',self.score)
s1 = Student('張三',80) #s1是實例對象,自動調用__init__() 方法
s1.say_score()
print('一共創建{0}個Student對象'.format(Student.count))
17.4.3 類方法
類方法是從屬於"類對象"的方法。類方法通過裝飾器@classmethod來定義,格式如下:
@classmethod
def 類方法名(cls [,開參列表]):
函數體
要點如下:
- @classmethod必須位於方法上面一行。
- 第一個cls必須有;cls指的就是"類對象"本身。
- 調用類方法格式:"類名.類方法名(參數列表)"。參數列表中,不需要也不能給cls傳遞值。
- 類方法中訪問實例屬性和實例方法導致錯誤。
- 子類繼承父類方法時,傳入cls是子類對象,而非父類對象。
class Student(object):
'''類屬性/類方法的使用測試'''
company = 'SXT' #類屬性
count = 0 #類屬性
@classmethod
def printCompany(cls):
print(cls.company)
def __init__(self,name,score):
self.name = name #實例屬性
self.score = score
Student.count += 1
def say_score(self): #實例方法
print('我的公司是:',Student.company)
print(self.name,'的分數是:',self.score)
# s1 = Student('張三',80) #s1是實例對象,自動調用__init__() 方法
# s1.say_score()
# print('一共創建{0}個Student對象'.format(Student.count))
Student.printCompany()
17.4.4 靜態方法
python中允許定義與"類對象"無關的方法,稱為"靜態方法".
“靜態方法”和在模塊中定義普通函數沒有區別,只不過“靜態方法”放到了“類的名字空間裡面”,需要通過“類調用”。
靜態方法通過裝飾器@staticmethod來定義,格式如下:
@staticmethod
def 靜態方法名([形參列表]) :
函數體
要點如下:
- @staticmethod必須位於方法上面一行
- 調用靜態方法格式:“類名.靜態方法名(參數列表)”。
- 靜態方法中訪問實例屬性和實例方法會導致錯誤
class Student(object):
'''類屬性的使用測試'''
company = 'SXT' #類屬性
count = 0 #類屬性
def __init__(self,name,score):
self.name = name #實例屬性
self.score = score
Student.count += 1
def say_score(self): #實例方法
print('我的公司是:',Student.company)
print(self.name,'的分數是:',self.score)
@staticmethod
def add(a, b): # 靜態方法
print('{0}+{1}={2}'.format(a, b, a + b))
return a + b
@classmethod
def printCompany(cls):#類方法
print(cls.company)
s1 = Student('張三',80) #s1是實例對象,自動調用__init__() 方法
# s1.say_score()
# print('一共創建{0}個Student對象'.format(Student.count))
# Student.printCompany()
print(Student.add(1.4,7))
s1.add(1,2)
1.4+7=8.4
8.4
1+2=3
17.5 del()方法(析構函數)和垃圾回收機制
**__del__方法稱為“析構方法”,用於實現對象被銷毀時所需的操作**。比如:釋放對象 占用的資源,例如:打開的文件資源、網路連接等。
Python實現自動的垃圾回收,當對象沒有被引用時(引用計數為 0),由垃圾回收器調用__del__方法。
我們也可以通過del 語句刪除對象,從而保證調用__del__方法。
系統會自動提供__del__方法,一般不需要自定義析構方法。
class Person(object):
def __del__(self):
print('銷毀對象{0}'.format(self))
p1 = Person()
p2 = Person()
del p2
print('程式結束 ')
17.6 call()方法和可調用對象
定義了__call__方法的對象,稱為“可調用對象”,即該對象可以像函數一樣被調用
class SalaryAccount(object):
def __call__(self, salary,*args, **kwargs):
print('算工資啦')
# return 3000
yearsalary = salary*12
daysalary = salary//22.5
hoursalary = daysalary//8
return dict(yearsalary= yearsalary,daysalary=daysalary,hoursalary=hoursalary)
s = SalaryAccount()
print(s(30000))
17.7 方法沒有重載/方法的動態性
17.7.1 方法沒有重載
在其他語言中,可以定義多個重名的方法,只要保證方法簽名唯一即可。方法簽名包含 3 個部分:方法名、參數數量、參數類型。
Python中,方法的的參數沒有聲明類型(調用時確定參數的類型),參數的數量也可以由 可變參數控制。因此,Python中是沒有方法的重載的。定義一個方法即可有多種調用方式, 相當於實現了其他語言中的方法的重載。
如果我們在類體中定義了多個重名的方法,只有最後一個方法有效。
建議:不要使用重名的方法!Python中方法沒有重載。只有最後一個有效。
17.7.2 方法的動態性
class Person(object):
# def __del__(self):
# print('銷毀對象{0}'.format(self))
def work(self):
print('努力上班!')
def play_game(self):
print('{0}玩游戲'.format(self))
def work2(s):
print('好好工作,努力上班')
Person.play_game = play_game
p1 = Person()
p2 = Person()
p1.work()
p1.play_game()
Person.work = work2
p1.work()
努力上班!
<__main__.Person object at 0x000001D55025F9B0>玩游戲
好好工作,努力上班
17.8 私有屬性和私有方法(實現封裝)
Python對於類的成員沒有嚴格的訪問控制限制,這與其他面向對象語言有區別。關於私有 屬性和私有方法,有如下要點:
- 通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public)
- 類內部可以訪問私有屬性(方法)
- 類外部不能直接訪問私有屬性(方法)
- **類外部可以通過“_類名__私有屬性(方法)名”訪問私有屬性(方法)**
【註】方法本質上也是屬性!只不過是可以通過()執行而已。所以,此處講的私有屬性和公 有屬性,也同時講解了私有方法和公有方法的用法。如下測試中,同時也包含了私有方法和 公有方法的例子。
#測試私有屬性
class Employee(object):
__company = '藍星科技' #類的私有屬性
def __init__(self,name,age):
self.name = name
self.__age = age #實例的私有屬性,加了兩個下劃線
def __work(self):#私有方法,加了兩個下劃線
print('好好工作,賺錢養老婆')
print('年齡:{0}'.format(self.__age))#類內部調用私有屬性是完全沒有問題的。
print(Employee.__company)
e = Employee('小藍',18)
print(e.name)
# print(e.__age)
print(e._Employee__age)
print(dir(e))
e._Employee__work()
print(Employee._Employee__company)
小藍
18
['_Employee__age', '_Employee__company', '_Employee__work', '__class__', '__delattr__', '__dict__', '__dir__', '__do
c__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '
__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__s
izeof__', '__str__', '__subclasshook__', '__weakref__', 'name']
好好工作,賺錢養老婆
年齡:18
藍星科技
藍星科技
從列印的 Person 對象所有屬性我們可以看出。私有屬性“__age”在實際存儲時是按照 “_Person__age”這個屬性來存儲的。這也就是為什麼我們不能直接使用“__age”而可以 使用“_Person__age”的根本原因。
17.9 @property裝飾器
@property可以將一個方法的調用方式變成"屬性調用"。一般用來給對應的屬性增加get和set方法。
@property 主要用於幫助我們處理屬性的讀操作、寫操作。對於某一個屬性,我們可以直 接通過:
emp1.salary = 30000
如上的操作:讀操作、寫操作。但是,這種做法不安全。比如,我需要限制薪水必須為1-10000 的數字。這時候,我們就需要通過getter、setter方法來處理。
class Employee(object):
def __init__(self,name,salary):
self.__name = name
self.__salary = salary
@property #定義下麵的方法變成了一個屬性
def salary(self):
print('計算工資')
return self.__salary
@salary.getter
def get_salary(self,salary):
return self.__salary
@salary.setter #針對salary屬性的一個設置
def salary(self,salary):
if 1000<salary<50000:
self.__salary = salary
else:
print('錄入錯誤,薪水在1000--50000這個範圍')
'''
def get_salary(self):
return self.__salary
def set_salary(self,salary):
if 1000<salary<50000:
self.__salary = salary
else:
print('錄入錯誤,薪水在1000--50000這個範圍')
'''
# emp1 = Employee('藍星',30000)
# print(emp1.get_salary())
# emp1.set_salary(20000)
# print(emp1.get_salary())
# emp1.salary = 20000 #不能設置
# emp1.salary() #也可像方法一樣調用了,因為salary已經是一個屬性了。
emp2 = Employee('lanxing',20000)
print(emp2.salary)
emp2.salary = -29990
emp2.salary = 2000
17.10 屬性和方法命名總結/類編程風格
17.10.1 屬性和方法命名總結
· _xxx
:保護成員,不能用“frommodule import * ”導入,只有類對象和子類對象能訪 問這些成員。
· __xxx__
:系統定義的特殊成員
· __xxx
: 類中的私有成員,只有類對象自己能訪問,子類對象也不能訪問。(但,在類外部可以通過“對象名._類名__xxx”這種特殊方式訪問。 Python 不存在嚴格意義的私有成員)
註:再次強調,方法和屬性都遵循上面的規則。
17.10.2 類編碼風格
- 類名首字母大寫,多個單詞之間採用駝峰原則。
- 實例名、模塊名採用小寫,多個單詞之間採用下劃線隔開。
- 每個類,應緊跟“文檔字元串”,說明這個類的作用。
- 可以用空行組織代碼,但不能濫用。在類中,使用一個空行隔開方法;模塊中,使用兩個空行隔開多個類。
17.11 繼承(inheritance)
繼承是面向對象程式設計的重要特征,也是實現“代碼復用”的重要手段。
如果一個新類繼承自一個設計好的類,就直接具備了已有類的特征,就大大降低了工作難度。已有的類,我們稱為“父類或者基類”,新的類,我們稱為“子類或者派生類”。
17.11.1 繼承的語法格式
python支持多重繼承,一個子類可以繼承多個父類。繼承的語法格式如下:
class 子類類名(父類1[,父類2,...]):
類體
如果在類定義中沒有指定父類,則預設父類是object類。也就是說,object是所有類的父類,裡面定義了一些所有類共有的預設實現,比如__new__().
定義子類時,必須在其構造函數中調用父類的構造函數。調用格式如下:
父類名.__init__(self,參數列表):
class Person(object):
def __init__(self,name,age):
self.name = name
self.__age = age #私有屬性
def say_age(self):
print('年齡,年齡,我也不知道')
class Student(Person):
def __init__(self,name,age,score):
#調用父類的構造函數方法一
Person.__init__(self,name,age)#語法級別上不調用也沒錯,但是作為子類,必須要去調用,不然這個子類就沒有name屬性了。
self.score = score
#Student--->Person--->object類
print(Student.mro())#列印繼承順序
s = Student('lanxing',22,78)
s.say_age()
print(s.name)
# print(s.age)
print(s._Person__age)
17.11.2 類成員的繼承和重寫
- 成員繼承:子類繼承了父類除構造方法之外的所有成員。
- 方法重寫:子類可以重新定義父類中的方法,這樣就會覆蓋父類的方法,也稱為重寫
#類成員繼承和方法重寫的案例
class Person(object):
def __init__(self,name,age):
self.name = name
self.__age = age
def say_age(self):
print('我的年齡是',self.__age)
def say_intro(self):
print('我的名字是{0}'.format(self.name))
class Student(Person):
def __init__(self,name,age,score):
Person.__init__(self,name,age) #必須顯式的調用父類初始化方法,不然解釋器不會去調用。
self.score = score
def say_score(self):
print(self.name,'的分數是:',self.score )
def say_name(self): #重寫父類的方法
print('報告老師,我是',self.name)
s1 = Student('張三',15,85)
s1.say_score()
s1.say_name()
s1.say_age()
17.11.3 查看類的繼承層次結構
通過類的方法mro()或者類的屬性__mro__可以輸出這個類的繼承層次結構。
class A:
pass
class B(A):
pass
class C(B):
pass
#print(C.mro())
print(C.__mro__)#與上方代碼是一樣的效果
[<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
17.12 object根類
17.12.1 object根類的屬性
object類是所有類的父類,因此所有的類都有object類的屬性和方法。顯然有必要深入研究下object類的結構。
- dir()查看對象屬性
內置函數dir()可以查看指定對象所有的屬性。
class Person(object):
def __init__(self,name,age):
self.name = name
self.__age = age #私有屬性
def say_age(self):
print(self.name,'的年齡是:',self.age)
obj = object() #obj是object這個基類實例化的對象
print(dir(obj)) #輸出obj這個對象的所有屬性
print('----------')
s2 = Person('lanxing',19)
print(dir(s2))
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format
__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '
__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce
__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str_
_', '__subclasshook__']
-------------------
['_Person__age', '__class__', '__delattr__', '__dict__', '__dir__', '_
_doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__
', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '_
_module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__rep
r__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__w
eakref__', 'name', 'say_age']
從上面我們可以發現這樣幾個要點:
- Person對象增加了六個屬性: dict, module, weakref ,age, name, say_age
- object的所有屬性,Person 類作為object 的子類,顯然包含了所有的屬性。
- 我們列印age、name、say_age,發現say_age 雖然是方法,實際上也是屬性。只不過, 這個屬性的類型是“method”而已。
age <class 'int'>
name <class 'str'>
say_age <class 'method'>
17.12.2 重寫__str__方法
class Person(object):
def __init__(self,name,age):
self.name = name
self.__age = age #私有屬性
p = Person('LANXING',22)
print(p)
<__main__.Person object at 0x000001EC5F1CF940>
class Person(object):
def __init__(self,name,age):
self.name = name
self.__age = age #私有屬性
def __str__(self):
'''將對象轉化成一個字元串,一般用於print方法'''
return '名字是:{0},年齡是{1}'.format(self.name,self.__age)
p = Person('LANXING',22)
print(p)
名字是:LANXING,年齡是22
17.13 多重繼承
python支持多重繼承,一個子類可以有多個"直接父類"。這樣,就具備了多個父類的特點。但是,由於這樣會被類的整體層次搞的異常複雜,儘量避免使用。
在python3中,不管是新式類寫法還是經典類寫法,都是按照廣度優先進行查詢。
python2中,新式類寫法是按照廣度優先,經典類寫法是按照深度優先。
class A: #經典類寫法
pass
class B(A): #新式類寫法
pass
class C(B,A): #多重繼承
pass
class D(C,B)
#廣度優先就是D先從C查詢,C沒有,就找B,B再沒有就找A。
#深度優先就是D先從C查詢,如果C沒有,就再找A。
17.14 super()方法獲得父類定義
在子類中,如果想要獲得父類的方法時,可以通過super()來獲得。
super()代表父類的定義,不是父類的對象。
class A:
def say(self):
print('A:',self)
class B(A):
def say(self):
# A.say(self)
super().say() #super()=A
print('B:',self)
B().say()
17.15 多態(polymorphism)
多態(polymorphism)是指同一個方法調用由於對象不同可能會產生不同的行為。
關於多態要註意以下2點:
- 多態是方法的多態,屬性沒有多態。
- 多態的存在有2個必要條件:繼承和方法重寫。
class Man(object):
def eat(self):
print('餓了,吃飯了!')
class Chinese(Man):
def eat(self): #方法重寫
print('中國人用筷子吃飯')
class English(Man):
def eat(self):
print('英國人用叉子吃飯')
class Indian(Man):
def eat(self):
print('印度人用右手吃飯')
def manEat(m):
if isinstance(m,Man):
m.eat() #多態
else:
print('不能吃飯')
manEat(Chinese())
manEat(English())
17.16 特殊方法和特性屬性
17.17 對象的淺拷貝和深拷貝
- 變數的賦值操作
只是形成兩個變數,實際還是指向同一個對象。 - 淺拷貝
Python拷貝一般都是淺拷貝。拷貝時,對象包含的子對象內容不拷貝。因此,源對象 和拷貝對象會引用同一個子對象。 - 深拷貝
使用copy模塊的 deepcopy 函數,遞歸拷貝對象中包含的子對象。源對象和拷貝對象所有的子對象也不同。
import copy
class MobilePhone(object):
def __init__(self,cpu,screen):
self.cpu = cpu
self.screen = screen
class Cpu:
def calculate(self):
print('計算,算個12345')
print('cpu的對象',self)
class Screen:
def show(self):
print('顯示一個好看的畫面')
print('屏幕對象:',self)
#測試變數賦值
c1 = Cpu()
c2 = c1
# print(c1)
# print(c2)
#測試淺複製
s1 = Screen()
m1 = MobilePhone(c1,s1)
m2 = copy.copy(m1)
# print(m1,m1.cpu,m1.screen)
# print(m2,m2.cpu,m2.screen)
#測試深複製
m3 = copy.deepcopy(m1)
print(m1,m1.cpu,m1.screen)
print(m3,m3.cpu,m3.screen)
17.18 組合
"is-a"關係,我們可以使用“繼承”。從而實現子類擁有父類的方法和屬性。“is-a” 關係指的是類似這樣的關係:狗是動物,dog is animal。狗類就應該繼承動物類。
"has -a"關係,我們可以使用“組合”,也能實現一個類擁有另一個類的方法和屬性。has-a”關係指的是這樣的關係:手機擁有 CPU。 MobilePhone has a CPU.
#使用繼承實現代碼的復用
class A1:
def say_a1(self):
print('a1,a1,a1')
class B1(A1):
pass
b1 = B1()
b1.say_a1()
#同樣的效果,使用組合來實現代碼的復用
class A2:
def say_a2(self):
print('a2,a2,a2')
class B2:
def __init__(self,a):
self.a =a
a2 = A2()
b2 = B2(a2)
b2.a.say_a2()
18. 面向對象三大特征
python是面向對象的語言,也支持面向對象編程的三大特性:繼承/封裝(隱藏)/多態
18.1 封裝(隱藏)
隱藏對象的屬性和實現細節,只對外提供必要的方法。相當於將“細節封裝起來”,只對外暴露“相關調用的方法”。
通過前面學習的“私有屬性/私有方法”的方式,實現“封裝”。python追求簡潔的語法,沒有嚴格的語法級別的訪問控制符,更多的是依靠程式員自覺實現。
18.2 繼承
繼承可以讓子類具有父類的特性,提高了代碼的重用性。
從設計上是一種增量進化,原有父類設計不變的情況下,可以增加新的功能,或者改進已有的演算法。
18.3 多態
多態是指同一個方法調用由於對象不同會產生不同的行為。生活中這樣的例子比比皆是:同樣是休息方法,人不同休息方法不同。張三休息是睡覺,李四休息是玩游戲,程式員休息是"敲幾行代碼"。即“一個介面,多種實現”。
19. 設計模式:工廠模式與單例模式
設計模式是面向對象語言特有的內容,是我們在面臨某一類問題時候固定的做法,設計模式有很多種,比較流行的是:GOF,23種設計模式。
對於初學者,學習兩個最常用的模式:工廠模式與單例模式。
19.1 工廠模式
工廠模式實現了創建者和調用者的分離,使用專門的工廠類將選擇實現類/創建對象進行了統一的管理和控制。越是大型的軟體,越是需要工廠模式。
#工廠模式
class CarFactory(object):
def createCar(self,brand):
if brand =="benz":
return Benz()
elif brand =="baoma":
return Baoma()
elif brand == "biyadi":
return Biyadi()
else:
return '未知品牌,不能代工'
class Benz:
pass
class Baoma:
pass
class Biyadi:
pass
factory = CarFactory()
c1 = factory.createCar('benz')
c2 = factory.createCar('baoma')
print(c1)
print(c2)
<__main__.Benz object at 0x0000022FD5D604A8>
<__main__.Baoma object at 0x0000022FD5D604E0>
19.2 單例模式
單例模式(singleton pattern)的核心作用是確保一個類只有一個實例對象,並且提供一個訪問該實例的全局訪問點。
單例模式只生成一個實例對象,減少了對系統資源的開銷。當一個對象的產生需要比較多的資源,如讀取配置文件/產生其他依賴對象時,可以產生一個“單例對象”,然後永久駐留記憶體中,從而極大的降低開銷。
單例模式有多種實現的方法,這裡推薦重寫__new__()的方法。
class MySingleton(object):
__obj = None #類屬性
__init_flag = True
def __new__(cls, *args, **kwargs):
if cls.__obj == None:
cls.__obj =object.__new__(cls)
return cls.__obj
def __init__(self,name):
if MySingleton.__init_flag:
self.name = name
print('init.....')
MySingleton.__init_flag = False
a = MySingleton('aa')
b = MySingleton('bb')
print(a)
print(b)