python學習筆記之七:魔法方法,屬性


      在python中,有的名稱會在前面和后面加上兩個下划線,由這些名字組成的集合所包含的方法稱為魔法方法(或者是特殊方法)。如果對象實現了這些方法中的某一個,那么這個方法會在特殊的情況下(確切地說是根據名字)被python調用。而幾乎沒有直接調用它們的必要。

      這里會詳細討論一些重要的魔法方法(最重要的是__init__方法和一些處理對象訪問的方法,這些方法允許你創建自己的序列或者是映射),還會處理屬性(通過property函數來處理)。

 

一. 構造方法

1.1 介紹與創建

      首先要討論的第一個魔法方法是構造方法,它代表着類似於以前例子中使用過的那種名為init的初始化方法。但構造方法和其他普通方法不同的地方在於,當一個對象被創建后,會立即調用構造方法。

      在python中創建一個構造方法只要將init方法的名字從簡單的init修改為魔法版本__init__即可:

__metaclass__ = type
 
class FooBar:
    def __init__(self):
        self.somevar = 42

       如果給構造方法傳幾個參數的話,要怎么做呢:

__metaclass__ = type
 
class FooBar:
    def __init__(self,value = 42):
        self.somevar = value

因為參數是可選的,所以你可以繼續,當做什么事情都沒發生:

>>> s = FooBar(100)
>>> s.somevar
100

 

1.2 重寫一般方法和特殊的構造方法

      重寫是繼承機制中的一個重要內容,對於構造方法尤其重要。構造方法用來初始化新創建對象的狀態,大多數子類不僅要擁有自己的初始化代碼,還要擁有超類的初始化代碼。當一個類的構造方法被重寫,那么就需要調用超類的構造方法,否則對象可能不會被正確地初始化。

__metaclass__ = type
 
class Bird:
    def __init__(self):
        self.hungry = True
    def eat(self):
        if self.hungry:
            print "Aaaah..."
            self.hungry = False
        else:
            print 'No,thanks'
        
    
class SongBird(Bird):
    def __init__(self):
        self.sound = 'Squark!'
    def sing(self):
        print self.sound

      下面介紹2種重寫構造函數的方法:調用超類構造方法的未綁定版本,或者使用super函數。

(1)調用超類構造方法的未綁定版本

class SongBird(Bird):
    def __init__(self):
        Bird.__init__(self)
        self.sound = 'Squark!'
    def sing(self):
        print self.sound

      在調用一個實例的方法時,該方法的self參數會被自動綁定到實例上。但是如果直接調用類的方法,那么就沒有實例會被綁定。這樣就可以自由第提供需要的self參數。這樣的方法稱為未綁定方法。

      通過將當前的實例作為self參數提供給未綁定方法,SongBird就能使用其超類構造方法的所有實現,也就是說屬性hungry能被設置。

 

(2)使用super函數

      如果讀者不想堅守舊版python陣營的話,那么就應該使用super函數。當前的類和對象都可以作為super函數的參數使用,調用函數返回的對象的任何方法都是調用超類的方法,而不是當前類的方法。可以直接使用super(SongBird,self)。

class SongBird(Bird):
    def __init__(self):
        super(SongBird,self).__init__()
        self.sound = 'Squark!'
    def sing(self):
        print self.sound

 

二. 成員訪問

本節會討論一個有用的魔法方法集合,它可以創建行為類似於序列或映射的對象。

2.1 基本的序列和映射規則

序列和映射是對象的集合。為了實現它們的基本行為,如果對象是不可變的,那么就需要2個魔法方法,如果是可變的則需要4個:

(1)__len__(self):這個方法返回集合中所含項目的數量。如果返回0,會被當做一個布爾變量中的假值處理;

(2)__getitem__(self,key):這個方法返回與所給鍵對應的值。

(3)__setitem__(self,key,value):這個方法按照一定的方式存儲和key相關的value,該值隨后可使用__getitem__來獲取。當然,只能為可修改對象定義這個方法。

(4)__delitem__(self,key):這個方法都在對一部分對象使用del語句時被調用,同時必須刪除和元素相關的鍵。

 

2.2 子類化列表,字典和字符串

      標准庫有3個關於序列和映射規則(UserList,UserString和UserDict)可以立即使用的實現“只想在一個操作中自定義行為,那么其他的方法都不要實現”。在較新版本的Python中,可以子類化內建類型。

      因此,如果希望實現一個和內建列表行為類似的序列,可以使用子類list。下面來看看帶訪問計數的列表:

__metaclass__ = type
 
