python核心編程--第十三章

博客專區 > fzyz_sb的博客 > 博客詳情
python核心編程--第十三章
fzyz_sb 發表於4年前
 
 
 
python核心編程--第十三章
 
  • 發表於 4年前 
  • 閱讀 4679 
  • 收藏 16 
  • 點贊 2 
  • 評論 9

13.1 介紹

https://my.oschina.net/voler/blog/138242

類與實例

類與實例相互關聯着:類是對象的定義,而實例是"真正的實物",它存放了類中所定義的對象
的具體信息。

>>> 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的獨有屬性,並且是動態的:你不需要在構造器中,或其它任何地方爲它們預先聲明或者賦值。

方法

方法爲類的屬性,除了靜態方法(個人理解爲靜態方法爲函數),方法必須有實例來調用。

>>> 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, "[email protected]")
created instance for: john doe
>>> john
<__main__.EmplAddrBookEntry object at 0x02115FD0>
>>> john.name
'john doe'
>>> john.phone
'408-555-1212'
>>> john.email
'[email protected]'
>>> 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
'[email protected]'

13.2 面向對象編程

常用術語

抽象/實現

抽象指對現實世界問題和實體的本質表現,行爲和特徵建模,建立一個相關的子集,可以用於描繪程序結構,從而實現這種模型。抽象不僅包括這種模型的數據屬性,還定義了這些數據的接口。對某種抽象的實現就是對此數據及與之相關接口的現實化。現實化這個過程對於客戶程序應當是透明而且無關的。

封裝/接口

封裝描述了對數據/信息進行隱藏的觀念,它對數據屬性提供接口和訪問函數。由於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: TestClassMethod
13.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)
True
13.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.22
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)
    __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'
這種特性的主要目的是節約內存,其副作用是某種類型的“安全”,它能防止用戶隨心所欲的動態增加實例屬性。 
13.16.3 特殊方法__getattribute__()

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.8901
13-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):Q
13-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

粉絲 259
 
博文 203
 
碼字總數 438425
評論 (9)
Ctrl+Enter 
  
紅魔鬼
哥們太認真了!
紅魔鬼
對於你提到的幾個問題,探討一下:


01
>>> class HideX(object):
02
def __init__(self, x):
03
self.__x = x
04
def get_x(self):
05
return ~self.__x
06
def set_x(self, x):
07
assert isinstance(x, int),"x must be an integer!"
08
self.__x = ~x
09
x = property(get_x, set_x)
10

11

12
>>> inst = HideX(20)
13
>>> print inst.x
14
-21
15
>>> inst.x = -21
16
>>> print inst.x
17
-21
18
>>> inst.x = 20
19
>>> print inst.x

我有個疑問是:爲什麼初始化爲20的時候,會輸出21呢???

這是顯然的,inst = HideX(20),會調用__init__,將20賦給__x,當輸出時,調用get_x,將20取反,顯示-21。

其實你寫的程序和書上的不一樣,書上的__init__是self.x = x, 你寫的是self.__x = x。按書上的寫法,初始化時__init__會自動調用set_x,將20取反賦給__x(即__x爲-21),輸出時get_x再取反,故爲20。


紅魔鬼
下面的property裝飾器方法,你寫錯了,懷疑你看的是中文版,縮進什麼的都錯了。應爲:

class HideX(object):
def __init__(self, x):
self.x = x

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 = property(**x())
紅魔鬼
class HideX(object):
......def __init__(self, x):
............self.x = x

.......def x():
............def fget(self):
..................return ~self.__x

............def fset(self, x):
..................assert isinstance(x, int), "'x' must be an int'
..................self.__x = ~x

............return locals()

........x = property(**x())

上面用點號代替縮進
紅魔鬼
最後一行,x = property(**x()),x() 即會調用locals(), 返回函數x的字典,字典兩個key是fget和fset。
**是關鍵字參數的用法。最後一行即等於x = property(fget=..., fset=...)
其餘的,不難了。
fzyz_sb

引用來自“紅魔鬼”的評論

對於你提到的幾個問題,探討一下:


01
>>> class HideX(object):
02
def __init__(self, x):
03
self.__x = x
04
def get_x(self):
05
return ~self.__x
06
def set_x(self, x):
07
assert isinstance(x, int),"x must be an integer!"
08
self.__x = ~x
09
x = property(get_x, set_x)
10

11

12
>>> inst = HideX(20)
13
>>> print inst.x
14
-21
15
>>> inst.x = -21
16
>>> print inst.x
17
-21
18
>>> inst.x = 20
19
>>> print inst.x

我有個疑問是:爲什麼初始化爲20的時候,會輸出21呢???

這是顯然的,inst = HideX(20),會調用__init__,將20賦給__x,當輸出時,調用get_x,將20取反,顯示-21。

其實你寫的程序和書上的不一樣,書上的__init__是self.x = x, 你寫的是self.__x = x。按書上的寫法,初始化時__init__會自動調用set_x,將20取反賦給__x(即__x爲-21),輸出時get_x再取反,故爲20。


確實,當時真蠢,謝謝啦。
fzyz_sb

引用來自“紅魔鬼”的評論

class HideX(object):
......def __init__(self, x):
............self.x = x

.......def x():
............def fget(self):
..................return ~self.__x

............def fset(self, x):
..................assert isinstance(x, int), "'x' must be an int'
..................self.__x = ~x

............return locals()

........x = property(**x())

上面用點號代替縮進

我是直接邊運行邊寫的,木有寫成一個文件方式。
紅魔鬼
即使在解釋器裏直接寫,也是要考慮縮進的,不同的縮進會有不同的結果。

tianyi210
習題13-19中的無窮遞歸,就把def keys改爲def skeys,最後打印的改成d.skeys()即可,就是名字覆蓋了,不知道理解如何
頂部
發佈了18 篇原創文章 · 獲贊 11 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章