1. 程式人生 > 實用技巧 >Python12月4日

Python12月4日

面向物件高階程式設計

使用__slots__

如果我們想要限制例項的屬性怎麼辦?比如,只允許對Student例項新增nameage屬性。

為了達到限制的目的,Python允許在定義class的時候,定義一個特殊的__slots__變數,來限制該class例項能新增的屬性

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定義允許繫結的屬性名稱

然後,我們試試:

>>> s = Student() # 建立新的例項
>>> s.name = 'Michael' # 繫結屬性'name'
>>> s.age = 25 # 繫結屬性'age'
>>> s.score = 99 # 繫結屬性'score'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

由於'score'沒有被放到__slots__中,所以不能繫結score屬性,試圖繫結score將得到AttributeError的錯誤。

使用__slots__要注意,__slots__定義的屬性僅對當前類例項起作用,對繼承的子類是不起作用的:

>>> class GraduateStudent(Student):
...     pass
...
>>> g = GraduateStudent()
>>> g.score = 9999

除非在子類中也定義__slots__,這樣,子類例項允許定義的屬性就是自身的__slots__加上父類的__slots__

使用@property

@property的實現比較複雜,我們先考察如何使用。把一個getter方法變成屬性,只需要加上@property就可以了,此時,@property本身又建立了另一個裝飾器@score.setter,負責把一個setter方法變成屬性賦值,於是,我們就擁有一個可控的屬性操作:

>>> s = Student()
>>> s.score = 60 # OK,實際轉化為s.set_score(60)
>>> s.score # OK,實際轉化為s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
  ...
ValueError: score must between 0 ~ 100!

注意到這個神奇的@property,我們在對例項屬性操作的時候,就知道該屬性很可能不是直接暴露的,而是通過getter和setter方法來實現的。

還可以定義只讀屬性,只定義getter方法,不定義setter方法就是一個只讀屬性:

class Student(object):

    @property
    def birth(self):
        return self._birth

    @birth.setter
    def birth(self, value):
        self._birth = value

    @property
    def age(self):
        return 2015 - self._birth

上面的birth是可讀寫屬性,而age就是一個只讀屬性,因為age可以根據birth和當前時間計算出來。

多重繼承

繼承是面向物件程式設計的一個重要的方式,因為通過繼承,子類就可以擴充套件父類的功能。若需要多層的繼承,則可以通過多重繼承的方法,通過多重繼承,一個子類就可以同時獲得多個父類的所有功能。

MixIn

在設計類的繼承關係時,通常,主線都是單一繼承下來的,例如,Ostrich繼承自Bird。但是,如果需要“混入”額外的功能,通過多重繼承就可以實現,比如,讓Ostrich除了繼承自Bird外,再同時繼承Runnable。這種設計通常稱之為MixIn。

為了更好地看出繼承關係,我們把RunnableFlyable改為RunnableMixInFlyableMixIn。類似的,你還可以定義出肉食動物CarnivorousMixIn和植食動物HerbivoresMixIn,讓某個動物同時擁有好幾個MixIn:

class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
    pass

MixIn的目的就是給一個類增加多個功能,這樣,在設計類的時候,我們優先考慮通過多重繼承來組合多個MixIn的功能,而不是設計多層次的複雜的繼承關係。

Python自帶的很多庫也使用了MixIn。舉個例子,Python自帶了TCPServerUDPServer這兩類網路服務,而要同時服務多個使用者就必須使用多程序或多執行緒模型,這兩種模型由ForkingMixInThreadingMixIn提供。通過組合,我們就可以創造出合適的服務來。

由於Python允許使用多重繼承,因此,MixIn就是一種常見的設計。

只允許單一繼承的語言(如Java)不能使用MixIn的設計。

定製類

看到類似__slots__這種形如__xxx__的變數或者函式名就要注意,這些在Python中是有特殊用途的。

__slots__我們已經知道怎麼用了,__len__()方法我們也知道是為了能讓class作用於len()函式。

除此之外,Python的class中還有許多這樣有特殊用途的函式,可以幫助我們定製類。

__str__

使用__str__打印出來的例項不但好看,還可以很清楚的看出例項內部重要的資料

只需要定義好__str__()方法,返回一個好看的字串就可以了:

>>> class Student(object):
...     def __init__(self, name):
...         self.name = name
...     def __str__(self):
...         return 'Student object (name: %s)' % self.name
...
>>> print(Student('Michael'))
Student object (name: Michael)

__iter__

如果一個類想被用於for ... in迴圈,類似list或tuple那樣,就必須實現一個__iter__()方法,該方法返回一個迭代物件,然後,Python的for迴圈就會不斷呼叫該迭代物件的__next__()方法拿到迴圈的下一個值,直到遇到StopIteration錯誤時退出迴圈。

我們以斐波那契數列為例,寫一個Fib類,可以作用於for迴圈:

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1 # 初始化兩個計數器a,b

    def __iter__(self):
        return self # 例項本身就是迭代物件,故返回自己

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b # 計算下一個值
        if self.a > 100000: # 退出迴圈的條件
            raise StopIteration()
        return self.a # 返回下一個值

現在,試試把Fib例項作用於for迴圈:

>>> for n in Fib():
...     print(n)
...
1
1
2
3
5
...
46368
75025

Python的class允許定義許多定製方法,可以讓我們非常方便地生成特定的類。

使用列舉類

Enum類的用法,定義類,繼承Eunm父類,就自動獲得了name和value
首先要先從enum模組中匯入Enum類,通過下面的語句:
from enum import Enum

type()用法:
def fn(self, name=‘world’): # 先定義函式
print(‘Hello, %s.’ % name)

Hello = type(‘Hello’, (object,), dict(hello=fn)) # 建立Hello class
class的名稱–Hello;
繼承的父類集合,注意Python支援多重繼承,如果只有一個父類,別忘了tuple的單元素寫法–object,;
class的方法名稱與函式繫結,這裡我們把函式fn繫結到方法名hello上–hello=fn。