1. 程式人生 > >Python學習筆記13:Python面向物件程式設計

Python學習筆記13:Python面向物件程式設計

1、引言
(1)類和例項:類是物件的定義,例項是“真正的實物”。
定義類:類名通常大寫字母打頭。

class MyNewObjectType(bases):
    'define MyNewObjectType class'
    class_suite

bases可以是一個(單繼承)或多個(多重繼承)用於繼承的父類。
object是“所有類之母”。
Python呼叫類進行例項化,例項化類不使用new關鍵字。

>>>c=MyNewObjectType()

類有時可以僅作為名稱空間。

class MyData(object):    
    pass
mathobj=MyData() mathobj.x=4 mathobj.y=5

這些屬性是動態的,不需要在構造器中或其他任何地方為它們預先宣告或賦值。
(2)方法
定義方法:屬性和方法使用駝峰記法。

class MyDataWithMethod(object):
    def printFoo(self):
        print ....

所有方法都存在self,表示例項物件本身。
靜態方法或類方法不需要self。
__init__()方法類似於建構函式,但不同於建構函式,因Python不new。該方法在例項被建立後,例項化呼叫返回這個例項之前被呼叫。

2、面向物件程式設計常用術語

  • 抽象/實現:建模 現實化
  • 封裝/介面
  • 合成:聯合、聚合
  • 繼承/派生
  • 泛化/特化
  • 多型
  • 自省/反射

3、類
Python中,一切皆為物件。
下面是類的定義語法。

class ClassName(object):
    'class documentation string'
    class_suite

Python不支援純虛擬函式(像C++)或者抽象方法等。
替代方案:在基類方法中引發NotImplementedError異常。

4、類屬性
(1)屬性
屬性(資料或函式),使用句點屬性識別符號來訪問。
屬性本身也是一個物件,也有自己的屬性,所以訪問屬性時會形成一個屬性鏈。
(2)類屬性/例項資料屬性


例項屬性在OOP中用得最多,類屬性僅當需要有更加“靜態”資料型別時才變得有用,它和任何例項無關,方法是類屬性。
Python要求,沒有例項,方法不能被呼叫。方法必須“繫結”到一個例項才能直接被呼叫。非繫結的方法可能可以被呼叫,但例項物件一定要明確給出,才能保證呼叫成功。然而,不管是否繫結,方法都是它所在的類的固有屬性,即使它們幾乎總是通過例項來呼叫的。
(3)確定一個類有哪些屬性的方法

  • 使用內建函式dir()
  • 訪問類的字典屬性__dict__
  • 內建函式vars()接受類物件作為引數,返回類的__dict__屬性的內容。

(4)特殊的類屬性

  • C.__name__ 類C的名字(字串)
  • C.__doc__ 類C的文件字串
  • C.__bases__ 類C的所有父類構成的元組
  • C.__dict__ 類C的屬性
  • C.__module__ 類C所在的模組(1.5)
  • C.__class__ 例項C對應的類(新式類)

5、例項
Python 2.2中統一了類和型別。
Python通過呼叫類物件來建立例項。
①__init__()方法
當類被呼叫,建立例項物件,物件建立後,呼叫__init__()方法完成特別的操作,執行完返回類物件,例項化結束。
Python沒有使用new建立例項,沒有定義構造器,由Python建立物件。
②__new__()方法
“構造器”方法,與__init__()方法相比,__new__()更像一個真正的構造器,因為__new__()必須返回一個合法的例項,該例項作為self傳給__init__()方法。
__new__()方法會呼叫父類的__new__()方法來建立物件。
在對內建型別進行派生時,__new__()方法可以例項化不可變物件。
③__del__()方法
“解構器”方法,當例項物件所有的引用都被清除掉後才執行該方法,用於例項釋放前進行特殊處理。

  • __del__()方法只能被呼叫一次。
  • 使用__del__()方法,不要忘記首先呼叫父類的__del__()方法。
  • del x不表示呼叫x.__del__()方法,僅引用計數減少。
  • 若存在迴圈引用,則物件的__del__()方法可能永遠不會被執行。
  • __del__()方法未捕獲的異常會被忽略掉,除非有必要,否則不去實現__del__()方法。
  • 如果定義了__del__()方法,且例項是某個迴圈的一部分,垃圾回收器將不會終止這個迴圈,你需要自己顯式呼叫del。

6、例項屬性
方法嚴格來說是類屬性。例項僅擁有資料屬性。
(1)”例項化”例項屬性
①在__init__()方法中設定例項屬性。
設定例項的屬性可以在例項建立後任意時間進行。__init__()方法是設定這些屬性的關鍵點之一。Python能夠在“執行時”建立例項屬性(Python優秀特性之一)
②預設引數提供預設的例項安裝。

