來源:http://blog.csdn.net/nilxin/article/details/1613574
類中的特殊方法
一般說來,特殊的方法都被用來模仿某個行為。例如,如果你想要為你的類使用x[key]這樣的索引操作(就像列表和元組一樣),那麼你只需要實現__getitem__()方法就可以了。想一下,Python就是對list類這樣做的!
下面這個表中列出了一些有用的特殊方法。如果你想要知道所有的特殊方法,你可以在《Python參考手冊》中找到一個龐大的列表。
名稱 說明
---------------------------------------------------------
__init__(self,...) 這個方法在建立對象恰好要被返回使用之前被調用。
__del__(self) 恰好在對象要被刪除之前調用。
__str__(self) 在我們對對象使用print語句或是使用str()的時候調用。
__lt__(self,other) 當使用 小於 運算子(<)的時候調用。類似地,對於所有的運算子(+,>等等)都有特殊的方法。
__getitem__(self,key) 使用x[key]索引操作符的時候調用。
__len__(self) 對序列對象使用內建的len()函數的時候調用。
__repr__(s) repr() and `...` conversions
__cmp__(s, o) Compares s to o and returns <0, 0, or >0.
Implements >, <, == etc...
__hash__(s) Compute a 32 bit hash code; hash() and dictionary ops
__nonzero__(s) Returns 0 or 1 for truth value testing
__getattr__(s, name) called when attr lookup doesn't find <name>
__setattr__(s, name, val) called when setting an attr
(inside, don't use "self.name = value"
use "self.__dict__[name] = val")
__delattr__(s, name) called to delete attr <name>
__call__(self, *args) called when an instance is called as function.
exec和eval語句
exec語句用來執行儲存在字串或檔案中的Python語句。例如,我們可以在運行時產生一個包含Python代碼的字串,然後使用exec語句執行這些語句。
下面是一個簡單的例子。
>>> exec 'print "Hello World"'
Hello World
eval語句用來計算儲存在字串中的有效Python運算式。下面是一個簡單的例子。
>>> eval('2*3')
6
assert語句
assert語句用來聲明某個條件是真的。例如,如果你非常確信某個你使用的列表中至少有一個元素,而你想要檢驗這一點,並且在它非真的時候引發一個錯誤,那麼assert語句是應用在這種情形下的理想語句。當assert語句失敗的時候,會引發一個AssertionError。
>>> mylist = ['item']
>>> assert len(mylist) >= 1
>>> mylist.pop()
'item'
>>> assert len(mylist) >= 1
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AssertionError repr函數
repr函數用來取得對象的規範字串表示。反引號(也稱轉換符)可以完成相同的功能。注意,在大多數時候有eval(repr(object)) == object。
>>> i = []
>>> i.append('item')
>>> `i`
"['item']"
>>> repr(i)
"['item']"
基本上,repr函數和反引號用來擷取對象的可列印的表示形式。你可以通過定義類的__repr__方法來控制你的對象在被repr函數調用的時候返回的內容。
類和執行個體變數
有兩種類型的域 —— 類的變數和對象的變數,它們根據是類還是對象擁有這個變數而區分。
類的變數 由一個類的所有對象(執行個體)共用使用。只有一個類變數的拷貝,所以當某個對象對類的變數做了改動的時候,這個改動會反映到所有其他的執行個體上。
對象的變數 由類的每個對象/執行個體擁有。因此每個對象有自己對這個域的一份拷貝,即它們不是共用的,在同一個類的不同執行個體中,雖然對象的變數有相同的名稱,但是是互不相關的。通過一個例子會使這個易於理解。
#!/usr/bin/python
# Filename: objvar.py
class Person:
'''Represents a person.'''
population = 0
def __init__(self, name):
'''Initializes the person's data.'''
self.name = name
print '(Initializing %s)' % self.name
# When this person is created, he/she
# adds to the population
Person.population += 1
def __del__(self):
'''I am dying.'''
print '%s says bye.' % self.name
Person.population -= 1
if Person.population == 0:
print 'I am the last one.'
else:
print 'There are still %d people left.' % Person.population
def sayHi(self):
'''Greeting by the person.
Really, that's all it does.'''
print 'Hi, my name is %s.' % self.name
def howMany(self):
'''Prints the current population.'''
if Person.population == 1:
print 'I am the only person here.'
else:
print 'We have %d persons here.' % Person.population
swaroop = Person('Swaroop')
swaroop.sayHi()
swaroop.howMany()
kalam = Person('Abdul Kalam')
kalam.sayHi()
kalam.howMany()
swaroop.sayHi()
swaroop.howMany()輸出
___FCKpd___5nbsp;python objvar.py
(Initializing Swaroop)
Hi, my name is Swaroop.
I am the only person here.
(Initializing Abdul Kalam)
Hi, my name is Abdul Kalam.
We have 2 persons here.
Hi, my name is Swaroop.
We have 2 persons here.
Abdul Kalam says bye.
There are still 1 people left.
Swaroop says bye.
I am the last one.
這是一個很長的例子,但是它有助於說明類與對象的變數的本質。這裡,population屬於Person類,因此是一個類的變數。name變數屬於對象(它使用self賦值)因此是對象的變數。
觀察可以發現__init__方法用一個名字來初始化Person執行個體。在這個方法中,我們讓population增加1,這是因為我們增加了一個人。同樣可以發現,self.name的值根據每個對象指定,這表明了它作為對象的變數的本質。
記住,你只能使用self變數來參考同一個對象的變數和方法。這被稱為 屬性參考 。
在這個程式中,我們還看到docstring對於類和方法同樣有用。我們可以在運行時使用Person.__doc__和Person.sayHi.__doc__來分別訪問類與方法的文檔字串。
就如同__init__方法一樣,還有一個特殊的方法__del__,它在對象消逝的時候被調用。對象消逝即對象不再被使用,它所佔用的記憶體將返回給系統作它用。在這個方法裡面,我們只是簡單地把Person.population減1。
當對象不再被使用時,__del__方法運行,但是很難保證這個方法究竟在什麼時候運行。如果你想要指明它的運行,你就得使用del語句,就如同我們在以前的例子中使用的那樣。
給C++/Java/C#程式員的注釋
Python中所有的類成員(包括資料成員)都是公用的 ,所有的方法都是有效。
只有一個例外:如果你使用的資料成員名稱以雙底線首碼 比如__privatevar,Python的名稱管理體系會有效地把它作為私人變數。
這樣就有一個慣例,如果某個變數只想在類或對象中使用,就應該以單底線首碼。而其他的名稱都將作為公用的,可以被其他類/對象使用。記住這隻是一個慣例,並不是Python所要求的(與雙底線首碼不同)。
同樣,注意__del__方法與destructor的概念類似。
繼承
物件導向的編程帶來的主要好處之一是代碼的重用,實現這種重用的方法之一是通過繼承機制。繼承完全可以理解成類之間的類型和子類型關係。
假設你想要寫一個程式來記錄學校之中的教師和學生情況。他們有一些共同屬性,比如姓名、年齡和地址。他們也有專有的屬性,比如教師的薪水、課程和假期,學生的成績和學費。
你可以為教師和學生建立兩個獨立的類來處理它們,但是這樣做的話,如果要增加一個新的共有屬性,就意味著要在這兩個獨立的類中都增加這個屬性。這很快就會顯得不實用。
一個比較好的方法是建立一個共同的類稱為SchoolMember然後讓教師和學生的類繼承 這個共同的類。即它們都是這個類型(類)的子類型,然後我們再為這些子類型添加專有的屬性。
使用這種方法有很多優點。如果我們增加/改變了SchoolMember中的任何功能,它會自動地反映到子類型之中。例如,你要為教師和學生都增加一個新的身份證域,那麼你只需簡單地把它加到SchoolMember類中。然而,在一個子類型之中做的改動不會影響到別的子類型。另外一個優點是你可以把教師和學生對象都作為SchoolMember對象來使用,這在某些場合特別有用,比如統計學校成員的人數。一個子類型在任何需要父類型的場合可以被替換成父類型,即對象可以被視作是父類的執行個體,這種現象被稱為多態現象。
另外,我們會發現在重用父類的代碼的時候,我們無需在不同的類中重複它。而如果我們使用獨立的類的話,我們就不得不這麼做了。
在上述的場合中,SchoolMember類被稱為基本類或超類 。而Teacher和Student類被稱為匯出類或子類 。
現在,我們將學習一個例子程式。
使用繼承
#!/usr/bin/python
# Filename: inherit.py
class SchoolMember:
'''Represents any school member.'''
def __init__(self, name, age):
self.name = name
self.age = age
print '(Initialized SchoolMember: %s)' % self.name
def tell(self):
'''Tell my details.'''
print 'Name:"%s" Age:"%s"' % (self.name, self.age),
class Teacher(SchoolMember):
'''Represents a teacher.'''
def __init__(self, name, age, salary):
SchoolMember.__init__(self, name, age)
self.salary = salary
print '(Initialized Teacher: %s)' % self.name
def tell(self):
SchoolMember.tell(self)
print 'Salary: "%d"' % self.salary
class Student(SchoolMember):
'''Represents a student.'''
def __init__(self, name, age, marks):
SchoolMember.__init__(self, name, age)
self.marks = marks
print '(Initialized Student: %s)' % self.name
def tell(self):
SchoolMember.tell(self)
print 'Marks: "%d"' % self.marks
t = Teacher('Mrs. Shrividya', 40, 30000)
s = Student('Swaroop', 22, 75)
print # prints a blank line
members = [t, s]
for member in members:
member.tell() # works for both Teachers and Students輸出
$ python inherit.py
(Initialized SchoolMember: Mrs. Shrividya)
(Initialized Teacher: Mrs. Shrividya)
(Initialized SchoolMember: Swaroop)
(Initialized Student: Swaroop)
Name:"Mrs. Shrividya" Age:"40" Salary: "30000"
Name:"Swaroop" Age:"22" Marks: "75"
為了使用繼承,我們把基本類的名稱作為一個元組跟在定義類時的類名稱之後。然後,我們注意到基本類的__init__方法專門使用self變數調用,這樣我們就可以初始化對象的基本類部分。這一點十分重要——Python不會自動調用基本類的constructor,你得親自專門調用它。
我們還觀察到我們在方法調用之前加上類名稱首碼,然後把self變數及其他參數傳遞給它。
注意,在我們使用SchoolMember類的tell方法的時候,我們把Teacher和Student的執行個體僅僅作為SchoolMember的執行個體。
另外,在這個例子中,我們調用了子類型的tell方法,而不是SchoolMember類的tell方法。可以這樣來理解,Python總是首先尋找對應類型的方法,在這個例子中就是如此。如果它不能在匯出類中找到對應的方法,它才開始到基本類中逐個尋找。基本類是在類定義的時候,在元組之中指明的。
一個術語的注釋——如果在繼承元組中列了一個以上的類,那麼它就被稱作多重繼承 。
字串
字串是字元的序列。字串基本上就是一組單詞。
我幾乎可以保證你在每個Python程式中都要用到字串,所以請特別留心下面這部分的內容。下面告訴你如何在Python中使用字串。
* 使用單引號(')
你可以用單引號指示字串,就如同'Quote me on this'這樣。所有的空白,即空格和定位字元都照原樣保留。
* 使用雙引號(")
在雙引號中的字串與單引號中的字串的使用完全相同,例如"What's your name?"。
* 使用三引號('''或""")
利用三引號,你可以指示一個多行的字串。你可以在三引號中自由的使用單引號和雙引號。例如:
'''This is a multi-line string. This is the first line.
This is the second line.
"What's your name?," I asked.
He said "Bond, James Bond."
'''
* 轉義符
假設你想要在一個字串中包含一個單引號('),那麼你該怎麼指示這個字串?例如,這個字串是What's your name?。你肯定不會用'What's your name?'來指示它,因為Python會弄不明白這個字串從何處開始,何處結束。所以,你需要指明單引號而不是字串的結尾。可以通過 轉義符 來完成這個任務。你用/'來指示單引號——注意這個反斜線。現在你可以把字串表示為'What/'s your name?'。
另一個表示這個特別的字串的方法是"What's your name?",即用雙引號。類似地,要在雙引號字串中使用雙引號本身的時候,也可以藉助於轉義符。另外,你可以用轉義符//來指示反斜線本身。
值得注意的一件事是,在一個字串中,行末的單獨一個反斜線表示字串在下一行繼續,而不是開始一個新的行。例如:
"This is the first sentence./
This is the second sentence."
等價於"This is the first sentence. This is the second sentence."
* 自然字串
如果你想要指示某些不需要如轉義符那樣的特別處理的字串,那麼你需要指定一個自然字串。自然字串通過給字串加上首碼r或R來指定。例如r"Newlines are indicated by /n"。
* Unicode字串
Unicode是書寫國際文本的標準方法。如果你想要用你的母語如北印度語或阿拉伯語寫文本,那麼你需要有一個支援Unicode的編輯器。類似地,Python允許你處理Unicode文本——你只需要在字串前加上首碼u或U。例如,u"This is a Unicode string."。
記住,在你處理文字檔的時候使用Unicode字串,特別是當你知道這個檔案含有用非英語的語言寫的文本。
* 字串是不可變的
這意味著一旦你創造了一個字串,你就不能再改變它了。雖然這看起來像是一件壞事,但實際上它不是。我們將會在後面的程式中看到為什麼我們說它不是一個缺點。
* 按字面意義級連字號串
如果你把兩個字串按字面意義相鄰放著,他們會被Python自動級連。例如,'What/'s' 'your name?'會被自動轉為"What's your name?"。
給C/C++程式員的注釋
在Python中沒有專門的char資料類型。確實沒有需要有這個類型,我相信你不會為此而煩惱。
給Perl/PHP程式員的注釋
記住,單引號和雙引號字串是完全相同的——它們沒有在任何方面有不同。
給Regex使用者的注釋
一定要用自然字串處理Regex。否則會需要使用很多的反斜線。例如,後向引用符可以寫成'//1'或r'/1'。
私人變數
Python對私人類成員有部分支援。任何象__spam這樣形式的標識符(至少有兩個前置底線,至多有一個結尾底線)目前被替換成_classname__spam,其中classname是所屬類名去掉前置底線的結果。這種攪亂不管標識符的文法位置,所以可以用來定義類私人的執行個體、變數、方法,以及全域變數,甚至於儲存對於此類是私人的其它類的執行個體。如果攪亂的名字超過255個字元可能會發生截斷。在類外面或類名只有底線時不進行攪亂。
名字攪亂的目的是給類一種定義“私人”執行個體變數和方法的簡單方法,不需擔心它的其它類會定義同名變數,也不怕類外的代碼弄亂執行個體的變數。注意攪亂規則主要是為了避免偶然的錯誤,如果你一定想做的話仍然可以訪問或修改私人變數。這甚至是有用的,比如偵錯工具要用到私人變數,這也是為什麼這個漏洞沒有堵上的一個原因。(小錯誤:匯出類和基類取相同的名字就可以使用基類的私人變數)。
注意傳遞給exec,eval()或evalfile()的代碼不會認為調用它們的類的類名是當前類,這與global語句的情況類似,global的作用局限於一起位元組編譯的代碼。同樣的限制也適用於getattr() ,setattr()和delattr(),以及直接存取__dict__的時候。
下面例子中的類實現了自己的__getattr__和__setattr__方法,把所有屬性儲存在一個私人變數中,這在Python的新舊版本中都是可行的:
class VirtualAttributes:
__vdict = None
__vdict_name = locals().keys()[0]
def __init__(self):
self.__dict__[self.__vdict_name] = {}
def __getattr__(self, name):
return self.__vdict[name]
def __setattr__(self, name, value):
self.__vdict[name] = value
參考資料:
http://www.e7blog.com/blog/user1/python/
http://www.czug.org/docs/python/TutorialCN/X_e7_ac_ac_e4_b9_9d_e7_ab_a0_e7_b1_bb/