python中類的全面分析
面向?qū)ο笾匾母拍罹褪穷?Class)和實例(Instance),類是抽象的模板,而實例是根據(jù)類創(chuàng)建出來的一個個具體的“對象”,每個對象都擁有相同的方法,但各自的數(shù)據(jù)可能不同。
先回顧下 OOP 的常用術語:
- 類:對具有相同數(shù)據(jù)和方法的一組對象的描述或定義。
 - 對象:對象是一個類的實例。
 - 實例(instance):一個對象的實例化實現(xiàn)。
 - 實例屬性(instance attribute):一個對象就是一組屬性的集合。
 - 實例方法(instance method):所有存取或者更新對象某個實例一條或者多條屬性的函數(shù)的集合。
 - 類屬性(classattribute):屬于一個類中所有對象的屬性,不會只在某個實例上發(fā)生變化
 - 類方法(classmethod):那些無須特定的對象實例就能夠工作的從屬于類的函數(shù)。
 
類概述
在Python中,定義類是通過class關鍵字:
- class Student(object):
 - pass
 
class后面緊接著是類名,即Student,類名通常是大寫開頭的單詞,緊接著是(object),表示該類是從哪個類繼承下來的。通常,如果沒有合適的繼承類,就使用object類,這是所有類最終都會繼承的類。
定義好了Student類,就可以根據(jù)Student類創(chuàng)建出Student的實例,創(chuàng)建實例是通過類名+()實現(xiàn)的:
- >>> bart = Student()
 - >>> bart
 - <__main__.Student object at 0x10a67a590>
 - >>> Student
 - <class '__main__.Student'>
 
可以看到,變量bart指向的就是一個Student的object,后面的0x10a67a590是內(nèi)存地址,每個object的地址都不一樣,而Student本身則是一個類。
可以自由地給一個實例變量綁定屬性,比如,給實例bart綁定一個name屬性:
- >>> bart.name = 'Bart Simpson'
 - >>> bart.name
 - 'Bart Simpson'
 
由于類可以起到模板的作用,因此,可以在創(chuàng)建實例的時候,把一些我們認為必須綁定的屬性強制填寫進去。通過定義一個特殊的init方法,在創(chuàng)建實例的時候,就把name,score等屬性綁上去。
- class Student(object):
 - def __init__(self, name, score):
 - self.name = name
 - self.score = score
 
注意到init方法的***個參數(shù)永遠是self,表示創(chuàng)建的實例本身,因此,在init方法內(nèi)部,就可以把各種屬性綁定到self,因為self就指向創(chuàng)建的實例本身。
有了init方法,在創(chuàng)建實例的時候,就不能傳入空的參數(shù)了,必須傳入與init方法匹配的參數(shù),但self不需要傳,Python解釋器自己會把實例變量傳進去:
- >>> bart = Student('Bart Simpson', 59)
 - >>> bart.name
 - 'Bart Simpson'
 - >>> bart.score
 - 59
 
和普通的函數(shù)相比,在類中定義的對象函數(shù)(還有靜態(tài)方法,類方法)只有一點不同,就是***個參數(shù)永遠是實例變量self,并且,調(diào)用時不用傳遞該參數(shù)。
新式類、舊式類
python的新式類是2.2版本引進來的,之前的類叫做經(jīng)典類或者舊類。Python 2.x 中如果一個類繼承于一個基類(可以是自定義類或者其它類)或者繼承自 object,則該類為新式類;沒有繼承的類為經(jīng)典類。Python 3.x 則全部為新式類。
新式類被賦予了很多新的特性(如:統(tǒng)一了types和classes),并改變了以往經(jīng)典類的一些內(nèi)容(如:改變了多繼承下方法的執(zhí)行順序)。
關于統(tǒng)一類(class)和類型(type),具體看下面的例子
- class OldClass():
 - pass
 - o = OldClass()
 - print o.__class__ # __main__.OldClass
 - print type(o) # <type 'instance'>
 - class newClass(object):
 - pass
 - n = newClass()
 - print n.__class__ # <class '__main__.newClass'>
 - print type(n) # <class '__main__.newClass'>
 