class HotelRoomClac(object):
    def __init__(self,rt,sales=0.085,rm=0.1):
        self.salesTax=sales
        self.roomTax=rm
        self.roomRate=rt

③__init__()方法應該返回None
__init__()方法不應該返回任何物件,因為例項物件是自動在例項化呼叫後返回的。
(2)檢視例項屬性

  • 檢視例項屬性:dir()、__dict__屬性、vars()
  • 特殊的例項屬性:I.__class__、I.__dict__
  • 內建型別屬性:內建型別可以使用dir()方法,不可以訪問__dict__特殊屬性,因為在內建型別中,不存在這個屬性。

(3)類屬性和例項屬性(類似於自動變數和靜態變數)
可以採用類來訪問類屬性,若例項沒有同名的屬性的話,也可以用例項來訪問。
類屬性可以通過類或例項來訪問,不過只能使用類訪問類屬性時,才能更新類屬性的值。若在例項中更新類屬性,將會建立同名的例項屬性,“遮蔽”了類屬性。當刪除同名的例項屬性,類屬性才起作用。所以,從例項中訪問類屬性須謹慎。

class C(object): #定義類
    version = 1.2#靜態成員
>>>c=C()
>>>C.version #通過類來訪問
>>>c.version #通過例項來訪問
>>>C.version+=0.1 #通過類(只能這樣)來更新類屬性
>>>c.version =1.3 #任何對例項屬性的賦值都會建立一個例項屬性,而不是更新類屬性

當類屬性是可變型別時,並不會建立例項屬性,直接操作的是類屬性。

class Foo(object): 
    x={2003:'poe2'}
>>>foo=Foo()
>>>foo.x[2004]='valid path'
>>>foo.x
{2003:'poe2',2004:'valid path'}
>>>Foo.x
{2003:'poe2',2004:'valid path'} #生效了
>>>del foo.x #刪除會報錯,因為沒有遮蔽所以不能刪除掉

(4)類屬性永續性
類屬性,任憑整個例項(及其屬性)的如何進展,他都不理不睬(因此獨立於例項),類屬性的修改會影響到所有的例項。類屬性是靜態成員。

7、繫結和方法呼叫
(1)繫結
方法僅僅是類內部定義的函式,意味著方法是類屬性而不是例項屬性。
方法只有在類擁有例項時,才能被呼叫。方法被認為是繫結到例項。方法中的變數self表示呼叫此方法的例項物件。
(2)方法呼叫
①呼叫非繫結的方法(不常見):類還未例項化。

class EmplAddrBookEntry(AddrBookEntry):
    'Employee Address Book Entry class'
    def __init__(self,nm,ph,em):
        AddrBookEntry.__init__(self,nm,ph) #覆蓋父類方法
        self.empid=em

②呼叫繫結方法:類已經例項化。

>>>mc=MyClass()
>>>mc.foo()

總結:方法定義於類內部,是類方法;方法繫結到例項,由例項呼叫;未繫結,由類呼叫。

8、靜態方法和類方法(2.2)
(1)經典類中建立靜態方法和類方法的例子

class TestStaticMethod:
    def foo():
        print 'calling static method foo()'
        foo=staticmethod(foo)  #內建函式,將方法轉換成靜態方法
class TestClassMethod:
    def foo(cls):  #cls為類物件,類似於self
        print 'calling class method foo()'
        foo=classmethod(foo)   #內建函式,將方法轉換成類方法

可以通過類或者例項呼叫這些函式。

>>>tsm=TestStaticMethod()
>>>TestStaticMethod.foo()
>>>tsm.foo()
>>>tcm=TestClassMethod()
>>>TestClassMethod.foo()
>>>tcm.foo()

(2)使用函式修飾符建立靜態方法和類方法的例子(2.4)

class TestStaticMethod:
    @staticmethod
    def foo():
        print 'calling static method foo()'
class TestClassMethod:
    @classmethod
    def foo(cls):
        print 'calling class method foo()'

9、繼承
(1)通過繼承覆蓋方法
子類定義與基類相同的方法時,會覆蓋(override)基類方法。
子類可以使用呼叫非繫結的基類方法的方法呼叫基類方法。
也可以使用super()內建方法呼叫基類方法。
當從一個帶構造器__init__()的類派生,如果你不去覆蓋__init__(),它將會被繼承並自動呼叫,但如果你在子類中覆蓋了__init__(),子類被例項化時,基類的__init__()就不會被自動呼叫。若要呼叫父類的__init__()方法,需要使用super()。
(2)從標準型別派生
經典類中,不能對標準型別進行子類化。
2.2後,可以對標準型別進行子類化。
子類化Python型別:其中一個是可變型別;另一個是不可變型別。
①子類化不可變型別

