Python_基礎_(面向物件進階)
一,isinstance(obj,cls) issubclass(sub,super)
isinstance(obj,cls) # 判斷物件obj是否是cls的一個例項
class Test: pass t = Test() print(isinstance(t,Test)) # True
issubclass(sub,super) # 判斷類sub是否為類super的子類
class Test: pass class Sub(Test): pass print(issubclass(Sub,Test)) #True s = Sub() print(isinstance(s,Sub)) # True print(isinstance(s,Test)) # True
type(f) # 可以用來看物件f使用哪個類產生的
class Foo: pass f = Foo() print(type(f)) # <class '__main__.Foo'>
....
二,__getattribute__
## 當 __getattr__和 __getattribute__同時出現時
class Test:def __init__(self,name): self.name = name def __getattr__(self,item): print("執行的是__getattr__") def __getattribute__(self,item): print("執行的是__getattribute__") t = Test("henry") t.name # 當呼叫存在的屬性:觸發 __getattribute__ 不觸發 __getattr t.xxxxxx# 當呼叫不存在的屬性:觸發 __getattribute__
## __getattribute__ 丟擲異常時
class Test: def __init__(self,name): self.name = name def __getattr(self,item): print("執行的是__getattr__") def __getattribute__(self,item): print("執行的是__getattribute__") raise AttributeError("丟擲一個屬性異常") t = Test("henry") t.xxxxxx # 執行 __getattribute__時會丟擲一個屬性異常,不會直接報錯,而後會去執行 __getattr__ ## 輸出 執行的是__getattribute__ 執行的是__getattr__ # 注:當丟擲的異常不是AttributeError,不會去執行 __getattr__ ,而是直接終止程式
...
三,__setitem__ __getitem__ __delitem__
# 字典形式的操作屬性就是和item相關
# 點的形式操作屬性,就是和內建方法相關
class Test: def __setitem__(self,key,value): parint("__sttitem__") self.__dic__[key] = value def __getitem__(self,item): parint("__getitem__") def __delitem__(self,key): print("__delitem__") self.__dic__.pop(key) ## 新增 t = Test() t["name"] = "henry" # 新增一個屬性和值,會觸發 __setitem__ t["user"] = "alex" print(t.__dic__) # {"name":"henry"} ## 刪除 del t.name # 不會觸發item操作 只是普通的操作 print(t.__dic__) # {} del t["name"] # 會觸發__delitem__ print(t.__dic__) # {} ## 獲取 t.user # 不會觸發getitem操作 t["user"] # 會觸發__getitem__
...
四,__str__ __repr__
# 當列印一個例項化的物件時
class Test: pass t = Test() print(t) # <__main__.Test object at 0x00000299CE10EBA8>
# __str__ 可以控制列印例項化的物件時的資訊
class Test: def __str__(self): return "我是例項化的物件列印的資訊" t = Test() print(t) # 我是例項化的物件列印的資訊
# __repr__ 也是用於控制輸出,應用於直譯器中(cmd)
class Test: def __repr__(self): return "我是例項化的物件列印的資訊" t = Test() print(t) # 我是例項化的物件列印的資訊
# 注意
## 兩者共存時,優先選擇str
## 如果__str__沒有被定義,那麼就會用__repr__來替代輸出
## str 和 repr 輸出的結果返回的值都為字串型別
class Test: def __str__(self): return "自定義的物件顯示方式str" def __repr__(self): return "自定義的物件顯示方式repr" t = Test() print(t) # 輸出:自定義的物件顯示方式str
...
五,自定義格式化 __format__
class Test: def __init__(self,year,mon,day): self.year = year self.mon = mon self.day = day t = Test(2018,12,22) x = "{0.year},{0.mon},{0.day}".format(t) y = "{0.year}:{0.mon}:{0.day}".format(t) z = "{0.year}-{0.mon}-{0.day}".format(t) print(x) # 2018,12,22 print(y) # 2018:12:22 print(z) # 2018-12-22
# 當上方的程式要換一種日期輸出格式時,必須得使用者來定義,這必然是不行的
format_dic = { "ymd":"{0.year},{0.mon},{0.day}", "y:m:d":"{0.year}:{0.mon}:{0.day}", "y-m-d":"{0.year}-{0.mon}-{0.day}" } class Test: def __init__(self, year, mon, day): self.year = year self.mon = mon self.day = day def __format__(self, format_spec): if not format_spec or format_spec not in format_dic: # 當用戶沒有輸入格式時,程式自己選擇為ymd # 當用戶輸入的格式不存在時 format_spec = "ymd" fm = format_dic[format_spec] return fm.format(self) # 相當於"{0,year}-{0,mon}-{0,day}",format(t) t = Test(2018, 12, 22) format(t) # 相當於執行了 t.__format__() print(format(t, "ymd")) # 2018,12,22 print(format(t, "y:m:d")) # 2018:12:22 print(format(t, "y-m-d")) # 2018-12-22
...
六,__slots__
1:__slots__:是一個變數,變數的值可以是列表,元組,字串,可迭代的物件
2:當用點來訪問一個屬性時,實際上是在訪問類或者物件的__dict__屬性字典(類的屬性字典是共享的,可被每個例項物件訪問;二例項的字典是獨立的)
3:為何使用了__slots__:因為字典會佔用大量的記憶體,使用了__slots__不能再給其新增屬性,只能使用之前在__slots__中定義的
4:定義了__slots__後不在支援一些普通類的特性,比如繼承
# 基本形式
class Test: __slots__ = "name" # __slots__ = ['name','age'] t = Test() t.name = "henry" t.age = 18 # 報錯,當前 __slots__替代了 __dic__,不能新增該屬性 # 限制建立屬性 print(t.__slots__) # name print(Test.__slots__) # name
...
七,__doc__ __module__ __class__
# __doc__
# 注:該屬性不能被繼承 class Test: '我是文件註釋' pass pring(Test.__doc__) # 不能被繼承 class Test: '我是文件註釋' pass class Foo(Test): pass print(Foo.__doc__) # 無法繼承給子類
# __module__ __class__
# test1.py位於檔案 lib下 class A: def __init__(self): self.name = name # 在test2.py檔案中匯入 test1.py模組 from lib.test1 import A obj = A() print(obj.__module__) # 輸出lib.test1 # 輸出模組 print(obj.__class__) # 輸出 lib.test1.A # 輸出類
...
八,__del__析構方法
1:當物件在記憶體中被釋放時,自動觸發執行
2:在Python中程式設計師無需關心記憶體的分配和釋放,Python直譯器會自動來執行,解構函式的呼叫是由直譯器在進行垃圾回收時自動觸發執行的,該方法一般無需定義
# 當刪除一個物件時,觸發__del__
class Test: def __del__(self): print("執行了del方法") t = Test() del t # 刪除物件 print("程式執行完畢") # 輸出 執行了del方法 程式執行完畢
# 當程式執行完畢後,觸發__del__
class Test: def __del__(self): print("執行了del方法") t = Test() print("程式執行完畢") # 輸出 程式執行完畢 執行了del方法 # 在程式執行完成後,會執行垃圾回收機制
# 當刪除一個屬性時,不會觸發__del__
# 當刪除一個屬性時,不會執行__del__方法 class Test: def __init__(self,name): self.name = name def __del__(self): print("執行了del方法") t = Test("henry") del t.name print("程式執行完畢") # 輸出 程式執行完畢 執行了del方法
...
九,__call__
class Test: def __call__(self, *args, **kwargs): print("執行了__call__") t = Test() t() # 一個物件後加括號,觸發執行__call__
# 一切皆物件,假如Test類由類Foo產生,則執行Test(),相當於執行了類Foo下的__call__方法
十,__next__ __iter__
..待完成
十一,描述符的基本概況
# 描述符:本質就是在一個新式類中,這個新式類中至少實現 __get__(),__set__(),__delete__()中的一個,這三個也被稱為描述符協議
__get__() # 呼叫一個屬性時觸發
__set__() # 給一個屬性賦值時觸發
__delete__() # 利用del刪除屬性時觸發
# 什麼情況下會觸發著三個方法(以下為錯誤示範)
class Test: def __init__(self,name,age): self.name = name self.age = age def __set__(self, instance, value): print("__set__") def __get__(self, instance, owner): print("__get__") def __delete__(self, instance): print("__delete__") t = Test("henry",18) t.name # 沒呼叫 t.name = "alex" # 沒呼叫 del t.name # 沒呼叫
# 什麼情況下會觸發這三個方法(以下為正確示範)
class Test: def __get__(self, instance, owner): print("觸發__get__") def __set__(self, instance, value): print("觸發__set__") def __delete__(self, instance): print("觸發__delete__") class Bar: name = Test() # 資料描述符 # 表示一個描述符 所有與name有關的操作都去找類Test # 表示在類Bar中利用類Test來描述name屬性 def __init__(self,name): # name被Test()代理 self.name = name b = Bar("henry") # 觸發__set__,instance為b,value為henry b.name # 觸發__get__ b.name = "heihei" # 觸發__set__ del b.name # 觸發__delete__ print(b.__dict__) # {} print(Bar.__dict__) # 'name': <__main__.Test object at 0x0000017E78719470>
class Test_1: def __get__(self, instance, owner): print("Test_1觸發__get__") def __set__(self, instance, value): print("Test_1觸發__set__") def __delete__(self, instance): print("Test_1觸發__delete__") class Test_2: def __get__(self, instance, owner): print("Test_2觸發__get__") def __set__(self, instance, value): print("Test_2觸發__set__") def __delete__(self, instance): print("Test_2觸發__delete__") class Bar: name = Test_1() age = Test_2() def __init__(self,name,age): # name被Test_1()代理,age被Test_2()代理 self.name = name self.age = age b = Bar("henry",18) # Test_1觸發__set__ # Test_2觸發__set__ # Test_1 b.name b.name = "heihei" del b.name # Test_1觸發__get__ # Test_1觸發__set__ # Test_1觸發__delete__ # Test_2 b.age b.age = 19 del b.age # Test_2觸發__get__ # Test_2觸發__set__ # Test_2觸發__delete__ print(b.__dict__) # {} print(Bar.__dict__) # 'name': <__main__.Test_1 object at 0x0000021EF9F7F780>, # 'age': <__main__.Test_2 object at 0x0000021EF9F7FCC0>,相同的一種示範
# 資料描述符和非資料描述符
# 資料描述符 class Foo: def __set__(self, instance, value): print('set') def __get__(self, instance, owner): print('get') # 二 非資料描述符:沒有實現__set__() class Foo: def __get__(self, instance, owner): print('get')示例程式碼
# 描述符的注意事項
一:描述符不本身應該定義成新式類,被代理的類也應該時形式類
二:必須把描述符定義成則個類的類屬性,不能為定義到建構函式中
三:要嚴格遵循優先順序
1,類屬性
2,資料描述符 # 具有 __get__()和__set__()
3,例項屬性
4,非資料描述符 # 沒有__set__()
5,找不到屬性觸發 __getattr__()
# 描述符優先順序問題
## 類屬性>資料描述符
class Test: def __get__(self, instance, owner): print("呼叫get") def __set__(self, instance, value): print("呼叫set") def __delete__(self, instance): print("呼叫delete") class Foo: name = Test() def __init__(self,name): self.name = name Foo.name # # 輸出:呼叫get # 在Foo的屬性字典中,'name': <__main__.Test object at 0x00000262B7BF97B8>, # 當呼叫屬性name時,屬性name為描述符偽裝成類屬性name,當在類中找不到屬性對飲的值,就到類Test中,觸發了__get__() Foo.name = "henry" # 直接賦值類的一個屬性,因為類屬性擁有更高的優先順序,相當於覆蓋了描述符,不會觸發描述符中的__set__() print(Foo.__dict__) # "name":"henry" del Foo.name # 刪除的為類中的類屬性,沒有觸發__delete__()示例程式碼
## 資料描述符>例項屬性
class Test: def __get__(self, instance, owner): print("呼叫get") def __set__(self, instance, value): print("呼叫set") def __delete__(self, instance): print("呼叫delete") class Foo: name = Test() def __init__(self,name): self.name = name f = Foo("henry") print(f.__dict__) # {} # 例項的屬性字典中沒有name,因為name是一個數據描述符,優先順序高於例項屬性,檢視/賦值/刪除/都和例項無關 f.name = "heihei" # 輸出:呼叫set # 先在自己的例項屬性中找,找不到則到類中找 # f.name的呼叫與賦值都是觸發描述符的操作,與f本身無法無關,相當於覆蓋了例項屬性 del f.name # 呼叫delete示例程式碼
## 例項屬性>非資料描述符
class Test: def __get__(self, instance, owner): print("呼叫get") # def __delete__(self, instance): # print("呼叫delete") class Foo: name = Test() # 變成了非資料描述符 # name是一個非資料描述符,因為name=Test() 而Test()沒有實現set方法,因而比例項屬性由更低的優先順序 f = Foo() f.name # 呼叫get f.name = "henry" # 相當於在類Foo中進行設定屬性的值 print(f.__dict__) # {'name': 'henry'} # 例項屬性的優先順序高於非資料描述符,當描述符中沒有set時,就在自己的例項上新增屬性示例程式碼
...
十二,描述符的應用
# 描述符中的引數
class Test: def __get__(self, instance, owner): print("執行get") print("instance引數為:%s" %instance) print("owner引數為:%s" % owner) def __set__(self, instance, value): print("執行set") print("instance引數為:%s" % instance) print("value引數為:%s" % value) def __delete__(self, instance): print("執行delete") print("instance引數為:%s" % instance) class Foo: name = Test() def __init__(self,name,age,salary): self.name = name self.age = age self.salary = salary f = Foo("henry",18,10.1) # 執行set # instance引數為:<__main__.Foo object at 0x000001F1E436E748> # value引數為:henry f.name # 執行get # instance引數為:<__main__.Foo object at 0x000001F1E436E748> # owner引數為:<class '__main__.Foo'> print(f.__dict__) # name不存在物件f的字典中 # {'age': 18, 'salary': 10.1}示例程式碼
# 簡單操作
class Test: def __init__(self,key): self.key = key def __get__(self, instance, owner): print("執行get") return instance.__dict__[self.key] # instance為物件f,owner為類Foo def __set__(self, instance, value): print("執行set") instance.__dict__[self.key] = value # 相當於給物件f新增鍵值對 def __delete__(self, instance): print("執行delete") instance.__dict__.pop(self.key) class Foo: name = Test("name") def __init__(self,name,age,salary): self.name = name self.age = age self.salary = salary f = Foo("henry",18,10.1) print(f.name) # henry print(f.__dict__) # {'key': 'henry', 'age': 18, 'salary': 10.1} f.name = "heihei" print(f.__dict__) # {'key': 'heihei', 'age': 18, 'salary': 10.1} del f.name print(f.__dict__) # {'age': 18, 'salary': 10.1}示例程式碼
# 針對name賦值時的型別檢查
class Test: def __init__(self,key): self.key = key def __get__(self, instance, owner): print("執行get") return instance.__dict__[self.key] # instance為物件f,owner為類Foo def __set__(self, instance, value): print("執行set") if not type(value) == str: # 判斷型別 raise TypeError("你傳入的不是字串型別") instance.__dict__[self.key] = value def __delete__(self, instance): print("執行delete") instance.__dict__.pop(self.key) class Foo: name = Test("name") def __init__(self,name,age,salary): self.name = name self.age = age self.salary = salary f = Foo(123,18,10.1) 執行set Traceback (most recent call last): File "F:/Python_Project/Test/描述符的應用.py", line 26, in <module> f = Foo(123,18,10.1) File "F:/Python_Project/Test/描述符的應用.py", line 22, in __init__ self.name = name File "F:/Python_Project/Test/描述符的應用.py", line 12, in __set__ raise TypeError("你傳入的不是字串型別") TypeError: 你傳入的不是字串型別示例程式碼
# 針對name和age賦值時的型別檢查
class Test: def __init__(self,key,expected_type): # expected_type想要檢測的型別 self.key = key self.expected_type = expected_type def __get__(self, instance, owner): print("執行get") return instance.__dict__[self.key] # instance為物件f,owner為類Foo def __set__(self, instance, value): print("執行set") if not type(value) == self.expected_type: # 判斷型別 raise TypeError("你傳入的不是%s型別" %self.expected_type) instance.__dict__[self.key] = value def __delete__(self, instance): print("執行delete") instance.__dict__.pop(self.key) class Foo: name = Test("name",str) age = Test("age",int) def __init__(self,name,age,salary): self.name = name self.age = age self.salary = salary # f = Foo(123,18,10.1) # TypeError: 你傳入的不是<class 'str'>型別 f = Foo("henry","18",10.1) # TypeError: 你傳入的不是<class 'int'>型別示例程式碼
...
十三,__enter__ __exit__ 上下文管理協議
## 第一種檔案操作(使用完得關閉檔案) f = open("xxx","r") 檔案操作 f.close ## 另一種檔案操作(使用完不必關閉檔案) with open("xxxx","r") as file "檔案操作程式碼塊"
# 上述稱為上下文管理協議,即with語句,為了讓一個物件相容with語句,必須在這個物件的類中宣告__enter__和__exit__方法
## 上下文管理協議
# 一 資料描述符:至少實現了__get__()和__set__() class Open: def __init__(self,name): self.name = name def __enter__(self): # 在with Open("a.txt") as f:執行時觸發 print("執行了enter") return self def __exit__(self, exc_type, exc_val, exc_tb): # 等到程式碼塊執行完成後執行 print("執行了exit") return self with Open("a.txt") as f: print("程式碼塊執行完成") # 輸出 執行了enter 程式碼塊執行完成 執行了exit # Open("a.txt")的過程建立了一個物件,當執行with時執行了__enter__(),__enter__()返回一個值(self)賦值給f,f就是類Open產生的一個物件
## 當with中的程式碼塊出現異常時
class Open: def __init__(self,name): self.name = name def __enter__(self): # 在with Open("a.txt") as f:執行時觸發 print("執行了enter") def __exit__(self, exc_type, exc_val, exc_tb): # 等到程式碼塊執行完成後執行 print("執行了exit") print(exc_type) print(exc_val) print(exc_tb) # return True 當在exit中返回一個True時,代表不丟擲程式出現異常 with Open("a.txt") as f: print("程式碼塊開始執行執行") print(abc) # 當在with中出現一個錯誤時 print("程式碼塊執行完成") print("11111111111")
# 當不return True時的 輸出的結果 執行了enter 程式碼塊開始執行執行 執行了exit Traceback (most recent call last): <class 'NameError'> File "F:/Python_Project/Test/利用描述符定製property.py", line 17, in <module> name 'abc' is not defined print(abc) # 當在with中出現一個錯誤時 <traceback object at 0x000001E2A2D59FC8> NameError: name 'abc' is not defined
# 當return True時的結果 執行了enter 程式碼塊開始執行執行 執行了exit <class 'NameError'> name 'abc' is not defined <traceback object at 0x000001E205DB9F88> 11111111111
## exit中的三個引數
exc_type:異常類
exc_val:異常值
exc_tb:異常追蹤資訊
<class 'NameError'>
+
name 'abc' is not defined
+
<traceback object at 0x0000016F7FAE9FC8>
=
NameError: name 'abc' is not defined
## 總結
with obj as f:
"程式碼塊"
1:with obj # 觸發obj.__enter__(),獲得返回值self
2:as f # 將__enter__返回值賦值給f,f = 返回值
3:with obj as f # 等同於 f = obj.__enter__()
4:執行程式碼塊
1:在沒有發生異常的情況下,整個程式碼塊執行完成後觸發__exit__,exit中的三個引數都為None
2:在有異常的情況下,從異常出現的位置直接觸發__exit__
a:如果__exit__的返回值為True,代表吞掉異常,不丟擲異常,程式不報錯
b:如果__exit__的返回值不為True,表示丟擲異常,程式報錯
c:__exit__的執行完畢,表示整個with語句的執行完畢
...