class CouterList(list):
    def __init__(self,*arg):
        super(CounterList,self).__init__(*arg)
        self.counter = 0
    def __getitem__(self,index):
        self.counter += 1
        return super(CounterList,self).__getitem__(index)

CountList類嚴重依賴於它的子類化超類(list)的行為,它沒有重寫任何的方法都能被直接使用。在2個被重寫的方法中,super方法被用來調用相應的超類的方法,只在__init__中添加了所需的初始化counter特性的行為,並在__getitem__中更新了counter特性。

 

三. 屬性

      訪問器是一個簡單的方法,它能夠使用getHeight,setHeight這樣的名字來得到或者重綁定一些特性。如果在訪問給定的特性時必須要采取一些行動,那么像這樣的封裝狀態變量就很重要。

3.1 property函數

__metaclass__ = type
 
class Rectangle:
    def __init__(self):
        self.width = 0
        self.height = 0
    def setSize(self,size):
        self.width,self.height = size
    def getSize(self):
        return self.width,self.height

上面的代碼中,當計算面積或者對角線長度時就要考慮size是怎么實現的,如果將size變成一個真正的特性,這樣width和height就能動態算出。那么怎么解決呢?把所有屬性都放到訪問器方法中?但是如果有很多簡單的特性,那就要寫很多訪問器方法了,它們除了返回或者設置特性就不做任何事了。

      幸好,python能隱藏訪問器方法,讓所有特性看起來一樣,這些通過訪問器定義的特性被稱為屬性,在新式類中可以使用property函數,創建屬性。

      它的使用很簡單,只需要增加一行代碼:

__metaclass__ = type
 
class Rectangle:
    def __init__(self):
        self.width = 0
        self.height = 0
    def setSize(self,size):
        self.width,self.height = size
    def getSize(self):
        return self.width,self.height
    size = property(getSize,setSize)

property函數創建了一個屬性,其中訪問器函數被用做參數,這個屬性命名為size,這樣一來就不用擔心它是怎么實現的了。可以用同樣的方式處理width,height和size:

>>> r = Rectangle()
>>> r.width = 10
>>> r.height = 5
>>> r.size
(10, 5)
>>> r.size = 150,100
>>> r.width
150

很明顯,size特性仍然取決於getSize和setSize中的計算,但它看起來就像普通的屬性一樣。

      實際上,property函數可以用0,1,2,3或者4個參數來調用。如果沒有參數,產生的屬性既不可讀,也不可寫。如果只使用一個參數(一個取值方法),產生的屬性是只讀的。第三個參數是一個用於刪除特性的方法,第四個參數是一個文檔字符串。property的4個參數分別被叫做fget,fset,fdel和doc。

      理論上說,在新式類中應該使用property函數而不是訪問器方法。

 

3.2 靜態方法和類成員方法

      靜態方法和類成員方法分別在創建時分別被裝入Staticmethod類型和Classmethod類型的對象中。靜態方法的定義沒有self參數,且能夠被類本身直接調用。類方法在定義時需要名為cls的類似於self的參數,類成員方法可以直接被類的具體對象調用,但cls參數是自動被綁定到類的。

      在python2.4中,為這樣的包裝方法引入了一個叫做裝飾器的新語法,使用@操作符,在方法(或者函數)的上方將裝飾器列出,從而指定一個或者更多的裝飾器。

__metaclass__ = type
 
class MyClass:
    @staticmethod
    def smeth():
        print 'this is a static method'

    @classmethod
    def cmeth(cls):
        print 'this is a class method of ',cls

定義好之后,可以如下那樣使用:

>>> MyClass.smeth()
this is a static method
>>> MyClass.cmeth()
this is a class method of  <class '__main__.MyClass'>

 

3.3 __getattr__,__setattr__和它的朋友們

攔截對象的所有特性訪問是可能的,這樣可以用舊式類實現屬性。為了在訪問特性時可以執行代碼,必須使用一些魔法方法:(在舊式類中只需要后3個)

(1)__getattribute__(self,name):當特性name被訪問時自動被調用(只能在新式類中使用);

(2)__getattr__(self,name):當特性name被訪問且對象沒有相應的特性時被自動調用;

(3)__setattr__(self,name,value):當試圖給name賦值時會被自動調用;

(4)__delattr__(self,name):當試圖刪除特性name時被自動調用;

__metaclass__ = type
 
class Rectangle:
    def __init__(self):
        self.width = 0
        self.height = 0
    def __setattr__(self,name,value):
        if name == 'size':
            self.width,self.height = size
        else:
            self.__dict__[name] = value
    def __getattr__(self,name):
        if name == 'size':
            return self.width,self.height
        else:
            raise AttributeError

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM