1. 程式人生 > >重拾Python學習(六)----------面向物件高階程式設計

重拾Python學習(六)----------面向物件高階程式設計

本文參考:廖雪峰的官方網站:https://www.liaoxuefeng.com

使用__slots__

果我們想要限制例項的屬性,比如,只允許對Student例項新增name和age屬性。

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

定義一個特殊的__slots__變數,來限制該class例項能新增的屬性:

>>> 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'

對繼承的子類是不起作用的:

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

使用@property

裝飾器(decorator)可以給函式動態加上功能,對於類的方法,裝飾器一樣起作用。Python內建的@property裝飾器就是負責把一個方法變成屬性呼叫的:

class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if
not isinstance(value, int): raise ValueError('score must be an integer!') if value < 0 or value > 100: raise ValueError('score must between 0 ~ 100!') self._score = value

把一個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!

多重繼承

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

class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
    pass

舉個例子,Python自帶了TCPServerUDPServer這兩類網路服務,而要同時服務多個使用者就必須使用多程序或多執行緒模型,這兩種模型由ForkingMixInThreadingMixIn提供

class MyTCPServer(TCPServer, ForkingMixIn):
    pass
class MyUDPServer(UDPServer, ThreadingMixIn):
    pass

定製類

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

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

__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)

【注】不用print,打印出來的例項:

>>> s = Student('Michael')
>>> s
<__main__.Student object at 0x109afb310>

這是因為直接顯示變數呼叫的不是__str__(),而是__repr__(),兩者的區別是__str__()返回使用者看到的字串,而__repr__()返回程式開發者看到的字串,也就是說,__repr__()是為除錯服務的。(解決: __repr__ = __str__

__iter__

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

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 # 返回下一個值
>>> for n in Fib():
...     print(n)
...
1
1
2
3
5
...
46368
75025
__getitem__

要表現得像list那樣按照下標取出元素,需要實現__getitem__()方法:

class Fib(object):
    def __getitem__(self, n):
        a, b = 1, 1
        for x in range(n):
            a, b = b, a + b
        return a
>>> f = Fib()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
>>> f[3]
3
>>> f[10]
89
>>> f[100]
573147844013817084101

新增切片方法:

class Fib(object):
    def __getitem__(self, n):
        if isinstance(n, int): # n是索引
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice): # n是切片
            start = n.start
            stop = n.stop
            if start is None:
                start = 0
            a, b = 1, 1
            L = []
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a + b
            return L
>>> f = Fib()
>>> f[0:5]
[1, 1, 2, 3, 5]
>>> f[:10]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

沒有對step引數作處理,也沒有對負數作處理,所以,要正確實現一個__getitem__()還是有很多工作要做的

__getattr__

正常情況下,當我們呼叫類的方法或屬性時,如果不存在,就會報錯,要避免這個錯誤,除了可以加上這個屬性外,寫一個__getattr__()方法,動態返回一個屬性

class Student(object):

    def __init__(self):
        self.name = 'Michael'

    def __getattr__(self, attr):
        if attr=='score':
            return 99

當呼叫不存在的屬性時,比如score,Python直譯器會試圖呼叫__getattr__(self, 'score')來嘗試獲得屬性

>>> s = Student()
>>> s.name
'Michael'
>>> s.score
99

只有在沒有找到屬性的情況下,才呼叫__getattr__,已有的屬性,比如name,不會在__getattr__中查詢。

這實際上可以把一個類的所有屬性和方法呼叫全部動態化處理了,不需要任何特殊手段。這種完全動態呼叫的特性有什麼實際作用呢?作用就是,可以針對完全動態的情況作呼叫。

利用完全動態的__getattr__,我們可以寫出一個鏈式呼叫:

class Chain(object):

    def __init__(self, path=''):
        self._path = path

    def __getattr__(self, path):
        return Chain('%s/%s' % (self._path, path))

    def __str__(self):
        return self._path

    __repr__ = __str__
>>> Chain().status.user.timeline.list
'/status/user/timeline/list'

如果要寫SDK,給每個URL對應的API都寫一個方法,那得累死,而且,API一旦改動,SDK也要改。這樣,無論API怎麼變,SDK都可以根據URL實現完全動態的呼叫,而且,不隨API的增加而改變!

__call__

當我們呼叫例項方法時,我們用instance.method()來呼叫,只需要定義一個__call__()方法,就可以直接對例項進行呼叫

class Student(object):
    def __init__(self, name):
        self.name = name

    def __call__(self):
        print('My name is %s.' % self.name)

呼叫方式如下:

>>> s = Student('Michael')
>>> s() # self引數不要傳入
My name is Michael.

使用列舉類

Python提供了Enum類來實現每個常量都是class的一個唯一例項

from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

更精確地控制列舉型別,可以從Enum派生出自定義類:

from enum import Enum, unique

@unique
class Weekday(Enum):
    Sun = 0 # Sun的value被設定為0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

@unique裝飾器可以幫助我們檢查保證沒有重複值。

>>> day1 = Weekday.Mon
>>> print(day1)
Weekday.Mon
>>> print(Weekday.Tue)
Weekday.Tue
>>> print(Weekday['Tue'])
Weekday.Tue
>>> print(Weekday.Tue.value)
2
>>> print(day1 == Weekday.Mon)
True
>>> print(day1 == Weekday.Tue)
False
>>> print(Weekday(1))
Weekday.Mon
>>> print(day1 == Weekday(1))
True