對象屬性
Python 中對象的屬性包含對象的所有內(nèi)容:方法和數(shù)據(jù),注意方法也是對象的屬性。查找對象的屬性時,首先在對象的__dict__ 里面查找,然后是對象所屬類的dict,再往后是繼承體系中父類(MRO解析)的dict,任意一個地方查找到就終止查找,并且調(diào)用 __getattribute__(也有可能是__getattr__) 方法獲得屬性值。
方法
在 Python 類中有3種方法,即靜態(tài)方法(staticmethod),類方法(classmethod)和實例方法:
- 對于實例方法,在類里每次定義實例方法的時候都需要指定實例(該方法的***個參數(shù),名字約定成俗為self)。這是因為實例方法的調(diào)用離不開實例,我們必須給函數(shù)傳遞一個實例。假設對象a具有實例方法 foo(self, *args, **kwargs),那么調(diào)用的時候可以用 a.foo(*args, **kwargs),或者 A.foo(a, *args, **kwargs),在解釋器看來它們是完全一樣的。
 - 類方法每次定義的時候需要指定類(該方法的***個參數(shù),名字約定成俗為cls),調(diào)用時和實例方法類似需要指定一個類。
 - 靜態(tài)方法其實和普通的方法一樣,只不過在調(diào)用的時候需要使用類或者實例。之所以需要靜態(tài)方法,是因為有時候需要將一組邏輯上相關的函數(shù)放在一個類里面,便于組織代碼結(jié)構。一般如果一個方法不需要用到self,那么它就適合用作靜態(tài)方法。
 
具體的例子如下:
- def foo(x):
 - print "executing foo(%s)"%(x)
 - class A(object):
 - def foo(self):
 - print "executing foo(%s)" % self
 - @classmethod
 - def class_foo(cls):
 - print "executing class_foo(%s)" % cls
 - @staticmethod
 - def static_foo():
 - print "executing static_foo()"
 - a = A()
 - print a.foo
 - print A.foo
 - print a.class_foo
 - print A.class_foo
 - print A.static_foo
 - print a.static_foo
 - print foo
 - # <bound method A.foo of <__main__.A object at 0x10d5f90d0>>
 - # <unbound method A.foo>
 - # <bound method type.class_foo of <class '__main__.A'>>
 - # <bound method type.class_foo of <class '__main__.A'>>
 - # <function static_foo at 0x10d5f32a8>
 - # <function static_foo at 0x10d5f32a8>
 - # <function foo at 0x10d5f1ed8>
 
在訪問類方法的時候有兩種方法,分別叫做 未綁定的方法(unbound method) 和 綁定的方法(bound method):
- 未綁定的方法:通過類來引用實例方法返回一個未綁定方法對象。要調(diào)用它,你必須顯示地提供一個實例作為***個參數(shù),比如 A.foo。
 - 綁定的方法:通過實例訪問方法返回一個綁定的方法對象。Python自動地給方法綁定一個實例,所以調(diào)用它時不用再傳一個實例參數(shù),比如 a.foo。
 
數(shù)據(jù)屬性
下面創(chuàng)建了一個Student的類,并且實現(xiàn)了這個類的初始化函數(shù)"__init__":
- class Student(object):
 - count = 0
 - books = []
 - def __init__(self, name, age):
 - self.name = name
 - self.age = age
 
在上面的Student類中,count, books, name 和 age 都被稱為類的數(shù)據(jù)屬性,但是它們又分為類數(shù)據(jù)屬性和實例數(shù)據(jù)屬性。直接定義在類體中的屬性叫類屬性,而在類的方法中定義的屬性叫實例屬性。
首先看下面代碼,展示了對類數(shù)據(jù)屬性和實例數(shù)據(jù)屬性的訪問:
- Student.books.extend(["python", "javascript"])
 - print "Student book list: %s" %Student.books
 - # class can add class attribute after class definition
 - Student.hobbies = ["reading", "jogging", "swimming"]
 - print "Student hobby list: %s" %Student.hobbies
 - print dir(Student)
 - # class instance attribute
 - wilber = Student("Wilber", 28)
 - print "%s is %d years old" %(wilber.name, wilber.age)
 - # class instance can add new attribute
 - # "gender" is the instance attribute only belongs to wilber
 - wilber.gender = "male"
 - print "%s is %s" %(wilber.name, wilber.gender)
 - # class instance can access class attribute
 - wilber.books.append("C#")
 - print wilber.books
 
