【廖雪峰python進階筆記】定製類
1. __str__
和__repr__
如果要把一個類的例項變成 str,就需要實現特殊方法__str__()
:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __str__(self):
return '(Person: %s, %s)' % (self.name, self.gender)
現在,在互動式命令列下用 print 試試:
>>> p = Person('Bob', 'male')
>>> print p
(Person: Bob, male)
但是,如果直接敲變數 p:
>>> p
<main.Person object at 0x10c941890>
似乎str() 不會被呼叫。
因為 Python 定義了__str__()
和__repr__()
兩種方法,__str__()
用於顯示給使用者,而__repr__()
用於顯示給開發人員。
有一個偷懶的定義__repr__
的方法:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __str__(self):
return '(Person: %s, %s)' % (self.name, self.gender)
__repr__ = __str__
2. __cmp__
對 int、str 等內建資料型別排序時,Python的 sorted()
按照預設的比較函式 cmp
排序,但是,如果對一組 Student 類的例項排序
時,就必須提供我們自己的特殊方法__cmp__()
:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def __str__(self):
return '(%s: %s)' % (self.name, self.score)
__repr__ = __str__
def __cmp__(self, s):
if self.name < s.name:
return -1
elif self.name > s.name:
return 1
else:
return 0
上述 Student 類實現了__cmp__()
方法,__cmp__
用例項自身self和傳入的例項 s 進行比較,如果 self 應該排在前面,就返回 -1,如果 s 應該排在前面,就返回1,如果兩者相等,返回 0。
Student類實現了按name進行排序:
>>> L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 77)]
>>> print sorted(L)
[(Alice: 77), (Bob: 88), (Tim: 99)]
注意: 如果list不僅僅包含 Student 類,則 __cmp__
可能會報錯:
L = [Student('Tim', 99), Student('Bob', 88), 100, 'Hello']
print sorted(L)
請思考如何解決。
例項
請修改 Student 的 __cmp__
方法,讓它按照分數從高到底排序,分數相同的按名字排序。
分析
需要先比較 score,在 score 相等的情況下,再比較 name。
參考程式碼:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def __str__(self):
return '(%s: %s)' % (self.name, self.score)
__repr__ = __str__
def __cmp__(self, s):
if self.score > s.score:
return -1
elif self.score == s.score:
if self.name > s.name:
return 1
elif self.name == s.name:
return 0
else:
return -1
else:
return 1
L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 99)]
print sorted(L)
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def __str__(self):
return '(%s: %s)' % (self.name, self.score)
__repr__ = __str__
def __cmp__(self, s):
if self.score == s.score:
return cmp(self.name, s.name)
return -cmp(self.score, s.score)
L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 99)]
print sorted(L)
3. __len__
如果一個類表現得像一個list,要獲取有多少個元素,就得用 len()
函式。
要讓 len() 函式工作正常,類必須提供一個特殊方法__len__()
,它返回元素的個數。
例如,我們寫一個 Students 類,把名字傳進去:
class Students(object):
def __init__(self, *args):
self.names = args
def __len__(self):
return len(self.names)
只要正確實現了__len__()
方法,就可以用len()函式返回Students例項的“長度”:
>>> ss = Students('Bob', 'Alice', 'Tim')
>>> print len(ss)
3
例項
斐波那契數列是由 0, 1, 1, 2, 3, 5, 8…構成。
請編寫一個Fib類,Fib(10)表示數列的前10個元素,print Fib(10) 可以列印出數列的前 10 個元素,len(Fib(10))可以正確返回數列的個數10。
分析
需要根據num計算出斐波那契數列的前N個元素。
參考程式碼:
class Fib(object):
def __init__(self, num):
a, b, L = 0, 1, []
for n in range(num):
L.append(a)
a, b = b, a + b
self.numbers = L
def __str__(self):
return str(self.numbers)
__repr__ = __str__
def __len__(self):
return len(self.numbers)
f = Fib(10)
print f
print len(f)
4. 數學運算
Python 提供的基本資料型別 int、float 可以做整數和浮點的四則運算以及乘方等運算。
但是,四則運算
不侷限於int和float,還可以是有理數、矩陣等。
要表示有理數,可以用一個Rational類
來表示:
class Rational(object):
def __init__(self, p, q):
self.p = p
self.q = q
p、q 都是整數,表示有理數 p/q
。
如果要讓Rational進行+運算,需要正確實現__add__
:
class Rational(object):
def __init__(self, p, q):
self.p = p
self.q = q
def __add__(self, r):
return Rational(self.p * r.q + self.q * r.p, self.q * r.q)
def __str__(self):
return '%s/%s' % (self.p, self.q)
__repr__ = __str__
現在可以試試有理數加法:
>>> r1 = Rational(1, 3)
>>> r2 = Rational(1, 2)
>>> print r1 + r2
5/6
例項
Rational類雖然可以做加法,但無法做減法、乘方和除法,請繼續完善Rational類,實現四則運算。
提示:
減法運算:__sub__
乘法運算:__mul__
除法運算:__div__
如果運算結果是 6/8,在顯示的時候需要歸約到最簡形式3/4。
參考程式碼:
def gcd(a, b):
if b == 0:
return a
return gcd(b, a % b)
class Rational(object):
def __init__(self, p, q):
self.p = p
self.q = q
def __add__(self, r):
return Rational(self.p * r.q + self.q * r.p, self.q * r.q)
def __sub__(self, r):
return Rational(self.p * r.q - self.q * r.p, self.q * r.q)
def __mul__(self, r):
return Rational(self.p * r.p, self.q * r.q)
def __div__(self, r):
return Rational(self.p * r.q, self.q * r.p)
def __str__(self):
g = gcd(self.p, self.q)
return '%s/%s' % (self.p / g, self.q / g)
__repr__ = __str__
r1 = Rational(1, 2)
r2 = Rational(1, 4)
print r1 + r2
print r1 - r2
print r1 * r2
print r1 / r2
5. 型別轉換
Rational類實現了有理數運算,但是,如果要把結果轉為 int 或 float 怎麼辦?
考察整數和浮點數的轉換:
>>> int(12.34)
12
>>> float(12)
12.0
如果要把 Rational 轉為 int,應該使用:
r = Rational(12, 5)
n = int(r)
要讓int()函式正常工作,只需要實現特殊方法__int__()
:
class Rational(object):
def __init__(self, p, q):
self.p = p
self.q = q
def __int__(self):
return self.p // self.q
結果如下:
>>> print int(Rational(7, 2))
3
>>> print int(Rational(1, 3))
0
同理,要讓float()函式正常工作,只需要實現特殊方法__float__()
。
def __float__(self):
return float(self.p) / self.q
#或者
def __float__(self):
return 1.0 * self.p / self.q
6. @property
考察 Student 類:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
當我們想要修改一個 Student 的 scroe 屬性時,可以這麼寫:
s = Student('Bob', 59)
s.score = 60
但是也可以這麼寫:
s.score = 1000
顯然,直接給屬性賦值無法檢查分數的有效性。
如果利用兩個方法:
class Student(object):
def __init__(self, name, score):
self.name = name
self.__score = score
def get_score(self):
return self.__score
def set_score(self, score):
if score < 0 or score > 100:
raise ValueError('invalid score')
self.__score = score
這樣一來,s.set_score(1000) 就會報錯。
這種使用get/set
方法來封裝對一個屬性的訪問在許多物件導向程式設計的語言中都很常見。
但是寫 s.get_score() 和 s.set_score() 沒有直接寫 s.score 來得直接。
有沒有兩全其美的方法?—-有。
因為Python支援高階函式,在函數語言程式設計中我們介紹了裝飾器函式
,可以用裝飾器函式把 get/set 方法“裝飾”成屬性呼叫:
class Student(object):
def __init__(self, name, score):
self.name = name
self.__score = score
@property
def score(self):
return self.__score
@score.setter
def score(self, score):
if score < 0 or score > 100:
raise ValueError('invalid score')
self.__score = score
注意: 第一個score(self)是get方法,用@property裝飾,第二個score(self, score)是set方法,用@score.setter裝飾,@score.setter是前一個@property裝飾後的副產品。
現在,就可以像使用屬性一樣設定score了:
>>> s = Student('Bob', 59)
>>> s.score = 60
>>> print s.score
60
>>> s.score = 1000
Traceback (most recent call last):
...
ValueError: invalid score
說明對 score 賦值實際呼叫的是 set方法。
例項
如果沒有定義set方法,就不能對“屬性”賦值,這時,就可以建立一個只讀“屬性”。
請給Student類加一個grade屬性,根據 score 計算 A(>=80)、B、C(<60)。
分析
用 @property 修飾 grade 的 get 方法即可實現只讀屬性。
參考程式碼:
class Student(object):
def __init__(self, name, score):
self.name = name
self.__score = score
@property
def score(self):
return self.__score
@score.setter
def score(self, score):
if score < 0 or score > 100:
raise ValueError('invalid score')
self.__score = score
@property
def grade(self):
if self.score < 60:
#self.__score < 60 結果相同
return 'C'
if self.score < 80:
return 'B'
return 'A'
s = Student('Bob', 59)
print s.grade
s.score = 60
print s.grade
s.score = 99
print s.grade
7. __slots__
由於Python是動態語言,任何例項在執行期都可以動態地新增屬性。
如果要限制
新增的屬性,例如,Student類只允許新增 name、gender和score 這3個屬性,就可以利用Python的一個特殊的__slots__
來實現。
顧名思義,__slots__
是指一個類允許的屬性列表:
class Student(object):
__slots__ = ('name', 'gender', 'score')
def __init__(self, name, gender, score):
self.name = name
self.gender = gender
self.score = score
現在,對例項進行操作:
>>> s = Student('Bob', 'male', 59)
>>> s.name = 'Tim' # OK
>>> s.score = 99 # OK
>>> s.grade = 'A'
Traceback (most recent call last):
...
AttributeError: 'Student' object has no attribute 'grade'
__slots__
的目的是限制當前類所能擁有的屬性,如果不需要新增任意動態的屬性,使用__slots__
也能節省記憶體。
8. __call__
在Python中,函式其實是一個物件:
>>> f = abs
>>> f.__name__
'abs'
>>> f(-123)
123
由於 f 可以被呼叫,所以,f 被稱為可呼叫物件。
所有的函式都是可呼叫物件。
一個類例項也可以變成一個可呼叫物件
,只需要實現一個特殊方法__call__()
。
我們把 Person 類變成一個可呼叫物件:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __call__(self, friend):
print 'My name is %s...' % self.name
print 'My friend is %s...' % friend
現在可以對 Person 例項直接呼叫:
>>> p = Person('Bob', 'male')
>>> p('Tim')
My name is Bob...
My friend is Tim...
單看 p(‘Tim’) 你無法確定 p 是一個函式還是一個類例項,所以,在Python中,函式也是物件,物件和函式的區別並不顯著。
相關文章
- 【廖雪峰python進階筆記】類的繼承Python筆記繼承
- 【廖雪峰python進階筆記】模組Python筆記
- 【廖雪峰python進階筆記】物件導向程式設計Python筆記物件程式設計
- 【廖雪峰python進階筆記】函數語言程式設計Python筆記函數程式設計
- 【廖雪峰python入門筆記】dictPython筆記
- 【廖雪峰python入門筆記】setPython筆記
- 【廖雪峰python入門筆記】切片Python筆記
- 【廖雪峰python入門筆記】迭代Python筆記
- 【廖雪峰python入門筆記】函式Python筆記函式
- 【廖雪峰python入門筆記】變數Python筆記變數
- 【廖雪峰python入門筆記】if語句Python筆記
- 【廖雪峰python入門筆記】for迴圈Python筆記
- 【廖雪峰python入門筆記】列表生成式Python筆記
- 【廖雪峰python入門筆記】list_建立Python筆記
- 【廖雪峰python入門筆記】tuple_建立Python筆記
- 【廖雪峰python入門筆記】while迴圈Python筆記While
- 【廖雪峰python入門筆記】break和continuePython筆記
- 【廖雪峰python入門筆記】多重迴圈Python筆記
- 20190228 學習筆記——廖雪峰 git筆記Git
- 【廖雪峰python入門筆記】list刪除元素_pop()Python筆記
- 【廖雪峰python入門筆記】list_替換元素Python筆記
- 【廖雪峰python入門筆記】tuple_“元素可變”Python筆記
- 【廖雪峰python入門筆記】tuple_建立單元素Python筆記
- 【廖雪峰python入門筆記】字串_轉義字元的使用Python筆記字串字元
- 【廖雪峰python入門筆記】raw 字串和多行字串表示Python筆記字串
- 【廖雪峰python入門筆記】Unicode編碼_UnicodeDecodeError處理Python筆記UnicodeError
- 【廖雪峰python入門筆記】整數和浮點數Python筆記
- 【廖雪峰python入門筆記】list_按照索引訪問Python筆記索引
- 【廖雪峰python入門筆記】list_倒序訪問Python筆記
- 【廖雪峰python入門筆記】list新增元素_append()和insert()Python筆記APP
- 【廖雪峰python入門筆記】布林運算和短路計算Python筆記
- 跟著廖雪峰學python 005Python
- 廖雪峰Git學習筆記1-Git簡介Git筆記
- python進階學習筆記(一)Python筆記
- 進階筆記筆記
- flaskr 進階筆記Flask筆記
- Arthas進階-筆記筆記
- Python大牛廖雪峰13個案例帶你全面掌握商業爬蟲!Python爬蟲