python 第十課 物件導向 2

weixin_34194087發表於2016-09-09

1.使用__slots__

class Student(object): 
      pass
>>> s = Student()
>>> s.name = 'Michael' # 動態給例項繫結一個屬性
>>> print(s.name)
Michael

還可以嘗試給例項繫結一個方法:

>>> def set_age(self, age): # 定義一個函式作為例項方法
...     self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 給例項繫結一個方法
>>> s.set_age(25) # 呼叫例項方法
>>> s.age # 測試結果
25

但是,給一個例項繫結的方法,對另一個例項是不起作用的:

>>> s2 = Student() # 建立新的例項
>>> s2.set_age(25) # 嘗試呼叫方法
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'set_age'

為了給所有例項都繫結方法,可以給class繫結方法:

>>> def set_score(self, score):
...     self.score = score
...
>>> Student.set_score = set_score

給class繫結方法後,所有例項均可呼叫:

>>> s.set_score(100)
>>> s.score
100
>>> s2.set_score(99)
>>> s2.score
99

使用__slots__
但是,如果我們想要限制例項的屬性怎麼辦?比如,只允許對Student例項新增name和age屬性。

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'
 使用__slots__要注意,__slots__定義的屬性僅對當前類例項起作用,
對繼承的子類是不起作用的:
除非在子類中也定義__slots__,這樣,子類例項允許定義的屬性就是
自身的__slots__加上父類的__slots__。

2.使用@property

還可以定義只讀屬性,只定義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
# 利用@property給一個Screen物件加上width和height屬性,以及一個只讀屬性resolution
# width/height unit: px
class Screen(object):
    def __init__(self):
        self.__height = 0
        self.__width = 0

    @staticmethod
    def __check_param(value):
        if not isinstance(value, int):
            raise TypeError('Must a int type')
        if value <= 0:
            raise ValueError('Must great than zero')

    @property
    def width(self):
        return self.__width

    @width.setter
    def width(self, value):
        self.__check_param(value)
        self.__width = value

    @property
    def height(self):
        return self.__height

    @height.setter
    def height(self, value):
        self.__check_param(value)
        self.__height = value

    @property
    def resolution(self):
        return self.__width * self.__height

# test:
s = Screen()
s.width = 1024
s.height = 768
print(s.resolution)     # 786432
assert s.resolution == 786432, '1024 * 768 = %d ?' % s.resolution

4.多繼承

class Bat(Mammal, Flyable): 
        pass

5.定製類

看到類似slots這種形如xxx的變數或者函式名就要注意,這些在Python中是有特殊用途的。
slots我們已經知道怎麼用了,len()方法我們也知道是為了能讓class作用於len()函式。
除此之外,Python的class中還有許多這樣有特殊用途的函式,可以幫助我們定製類。

1)__str__
>>> class Student(object):
...     def __init__(self, name):
...         self.name = name
...
>>> print(Student('Michael'))
<__main__.Student object at 0x109afb190>

改變列印方式

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

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

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

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

2)__iter__

如果一個類想被用於for ... in迴圈,類似list或tuple那樣,就必須實現一個__iter__()方法,該方法返回一個迭代物件,直到遇到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 # 返回下一個值

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

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

3)__getitem__

Fib例項雖然能作用於for迴圈,看起來和list有點像,但是,把它當成list來使用還是不行,比如,取第5個元素:

>>> Fib()[5]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'Fib' object does not support indexing
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

但是list有個神奇的切片方法

>>> list(range(100))[5:10]
[5, 6, 7, 8, 9]

對於Fib卻報錯。原因是__getitem__()傳入的引數可能是一個int,也可能是一個切片物件slice,所以要做判斷:

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

與之對應的是__setitem__()方法,把物件視作list或dict來對集合賦值。最後,還有一個__delitem__()方法,用於刪除某個元素。

總之,通過上面的方法,我們自己定義的類表現得和Python自帶的list、tuple、dict沒什麼區別,這完全歸功於動態語言的“鴨子型別”,不需要強制繼承某個介面。

4)__getattr__

當我們呼叫類的方法或屬性時,如果不存在,就會報錯。

>>> s = Student()
>>> print(s.name)
Michael
>>> print(s.score)
Traceback (most recent call last):
  ...
AttributeError: 'Student' object has no attribute 'score'

Python還有另一個機制,那就是寫一個__getattr__()方法,動態返回一個屬性。修改如下:

class Student(object):

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

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

返回函式也是完全可以的:

class Student(object):

    def __getattr__(self, attr):
        if attr=='age':
            return lambda: 25

>>> s.age()
25

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

通過callable()函式,我們就可以判斷一個物件是否是“可呼叫”物件

>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('str')
False

6.使用列舉類

from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
//列印
for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value)

value屬性則是自動賦給成員的int常量,預設從1開始計數。
如果需要更精確地控制列舉型別,可以從Enum派生出自定義類:

from enum import Enum, unique
//@unique裝飾器可以幫助我們檢查保證沒有重複值。
@unique
class Weekday(Enum):
    Sun = 0 # Sun的value被設定為0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

訪問這些列舉型別可以有若干種方法:

>>> 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
>>> Weekday(7)
Traceback (most recent call last):
  ...
ValueError: 7 is not a valid Weekday
>>> for name, member in Weekday.__members__.items():
...     print(name, '=>', member)
...
Sun => Weekday.Sun
Mon => Weekday.Mon
Tue => Weekday.Tue
Wed => Weekday.Wed
Thu => Weekday.Thu
Fri => Weekday.Fri
Sat => Weekday.Sat

7.元類???

相關文章