PythonI/O進階學習筆記_3.2面向物件程式設計_python的封裝
前言:
本篇相關內容分為3篇多型、繼承、封裝,這篇為第三篇 封裝。
本篇內容圍繞 python基礎教程這段: 在面向物件程式設計中,術語物件大致意味著一系列資料(屬性)以及一套訪問和操作這些資料的方法。使用物件而非全域性變數和函式的原因有多個,下面列出了使用物件的最重要的好處。 多型:可對不同型別的物件執行相同的操作,而這些操作就像“被施了魔法”一樣能夠正常執行。 封裝:對外部隱藏有關物件工作原理的細節。 繼承:可基於通用類創建出專用類。 內容較多,這篇為下篇。Content:
- 封裝
1.資料封裝和私有屬性
2. 類變數和例項變數(物件變數)
3. 類屬性和例項屬性得查詢順序(MRO)
4. 靜態方法 類方法和物件方法使用以及引數
5. python的介面和自省機制
6. 上下文管理器
====================================
1.python的資料封裝和私有屬性
a.python的__私有屬性
python用__開頭完成私有屬性的封裝。用__開頭的屬性名或者方法就沒法直接外部獲取,只有類中的公共方法才可以訪問。
python的資料封裝和java c++這種靜態語言不同的是,靜態語言其實本身有private型別的。而python是用小技巧實現了這種私有屬性。
b.如何實現的? 以__開頭的變數,python會對它進行變形,變形的模式是加上當前的class和變形的名稱。 例: 如下的User中的__birthday變數,最後呼叫的時候變為_User__birthday- 隱藏起一個屬性,不想讓外部呼叫
- 保護這個屬性,不想讓這個屬性隨意改變
- 保護這個屬性不被子類繼承
2.類變數和例項變數
a.什麼是python的類變數?
類下的變數。
例:class A(): aa=1其中aa就是類變數。 b.什麼是例項變數?
class A: aa=1 def __init__(self,x,y): self.x=x self.y=y
class Init(object): def __init__(self, v): #print("init") self.val = v #print("init:",self.val) class Add2(Init): def __init__(self, val): #print("Add2") super(Add2, self).__init__(val) #print("add2:",self.val) self.val += 2 class Mult(Init): def __init__(self, val): #print("Mult") super(Mult, self).__init__(val) #print("Mult:",self.val) self.val *= 5 class HaHa(Init): def __init__(self, val): #print("HAHA") super(HaHa, self).__init__(val) #print("Haha:",self.val) self.val /= 5 class Pro(Add2,Mult,HaHa): # pass class Incr(Pro): def __init__(self, val): super(Incr, self).__init__(val) self.val+= 1 # Incr Pro Add2 Mult HaHa Init p = Incr(5) print(p.val) c = Add2(2) print(c.val)
把程式碼中的print註釋都拿掉,可以發現整個流程為:
4. 靜態方法 類方法和物件方法使用以及引數
a.靜態方法 staticmethod
- 靜態方法定義:
使用裝飾器@staticmethod。引數隨意,沒有“self”和“cls”引數,但是方法體中不能使用類或例項的任何屬性和方法
- 靜態方法的一些特點:
靜態方法是類中的函式,不需要例項。
靜態方法主要是用來存放邏輯性的程式碼,主要是一些邏輯屬於類,但是和類本身沒有互動,即在靜態方法中,不會涉及到類中的方法和屬性的操作。
可以理解為將靜態方法存在此類的名稱空間中。事實上,在python引入靜態方法之前,通常是在全域性名稱空間中建立函式。-
- 靜態方法呼叫:
例:我要在類中實現一個得到現在時間的方法。與傳進去的任何引數都無關那種。
import time class TimeTest(object): def __init__(self, hour, minute, second): self.hour = hour self.minute = minute self.second = second @staticmethod def showTime(): return time.strftime("%H:%M:%S", time.localtime()) print(TimeTest.showTime()) t = TimeTest(2, 10, 10) nowTime = t.showTime() print(nowTime)
b.類方法
- 類方法定義:
使用裝飾器@classmethod。第一個引數必須是當前類物件,該引數名一般約定為“cls”,通過它來傳遞類的屬性和方法(不能傳例項的屬性和方法)
- 類方法的一些特點:
將類本身作為物件進行操作。
不管這個方式是從例項呼叫還是從類呼叫,它都用第一個引數把類傳遞過來
- 類方法使用:
例:實現一個基類做一個有顏色屬性的抽象共性,對於實際的顏色的值需要結合實際子類傳遞的值進行匹配
class ColorTest(object): color = "color" @classmethod def value(self): return self.color class Red(ColorTest): color = "red" class Green(ColorTest): color = "green" g = Green() print(g.value()) print(Green.value())
這時候可能有人會覺得,這個跟例項方法(普通方法)不是一樣的嘛,繼承父類,並且用繼承中的知識重寫父類中的屬性或者方法?
重點就在於,如果我們把@classmethod這個方法去掉,Green.value()這樣去呼叫是會報錯的。
因為需要傳遞例項引數進去,而不能直接用類呼叫。
c.物件方法(例項方法)
- 例項方法定義:
第一個引數必須是例項物件,該引數名一般約定為“self”,通過它來傳遞例項的屬性和方法(也可以傳類的屬性和方法)
- 例項方法的一些特點:
只能由類的例項來呼叫,就是我們平時最常用的。
比較簡單,沒有特殊裝飾器,暫不舉例。
5.python的介面和自省機制
a.什麼是python的自省機制
當我們需要實現一個通用的DBM框架時,可能需要對資料物件的欄位賦值,但我們無法預知用到這個框架的資料物件都有些什麼欄位,換言之,我們在寫框架的時候需要通過某種機制訪問未知的屬性。
也就是說,我們需要在很多時候去訪問python自己為我們做了哪些隱藏的事情,或者某個框架裡具體實現了哪些方法等。
通過python的自省機制去讓python告訴我們,我們查詢的物件是什麼,有哪些功能等。
b.python自省之訪問物件的屬性
例有下面這個類,並且例項化了一個a物件。
class Company(object): def __init__(self,company_name,staffs=[]): self.company_name=company_name self.staffs=staffs def add(self,staff): self.staffs.append(staff) def remove(self,staff): self.staffs.remove(staff) user_list=['tangrong1','tangrong2','tangrong3'] a=Company("aaa",user_list)
我需要去得到類裡的一些方法和屬性:
####訪問物件的屬性 #dir() 呼叫這個方法將返回包含obj大多數屬性名的列表(會有一些特殊的屬性不包含在內)。obj的預設值是當前的模組物件。 print("dir()") print(dir(Company)) print(dir(a)) #hasattr(obj,attr) 這個方法用於檢查obj是否有一個名為attr的值的屬性,返回一個布林值 print("hasattr()") print(hasattr(a,"add")) print(hasattr(a,"staffs")) print(hasattr(a,"a")) #getattr(obj, attr) 呼叫這個方法將返回obj中名為attr值的屬性的值,例如如果attr為'staffs',則返回obj.staffs。 print("getattr()") print(getattr(a,"add")) print(getattr(a,"staffs")) #setattr(obj, attr, val) 呼叫這個方法將給obj的名為attr的值的屬性賦值為val。例如如果attr為'bar',則相當於obj.bar = val。 print("setattr()") print(setattr(a,"staffs",["tangrong4","tangrong5"])) print(a.staffs)
輸出為:
dir() ##ps:可以發現,例項和類的dir()列出來的有些不一樣 ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__',
'__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__',
'add', 'remove'] ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__',
'__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__',
'add', 'company_name', 'remove', 'staffs']
hasattr() True True False
getattr() <bound method Company.add of <__main__.Company object at 0x000002D3172F2518>> ['tangrong1', 'tangrong2', 'tangrong3']
setattr() None ['tangrong4', 'tangrong5']
c.python自省之訪問物件的元資料
包括各種__dict__()\__doc__\__bases__等
內容比較多,可看這篇:https://www.cnblogs.com/huxi/archive/2011/01/02/1924317.html
6.上下文管理器
a.什麼是上下文管理器?
上下文管理器就是實現了上下文管理協議的物件。主要用於儲存和恢復各種全域性狀態,關閉檔案等,上下文管理器本身就是一種裝飾器。
感覺上句很像白說是不是- -。實際例就是,類似於with語句,就是遵循了上下文管理協議,才能在內部我們看不到的地方,幫我們完成了退出時做出關閉檔案、執行自定義程式碼塊的操作的。就不用我們顯示判斷呼叫讀取完了就關閉檔案這種操作。
with open("test/test.txt","w") as f_obj: f_obj.write("hello")
b.用with看他遵循的上下文管理協議
上下文管理協議包括兩個方法:
-
contextmanager.__enter__()
從該方法進入執行時上下文,並返回當前物件或者與執行時上下文相關的其他物件。如果with語句有as關鍵詞存在,返回值會繫結在as後的變數上。 -
contextmanager.__exit__(exc_type, exc_val, exc_tb)
退出執行時上下文,並返回一個布林值標示是否有需要處理的異常。如果在執行with語句體時發生異常,那退出時引數會包括異常型別、異常值、異常追蹤資訊,否則,3個引數都是None。
能用with語句的物件,也是因為這個物件裡,遵循了這個協議。
with語句就是為支援上下文管理器而存在的,使用上下文管理協議的方法包裹一個程式碼塊(with語句體)的執行,併為try...except...finally提供了一個方便使用的封裝。
我們建立一個能支援with(上下文管理協議)的類,這個類實現了db最開始建立連線,退出時關閉連線的操作。
import sqlite3 class DataConn: def __init__(self,db_name): self.db_name = db_name def __enter__(self): self.conn = sqlite3.connect(self.db_name) return self.conn def __exit__(self,exc_type,exc_val,exc_tb): self.conn.close() if exc_val: raise if __name__ == "__main__": db = "test/test.db" with DataConn(db) as conn: cursor = conn.cursor()
c.用contextlib自定義上下文管理器
from contextlib import contextmanager @contextmanager def file_open(path): try: f_obj = open(path,"w") yield f_obj except OSError: print("We had an error!") finally: print("Closing file") f_obj.close() if __name__ == "__main__": with file_open("test/test.txt") as fobj: fobj.write("Testing context managers")
或者簡單版:
這個裝飾器裝飾的一定要是生成器。 __enter__中的程式碼都是yield之前的邏輯程式碼。 __exit__程式碼實在yield之後實現的程式碼邏輯。