class RoundFloat(float):
    def __new__(cls,val):
        return float.__new__(cls,round(val,2))

所有的__new__()方法都是類方法,所以顯式地傳入類作為第一個引數。

class RoundFloat(float):
    def __new__(cls,val):
        return super(RoundFloat,cls).__new__(cls,round(val,2))

通常使用super()內建函式去捕獲對應的父類以呼叫它的__new__()方法。
②子類化可變型別

class SortedKeyDict(dict):
    def keys(self):
        return sorted(super(SortedKeyDict,self).keys())

(3)多重繼承中方法解釋順序(MRO)
2.2之前,演算法簡單:深度優先,從左至右進行搜尋,取得在子類中使用的屬性。多重繼承取找到的第一個名字。
2.2提出新的MRO,演算法思想是根據每個祖先類的繼承結構編譯出一張列表,包括搜尋到的類,按策略刪除重複的。
2.3使用新的C3演算法替換,採用廣度優先。
新式類有__mro__屬性,告訴你查詢順序。
新式類使用經典類的MRO會失敗。
菱形效應
這裡寫圖片描述
使用經典類的MRO,當例項化D時,不再得到C.__init__()之結果,而得到object.__init__()之結果。使用新式類,需要出現基類,這樣在繼承結構中,就形成了一個菱形。
補充:文件字串不會從基類中繼承過來。因為文件字串對類,函式/方法,還有模組來說都是唯一的。

10、類、例項、其他物件的內建函式
(1)issubclass()
布林函式,判斷一個類是另一個類的子類或子孫類(一個類可視為其自身的子類)。

issubclass(sub,sup)

從2.3開始,第二個引數可以是可能的父類組成的元組。只要sub是其中任何一個的子類都返回True。
(2)isinstance()
布林函式,判定一個物件是否是另一個給定類的例項。

isinstance(obj1,obj2)   

obj1是obj2的一個例項,或是obj2的子類的一個例項時,返回True。
從2.2開始,obj2可以是一個元組,obj1是obj2元組中任何一個候選型別或類的例項時,就返回True。
(3)hasattr(),getattr(),setattr(),delattr()
*attr()系列函式可工作於各種物件,不限於類和例項。
*attr(obj,’attr’….)相當於操作obj.attr。
hasattr()布林函式,決定一個物件是否有一個特定的屬性。
getattr(),setattr()相應地取得和賦值給物件的屬性。getattr()會在你試圖讀取一個不存在的屬性時,引發AttributeError異常。
delattr()刪除屬性。
(4)dir()
可用於例項或者類或者模組。

  • 用於例項,顯示例項變數,還有在例項所在的類及所有它的基類中定義的方法和類屬性。
  • 用於類,顯示類及它所有基類的__dict__中的內容,但不會顯示定義在元類中的類屬性。
  • 用於模組,顯示模組的__dict__的內容。
  • dir()不帶引數時,顯示呼叫者的區域性變數。

(5)super()(2.2)
幫助程式設計師找出相應的父類,然後呼叫相關屬性。
super(type[,obj])返回type的父類,傳入obj引數進行父類繫結。

  • obj是一個例項,isinstance(obj,type)必須返回True;
  • obj是一個類或型別,issubclass(obj,type)必須返回True。
super(MyClass,self).__init__()

(6)vars()
與dir()相似,只是給定的物件引數都必須有一個__dict__屬性。如果提供的物件沒有一個這樣的屬性,則會引發一個TypeError異常。
vars()返回一個字典,包含儲存於物件__dict__中的屬性(鍵)和值。如果沒有為vars()提供引數,將顯示一個包含本地名稱空間的屬性(鍵)及其值的字典,也就是locals()。

11、用特殊方法定製類
Python特殊方法可以用來擴充類的功能,可以實現:

  • 模擬標準型別;
  • 過載操作符。

(1)Python中用來定製類的特殊方法
①基本定製型

  • C.__init__(self[,arg1,…]) 構造器(帶一些可選的引數)
  • C.__new__(self[,arg1,…])構造器(帶一些可選的引數);通常用在設定不變資料型別的子類
  • C.__del__(self) 解析器
  • C.__str__(self) 可列印的字元輸出;內建str()及print語句
  • C.__repr__(self) 執行時的字串輸出;內建repr()和“操作符
  • C.__unicode__(self) Unicode字串輸出:內建unicode()
  • C.__call__(self,*args) 表示可呼叫的例項
  • C.__nonzero__(self) 為object定義False值;內建bool()
  • C.__len__(self) “長度”(可用於類);內建len()

