slots —— 限制實例能添加的屬性
我們可以給該實例綁定任何屬性和方法
class Student(object):
pass
s = Student()
給實例綁定屬性
s.name = 'Michael'
print(s.name)
給實例綁定方法
from types import MethodType
def set_age(self, age):
self.age = age
s.set_age = MethodType(set_age, s)
s.set_age(25)
s.age
給class綁定方法东且,則對該類的所有實例都有該方法
def set_score(self, score):
self.score = score
Student.set_score = set_score
使用slots
試圖綁定不在允許列表里的屬性將得到 AttributeError
的錯誤
class Student(object):
# 用tuple定義允許綁定的屬性名稱
__slots__ = ('name', 'age')
__slots__
定義的屬性僅對當(dāng)前類實例起作用义钉,對繼承的子類是不起作用的
如果在子類中也定義 __slots__
,子類實例允許定義的屬性就是自身的 __slots__
加上父類的 __slots__
@property —— 把一個方法變成屬性調(diào)用
class Student(object):
@property
def score(self):
return self._score
# @property本身又創(chuàng)建了另一個裝飾器@score.setter典唇,負(fù)責(zé)把一個setter方法變成屬性賦值
@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方法,不定義setter方法就是一個只讀屬性:
class Student(object):
# birth 是可讀寫屬性
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
self._birth = value
# age 是只讀屬性
@property
def age(self):
return 2015 - self._birth
多重繼承
在設(shè)計類的繼承關(guān)系時胯府,通常介衔,主線都是單一繼承下來的,例如骂因,Ostrich繼承自Bird炎咖。但是,如果需要“混入”額外的功能寒波,通過多重繼承就可以實現(xiàn)乘盼,比如,讓Ostrich除了繼承自Bird外影所,再同時繼承Runnable蹦肴。這種設(shè)計通常稱之為MixIn。
為了更好地看出繼承關(guān)系猴娩,我們把Runnable和Flyable改為RunnableMixIn和FlyableMixIn
定制類
str() & repr() —— 打印實例的信息
__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)
__repr__()
返回程序開發(fā)者看到的字符串阴幌,即 __repr__()
是為調(diào)試服務(wù)的
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name: %s)' % self.name
# 通常 __str__() 和 __repr__() 代碼都是一樣的,所以卷中,用偷懶的寫法:
__repr__ = __str__
iter() & next() —— 使一個對象可迭代
__iter__()
方法矛双,該方法返回一個迭代對象,然后蟆豫,Python的for循環(huán)就會不斷調(diào)用該迭代對象的 __next__()
方法拿到循環(huán)的下一個值
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1
def __iter__(self):
# 返回實例本身议忽,因為實例實現(xiàn)了 __next__() 方法,所以是個可迭代對象
return self
def __next__(self):
self.a, self.b = self.b, self.a + self.b
if self.a > 100000:
raise StopIteration()
return self.a
getitem() —— 使一個對象能像list那樣按照下標(biāo)取出元素
class Fib(object):
def __getitem__(self, n):
# n是索引
if isinstance(n, int):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
# n是切片
# 這里沒有對step參數(shù)作處理十减,也沒有對負(fù)數(shù)作處理
if isinstance(n, slice):
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[3]
print(f[0:5])
print(f[:10])
如果把對象看成 dict 栈幸,__getitem__()
的參數(shù)也可能是一個可以作 key 的 object ,例如str
與之對應(yīng)的是 __setitem__()
方法帮辟,把對象視作list或dict來對集合賦值
還有一個 __delitem__()
方法速址,用于刪除某個元素
getattr() 動態(tài)返回一個屬性
當(dāng)調(diào)用不存在的屬性時,比如score由驹,Python解釋器會試圖調(diào)用getattr(self, 'score')來嘗試獲得屬性芍锚,這樣,我們就有機(jī)會返回score的值
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
# __getattr__默認(rèn)返回就是None
# 要讓class只響應(yīng)特定的幾個屬性,我們就要按照約定并炮,拋出AttributeError的錯誤
raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
call —— 調(diào)用實例本身
對實例進(jìn)行直接調(diào)用就好比對一個函數(shù)進(jìn)行調(diào)用一樣默刚,所以你完全可以把對象看成函數(shù),把函數(shù)看成對象逃魄,因為這兩者之間本來就沒啥根本的區(qū)別荤西。
class Student(object):
def __init__(self, name):
self.name = name
# __call__()還可以定義參數(shù)
def __call__(self):
print('My name is %s.' % self.name)
s = Student('Michael')
s() ==> My name is Michael.
判斷一個對象是否能被調(diào)用,能被調(diào)用的對象就是一個Callable對象嗅钻,比如函數(shù)和我們上面定義的帶有call()的類實例
callable(Student()) ==> True
callable(max) ==> True
callable([1, 2, 3]) ==> False
callable(None) ==> False
callable('str') ==> False
其他特殊的方法名見:special-method-names
枚舉類
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
這樣我們就獲得了Month類型的枚舉類皂冰,可以直接使用Month.Jan來引用一個常量,或者枚舉它的所有成員
for name, member in Month.__members__.items():
# value屬性則是自動賦給成員的int常量养篓,默認(rèn)從1開始計數(shù)
print(name, '=>', member, ',', member.value)
如果需要更精確地控制枚舉類型秃流,可以從Enum派生出自定義類:
from enum import Enum, unique
# @unique裝飾器可以幫助我們檢查保證沒有重復(fù)值
@unique
class Weekday(Enum):
Sun = 0 # Sun的value被設(shè)定為0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
訪問這些枚舉類型的方法:
print(Weekday.Tue)
day1 = Weekday.Tue
print(day1)
print(Weekday['Tue'])
print(Weekday(2))
print(Weekday.Tue.value) ==> 2
使用元類
type() —— 動態(tài)創(chuàng)建類
假設(shè)有一 hello.py
模塊
class Hello(object):
def hello(self, name='world'):
print('Hello, %s.' % name)
from hello import Hello
h = Hello()
h.hello() ==> Hello, world.
# Hello是一個class,它的類型就是type
print(type(Hello)) ==> <class 'type'>
# h是一個實例柳弄,它的類型就是class Hello
print(type(h)) ==> <class 'hello.Hello'>
type()
函數(shù)既可以返回一個對象的類型舶胀,又可以創(chuàng)建出新的類型
比如,我們可以通過type()函數(shù)創(chuàng)建出Hello類碧注,而無需通過class Hello(object)...的定義:
# 先定義函數(shù)
def fn(self, name='world'):
print('Hello, %s.' % name)
# 參數(shù):
# 1. class的名稱
# 2. 繼承的父類集合嚣伐,注意Python支持多重繼承,如果只有一個父類萍丐,別忘了tuple的單元素寫法
# 3. class的方法名稱與函數(shù)綁定轩端,這里我們把函數(shù)fn綁定到方法名hello上
Hello = type('Hello', (object,), dict(hello=fn)) # 創(chuàng)建Hello class
h = Hello()
h.hello() ==> Hello, world.
print(type(Hello)) ==> <class 'type'>
print(type(h)) ==> <class '__main__.Hello'>
type()
函數(shù)也允許我們動態(tài)創(chuàng)建出類來,也就是說逝变,動態(tài)語言本身支持運行期動態(tài)創(chuàng)建類
metaclass —— 控制類的創(chuàng)建行為(缺)
先定義metaclass基茵,就可以創(chuàng)建類,最后創(chuàng)建實例
所以壳影,metaclass允許你創(chuàng)建類或者修改類拱层。換句話說,你可以把類看成是metaclass創(chuàng)建出來的“實例”
一個簡單的例子:
# 這個metaclass可以給我們自定義的MyList增加一個add方法
# metaclass是類的模板宴咧,所以必須從`type`類型派生:
class ListMetaclass(type):
# __new__()方法參數(shù)
# 1. 當(dāng)前準(zhǔn)備創(chuàng)建的類的對象
# 2. 類的名字
# 3. 類繼承的父類集合
# 4. 類的方法集合
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
# 指示Python解釋器在創(chuàng)建MyList時根灯,要通過ListMetaclass.__new__()來創(chuàng)建
class MyList(list, metaclass=ListMetaclass):
pass
動態(tài)修改的意義在于:總會遇到需要通過metaclass修改類定義的