1. 程式人生 > 實用技巧 >Python第九章--特殊方法、屬性、迭代器

Python第九章--特殊方法、屬性、迭代器

一、構造方法

1.構造方法

(1.構造方法類似於“第六章函式與模組”中使用過的名為init的初始化方法,與普通方法不同的是,當物件被建立後,會立即呼叫構造方法

例如,原來新建一個類物件需要做如下工作
>>> class MyNewClass():
    def init(self):
        self.var = 30
    
>>> f = MyNewClass()
>>> f.init()
>>> f.var
30

現在可以通過建立一個構造方法,即把init方法的名字改為__init__,這樣前面構造一個物件時工作就會簡化
>>> class MyNewClass(): def __init__(self): self.var = 30 >>> f = MyNewClass() >>> f.var 30

(2.構造方法中的引數傳遞

顯然可以在構造方法中定義其他引數,比如下面的例子中我們設定了另外一個關鍵字引數var
>>> class MyNewClass():
    def __init__(self, var = 'Hello, world!'):
        self.var = var


如果使用以下方法新建一個MyNewClass物件,
>>> f = MyNewClass('Good morning!') >>> f.var 'Good morning! '

2.重寫一般方法

(1.我們知道每個類可能擁有一個或多個超類,並從超類那裡繼承行為方式;如果一個方法在B類的例項中被呼叫,但是B的定義中沒有該方法,那麼就會去B的超類A中去找。

>>> class A:
    def hello(self):
        print('Hello, I am A.')
        
>>> class B(A):
    pass


>>> a = A()
>>> b = B() >>> a.hello() Hello, I am A. >>> b.hello() Hello, I am A. 由於B沒有自己的hello方法,所以在其例項中呼叫hello方法時實際上是其超類A的hello方法 在子類中增加功能的最基本方法就是增加方法,因此B可以重寫這個名為hello的方法 >>> class B(A): def hello(self): print('Hello, I am B.') >>> b = B() >>> b.hello() Hello, I am B.

3.重寫構造方法

(1.處理構造方法比重寫普通方法時,更可能遇到特別的問題:一個類的構造方法被重寫,那麼就需要呼叫其超類的構造方法,否則物件可能不會被正確地初始化

假設定義一個Bird類
class Bird():
    def __init__(self):
        self.hungry = True
    def eat(self):
        if self.hungry:
            print('Aaaah...')
            self.hungry = False
        else:
            print('No, thanks.')


Bird類定義了所有的鳥都具有的一些基本能力:吃(eat)
>>> b = Bird()
>>> b.eat()
Aaaah...
>>> b.eat()
No, thanks.
在上面例子中可以看到,鳥吃過之後就不會再覺得飢餓

現在為類Bird新增一個子類SongBird
class SongBird(Bird):
    def __init__(self):
        self.sound = 'Squawk!'
    def sing(self):
        print(self.sound)
因為SongBird是Bird的子類,所以它繼承了Bird類的eat方法


但如果呼叫該方法,你會發現
>>> songb = SongBird()
>>> songb.eat()
Traceback (most recent call last):
  File "<pyshell#56>", line 1, in <module>
    songb.eat()
  File "C:/python/bird.py", line 5, in eat
    if self.hungry:
AttributeError: 'SongBird' object has no attribute 'hungry‘
異常顯示SongBird類物件沒有hungry這個屬性,這是因為在其構造方法中沒有任何關於hungry初始化的程式碼
方案一:
為了解決前面的問題,我們可以在SongBird類的構造方法中呼叫其超類的構造方法,即
class SongBird(Bird):
    def __init__(self):
        Bird.__init__(self)  # 增加了一行其超類的構造方法的呼叫語句
        self.sound = 'Squawk!‘
……
>>> b = SongBird()
>>> b.eat()
Aaaah...
>>> b.eat()
No, thanks.


方案二:
另外一種方法是在SongBird類的構造方法中呼叫super函式,即
class SongBird(Bird):
    def __init__(self):
        super(SongBird, self).__init__()        # 增加了super函式的呼叫
        self.sound = 'Squawk!‘
……
>>> b = SongBird()
>>> b.eat()
Aaaah...
>>> b.eat()
No, thanks.

二.成員訪問

1.協議(規則):規則(或協議protocol)是用來描述管理某種形式的行為準則,說明了應該實現何種方法和這些方法應該做什麼
2.基本的序列和對映規則
(1.序列和對映均是物件的集合
為了實現序列和對映的基本行為(規則),其物件必須實現2個(對不可變物件而言)或4個方法(對可變物件而言)
(2.
__len__(self):返回序列中元素的個數或者對映中鍵/值對的數量
__getitem__(self, key):返回序列中與鍵對應的值,如果是序列,鍵的取值為0~n-1的整數,其中n是序列的長度,此外序列中鍵也可以是分片物件;當鍵是負數時則從末尾開始計數
__setitem__(self, key, value):儲存和鍵key相關的值value,該值隨後可以通__getitem__來獲取
__delitem__(self, key):在序列中刪除與鍵key對應的元素,在對映中刪除鍵key以及對應的值

建立一個無窮序列

def checkIndex(key):
    #檢查鍵是否是可接受的索引,如果鍵不是整數,會引發TypeError異常;如果是負數,則會引發IndexError
    if not isinstance(key, int):  # 檢驗key是否是整數物件的例項
        raise TypeError
    if key < 0:
        raise IndexError
class ArithmeticSequence:
    def __init__(self, start = 0, step = 1):
        '''序列初始化'''
        self.start = start      # 預設的序列初始值為start=0
        self.step = step      # 預設情況下每個元素之間相差1
        self.changed = {}    # 存放所有索引/值對的字典

def __getitem__(self, key):
        '''從序列中獲取一個元素'''
        checkIndex(key)
        try:
            return self.changed[key]
        except KeyError:        # 若鍵key不存在,則新生成該鍵對應的值
            return self.start + key * self.step

def __setitem__(self, key, value):
        '''設定與key對應的值'''
        checkIndex(key)
        self.changed[key] = value    # 設定鍵key/值value對



下面是應用例項:
>>> acs = ArithmeticSequence()
# 上面建立了一個序列,從0開始,元素之間間隔為1
>>> acs.__getitem__(5)         # 獲取序列中關鍵字為5的元素
5                           # 返回0 + 5 * 1
>>> acs[5]            # 序列中利用索引獲取元素的方法
5

>>> acs = ArithmeticSequence(3, 2)
# 上面建立了一個序列,從3開始,元素之間間隔為2
>>> acs.__getitem__(5)
13                        # 返回 3 + 5 * 2
>>> acs[5]
13


注意:
在函式checkIndex中使用了內建函式isinstance,用於測試索引key是否是整型物件的例項
注意這個類ArithmeticSequence中沒有定義__delitem__方法,這是因為作者不希望元素被刪除,如果確實有刪除的必要,則可以自行定義;另外也沒有__len__方法
你可以自行嘗試使用一個非法型別的索引,會引發TypeError異常;若是負數索引,則會引發IndexError異常。這些異常均是程式設計師設定由raise語句主動引發的異常

3.定義列表、字典和字串的子類
(1.Python支援對內建型別進行子類化,假如需要實現一個和內建列表行為相似的序列,可以子類化list來實現

class CounterList(list):
    def __init__(self, *args):
        super(CounterList, self).__init__(*args)
        self.counter = 0
    def __getitem__(self, index):
        self.counter +=1
        return super(CounterList, self).__getitem__(index)
容易看出CounterList類沒有重寫任何的方法,由於是list類的子類,可以直接使用list的各種方法,包括append、extend等等


CounterList類與list類最大的不同在於,前者有一個例項變數self.counter,每次訪問該列表中的元素時,這個變數都會自增
>>> cl = CounterList(range(8))
>>> cl.reverse()                     # 直接呼叫父類的reverse方法
>>> cl
[7, 6, 5, 4, 3, 2, 1, 0]
>>> cl.append(8)            # 直接呼叫父類的append方法
>>> cl
[7, 6, 5, 4, 3, 2, 1, 0, 8]
>>> cl.counter            # 尚未訪問列表中的元素
0


>>> cl
[7, 6, 5, 4, 3, 2, 1, 0, 8]
>>> cl[2:4] = []
>>> cl
[7, 6, 3, 2, 1, 0, 8]
>>> cl[2] + cl[5]         # 訪問列表元素會呼叫__getitem__方法
3
>>> cl.counter           # 上面訪問兩次:cl[2]和cl[5]
2

三.屬性

(1.訪問器方法

下面定義了一個Rectangle類
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



>>> r = Rectangle()
>>> r.width = 4
>>> r.height = 5
>>> r.getSize()
(4, 5)
>>> r.setSize((6, 7))
>>> r.getSize()
(6, 7)
可以認為Rectangle類中有一個特性名為size,是由width和height組成的元組,這裡的setSize和getSize方法均被稱為訪問器方法,而通過訪問器方法定義的特性(size)被稱為屬性

(2.利用property函式建立屬性

我們將前面Rectangle類的定義稍微改寫一下
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)  # 增加了一行!!!
getsize:取屬性值方法
setsize;設定屬性值方法


>>> r = Rectangle()
>>> r.width = 4
>>> r.height = 5
>>> r.size
(4, 5)
>>> r.size = 10, 20
>>> r.height
20
此時的size就像普通的屬性一樣,其特性仍然通過getSize和setSize方法確定

四.迭代器

1.迭代的意思是將一些事重複做若干次,和迴圈類似
2.我們曾經對序列和字典等物件使用for迴圈進行迭代,其實對其他物件也是可以的,只要該物件實現了__iter__方法,該方法是迭代器規則的基礎
3.
(1.__iter__方法會返回一個迭代器(iterator),所謂迭代器(iterator)就是具有__next__方法的物件,呼叫該方法時返回物件例項中的下一個值
(2.為什麼不用列表來一次性獲取所有的值

原因:如果有很多值,那麼列表就會佔用太多記憶體,而使用__iter__方法的好處是一個接一個地計算值

4.請記住:一個實現了__iter__方法的物件是可迭代的,

一個實現了__next__方法的物件是迭代器!

下面定義了一個斐波那契數列,
class Fibs:
    def __init__(self):
        self.a = 0
        self.b = 1
    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        return self.a
    def __iter__(self):
        return self
這裡的Fibs物件是可迭代的,同時也是一個迭代器


>>> f = Fibs()
>>> f.__next__()  #f是可迭代物件,可以利用該方法獲取下一個元素
1
>>> f.__next__()
1
>>> f.__next__()
2
>>> for i in f.__iter__(): #__iter__返回迭代器,因此可以用於迴圈。此   # 時取出的下一個元素是當前(!)的下一元素,並不總是第一個元素
    if i > 20:
        break
    else:
        print(i, end = ' ')

3 5 8 13

(1.內建函式iter可以從可迭代的物件中獲得迭代器,然後使用__next__方法來獲取迭代器中的下一個元素
>>> it = iter([1, 2, 3])
>>> it.__next__()
1
>>> it.__next__()
2
(2.當我們使用for語句的時候,for語句就會自動地通過__iter__()方法來獲得迭代器物件,並且通過__next__()方法來獲取下一個元素

class MyRange(object):
    def __init__(self, n):
        self.idx = 0
        self.n = n

    def __iter__(self):
        return self

    def __next__(self):
        if self.idx < self.n:
            val = self.idx
            self.idx += 1
            return val
        else:
            raise StopIteration()

執行如下程式碼:
myRange =MyRange(3)
for i in myRange:
print(i)

執行如下程式碼:
myRange =MyRange(3)
print(myRange is iter(myRange))

print([i for i in myRange])
print([i for i in myRange])

class Zrange:
    def __init__(self, n):
          self.n = n
    def __iter__(self):
          return ZrangeIterator(self.n)
 
class ZrangeIterator:
    def __init__(self, n):
          self.i = 0
          self.n = n
    def __iter__(self):
          return self
    def __next__(self):
          if self.i < self.n:
               i = self.i
               self.i += 1
               return i
          else:
               raise StopIteration() 

執行如下程式碼:
zrange = Zrange(3)
print(zrange is iter(zrange)) 

print([i for i in zrange])
print([i for i in zrange])