②物件(值)比較

  • C.__cmp__(self,obj) 物件比較:內建cmp()
  • C.__lt__(self,obj) and C.__le__(self,obj) 小於/小於或等於:對應<及<=操作符
  • C.__gt__(self,obj) and C.__ge__(self,obj) 大於/大於或等於:對應>及>=操作符
  • C.__eq__(self,obj) and C.__ne__(self,obj) 等於/不等於:對應==,!=及<>操作符

③屬性

  • C.__getattr__(self,attr) 獲取屬性:內建getattr(),僅當屬性沒有找到時呼叫
  • C.__setattr__(self,attr,val) 設定屬性
  • C.__delattr__(self,attr) 刪除屬性
  • C.__getattribute__(self,attr) 獲取屬性:內建getattr(),總是被呼叫
  • C.__get__(self,attr) (描述符)獲取屬性
  • C.__set__(self,attr,val) (描述符)設定屬性
  • C.__delete__(self,attr) (描述符)刪除屬性

④數值型別:二元操作符

  • C.__*add__(self,obj) 加:+操作符
  • C.__*sub__(self,obj) 減:-操作符
  • C.__*mul__(self,obj) 乘:*操作符
  • C.__*div__(self,obj) 除:/操作符
  • C.__*truediv__(self,obj) True除:/操作符
  • C.__*floordiv__(self,obj) Flooor除://操作符
  • C.__*mod__(self,obj) 取模/取餘:%操作符
  • C.__*divmod__(self,obj) 除和取模:內建divmod()
  • C.__*pow__(self,obj[,mod]) 乘冪:內建pow(),**操作符

⑤數值型別:二進位制操作符

  • C.__*lshift__(self,obj) 左移位:<<操作符
  • C.__*rshift__(self,obj) 右移位:>>操作符
  • C.__*and__(self,obj) 按位與:&操作符
  • C.__*or__(self,obj) 按位或:|操作符
  • C.__*xor__(self,obj) 按位與或:^操作符

⑥數值型別:一元操作符

  • C.__neg__(self) 一元負
  • C.__pos__(self) 一元正
  • C.__abs__(self) 絕對值,內建abs()
  • C.__invert__(self) 按位求反,~操作符

⑦數值型別:數值轉換

  • C.__complex__(self,com) 轉為complex(複數),內建complex()
  • C.__int__(self) 轉為int,內建int()
  • C.__long__(self) 轉為long,內建long()
  • C.__float__(self) 轉為float,內建float()

⑧數值型別:基本表示法(String)

  • C.__oct__(self) 八進位制表示,內建oct()
  • C.__hex__(self) 十六進位制表示,內建hex()

⑨數值型別:數值壓縮

  • C.__coerce__(self,num) 壓縮成同樣的數值型別,內建coerce()
  • C.__index__(self) 在有必要時,壓縮可選的數值型別為整型(比如用於切片索引等)

⑩序列型別

  • C.__len__(self) 序列中項的數目
  • C.__getitem__(self,ind) 得到單個序列
  • C.__setitem__(self,ind,val) 設定單個序列元素
  • C.__delitem__(self,ind) 刪除單個序列元素
  • C.__getslice__(self,ind1,ind2) 得到序列片段
  • C.__setslice__(self,ind1,ind2,val) 設定序列片段
  • C.__delslice__(self,ind1,ind2) 刪除序列片段
  • C.__contains__(self,val) 測試序列成員:內建in關鍵字
  • C.__*add__(self,obj) 串聯:+操作符
  • C.__*mul__(self,obj) 重複:*操作符
  • C.__iter__(self) 建立迭代器:內建iter()

⑪對映型別

  • C.__len__(self) mapping中項的數目
  • C.__hash__(self) 雜湊(hash)函式值
  • C.__getitem__(self,key) 得到給定鍵(key)的值
  • C.__setitem__(self,key,val) 設定給定鍵(key)的值
  • C.__delitem__(self,key) 刪除給定鍵(key)的值
  • C.__missing__(self,key) 給定鍵如果不存在字典中,則提供一個預設值

(2)簡單定製
自己實現init(),str(),repr()等。
print使用str()方法,真正的字串物件表示使用repr()方法。

#! /usr/bin/env python
class RoundFloatManual(object):
    def __init__(self,val):
        assert isinstance(val,float),\
        "Value must be a float!"
        self.value=round(val,2)
    def __str__(self):
        return '%.2f' % self.value
    __repr__=__str__