通過內(nèi)建函數(shù)dir(),或者訪問類的字典屬性__dict__,這兩種方式都可以查看類或者實例有哪些屬性。對于類數(shù)據(jù)屬性和實例數(shù)據(jù)屬性,可以總結(jié)為:
- 類數(shù)據(jù)屬性屬于類本身,可以通過類名進行訪問/修改;
 - 類數(shù)據(jù)屬性也可以被類的所有實例訪問/修改;
 - 在類定義之后,可以通過類名動態(tài)添加類數(shù)據(jù)屬性,新增的類屬性也被類和所有實例共有;
 - 實例數(shù)據(jù)屬性只能通過實例訪問;
 - 在實例生成后,還可以動態(tài)添加實例數(shù)據(jù)屬性,但是這些實例數(shù)據(jù)屬性只屬于該實例;
 
再看下面的程序
- class Person:
 - name="aaa"
 - p1=Person()
 - p2=Person()
 - p1.name="bbb"
 - print p1.name # bbb
 - print p2.name # aaa
 - print Person.name # aaa
 
上面程序中,p1.name="bbb"是實例調(diào)用了類變量,p1.name一開始是指向的類變量name="aaa",但是在實例的作用域里把類變量的引用改變了,就變成了一個實例變量。self.name不再引用Person的類變量name了。
- class Person:
 - name=[]
 - p1=Person()
 - p2=Person()
 - p1.name.append(1)
 - print p1.name # [1]
 - print p2.name # [1]
 - print Person.name # [1]
 
特殊的類屬性
對于所有的類,都有一組特殊的屬性:
通過這些屬性,可以得到 Student類的一些信息,如下:
類的繼承
Python 是面向?qū)ο笳Z言,支持類的繼承(包括單重和多重繼承),繼承的語法如下:
- class DerivedClass(BaseClass1, [BaseClass2...]):
 - <statement-1>
 - .
 - <statement-N>
 
子類可以覆蓋父類的方法,此時有兩種方法來調(diào)用父類中的函數(shù):
- 調(diào)用父類的未綁定的構造方法。在調(diào)用一個實例的方法時,該方法的self參數(shù)會被自動綁定到實例上(稱為綁定方法)。但如果直接調(diào)用類的方法(比如A.init),那么就沒有實例會被綁定。這樣就可以自由的提供需要的self參數(shù),這種方法稱為未綁定(unbound)方法。大多數(shù)情況下是可以正常工作的,但是多重繼承的時候可能會重復調(diào)用父類。
 - 通過 super(cls, inst).method() 調(diào)用 MRO中下一個類的函數(shù),這里有一個非常不錯的解釋,看完后對 super 應該就熟悉了。
 
未綁定(unbound)方法調(diào)用如下:
- class Base(object):
 - def __init__(self):
 - print("Base.__init__")
 - class Derived(Base):
 - def __init__(self):
 - Base.__init__(self)
 - print("Derived.__init__")
 
supper 調(diào)用如下:
- class Base(object):
 - def __init__(self):
 - print "Base.__init__"
 - class Derived(Base):
 - def __init__(self):
 - super(Derived, self).__init__()
 - print "Derived.__init__"
 - class Derived_2(Derived):
 - def __init__(self):
 - super(Derived_2, self).__init__()
 - print "Derived_2.__init__"
 
繼承機制 MRO
MRO 主要用于在多繼承時判斷調(diào)用的屬性來自于哪個類。Python2.2以前的類為經(jīng)典類,它是一種沒有繼承的類,實例類型都是type類型,如果經(jīng)典類被作為父類,子類調(diào)用父類的構造函數(shù)時會出錯。這時MRO的方法為DFS(深度優(yōu)先搜索),子節(jié)點順序:從左到右。inspect.getmro(A)可以查看經(jīng)典類的MRO順序。
兩種繼承模式在DFS下的優(yōu)缺點:
***種,兩個互不相關的類的多繼承,這種情況DFS順序正常,不會引起任何問題;
第二種,棱形繼承模式,存在公共父類(D)的多繼承,這種情況下DFS必定經(jīng)過公共父類(D)。如果這個公共父類(D)有一些初始化屬性或者方法,但是子類(C)又重寫了這些屬性或者方法,那么按照DFS順序必定是會先找到D的屬性或方法,那么C的屬性或者方法將永遠訪問不到,導致C只能繼承無法重寫(override)。這也就是新式類不使用DFS的原因,因為他們都有一個公共的祖先object。
為了使類和內(nèi)置類型更加統(tǒng)一,Python2.2版本引入了新式類。新式類的每個類都繼承于一個基類,可以是自定義類或者其它類,默認承于object,子類可以調(diào)用父類的構造函數(shù)??梢杂? A.__mro__ 可以查看新式類的順序。
在 2.2 中,有兩種MRO的方法:
- 如果是經(jīng)典類MRO為DFS;
 - 如果是新式類MRO為BFS(廣度優(yōu)先搜索),子節(jié)點順序:從左到右。
 
