13.1 介紹 python
類與實例 程序員
類與實例相互關聯着:類是對象的定義,而實例是"真正的實物",它存放了類中所定義的對象
的具體信息。 算法
>>> class MyData(): pass >>> mathObj = MyData() >>> mathObj.x = 4 >>> mathObj.y = 5 >>> mathObj.x + mathObj.y 9 >>> mathObj.x * mathObj.y 20這裏注意一點是:x,y不是類MyData的屬性,它們是實例對象mathObj的獨有屬性,而且是動態的:你不須要在構造器中,或其它任何地方爲它們預先聲明或者賦值。
方法 shell
方法爲類的屬性,除了靜態方法(我的理解爲靜態方法爲函數),方法必須有實例來調用。 數據庫
>>> class MyDataWithMethod(object): def printFoo(self): print "you invoked printFoo()!" >>> oneObj = MyDataWithMethod() >>> oneObj.printFoo() you invoked printFoo()!特殊的參數:self。參數self偏偏代表了方法是類的屬性,方式的調用經過self來關聯到類中。
咱們來看一個稍微完整的類的定義及其應用: 編程
類的定義: 數組
>>> class AddrBookEntry(object): "address book entry class" def __init__(self, nm, ph): self.name = nm self.phone = ph print "created instance for:", self.name def updatePhone(self, newph): self.phone = newph print "updated phone# for:", self.name建立實例(實例化)
>>> john = AddrBookEntry("john doe","408-555-1212") created instance for: john doe >>> jane = AddrBookEntry("jane doe","650-555-1212") created instance for: jane doe你會發現,當建立實例的時候,__init__會自動被調用
訪問實例屬性 安全
>>> john <__main__.AddrBookEntry object at 0x020D8190> >>> john.name 'john doe' >>> john.phone '408-555-1212' >>> jane.name 'jane doe' >>> jane.phone '650-555-1212'方法調用(經過實例)
>>> john.updatePhone("415-555-1212") updated phone# for: john doe >>> john.phone '415-555-1212'建立子類
靠繼承來進行子類化是建立和定製新類類型的一種方式,新的類將保持已存在類全部的特性,而不會改動原來類的定義。而且子類能夠定製本身的方法。 數據結構
>>> class EmplAddrBookEntry(AddrBookEntry): "employee 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, "john@spam.doe") created instance for: john doe >>> john <__main__.EmplAddrBookEntry object at 0x02115FD0> >>> john.name 'john doe' >>> john.phone '408-555-1212' >>> john.email 'john@spam.doe' >>> john.updatePhone("415-555-1212") updated phone# for: john doe >>> john.phone '415-555-1212' >>> john.updateEmail("john@doe.spam") updated e-mail address for: john doe >>> john.email 'john@doe.spam'
13.2 面向對象編程 app
經常使用術語
抽象/實現
抽象指對現實世界問題和實體的本質表現,行爲和特徵建模,創建一個相關的子集,能夠用於描繪程序結構,從而實現這種模型。抽象不只包括這種模型的數據屬性,還定義了這些數據的接口。對某種抽象的實現就是對此數據及與之相關接口的現實化。現實化這個過程對於客戶程序應當是透明並且無關的。
封裝/接口
封裝描述了對數據/信息進行隱藏的觀念,它對數據屬性提供接口和訪問函數。因爲python中,全部的類屬性都是公開的,因此在設計時,對數據提供相應的接口,以避免客戶程序經過不規範的操做來存取封裝的數據屬性。
合成
合成擴充了對類的描述,使得多個不一樣的類合成爲一個大的類,來解決現實問題。
派生/繼承/繼承結構
派生描述了子類的建立,新類保留已存類類型中全部須要的數據和行爲,但容許修改或者其它的自定義操做,都不會修改原類的定義。繼承描述了子類屬性從祖先類繼承這樣一種方式。
泛化/特化
泛化表示全部子類與其父類及祖先類有同樣的特定。而特化描述全部子類的自定義,即什麼屬性讓它與其祖先類不一樣。
多態
多態的概念指出了對象如何經過他們共同的屬性和動做來操做及訪問,而不須要考慮他們具體的類。多態代表了動態綁定的存在,容許重載及運行時類型肯定和驗證。
自省/反射
代表程序員運行期檢查的能力。
13.3 類
13.3.1 建立類
>>> class ClassName(object): "class documentation string" pass基類是一個或多個用於繼承的父類的集合;類體由全部聲明語句,類成員定義,數據屬性和函數組成。類一般在一個模塊的頂層進行定義,以便類實例可以在類所定義的源代碼文件中的任何地方被建立。
在python中有點特別的重要:聲明和定義是一塊兒的。
13.4 類屬性
屬性就是屬於另外一個對象的數據或者函數元素,能夠經過咱們熟悉的句點屬性標識來訪問。一些python類型好比複數有數據屬性(實部和虛部),而列表和字典,擁有方法(函數屬性)
關於屬性,有個有趣的地方是:當你訪問一個屬性時,它同時也是一個對象,擁有它本身的屬性,能夠訪問,這致使一個屬性鏈:
sys.stdout.write("foo") print myModule.myClass.__doc__ myList.extend(map(upper, open("x").readlines()))13.4.1 類的數據屬性和方法
數據屬性僅僅是所定義的類的變量。它們能夠像任何其它變量同樣在類建立後被使用,而且被更新。在C++或者Java中就是靜態變量。數據屬性表示這些數據是與它們所屬的類對象綁定的,不依賴於任何類實例。
>>> class C(object): foo = 100 >>> C.foo 100 >>> C.foo += 1 >>> C.foo 101方法僅僅是一個做爲類定義一部分定義的函數(這使得方法成爲類屬性),而且只能經過實例來調用(我並無把靜態方法和類方法看成方法,僅僅認爲它們只是函數---獨立於類對象)
>>> class MyClass(object): def myMethod(self): print "hello world" >>> oneObj = MyClass() >>> oneObj.myMethod() hello world >>> MyClass.myMethod() Traceback (most recent call last): File "<pyshell#69>", line 1, in <module> MyClass.myMethod() TypeError: unbound method myMethod() must be called with MyClass instance as first argument (got nothing instead)13.4.2 決定類的屬性
咱們可使用方法來查看類的屬性:dir()和__dict__
>>> class MyClass(object): "myclass class definition" myVersion = "1.1" def showMyVersion(self): print MyClass.myVersion >>> 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 0x02114C70>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, 'myVersion': '1.1', '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': 'myclass class definition'})13.4.3 特殊的類屬性
C.__name__ 類C的名字(字符串)
C.__doc__ 類C的文檔字符串
C.__bases__ 類C的全部父類構成的元組
C.__dict__ 類C的屬性
C.__module__ 類C定義所在的模塊(1.5 版本新增)
C.__class__ 實例C對應的類(僅新式類中)
>>> MyClass.__name__ 'MyClass' >>> MyClass.__doc__ 'myclass class definition' >>> MyClass.__bases__ (<type 'object'>,) >>> MyClass.__module__ '__main__' >>> MyClass.__class__ <type 'type'>
13.5 實例
若是說類是一種數據結構定義類型,那麼實例則聲明瞭一個這種類型的變量。
13.5.1 初始化:經過調用類對象來建立實例
>>> class MyClass(object): pass >>> mc = MyClass()13.5.2 __init__()「構造器」方法
當類被調用,實例化的第一步是建立實例對象。一旦對象建立了,Python 檢查是否實現了__init__()方法。默認狀況下,若是沒有定義(或覆蓋)特殊方法__init__(),對實例不會施加任何特別的操做.任何所需的特定操做,都須要程序員實現__init__(),覆蓋它的默認行爲。若是__init__()沒有實現,則返回它的對象,實例化過程完畢。
然而,若是__init__()已經被實現,那麼它將被調用,實例對象做爲第一個參數(self)被傳遞進去,像標準方法調用同樣。調用類時,傳進的任何參數都交給了__init__()。實際中,你能夠想像成這樣:把建立實例的調用當成是對構造器的調用。
13.5.3 __new__()"構造器"方法
咱們能夠經過__new__()來實例化不可變對象。
13.5.4 __del__()"解構器"方法
咱們來經過跟蹤實例來理解__del__()的意義:
>>> class InstCt(object): count = 0 def __init__(self): InstCt.count += 1 def __del__(self): InstCt.count -= 1 def howMany(self): return InstCt.count >>> a = InstCt() >>> b = InstCt() >>> b.howMany() 2 >>> a.howMany() 2 >>> del b >>> a.howMany() 1 >>> del a >>> InstCt.count 0
13.6 實例屬性
實例僅擁有數據屬性(方法嚴格來講是類屬性),當一個實例被釋放後,它的屬性同時也被清除了。
13.6.1 「實例化」實例屬性(或建立一個更好的構造器)
設置實例的屬性能夠在實例建立後任意時間進行,也能夠在可以訪問實例的代碼中進行。構造器__init__是設置這些屬性的關鍵點之一。
在構造器中首先設置實例屬性
構造器是最先能夠設置實例屬性的地方,由於__init__()是實例建立後第一個調用的方法。一旦__init__()執行完畢,返回實例對象,即完成了實例化過程。
默認參數提供默認的實例安裝,且默認參數必須是不變得對象。
>>> class Person(object): def __init__(self, name = "voler", age = 24): self.name = name self.age = age def show(self): print "name is:%s and age is:%d" % (self.name, self.age) >>> onePerson = Person() >>> onePerson.show() name is:voler and age is:24備註:__init__()返回None
13.6.2 查看實例屬性 + 13.6.3 特殊的實例屬性
dir(),__dict__,__class__
>>> class C(object): def __init__(self, x = 0, y = 0): self.x = x self.y = y >>> c = C() >>> dir(c) ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'x', 'y'] >>> c.__dict__ {'y': 0, 'x': 0} >>> c.__class__ <class '__main__.C'>13.6.5 實例屬性VS類屬性
訪問類屬性
類屬性可經過類或實例來訪問。固然,經過實例訪問一個類屬性時,只是創建一個臨時的類屬性引用,當實例消失後,對應的類屬性也消失。
>>> class C(object): version = 1.2 >>> c = C() >>> C.version 1.2 >>> c.version 1.2 >>> C.version += 0.1 >>> C.version 1.3 >>> c.version 1.3 >>> c.version += 0.1 >>> c.version 1.4000000000000001 >>> C.version 1.3可是,若是類屬性可變的話,一切都不一樣了:
>>> 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 "<pyshell#142>", line 1, in <module> del foo.x AttributeError: 'Foo' object attribute 'x' is read-only >>> del foo因此,並不推薦使用實例來訪問類屬性。雖然方法也被稱爲類屬性,但方法特殊就特殊在於:它經過self參數綁定到了實例對象去了,因此方法一般要用到實例來調用,用類調用不行-----緣由也挺簡單的,方法中操做的是實例數據,通常不多操做類數據屬性,因此要用實例來調用。
13.7 從這裏開始校對----綁定和方法調用
方法三要點:
首先,方法僅僅是類內部定義的函數(這意味着方法是類屬性而不是實例屬性)
其次,方法只有在其所屬的類擁有實例時,才能被調用。當存在一個實例時,方法才被認爲是綁定到那個實例來。沒有實例時方法就是未綁定的。
最後,任何一個方法定義中的第一個參數就是變量self,它表示調用此方法的實例對象。
核心筆記:self是什麼?
self變量用於在類實例方法中引用方法所綁定的實例。由於方法的實例在任何方法調用中老是做爲第一個參數傳遞的,self被選中用來表明實例。你必須在方法聲明中放上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
13.8 靜態方法和類方法
並不推薦使用靜態方法,通常來講,咱們可使用模塊函數來達到目的。
13.8.1 staticmethod()和classmethod()內建函數
class TestStaticMethod(object): def foo(): print "calling static method foo()" foo = staticmethod(foo) class TestClassMethod(object): def foo(cls): print "calling class method foo()" print "foo() is part of class:", cls.__name__ foo = clasmethod(foo)
>>> 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: TestClassMethod13.8.2 使用函數修飾符(也稱爲裝飾器)
class TestStaticMethod(object): @staticmethod def foo(): print "calling static method foo()" class TestClassMethod(object): @classmethod def foo(cls): print "calling class method foo()" print "foo() is part of class:", cls.__name__
13.9 組合
寫在前面:組合優於派生
一個類被定義後,目標就是要把它當成一個模塊來使用,並把這些對象嵌入到你的代碼中去,同其它數據類型及邏輯執行流混合使用。有兩種方法能夠在你的代碼中利用類。第一種是組合,就是讓不一樣的類混合並加入到其它類中,來增長功能和代碼重用性。你能夠在一個大點的類中建立你本身的類的實例,顯示一些其它的屬性和方法來加強原來的類對象。另外一種方法是經過派生。
13.10 子類和派生
當類之間有顯著不一樣,而且較小的類是較大的類所須要的組件時,組合表現的很好,噹噹你設計「相同的類但又一些不一樣的功能」時,派生就是一個更加合理的選擇了。
OOP的更強大方面之一是可以使用一個已經定義好的類,擴展它或者對其進行修改,而不會影響系統中使用現存類的其餘代碼片斷。
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 Parent(object): def __init__(self): self.x = 1 class Child(Parent): pass c = Child() print c.x程序輸出:1
13.11.1 __base__類屬性
__base__類屬性包含其父類的集合的元祖:
class A(object): pass class B(A): pass class C(B): pass class D(B,A): pass程序輸出:
>>> A.__bases__ (<type 'object'>,) >>> B.__bases__ (<class '__main__.A'>,) >>> C.__bases__ (<class '__main__.B'>,) >>> D.__bases__ (<class '__main__.B'>, <class '__main__.A'>)這裏有點須要注意:D必須先繼承B,才能繼承A,由於繼承是廣度優先,深度其次,因此要先繼承到小的父類,最後纔是大的父類。
13.11.2 經過繼承覆蓋方法
子類的方法覆蓋掉父類的方法很正常,可是當你想調用父類的方法的時候,只要經過父類類名直接調用便可,固然記得傳遞一個對象進去。
class P(object): def foo(self): print "parent" class C(P): def foo(self): print "child" c = C() print P.foo(c)程序輸出:
>>> parent None這裏怎麼多出一個None呢?很簡單,任何一個方法都會返回一個對象(__init__()會返回一個None)。咱們這樣修改的話,None就會消失:
class P(object): def foo(self): return "parent" class C(P): def foo(self): return "child" c = C() print P.foo(c)不過一般狀況下,咱們會這樣編碼:
class P(object): def foo(self): print "parent" class C(P): def foo(self): super(C, self).foo() print "child" c = C() c.foo()程序輸出;
>>> parent child備註:重寫__init__不會自動調用基類的__init__,因此請添加相似下面的代碼:
class C(P): def __init__(self): super(C, self).__init__()這裏用super的漂亮之處在於:你不須要寫出任何基類的名字(當你繼承了不少基類的時候你會感謝super的)
13.11.3 從標準類型派生
子類化python類型之不可變類型
咱們定製一個類,繼承於float,可是控制小數位爲2位:
class RoundFloat(float): def __new__(cls, val): return float.__new__(cls, round(val, 2))固然,用super更好:
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子類化python類型之可變類型
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:", [key for key in d] print "by keys():", d.keys()程序輸出:
>>> by iterator: ['zheng-cai', 'xin-yi', 'hui-jun'] by keys(): ['hui-jun', 'xin-yi', 'zheng-cai']13.11.4 多重繼承
方法解釋順序(MRO)
在python2.2之前的版本,算法很是簡單:深度優先,從左至右進行搜索,取得在子類中使用的屬性。
咱們先來看看經典類的MRO:
class P1: def foo(self): print "called P1-foo()" class P2: 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 ==> P1 called P1-foo() >>> gc.bar() # GC ==> C1 ==> P1 ==> P2 called P2-bar()但新式類就不一樣了,廣度優先,從左至右(將上述代碼中繼承object便可成爲新式類):
>>> gc = GC() >>> gc.foo() # GC ==> C1 ==> C2 ==> P1 called P1-foo() >>> gc.bar() # GC ==> C1 ==> C2 called C2-bar()新式類有一個__mro__屬性,告訴你查找順序是怎樣的:
>>> GC.__mro__ (<class '__main__.GC'>, <class '__main__.C1'>, <class '__main__.C2'>, <class '__main__.P1'>, <class '__main__.P2'>, <type 'object'>)
經典類的MRO問題根源在於:當你深度優先的時候,一旦到達object(全部類的基類)而且找到你想找的(好比__init__()),那麼你就會放棄右邊的類(從左至右)的方法(可是我寫的代碼結果竟然和書上的結果不同,可能python2.7進行了優化吧,下面代碼中理應不可能有輸出的)
class B(object): pass class C(object): def __init__(self): print "the default constructor" class D(B,C): pass
>>> d = D() the default constructor
13.12 類,實例和其餘對象的內建函數
13.12.1 issubclass()
issubclass()布爾函數判斷一個類是不是另外一個類的子類或子孫類:
issubclass(sub, sup),固然,sup能夠爲一個父類組成的元祖
>>> issubclass(int, object) True
13.12.2 isinstance()
isinstance()布爾函數在斷定一個對象是不是另外一個給定類的實例時,很是有用。
isinstance(obj1, obj2)
>>> 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(4, int) True >>> isinstance(4, str) False >>> isinstance("hello", str) True13.12.3 hasattr(), getattr(), setattr(), delattr()
hasattr()函數是boolean型的,它的目的就是爲了決定一個對象是否有一個特定的屬性,通常用於訪問某屬性前先作一個檢查。getattr()和setattr()函數相應的取得和賦值給對象的屬性,getattr()會在你試圖讀取一個不存在的屬性時,引起AttributeError異常,除非給出那個可選的默認參數。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 "<pyshell#30>", line 1, in <module> getattr(myInst,"bar") AttributeError: 'myClass' object has no attribute 'bar' >>> setattr(myInst, "bar","my attr") >>> dir(myInst) ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bar', 'foo'] >>> myInst.__dict__ {'foo': 100, 'bar': 'my attr'} >>> delattr(myInst,"foo") >>> myInst.__dict__ {'bar': 'my attr'}
13.12.4 dir()
我以爲dir惟一的好處就是:當你對一個模塊不熟悉,又要調用裏面的方法時候,能夠用到dir.
13.12.5 super()
super()的主要用途就是:查找父類的屬性,而且出奇的方便。
13.12.6 vars()
vars()內建函數與dir()類似,只是給定的對象參數都必須有一個__dict__屬性。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(4.22) >>> rfm <__main__.RoundFloatManual object at 0x021F8510> >>> print rfm <__main__.RoundFloatManual object at 0x021F8510>因此,咱們得重寫__str__屬性,畢竟print關係到的是str()。固然,咱們順便也把__repr__給重寫了。
當咱們沒有重寫__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程序輸出:
>>> rfm = RoundFloatManual(4.2234) >>> rfm <__main__.RoundFloatManual object at 0x02078510> >>> print rfm 4.22當咱們重寫__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(4.2234) >>> rfm 4.22 >>> print rfm 4.2213.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) __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程序輸出:
>>> time1 = Time60(4,5) >>> time2 = Time60(6,7) >>> time1 + time2 10:12 >>> time1 += time2 >>> time1 10:12這裏惟一要注意到是:self.__class__的使用,它會實例化self.
最後咱們來看個隨機序列迭代器的例子:
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)程序輸出:
>>> randObj = RandSeq([1,2,3,4,5,6]) >>> for i in range(10): print randObj.next(), 6 4 2 4 3 3 2 3 2 3
13.13.3 迭代器
上述例子中,__init__()方法執行賦值操做,而__iter__()僅返回self,這就是如何將一個對象聲明爲迭代器的方式,最後,調用next()來獲得迭代器中連續的值。
咱們來編寫一個任意項的迭代器:
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(range(10)) >>> for j in range(1,5): print j,":", a.next(j) 1 : [0] 2 : [1, 2] 3 : [3, 4, 5] 4 : [6, 7, 8, 9]出異常的輸出:
>>> a.next(14) Traceback (most recent call last): File "<pyshell#4>", line 1, in <module> a.next(14) File "C:\Python27\hello.py", line 11, in next retval.append(self.iter.next()) StopIteration對異常進行處理的輸出:
>>> a = AnyIter(range(10), True) >>> a.next(14) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]13.13.4 多類型定製(NumStr)
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) __repr__ = __str__ def __add__(self, other): if isinstance(other, NumStr): return self.__class__(self.__num + other.__num, self.__string + other.__string) else: raise TypeError, "illegal 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,"ilegal 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) >>> a [3::'foo'] >>> a < b True >>> a == a True >>> b * 2 [6::'googoo'] >>> b + c [5::'goofoo'] >>> cmp(a,b) -1 >>> cmp(b,c) 1 >>> cmp(a,a) 0
13.14 私有化
在變量名加雙下劃線,這是簡單的數據私有化方法,不過我的並不推薦。
13.15 受權
13.15.1 包裝
對一個已存在的對象進行包裝,無論它是數據類型,仍是一段代碼,能夠是對一個已存在的對象,增長新的,刪除不要的,或者修改其它已存在的功能。
13.15.2 實現受權
受權是包裝的一個特性,可用於簡化處理有關dictating功能,採用已存在的功能以達到最大限度的代碼重用。
包裝一個類型一般是對已存在的類型的一些定製,這種作法能夠新建,修改或刪除原有產品的功能。其它的保持原樣,或者保留已存功能和行爲。受權的過程,便是全部更新功能都是由昕蕾的某部分來處理,但已存在的功能就受權給對象的默認屬性。
實現受權的關鍵點就是覆蓋__getattr__()方法,在代碼中包含一個對getattr()內建函數的調用。特別地,調用getattr()以獲得默認對象屬性(數據屬性或者方法)並返回它以便訪問或調用。特殊方法__getattr__()的工做方式是,當搜索一個屬性時,任何局部對象首先被找到(定製的對象)。若是搜索失敗了,則__getattr__()會被調用,而後調用getattr()獲得一個對象的默認行爲。
下面是包裝對象的簡例:
class WrapMe(object): def __init__(self, obj): self.__data = obj def get(self): return self.__data def __repr__(self): return repr(self.__data) def __str__(self): return str(self.__data) def __getattr__(self, attr): return getattr(self.__data, attr)注意程序的輸出:
>>> wrappedComplex = WrapMe(3.5+4.2j) >>> wrappedComplex (3.5+4.2j) >>> wrappedComplex.real 3.5 >>> wrappedComplex.imag 4.2 >>> wrappedComplex.conjugate() (3.5-4.2j) >>> wrappedComplex.get() (3.5+4.2j)這裏,咱們調用複數的三種屬性,可是這三種屬性在類的定義中並不存在。對這些屬性的訪問,是經過getattr()方法,受權給對象。最終調用get()方法沒有受權,由於它是爲咱們的對象定義的--它返回包裝的真實的數據對象。
>>> wrappedList = WrapMe([123, 'foo', 45.67]) >>> wrappedList.append('bar') >>> wrappedList.append(123) >>> wrappedList [123, 'foo', 45.67, 'bar', 123] >>> wrappedList.index(45.67) 2 >>> wrappedList.count(123) 2 >>> wrappedList.pop() 123 >>> wrappedList [123, 'foo', 45.67, 'bar']可是咱們爲何要定義get()函數呢?只是這樣咱們就能夠取得原對象,進行某些特殊的操做,好比:
>>> wrappedList [123, 'foo', 45.67, 'bar'] >>> wrappedList[3] Traceback (most recent call last): File "<pyshell#35>", line 1, in <module> wrappedList[3] TypeError: 'WrapMe' object does not support indexing >>> wrappedList.get()[3] 'bar'更新簡單的包裹類
from time import time, ctime class TimedWrapMe(object): def __init__(self, obj): self.__data = obj self.__ctime = self.__mtime = self.__atime = time() def get(sefl): 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' required" 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 repr(self.__data) def __str__(self): self.__atime = time() return str(self.__data) def __getattr__(self, attr): self.__atime = time() return getattr(self.__data, attr)
>>> timeWrappedObj = TimedWrapMe(932) >>> timeWrappedObj.gettimestr('c') 'Tue Jun 18 19:49:03 2013' >>> timeWrappedObj.gettimestr('m') 'Tue Jun 18 19:49:03 2013' >>> timeWrappedObj.gettimestr('a') 'Tue Jun 18 19:49:03 2013' >>> timeWrappedObj 932 >>> timeWrappedObj.set('time is up!') >>> timeWrappedObj 'time is up!' >>> timeWrappedObj.gettimestr('c') 'Tue Jun 18 19:49:03 2013' >>> timeWrappedObj.gettimestr('m') 'Tue Jun 18 19:49:43 2013' >>> timeWrappedObj.gettimestr('a') 'Tue Jun 18 19:49:57 2013'
13.16 新式類的高級特性
13.16.1 新式類的通用特性
工廠函數的誕生:
int(), long(), float(), complex(),str(), unicode(),list(), tuple(), type(),basestring(), dict(), bool(), set(), frozenset(),object(), classmethod(), staticmethod(), super(), property(), file()
在判斷類型的時候:
old:
if type(obj) == type(0)
if type(obj) == types.IntType
Better:
if type(obj) is type(0)
even better:
if isinstance(obj, int)
if isinstance(obj, (int,long))
if type(obj) is int
13.16.2 __slots__類屬性
字典位於實例的「心臟」。__dict__屬性跟蹤全部實例屬性。好比一個實例inst,它有個屬性foo,那使用inst.foo來訪問它等價於inst.__dict__["foo"]
字典會佔據大量內存,若是你有一個屬性數量不多的類,但有不少實例,那麼正好是這種狀況。爲內存上的考慮,用戶如今可使用__slots__屬性來替代__dict__。
基本上,__slots__是一個類變量,由一序列型對象組成,由全部合法標識構成的實例屬性的集合來表示。它能夠是一個列表,元組或可迭代對象。也能夠是標識實例能擁有的惟一的屬性的簡單字符串。任何試圖建立一個其名不在__slots__中的名字的實例屬性都將致使AttributeError 異常:
>>> class SlottedClass(object): __slots__ = ("foo","bar") >>> c = SlottedClass() >>> c.foo = 42 >>> c.xxx = "do not think so" Traceback (most recent call last): File "<pyshell#52>", line 1, in <module> c.xxx = "do not think so" AttributeError: 'SlottedClass' object has no attribute 'xxx'這種特性的主要目的是節約內存,其反作用是某種類型的「安全」,它能防止用戶爲所欲爲的動態增長實例屬性。
python類有一個名爲__getattr__()的特殊方法,它僅當屬性不能在實例的__dict__或它的類(類的__dict__),或者祖先類(其__dict__)中找到時,才被調用。
__getattribute__的做用是:當屬性被訪問時,它就一直均可以被調用,而不侷限於不能找到的狀況。
13.16.4 描述符
描述符表示對象屬性的一個代理。當須要屬性時,可經過描述符或者採用常規方式(句點屬性標識法)來訪問它。
__get__(),__set__(),__delete__()特殊方法
__getattribute__()特殊方法
使用描述符的順序很重要,有一些描述符的級別要高於其它的。整個描述符系統的心臟是__getattribute__(),由於對每一個屬性的實例都會調用到這個特殊的方法。這個方法被用來查找類的屬性,同時也是你的一個代理,調用它能夠進行屬性的訪問等操做。
若是一個實例調用了__get__()方法,這就可能傳入了一個類型或類的對象。舉例來講,給定類X 和實例x, x.foo 由__getattribute__()轉化成:
type(x).__dict__['foo'].__get__(x, type(x))若是類調用了__get__()方法,那麼None 將做爲對象被傳入(對於實例, 傳入的是self):
X.__dict__['foo'].__get__(None, X)最後,若是super()被調用了,好比,給定Y 爲X 的子類,而後用super(Y,obj).foo 在obj.__class__.__mro__中緊接類Y 沿着繼承樹來查找類X,而後調用:
X.__dict__['foo'].__get__(obj, X)而後,描述符會負責返回須要的對象。
優先級別
1. 類屬性
2. 數據描述符
3. 實例屬性
4. 非數據描述符
5. 默認爲__getattr__()
描述符是一個類屬性,所以全部的類屬性皆具備最高的優先級。你其實能夠經過把一個描述符的引用賦給其它對象來替換這個描述符。比它們優先級別低一等的是實現了__get__()和__set__()方法的描述符。若是你實現了這個描述符,它會像一個代理那樣幫助你完成全部的工做!
不然,它就默認爲局部對象的__dict__的值,也就是說,它能夠是一個實例屬性。接下來是非數據描述符。
描述符舉例:
>>> class DevNull1(object): def __get__(self, obj, typ = None): pass def __set__(self, obj, val): pass >>> class C1(object): foo = DevNull1() >>> c1 = C1() >>> c1.foo = "bar" >>> c1.foo >>> print c1.foo None任何一件事情,剛開始的時候,老是索然無趣,並且簡單。後面則慢慢的。。。。。。
>>> class DevNull2(object): def __get__(self, obj, typ = None): print "accessing attribute...ignoring" def __set__(self, obj, val): print "attempt to asign %r...ignoring" % (val) >>> class C2(object): foo = DevNull2() >>> c2 = C2() >>> c2.foo = "bar" attempt to asign 'bar'...ignoring >>> x = c2.foo accessing attribute...ignoring >>> print x None忽然,就這樣變得有趣起來了。那麼,來點更刺激的吧:
>>> 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 "assigning %r to [%s]...ignoring" % (val, self.name) >>> class C3(object): foo = DevNull3("foo") >>> c3 = C3() >>> c3.foo = "bar" assigning 'bar' to [foo]...ignoring >>> x = c3.foo accessing [foo]...ignoring >>> print x None >>> c3.__dict__["foo"] = "bar" >>> x = c3.foo accessing [foo]...ignoring >>> print x None >>> print c3.__dict__["foo"] bar最後兩個輸出很特殊,說明了數據描述符比實例屬性的優先級高,所賦的值「bar」被隱藏或覆蓋了。
一樣地,因爲實例屬性比非數據描述符的優先級高,你也能夠將非數據描述符隱藏。這就和你給一個實例屬性賦值,將對應類的同名屬性隱藏起來是同一個道理:
>>> 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 <bound method FooFoo.foo of <__main__.FooFoo object at 0x01E166F0>> >>> bar.foo() very important foo() method.可是,下面代碼可能更能清楚的表達意思:
>>> 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.讓咱們來看最後一個例子:使用文件來存儲屬性
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" % (self.name) def __set__(self, obj, val): f = open(self.name, "w") 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) FileDescr.saved.remove(self.name) except (OSError, ValueError),e: pass程序輸出:
>>> class MyFileVarClass(object): foo = FileDescr("foo") bar = FileDescr("bar") >>> fvc = MyFileVarClass() >>> print fvc.foo Traceback (most recent call last): File "<pyshell#121>", line 1, in <module> print fvc.foo File "C:\Python27\hello.py", line 10, in __get__ raise AttributeError,"%r used before assignment" % self.name AttributeError: 'foo' used before assignment >>> fvc.foo = 42 >>> fvc.bar = "leanna" >>> print fvc.foo,fvc.bar 42 leanna >>> del fvc.foo >>> print fvc.foo Traceback (most recent call last): File "<pyshell#126>", line 1, in <module> print fvc.foo File "C:\Python27\hello.py", line 10, in __get__ raise AttributeError,"%r used before assignment" % self.name AttributeError: 'foo' used before assignment >>> print fvc.bar leanna
屬性和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(12) >>> print inst.x 12 >>> inst.x = 20 Traceback (most recent call last): File "<pyshell#138>", line 1, in <module> inst.x = 20 AttributeError: can't set attribute並無定義set的方法,故最後進行賦值出現異常。
那麼,咱們增長set方法吧。
>>> 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 -21 >>> inst.x = -21 >>> print inst.x -21 >>> inst.x = 20 >>> print inst.x 20我有個疑問是:爲何初始化爲20的時候,會輸出21呢???
可是這樣會搞亂類的命名空間,書上提供了一種方法,不過不是看得很懂。。。。
1 「借用」一個函數的名字空間
2編寫一個用做內部函數的方法做爲 property()的(關鍵字)參數
3 (用 locals())返回一個包含全部的(函數/方法)名和對應對象的字典
4 把字典傳入 property(),而後
5去掉臨時的名字空間
>>> 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 = ~x return locals()不太理解,爲何x()被定義成一個函數而不是一個方法 .
13.6.5 Metaclasses和__metaclass__
不管發生什麼,都要問問本身,當初本身是爲了什麼而出發的!!!
元類(Metaclasses)是什麼?
元類讓你來定義某些類是如何被建立的,從根本上說,賦予你如何建立類的控制權。
從根本上說,你能夠把元類想成是一個類中類,或是一個類,它的實例是其它的類。實際上,當你建立一個新類時,你就是在使用默認的元類,它是一個類型對象。(對傳統的類來講,它們的元類是types.ClassType.)當某個類調用type()函數時,你就會看到它究竟是誰的實例:
>>> 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__.全部新風格的類若是沒有任何父類,會從對象或類型中繼承,固然是object。
在執行類定義的時候,將檢查此類正確的(通常是默認的)元類,元類(一般)傳遞三個參數(到構造器):類名,從基類繼承數據的元組,和(類的)屬性字典。
誰在用元類?
程序員
元類示例1
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"程序輸出:
>>> ***welcome to metaclasses! metaclass declaration first class 'foo' declaration next ***created class 'Foo' at:Tue Jun 18 22:11:12 2013 class 'foo' instantiation next ***instantiated class 'Foo' at:Tue Jun 18 22:11:12 2013 Done彷佛明白了什麼。
元類示例2
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.18 練習
13-1. 程序設計。請列舉一些面向對象編程與傳統舊的程序設計形式相比的先進之處。
OOP的好處,我的理解是:解放了程序員編程的負擔。
13-2. 函數和方法的比較。函數和方法之間的區別是什麼?
方法是類屬性,可是函數不是,方法因類而存在。
13-3. 對類進行定製。寫一個類,用來將浮點數值轉換爲金額。在本練習裏,咱們使用美國
貨幣,但讀者也能夠自選任意貨幣。
基本任務: 編寫一個dollarize()函數,它以一個浮點數值做爲輸入,返回一個字符串形式的
金額數。好比說:
dollarize(1234567.8901) ==> ‘$1,234,567.89.
dollarize()返回的金額數裏應該容許有逗號(好比1,000,000),和美圓的貨幣符號。若是有負
號,它必須出如今美圓符號的左邊。完成這項工做後,你就能夠把它轉換成一個有用的類,名爲
MoneyFmt。
def dollarize(fValue): strValue = str(fValue) isNegative = False if strValue[0] == "-": isNegative = True strValue = strValue[1:] strMoney = strValue.split(".") strTemp = [] while (len(strMoney[0]) - 1) / 3: strTemp.append(strMoney[0][-3:]) strMoney[0] = strMoney[0][:-3] strTemp.append(strMoney[0]) strTemp.reverse() myDoller = ",".join(strTemp) + "." + strMoney[1] if isNegative: myDoller = "-" + myDoller return myDoller class MoneyFmt(object): def __init__(self, value = 0.0): self.value = dollarize(value) def update(self, value = None): self.value = dollarize(value) def __repr__(self): return repr(self.value) def __str__(self): val = "$" if self.value[0] == "-": val = "-$" + self.value[1:] else: val += self.value return val def __nonzero__(self): return bool(self.value)
程序輸出:
>>> cash = MoneyFmt(1234567.8901) >>> cash '1,234,567.8901' >>> print cash $1,234,567.8901 >>> cash = MoneyFmt(-1234567.8901) >>> cash '-1,234,567.8901' >>> print cash -$1,234,567.890113-4. 用戶註冊。創建一個用戶數據庫(包括登陸名、密碼和上次登陸時間戳)類(參考練習7-5和9-12),來管理一個系統,該系統要求用戶在登陸後才能訪問某些資源。這個數據庫類對用戶進行管理,並在實例化操做時加載以前保存的用戶信息,提供訪問函數來添加或更新數據庫的信息。在數據修改後,數據庫會在垃圾回收時將新信息保存到磁盤。
class PersonDataBase(object): def __init__(self): self.__personDataBase = [("user1",1),("user2",2)] def login(self, userName, userPasswd): if (userName, userPasswd) not in self.__personDataBase: print "sorry, error" return None else: print "ok" oneObj = PersonDataBase() oneObj.login("test",1) oneObj.login("user1",1)沒什麼心思寫這題,由於題目看不懂。。。就是不知道具體要寫什麼,是寫複雜仍是寫簡單,要達到什麼效果等等。。
13-5. 幾何. 建立一個由有序數值對(x, y) 組成的Point 類,它表明某個點的X 座標和Y 座標。X 座標和Y 座標在實例化時被傳遞給構造器,若是沒有給出它們的值,則默認爲座標的原點。
class Point(object): def __init__(self, x, y): self.__x = x self.__y = y def get_xy(self): return (self.__x, self.__y) def set_xy(self, point): self.__x, self.__y = point def __str__(self): return "%d:%d" % (self.__x, self.__y) __repr__ = __str__ point = property(get_xy, set_xy)
程序輸出:
>>> p = Point(2,3) >>> p.point = 5,6 >>> p 5:6 >>> p.point = 7,8 >>> print p 7:8
13-6. 幾何. 建立一個直線/直線段類。除主要的數據屬性:一對座標值(參見上一個練習)外,
它還具備長度和斜線屬性。你須要覆蓋__repr__()方法(若是須要的話,還有__str__()方法),使得
表明那條直線(或直線段)的字符串表示形式是由一對元組構成的元組,即,((x1, y1), (x2, y2)).
總結:
__repr__ 將直線的兩個端點(始點和止點)顯示成一對元組
length 返回直線段的長度 - 不要使用"len", 由於這樣令人誤解它是整數。
slope 返回此直線段的斜率(或在適當的時候返回None)
import math class Point(object): def __init__(self, x, y): self.x = x self.y = y def get_xy(self): return (self.x, self.y) def set_xy(self, point): self.x, self.y = point def __str__(self): return "%d:%d" % (self.x, self.y) __repr__ = __str__ point = property(get_xy, set_xy) class Line(object): def __init__(self, pointStart, pointEnd): self.pointStart = pointStart self.pointEnd = pointEnd def get_line(self): return (self.pointStart, self.pointEnd) def set_line(self, line): self.pointStart,self.pointEnd = line def __str__(self): return "%s-->%s" % (self.pointStart, self.pointEnd) def length(self): return math.sqrt((self.pointStart.x - self.pointEnd.x) ** 2 + (self.pointStart.y - self.pointEnd.y) ** 2) __repr__ = __str__ line = property(get_line, set_line) p1 = Point(2,3) p2 = Point(4,5) line1 = Line(p1, p2) print line1.length()
程序輸出:
>>> 2.82842712475斜率就不寫了。這裏有個特別注意到點是:Point數據不要寫成私有的,不然調用起來特別難,除非你編寫一個get函數來獲取數據 。
13-7. 數據類。提供一個time 模塊的接口,容許用戶按照本身給定時間的格式,好比:
「MM/DD/YY,」 「MM/DD/YYYY,」 「DD/MM/YY,」 「DD/MM/ YYYY,」 「Mon DD, YYYY,」 或是標準
的Unix 日期格式:「Day Mon DD, HH:MM:SS YYYY」 來查看日期。你的類應該維護一個日期值,並
用給定的時間建立一個實例。若是沒有給出時間值,程序執行時會默認採用當前的系統時間。還包
括另一些方法:
update() 按給定時間或是默認的當前系統時間修改數據值
display() 以表明時間格式的字符串作參數,並按照給定時間的格式顯示:
'MDY' ==> MM/DD/YY
'MDYY' ==> MM/DD/YYYY
'DMY' ==> DD/MM/YY
'DMYY' ==> DD/MM/YYYY
'MODYY' ==> Mon DD, YYYY
import time class Date(object): def __init__(self, time = time.ctime()): self.time = time def choice(self, choiceTime): date = [] date = self.time.split(" ") dateDict = {} dateDict["MMY"] = date[1] + "/" + date[2] + "/" + date[4][2:] dateDict["MDYY"] = date[1] + "/" + date[2] + "/" + date[4] dateDict["DMY"] = date[2] + "/" + date[1] + "/" + date[4][2:] dateDict["DMYY"] = date[2] + "/" + date[1] + "/" + date[4] dateDict["MODYY"] = date[1] + " " + date[2] + "," + date[4] return dateDict[choiceTime] if __name__ == "__main__": date1 = Date() while True: print "'MMY'-->MM/DD/YY" print "'MDYY' ==> MM/DD/YYYY" print "'DMY' ==> DD/MM/YY" print "'DMYY' ==> DD/MM/YYYY" print "'MODYY' ==> Mon DD, YYYY" choiceTime = raw_input("please enter your choice(q to quit):") if choiceTime.lower() == "q": break print date1.choice(choiceTime)程序輸出:
>>> 'MMY'-->MM/DD/YY 'MDYY' ==> MM/DD/YYYY 'DMY' ==> DD/MM/YY 'DMYY' ==> DD/MM/YYYY 'MODYY' ==> Mon DD, YYYY please enter your choice(q to quit):MMY Jun/18/13 'MMY'-->MM/DD/YY 'MDYY' ==> MM/DD/YYYY 'DMY' ==> DD/MM/YY 'DMYY' ==> DD/MM/YYYY 'MODYY' ==> Mon DD, YYYY please enter your choice(q to quit):MDYY Jun/18/2013 'MMY'-->MM/DD/YY 'MDYY' ==> MM/DD/YYYY 'DMY' ==> DD/MM/YY 'DMYY' ==> DD/MM/YYYY 'MODYY' ==> Mon DD, YYYY please enter your choice(q to quit):DMY 18/Jun/13 'MMY'-->MM/DD/YY 'MDYY' ==> MM/DD/YYYY 'DMY' ==> DD/MM/YY 'DMYY' ==> DD/MM/YYYY 'MODYY' ==> Mon DD, YYYY please enter your choice(q to quit):Q13-8. 堆棧類
class Stack(list): def __init__(self, stack): super(Stack, self).__init__() self.stack = stack def push(self, oneElement): self.stack.append(oneElement) def pop(self): return self.stack.pop() def isEmpty(self): return (not len(self.stack)) def peek(self): return self.stack[-1] def __str__(self): return str(self.stack) __repr__ = __str__程序輸出:
>>> stk = Stack([1,2,3]) >>> stk [1, 2, 3] >>> stk.push(4) >>> stk [1, 2, 3, 4] >>> stk.pop() 4 >>> stk.pop() 3 >>> stk [1, 2] >>> stk.peek() 2 >>> stk [1, 2] >>> stk.isEmpty() False這個類實際上編寫的並不完美,由於根本就沒有繼承list。不知道如何繼承list來編寫堆棧類。我記憶中以前寫過,用到list的繼承後,寫的特別的輕鬆,可是忘記內容在哪裏了。
13-9. 隊列類
class Queue(object): def __init__(self, queue): self.queue = queue def enqueue(self, element): self.queue.append(element) def dequeue(self): element = self.queue[0] self.queue = self.queue[1:] return element def __str__(self): return str(self.queue) __repr__ = __str__程序輸出:
>>> que = Queue([1,2,3,4]) >>> que [1, 2, 3, 4] >>> que.enqueue(5) >>> que.dequeue() 1 >>> que [2, 3, 4, 5] >>> que.dequeue() 2 >>> que [3, 4, 5]這裏編寫的也不太好,由於並無進行判斷是否存在異常的現象等。
13-10. 堆棧和隊列。編寫一個類,定義一個可以同時具備堆棧(FIFO)和隊列(LIFO)操做行爲的數據結構。這個類和Perl 語言中數組相像。須要實現四個方法:
shift() 返回並刪除列表中的第一個元素,相似於前面的dequeue()函數。
unshift() 在列表的頭部"壓入"一個新元素
push() 在列表的尾部加上一個新元素,相似於前面的enqueue()和push()方法。
pop() 返回並刪除列表中的最後一個元素,與前面的pop()方法徹底同樣。
class StackQueue(object): def __init__(self,StackQueue): self.StackQueue = StackQueue def isEmpty(self): return (not len(self.StackQueue)) def shift(self): if self.isEmpty(): print "empty, can not shift" else: element = self.StackQueue[0] self.StackQueue = self.StackQueue[1:] return element def unshift(self, element): self.StackQueue = [element] + self.StackQueue def push(self, element): self.StackQueue.append(element) def pop(self): self.StackQueue.pop() def __str__(self): return str(self.StackQueue) __repr__ = __str__程序輸出:
>>> stkque = StackQueue([1,2,3]) >>> stkque [1, 2, 3] >>> stkque.shift() 1 >>> stkque.unshift(6) >>> stkque [6, 2, 3] >>> stkque.push(7) >>> stkque [6, 2, 3, 7] >>> stkque.pop() >>> stkque [6, 2, 3]後面程序愈來愈有點:變態。。。。。。要我用python作出一個QQ來嗎?我當時看wxpython,頭都看暈了。。
習題13.11,13.12,13.13先跳過(之後可能也不會回頭作這三道題。。。)
13-14. DOS. 爲DOS 機器編寫一個UNIX 操做界面的shell。你向用戶提供一個命令行,使得用戶能夠在那裏輸入Unix 命令,你能夠對這些命令進行解釋,並返回相應的輸出,例如:「ls」命令調用「dir」來顯示一個目錄中的文件列表,「more」調用同名命令(分頁顯示一個文件),「cat」 調用 「type,」 「cp」 調用「copy,」 「mv」 調用 「ren,」 「rm」 調用 「del,」 等.
import os def cmdDir(): dirName = os.getcwd() for i in os.listdir(dirName): print dirName + "\\" + i def cmdmore(cmd): fileName = cmd.split(" ")[1] with open(fileName) as fobj: for line in fobj: print line def cmdtype(cmd): fileName = cmd.split(" ")[1] type(fileName) def cmdcopy(cmd): oldFile = cmd.split(" ")[1] newFile = cmd.split(" ")[2] with open(oldFile,"r") as foldObj: with open(newFile,"w") as fnewObj: for line in foldObj: fnewObj.write(line) def cmdren(cmd): cmdcopy(cmd) delFile = cmd.split(" ")[1] os.remove(delFile) def cmddel(cmd): delFile = cmd.split(" ")[1] os.remove(delFile) def DOS(): while True: cmd = raw_input("-->") if cmd.find("ls") != -1: cmdDir() elif cmd.find("more") != -1: cmdmore(cmd) elif cmd.find("cat") != -1: cmdtype(cmd) elif cmd.find("cp") != -1: cmdcopy(cmd) elif cmd.find("mv") != -1: cmdren(cmd) elif cmd.find("rm") != -1: cmddel(cmd) else: print "sorry, command is wrong.please enter:ls,more,cat,cp,mv or rm" if __name__ == "__main__": DOS()
我惟一不理解的是cat這條命令,用type來表示,我如何type一個文件???
13-15. 受權。示例13.8 的執行結果代表咱們的類CapOpen 能成功完成數據的寫入操做。在咱們的最後評論中,提到可使用CapOpen() 或 open()來讀取文件中的文本。爲何呢?這二者使用起來有什麼差別嗎?
實際上使用起來沒什麼差別,只是使用CapOpen()的時候,它重寫了write的方法罷了。
13-16. 受權和函數編程。
(a) 請爲示例13.8 中的CapOpen 類編寫一個writelines()方法。這個新函數將能夠一次讀入多行文本,而後將文本數據轉換成大寫的形式,它與write()方法的區別和一般意思上的writelines()與write()方法之間的區別類似。注意:編寫完這個方法後,writelines()將再也不由文件對象"代理"。
(b) 在writelines()方法中添加一個參數,用這個參數來指明是否須要爲每行文本加上一個換行符。此參數的默認值是False,表示不加換行符。
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 repr(self.file) def write(self, line): self.file.write(line.upper()) def writelines(self, lines, isNewLine = False): for line in lines: self.file.write(line) if not isNewLine: self.file.write("\n") def __getattr__(self, attr): return getattr(self.file, attr) if __name__ == "__main__": fobj = CapOpen("data.txt") print fobj.read() fobj.close() fobj = CapOpen("data.txt","a+") fobj.write("newline\n") fobj.close() fobj = CapOpen("data.txt","a+") lines = ["a\n","new\n","line\n"] fobj.writelines(lines, True) print fobj = CapOpen("data.txt") print fobj.read() fobj.close()程序輸出:
>>> hello world i love this world and i love python too hello world i love this world and i love python tooNEWLINE a new line
13-17. 數值類型子類化。在示例13.3 中所看到的moneyfmt.py 腳本基礎上修改它,使得它能夠擴展Python 的浮點類型。請確保它支持全部操做,並且是不可變的。
class MoneyFmt(float): def __init__(self, value = 0.0): super(MoneyFmt, self).__init__() self.value = float(value) def update(self, value = None): self.value = float(value) def __repr__(self): return repr(self.value) def __str__(self): return "%f" % self.value def __nonzero__(self): return bool(self.value) # def __getattr__(self, attr): # return getattr(self.value, attr) if __name__ == "__main__": fValue = MoneyFmt("123.456") print fValue fValue.update("1234") print fValue newfValue = MoneyFmt("111.222") print newfValue + fValue程序輸出:
>>> 123.456000 1234.000000 234.678
這裏採起的策略是:直接從float派生。通過測試,雖然有getattr,可是調用float的函數仍是得顯式進行調用。好比newfValue.__add__(fValue)
13-19. 映射類型子類化。假設在13.11.3 節中字典的子類,若將keys()方法重寫爲:
def keys(self):
return sorted(self.keys())
(a) 當方法keys()被調用,結果如何?
(b) 爲何會有這樣的結果?如何使咱們的原解決方案順利工做?
class SortedKeyDict(dict): def keys(self): return sorted(self.keys()) d = SortedKeyDict((('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2))) print d.keys()結果很是明顯:無窮遞歸。。。。。。
若是不調用父類的keys()方法的話,我不知道怎麼改。。。。。。。。。
13-20. 類的定製。改進腳本time60.py,見13.13.2 節,示例13.3.
(a) 容許「空」實例化: 若是小時和分鐘的值沒有給出,默認爲零小時、零分鐘。
(b) 用零佔位組成兩位數的表示形式,由於當前的時間格式不符合要求。以下面的示例,wed
應該輸出爲「12:05.」
(c)除了用hours (hr) 和minutes (min)進行初始化外,還支持如下時間輸入格式:
一個由小時和分鐘組成的元組(10, 30)
一個由小時和分鐘組成的字典({'hr': 10, 'min': 30})
一個表明小時和分鐘的字符串("10:30")
附加題: 容許不恰當的時間字符串表示形式,如 「12:5」.
(d) 咱們是否須要實現__radd__()方法? 爲何? 若是沒必要實現此方法,那咱們何時可
以或應該覆蓋它?
(e) __repr__()函數的實現是有缺陷並且被誤導的。咱們只是重載了此函數,這樣咱們能夠省
去使用print 語句的麻煩,使它在解釋器中很好的顯示出來。可是,這個違背了一個原則:對於可估
值的Python 表達式,repr()老是應該給出一個(有效的)字符串表示形式。12:05 自己不是一個合法
的Python 表達式,但Time60('12:05')是合法的。請實現它。
(f) 添加六十進制(基數是60)的運算功能。下面示例中的輸出應該是19:15,而不是18:75:
>>> thu = Time60(10, 30)
>>> fri = Time60(8, 45)
>>> thu + fri
18:75
class Time60(object): def __init__(self, *args1): if type(args1[0]) is tuple: self.hr = args1[0][0] self.min = args1[0][1] elif type(args1[0]) is dict: self.hr = args1[0]["hr"] self.min = args1[0]["min"] elif type(args1[0]) is str: self.hr = int(args1[0].split(":")[0]) self.min = int(args1[0].split(":")[1]) elif type(args1) is tuple: self.hr = args1[0] self.min = args1[1] def __str__(self): return "%02d:%02d" % (self.hr, self.min) def __repr__(self): return repr("%02d:%02d" % (self.hr, self.min)) def __add__(self, other): hour = self.hr + other.hr min = self.min + other.min if min >= 60: min -= 60 hour += 1 return self.__class__(hour, min) def __radd__(self, other): self.hr += other.hr self.min += other.min if self.min >= 60: self.min -= 60 self.hr += 1 return self def __iadd__(self, other): self.hr += other.hr self.min += other.min if self.min >= 60: self.min -= 60 self.hr += 1 return self thu = Time60(10,30) fri = Time60(8,35) print thu + fri thu = Time60((10,30)) fri = Time60({"hr":8,"min":35}) mon = Time60("10:30") print thu + fri print fri + mon thu += fri print thu程序輸出:
>>> 19:05 19:05 19:05 19:05