由于歷史原因,Python 2.x 同時(shí)存在兩種類模型,算是個(gè)不大不小的坑。面向?qū)ο笏枷氲难葑円苍谟绊懼Z言的進(jìn)化,單根繼承在 Python 中對(duì)應(yīng)的是 New-Style Class,而非 Classic Class。
Python 3 終于甩掉包袱,僅保留 New-Style Class。所以呢,就算還在用 2.x 開發(fā),也別再折騰 Classic Class,踏踏實(shí)實(shí)從 object 繼承,或在源文件設(shè)置默認(rèn)元類。
>>> class User: pass
>>> type(User) # 2.x 默認(rèn)是 Classic Class。
<type 'classobj'>
>>> issubclass(User, object) # 顯然不是從 object 繼承。
False
>>> __metaclass__ = type # 指定默認(rèn)元類。
>>> class Manager: pass # 還是沒有顯式從 object 繼承。
>>> type(Manager) # 但已經(jīng)是 New-Style Class。
<type 'type'>
>>> issubclass(Manager, object) # 確定了!
True
本書所有內(nèi)容均使用 New-Style Class。
類型是類型,實(shí)例是實(shí)例。如同 def,關(guān)鍵字 class 的作用是創(chuàng)建類型對(duì)象。前面章節(jié)也曾提到過,類型對(duì)象很特殊,在整個(gè)進(jìn)程中是單例的,是不被回收的。
typedef struct
{
PyObject_HEAD
PyObject*cl_bases; /* A tuple of class objects */
PyObject*cl_dict; /* A dictionary */
PyObject*cl_name; /* A string */
PyObject*cl_getattr;
PyObject*cl_setattr;
PyObject*cl_delattr;
} PyClassObject;
因?yàn)?New-Style Class,Class 和 Type 總算是一回事了。
>>> class User(object): pass
>>> u = User()
>>> type(u)
<class '__main__.User'>
>>> u.__class__
<class '__main__.User'>
類型 (class) 存儲(chǔ)了所有的靜態(tài)字段和方法 (包括實(shí)例方法),而實(shí)例 (instance) 僅存儲(chǔ)實(shí)例字段,從基類 object 開始,所有繼承層次上的實(shí)例字段。官方文檔將所有成員統(tǒng)稱為 Attribute。
typedef struct
{
PyObject_HEAD
PyClassObject *in_class; /* The class object */
PyObject *in_dict; /* A dictionary */
PyObject *in_weakreflist; /* List of weak references */
} PyInstanceObject;
類型和實(shí)例各自擁有自己的名字空間。
>>> User.__dict__
<dictproxy object at 0x106eaa718>
>>> u.__dict__
{}
訪問對(duì)象成員時(shí),就從這幾個(gè)名字空間中查找,而非以往的 globals、locals。
成員查找順序: instance.__dict__ -> class.__dict__ -> baseclass.__dict__
注意分清對(duì)象成員和普通名字的差別。就算在對(duì)象方法中,普通名字依然遵循 LEGB 規(guī)則。
字段 (Field) 和 屬性 (Property) 是不同的。
>>> class User(object):
... table = "t_user"
... def __init__(self, name, age):
... self.name = name
... self.age = age
>>> u1 = User("user1", 20) # 實(shí)例字段存儲(chǔ)在 instance.__dict__。
>>> u1.__dict__
{'age': 20, 'name': 'user1'}
>>> u2 = User("user2", 30) # 每個(gè)實(shí)例的狀態(tài)都是相互隔離的。
>>> u2.__dict__
{'age': 30, 'name': 'user2'}
>>> for k, v in User.__dict__.items(): # 靜態(tài)字段存儲(chǔ)在 class.__dict__。
... print "{0:12} = {1}".format(k, v)
__module__ = __main__
__dict__ = <attribute '__dict__' of 'User' objects>
__init__ = <function __init__ at 0x106eb4398>
table = t_user
可以在任何時(shí)候添加實(shí)例字段,僅影響該實(shí)例名字空間,與其他同類型實(shí)例無關(guān)。
>>> u1.x = 100
>>> u1.__dict__
{'x': 100, 'age': 20, 'name': 'user1'}
>>> u2.__dict__
{'age': 30, 'name': 'user2'}
要訪問靜態(tài)字段,除了 class.
>>> User.table # 使用 class.<name> 查找靜態(tài)成員。
't_user'
>>> u1.table # 使用 instance.<name> 查找靜態(tài)成員。
't_user'
>>> u2.table # 靜態(tài)成員為所有實(shí)例對(duì)象共享。
't_user'
>>> u1.table = "xxx" # 在 instance.__dict__ 創(chuàng)建一個(gè)同名成員。
>>> u1.table # 這回按照查找順序,命中的就是實(shí)例成員了。
'xxx'
>>> u2.table # 當(dāng)然,這不會(huì)影響其他實(shí)例對(duì)象。
't_user'
面向?qū)ο笠粋€(gè)很重要的特征就是封裝,它隱藏對(duì)象內(nèi)部實(shí)現(xiàn)細(xì)節(jié),僅暴露用戶所需的接口。因此私有字段是極重要的,可避免非正常邏輯修改。
私有字段以雙下劃線開頭,無論是靜態(tài)還是實(shí)例成員,都會(huì)被重命名: _
>>> class User(object):
... __table = "t_user"
...
... def __init__(self, name, age):
... self.__name = name
... self.__age = age
...
... def __str__(self):
... return "{0}: {1}, {2}".format(
... self.__table, # 編碼時(shí)無需關(guān)心重命名。
... self.__name,
... self.__age)
>>> u = User("tom", 20)
>>> u.__dict__ # 可以看到私有實(shí)例字段被重命名了。
{'_User__name': 'tom', '_User__age': 20}
>>> str(u)
't_user: tom, 20'
>>> User.__dict__.keys() # 私有靜態(tài)字段也被重命名。
['_User__table', ...]
某些時(shí)候,我們既想使用私有字段,又不想放棄外部訪問權(quán)限。
不必過于糾結(jié) "權(quán)限" 這個(gè)詞,從底層來看,本就沒有私有一說。
屬性 (Property) 是由 getter、setter、deleter 幾個(gè)方法構(gòu)成的邏輯。屬性可能直接返回字段值,也可能是動(dòng)態(tài)邏輯運(yùn)算的結(jié)果。
屬性以裝飾器或描述符實(shí)現(xiàn),原理以后再說。實(shí)現(xiàn)規(guī)則很簡單,也很好理解。
>>> class User(object):
... @property
... def name(self): return self.__name # 注意幾個(gè)方法是同名的。
...
... @name.setter
... def name(self, value): self.__name = value
...
... @name.deleter
... def name(self): del self.__name
>>> u = User()
>>> u.name = "Tom"
>>> u.__dict__ # 從 instance.__dict__ 可以看出屬性和字段的差異。
{'_User__name': 'Tom'}
>>> u.name # instance.__dict__ 中并沒有 name,顯然是 getter 起作用了。
'Tom'
>>> del u.name # 好吧,這是 deleter。
>>> u.__dict__
{}
>>> for k, v in User.__dict__.items():
... print "{0:12} = {1}".format(k, v)
...
__module__ = __main__
__dict__ =<attribute '__dict__' of 'User' objects>
name = <property object at 0x106ed6100>
從 class.dict 可以看出,幾個(gè)屬性方法最終變成了 property object。這也解釋了幾個(gè)同名方法為何沒有引發(fā)錯(cuò)誤。既然如此,我們可以直接用 property() 實(shí)現(xiàn)屬性。
>>> class User(object):
... def get_name(self): return self.__name
... def set_name(self, value): self.__name = value
... def del_name(self): del self.__name
... name = property(get_name, set_name, del_name, "help...")
>>> for k, v in User.__dict__.items():
... print "{0:12} = {1}".format(k, v)
__module__ = __main__
__dict__ = <attribute '__dict__' of 'User' objects>
set_name = <function set_name at 0x106eb4b18>
del_name = <function del_name at 0x106eb4b90>
get_name = <function get_name at 0x106eb4aa0>
name = <property object at 0x106ec8db8>
>>> u = User()
>>> u.name = "Tom"
>>> u.__dict__
{'_User__name': 'Tom'}
>>> u.name
'Tom'
>>> del u.name
>>> u.__dict__
{}
區(qū)別不大,只是 class.dict 中保留了幾個(gè)方法。
屬性方法多半都很簡單,用 lambda 實(shí)現(xiàn)會(huì)更加簡潔。鑒于 lambda 函數(shù)不能使用賦值語句,故改用 setattr。還得注意別用會(huì)被重命名的私有字段名做參數(shù)。
>>> class User(object):
... def __init__(self, uid):
... self._uid = uid
...
... uid = property(lambda o: o._uid) # 只讀屬性。
...
... name = property(lambda o: o._name, \ # 可讀寫屬性。
... lambda o, v: setattr(o, "_name", v))
>>> u = User(1)
>>> u.uid
1
>>> u.uid = 100
AttributeError: can't set attribute
>>> u.name = "Tom"
>>> u.name
'Tom'
不同于前面提過的對(duì)象成員查找規(guī)則,屬性總是比同名實(shí)例字段優(yōu)先。
>>> u = User(1)
>>> u.name = "Tom"
>>> u.__dict__
{'_uid': 1, '_name': 'Tom'}
>>> u.__dict__["uid"] = 1000000 # 顯式在 instance.__dict__ 創(chuàng)建同名實(shí)例字段。
>>> u.__dict__["name"] = "xxxxxxxx"
>>> u.__dict__
{'_uid': 1, 'uid': 1000000, 'name': 'xxxxxxxx', '_name': 'Tom'}
>>> u.uid # 訪問的依舊是屬性。
1
>>> u.name
'Tom'
盡可能使用屬性,而不是直接暴露內(nèi)部字段。
實(shí)例方法和函數(shù)的最大區(qū)別是 self 這個(gè)隱式參數(shù)。
>>> class User(object):
... def print_id(self):
... print hex(id(self))
>>> u = User()
>>> u.print_id
<bound method User.print_id of <__main__.User object at 0x10cf58b50>>
>>> u.print_id()
0x10cf58b50
>>> User.print_id
<unbound method User.print_id>
>>> User.print_id(u)
0x10cf58b50
從上面的代碼可以看出實(shí)例方法的特殊性。當(dāng)用實(shí)例調(diào)用時(shí),它是個(gè) bound method,動(dòng)態(tài)綁定到對(duì)象實(shí)例。而當(dāng)用類型調(diào)用時(shí),是 unbound method,必須顯式傳遞 self 參數(shù)。
那么靜態(tài)方法呢?為什么必須用 staticmethod、classmethod 裝飾器?
>>> class User(object):
... def a(): pass
...
... @staticmethod
... def b(): pass
...
... @classmethod
... def c(cls): pass
>>> User.a
<unbound method User.a>
>>> User.b
<function b at 0x10c8ef320>
>>> User.c
<bound method type.c of <class '__main__.User'>>
不使用裝飾器的方法 a,將被當(dāng)做了實(shí)例方法,自然不能以靜態(tài)方法調(diào)用。
>>> User.a()
TypeError: unbound method a() must be called with User instance as first argument (got
nothing instead)
裝飾器 classmethod 綁定了類型對(duì)象作為隱式參數(shù)。
>>> User.b()
>>> User.c()
<class '__main__.User'>
除了上面說的這些特點(diǎn)外,方法的使用和普通函數(shù)類似,可以有默認(rèn)值、變參。實(shí)例方法隱式參數(shù) self 只是習(xí)慣性命名,可以用你喜歡的任何名字。
說到對(duì)象,總會(huì)有幾個(gè)特殊的可選方法:
>>> class User(object):
... def __new__(cls, *args, **kwargs):
... print "__new__", cls, args, kwargs
... return object.__new__(cls)
...
... def __init__(self, name, age):
... print "__init__", name, age
...
... def __del__(self):
... print "__del__"
>>> u = User("Tom", 23)
__new__ <class '__main__.User'> ('Tom', 23) {}
__init__ Tom 23
>>> del u
__del__
構(gòu)造方法 new 可返回任意類型,但不同的類型會(huì)導(dǎo)致 init 方法不被調(diào)用。
>>> class User(object):
... def __new__(cls, *args, **kwargs):
... print "__new__"
... return 123
...
... def __init__(self):
... print "__init__"
>>> u = User()
__new__
>>> type(u)
<type 'int'>
>>> u
123
在方法里訪問對(duì)象成員時(shí),必須使用對(duì)象實(shí)例引用。否則會(huì)當(dāng)做普通名字,依照 LEGB 規(guī)則查找。
>>> table = "TABLE"
>>> class User(object):
... table = "t_user"
...
... def __init__(self, name, age):
... self.__name = name
... self.__age = age
...
... def tostr(self):
... return "{0}, {1}".format(
... self.__name, self.__age) # 使用 self 引用實(shí)例字段。
...
... def test(self):
... print self.tostr() # 使用 self 調(diào)用其他實(shí)例方法。
... print self.table # 使用 self 引用靜態(tài)字段。
... print table # 按 LEGB 查找外部名字空間。
>>> User("Tom", 23).test()
Tom, 23
t_user
TABLE
因?yàn)樗蟹椒ǘ即鎯?chǔ)在 class.dict,不可能出現(xiàn)同名主鍵,所以不支持方法重載 (overload)。
除了所有基類的實(shí)例字段都存儲(chǔ)在 instance.dict 外,其他成員依然是各歸各家。
>>> class User(object):
... table = "t_user"
...
... def __init__(self, name, age):
... self._name = name
... self._age = age
...
... def test(self):
... print self._name, self._age
>>> class Manager(User):
... table = "t_manager"
...
... def __init__(self, name, age, title):
... User.__init__(self, name, age) # 必須顯式調(diào)用基類初始化方法。
... self._title = title
...
... def kill(self):
... print "213..."
>>> m = Manager("Tom", 40, "CXO")
>>> m.__dict__ # 實(shí)例包含了所有基類的字段。
{'_age': 40, '_title': 'CXO', '_name': 'Tom'}
>>> for k, v in Manager.__dict__.items(): # 派生類名字空間里沒有任何基類成員。
... print "{0:5} = {1}".format(k, v)
table = t_manager
kill = <function kill at 0x10c9032a8>
>>> for k, v in User.__dict__.items():
... print "{0:5} = {1}".format(k, v)
table = t_user
test = <function test at 0x10c903140>
如果派生類不提供初始化方法,則默認(rèn)會(huì)查找并使用基類的方法。
基類引用存儲(chǔ)在 base,直接派生類存儲(chǔ)在 subclasses。
>>> Manager.__base__
<class '__main__.User'>
>>> User.__subclasses__()
[<class '__main__.Manager'>]
可以用 issubclass() 判斷是否繼承自某個(gè)類型,或用 isinstance() 判斷實(shí)例對(duì)象的基類。
>>> issubclass(Manager, User)
True
>>> issubclass(Manager, object) # 可以是任何層級(jí)的基類。
True
>>> isinstance(m, Manager)
True
>>> isinstance(m, object)
True
成員查找規(guī)則允許我們用實(shí)例引用基類所有成員,包括實(shí)例方法、靜態(tài)方法、靜態(tài)字段。 但這里有個(gè)坑:如果派生類有一個(gè)與基類實(shí)例方法同名的靜態(tài)成員,那么首先被找到的是該靜態(tài)成員,而不是基類的實(shí)例方法了。因?yàn)榕缮惖拿挚臻g優(yōu)先于基類。
>>> class User(object):
... def abc(self):
... print "User.abc"
>>> class Manager(User):
... @staticmethod
... def abc():
... print "Manager.static.abc"
...
... def test(self):
... self.abc() # 按照查找順序,首先找到的是 static abc()。
... User.abc(self) # 只好顯式調(diào)用基類方法。
>>> Manager().test()
Manager.static.abc
User.abc
同樣因?yàn)閮?yōu)先級(jí)的緣故,只需在派生類創(chuàng)建一個(gè)同名實(shí)例方法,就可實(shí)現(xiàn) "覆蓋 (override)",簽名可完全不同。
>>> class User(object):
... def test(self):
... print "User.test"
>>> class Manager(User):
... def test(self, s): # 依然是因?yàn)榕缮惷挚臻g優(yōu)先于基類。
... print "Manager.test:", s
... User.test(self) # 顯式調(diào)用基類方法。
>>> Manager().test("hi")
Manager.test: hi
User.test
多重繼承
Python 誕生的時(shí)候,單繼承還不是主流思想。至于多重繼承好不好,估計(jì)要打很久的口水仗。
>>> class A(object):
... def __init__(self, a):
... self._a = a
>>> class B(object):
... def __init__(self, b):
... self._b = b
>>> class C(A, B): # 多重繼承?;愴樞蛴绊懗蓡T搜索順序。
... def __init__(self, a, b):
... A.__init__(self, a) # 依次調(diào)用所有基類初始化方法。
... B.__init__(self, b)
>>> C.__bases__
(<class '__main__.A'>, <class '__main__.B'>)
>>> c = C(1, 2)
>>> c.__dict__ # 包含所有基類實(shí)例字段。
{'_b': 2, '_a': 1}
>>> issubclass(C, A), isinstance(c, A)
(True, True)
>>> issubclass(C, B), isinstance(c, B)
(True, True)
多重繼承成員搜索順序,也就是 mro (method resolution order) 要稍微復(fù)雜一點(diǎn)。歸納一下就是:從下到上 (深度優(yōu)先,從派生類到基類),從左到右 (基類聲明順序)。mro 和我們前面提及的成員查找規(guī)則是有區(qū)別的,mro 列表中并沒有 instance。所以在表述時(shí),需要注意區(qū)別。
>>> C.mro()
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <type 'object'>]
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <type 'object'>)
super
super() 起到其他語言 base 關(guān)鍵字的作用,它依照 mro 順序搜索基類成員。
>>> class A(object):
... def a(self): print "a"
>>> class B(object):
... def b(self): print "b"
>>> class C(A, B):
... def test(self):
... base = super(C, self) # 可以考慮放在 __init__。
... base.a() # A.a(self)
... base.b() # B.b(self)
>>> C().test()
a
b
super 的類型參數(shù)決定了在 mro 列表中的搜索起始位置,總是返回該參數(shù)后續(xù)類型的成員。單繼承時(shí)總是搜索該參數(shù)的基類型。
>>> class A(object):
... def test(self): print "a"
>>> class B(A):
... def test(self): print "b"
>>> class C(B):
... def __init__(self):
... super(C, self).test() # 從 mro 中 C 的后續(xù)類型,也就是 B 開始查找。
... super(B, self).test() # 從 B 的后續(xù)類型 A 開始查找。
>>> C.__mro__
[<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <type 'object'>]
>>> C()
b
a
<__main__.C object at 0x101498f90>
不建議用 self.class 代替當(dāng)前類型名,因?yàn)檫@可能會(huì)引發(fā)混亂。
>>> class A(object):
... def test(self):
... print "a"
>>> class B(A):
... def test(self): # 以 c instance 調(diào)用,那么
... super(self.__class__, self).test() # self.__class__ 就是 C 類型對(duì)象。
... print "b" # super(C, self) 總是查找其基類 B。
# 于是死循環(huán)發(fā)生了。
>>> class C(B):
... pass
>>> C().test()
RuntimeError: maximum recursion depth exceeded while calling a Python object
在多重繼承初始化方法中使用 super 可能會(huì)引發(fā)一些奇怪的狀況。
>>> class A(object):
... def __init__(self):
... print "A"
... super(A, self).__init__() # 找到的是 B.__init__
>>> class B(object):
... def __init__(self):
... print "B"
... super(B, self).__init__() # object.__init__
>>> class C(A, B):
... def __init__(self):
... A.__init__(self)
... B.__init__(self)
>>> o = C() # 對(duì)輸出結(jié)果很意外?
A # super 按照 mro 列表順序查找后續(xù)類型。
B # 那么在 A.__init__ 中的 super(A, self) 實(shí)際返回 B,
B # super(A, self).__init__() 實(shí)際是 B.__init__()。
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <type 'object'>)
多重繼承將很多問題復(fù)雜化,建議改用組合模式實(shí)現(xiàn)類似的功能。
bases
類型對(duì)象有兩個(gè)相似的成員:
>>> class A(object): pass
>>> class B(object): pass
>>> class C(B): pass
>>> C.__bases__ # 直接基類型元組
(<class '__main__.B'>,)
>>> C.__base__ # __bases__[0]
<class '__main__.B'>
>>> C.__mro__ # mro
(<class '__main__.C'>, <class '__main__.B'>, <type 'object'>)
>>> C.__bases__ = (A,) # 通過 __bases__ 修改基類
>>> C.__base__ # __base__ 變化
<class '__main__.A'>
>>> C.__mro__ # mro 變化
(<class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
對(duì)多繼承一樣有效,比如調(diào)整基類順序。
>>> class C(A, B): pass
>>> C.__bases__
(<class '__main__.A'>, <class '__main__.B'>)
>>> C.__base__
<class '__main__.A'>
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <type 'object'>)
>>> C.__bases__ = (B, A) # 交換基類型順序
>>> C.__base__ # __base__ 總是返回 __bases__[0]
<class '__main__.B'>
>>> C.__mro__ # mro 順序也發(fā)生變化
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <type 'object'>)
通過更換基類,我們可實(shí)現(xiàn)代碼注入 (Code Inject),影響既有類型的行為。事實(shí)上,我們還可以更改實(shí)例的類型。
>>> class A(object): pass
>>> class B(object): pass
>>> a = A()
>>> a.__class__ = B
>>> type(a)
__main__.B
抽象類
抽象類 (Abstract Class) 無法實(shí)例化,且派生類必須 "完整" 實(shí)現(xiàn)所有抽象成員才可創(chuàng)建實(shí)例。
>>> from abc import ABCMeta, abstractmethod, abstractproperty
>>> class User(object):
... __metaclass__ = ABCMeta # 通過元類來控制抽象類行為。
...
... def __init__(self, uid):
... self._uid = uid
...
... @abstractmethod
... def print_id(self): pass # 抽象方法
...
... name = abstractproperty() # 抽象屬性
>>> class Manager(User):
... def __init__(self, uid):
... User.__init__(self, uid)
...
... def print_id(self):
... print self._uid, self._name
...
... name = property(lambda s: s._name, lambda s, v: setattr(s, "_name", v))
>>> u = User(1) # 抽象類無法實(shí)例化。
TypeError: Can't instantiate abstract class User with abstract methods name, print_id
>>> m = Manager(1)
>>> m.name = "Tom"
>>> m.print_id()
1 Tom
如果派生類也是抽象類型,那么可以部分實(shí)現(xiàn)或完全不實(shí)現(xiàn)基類抽象成員。
>>> class Manager(User):
... __metaclass__ = ABCMeta
...
... def __init__(self, uid, name):
... User.__init__(self, uid)
... self.name = name
...
... uid = property(lambda o: o._uid)
... name = property(lambda o: o._name, lambda o, v: setattr(o, "_name", v))
... title = abstractproperty()
>>> class CXO(Manager):
... def __init__(self, uid, name):
... Manager.__init__(self, uid, name)
...
... def print_id(self):
... print self.uid, self.name, self.title
...
... title = property(lambda s: "CXO")
>>> c = CXO(1, "Tom")
>>> c.print_id()
1 Tom CXO
派生類 Manager 也是抽象類,它實(shí)現(xiàn)了部分基類的抽象成員,又增加了新的抽象成員。這種做法在面向?qū)ο竽J嚼锖艹R?,只須保證整個(gè)繼承體系走下來,所有層次的抽象成員都被實(shí)現(xiàn)即可。
Open Class 幾乎是所有動(dòng)態(tài)語言的標(biāo)配,也是精華所在。即便是運(yùn)行期,我們也可以隨意改動(dòng)對(duì)象,增加或刪除成員。
增加成員時(shí),要明確知道放到哪兒,比如將實(shí)例方法放到 instance.dict 是沒效果的。
>>> class User(object): pass
>>> def print_id(self): print hex(id(self))
>>> u = User()
>>> u.print_id = print_id # 添加到 instance.__dict__
>>> u.__dict__
{'print_id': <function print_id at 0x10c88e320>}
>>> u.print_id() # 失敗,不是 bound method。
TypeError: print_id() takes exactly 1 argument (0 given)
>>> u.print_id(u) # 僅當(dāng)做一個(gè)普通函數(shù)字段來用。
0x10c91c0d0
因?yàn)椴皇?bound method,所以必須顯式傳遞對(duì)象引用。正確的做法是放到 class.dict。
>>> User.__dict__["print_id"] = print_id # dictproxy 顯然是只讀的。
TypeError: 'dictproxy' object does not support item assignment
>>> User.print_id = print_id # 同 setattr(User, "print_id", print_id)
>>> User.__dict__["print_id"]
<function print_id at 0x10c88e320>
>>> u = User()
>>> u.print_id # 總算是 bound method 了。
<bound method User.print_id of <__main__.User object at 0x10c91c090>>
>>> u.print_id() # 測試通過。
0x10c91c090
靜態(tài)方法必須用裝飾器 staticmethod、classmethod 包裝一下,否則會(huì)被當(dāng)做實(shí)例方法。
>>> def mstatic(): print "static method"
>>> User.mstatic = staticmethod(mstatic) # 使用裝飾器包裝。
>>> User.mstatic # 正常的靜態(tài)方法。
<function mstatic at 0x10c88e398>
>>> User.mstatic() # 調(diào)用正常。
static method
>>> def cstatic(cls): # 注意 classmethod 和 staticmethod 的區(qū)別。
... print "class method:", cls
>>> User.cstatic = classmethod(cstatic)
>>> User.cstatic # classmethod 綁定到類型對(duì)象。
<bound method type.cstatic of <class '__main__.User'>>
>>> User.cstatic() # 調(diào)用成功。
class method: <class '__main__.User'>
在運(yùn)行期調(diào)整對(duì)象成員,時(shí)常要用到幾個(gè)以字符串為參數(shù)的內(nèi)置函數(shù)。其中 hasattr、getattr 依照成員查找規(guī)則搜索對(duì)象成員,而 setattr、delattr 則直接操作實(shí)例和類型的名字空間。
>>> class User(object):pass
>>> u = User()
>>> setattr(u, "name", "tom") # u.name = "tom"
>>> u.__dict__
{'name': 'tom'}
>>> setattr(User, "table", "t_user") # User.table = "t_user"
>>> User.table
't_user'
>>> u.table
't_user'
>>> hasattr(u, "table") # mro: User.__dict__["table"]
True
>>> getattr(u, "table", None)
't_user'
>>> delattr(u, "table") # Error: "table" not in u.__dict__
AttributeError: table
>>> delattr(User, "table")
>>> delattr(u, "name") # del u.__dict__["name"]
>>> u.__dict__
{}
slots
slots 屬性會(huì)阻止虛擬機(jī)創(chuàng)建實(shí)例 dict,僅為名單中的指定成員分配內(nèi)存空間。這有助于減少內(nèi)存占用,提升執(zhí)行性能,尤其是在需要大量此類對(duì)象的時(shí)候。
>>> class User(object):
... __slots__ = ("_name", "_age")
...
... def __init__(self, name, age):
... self._name = name
... self._age = age
>>> u = User("Tom", 34)
>>> hasattr(u, "__dict__")
False
>>> u.title = "CXO" # 動(dòng)態(tài)增加字段失敗。
AttributeError: 'User' object has no attribute 'title'
>>> del u._age # 已有字段可被刪除。
>>> u._age = 18 # 將坑補(bǔ)回是允許的。
>>> u._age
18
>>> del u._age # 該誰的就是誰的,換個(gè)主是不行滴。
>>> u._title = "CXO"
AttributeError: 'User' object has no attribute '_title'
>>> vars(u) # 因?yàn)闆]有 __dict__,vars 失敗。
TypeError: vars() argument must have __dict__ attribute
雖然沒有了 dict,但依然可以用 dir() 和 inspect.getmembers() 獲取實(shí)例成員信息。
>>> import inspect
>>> u = User("Tom", 34)
>>> {k:getattr(u, k) for k in dir(u) if not k.startswith("__")}
{'_age': 34, '_name': 'Tom'}
>>> {k:v for k, v in inspect.getmembers(u) if not k.startswith("__")}
{'_age': 34, '_name': 'Tom'}
其派生類同樣必須用 slots 為新增字段分配存儲(chǔ)空間 (即便是空 slots = []),否則依然會(huì)創(chuàng)建 dict,反而導(dǎo)致更慢的執(zhí)行效率。
>>> class Manager(User):
... __slots__ = ("_title")
...
... def __init__(self, name, age, title):
... User.__init__(self, name, age)
... self._title = title
如果需要?jiǎng)?chuàng)建 "海量" 對(duì)象實(shí)例,優(yōu)先考慮 slots 將節(jié)約大量內(nèi)存。
setitem
又稱索引器,像序列或字典類型那樣操作對(duì)象。
>>> class A(object):
... def __init__(self, **kwargs):
... self._data = kwargs
...
... def __getitem__(self, key):
... return self._data.get(key)
...
... def __setitem__(self, key, value):
... self._data[key] = value
...
... def __delitem__(self, key):
... self._data.pop(key, None)
...
... def __contains__(self, key):
... return key in self._data.keys()
>>> a = A(x = 1, y = 2)
>>> a["x"]
1
>>> a["z"] = 3
>>> "z" in a
True
>>> del a["y"]
>>> a._data
{'x': 1, 'z': 3}
call
像函數(shù)那樣調(diào)用對(duì)象,也就是傳說中的 callable。
>>> class A(object):
... def __call__(self, *args, **kwargs):
... print hex(id(self)), args, kwargs
>>> a = A()
>>> a(1, 2, s = "hi") # 完全可以把對(duì)象實(shí)例偽裝成函數(shù)接口。
0x10c8957d0 (1, 2) {'s': 'hi'}
dir
配合 slots 隱藏內(nèi)部成員。
>>> class A(object):
... __slots__ = ("x", "y")
...
... def __init__(self, x, y):
... self.x = x
... self.y = y
...
... def __dir__(self): # 必須返回 list,而不是 tuple。
... return ["x"]
>>> a = A(1, 2)
>>> dir(a) # y 不見了。
['x']
getattr
先看看這幾個(gè)方法的觸發(fā)時(shí)機(jī)。
不要在這幾個(gè)方法里直接訪問對(duì)象成員,也不要用 hasattr/getattr/setattr/delattr 函數(shù),因?yàn)樗鼈儠?huì)被再次攔截,形成無限循環(huán)。正確的做法是直接操作 dict。
而 getattribute 連 dict 都會(huì)攔截,只能用基類的 getattribute 返回結(jié)果。
>>> class A(object):
... def __init__(self, x):
... self.x = x # 會(huì)被 __setattr__ 捕獲。
...
... def __getattr__(self, name):
... print "get:", name
... return self.__dict__.get(name)
...
... def __setattr__(self, name, value):
... print "set:", name, value
... self.__dict__[name] = value
...
... def __delattr__(self, name):
... print "del:", name
... self.__dict__.pop(name, None)
...
... def __getattribute__(self, name):
... print "attribute:", name
... return object.__getattribute__(self, name)
>>> a = A(10) # __init__ 里面的 self.x = x 被 __setattr__ 捕獲。
set: x 10
attribute: __dict__
>>> a.x # 訪問已存在字段,僅被 __getattribute__ 捕獲。
attribute: x
10
>>> a.y = 20 # 創(chuàng)建新的字段,被 __setattr__ 捕獲。
set: y 20
attribute: __dict__
>>> a.z # 訪問不存在的字段,被 __getattr__ 捕獲。
attribute: z
get: z
attribute: __dict__
>>> del a.y # 刪除字段被 __delattr__ 捕獲。
del: y
attribute: __dict__
cmp
cmp 通過返回?cái)?shù)字來判斷大小,而 eq 僅用于相等判斷。
>>> class A(object):
... def __init__(self, x):
... self.x = x
...
... def __eq__(self, o):
... if not o or not isinstance(o, A): return False
... return o.x == self.x
...
... def __cmp__(self, o):
... if not o or not isinstance(o, A): raise Exception()
... return cmp(self.x, o.x)
>>> A(1) == A(1)
True
>>> A(1) == A(2)
False
>>> A(1) < A(2)
True
>>> A(1) <= A(2)
True
面向?qū)ο罄碚摵軓?fù)雜,涉及到的內(nèi)容十分繁復(fù),應(yīng)該找本經(jīng)典的大部頭好好啃啃。