2011年9月10日 星期六

特殊方法名稱

在Python中定義類別時,有些__name__的特殊函式名稱,是用定義運算子或特定操作的行為。舉個例子來說,可以定義一個有理數類別,並定義其+、-、*、/等行為:

class Rational:
    def __init__(self, n, d):  # 物件建立之後所要建立的初始化動作
        self.numer = n
        self.denom = d
    
    def __str__(self):   # 定義物件的字串描述
        return str(self.numer) + '/' + str(self.denom)
    
    def __add__(self, that):  # 定義 + 運算
        return Rational(self.numer * that.denom + that.numer * self.denom, 
                        self.denom * that.denom)
    
    def __sub__(self, that):  # 定義 - 運算
        return Rational(self.numer * that.denom - that.numer * self.denom,
                        self.denom * that.denom)
                           
    def __mul__(self, that):  # 定義 * 運算
        return Rational(self.numer * that.numer, 
                        self.denom * that.denom)
        
    def __truediv__(self, that):   # 定義 / 運算
        return Rational(self.numer * that.denom,
                        self.denom * that.denom)

    def __eq__(self, that):   # 定義 == 運算
        return self.numer * that.denom == that.numer * self.denom

x = Rational(1, 2)
y = Rational(2, 3)
z = Rational(2, 3)
print(x)       # 1/2
print(y)       # 2/3
print(x + y)   # 7/6
print(x - y)   # -1/6
print(x * y)   # 2/6
print(x / y)   # 3/6
print(x == y)  # False
print(y == z)  # True

__init__()定義物件建立後要執行的初始化過程,相對它的是__del__()方法,在物件被回收前會被執行(因為回收物件的時間不一定,所以不建議用在要求立即性的情況)。常見的+、-、*、/、==等操作,則分別是由__add__()__sub__()__mul__()__truediv__()(//則是由__floordiv__()定義)與__eq__()定義。

__str__()用來定義傳回物件描述字串,通常用來描述的字串是對使用者友善的說明文字,如果對物件
使用str(),所呼叫的就是__str__()。如果要定義對開發人員較有意義的描述,例如傳回產生實例的類別名稱之類的,則可以定義__repr__(),如果對物件使用repr(),則所呼叫的就是__repr__()。

特性名稱空間 中看過的例子則是__getattr__()方法,相對它的方法是__setattr__(),而用來定義實例的特性被設定時該作什麼動作。例如:
>>> class Some:
...     def __setattr__(self, name, value):
...         print(name, value)
...     def __init__(self):
...         self.x = 10
...
>>> s = Some()
x 10
>>> s.w = 100
w 100
>>> s.__dict__['z'] = 200
>>> print(s.__dict__)
{'z': 200}
>>>


一旦有定義__setattr__(),則所有以 . 運算子來設定特性的操作,都會呼叫__setattr__(),但直接對實例的__dict__操作則不會。

__getitem__()__setitem__()則用來設定[]運算子的行為。例如:

>>> class Some:
...     def __init__(self):
...         self.inner = {}
...     def __setitem__(self, name, value):
...         self.inner[name] = value
...     def __getitem__(self, name):
...         return self.inner[name]
...
>>> s = Some()
>>> s[0] = 100
>>> s['Justin'] = 'Message'
>>> s[0]
100
>>> s['Justin']
'Message'
>>>


在設計程式的過程中,經常有的需求之一,就是希望逐一取得某物件內部的所有資料(或物件),像是取得串列中所有的資料,或取 得集合中所有的資料。

因為串列是有序結構並有索引特性,而集合則為無序不重複的特性,兩者所提供的公開存取方法也不相同,
如 何以一致方式取得不同資料結構的群集物件是個問題。

在Python中,你可以讓物件實作__iter__()方法,這個方法可以傳回一個迭代器(Iterator),一個具有__next__()方法的物件。迭代器走訪物件內容收集物件後傳回,每次呼叫迭代器物件的__next__()方法,必須傳回群集的下一個元素,如果沒有下一個元素了,則丟出StopIteration物件。例如:
class Some:
    class Iterator:
        def __init__(self, length):
            self.length = length
            self.number = -1
        def __next__(self):
            self.number = self.number + 1
            if self.number == self.length:
                raise StopIteration
            return self.number
    
    def __init__(self, length):
        self.length = length

    def __iter__(self):
        return Some.Iterator(self.length)

s = Some(3)
it = iter(s)
print(next(it))   # 0
print(next(it))   # 1
print(next(it))   # 2
print(next(it))   # StopIteration

實際上,你可以使用iter()來代為呼叫物件的__iter__()方法,使用next()方法代為呼叫物件的__next__()方法。事實上,你可以結合for in迴圈來提取物件,for in迴圈會透過__iter__()取得迭代器,然後在每次迴圈中呼叫__next__()方法,而後遇到StopIteration丟出後離開迴圈。例如:
for n in Some(10):
    print(n)       # 顯示 0 到 9

以上先簡介一些簡單的特殊方法名稱,詳細的特殊方法說明,可以參考 Special method names

沒有留言:

張貼留言