__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__
__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)
__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
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)
數學運算:
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__
@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: 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
__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__也能節省內存。
__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中,函數也是對象,對象和函數的區別並不顯著。
class Fib(object):
def __call__(self,num):
a, b ,L = 0, 1, []
for n in range(num):
L.append(a)
a,b=b,a+b
return L
f = Fib()
print f(10)