一、 面向對象特性之多態:
上一篇已經介紹了面向對象的三大特性的前兩種(封裝、繼承),下面來說說第三種多態,在python中用不上,但在其他語言中很重要。
多態的意思就是多種類型、多種形態,比如字符類型,數字,字典,列表等。在python中定義類或函數時不需要,指定是那種數據類型全都支持,但是在java、c#等其他語言中需要指定如下實例:
def func(A arg) print(arg) arg :參數必須是A 類型,或A類型的子類
二、類成員:
類成員分為三大類:字段、方法、屬性
1、字段
在類中字段分為靜態字段和普通字段
普通字段(動態字段):存儲在對象中,由對象調用
靜態字段:存儲在類中,由類調用,在代碼加載時,就已經創建了
調用規則:
1、一般情況下自己訪問自己;
2、普通字段,只能由對象訪問
3、靜態字段用類訪問,(萬不得已的時候也可以用對象訪問,不建議使用)
兩者區別如下:
class Foo: CC = 123 # CC是靜態字段,保存在類中 def __init__(self): self.name = "tom" # name 就是普通字段,保存在對象中
實際調用如下:
class Province: contry="中國" #靜態字段 def __init__(self,name): self.name=name #普通字段 sx=Province("河南") #靜態調用 如 Province.contry #普通調用 如 sx.name print(Province.contry,sx.name) #顯示結果 中國 河南
靜態字段存儲在類中, 只在內存中保存一份;
普通字段存儲在每個對象中,需要在每個對象中都保存一份
應用場景:如果創建對象時,都要需要某一個相同的字段,可以把字段設置為靜態字段,節省內存
2、方法
方法都屬於類包括:
靜態方法:屬於類,由類來調用執行,無默認參數,等同於函數。創建方式: 方法上邊加個@staticmethod
普通方法:由對象調用,至少需要一個self參數。執行普通方法時,self就是對象本身,自動將調用該方法的對象賦值給self
類方法:是靜態方法的一種特殊形式。由類調用,至少要有一個參數cls,值為類名。創建方式: @classmethod
class Province: contry="中國" def __init__(self,name): #普通方法 self.name=name def show(self): print("普通方法") @classmethod def class_show(cls): #類方法 print(cls) @staticmethod def static_show(): #靜態方法 print("靜態方法") sx=Province("河南") #普通方法調用 sx.show() #類方法調用 Province.class_show() #靜態方法調用 Province.static_show() #顯示結果 普通方法 <class '__main__.Province'> #是這個類名 靜態方法
相同點:由於所有的方法都屬於類, 所以在內存中存儲只保存一份。
不同點:由於各種方法的調用方式不同,調用方法時自動傳入的參數不同。
給上面字段一樣,在對象中也是可以調用靜態方法和類方法的。不到萬不得已還是不要用,要遵循變成原則。
3、屬性
屬性就是普通方法的變種
下面來看一下屬性的定義:
class Foo: def show(self): print("普通方法") @property #定義屬性 def prop(self): pass #調用 obj=Foo() obj.show() #調用方法 obj.prop #調用屬性
注意:
定義時,在普通方法的基礎上添加@property裝飾器;僅有一個參數self
調用時,無需加括號
還有一種屬性的定義調用方法:
class Foo(): @property def price(self): print('查詢') @price.setter def price(self, value): print('設置') @price.deleter def price(self): print('刪除') # ############### 調用 ############### obj = Foo() obj.price # 自動執行 @property 修飾的 price 方法,並獲取方法的返回值 obj.price = 123 # 自動執行 @price.setter 修飾的 price 方法,並將 123 賦值給方法的參數 del obj.price # 自動執行 @price.deleter 修飾的 price 方法 #執行結果 查詢 設置 刪除
三、類成員修飾符
公有成員:就是在哪都能訪問,
私有成員,只有在類的內部才能放問,其他都不能訪問,繼承關系也不能
定義私有成員:命名是前面是兩個下劃線,(特殊成員除外,例如:__init__、__call__、__dict__等)
如下:
class Foo: def __init__(self): self.name="公有字段" self.__fuck="私有字段"
四、類的特殊成員
1、__init__
構造方法,通過類創建對象時,自動觸發
class Foo: def __init__(self,name): self.name = name # name 就是普通字段 self.job="IT" obj=Foo("tom") #自動執行類中的__init__方法 print(obj.name) print(obj.job) #結果 tom IT
2、__doc__
表示類的描述信息,就是注釋
class Foo: ''' 描述信息 ''' def __init__(self,name): self.name = name # name 就是普通字段 self.job="IT" print(Foo.__doc__) #顯示結果 描述信息View Code
3、__del__
析構方法,用於對象在內存中垃圾回收時,自動觸發執行
4、__call__
在對象後面加括號,執行
對於 __call__ 方法的執行是由對象後加括號觸發的,即:對象() 或者 類()()
class Foo: def __init__(self,name): self.name = name # name 就是普通字段 def __call__(self, *args, **kwargs): print("執行call方法") obj=Foo("test") #執行__init__方法 obj() #執行__call__方法View Code
5、__module__、__class__
__module__表示當前操作的對象在那個模塊
__class__ 表示當前操作的對象的類是誰
#!/usr/bin/env python3 # -*- coding: utf-8 -*- class Foo: ''' 這是類的描述信息, 由特殊成員 __doc__調用 ''' def __init__(self,name): self.name = name # print(self.name) def show_info(self): print(self.name) bin/modules.py內容bin/modules.py
from bin import modules obj = modules.Foo('DBQ') print(obj.__module__) # 查看當前操作的對象屬於哪個模塊 print(obj.__class__) #查看當前操作的對象的類是哪個 #執行結果: bin.modules <class 'bin.modules.F1'>View Code
6、__dict__
獲取對象或類中的所有成員
class Foo: CC="test" def __init__(self,name): self.name = name # name 就是普通字段 #獲取類中的所有成員 print(Foo.__dict__) #獲取對象中的成員 obj=Foo("tom") print(obj.__dict__) #顯示結果 {'__weakref__': <attribute '__weakref__' of 'Foo' objects>, 'CC': 'test', '__doc__': None, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__init__': <function Foo.__init__ at 0x00000041DAED61E0>} {'name': 'tom'}View Code
7、__str__
如果一個類中定義了__str__方法,那麼打印對象時,默認輸出該方法的返回值
class Foo: CC="test" def __init__(self,name): self.name = name # name 就是普通字段 def __str__(self): return "hi tom" obj=Foo("tom") print(obj) #結果 hi tomView Code
8、__iter__
用於迭代器,之所以列表、字典、元組可以進行for循環,是因為類型內部定義了__iter__
class Foo(): def __init__(self, sq): self.sq = sq def __iter__(self): return iter(self.sq) obj = Foo([11,22,33,44]) for i in obj: print(i)View Code
9、__getitem__、__setitem__、__deltiem__
用於索引操作, 如字典。 分別表示獲取、設置、刪除數據。
class Foo(): def __getitem__(self, item): print("執行get操作,調用__getitem__方法") def __setitem__(self, key, value): print("執行set操作,調用__setitem__方法") def __delitem__(self, key): print("執行del操作,調用__delitem__方法") obj=Foo() obj['a1'] #get操作,自動觸發__getitem__方法 obj["a1"]=[1,2,3,4,5] #set操作,自動調用__setitem__方法" del obj['a1'] #del操作,自動調用__delitem__方法 #結果 執行get操作,調用__getitem__方法 執行set操作,調用__setitem__方法 執行del操作,調用__delitem__方法View Code
10、__getslice__、__setslice__、__delslice__
用於分片操作
class Foo(object): def __getslice__(self, i, j): print '__getslice__',i,j def __setslice__(self, i, j, sequence): print '__setslice__',i,j def __delslice__(self, i, j): print '__delslice__',i,j obj = Foo() obj[-1:1] # 自動觸發執行 __getslice__ obj[0:1] = [11,22,33,44] # 自動觸發執行 __setslice__ del obj[0:2] # 自動觸發執行 __delslice__python2.7執行
在python3中還是運用的__getitem__、__setitem__、__deltiem__
class Foo(): def __getslice__(self, i, j): print('__getslice__',i,j) def __setslice__(self, i, j, sequence): print('__setslice__',i,j) def __delslice__(self, i, j): print('__delslice__',i,j) def __getitem__(self, item): print("執行get操作,調用__getitem__方法") def __setitem__(self, key, value): print("執行set操作,調用__setitem__方法") def __delitem__(self, key): print("執行del操作,調用__delitem__方法") obj = Foo() obj[-1:1] # 自動觸發執行 __getslice__ obj[0:1] = [11,22,33,44] # 自動觸發執行 __setslice__ del obj[0:2] # 自動觸發執行 __delslice__ #結果 執行get操作,調用__getitem__方法 執行set操作,調用__setitem__方法 執行del操作,調用__delitem__方法python3中
11. __new__ 、 __metaclass__
class F1: def __init__(self,name): self.name = name def show_info(self): print(self.name) obj = F1('tom') print(type(obj)) print(type(F1)) # #執行代碼結果: # <class '__main__.F1'> #表示 obj對象由 F1類實例化而來 # <class 'type'> #表示 F1類由 type 類創建
python中一切介對象,上述代碼中obj是F1的一個對象,那麼可以推理F1其實也是一個對象
所以,obj對象時F1類的一個實例,F1類對象時type類的一個實例,F1類對象時通過type類構造方法創建
創建方法有兩種普通方法、特殊方法:
普通方法:
class F1: def __init__(self,name): self.name = name def show_info(self): print(self.name)
特殊方法:
def show_info(self): print('tom') F1 = type('F1',(object,),{'show_info':show_info}) #第一個參數: 類名 #第二個參數: 當前類的基類 #第三個參數: 類成員 obj = F1() obj.show_info()
==》 類 是由 type 類實例化產生
那麼問題來了,類默認是由 type 類實例化產生,type類中如何實現的創建類?類又是如何創建對象?
答:類中有一個屬性 __metaclass__,其用來表示該類由 誰 來實例化創建,所以,我們可以為 __metaclass__ 設置一個type類的派生類,從而查看 類 創建的過程。
五、面向對象其他相關知識
1、isinstance(obj,cls)
檢查是否obj是否是類 cls 的對象
判斷一個對象是不是類創建的()實例,返回布爾值,繼承的父類也為真
class F1: pass class F2(F1): pass obj = F2() print(isinstance(obj,F1)) #查看是否是父類的實例, 為真 print(isinstance(obj,F2)) #查看是否是F2類的實例, 為真
2、issubclass(F1,F2)
檢查F1是否是F2的子類
查看是否是某類的子類
class F1: pass class F2(F1): pass obj = F2() print(issubclass(F2,F1)) #查看F2是否是F1的子類, 為真 print(issubclass(F1,F2)) #查看F1是否是F2的子類, 為假
3、 super
擴展別人的源碼 ,盡量不在源碼中修改
class C1: def f1(self): print('C1.f1') class C2(C1): def f1(self): super(C2,self).f1() #在執行C2代碼之前,執行C1中的f1方法 也就是C2父類的f1方法 print('C2.f1') obj = C2() obj.f1() #結果 C1.f1 C2.f1
五、異常處理與捕獲
1、異常處理基礎
增加友好性,在程序出現bug中一般不會將錯誤信息顯示給用戶,而是顯示一個頁面
while True: num = input('請輸入你一個或多個整數: ').strip() try: num = int(num) print('你輸入的數字是: %d'%num) except Exception: print('%s, 你輸入的不是一個整數格式!'%Exception) # 如果輸入的是一個整數類型,將返回輸入的號碼 # 如果輸入的是其他的類型,如字符串、浮點數等,會提示用戶輸入的不是一個整數格式! ####執行結果: 請輸入你一個或多個整數: 123 你輸入的數字是: 123 請輸入你一個或多個整數: a <class 'Exception'>, 你輸入的不是一個整數格式! 請輸入你一個或多個整數: 1. <class 'Exception'>, 你輸入
2.異常處理
捕獲異常可以使用 try / except語句。try: 用來檢測語句塊中的錯誤,從而讓 except中語句捕獲的異常信息並處理。
打開一個文件,往文件中寫入內容,並且沒有發生異常:
try: f = open('test.txt','w') f.write('測試文件,用於測試異常捕獲') except IOError: print('Error: 寫入失敗, 沒有找到文件或者權限不足!') else: print('寫入成功!') f.close() #執行結果: 寫入成功! #文件內容: Daniel-Mac:blog daniel$ cat test.txt &&echo 測試文件,用於測試異常捕獲
修改文件的權限沒有寫,而後在打開文件,往文件中寫入內容,查看異常:
chmod -w test.txt try: f = open('test.txt','w') f.write('測試文件,用於測試異常捕獲') except IOError: print('Error: 寫入失敗, 沒有找到文件或者權限不足!') else: print('寫入成功!') f.close() #再次執行代碼: Error: 寫入失敗, 沒有找到文件或者權限不足!
常用異常:
AttributeError 試圖訪問一個對象沒有的樹形,比如foo.x,但是foo沒有屬性x IOError 輸入/輸出異常;基本上是無法打開文件 ImportError 無法引入模塊或包;基本上是路徑問題或名稱錯誤 IndentationError 語法錯誤(的子類) ;代碼沒有正確對齊 IndexError 下標索引超出序列邊界,比如當x只有三個元素,卻試圖訪問x[5] KeyError 試圖訪問字典裡不存在的鍵 KeyboardInterrupt Ctrl+C被按下 NameError 使用一個還未被賦予對象的變量 SyntaxError Python代碼非法,代碼不能編譯(個人認為這是語法錯誤,寫錯了) TypeError 傳入對象類型與要求的不符合 UnboundLocalError 試圖訪問一個還未被設置的局部變量,基本上是由於另有一個同名的全局變量, 導致你以為正在訪問它 ValueError 傳入一個調用者不期望的值,即使值的類型是正確的
實例:
下標異常
dic = ["tom", 'jerry'] try: dic[10] except IndexError, e: print e
key異常
dic = {'k1':'v1'} try: dic['k20'] except KeyError, e: print e
元素異常
s1 = 'hello' try: int(s1) except ValueError, e: print e
對於上述實例,異常類只能用來處理指定的異常情況,如果沒有指定異常則無法處理。
# 未捕獲到異常,程序直接報錯 tt = 234 try: str(tt) except IndexError,e: print e
如果想通吃各種異常,python中也提供了一個萬能異常Exception,就能捕獲任意異常,目的是保證程序能正常運行
tt = 234 try: str(tt) except Exception,e: print e
如果你還想要知道異常是什麼,在那一塊報錯了,還有一個更周全的方案,如下:
s1 = 'hello world' try: int(s1) except KeyError: print('Error: Key錯誤!') except IndexError: print('Error: 索引錯誤!') except ValueError: print('Error: 值錯誤!') except Exception: print('Error: 出錯了!') else: print('你的值是: %s'%s1)
六、設計模式,單實例
例模式,顧名思義,也就是單個實例的意思。
模式特點:保證類僅有一個實例,並提供一個訪問它的全局訪問點。
class Singleton: __instance = None #定義一個私有靜態字段為初始值 def __init__(self,name): self.name = name def show(self): print(self.name) return 'test_instance' @classmethod def get_instance(cls): if cls.__instance: #如果字段內有值,直接返回字段值 return cls.__instance else: obj = cls('DBQ') #實例化 cls.__instance = obj #將對象賦值給字段 return cls.__instance #返回對象 a = Singleton.get_instance() b = Singleton.get_instance() print(a) print(id(a)) #內存地址和b相同 print() print(b) print(id(b)) #內存地址和a相同 # 後面再來幾個對象,也是一樣的! #代碼執行結果: <__main__.Singleton object at 0x101b769b0> <__main__.Singleton object at 0x101b769b0>單實例
單例模式的存在主要是保證當前內存中存在單個實例,避免內存資源浪費。