新式類兩種繼承模式在BFS下的優(yōu)缺點:
***種,正常繼承模式。比如B明明繼承了D的某個屬性(假設為foo),C中也實現(xiàn)了這個屬性foo,那么BFS明明先訪問B然后再去訪問C,但是A的foo屬性是c,這個問題稱為單調(diào)性問題。
第二種,棱形繼承模式,BFS的查找順序解決了DFS順序中的只能繼承無法重寫的問題。
因為DFS 和 BFS 都存在較大的問題,所以從Python2.3開始新式類的 MRO采用了C3算法,解決了單調(diào)性問題,和只能繼承無法重寫的問題。MRO的C3算法順序如下圖:
C3 采用圖的拓撲排序算法,具體實現(xiàn)可以參考官網(wǎng)文檔。
多態(tài)
多態(tài)即多種形態(tài),在運行時確定其狀態(tài),在編譯階段無法確定其類型,這就是多態(tài)。Python中的多態(tài)和Java以及C++中的多態(tài)有點不同,Python中的變量是動態(tài)類型的,在定義時不用指明其類型,它會根據(jù)需要在運行時確定變量的類型。
Python本身是一種解釋性語言,不進行預編譯,因此它就只在運行時確定其狀態(tài),故也有人說Python是一種多態(tài)語言。在Python中很多地方都可以體現(xiàn)多態(tài)的特性,比如內(nèi)置函數(shù)len(object),len函數(shù)不僅可以計算字符串的長度,還可以計算列表、元組等對象中的數(shù)據(jù)個數(shù),這里在運行時通過參數(shù)類型確定其具體的計算過程,正是多態(tài)的一種體現(xiàn)。
特殊的類方法
類中經(jīng)常有一些方法用兩個下劃線包圍來命名,下圖給出一些例子。合理地使用它們可以對類添加一些“魔法”的行為。
構造與析構
當我們調(diào)用 x = SomeClass() 的時候,***個被調(diào)用的函數(shù)是 __new__ ,這個方法創(chuàng)建實例。接下來可以用 __init__ 來指明一個對象的初始化行為。當這個對象的生命周期結(jié)束的時候, __del__ 會被調(diào)用。
- __new__(cls,[...]) 是對象實例化時***個調(diào)用的方法,它只取下 cls 參數(shù),并把其他參數(shù)傳給init。
 - __init__(self,[...]) 為類的初始化方法。它獲取任何傳給構造器的參數(shù)(比如我們調(diào)用 x = SomeClass(10, ‘foo’) ,init 函數(shù)就會接到參數(shù) 10 和 ‘foo’) 。
 - __del__(self):new和init是對象的構造器, del則是對象的銷毀器。它并非實現(xiàn)了語句 del x (因此該語句不等同于x.__del__()),而是定義當對象被回收時的行為。
 
當我們創(chuàng)建一個類的實例時,首先會調(diào)用new創(chuàng)建實例,接著才會調(diào)用init來進行初始化。不過注意在舊式類中,實例的創(chuàng)建并沒有調(diào)用new方法,如下例子:
- class A:
 - def __new__(cls):
 - print "A.__new__ is called" # -> this is never called
 - A()
 
對于新式類來說,我們可以覆蓋new方法,注意該方法的***個參數(shù)cls(其實就是當前類類型)用來指明要創(chuàng)建的類型,后續(xù)參數(shù)用來傳遞給init進行初始化。如果new返回了cls類型的對象,那么接下來調(diào)用init,否則的話不會調(diào)用init(調(diào)用該方法必須傳遞一個實例對象)。
- class A(object): # -> don't forget the object specified as base
 - def __new__(cls):
 - print "A.__new__ called"
 - return super(A, cls).__new__(cls)
 - def __init__(self):
 - print "A.__init__ called"
 - A()
 - # A.__new__ called
 - # A.__init__ called
 