(3)數值定製
過載__add__()方法,就過載了(+)操作符。
還可以使用__radd__()方法和__iadd__()方法。

def __add__(self,other):
    return self.__class__(self.hr+other.hr,self.min+other.min)

覆蓋“原位”操作,實現增量賦值(2.0),比如iadd()支援mon+=tue。
(4)定製迭代器
實現類中的__iter__()和next()方法來建立一個迭代器。

#! /usr/bin/env python
class AnyIter(object):
    def __init__(self,data,safe=False):
        self.safe=safe
        self.iter=iter(data)
    def __iter__(self):
        return self
    def next(self,howmany=1):
        retval=[]
        for eachItem in range(howmany):
            try:
                retval.append(self.iter.next())
            catch StopIteration:
                if self.safe:
                    break
                else:
                    raise
        return retval

12、私有化
類中屬性預設情況下是“公開的”,類所在模組以及匯入類所在模組中的程式碼都可以訪問到。
(1)雙下劃線
Python使用雙下劃線(__)來“混淆”屬性,不允許直接訪問。
混淆後的屬性,會在名字前面加上下劃線和類名,比如NumStr類中的__num屬性,被混淆後,用於訪問這個資料值的識別符號就變成了self._NumStr__num。混淆操作可以防止在父類或子類中的同名衝突。
(2)單下劃線
使用單下劃線(_)實現簡單的模組級私有化。

13、授權
(1)包裝
包裝任何型別作為一個類的核心成員,使新物件的行為模仿你想要的資料型別中已經存在的行為,且去掉不希望存在的行為。擴充Python是包裝的另一種形式。
(2)實現授權
授權是包裝的一個特性。
授權的過程即是所有更新的功能都是由新類的某部分來處理,但已存在的功能就授權給物件的預設屬性。實現授權的關鍵點是覆蓋__getattr__()方法,在程式碼中包含一個對getattr()內建函式的呼叫。

class WrapMe(object):
    def __init__(self,obj):
        self.__data=obj
    def get(self):
        return self.__data
    def __repr__(self):
        return 'self.__data'
    def __str__(self):
        return str(self.__data)
    def __getattr__(self,attr):
        return getattr(self.__data,attr)
>>>wrappedComplex=WrapMe(3.5+4j)
>>>wrappedComplex.real

訪問real屬性時,Python直譯器將試著在區域性名稱空間中查詢那個名字;若沒有找到,會搜尋類名稱空間,以一個類屬性訪問;若還沒有找到,搜素則對原物件開始授權請求,此時呼叫__getattr__()方法,__getattr__()方法中呼叫getattr()方法得到一個物件的預設行為。
總結:通過覆蓋__getattr__()方法實現授權。
授權只能訪問屬性,特殊行為不可以。例如對列表的切片操作,它內建於型別中,不是屬性,不能授權訪問。
屬性可以是資料屬性,還可以是函式或者方法。Python所有數值型別,只有複數擁有屬性:資料屬性和conjugate()內建方法。

>>>wrappedList=WrapMe([123,'foo',45.67])
>>>wrrapedList[3]   #會丟擲AttributeError

此時可以採用“作弊”的方法來訪問實際物件和它的切片能力。

>>>realList=wrappedList.get()  #get()方法取得對原物件的訪問
>>>realList[3]   

14、新式類的高階特性(2.2+)
(1)新式類的通用特性
型別和類的統一,使得可以子類化Python資料型別。同時,所有的Python內建的“casting”或轉換函式現在都是工廠函式。例如:int(),long(),float(),complex;str(),unicode();list(),tuple();type()。
另外,還加入了一些新的函式:basestring();dict();bool();set(),frozenset();object();classmethod();staticmethod();super();property();file()。這些類名和工廠函式,不僅能建立這些類名的新物件,還可以用來作為基類,去子類化型別。現在還可以用於isinstance()內建函式,isinstance()函式在obj是一個給定型別的例項或其子類的例項時返回True。

OLD(not as good):
if type(obj)==type(0)...
if type(obj)==types.IntType...
BETTER:
if type(obj) is type(0)...
EVEN BETTER:
if isinstance(obj,int)...
if isinstance(obj,(int,long))...
if type(obj) is int...

