在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ Python/
附錄
進(jìn)程通信
操作系統(tǒng)
迭代器
模塊
描述符
裝飾器
第三部分 擴(kuò)展庫
內(nèi)置類型
數(shù)據(jù)存儲(chǔ)
數(shù)據(jù)類型
基本環(huán)境
文件與目錄
異常
程序框架
數(shù)學(xué)運(yùn)算
函數(shù)
元類
字符串
表達(dá)式

由于歷史原因,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) 是不同的。

  • 實(shí)例字段存儲(chǔ)在 instance.dict,代表單個(gè)對(duì)象實(shí)體的狀態(tài)。
  • 靜態(tài)字段存儲(chǔ)在 class.dict,為所有同類型實(shí)例共享。
  • 必須通過類型和實(shí)例對(duì)象才能訪問字段。
  • 以雙下劃線開頭的 class 和 instance 成員視為私有,會(huì)被重命名。(module 成員不變)
>>> 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. 外,也可以用 instance.。按照成員查找規(guī)則,只要沒有同名的實(shí)例成員,那么就繼續(xù)查找 class.dict。

>>> 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)限。

  • 用重命名后的格式訪問。
  • 只用一個(gè)下劃線,僅提醒,不重命名。

不必過于糾結(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è)特殊的可選方法:

  • new: 創(chuàng)建對(duì)象實(shí)例。
  • init: 初始化對(duì)象狀態(tài)。
  • del: 對(duì)象回收前被調(diào)用。
>>> 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è)相似的成員:

  • base: 只讀,總是返回 bases[0]。
  • bases: 基類列表,可直接修改來更換基類,影響 mro 順序。
>>> 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ī)。

  • getattr: 訪問不存在的成員。
  • setattr: 對(duì)任何成員的賦值操作。
  • delattr: 刪除成員操作。
  • getattribute: 訪問任何存在或不存在的成員,包括 dict。

不要在這幾個(gè)方法里直接訪問對(duì)象成員,也不要用 hasattr/getattr/setattr/delattr 函數(shù),因?yàn)樗鼈儠?huì)被再次攔截,形成無限循環(huán)。正確的做法是直接操作 dict。

getattributedict 都會(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)典的大部頭好好啃啃。

上一篇:程序框架下一篇:附錄