這里我們調(diào)用super()來獲取 MRO 中A的下一個類(在這里其實就是基類 object)的new方法來創(chuàng)建一個cls的實例對象,接著用這個對象來調(diào)用了init。下面的例子中,并沒有返回一個合適的對象,所以并沒有調(diào)用init:
- class Sample(object):
 - def __str__(self):
 - return "SAMPLE"
 - class A(object):
 - def __new__(cls):
 - print "A.__new__ called"
 - return super(A, cls).__new__(Sample)
 - # return Sample()
 - def __init__(self):
 - print "A.__init__ called" # -> is actually never called
 - a = A()
 - # A.__new__ called
 
關于 super,這里是一個非常不錯的解釋,簡單來說super做了下面的事情:
- def super(cls, inst):
 - mro = inst.__class__.mro()
 - return mro[mro.index(cls) + 1]
 
關于 MRO,這篇文章非常棒:你真的理解Python中MRO算法嗎?,簡單來說,在新式類MRO的 C3 算法中,保證:基類永遠出現(xiàn)在派生類后面,如果有多個基類,基類的相對順序保持不變。
操作符
利用特殊方法可以構建一個擁有Python內(nèi)置類型行為的對象,這意味著可以避免使用非標準的、丑陋的方式來表達簡單的操作。在一些語言中,這樣做很常見:
- if instance.equals(other_instance):
 - # do something
 
Python中當然也可以這么做,但是這樣做讓代碼變得冗長而混亂。不同的類庫可能對同一種比較操作采用不同的方法名稱,這讓使用者需要做很多沒有必要的工作。因此我們可以定義方法__eq__,然后就可以像下面這樣使用:
- if instance == other_instance:
 - # do something
 
Python 有許多特殊的函數(shù)對應到常用的操作符上,比如:
- __cmp__(self, other):定義了所有比較操作符的行為。應該在 self < other 時返回一個負整數(shù),在 self == other 時返回0,在 self > other 時返回正整數(shù)。
 - __eq__(self, other):定義等于操作符(==)的行為。
 - __ne__(self, other):定義不等于操作符(!=)的行為(定義了 eq 的情況下也必須再定義 ne!)
 - __le__(self, other):定義小于等于操作符(<)的行為。
 - __ge__(self, other):定義大于等于操作符(>)的行為。
 
數(shù)值操作符
就像可以使用比較操作符來比較類的實例,也可以定義數(shù)值操作符的行為??梢苑殖晌孱悾阂辉僮鞣R娝銛?shù)操作符,反射算數(shù)操作符,增強賦值操作符,和類型轉(zhuǎn)換操作符,下面為一些例子:
- __pos__(self) 實現(xiàn)取正操作,例如 +some_object
 - __invert__(self) 實現(xiàn)取反操作符 ~
 - __add__(self, other) 實現(xiàn)加法操作
 - __sub__(self, other) 實現(xiàn)減法操作
 - __radd__(self, other) 實現(xiàn)反射加法操作
 - __rsub__(self, other) 實現(xiàn)反射減法操作
 - __floordiv__(self, other) 實現(xiàn)使用 // 操作符的整數(shù)除法
 - __iadd__(self, other) 實現(xiàn)加法賦值操作。
 - __isub__(self, other) 實現(xiàn)減法賦值操作。
 - __int__(self) 實現(xiàn)到int的類型轉(zhuǎn)換。
 - __long__(self) 實現(xiàn)到long的類型轉(zhuǎn)換。
 
反射運算符方法和它們的常見版本做的工作相同,只不過是處理交換兩個操作數(shù)之后的情況。類型轉(zhuǎn)換操作符,主要用于實現(xiàn)類似 float() 這樣的內(nèi)建類型轉(zhuǎn)換函數(shù)的操作。
類的表示
使用字符串來表示類是一個相當有用的特性。在Python中有一些內(nèi)建方法可以返回類的表示,相對應的,也有一系列特殊方法可以用來自定義在使用這些內(nèi)建函數(shù)時類的行為。
- __str__(self) 定義對類的實例調(diào)用 str() 時的行為。
 - __repr__(self) 定義對類的實例調(diào)用 repr() 時的行為。 str() 和 repr() 最主要的差別在于“目標用戶”,repr() 的作用是產(chǎn)生機器可讀的輸出(大部分情況下,其輸出可以作為有效的Python代碼),而 str() 則產(chǎn)生人類可讀的輸出。
 - __dir__(self) 定義對類的實例調(diào)用 dir() 時的行為,這個方法應該向調(diào)用者返回一個屬性列表。如果重定義了__getattr__ 或者使用動態(tài)生成的屬性,以實現(xiàn)類的交互式使用,那么這個方法是必不可少的。
 
