一、python 對象
Python 使用對象模型來存儲數(shù)據,構造的任何類型的值都是一個對象(比如我們創(chuàng)建的整數(shù):26,字符串:“hello world”,列表:[1, 2, 3] 等都是對象)。對象可以理解為保存在內存中的一段具有固定格式的數(shù)據,所有的 Python 對象都擁有三個特性:身份(ID),類型 和 值。
1、身份(ID)
每一個對象都有一個唯一的身份標識自己,對象一旦建立,它的ID永遠不會改變,你可以認為它是該對象在內存中的地址。
內建函數(shù) id()
內建函數(shù) id()
函數(shù)返回一個表示對象ID的整數(shù)。
CPython實現(xiàn)細節(jié):對于CPython,id(x)為x存儲在內存中的地址。
操作符 is 和 is not
is
和 is not
操作符比較兩個變量所指向的對象(或者變量指向的對象)的ID是否相同,也就是比較兩個變量是否指向同一個對象。
2、類型
每個對象的頭部信息中都有一個類型標識符來標識這個對象的類型(實際上是一個指向對應類型對象(比如:int、str、dict等)的指針)。對象的類型決定了對象數(shù)據的特性以及支持的操作,還定義了該類型的對象可能具有的值。
type()
函數(shù)返回對象的類型(它本身也是一個對象)。與ID 一樣,對象的類型也是不可以修改的。
3、值
某些值可以改變的對象稱為可變的;一旦建立,值就不可以改變的對象稱為不可變的。一個對象的可變性由它的類型所決定。注意:上面三個特性在對象創(chuàng)建的時候就被賦值,除了值之外,其它兩個特性都是只讀的。
4、對象屬性
某些 Python 對象有屬性:數(shù)據或相關聯(lián)的可執(zhí)行代碼(比如方法)。 Python 用點(.
)標記法來訪問屬性。屬性包括相應對象的名字等等,最常用的屬性是方法,不過有一些 Python 類型也有數(shù)據屬性。含有數(shù)據屬性的對象包括(但不限于):類、類實例、模塊、復數(shù)和文件。
5、引用計數(shù)器
每個對象的頭部信息中不僅包含了標識該對象類型的類型標識符,還包含一個引用的計數(shù)器,用來計數(shù)這個對象被變量的引用次數(shù),來決定是不是可以回收這個對象。
你可以向Python查詢對一個對象的引用的次數(shù):在 sys 模塊中的 getrefcount 函數(shù)會返回對象的引用次數(shù)。
Python提供了強大的內置對象類型作為語言的組成部分,除非你有內置類型無法提供的特殊對象要處理,最好總是使用內置對象而不是使用自己的實現(xiàn)。
Python的內置工具是標準的,他們一般都是一致的。對于簡單的任務,內置類型往往能夠表現(xiàn)問題領域的所有結構,僅使用Python內置對象類型就能夠完成很多工作。而且Python的內置對象類型優(yōu)化了用C實現(xiàn)的數(shù)據結構算法。盡管可以實現(xiàn)屬于自己的類似的數(shù)據類型,但往往很難達到內置數(shù)據類型所提供的性能水平。對于復雜的任務,或許仍然需要提供自己的對象,這時需要使用Python的類或C語言的接口,人工實現(xiàn)的對象往往建立在像列表和字典這樣的內置類型的基礎上。
二、標準類型(基本數(shù)據類型)
- 整型
- 布爾型
- 浮點型
- 復數(shù)型
- 字符串
- 列表
- 元祖
- 字典
這些類型是 Python 內建的基本數(shù)據類型,我們會在后面的章節(jié)來詳細介紹它們。
標準類型的分類
如果讓我們以最啰嗦的方式來描述標準類型,我們也許會稱它們是 Python 的 “基本內建數(shù)據對象原始類型” 。
- “基本”,是指這些類型都是 Python 提供的標準或核心類型。
- “內建”,是由于這些類型是 Python 默認就提供的
- “數(shù)據”,因為他們用于一般數(shù)據存儲
- “對象”,因為對象是數(shù)據和功能的默認抽象
- “原始”,因為這些類型提供的是最底層的粒度數(shù)據存儲
- “類型”,因為他們就是數(shù)據類型
1、存儲模型
我們對類型進行分類的第一種方式, 就是看看這種類型的對象能保存多少個對象。Python的類型, 就象絕大多數(shù)其它語言一樣,能容納一個或多個值。一個能保存單個字面對象的類型我們稱它為原子或標量存儲。那些可容納多個對象的類型,我們稱之為容器存儲。容器類型又帶來一個新問題,那就是它是否可以容納不同類型的對象。所有的 Python 容器對象都能夠容納不同類型的對象。字符串看上去像一個容器類型,因為它“包含”字符(并且經常多于一個字符),不過由于 Python 并沒有字符類型,所以字符串是一個自我包含的文字類型。
2、更新模型
另一種對標準類型進行分類的方式就是根據對象創(chuàng)建成功之后,它的值可不可以進行更新。可變對象允許他們的值被更新,而不可變對象則不允許他們的值被更改。
3、訪問模型
根據訪問我們存儲的數(shù)據的方式對數(shù)據類型進行分類。在訪問模型中共有三種訪問方式:直接存取,順序,和映射。
對非容器類型可以直接訪問。所有的數(shù)值類型都歸到這一類。
序列類型是指容器內的元素可以按從 0 開始的索引順序訪問。一次可以訪問一個元素或多個元素, 也就是大家所了解的切片(slice)。 字符串, 列表和元組都歸到這一類。
映射類型類似序列的索引屬性,不過它的索引并不使用順序的數(shù)字偏移量取值, 它的元素無序存放, 通過一個唯一的 key 來訪問, 這就是映射類型, 它容納的是哈希鍵-值對的集合。
三、其他內建類型
- 類型
- None
- 文件
- 集合
- 函數(shù)
- 模塊
- 類
這些是當你做 Python 開發(fā)時可能會用到的一些數(shù)據類型。我們在這里討論 Type 和 None類型的使用,除此之外的其他類型我們會在后面的單獨章節(jié)來詳細介紹它們。
Python中所有一切都是某種類型的對象,即便是某個對象的類型!任何對象的類型都是類型為 “type” 的對象。
1、type 類型對象
對象的一系列固有行為和特性(比如支持哪些運算,具有哪些方法)必須事先定義好。從這個角度看,對象的類型正是保存這些信息的最佳位置。描述一種類型所需要的信息不可能用一個字符串來搞定,所以類型不能是一個簡單的字符串,這些信息不能也不應該和數(shù)據保存在一起, 所以我們將類型定義成對象。
通過調用 type()
函數(shù)你能夠得到特定對象的類型信息。
我們得到一個簡潔的輸出結果<class 'int'>。但是它并不是一個簡簡單單的告訴你 123 是個整數(shù)這樣的字符串。您看到的<class 'int'>實際上是一個類型對象,碰巧它輸出了一個字符串來告訴你它是個 int 型對象。
所有類型對象的類型都是 type,它也是所有 Python 類型的根和所有 Python 標準類的默認元類(metaclass)。
2、None 對象
Python 有一個特殊的類型,被稱作 NoneType,它只有一個值,那就是 None。它用于表示在許多情況下不存在值,一般都用來起到一個空的占位符的作用。它不支持任何運算也沒有任何內建方法。None 沒有什么有用的屬性,它的布爾值總是 False。None不是意味著“未定義”,None是某些內容,而不是沒有內容,他是一個真正的對象,并且有一塊內存,由Python給定一個內置的名稱。
四、內部類型
- 代碼
- 幀
- 跟蹤記錄
- 切片
- 省略
- Xrange
我們在這里簡要介紹一下這些內部類型,一般的程序員通常不會直接和這些對象打交道。
(后期補充 ... )
五、動態(tài)類型
python是動態(tài)類型的(它自動的跟蹤對象的類型),Python中沒有類型聲明,運行的表達式的語法(創(chuàng)建對象時的表達式,例如:一個方括號的表達式會生成一個列表,大括號中的表達式會建立一個字典)決定了創(chuàng)建和使用的對象的類型。一旦創(chuàng)建一個對象,它就和操作集合綁定了(只可以對字符串對象進行字符串相關的操作,對列表對象進行列表相關的操作)所以Python也是強類型語言。
1、變量、對象和引用
在Python中我們使用對象模型來存儲數(shù)據,使用變量(變量名)來指向我們創(chuàng)建的對象,我們在程序代碼中使用變量名來引用他們所指向的對象。我們可以簡單的認為對象(數(shù)據)就是變量的值,實際上,變量為對象的一個引用。
對于大多數(shù)編譯型語言來說,變量在使用前必須先聲明。但是在 Python 中,無需顯式的聲明變量,變量在第一次被賦值時自動聲明并創(chuàng)建。變量一旦被賦值,您就可以通過變量名來訪問它的值,之后的賦值將會改變變量的值。變量只有被創(chuàng)建和賦值后才能被使用,當變量出現(xiàn)在表達式中,它會馬上被當前引用的對象所替代。變量的創(chuàng)建和使用
通過下面的例子,我們直觀的了解一下變量的創(chuàng)建和使用過程:1、創(chuàng)建一個int類型的對象代表值4;
2、創(chuàng)建一個變量 a,如果它還沒有被創(chuàng)建的話;
3、通過賦值運算符(=)將變量 a 指向對象4;
4、在表達式中將變量a替換為對象4;
5、對象4和對象5進行加法運算,打印運算結果的字符串格式 “9”;
圖示:在運行 a = 4 后,變量a變成對象4 的一個引用,在內部,變量事實上是到對象內存空間(通過運行常量表達式 = 4而創(chuàng)建)的一個指針。一旦變量a被使用,Python自動跟隨這個變量到對象4的鏈接,使用對象4參與和對象5的運算。
變量和對象
1、變量和對象保存在內存的不同部分,并通過引用(指針)相關聯(lián);
2、變量總是連接到對象,并且絕不會連接到其他變量上,但是更大的對象可能連接到其他的對象;
3、在Python內部,作為一種優(yōu)化,Python預先緩存了一些不變的對象并對其進行復用;
4、對象有更復雜的結構,而不僅僅是有足夠的空間表示它的值這么簡單。每一個對象都有兩個標準的頭部信息:一個類型標識符去標識這個對象的類型;一個引用的計數(shù)器,用來決定是不是可以回收這個對象;
2、對象的動態(tài)類型——類型標識符
python是動態(tài)類型的,變量名不但無需事先聲明, 而且也無需類型聲明。類型的概念僅存在于對象中而不是變量名中,變量永遠不會有任何的和它相關聯(lián)的類型信息或約束。所以變量是通用的,它只是在一個特定的時間點,簡單的引用了一個特定的對象而已。對象知道自己的類型,每個對象都包含了一個頭部信息——類型標識符(實際上是一個指向類型對象的指針),標記了這個對象的類型(例如:一個整數(shù)對象10,包含了值10以及一個頭部信息,告訴python,這個是一個整數(shù)對象)。一旦創(chuàng)建一個對象,它就和操作集合綁定了(只可以對字符串對象進行字符串相關的操作,對列表對象進行列表相關的操作),所以Python也是強類型語言。
Python 語言中,對象的類型和內存占用都是運行時確定的,在創(chuàng)建也就是賦值時,解釋器會根據運行的表達式的語法和右側的操作數(shù)(例如:一個方括號的表達式會生成一個列表,大括號中的表達式會建立一個字典)來決定新對象的類型。在對象創(chuàng)建后,一個該對象的引用會被賦值給左側的變量。
在代碼中檢驗特定的類型,會破壞代碼的靈活性,即限制它只能使用一種類型工作。沒有這樣的檢測,代碼也許能夠使用整個范圍的類型工作。在Python中,我們編寫對象接口(所支持的操作)而不是類型。不關注特定的類型意味著代碼會自動的適應他們中的很多類型:任何具有兼容接口的對象均能夠工作,而不管它是什么對象類型。動態(tài)類型是Python語言靈活性的根源。
3、對象的垃圾收集——引用計數(shù)器
在Python中,每當一個變量名被賦予了一個新的對象,之前的那個對象占用的空間就會被回收(如果它沒有被其他的變量名或對象所引用的話)。這種自動回收對象空間的技術叫做垃圾收集。
要保持追蹤內存中的對象, Python 使用了引用計數(shù)這一簡單技術。也就是說 Python 內部記錄著所有使用中的對象各有多少引用。在Python內部,它在每個對象的頭部信息中保存了一個引用計數(shù)器,計數(shù)器記錄了當前指向該對象的引用的數(shù)目。一旦(并精確的在同一時間)這個計數(shù)器被設置為零,這個對象的內存空間就會被自動回收(對象的空間自動放入自由內存空間池,等待后來的對象使用)。嚴格來說這不是 100%正確,不過現(xiàn)階段你可以就這么認為。
3.1 增加引用計數(shù)
當對象被創(chuàng)建并(將其引用)賦值給變量時,該對象的引用計數(shù)就被設置為 1。當同一個對象(的引用)又被賦值給其它變量時,或作為參數(shù)傳遞給函數(shù), 方法或類實例時, 或者被賦值為一個窗口對象的成員時,該對象的一個新的引用,或者稱作別名,就被創(chuàng)建(則該對象的引用計數(shù)自動加 1)。請看以下聲明:
x = 3.14
y = x
語句 x = 3.14 創(chuàng)建了一個浮點數(shù)對象(3.14)并將其引用賦值給 x。 x 是其第一個引用, 因此,該對象的引用計數(shù)被設置為 1。語句 y=x 創(chuàng)建了一個指向同一對象的別名 y(參閱圖 3-2)。事實上并沒有為 Y 創(chuàng)建一個新對象, 而是該對象的引用計數(shù)增加了 1 次(變成了 2)。這是對象引用計數(shù)增加的方式之一。還有一些其它的方式也能增加對象的引用計數(shù), 比如該對象作為參數(shù)被函數(shù)調用或這個對象被加入到某個容器對象當中時。
3.2 減少引用計數(shù)
當對象的引用被銷毀時,引用計數(shù)會減小。最明顯的例子就是當引用離開其作用范圍時,這種情況最經常出現(xiàn)在函數(shù)運行結束時,所有局部變量都被自動銷毀,對象的引用計數(shù)也就隨之減少。
當變量被賦值了另外一個其他對象時,原對象的引用計數(shù)也會自動減 1:
foo = 'xyz'
bar = foo
foo = 123
當字符串對象"xyz"被創(chuàng)建并賦值給 foo 時, 它的引用計數(shù)是 1。當增加了一個別名 bar時, 引用計數(shù)變成了 2。不過當 foo 被重新賦值給整數(shù)對象 123 時, xyz 對象的引用計數(shù)自動減 1,又重新變成了 1。其它造成對象的引用計數(shù)減少的方式包括使用 del 語句刪除一個變量, 或者當一個對象被移出一個窗口對象時(或該容器對象本身的引用計數(shù)變成了 0 時)。
總結一下,一個對象的引用計數(shù)在以下情況會減少:一個本地引用離開了其作用范圍。
3.3 del 語句
del 語句會刪除對象的一個引用,它的語法是:
del obj1[, obj2[,... objN]]
執(zhí)行 del x 刪除該對象的最后一個引用, 也就是該對象的引用計數(shù)會減為0, 這會導致該對象從此“無法訪問”或“無法抵達”。 從此刻起, 該對象就成為垃圾回收機制的回收對象。 注意任何追蹤或調試程序會給一個對象增加一個額外的引用, 這會推遲該對象被回收的時間。
3.4 垃圾收集
像上面說的,雖然解釋器跟蹤對象的引用計數(shù), 但垃圾收集器負責釋放內存。垃圾收集器是一塊獨立代碼, 它用來尋找引用計數(shù)為 0 的對象。它也負責檢查那些雖然引用計數(shù)大于 0 但也應該被銷毀的對象。
從技術上講,Python的垃圾收集主要基于引用計數(shù)器,然而它也有一部分功能可以及時的檢測并回收帶有循環(huán)引用的對象。由于引用實現(xiàn)為指針,一個對象有可能會引用自身,或者引用另一個引用了自身的對象。 這說明只靠引用計數(shù)是不夠的。 Python 的垃圾收集器實際上是一個引用計數(shù)器和一個循環(huán)垃圾收集器。 盡管這種情況相對 很少,由于這樣的對象的引用計數(shù)器不會清除為0,必須特別對待它們。
這里對Python的垃圾收集器的介紹只適用于標準的CPython,JPython和IronPython可能使用不同的方案。
垃圾收集最直接的、可感受到的好處就是,這意味著可以在腳本中任意使用對象而不需要考慮釋放內存空間,在程序運行時,Python將會清理那些不在使用的空間。Python 解釋器承擔了內存管理的復雜任務, 這大大簡化了應用程序的編寫。你只需要關心你要解決的問題,至于底層的事情放心交給 Python 解釋器去做就行了。
4、共享引用
在Python中一個變量可以被賦值引用多個對象,也可以多個變量名引用了同一個對象,在Python中這叫作共享引用。
4.1 修改變量的值——變量指向的對象為不可更改類型
圖示:運行賦值語句a=3,在內存空間中創(chuàng)建對象3和變量a,a的引用指向對象3的內存空間。運行賦值語句b=a,在內存中創(chuàng)建變量b,并且b的引用指向a變量引用指向的對象3。
圖示:運行賦值語句a = "hello",在內存空間中創(chuàng)建對象“hello'”,a的引用指向對象”hello“的內存空間。
在Python中,變量總是一個指向對象的指針,而不是可改變的內存區(qū)域的標簽:給一個變量賦一個新的值,并不是替換了原始的對象,而是讓這個變量去引用完全不同的另一個對象。
4.2 修改變量的值——變量指向的對象為可更改類型
圖示:運行賦值語句a=[1, 2, 3],在內存空間中創(chuàng)建對象[1, 2, 3]和變量a,a的引用指向對象[1, 2, 3]的內存空間。運行賦值語句b=a,在內存中創(chuàng)建變量b,并且b的引用指向a變量引用指向的對象[1, 2, 3]。運行a[0] = "hello",因為對象[1, 2, 3]為可變類型,所以直接修改對象的值。變量a和變量b指向對象的引用不變。
在列表中的元素是通過他們的位置進行讀取的,所以a[0]為對象1的引用,我們修改a[0]的值,也就是將a[0]的引用指向了其他對象,并不影響a對對象[1, 2 ,3]的引用。
對于這種可變對象也就是說可在原處直接修改的對象,共享引用時需要倍加小心,因為對一個變量名的修改會影響到其他的變量。這種行為通常來說就是你所想要的,應該了解它是如何運作的,讓它按照預期去工作。這也是默認的,如果你不想要這樣的現(xiàn)象發(fā)生,需要Python拷貝對象,而不是創(chuàng)建引用。有很多拷貝一個列表的方法,包括內置列表函數(shù),以及標準庫的copy模塊,也許最常用的方法是從頭到尾的分片。圖示:a和b應該是 == 的,但不是 is 的,但是因為小的整數(shù)和字符串被緩存并復用了,所以is表達式告訴我們a 和b引用了同一個對象。
5、作用域
作用域定義一個代碼塊中變量的可見性。如果一個局部變量在一個代碼塊中定義,那么它的作用域就在那個代碼塊中。如果定義出現(xiàn)在函數(shù)代碼塊中,那么其作用域擴展到這個函數(shù)代碼塊包含的任何代碼塊中,除非某個被包含的代碼塊為該名稱引入一個不同的綁定。
當一個變量在代碼塊中使用時,它使用包含它最近的作用域解析。對于一個代碼塊所有可見作用域的集合稱做代碼塊的環(huán)境。
當一個變量完全找不到時,將引發(fā)一個 NameError
異常。如果當前的作用域是一個函數(shù)作用域,而且變量引用一個局部變量,這個變量在該名稱使用的時候還沒有綁定到一個值,則引發(fā)一個UnboundLocalError
異常。UnboundLocalError是 NameError
的子類。
如果名稱綁定操作發(fā)生在代碼代碼塊內的任何地方,則代碼塊內的名稱的所有使用都被視為對當前代碼塊的引用。這可能會導致在代碼塊中綁定名稱之前出現(xiàn)錯誤。這個規(guī)則是微妙的。Python缺少聲明并允許在代碼塊內的任何地方進行名稱綁定操作。代碼代碼塊的局部變量可以通過掃描用于名稱綁定操作的代碼塊的整個文本來確定。
如果 global
語句出現(xiàn)在代碼塊內,在語句中指定的名稱的所有引用都是指該名稱在的頂級命名空間中的綁定。名稱在頂級命名空間中的解析通過搜索全局命名空間,即包含該代碼塊的模塊的命名空間,和內建的命名空間——模塊 builtins
的命名空間。首先搜索全局命名空間。如果在那里沒有找到名稱,則搜索 builtins 命名空間。global語句必須位于該名稱的所有引用之前。
global 語句的作用域與同一代碼塊中的名稱綁定操作相同。如果自由變量的最近的包圍作用域包含全局語句,則該自由變量被視為全局變量。
nonlocal 語句使得對應的名稱引用在最靠近的包含它的函數(shù)的作用域中綁定的變量。如果給定的名稱在任何包含它的函數(shù)的作用域中都找不到,則在編譯時刻引發(fā) SyntaxError。
模塊的命名空間在第一次導入模塊時自動創(chuàng)建。腳本的主模塊始終叫做 main 。
類定義以及 exec()
和eval()
的參數(shù)在名稱解析的上下文中比較特殊。類定義是可以使用和定義名稱的可執(zhí)行語句。這些引用遵循正常的名稱解析規(guī)則,除了一個例外,就是未綁定的局部變量在全局作用域中查找。類定義的命名空間成為類的屬性字典。在類代碼塊中定義的名稱的作用域限制在類代碼塊中;它不會延伸到方法的代碼塊中 —— 包括解析式和生成器表達式,因為它們是使用函數(shù)作用域實現(xiàn)的。也就是說下面這段代碼執(zhí)行會失敗:
class A:
a = 42
b = list(a + i for i in range(10))
《Python基礎手冊》系列:
Python基礎手冊 1 —— Python語言介紹
Python基礎手冊 2 —— Python 環(huán)境搭建(Linux)
Python基礎手冊 3 —— Python解釋器
Python基礎手冊 4 —— 文本結構
Python基礎手冊 5 —— 標識符和關鍵字
Python基礎手冊 6 —— 操作符
Python基礎手冊 7 —— 內建函數(shù)
Python基礎手冊 8 —— Python對象
Python基礎手冊 9 —— 數(shù)字類型
Python基礎手冊10 —— 序列(字符串)
Python基礎手冊11 —— 序列(元組&列表)
Python基礎手冊12 —— 序列(類型操作)
Python基礎手冊13 —— 映射(字典)
Python基礎手冊14 —— 集合
Python基礎手冊15 —— 解析
Python基礎手冊16 —— 文件
Python基礎手冊17 —— 簡單語句
Python基礎手冊18 —— 復合語句(流程控制語句)
Python基礎手冊19 —— 迭代器
Python基礎手冊20 —— 生成器
Python基礎手冊21 —— 函數(shù)的定義
Python基礎手冊22 —— 函數(shù)的參數(shù)
Python基礎手冊23 —— 函數(shù)的調用
Python基礎手冊24 —— 函數(shù)中變量的作用域
Python基礎手冊25 —— 裝飾器
Python基礎手冊26 —— 錯誤 & 異常
Python基礎手冊27 —— 模塊
Python基礎手冊28 —— 模塊的高級概念
Python基礎手冊29 —— 包