1. 程式人生 > >PythonI/O進階學習筆記_3.2面向物件程式設計_python的封裝

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
實際上在python中這並不是真正的安全。其實即使在java裡的private關鍵詞對反射機制比較瞭解其實也不是絕對安全性。 python比java突破這個安全性更加簡單。   c.一般什麼情況用這種私有屬性?
  • 隱藏起一個屬性,不想讓外部呼叫
  • 保護這個屬性,不想讓這個屬性隨意改變
  • 保護這個屬性不被子類繼承

 

2.類變數和例項變數

a.什麼是python的類變數?

類下的變數。

例:
class A():
    aa=1
其中aa就是類變數。   b.什麼是例項變數?
class A:
    aa=1
    def __init__(self,x,y):
        self.x=x
        self.y=y
其中,self是傳遞的物件(例項本身),x,y是物件引數。 先查詢物件變數,再查詢類變數。   c.類變數和例項變數的區別 類A是沒有物件x和y的。 但是例項化的a有。   d.類變數被修改的影響 和例項變數被修改的影響   如果修改例項物件中aa的值,修改結果如何改變呢? 修改物件變數的類變數的值的時候,相當於多新建了一個物件a中的aa的值,並且例項a的物件值得查詢順序是: 先查詢自己例項中,是否有這個變數,再往上查詢。   3. 類屬性和例項屬性得查詢順序(MRO) 在繼承那篇中,就有講到MRO查詢順序,但是重點是在於查詢子類的父類繼承順序,那麼同理得到的屬性查詢順序是什麼樣的呢? 回顧一下C3演算法,這篇講的比較明白:https://blog.csdn.net/u011467553/article/details/81437780 咱們再來看下面的輸入,是否能看出輸出是什麼呢?
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之後實現的程式碼邏輯。