本文我們繼續(xù)介紹一些Python面向?qū)ο缶幊讨械母呒?jí)用法绅你,依然參考廖雪峰老師的Python教程。
1交煞、使用__slots__
正常情況下,當(dāng)我們定義了一個(gè)class,創(chuàng)建了一個(gè)class的實(shí)例后,我們可以給該實(shí)例綁定任何屬性和方法剪返,這就是動(dòng)態(tài)語言的靈活性,看下面的例子:
class Animal():
pass
ani = Animal()
ani.name = 'hasky'
from types import MethodType
def set_age(self,age):
self.age = age
ani.set_age = MethodType(set_age,ani)
ani.set_age(20)
print(ani.age) #20
可以看到邓梅,我們可以直接通過賦值的方式給實(shí)例綁定屬性脱盲,同時(shí)可以通過MethodType方法給實(shí)例綁定方法。值得注意的是日缨,給一個(gè)實(shí)例綁定的屬性和方法钱反,對(duì)另一個(gè)實(shí)例是不起作用的。都會(huì)報(bào)AttributeError。
ani2 = Animal()
print(ani2.name) # AttributeError: 'Animal' object has no attribute 'name'
ani2.set_age(20) # AttributeError: 'Animal' object has no attribute 'set_age'
為了給所有實(shí)例都綁定方法面哥,那么我們給class綁定方法:
Animal.set_age = set_age
ani2.set_age(20)
print(ani2.age) # 20
但是哎壳,如果我們想要限制實(shí)例的屬性怎么辦?比如尚卫,只允許對(duì)Student實(shí)例添加name和age屬性归榕。
為了達(dá)到限制的目的,Python允許在定義class的時(shí)候焕毫,定義一個(gè)特殊的__slots__變量蹲坷,來限制該class實(shí)例能添加的屬性:
class Animal():
__slots__ = ['name','weight']
ani = Animal()
ani.name = 'hasky'
ani.weight = 90
ani.height = 20 # AttributeError: 'Animal' object has no attribute 'height'
可以看到,由于height沒有在__slots__中邑飒,所以無法給實(shí)例綁定該屬性循签,報(bào) AttributeError。
使用__slots__要注意疙咸,__slots__定義的屬性僅對(duì)當(dāng)前類實(shí)例起作用县匠,對(duì)繼承的子類是不起作用的:
class Dog(Animal):
pass
dog = Dog()
dog.height = 20
print(dog.height) # 20
2、使用@property
在綁定屬性時(shí)撒轮,如果我們直接把屬性暴露出去乞旦,雖然寫起來很簡單,但是题山,沒辦法檢查參數(shù)兰粉,導(dǎo)致可以把屬性值隨便改,為了避免這樣的情況顶瞳,我們可以使用getter和setter來限制對(duì)屬性值的修改玖姑,比如我們將動(dòng)物的體重限制1到200之間的整數(shù)。
class Animal(object):
def get_weight(self):
return self.weight
def set_weight(self, value):
if not isinstance(value, int):
raise ValueError('weight must be an integer!')
if value < 1 or value > 200:
raise ValueError('weight must between 1 ~ 200!')
self.weight = value
ani = Animal()
ani.set_weight(60)
print(ani.get_weight()) # 60
ani.set_weight(999) # ValueError: weight must between 1 ~ 200!
但是上面的調(diào)用方法太復(fù)雜慨菱,沒有直接用屬性這么簡單焰络,我們可以使用Python內(nèi)置的@property裝飾器來把一個(gè)方法變成屬性調(diào)用:
class Animal(object):
@property
def weight(self):
return self._weight
@weight.setter
def weight(self, value):
if not isinstance(value, int):
raise ValueError('weight must be an integer!')
if value < 1 or value > 200:
raise ValueError('weight must between 1 ~ 200!')
self._weight = value
ani = Animal()
ani.weight = 60
print(ani.weight) # 60
ani.weight = 999 # ValueError: weight must between 1 ~ 200!
上面的代碼中,注意的一點(diǎn)是符喝,由于我們的方法名已經(jīng)是weight了
闪彼,所以屬性的名稱不能再是weight,我們使用的是_weight协饲。把一個(gè)getter方法變成屬性畏腕,只需要加上@property就可以了,此時(shí)茉稠,@property本身又創(chuàng)建了另一個(gè)裝飾器@weight.setter描馅,負(fù)責(zé)把一個(gè)setter方法變成屬性賦值,于是战惊,我們就擁有一個(gè)可控的屬性操作。
還可以定義只讀屬性,只定義getter方法吞获,不定義setter方法就是一個(gè)只讀屬性:
class Animal(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
if not isinstance(value, int):
raise ValueError('birth must be an integer!')
if value < 1 or value > 2018:
raise ValueError('birth must between 1 ~ 2018!')
self._birth = value
@property
def age(self):
return 2018 - self._birth
ani = Animal()
ani.birth = 1958
print(ani.age) # 60
3况凉、定制類
看到形如__xxx__的變量或者函數(shù)名就要注意,這些在Python中是有特殊用途的各拷。比如我們剛剛介紹的__slots__用于限制實(shí)例綁定屬性刁绒。除此之外,Python的class中還有許多這樣有特殊用途的函數(shù)烤黍,可以幫助我們定制類知市。
3.1 __str__
對(duì)于一個(gè)實(shí)例,我們直接打印的話速蕊,往往是下面的結(jié)果:
print(ani) # <__main__.Animal object at 0x104fccd68>
我們?nèi)绾伟汛蛴〗Y(jié)果變的好看一些呢嫂丙?__str__就派上用場(chǎng)了。
class Animal(object):
def __init__(self,name):
self.name = name
def __str__(self):
return 'Animal object with name : %s' % self.name
print(Animal('tt')) # Animal object with name : tt
a = Animal('tt')
a # <__main__.Animal at 0x104e8ecf8>
可是规哲,如果我們不用print而是直接敲變量跟啤,打印出來的結(jié)果還是不好看,這是因?yàn)橹苯语@示變量調(diào)用的不是__str__()唉锌,而是__repr__()隅肥,兩者的區(qū)別是__str__()返回用戶看到的字符串,而__repr__()返回程序開發(fā)者看到的字符串袄简,也就是說腥放,__repr__()是為調(diào)試服務(wù)的。我們只需要在類中增加一行__repr__ =__str__就可以讓直接敲變量打印出來的結(jié)果好看一些绿语。
3.2 __iter__
如果一個(gè)類想被用于for ... in循環(huán)秃症,類似list或tuple那樣,就必須實(shí)現(xiàn)一個(gè)__iter__()方法汞舱,該方法返回一個(gè)迭代對(duì)象伍纫,然后,Python的for循環(huán)就會(huì)不斷調(diào)用該迭代對(duì)象的__next__()方法拿到循環(huán)的下一個(gè)值昂芜,直到遇到StopIteration錯(cuò)誤時(shí)退出循環(huán)莹规。
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化兩個(gè)計(jì)數(shù)器a,b
def __iter__(self):
return self # 實(shí)例本身就是迭代對(duì)象泌神,故返回自己
def __next__(self):
self.a, self.b = self.b, self.a + self.b # 計(jì)算下一個(gè)值
if self.a > 10: # 退出循環(huán)的條件
raise StopIteration()
return self.a # 返回下一個(gè)值
for n in Fib():
print(n)
這樣良漱,我們的輸出為:
3.3 __getitem__
使用__iter__()和__next__()雖然可以讓實(shí)例進(jìn)行循環(huán),但是并不能當(dāng)作一個(gè)list來使用欢际,比如想要獲取其中的某個(gè)元素或者進(jìn)行切片操作母市。要表現(xiàn)得像list那樣按照下標(biāo)取出元素,需要實(shí)現(xiàn)__getitem__()方法损趋。同時(shí)患久,__getitem__()傳入的參數(shù)可能是一個(gè)int,也可能是一個(gè)切片對(duì)象slice,可以獲得一個(gè)指定的元素或者一堆連續(xù)的元素:
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()
print(f[0:5]) # [1, 1, 2, 3, 5]
print(f[10]) # 89
3.4 __getattr__
正常情況下蒋失,當(dāng)我們調(diào)用類的方法或?qū)傩詴r(shí)返帕,如果不存在,就會(huì)報(bào)錯(cuò)篙挽。比如我們?cè)L問一個(gè)不存在的height屬性荆萤,就會(huì)報(bào)AttributeError。解決這個(gè)問題的方法一個(gè)是在類中加入一個(gè)height屬性铣卡,另一個(gè)就是寫一個(gè)__getattr__()方法链韭,動(dòng)態(tài)返回一個(gè)屬性:
class Animal():
def __init__(self,name,weight):
self.name = name
self.weight = weight
def __getattr__(self,attr):
if attr == 'height':
return 20
a = Animal("hasky",90)
print(a.height) # 20
上面的代碼中,當(dāng)調(diào)用不存在的屬性時(shí)height時(shí)煮落,Python解釋器會(huì)試圖調(diào)用__getattr__(self, 'height')來嘗試獲得屬性敞峭,這樣,我們就有機(jī)會(huì)返回height的值州邢。除了返回屬性外儡陨,返回函數(shù)也是可以的:
class Animal():
def __init__(self,name,weight):
self.name = name
self.weight = weight
def __getattr__(self,attr):
if attr == 'height':
return 20
elif attr == 'age':
return lambda: 25
a = Animal("hasky",90)
print(a.height) # 20
print(a.age()) # 25
注意,只有在沒有找到屬性的情況下量淌,才調(diào)用__getattr__骗村,已有的屬性,比如name呀枢,不會(huì)在__getattr__中查找胚股。同時(shí),如果定義了__getattr__方法裙秋,那么對(duì)于不存在的屬性琅拌,不會(huì)報(bào)錯(cuò),而是會(huì)統(tǒng)一返回None:
class Animal():
def __init__(self,name,weight):
self.name = name
self.weight = weight
def __getattr__(self,attr):
if attr == 'height':
return 20
elif attr == 'age':
return lambda: 25
a = Animal("hasky",90)
print(a.abc) # None
為了摘刑,讓程序拋出AttributeError的錯(cuò)誤进宝,我們需要對(duì)__getattr__()進(jìn)行修改:
class Animal():
def __init__(self,name,weight):
self.name = name
self.weight = weight
def __getattr__(self,attr):
if attr == 'height':
return 20
elif attr == 'age':
return lambda: 25
raise AttributeError('Animal object has no attribute %s' % attr)
a = Animal("hasky",90)
print(a.abc) # AttributeError: Animal object has no attribute abc