13.1 介紹
類與實例:
類與實例相互關聯,類是對象的定義,而實例是"真正的實物",它存放了類中所定義的對象的具體信息
下面的示例展示如何創建一個類:
class MyNewObjectType(bases):
'define MyNewObjectType class'
class_suite #類體
示例:
>>> class MyData(object): #創建類
... pass
...
>>> mathObj = MyData() # 類的實例
>>> mathObj.x = 4
>>> mathObj.y = 5
>>> mathObj.x + mathObj.y
9
>>> mathObj.x * mathObj.y
20
方法
我們改進類的方法之一就是給類添加功能,類的功能就是叫方法
在Python中,方法定義在類定義中,但只能被實例所調用.也就是說,調用一個方法的最終途徑必須是:
1.定義類(和方法) 2.創建一個實例 (3)用這個實例調用方法
class MyDataWithMethod(object):
def printFoo(self):
print 'You invoked printFoo()!'
現在我們來實例化這個類,然後調用那個方法:
>>> myObj = MyDataWithMethod()
>>> myObj.printFoo()
You invoked printFoo()!
注: __init__()方法在類中創建後,在實例化調用這個類並返回這個實例之前,會調用這個方法,執行一些額外的特定任務或設置
創建一個類(類定義)
>>> class AddrBookEntry(object): # 類定義
... 'address book entry class'
... def __init__(self, nm, ph): # 定義構造器
... self.name = nm # 設置 name
... self.phone =ph # 設置 phone
... print 'Created instance for: ', self.name
... def updatePhone(self, newph): # 定義方法
... self.phone = newph
... print 'Updated phone# for:', self.name
在AddrBookEntry類的定義中,定義了兩個方法: __init__()和updatePhone()
__init__()在實例化時被調用,即,在AddrBookEntry()被調用時,你可以認爲實例化是對__init__()的一種隱式調用,因爲傳給AddrBookEntry()的參數完全與__init__()接收到的參數是一樣的(除了self,它是自動傳遞的)
當方法在實例中被調用時,self(實例對象)參數自動由解釋器傳遞,所以在上面的__init__()中,需要參數是nm和ph,他們分別表示名字和電話號碼,__init__在實例化時,設置這兩個屬性,以便在實例化調用返回時是可見的
創建實例(實例化)
>>> john = AddrBookEntry('John Doe', '408-555-1212') #爲john Doe創建實例
Created instance for: John Doe
>>> jane = AddrBookEntry('Jane Doe', '650-555-1212') #爲john Doe創建實例
Created instance for: Jane Doe
這就是實例化調用,他會自動調用__init__(), self把實例對象自動傳入__init__()
當對象john被實例化後,它的john.name已經被設置了
訪問實例屬性:
>>> john
<__main__.AddrBookEntry object at 0x7f093c167a10>
>>> john.name
'John Doe'
>>> john.phone
'408-555-1212'
>>> jane.phone
'650-555-1212'
>>> jane.name
'Jane Doe'
方法調用(通過實例)
>>> john.updatePhone('415-555-1212') # 更新John Doe的電話
Updated phone# for: John Doe
>>> john.phone
'415-555-1212'
創建子類:
>>> class EmplAddrBookEntry(AddrBookEntry):
... 'Enployee Address Book Entry class'
... def __init__(self,nm,ph,id,em):
... addrBookEntry.__init__(self,nm,ph)
... self.empid = id
... self.email = em
... def updateEmail(self,newem):
... self.email = newem
... print 'Updated e-mail address for:', self.name
...
在上面的類中,不僅定義了__init__(), updatEmail()方法,而且EmplAddrBookEntry還從AddrBiikEntry中繼承了updatePhone()方法
注意,我們要顯式傳遞self實例對象給基類構造器,因爲我們而非在其實例而是在其子類實例中調用那個方法,因爲我們不是通過實例來調用它,這種非綁定的方法調用需要傳遞一個適當的實例(self)給方法
使用子類:
>>>
>>> class EmplAddrBookEntry(AddrBookEntry):
... 'Enployee Address Book Entry class'
... def __init__(self,nm,ph,id,em):
... AddrBookEntry.__init__(self,nm,ph)
... self.empid = id
... self.email = em
... def updateEmail(self,newem):
... self.email = newem
... print 'Updated e-mail address for:', self.name
...
>>> john = EmplAddrBookEntry("John Doe", '408-555-1212', 42,'[email protected]')
Created instance for: John Doe
>>> john
<__main__.EmplAddrBookEntry object at 0x7f093c167d10>
>>> john.name
'John Doe'
>>> john.phone
'408-555-1212'
>>> john.email
>>> john.updatePhone('415-555-1212')
Updated phone# for: John Doe
>>> john.phone
'415-555-1212'
>>> john.updateEmail('[email protected]')
Updated e-mail address for: John Doe
>>> john.email
13.2 面向對象編程:
13.2.1 面向對象設計與面向對象編程的關係
13.2.2 現實世界中的問題
13.2.3 常用術語
抽象/實現
封裝/接口
合成
派生/繼承/繼承結構
泛化/特化
多態
自省/反射
13.3 類
類是一種數據結構,可以用它來定義對象,後者把數據值和行爲特性融合在一起,實例是這些對象的具體化
def functionName(args):
"function documentation string" #函數文檔字符串
function_suite #函數體
class ClassName(args):
"Class documentation string" #函數文檔字符串
class_suite #函數體
13.3.1 創建類
class ClassName(bases):
"Class documentation string" #類文檔字符串
class_suite #類體
13.3.2 聲明與定義
13.4 類屬性
屬性就是屬於另一個對象的數據或函數元素,可以通過我們熟悉的句點屬性標示法來
訪問
當你訪問一個屬性時,它同時也是一個對象,擁有它自己的屬性,可以訪問,這導致一個屬性鏈
sys.dtdout.write('foo')
print myModule.myClass.__doc__
myList.extend(map(upper, open('x').readlines()))
13.4.1 類的數據屬性
數據屬性僅僅是所定義的類的變量,他們可以像任何其他變量一樣在類創建後被使用
看下面的例子,使用類數據屬性(foo):
>>> class C(object):
... foo = 100
...
>>> print C.foo
100
>>> C.foo = C.foo + 1
>>> print C.foo
101
注:上面的代碼中,看不到任何類實例的引用
13.4.2 方法
方式是一個作爲類定義一部分定義的函數,從而使得方法稱爲類屬性
>>> class MyClass(object):
... def myNoActionMethod(self):
... pass
...
>>> mc = MyClass()
>>> mc.myNoActionMethod()
13.4.2 決定類的屬性:
知道一個類有哪些屬性,有兩種方法,最簡單的是使用dir()內建函數,另外是通過訪問類的字典屬性__dict__
class MyClass(object):
... 'MyClass class definition'
... myVersion = '1.1'
... def showMyVersion(self):
... print MyClass.myVersion
使用dir()和特殊類屬性__dict__來查看類的屬性:
>>> dir(MyClass)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'myVersion', 'showMyVersion']
>>> MyClass.__dict__
dict_proxy({'__module__': '__main__', 'showMyVersion': <function showMyVersion at 0x7ffa067f1410>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, 'myVersion': '1.1', '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': 'MyClass class definition'})
>>> print MyClass.__dict__
{'__module__': '__main__', 'showMyVersion': <function showMyVersion at 0x7ffa067f1410>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, 'myVersion': '1.1', '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': 'MyClass class definition'}
dir()返回的僅是對象的屬性的一個名字列表,而__dict__返回的是一個字典,它的鍵(keys)是屬性名,鍵值(values)是相應的屬性對象的數據值
13.4.3 特殊的類屬性:
C.__name__類C的名字(字符串)
C.__doc__類C的文檔字符串
C.__bases__類C的所有父類構成的元祖
C.__dict__類C的屬性
C.__module__類C定義所在的模塊
C.__class__實例C對應的類
>>> MyClass.__name__
'MyClass'
>>> MyClass.__doc__
'MyClass class definition'
>>> MyClass.__bases__
(<type 'object'>,)
>>> print MyClass.__dict__
{'__module__': '__main__', 'showMyVersion': <function showMyVersion at 0x7ffa067f1410>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, 'myVersion': '1.1', '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': 'MyClass class definition'}
>>> MyClass.__module__
'__main__'
>>> MyClass.__class__
<type 'type'>
類型對象是一個內建類型的例子,它有__name__的屬性
>>> stype = type("What is your quest?")
>>> stype
<type 'str'>
>>> stype.__name__
'str'
>>> type(3.14159265)
<type 'float'>
>>> type(3.14159265).__name__
'float'
Python支持模塊間的類繼承,爲更清晰的對類進行描述,1.5版本引入了__module__,這樣類名就完全由模塊名所限定
>>> class C(object):
... pass
...
>>> C
<class '__main__.C'>
>>> C.__module__
'__main__'
類C的全名是"__main__.C",比如,source_module.class_name,如果類C位於一個導入的模塊中,如mymod,像下面的:
>>> from mymod import C
>>> C
<class '__main__.C at 0x54ea0'>
>>> C.__module__
'mymod'
13.5 實例:
類是一種數據結構定義類型,那麼實例則聲明瞭一個這種類型的變量
13.5.1 初始化:通過調用類對象來創建實例
>>> class MyClass(object): # 定義類
... pass
...
>>> mc = MyClass() # 初始化類
13.5.2 __init__()"構造器" 方法
當類被調用,實例化的第一步就是創建實例對象,一旦對象創建了,Python檢查是否實現了__init__()方法, 該方法若沒有定義,則不會做任何操作.
實際上,你可以理解 創建實例的調用即是對構造器的調用
13.5.3 __new__() "構造器"方法
Python的構造器實例化不可變對象
13.5.14 __del__() "解構器"方法(問題)
Python的解構器是在實例釋放前提供特殊處理功能的方法
13.6 實例屬性
實例僅擁有數據屬性,後者只是與某個類的實例相關聯的數據值,並且可以通過句點屬性標示法來訪問
13.6.1 "實例化" 實例屬性(或創建一個更好的構造器)
設置實例的屬性可以在實例創建後任意時間進行,也可以在能夠訪問實例的代碼中進行
在構造器中首先設置實例屬性
默認參數提供默認的實例安裝
例,使用缺省參數進行實例化
定義個類來計算這個旅館租房費用, __init__()構造器對一些實例屬性進行初始化,calcTotal()方法用來決定是計算每日總的租房費用還是計算所有全部的租房費
# vi hotel.py
------------------------------------
#!/usr/bin/env python
class HotelRoomCalc(object):
'Hotel room rate calculator'
def __init__(self, rt, sales=0.085, rm=0.1):
'''HotelRoomCalc default arguments:
sales tax = 8.5% and room tax =10%'''
self.salesTax = sales
self.roomTax = rm
self.roomRate = rt
def calcTotal(self, days=1):
'Calculate total;default to daily rate'
daily = round((self.roomRate *(1 + self.roomTax + self.salesTax)),2)
return float(days) * daily
sfo = HotelRoomCalc(299) # 新的實例
print sfo.calcTotal() # 日租金
print sfo.calcTotal(2) # 2天的租金
sea = HotelRoomCalc(189,0.086,0.058) # 新的實例
print sea.calcTotal()
print sea.calcTotal(4)
wasWkDay = HotelRoomCalc(169,0.045,0.02) # 新的實例
wasWkEnd = HotelRoomCalc(119,0.045,0.02) # 新的實例
print wasWkDay.calcTotal(5) + wasWkEnd.calcTotal() # 7天的租金
------------------------------------
# python hotel.py
---------------------------
354.31
708.62
216.22
864.88
1026.63
----------------------------
__init__()應當返回None
採用函數操作符調用類對象會創建一個類實例,也就是說這樣一種調用過程返回的對象就是實例
例:
>>> class MyClass(object):
... pass
...
>>> mc = MyClass()
>>> mc
<__main__.MyClass object at 0x7f0adbce3710>
如果定義了構造器,就不應當返回任何對象,應爲實例對象是自動在實例化調用後返回的,所以 __init__()就不應當放回任何對象,否則就會出現衝突,試着返回非None的任何其他對象都會導致TypeError異常:
>>> class MyClass:
... def __init__(self):
... print "initialized"
... return 1
...
>>> mc = MyClass()
initialized
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() should return None
13.6.2 查看實例屬性
內建函數dir()可以顯示類屬性,同時可以打印所有實例屬性:
>>> class C(object):
... pass
...
>>> c = C()
>>> c.foo = 'roger'
>>> c.bar = 'shrubber'
>>> dir(c)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bar', 'foo']
與類相似,實例也有一個__dict__特殊屬性,他是實例屬性構成的一個字典
>>> c.__dict__
{'foo': 'roger', 'bar': 'shrubber'}
13.6.3 特殊的實例屬性
1.__class__ 實例化1的類
1.__dict__ 1的屬性
現在使用類C及其實例C來看看這些特殊實例屬性
>>> class C(object):
... pass
...
>>> c = C()
>>> dir(c)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
>>> c.__dict__
{}
>>> c.__class__
<class '__main__.C'>
添加一些屬性:
>>> c.foo = 1
>>> c.bar = 'SPAM'
>>> '%d can of %s please' %(c.foo,c.bar)
'1 can of SPAM please'
>>> c.__dict__
{'foo': 1, 'bar': 'SPAM'}
13.6.4 內建類型屬性:
內建類型也可以使用dir(),與任何其他對象一樣,可以得到一個包含它屬性名字的列表:
>>> x = 3 + 0.14j
>>> x.__class__
<type 'complex'>
>>> dir(x)
['__abs__', '__add__', '__class__', '__coerce__', '__delattr__', '__div__', '__divmod__', '__doc__', '__eq__', '__float__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__int__', '__le__', '__long__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__nonzero__', '__pos__', '__pow__', '__radd__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rmod__', '__rmul__', '__rpow__', '__rsub__', '__rtruediv__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', 'conjugate', 'imag', 'real']
>>> [type (getattr(x,i)) for i in ('conjugate', 'imag', 'real')]
[<type 'builtin_function_or_method'>, <type 'float'>, <type 'float'>]
>>> x.imag
0.14
>>> x.real
3.0
>>> x.conjugate()
(3-0.14j)
訪問__dict__會失敗,因爲在內建類型中,不存在這個屬性:
>>> x.__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'complex' object has no attribute '__dict__'
13.6.5 實例屬性vs類屬性
訪問類屬性
類屬性可通過類或實例來訪問
>>> class C(object): # 定義類
... version = 1.2 # 靜態成員
...
>>> c = C() # 實例化
>>> C.version # 通過類訪問
1.2
>>> C.version += 0.1 # 通過類來更新
>>> C.version # 類訪問
1.3
>>> c.version # 實例訪問它,其值已被改變
1.3
從實例中訪問類屬性須謹慎
>>> class Foo(object):
... x = 1.5
...
>>> foo = Foo()
>>> foo.x
1.5
>>> foo.x =1.7 #嘗試更新類屬性
>>> foo.x # 看起來不錯
1.7
>>> Foo.x # 沒變,只是創建了一個新的實例屬性
1.5
刪除這個新的version實例
>>> del foo.x # 刪除實例屬性
>>> foo.x # 又可以訪問到類屬性
1.5
更新類屬性
>>> foo.x += .2 # 試着增加類屬性
>>> foo.x
1.7
>>> Foo.x # 照舊
1.5
注:賦值語句右邊的表達式計算出原類的變量,增加0.2,並且把這個值賦給新創建的實例對象,以上更新等價於下面的賦值方式
foo.x = Foo.x +0.2
在類屬性可變情況下:
>>> class Foo(object):
... x = {2003: 'poe2'}
...
>>> foo = Foo()
>>> foo.x
{2003: 'poe2'}
>>> foo.x[2004] = 'valid path'
>>> foo.x
{2003: 'poe2', 2004: 'valid path'}
>>> Foo.x # 生效了
{2003: 'poe2', 2004: 'valid path'}
>>> del foo.x # 沒有遮蔽所以不能刪除掉
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object attribute 'x' is read-only
類屬性持久性:
當一個實例在類屬性被修改後才創建,那麼更新的值就將生效,類屬性的修改該會影響所有的實例:
>>> class C(object):
... spam = 100 # 類屬性
...
>>> c1 = C() # 創建一個實例
>>> c1.spam # 通過實例訪問類屬性
100
>>> C.spam += 100 # 更新類屬性
>>> C.spam # 查看屬性值改變
200
>>> c1.spam # 在實例中驗證屬性值改變
200
>>> c2 = C() # 創建另一個實例
>>> c2.spam # 驗證類屬性
200
>>> del c1 # 刪除一個實例
>>> C.spam
200
>>> C.spam += 200 # 再次更新類屬性
>>> c2.spam # 驗證那個屬性值改變
400
13.7 從這裏開始校對-----綁定和方法調用
方法僅僅是類內部定義的函數,這意味着方法是類屬性而不是實例屬性
其次方法只有在其所屬的類擁有實例時,才被調用.
最後任何一個方法定義中的異地個參數都是變量self,它表示調用此方法的實例
對象
注:self變量用於在類實例方法中引用方法所綁定的實例,因爲方法的實例在任何方法調用中總是作爲第一個參數傳遞的,self被選中用來代表實例
13.7.1 調用綁定方法:
13.7.2 調用非綁定方法:
class EmplAddrBookEntry(AddrBookEntry):
'Employee Address Book Entry class'
def __init__(self,nm,ph,em):
AddrBookEntry.__init__(self,nm,ph)
self.empid = id
self.email = em
EmplAddrBookEntry是AddrBookEntry的子類,我們重載了構造器__init__(),我們想儘可能多的重用代碼,而不是從父類構造器中剪貼,黏貼代碼
13.8 靜態方法和類方法:
靜態方法僅僅是類中的函數
13.8.1 staticmethod()和classmethod()內建函數:
在經典類中創建靜態方法和類方法的一些例子:
>>> class TestStaticMethod:
... def foo():
... print 'calling static method foo()'
... foo = staticmethod(foo)
...
>>> class TestClassMethod:
... def foo(cls):
... print 'calling class method foo()'
... print 'foo() is part of class: ', cls.__name__
... foo = classmethod(foo)
對應的內建函數被轉換成他們相應的類型,並且重新賦值給列相同的變量名,如果沒有調用這兩個函數,兩者都會在Python編譯器中產生錯誤,顯示需要帶self的常規方法聲明,現在我們可以通過類或者實例調用這些函數:
>>> tsm = TestStaticMethod()
>>> TestStaticMethod().foo()
calling static method foo()
>>> tsm.foo()
calling static method foo()
>>>
>>> tcm = TestClassMethod()
>>> TestClassMethod.foo()
calling class method foo()
foo() is part of class: TestClassMethod
>>> tcm.foo()
calling class method foo()
foo() is part of class: TestClassMethod
13.8.2 使用函數修飾符:
替代statcimethod()使用函數修飾符
>>> class TestStaticMethod:
... @staticmethod
... def foo():
... print 'calling static method foo()'
...
>>> class TestClassMethod:
... @classmethod
... def foo(cls):
... print 'calling class method foo()'
... print 'foo() is part of class:', cls.__name__
>>> TestClassMethod.foo()
calling class method foo()
foo() is part of class: TestClassMethod
>>> TestStaticMethod.foo()
calling static method foo()
13.9 組合
>>> class NewAddrBookEntry(object): # 類定義
... 'new address book entry class'
... def __init__(self,nm,ph):# 定義構造器
... self.name = Name(nm) # 創建Name實例
... self.phone =Phone(ph)# 創建Phone實例
... print 'Created instance for:',self.name
13.10 子類的派生:
13.10.1 創建子類:
>>> class Parent(object):# 定義父類
... def parentMethod(self):
... print 'calling parent method'
...
>>> class Child(Parent):# 定義子類
... def childMethod(self):
... print 'calling child method'
...
>>> p = Parent()# 父類的實例
>>> p.parentMethod()
calling parent method
>>> c = Child()# 子類的實例
>>> c.childMethod()#子類調用它的方法
calling child method
>>> c.parentMethod()#調用父類的方法
calling parent method
13.11 繼承:
繼承描述了基類屬性如何"遺傳"給派生類,一個子類可以繼承它的基類任何屬性
例:
>>> class P(object):# 父類
... pass
...
>>> class C(P):# 子類
... pass
...
>>> c = C()# 實例化子類
>>> c.__class__# 子類"是一個"父類
<class '__main__.C'>
>>> C.__bases__# 子類的父類
(<class '__main__.P'>,)
因爲P沒有屬性,C沒有繼承到什麼,下面我們給P添加一些屬性:
>>> class P:#父類
... 'p class'
... def __init__(self):
... print 'created an instance of',self.__class__.__name__
...
>>> class C(P):#子類
... pass
...
現在所創建的P有文檔字符串(__doc__)和構造器,當我們實例化P時,他被執行
>>> p = P() # 父類實例
created an instance of P
>>> p.__class__ # 顯示p所屬類名
<class __main__.P at 0x8b281dc>
>>> P.__bases__ # 父類的父類
()
>>> P.__doc__ # 父類的文檔字符串
'p class'
"created an instance" 是由__init__()直接輸出的
現在我們實例化C
>>> c = C() # 子類實例
created an instance of C
>>> c.__class__ # 顯示c所屬類名
<class __main__.C at 0x8b2823c>
>>> C.__bases__ # 子類的父類
(<class __main__.P at 0x8b281dc>,)
>>> C.__doc__ # 子類的文檔字符串
C沒有聲明 __init__()方法,然而在類C的實例c被創建時,還是會有輸出信息,原因在於C繼承了P的__init__().
13.11.1 __bases__類屬性
父類是相對所有基類(它包括了所有祖先類) 那些沒有父類的類,__bases屬性爲空
>>> class A(object): pass
...
>>> class B(A): pass
...
>>> class C(B): pass
...
>>> class D(B, A): pass
...
>>> A.__bases__
(<type 'object'>,)
>>> C.__bases__
(<class '__main__.B'>,)
>>> D.__bases__
(<class '__main__.B'>, <class '__main__.A'>)
13.11.2 通過繼承覆蓋(Overriding)方法
我們在P中再寫一個函數,然後再其子類中對它進行覆蓋
>>> class P(object):
... def foo(self):
... print 'Hi,I am P-foo()'
...
>>> p = P()
>>> p.foo()
Hi,I am P-foo()
現在來創建子類C,從父類P派生:
>>> class C(P):
... def foo(self):
... print 'Hi,I am C-foo()'
...
>>> c = C()
>>> c.foo()
Hi,I am C-foo()
儘管C繼承了P的foo()方法,但因爲C定義了它自己的foo()方法,所以P中的foo()方法被覆蓋
如何不被覆蓋而是用父類的foo()方法:
調用一個未綁定的基類方法,明確給出子類的實例:
>>> P.foo(c)
Hi,I am P-foo()
在子類的重寫方法裏顯式地調用基類方法:
>>> class C(P):
... def foo(self):
... P.foo(self)
... print 'Hi,I am C-foo()'
...
>>> c = C()
>>> c.foo()
Hi,I am P-foo()
Hi,I am C-foo()
使用super()內建方法:
>>> class C(P):
... def foo(self):
... super(C, self).foo()
... print 'Hi,I am C-foo()'
...
super()不但能找到基類方法,而且還爲我們傳進self,這樣我們只需要調用子類方法,他會幫你完成一切:
>>> c = C()
>>> c.foo()
Hi,I am P-foo()
Hi,I am C-foo()
注: 重寫__init__不會自動調用基類的__init__
>>> class P(object):
... def __init__(self):
... print "calling P's constructor"
...
>>> class C(P):
... def __init__(self):
... print "calling C's constructor"
...
>>> c = C()
calling C's constructor
如果你還想調用基類的__init__(),你需要使用一個子類的實例去調用基類(未綁定)方法,相應的更新類C
>>> class C(P):
... def __init__(self):
... P.__init__(self)
... print "calling C's constructor"
...
>>> c = C()
calling P's constructor
calling C's constructor
使用super()內建函數調用基類__init__()
>>> class C(P):
... def __init__(self):
... super(C, self).__init__()
... print "calling C's constructor"
...
>>> c = C()
calling P's constructor
calling C's constructor
13.11.3 從標準類型派生:
不可變類型的例子:
假定你想在金融應用中,應用一個處理浮點數的子類,每次你得到一個貨幣值,你都需要通過四捨五入,變爲帶兩位小數位的數字:
>>> class RoundFloat(float):
... def __new__(cls,val):
... return float.__new__(cls, round(val,2))
>>> RoundFloat(1.5955)
1.6
使用內建函數super()去捕獲對應的父類以調用它的__new__方法
>>> class RoundFloat(float):
... def __new__(cls,val):
... return super(RoundFloat,cls).__new__(cls, round(val,2))
...
>>> RoundFloat(1.5955)
1.6
>>> RoundFloat(1.5945)
1.59
>>> RoundFloat(-1.9955)
-2.0
可變類型的例子:
創建一個新的字典類型,它的keys()方法會自動排序結果:
>>> class SortedKeyDict(dict):
... def keys(self):
... return sorted(super(SortedKeyDict, self).keys())
>>> d = SortedKeyDict((('zheng-cai',67),('hui-jun',68),('xin-yi',2)))
>>> print 'By iterator:'.ljust(12), [key for key in d]
By iterator: ['zheng-cai', 'xin-yi', 'hui-jun']
>>> print 'By iterator:'.ljust(12), d.keys()
By iterator: ['hui-jun', 'xin-yi', 'zheng-cai']
注:
ljust用法:string.ljust(s,width[,fillchar])
意思就是如果你的字符串本來長度是5,我要把它變成長度爲40,而且可以用字符填充。
>>> import string
>>> s="hello"
>>> string.ljust(s,40)
'hello '
>>> string.ljust(s,40,'x')
'helloxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
如果調用super()過於複雜, 取而代之:
def keys(self):
return sorted(self.keys())
13.11.4 多重繼承
>>> class P1:# 父類1
... def foo(self):
... print 'called P1-foo()'
...
>>> class P2:# 父類2
... def foo(self):
... print 'called P2-foo()'
... def bar(self):
... print 'called P2-bar()'
...
>>> class C1(P1,P2):#子類1,從P1,P2派生
... pass
...
>>> class C2(P1,P2):#子類2,從P1,P2派生
... def bar(self):
... print 'called C2-bar()'
...
>>> class GC(C1,C2):#定義子孫類
... pass#從C1,C2派生
...
圖:父類,子類及子孫類的關係圖,還有他們各自定義的方法:
(foo)P1 P2(foo,bar)
\ /
\ /
\/
/\
/ \
/ \
C1 C2(bar)
\ /
\ /
\ /
\/
GC
經典類:
>>> gc = GC()
>>> gc.foo()#GC ==> C1 ==> P1
called P1-foo()
>>> gc.bar() #GC ==> C1 ==> P1 ==> P2
called P2-bar()
當調用foo()時,它首先在當前類(GC)中查找,如果沒找到,就像上查找最親的父類,C1,查找未遂,就繼續沿樹上訪到父類P1,foo()被找到.
對bar()來說,它通過搜索GC,C1,P1然後在P2中找到,因爲使用這種解釋順序,C2.bar()根本就不會被搜索
如果需要調用C2的bar()方法,必須調用它的合法全名,採用典型的非綁定方式調用,並且提供一個合法實例:
>>> C2.bar(gc)
called C2-bar()
新式類:
>>> class P1(object):
... def foo(self):
... print 'called P1-foo()'
...
>>> class P2(object):
... def foo(self):
... print 'called P2-foo()'
... def bar(self):
... print 'called P2-bar()'
...
>>> class C1(P1,P2):
... pass
...
>>> class C2(P1,P2):
... def bar(self):
... print 'called C2-bar()'
...
>>> class GC(C1, C2):
... pass
...
>>> gc = GC()
>>> gc.foo()# GC ==> C1 ==> C2 ==>P1
called P1-foo()
>>> gc.bar() # GC ==> C1 ==> C2
called C2-bar()
與沿着繼承樹一步一步上溯不同,它首先查找同胞兄弟,採用廣度優先方式,當查找foo(),它檢查GC,然後是C1和C2,然後在P1中找到,如果P1中沒有,查找將會到達P2
然而bar(),它搜索GC和C1,緊接着在C2中找到了,這樣就不會再繼續搜索到祖父
P1和P2
新式類也有一個__mro__屬性,告訴你查找順序是怎樣的:
>>> GC.__mro__
(<class '__main__.GC'>, <class '__main__.C1'>, <class '__main__.C2'>, <class '__main__.P1'>, <class '__main__.P2'>, <type 'object'>)
13.12 類,實例和其他對象的內建函數:
13.12.1 issubclass()
issubclass() 布爾函數判斷一個類是另一個類的子類或子孫類
issubclass(sub, sup)
13.12.2 isinstance()
isinstance()布爾函數判斷一個對象是否是另一個給定類的實例:
isinstance(obj1,obj2)
isinstance()在obj1是obj2的一個實例,或者是obj2的子類的一個實例時,返回True,反之False
>>> class C1(object): pass
...
>>> class C2(object): pass
...
>>> c1 = C1()
>>> c2 = C2()
>>> isinstance(c1,C1)
True
>>> isinstance(c2,C1)
False
>>> isinstance(c1,C2)
False
>>> isinstance(c2,C2)
True
>>> isinstance(C2,c2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types
注:第二個參數應當是類,不然會得到一個TypeError
但如果第二個參數是一個類型的對象,則不會出現異常,因爲你可以使用instance()來檢查一個對象obje1是否是obj2的類型:
>>> isinstance(4,int)
True
>>> isinstance(4,str)
False
>>> isinstance('4',str)
True
13.12.3 hasattr(),getattr(),setattr(),delattr()
hasattr()函數是布爾型的,判斷一個對象是否有一個特定的屬性,一般用於訪問某屬性前先作一下檢查
getattr()和setattr()爲取得和賦值給對象的屬性
delattr()函數會從一個對象中刪除屬性
>>> class myClass(object):
... def __init__(self):
... self.foo = 100
...
>>> myInst = myClass()
>>> hasattr(myInst, 'foo')
True
>>> getattr(myInst, 'foo')
100
>>> hasattr(myInst, 'bar')
False
>>> getattr(myInst, 'bar')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'myClass' object has no attribute 'bar'
>>> setattr(myInst, 'bar','my attr')
>>> getattr(myInst,'bar')
'my attr'
>>> delattr(myInst, 'foo')
>>> dir(myInst)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bar']
>>> hasattr(myInst,'foo')
False
13.12.4 dir()
dir()作用在實例上(經典類或新式類),顯示實例變量,以及在實例所在的類及所有它的基類中定義的方法和類屬性
dir()作用在類上,顯示類以及他所有基類的__dict__中的內容,但不會顯示定義在元類(metaclass)中的類屬性
dir()作用的模塊上,顯示模塊的__dict__內容
dir()不帶參數,顯示調用者的局部變量
13.12.5 super()
super()函數目的就是幫助找出相應的父類,方便調用相關屬性
super(type[, obj])
super()的主要用途是用來查找父類屬性:
例如:
super(MyClass,self).__init__()
13.12.6 vars()
vars()與dir()相似,vars()返回一個字典,它包含了對象存儲於其__dict__中的屬性(鍵)及值,如果提供的對象沒有這個屬性,則會引發TypeError異常,如果沒有提供對象作爲vars()的一個參數,他將顯示一個包含本地名字空間的屬性(鍵)及其值的字典,也就是locals()
>>> class C(object):
... pass
...
>>> c = C()
>>> c.foo = 100
>>> c.bar = 'Python'
>>> c.__dict__
{'foo': 100, 'bar': 'Python'}
>>> vars(c)
{'foo': 100, 'bar': 'Python'}
13.13 用特殊方法定製類:
13.13.1 簡單定製(RoundFloat2)
>>> class RoundFloatManual(object):
... def __init__(self,val):
... assert isinstance(val, float), "Value must be a float!"
... self.value = round(val,2)
這個類僅接收一個浮點值,並且將其保存爲實例屬性
>>> rfm = RoundFloatManual(42)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
AssertionError: Value must be a float!
>>> rfm = RoundFloatManual(4.2)
>>> rfm
<__main__.RoundFloatManual object at 0x889064c>
>>> print rfm
<__main__.RoundFloatManual object at 0x889064c>
當你想顯示你的對象,實際上是想看到有意義的東西,而不僅僅是通常的Python對象字符串
添加一個__str()__方法,以覆蓋默認的行爲
>>> class RoundFloatManual(object):
... def __init__(self,val):
... assert isinstance(val, float), "Value must be a float!"
... self.value = round(val,2)
... def __str__(self):
... return str(self.value)
...
>>> rfm = RoundFloatManual(5.590464)
>>> rfm
<__main__.RoundFloatManual object at 0x889082c>
>>> print rfm
5.59
讓__repr__()作爲__str__()的一個別名,實現在四捨五入爲5.6的基礎上還想保留兩位小數,這裏就同時具備str()和repr()的輸出:
>>> class RoundFloatManual(object):
... def __init__(self,val):
... assert isinstance(val, float), "Value must be a float!"
... self.value = round(val,2)
... def __str__(self):
... return '%.2f' % self.value
... __repr__ = __str__
...
>>> rfm = RoundFloatManual(5.5964)
>>> rfm
<__main__.RoundFloatManual object at 0x8890a4c>
>>> print rfm
5.60
13.13.2 數值定製(Time60)
顯示:
>>> class Time60(object):
... def __init__(self, hr, min):
... self.hr = hr
... self.min = min
... def __str__(self):
... return '%d:%d' % (self.hr, self.min)
...
用此類,可以實例化一些對象,在下面的例子,我們啓動一個工時表來跟蹤對應構造器的計費小時數:
>>> mon = Time60(10, 30)
>>> tue = Time60(11, 15)
>>> print mon, tue
10:30 11:15
加法:
Python的重載操作符很簡單,像加號(+),我們只需要重載__add__()方法,創建另一個對象並填入計算出來的總數:
>>> class Time60(object):
... def __init__(self, hr, min):
... self.hr = hr
... self.min = min
... def __str__(self):
... return '%d:%d' % (self.hr,self.min)
... def __add__(self, other):
... return self.__class__(self.hr + other.hr, self.min + other.min)
...
>>> mon = Time60(10,30)
>>> tue = Time60(11,15)
>>> mon + tue
<__main__.Time60 object at 0x7f717b544c10>
>>> print mon + tue
21:45
原位加法:
__iadd__()用來支持像mon += tue這樣的操作符,並把正確的結果賦給mon
>>> class Time60(object):
... def __init__(self, hr, min):
... self.hr = hr
... self.min = min
... def __str__(self):
... return '%d:%d' % (self.hr,self.min)
... __repr__ = __str__
... def __iadd__(self, other):
... self.hr += other.hr
... self.min += other.min
... return self
...
>>> mon = Time60(10,30)
>>> tue = Time60(11,15)
>>> mon
10:30
>>> id(mon)
140125377154768
>>> mon += tue
>>> id(mon)
140125377154768
>>> mon
21:45
隨機序列迭代器
# vi randSeq.py
--------------------------
#!/usr/bin/env python
from random import choice
class RandSeq(object):
def __init__(self, seq):
self.data = seq
def __iter__(self):
return self
def next(self):
return choice(self.data)
r = RandSeq([1,2,3])
print r.next()
--------------------------
# python randSeq.py
1
# python randSeq.py
2
昇華:
中級定製:
# vi Time60.py
--------------------------
#!/usr/bin/env python
class Time60(object):
def __init__(self, hr, min):
self.hr = hr
self.min = min
def __str__(self):
return '%d:%d' % (self.hr, self.min)
__repr__ = __str__
def __add__(self,other):
return self.__class__(self.hr + other.hr, self.min + other.min)
def __iadd__(self, other):
self.hr += other.hr
self.min += other.min
return self
wed = Time60(12,5)
thu = Time60(10,30)
fri = Time60(8,45)
print wed
print (thu + fri)
---------------------------
# python Time60.py
12:5
18:75
13.13.3 迭代器(RandSeq和AnyIter)
RandSeq
from randseq import RandSeq
for eachItem in RandSeq(('rock','paper','scissors')):
print eachItem
例:任意項的迭代器
# vi anyIter.py
-----------------------------
#!/usr/bin/env python
class AnyIter(object):
def __init__(self, data, safe=False):
self.safe = safe
self.iter = iter(data)
def __iter__(self):
return self
def next(self, howmany=1):
retval= []
for eachItem in range(howmany):
try:
retval.append(self.iter.next())
except StopIteration:
if self.safe:
break
else:
raise
return retval
A = AnyIter([1,2,3])
print A.next(3)
a = AnyIter(range(10))
i = iter(a)
for j in range(1,5):
print j,':',i.next(j)
-----------------------------
# python anyIter.py
[1, 2, 3]
1 : [0]
2 : [1, 2]
3 : [3, 4, 5]
4 : [6, 7, 8, 9]
因爲超出了項的支持量,默認safe=False,所以出現了StopIteration異常
>>>a = AnyIter(range(10))
>>>i =iter(a)
>>>i.next(14)
File "anyIter.py", line 13, in next
retval.append(self.iter.next())
StopIteration
如果我們使用"安全(safe)"模式重建迭代器,則會在項失控前得到迭代器所得到的元素
>>>a = AnyIter(range(10),True)
>>>i =iter(a)
>>>print i.next(14)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
13.13.4 多類型定製(NumStr)
初始化,加法,乘法,False值,比較
例:
# vi numstr.py
-------------------------
#!/usr/bin/env python
class NumStr(object):
def __init__(self, num=0,string=''):
self.__num = num
self.__string = string
def __str__(self):
return '[%d :: %r]' % (self.__num,self.__string)
def __add__(self,other):
if isinstance(other,NumStr):
return self.__class__(self.__num + other.__num, \
self.__string + other.__string)
else:
raise TypeError, \
'IIIegal argument type for built-in operation'
def __mul__(self,num):
if isinstance(num,int):
return self.__class__(self.__num * num, self.__string * num)
else:
raise TypeError, \
'IIIegal argument type for built-in operation'
def __nonzero__(self):
return self.__num or len(self.__string)
def __norm_cval(self, cmpres):
return cmp(cmpres,0)
def __cmp__(self, other):
return self.__norm_cval(cmp(self.__num,other.__num)) + \
self.__norm_cval(cmp(self.__string,other.__string))
a = NumStr(3, 'foo')
b = NumStr(3, 'goo')
c = NumStr(2, 'foo')
d = NumStr()
e = NumStr(string='boo')
f = NumStr(1)
print a
print b
print c
print d
print e
print f
print a < b
print b < c
print a == a
print b * 2
print a * 3
print b + e
print e + b
if d: print 'not false'
if e: print 'not false'
print cmp(a,b)
print cmp(a,c)
print cmp(a,a)
---------------------------
# python numstr.py
[3 :: 'foo']
[3 :: 'goo']
[2 :: 'foo']
[0 :: '']
[0 :: 'boo']
True
False
True
[6 :: 'googoo']
[9 :: 'foofoofoo']
[3 :: 'gooboo']
[3 :: 'boogoo']
not false
-1
1
0
13.14 私有化
默認情況下,屬性在Python中都是"public",類所在模塊和導入了沒所在模塊的其他模塊的代碼都可以訪問到
雙下劃線(__)
單下劃線(_)
13.15 授權
13.15.1 包裝
包裝在Python中意思是對一個已存在的對象進行包裝,增加新的,刪除不要的,或者修改其他已存在的功能.
13.15.2 實現授權
實現授權的關鍵點就是覆蓋__getattr__()方法,在代碼中包含一個對getattr()內建函數的調用
包裝對象的簡例:
>>> class WrapMe(object):
... def __init__(self, obj):
... self.__data = obj
... def get(self):
... return self.__data
... def __repr__(self):
... return 'self.__data'
... def __str__(self):
... return str(self.__data)
... def __getattr__(self,attr):
... return getattr(self.__data,attr)
注:屬性可以是數據屬性,還可以是函數或者方法
>>> w = WrapMe(3.5+4.2j)
>>> w
self.__data
>>> print w
(3.5+4.2j)
>>> print w.real
3.5
>>> print w.imag
4.2
>>> print w.conjugate()
(3.5-4.2j)
>>> print w.get
<bound method WrapMe.get of self.__data>
>>> print w.get()
(3.5+4.2j)
使用列表對象執行多種操作:
>>> wm = WrapMe([123, 'foo', 45.67])
>>> wm.append('bar')
>>> wm.append('123')
>>> print wm
[123, 'foo', 45.67, 'bar', '123']
>>> print wm.index(45.67)
2
>>> print wm.count(123)
1
>>> print wm.pop()
123
>>> print wm
[123, 'foo', 45.67, 'bar']
>>> realList = wm.get()
>>> realList[3]
'bar'
>>> realList = wm.get()[3]
>>> realList
'bar'
get()方法返回一個對象,隨後被索引以得到切片片段
>>> f= WrapMe(open('/etc/motd'))
>>> f
self.__data
>>> f.get()
<open file '/etc/motd', mode 'r' at 0x9fa0c28>
>>> f.readline()
'Welcome to Ubuntu 12.04.3 LTS (GNU/Linux 3.8.0-29-generic i686)\n'
>>> f.tell()
64L
>>> f.seek(0)
>>> print f.readline()
Welcome to Ubuntu 12.04.3 LTS (GNU/Linux 3.8.0-29-generic i686)
>>> f.close()
>>> f.get()
<closed file '/etc/motd', mode 'r' at 0x9fa0c28>
>>> print "<%s file %s,mode %s at %x>" % \
... (f.closed and 'closed' or 'open', 'f.name','f.mode',id(f.get()))
<closed file f.name,mode f.mode at 9fa0c28>
更新簡單的包裹類:
例:類定義包裝了任何內建類型,增加時間屬性:get(),set(),還有字符串表示的方法,並授權所有保留的屬性,訪問這些標準類型:
# vi twrapme.py
-----------------------------
#!/usr/bin/env python
from time import time, ctime
class TimedWrapMe(object):
def __init__(self, obj):
self.__data = obj
self.__ctime = self.__mtime = \
self.__atime = time()
def get(self):
self.__atime = time()
return self.__data
def gettimeval(self, t_type):
if not isinstance(t_type,str) or \
t_type[0] not in 'cma':
raise TypeError, \
"argument of 'c','m',or 'a' req'd"
return getattr(self, '_%s__%stime' % \
(self.__class__.__name__,t_type[0]))
def gettimestr(self, t_type):
return ctime(self.gettimeval(t_type))
def set(self, obj):
self.__data = obj
self.__mtime = self.__atime = time()
def __repr__(self):
self.__atime =time()
return 'self.__data '
def __str__(self):
self.__atime = time()
return str(self.__data)
def __getattr__(self, attr):
self.__atime = time()
return getattr(self.__data,attr)
t = TimedWrapMe(932)
print t.gettimestr('c')
print t.gettimestr('m')
print t.gettimestr('a')
print t
t.set('time is up!')
print t.gettimestr('m')
print t
-----------------------------
改進包裝一個特殊對象:
例:包裝文件對象:(問題)
# vi capOpen.py
-------------------------------
#!/usr/bin/env python
class CapOpen(object):
def __init__(self,fn,mode='r',buf=-1):
self.file = open(fn,mode,buf)
def __str__(self):
return str(self.file)
def __repr__(self):
return 'self.file'
def write(self, line):
self.file.write(line.upper())
def __getattr__(self,attr):
return getattr(self.file, attr)
f = CapOpen('/tmp/1','r')
for eachLine in f:
print eachLine,
-------------------------------
13.16 新式類的高級特性(Python 2.2+)
13.16.1 新式類的通用特性
這些特性中最重要的是能夠子類化Python數據類型
下面是內建函數:
int(),long(),float(),complex()
str(),unicode()
list(),tuple()
type()
新的函數:
basestring()
dict()
bool()
set(), frozenset()
object()
classmethod()
staticmethod()
super()
property()
file()
13.16.2 __slots__類屬性:
__dict__屬性跟蹤所有實例屬性,假如你有一個實例inst,它有一個屬性foo,那使用inst.foo來訪問它與使用inst.__dict__['foo']來訪問是一致的
字典會佔據大量內存,如果你有一個屬性數量很少的類,但有很多實例,那麼正好是這種情況,爲內存上的考慮,用戶現在可以使用__slots__屬性來替代__dict__
__slots__是一個類變量,由一序列型對象組成,由所有合法標識構成的實例屬性的集合來標識,它可以是一個列表,元祖或可迭代對象,也可以是標識實例能擁有的唯一的屬性的簡單字符串,任何創建一個其名不在__slots__中的名字的實例屬性都會導致AttibuteError異常:
>>> class SlottedClass(object):
... __slot__ = ('foo', 'bar')
...
>>> c = SlottedClass()
>>> c.foo = 42
>>> c.xxx = "don't think so"
13.16.3 特殊方法__getattribute__()
Python類有一個名爲__getattr__()的特殊方法,它僅當屬性不能再實例的__dict__或它的類(類的__dict__),或者祖先類(其__dict__)中找到時,才被調用
13.16.4 描述符
描述符是標識對象屬性的一個代理,當需要屬性時,可根據你遇到的情況,通過描述符或採用常規方式來訪問它
__get__(),__set__(),__delete__()特殊方法
__getattribute__()特殊方法(二)
優先級別
例1:
-----------------------------------
>>> class DevNulll(object):
... def __get__(self, obj, typ=None):
... pass
... def __set__(self, obj, val):
... pass
...
-----------------------------------
我們建立一個類,這個類使用了這個描述符,給它複製並顯示其值:
-------------------------------------
>>> class C1(object):
... foo = DevNulll()
...
>>> c1 = C1()
>>> c1.foo = 'bar'
>>> print 'c1.foo contains:', cl.foo
c1.foo contains: None
--------------------------------------
例2,在描述符中寫一些輸出語句
--------------------------------------
>>> class DevNull2(object):
... def __get__(self, obj,typ=None):
... print 'Accessing attribute... ignoring'
... def __set__(self, obj,val):
... print 'Attempt to assign %r... ignoring' %(val)
...
--------------------------------------
修改後的結果
-------------------------------------
>>> class C2(object):
... foo = DevNull2()
...
>>> c2 = C2()
>>> c2.foo = 'bar'
Attempt to assign 'bar'... ignoring
>>> x = c2.foo
Accessing attribute... ignoring
>>> print 'c2.foo contains:',x
c2.foo contains: None
-------------------------------------
例3,我們在描述符所在的類中添加一個佔位符,佔位符包含有關於這個描述符的有用信息:
-----------------------------------------
>>> class DevNull3(object):
... def __init__(self, name = None):
... self.name = name
... def __get__(self, obj,typ=None):
... print 'Accessing [%s]... ignoring' %(self.name)
... def __set__(self, obj, val):
... print 'Assining %r to [%s]... ignoring' %(val, self.name)
-----------------------------------------
修改該後的結果:
-------------------------------------------
>>> class C3(object):
... foo = DevNull3('foo')
...
>>> c3 = C3()
>>> c3.foo = 'bar'
Assining 'bar' to [foo]... ignoring
>>> x = c3.foo
Accessing [foo]... ignoring
>>> print 'c3.foo contains:', x
c3.foo contains: None
>>> print 'Let us try to sneak it into c3 instance...'
Let us try to sneak it into c3 instance...
>>> c3.__dict__['foo'] = 'bar'
>>> x = c3.foo
Accessing [foo]... ignoring
>>> print 'c3.foo contains:', x
c3.foo contains: None
>>> print "c3.__dict__['foo'] contains: %r" % \
... c3.__dict__['foo'], "... why?!?"
c3.__dict__['foo'] contains: 'bar' ... why?!?
-------------------------------------------
例4:由於實例屬性比非數據描述符的優先級高,你可以將非數據描述符隱藏,這就和你給一個實例屬性複製,將對應類的同名屬性隱藏起來是一個道理:
-------------------------------------------
>>> class FooFoo(object):
... def foo(self):
... print 'Very important foo() method'
...
>>> bar = FooFoo()
>>> bar.foo()
Very important foo() method
>>> bar.foo = 'It is no longer here.'
>>> bar.foo
'It is no longer here.'
>>>
>>> del bar.foo
>>> bar.foo()
Very important foo() method
-------------------------------------------
例5,我們將foo做爲一個函數調用,然後又將她作爲一個字符串訪問,但我們也可以使用另一個函數,而且保持相同的調用機制:
--------------------------------------------
>>> def barBar():
... print 'foo() hidden by barBar()'
...
>>> bar.foo = barBar
>>> bar.foo()
foo() hidden by barBar()
>>> del bar.foo
>>> bar.foo()
Very important foo() method
--------------------------------------------
例: 使用文件來存儲屬性
# vi descr.py
------------------------------------------
#!/usr/bin/env python
import os
import pickle
class FileDescr(object):
saved = []
def __init__(self, name=None):
self.name = name
def __get__(self,obj, typ=None):
if self.name not in FileDescr.saved:
raise AttributeError, \
"%r used before assignment" % self.name
try:
f = open (self.name, 'r')
val = pickle.load(f)
f.close()
return val
except(pickle,InpicklingError, IOError,\
EOFError, AttributeError, \
ImportError,IndexError),e:
raise AttributeError, \
"could not read %r: %s" % self.name
def __set__(self, obj, val):
f = open(self.name, 'w')
try:
try:
pickle.dump(val, f)
FileDescr.saved.append(self.name)
except (TypeError, pickle.PicklingError),e:
raise AttributeError, \
"could not pickle %r" % self.name
finally:
f.close()
def __delete__(self, obj):
try:
os.unlink(self.name)
FileDe
scr.saved.remove(self.name)
except (OSError,ValueError),e:
pass
class MyFileVarClass(object):
foo = FileDescr('foo')
bar = FileDescr('bar')
fvc = MyFileVarClass()
#print fvc.foo # 會報錯
fvc.foo = 42
fvc.bar = 'leanna'
print fvc.foo, fvc.bar
del fvc.foo
#print fvc.foo, fvc.bar # 會報錯
------------------------------------------
描述符總結
屬性和property()內建函數
property()內建函數有四個參數:
property(fget=None, fset=None, fdel=None, doc=None)
property()用法是將它卸載一個類定義中,property()接受一些傳進來的函數作爲參數,實際上,property()實在它所在的類被創建時被調用的,這些傳進來的方法是非綁定的,所以這些方法其實就是函數.
下面的一個例子:
在類中建立一個只讀的整數屬性,用逐位異或操作符將它隱藏起來:
>>> class ProtectAndHideX(object):
... def __init__(self,x):
... assert isinstance(x, int), \
... '"x" must be an integer!'
... self.__x = ~x
... def get_x(self):
... return ~self.__x
... x = property(get_x)
它只保存我們第一次給出的值,而不允許我們對它做第二次修改:
>>> inst = ProtectAndHideX('foo')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in __init__
AssertionError: "x" must be an integer!
>>> inst = ProtectAndHideX(10)
>>> print 'inst.x = ', inst.x
inst.x = 10
>>> inst.x = 20
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
另一個關於setter的例子:
>>> class HideX(object):
... def __init__(self,x):
... self.x = x
... def get_x(self):
... return ~self.__x
... def set_x(self,x):
... assert isinstance(x, int), \
... '"x" must be an integer!'
... self.__x = ~x
... x =property(get_x, set_x)
本示例的輸出結果:
>>> inst = HideX(20)
>>> print inst.x
20
>>> inst = HideX(30)
>>> print inst.x
30
>>> inst.set_x(40)
>>> print inst.x
40
屬性成功保存到x中並顯示出來,是因爲在調用構造器給x賦初始值前,在getter中已經將~x賦給了self.__x.
例:給屬性添加一個文檔字符串
>>> from math import pi
>>> def get_pi(dummy):
... return pl
...
>>> class PI(object):
... pi = property(get_pi, doc='Constant "pi"')
我們在perperty中使用一個函數,調用函數時self作爲第一個參數被傳入,所以我們必須加一個僞變量把self丟棄,下面是本例的輸出:
>>> inst = PI()
>>> inst.pi
3.141592653589793
>>> print PI.pi.__doc__
Constant "pi"
你不必寫一個描述符類,只要把你寫的函數(或方法)全部傳遞給property()就可
以了
通過使用屬性property()來訪問屬性:
>>> class HideX(object):
... def __init__(self,x):
... self.x = x
... @property
... def x():
... def fget(self):
... return ~self.__x
... def fset(self,x):
... assert isinstance(x, int), \
... '"x" must be an integer!'
... self__ = ~x
... return locals()
13.16.5 元類和__metaclass__
元類(Metaclasses)
元類讓你來定義某個類是如何被創建的,從根本上說,賦予你如何創建類的控制權.你可以把元類想成一個類中類,或者類,它的實例時其他的類
>>> class C(object):
... pass
...
>>> class CC:
... pass
...
>>> type(C)
<type 'type'>
>>> type(CC)
<type 'classobj'>
>>> import types
>>> type(CC) is types.ClassType
True
什麼時候使用元類?
元類一般用於創建類,在執行類定義時,解釋器必須要知道這個類的正確的元類
解釋器會先尋找類屬性__metaclass__,如果屬性存在,就將這個屬性賦給此類作爲他的元類,如果此屬性沒有定義,他會想上查找父類中的__metaclass__
如果還沒發現__metaclass__屬性,解釋器會檢查名字爲__metaclass__的全局變量
誰在用元類?
元類何時被創建?
元類示例1,創建一個類,顯示時間標籤
# vi test34.py
-------------------------------
#!/usr/bin/env python
from time import ctime
print '*** Welcome to Metaclasses!'
print '\tMetaclass declaration first.'
class MetaC(type):
def __init_(cls,name,bases,attrd):
super(MetaC, cls).__init__(name, bases, attrd)
print '*** Created class %r at: %s' %(name, ctime())
print '\tClass "Foo" declaration next.'
class Foo(object):
__metaclass__ = MetaC
def __init__(self):
print '*** Instantiated class %r at: %s' \
%(self.__class__.__name__, ctime())
print '\tClass "Foo" instantiation next'
f = Foo()
print '\tDONE'
-------------------------------
輸出:
# python test34.py
--------------------------------------
*** Welcome to Metaclasses!
Metaclass declaration first.
Class "Foo" declaration next.
Class "Foo" instantiation next
*** Instantiated class 'Foo' at: Tue Dec 3 06:29:40 2013
DONE
---------------------------------------
元素示例2,創建一個元類,在類中提供一個__str__()方法(問題)
# vi meta.py
------------------------------------
#!/usr/bin/env python
from warnings import warn
class ReqStrSugRepr(type):
def __init__(cls, name, bases, attrd):
super(ReqStrSugRepr, cls).__init__(name,bases,attrd)
if '__str__' not in attrd:
raise TypeError("Class requires overriding of __str__()")
if '__repr__' not in attrd:
warn('Class suggests overriding of __repr__()\n', stacklevel=3)
print '*** Defined ReqStrSugRepr (meta)class\n'
class Foo(object):
__metaclass__ = ReqStrSugRepr
def __str__(self):
return 'Instance of class:', \
self.__class__.__name__
def __repr__(self):
return self.__class__.__name__
print '*** Defined Foo class\n'
class Bar(object):
__metaclass__ = ReqStrSugRepr
def __str__(self):
return 'Instance of class:', \
self.__class__.__name__
print '*** Defined Bar class\n'
class FooBar(object):
__metaclass__ = ReqStrSugRepr
print '*** Defined FooBar class\n'
------------------------------------
13.17 相關模塊和文檔:
下面的代碼檢查傳遞到foo函數的數據對象是否是一個整數或者一個字符串,不允許其他類型出現(否則會引發一個異常):
>>> def foo(data):
... if isinstance(data, int):
... print 'you entered an integer'
... elif isinstance(data, str):
... print 'you entered a string'
... else:
... raise TypeError, 'only integers or strings!'
以下代碼定義了三個向量, 前兩個包含着操作數, 最後一個代表程序員打算對兩個操作數進行一系列操作, 最外層循環遍歷每個操作運算,而最內層的兩個循環用每個操作數向量中的元素組成各種可能的有序數列對
最後 print 語句打印出將當前操作符應用在給定參數上所得的運算結果
>>> from operator import *
>>> vec1 = [12,24]
>>> vec2 = [2, 3, 4]
>>> opvec = (add, sub, mul,div)
>>> for eachOp in opvec:
... for i in vec1:
... for j in vec2:
... print '%s(%d, %d) = %d' % \
... (eachOp.__name__, i, j, eachOp(i, j))
...
add(12, 2) = 14
add(12, 3) = 15
add(12, 4) = 16
add(24, 2) = 26
add(24, 3) = 27
add(24, 4) = 28
sub(12, 2) = 10
sub(12, 3) = 9
sub(12, 4) = 8
sub(24, 2) = 22
sub(24, 3) = 21
sub(24, 4) = 20
mul(12, 2) = 24
mul(12, 3) = 36
mul(12, 4) = 48
mul(24, 2) = 48
mul(24, 3) = 72
mul(24, 4) = 96
div(12, 2) = 6
div(12, 3) = 4
div(12, 4) = 3
div(24, 2) = 12
div(24, 3) = 8
div(24, 4) = 6