(2)__slots__類屬性
__dict__屬性跟蹤所有例項屬性。
例項inst,屬性foo,那麼inst.foo與inst.__dict__[‘foo’]等價。
字典會佔用大量記憶體,為記憶體上的考慮,可用__slots__屬性替代__dict__。
__slots__是一個類變數,由一序列型物件組成。由所有合法標識構成的例項屬性的集合來表示。任何試圖建立一個其名不在__slots__中的名字的例項屬性都將導致AttributeError異常。帶__slots__屬性的類定義不會存在__dict__屬性了。使用__slots__屬性的目的是節約記憶體。使用__slots__屬性可以防止使用者隨心所欲的動態增加例項屬性。

class SlottedClass(object):
    __slots__=('foo','bar')
>>>c=SlottedClass()
>>>c.foo=42
>>>c.xxx='nihao'  #引發AttributeError異常

(3)__getattribute__()特殊方法
Python類有一個__getattr__()的特殊方法,僅當屬性不能在例項或類或祖先類的__dict__屬性中找到時,才被呼叫。__getattribute__()與__getattr__()類似,不同在於,當屬性被訪問時,它就一直可以被呼叫,而不侷限於不能找到的情況。在同時定義了__getattribute__()及__getattr__()方法的類中,除非明確從__getattribute__()方法呼叫,或者__getattribute__()方法引發了AttributeError異常,否則後者不會被呼叫。如果將要在__getattribute__()方法中訪問這個類或其祖先類的屬性,應該總是呼叫祖先類的同名方法,避免引起無窮遞迴。
(4)描述符(描述符就是可重用的屬性)
可認為描述符是表示物件屬性的一個代理,它為屬性提供了強大的API。當需要屬性時,可以通過描述符來訪問它(當然還可以使用常規的句點屬性標誌法來訪問屬性)。
__get__(),__set__(),__delete__()特殊方法分別用於得到一個屬性的值,對一個屬性進行賦值,刪除掉某個屬性。同時覆蓋__get__()和__set__()的類被稱作資料描述符。實現了__set__()方法的類被稱作非資料描述符,或方法描述符。
__get__(),__set__(),__delete__()的原型如下:

  • __get__(self,obj,typ=None)=>None
  • __set__(self,obj,val)=>None
  • __delete__(self,obj)=>None

整個描述符系統的心臟是__getattribute__()特殊方法,因為對每個屬性的訪問都會呼叫這個特殊的方法。
舉例來說,給定類X和例項x:
訪問例項屬性,x.foo由__getattribute__()轉化成:

type(x).__dict__['foo'].__get__(x,type(x))

訪問類屬性,那麼None將作為物件被傳入:

X.__dict__['foo'].__get__(None,X)

訪問父類屬性,super(Y,obj).foo(假設Y為X的子類):

X.__dict__['foo'].__get__(obj,X)

靜態方法、類方法、屬性,甚至所有的函式都是描述符。Python中函式之間的唯一區別在於呼叫方式的不同,分為繫結和非繫結狼類,函式描述符可以處理這些問題,描述符會根據函式的型別確定如何“封裝”這個函式和函式被繫結的物件,然後返回呼叫物件。使用描述符的順序很重要,有一些描述符的級別要高於其他的。描述符是一個類屬性,因此所有的類屬性皆具有最高的優先順序。優先順序排序:類屬性>資料描述符>例項屬性>非資料描述符>預設為__getattr__()。

#! /usr/bin/env python

import os
import pickle

class FileDescr(object):
    saved=[]

    def __init__(self,name=None):
        self.name=name

    def __get__(self,obj,typ=None):
        if self.name not in FileDescr.saved:
            raise AtrributeError,"%r used before assignment" % self.name
        try:
            f=open(self.name,'r')
            val=pickle.load(f)
            f.close()
            return val
        except(pickle.UnpicklingError,IOError,EOFError,AttributeError,ImportError,IndexError),e:
            raise AttributeError, "could not read %r" % self.name

    def __set__(self,obj,val):
        f=open(self.name,'w')       
        try:
            pickle.dump(val,f)
            FileDescr.saved.append(self.name)
        except(TypeError,pickle.PickingError),e:
            raise AttributeError, "could not pickle %r" % self.name
        finally:
            f.close()

    def __delete__(self,obj):
        try:
            os.unlink(self.name)
            FileDescr.saved.remove(self.name)
        except(OSError,ValueError),e:
            pass
>>>class MyFileValClass(object):
...         foo=FileDescr('foo')
...         bar=FileDescr('bar')
>>>fvc=MyFileVarClass()
>>>print fvc.foo  #引發AttributeError
>>>fvc.bar=42
>>>print fvc.bar  #列印42  

(5)property()內建函式
屬性是一種有用的特殊型別的描述符。屬性用來處理所有例項屬性的訪問。
使用句點符號訪問例項屬性,其實是在修改例項的__dict__屬性。
使用property()訪問例項屬性,使用的是函式(或方法)。