屬性控制
在Python中,重載__getattr__、__setattr__、__delattr__和__getattribute__方法可以用來管理一個自定義類中的屬性訪問。其中:
- getattr方法將攔截所有未定義的屬性獲取(當要訪問的屬性已經(jīng)定義時,該方法不會被調(diào)用,至于定義不定義,是由Python能否查找到該屬性來決定的);
 - getattribute方法將攔截所有屬性的獲取(不管該屬性是否已經(jīng)定義,只要獲取它的值,該方法都會調(diào)用),由于此情況,所以,當一個類中同時重載了getattr和getattribute方法,那么getattr永遠不會被調(diào)用,另外getattribute方法僅僅存在于Python2.6的新式類和Python3的所有類中;
 - setattr方法將攔截所有的屬性賦值;
 - delattr方法將攔截所有的屬性刪除。
 
在Python中,一個類或類實例中的屬性是動態(tài)的(因為Python是動態(tài)的),也就是說,可以往一個類或類實例中添加或刪除一個屬性。
由于getattribute、setattr、delattr方法對所有的屬性進行攔截,所以,在重載它們時,不能再像往常的編碼,要注意避免遞歸調(diào)用(如果出現(xiàn)遞歸,則會引起死循環(huán));然而對getattr方法,則沒有這么多的限制。
在重載setattr方法時,不能使用“self.name = value”格式,否則,它將會導致遞歸調(diào)用而陷入死循環(huán)。正確的應該是:
- def __setattr__(self, name, value):
 - # do-something
 - object.__setattr__(self, name, value)
 - # do-something
 
其中的object.__setattr__(self, name, value)一句可以換成self.__dict__[name] = value;但前提是,必須保證getattribute方法重載正確(如果重載了getattribute方法的話),否則,將在賦值時導致錯誤,因為self.dict將要觸發(fā)對self所有屬性中的dict屬性的獲取,這樣從而就會引發(fā)getattribute方法的調(diào)用,如果getattribute方法重載錯誤,setattr方法自然而然也就會失敗。
自定義序列
有許多辦法可以讓 Python 類表現(xiàn)得像是內(nèi)建序列類型(字典,元組,列表,字符串等)。
在Python中實現(xiàn)自定義容器類型需要用到一些協(xié)議。首先,不可變?nèi)萜黝愋陀腥缦聟f(xié)議:想實現(xiàn)一個不可變?nèi)萜鳎阈枰x__len__ 和 __getitem__。
可變?nèi)萜鞯膮f(xié)議除了上面提到的兩個方法之外,還需要定義 __setitem__ 和 __delitem__ 。如果你想讓你的對象可以迭代,你需要定義 __iter__ ,這個方法返回一個迭代器。迭代器必須遵守迭代器協(xié)議,需要定義 __iter__ (返回它自己)和 next 方法。
上下文管理
上下文管理協(xié)議(Context Management Protocol)包含方法 __enter__() 和 __exit__(),支持 該協(xié)議的對象要實現(xiàn)這兩個方法。
- enter: 進入上下文管理器的運行時上下文。如果指定了 as 子句的話,返回值賦值給 as 子句中的 target。
 - exit: 退出與上下文管理器相關的運行時上下文。返回一個布爾值表示是否對發(fā)生的異常進行處理。
 
在執(zhí)行with語句包裹起來的代碼塊之前會調(diào)用上下文管理器的 enter 方法,執(zhí)行完語句體之后會執(zhí)行 exit 方法。
with 語句的語法格式如下:
- with context_expression [as target(s)]:
 - with-body
 
Python 對一些內(nèi)建對象進行改進,加入了對上下文管理器的支持,可以用于 with 語句中,比如可以自動關閉文件、線程鎖的自動獲取和釋放等。如下面例子:
- >>> with open("etc/CS.json") as d:
 - ...: print d
 - <open file 'etc/CS.json', mode 'r' at 0x109344540>
 - >>> print d
 - <closed file 'etc/CS.json', mode 'r' at 0x109344540>
 - >>> print dir(d)
 - ['__class__', '__delattr__', '__doc__', '__enter__', '__exit__', ...]
 
通過使用 with 語句,不管在處理文件過程中是否發(fā)生異常,都能保證 with 語句執(zhí)行完畢后已經(jīng)關閉了打開的文件句柄。





















 
 
 


 
 
 
 