property(fget=None,fset=None,fdel=None,doc=None)

property()接受一些傳進來的函式作為引數,property()是在它所在的類被建立時被呼叫的,傳進來的函式都是非繫結的。

class HideX(object):
    def __init__(self,x):
        assert isinstance(x,int),'"x" must be an integer!'
        self.__x=~x
    def get_x(self):
        return ~self.__x
    def set_x(self,x):
        assert isinstance(x,int),'"x" must be an integer!'
        self.__x=~x
    x=property(get_x,set_x)

改進程式碼==>

class AdvancedHideX(object):
    def __init__(self,x):
        assert isinstance(x,int),'"x" must be an integer!'
        self.__x=~x
    @property
    def x():
        def fget(self):
            return ~self.__x
        def fset(self,x):
            assert isinstance(x,int),'"x" must be an integer!'
            self.__x=~x
        return locals()

改進後的程式碼:類名稱空間更加簡潔;使用者不能通過inst.set_x(40)來給屬性賦值,只能使用init.x=4。

(6)元類和__metaclass__
元類是一個類(一個類中類),它的例項是其他的類。當建立一個新類時,就是在使用預設的元類,它是一個型別物件(傳統類的元類是types.ClassType).當某個類呼叫type()函式時,就會看到它到底是誰的例項。元類一般用於建立類,在執行類定義時,直譯器必須知道類正確的元類,所以直譯器會尋找類屬性的__metaclass__屬性,若沒找到,會向上查詢父類的__metaclass__屬性,如果還沒找到,直譯器會檢查名字為__metaclass__的全域性變數,若還不存在,就用types.ClassType作為此類的元類。在類定義時,將檢查此類正確的元類,元類通常傳遞三個引數到構造器:類名,從基類繼承資料的元組和(類的)屬性字典。通過定義一個元類可以“迫使”程式設計師按照某種方式實現目標類,這樣既可以簡化他們的工作,也可以使編寫出的程式更符合特定標準。

# coding=gbk

#! /usr/bin/env python

from warnings import warn

class ReqStrSugRepr(type):

    def __init__(cls,name,bases,attrd):
        super(ReqStrSugRepr,cls).__init__(name,bases,attrd)

        if '__str__' not in attrd:
            raise TypeError('Class requires overriding of __str__()')

        if '__repr__' not in attrd:
            warn('Class suggests overriding of __repr__()\n',stacklevel=3)

print '*** Defined ReqStrSugRepr (meta)class \n'

class Foo(object):
    __metaclass__=ReqStrSugRepr

    def __str__(self):
        return 'Instance of class:',self.__class__.__name__

    def __repr__(self):
        return self.__class__.__name__

print '*** Defined Foo Class \n'

class Bar(object):
    __metaclass__=ReqStrSugRepr

    def __str__(self):
        return 'Instance of class:',self.__class__.__name__

print '*** Defined Bar Class\n'

class FooBar(object):
    __metaclass__=ReqStrSugRepr

print '*** Defined FooBar Class\n'

14.其他模組

  • UserList 提供一個列表物件的封裝類
  • UserDict 提供一個字典物件的封裝類
  • UserString 提供一個字串物件的封裝類
  • types 定義所有Python物件的型別再標準Python直譯器中的名字
  • operator 標準操作符的函式介面

相關推薦

Python學習筆記13Python面向物件程式設計

1、引言 (1)類和例項:類是物件的定義,例項是“真正的實物”。 定義類:類名通常大寫字母打頭。 class MyNewObjectType(bases): 'define MyNewObjectType class' class_su

python學習筆記(二)面向物件程式設計

面向物件程式設計 self代表類的例項 python支援多重繼承 特殊方法def init(self,): 作用:在init方法內部,就可以把各種屬性繫結到self 前後分別有兩個下劃線

Python 學習筆記(五)[面向物件]

變數 類的變數 class A: num = 1 a = A() # 1 a.num # 1 A.num # 修改類的變數值 A.num = 2 # 2 a.num # 修改物件的變數值 a.num = 3 2 A.num # 給物件新增屬性 a.age

Python學習筆記13標準庫之子程序(subprocess包)

ubprocess包主要功能是執行外部的命令和程式。從這個意義上來說,subprocess的功能與shell類似。 subprocess以及常用的封裝函式 當我們執行python的時候,我們都是在建立並執行一個程序。 在Python中,我們通過標準庫中的subprocess

Python學習筆記13selenium webdriver 實現驗證碼登入

#通過ActionChains+autoit進行驗證碼圖片的下載,通過pytesseract識別驗證碼中的字元,由於有一定錯誤率,進行重試直至成功from selenium import webdriver from LanternAnswer.login import Lo

Python學習筆記4一切皆物件,所有物件都是第一類的

一切都是物件 在python中下列語句其實都是一個共同點: i = 1 s = "abcde" def foo(): pass class C(object): pass instance = C() l = [1,2] t = (1,2) 他們在p

Python學習筆記4Python物件

1、Python物件 所有Python物件都擁有三個特性:身份(只讀,id())、型別(只讀,type())和值。 Python型別也是物件。物件的值是否可更改被稱為物件的可改變性(mutabili

Python學習筆記01Python解釋器

3.6 str 2.6 python安裝 blog 時間比較 info ima style 資料參考:廖雪峰的官方網站https://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac927076

Python學習筆記Python基礎

Python語法採用縮排形式,有以下幾點需要注意: 註釋以#開頭; 當語句以冒號:結尾時,縮排的語句視為程式碼塊; 始終堅持4個空格的縮排; 大小寫敏感; 1. 資料型別和變數 1.1 資料型別 1.1.1 整數 在程式中的寫法和數學中的寫法一樣

Python學習筆記19函數語言程式設計

import shutil#copy()複製檔案#格式: shutil.copy(來源路徑,目標路徑)#返回值:返回目標路徑#拷貝的同時,可以給檔案重新命名rst = shutil.copy('/home/dadada/hahaha.huhu', '/home/dadada/hahaha.hoho')prin

Python學習筆記3Python基礎

1、語句和語法 (1)註釋(#) (2)繼續( \) 存在兩種例外情況,一個語句不使用反斜線也可以跨行: 在使用閉合操作符(小括號,中括號,花括號)時單一語句可跨多行; 使用三引號包括下的字串

Python學習筆記Python中列表元素轉為數字

numbers = ['1', '5', '10', '8']; 方法一: numbers = [ int(x) for x in numbers ] 方法二: umbers = list(map(int, numbers))

學習筆記13】java面向物件-強制型別轉換

目前多型情況下不能訪問子類特有的成員。 如果需要訪問子類特有的成員,那麼需要進行型別強制轉換. 基本資料型別的轉換小資料型別-------->大的資料型別      自動型別轉換大資料型別--------->小資料型別       強制型別轉換     小資料型

php學習筆記(二)面向物件程式設計

public 公有的:本類,子類,外部物件都可以呼叫 protected 受保護的:本類 子類,可以執行,外部物件不可以呼叫 private 私有的:只能本類執行,子類與外部物件都不可呼叫 面向物件程式設計的三大特點 1)封閉性 封閉性,也可以稱為資訊隱藏。就是將一個

python學習筆記9面向對象編程,類

數據 相同屬性 技術 -o 必須 是把 oop ack 繼承 一、面向對象編程   面向對象--Object Oriented Programming,簡稱oop,是一種程序設計思想。   還有另一種程序設計思想——面向過程編程。面向過程的思想是把一個項目、一件事情按照一定

python學習筆記2字符串

nbsp 大小 alpha .com format 大小寫 fin 判斷大小 key python學習筆記2:字符串 總結:字符串是不可變變量,不能通過下標修改其值    字符串的方法都不會改變字符串原來的值,而是新生成一個字符串 一、3種寫法——單引號,雙引號,三引號  

Python學習筆記文件處理

alt 筆記 lin 系統 顯式 當前位置 open 刷新 大小 一:打開文件 open(name,mode,[bufferSize]) name:文件路徑 mode:文件打開方式 二:文件讀取 read()方法:可以一次讀取文件的全部內容,Python把內容讀到

Python學習筆記3簡單文件操作

name n) popu 元素 close nes pla () eof # -*- coding: cp936 -*- # 1 打開文件 # open(fileName, mode) # 參數:fileName文件名稱 # mode打開方式 # w

python學習筆記9正則表達式

使用 引入 常用 常用的正則表達式 需要 style pan 表達式 span 一、簡介   正則表達式就是用來查找字符串的;用來匹配一些比較復雜的字符串。   使用正確表達式需要引入re模塊 (regular定期的有規律的)    二、匹配字符串的方法

python學習筆記8異常處理

mage 可能 str 裏的 tro 信息 學習筆記 異常信息 常見 一、異常處理   在程序運行過程中,總會遇到各種各樣的錯誤。   程序一出錯就停止運行了,那我們不能讓程序停止運行吧,這時候就需要捕捉異常了,通過捕捉到的異常,我們再去做對應的處理。   如下,寫段代碼,