該文章內容整理自《Python程式設計:從入門到實踐》、《流暢的Python》、以及網上各大部落格
Python 底層原理
PyObject
Python中一切都是物件,全部的物件都有一個共同的基礎類別。Python 是用 C 實現的,C 是一種 OO 的語言,而 Python 是一個 OOP 的語言,那麼怎樣在 C 語言層面實現 OOP ,實現多型,這是一個有意思的話題。Python 內部使用了一個 PyObject 結構體來儲存全部物件共同的數據成員,以及實現GC機制 機製所須要的一些輔助欄位等,所以能夠說 PyObject 就是 Python 物件機制 機製的基石
PyObject 在 Include/object.h 中給出定義
typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
} PyObject;
其中
- _PyObject_HEAD_EXTRA:實現一個雙向鏈表,但是隻有在宏Py_TRACE_REFS被定義時,也就是Py_DEBUG被定義了,Python直譯器Release版本是不會有它的,目標只是分析原始碼的話完全可以忽略
- ob_refcnt:即object reference count,參照計數,和垃圾處理機制 機製有關。當減爲 0 時則會從堆上被刪除(Python 中物件是在堆上申請的結構體)。而 Py_ssize_t 會根據64位元系統或32位元系統而調整格式,但本質上是 int
- ob_type:指向一個 PyTypeObject,而 PyTypeObject 也是一個物件,用來指定一個物件型別的型別物件,記錄了不同的物件所需的記憶體空間等大小資訊。PyTypeObject 是元類的底層實現
typedef struct _typeobject {
PyObject_VAR_HEAD
const char *tp_name; /* For printing, in format "<module>.<name>" */
Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */
/* Methods to implement standard operations */
destructor tp_dealloc;
printfunc tp_print;
getattrfunc tp_getattr;
setattrfunc tp_setattr;
PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
or tp_reserved (Python 3) */
reprfunc tp_repr;
...
} PyTypeObject;
PyObject 內容相當的簡潔,在 C 層面上就是一個結構體。PyObject 儲存了每個物件都必須具備的東西,即參照計數和型別資訊,但其餘內容需要各自自行定義。每個Python物件記憶體的最前面的一塊就放着PyObject,所以各種物件可以通過強制轉換爲PyObject來單獨存取ob_refcnt以及ob_type屬性。如 Python 建立一個整形物件 PyIntObject,爲這個物件分配記憶體,並進行初始化。然後這個物件會由一個 PyObject 變數來維護,因爲每一個物件都擁有相同的物件頭部,這使得物件的參照變得非常的統一。無論物件實際上的型別是什麼,只需要通過PyObject指針就可以參照任意的一個物件
PyObject 是一個定長物件的結構體,而變長物件的結構體則是 PyVarObject。PyVarObject 只比 PyObject 多一個 ob_size 屬性,用來指明瞭變長物件中有多少個元素
typedef struct {
PyObject ob_base;
Py_ssize_t ob_size;
} PyVarObject;
儲存機制 機製
Python 通過三個方面進行記憶體管理
參照計數機制 機製
Python內部使用參照計數來保持追蹤記憶體中的物件,所有物件都有參照計數(即結構體 PyObject 中的 ob_refcnt)。sys.getrefcount(obj)函數可以獲得物件的當前參照計數
參照計數增加的情況(注意是整數 4 的參照計數):
- 物件被建立,如 x=4
- 物件賦值給其他別名,如 y=x
- 作爲容器物件的一個元素,如 a=[1,x,‘33’]
- 被作爲參數傳遞給函數,如 foo(x)
參照計數減少的情況:
- 物件的別名被顯式的銷燬,如 del x
- 物件的別名被賦值給其他物件,如 x=5
- 參照超出作用域,,如上面的foo(x)函數結束時
參照計數機制 機製是 Python 記憶體管理的主要方法。其優點是操作簡單、實時。缺點則是維護性高,而且會出現死鎖
a = [1,2]
b = [2,3]
a.append(b)
b.append(a)
DEL a
DEL b
垃圾回收機制 機製
當一個物件的參照計數歸零時,它將被垃圾收集機制 機製處理掉。當垃圾回收啓動時,Python 掃描到這個參照計數爲0的物件,就將它所佔據的記憶體清空
- 垃圾回收時,Python不能進行其它的任務,頻繁的垃圾回收將大大降低Python的工作效率
- Python只會在特定條件下,自動啓動垃圾回收(垃圾物件少就沒必要回收)
- 當Python執行時,會記錄其中分配物件(object allocation)和取消分配物件(object deallocation)的次數。當兩者的差值高於某個閾值時,垃圾回收纔會啓動。可以使用 gc 模組裡的 get_threshold() 函數來檢視閾值,並且可以使用 gc 模組裡的 collect() 函數來手動啓動垃圾回收
另外,Python 將所有的物件分爲0,1,2三代。所有的新建物件都是0代物件,當某一代物件經歷過垃圾回收,依然存活,就被歸入下一代物件。每10次0代垃圾回收,會配合1次1代的垃圾回收;而每10次1代的垃圾回收,纔會有1次的2代垃圾回收
記憶體池機制 機製
Python中以256K爲界限劃分大記憶體和小記憶體。其中,大記憶體使用malloc進行分配,小記憶體使用記憶體池進行分配
- 第 -1,-2層:操作系統進行操作
- 第0層:大記憶體。若請求分配的記憶體大於256K,malloc函數分配記憶體,free函數釋放記憶體
- 第1層和第2層:記憶體池,由 Python 的介面函數 PyMem_Malloc() 實現。若請求分配的記憶體在1~256位元組之間就使用記憶體池管理系統進行分配,呼叫malloc函數分配記憶體,但是每次只會分配一塊大小爲256K的大塊記憶體,不會呼叫free函數釋放記憶體,將該記憶體塊留在記憶體池中以便下次使用
- 第3層:最上層,使用者對Python物件的直接操作
關鍵字
keyword 模組中的 kwlist 屬性記錄了 Python 提供的關鍵字
import keyword
keyword.kwlist
['False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
- del:Python 中的 del 不同於 C 的 free 和 C++ 的 delete,Python 有GC機制 機製,無須擔心記憶體漏失問題,所以 del 作用在變數上,而不是數據物件上。因此 del 刪除的是變數,是解除變數和數據物件的參照關係,而不是刪除數據物件。del可作用於Python中的所有變數,包括函數名、類屬性等
- yield:yield 類似 return,不同的是它返回的是生成器。生成器是迭代器,其中的值都是實時生成的,並不是全部儲存在記憶體中。同時,生成器只能迭代一次,即在回圈中使用 for i in my_generator: 後,後面不能夠再在回圈中迭代一次。在一次迭代中,每次呼叫生成器時都會在上一次停止的地方繼續執行。另外,可通過呼叫生成器的 next() 方法或 __next__屬性獲得下一個迭代值
def createGenerator():
for i in range(3):
yield i*i
for i in createGenerator():
print(i)
Ellipsis
Ellipsis是…的別名,它是 Python 中的一個內建常數。…的常見用法如下:
def func():
...
- Typt Hint:Python3 開始就引入了型別提示Typt Hint。但這只是一種註解,並不能在執行的時候去檢查對與錯。即Typt Hint只會提示型別,而不是強制定義型別,Python 依然是動態型別語言
x:int = 1.1
y:str = 'hello'
z:list = []
def add(x:int,y:int)->int:
return x+y
from typing import Dict, List, Tuple
names: List[str] = ["Guido", "Jukka", "Ivan"]
version1: Tuple[int, int, int] = (3, 7, 1)
version2: Tuple[int, ...] = (3, 7, 1)
options: Dict[str, bool] = {"centered": False, "capitalize": True}
Card = Tuple[str, str]
Deck = List[Card]
def deal_hands(deck: Deck) -> Tuple[Deck, Deck, Deck, Deck]:
...
l = [1]
l.append(l)
d = {}
d[1] = d
內建函數
1.數學運算類
- abs(x):求絕對值。參數可以是整型,也可以是複數。若參數是複數,則返回覆 回復數的模
- complex([real[, imag]]):建立一個複數
- divmod(a, b):分別取商和餘數
- float([x]):將一個字串或數轉換爲浮點數。如果無參數將返回 0.0
- int([x[, base]]):將一個字元轉換爲int型別,base表示進位制
- long([x[, base]]):將一個字元轉換爲long型別
- pow(x, y[, z]):返回x的y次冪
- range([start], stop[, step]):產生一個序列,預設從0開始
- round(x[, n]):四捨五入
- sum(iterable[, start]):對集合求和
- oct(x):將一個數字轉化爲8進位制
- hex(x):將整數x轉換爲16進位制字串
- chr(i):返回整數i對應的ASCII字元
- bin(x):將整數x轉換爲二進制字串
- bool([x]):將x轉換爲Boolean型別
2.集合類操作
- basestring():str和unicode的超類。但不能直接呼叫,可以用作isinstance判斷
- format(value [, format_spec]):格式化輸出字串
- unichr(i):返回給定int型別的unicode
- enumerate(sequence [, start = 0]):返回一個可列舉的物件,該物件的next()方法將返回一個tuple
- iter(o[, sentinel]):生成一個物件的迭代器,第二個參數表示分隔符
- max(iterable[, args…][key]):返回集閤中的最大值
- min(iterable[, args…][key]):返回集閤中的最小值
- dict([arg]):建立數據字典
- list([iterable]):將一個集合類轉換爲另外一個集合類
- set():set物件範例化
- frozenset([iterable]):產生一個不可變的set
- str([object]):轉換爲string型別
- sorted(iterable[, cmp[, key[, reverse]]]):隊集合排序
- tuple([iterable]):生成一個tuple型別
- xrange([start], stop[, step]):xrange()函數與range()類似
3.邏輯判斷
- all(iterable):集閤中的元素都爲真的時候爲真。若爲空串返回爲True
- any(iterable):集閤中的元素有一個爲真的時候爲真。特別的,若爲空串返回爲False
- cmp(x, y):如果x < y,返回負數;x == y,返回0;x > y,返回正數
4.反射
- callable(object):檢查物件object是否可呼叫
- classmethod():用來說明這個方式是個類方法
- compile(source, filename,mode[, flags[, dont_inherit]]):將source編譯爲程式碼或者AST物件。程式碼物件能夠通過exec語句來執行或者eval()進行求值。其中,參數source爲字串或者AST(Abstract Syntax Trees)物件;參數 filename 爲程式碼檔案名稱,如果不是從檔案讀取程式碼則傳遞一些可辨認的值;參數 model 爲指定編譯程式碼的種類。可以指定爲 ‘exec’,’eval’,’single’
- dir([object]):不帶參數時,返回當前範圍內的變數、方法和定義的型別列表;帶參數時,返回參數的屬性、方法列表
- delattr(object, name):刪除object物件名爲name的屬性
- eval(expression [, globals [, locals]]):計算表達式expression的值。警惕eval()函數的安全漏洞,有點類似於SQL隱碼攻擊
- execfile(filename [, globals [, locals]]):用法類似exec(),不同的是execfile的參數filename爲檔名,而exec的參數爲字串
- filter(function, iterable):構造一個序列,等價於[ item for item in iterable if function(item)]。其中,參數function爲返回值爲True或False的函數,可以爲None;參數iterable爲序列或可迭代物件
- getattr(object, name [, defalut]):獲取一個類的屬性
- globals():返回一個描述當前全域性符號表的字典
- hasattr(object, name):判斷物件object是否包含名爲name的特性
- hash(object):如果物件object爲雜湊表型別,返回物件object的雜湊值
- id(object):返回物件的唯一標識
- isinstance(object, classinfo):判斷object是否是class的範例
- issubclass(class, classinfo):判斷是否是子類
- len(s):返回集合長度
- locals():返回當前的變數列表
- map(function, iterable, …):遍歷每個元素,執行function操作
- memoryview(obj):返回一個記憶體映象型別的物件
- next(iterator[, default]):類似於iterator.next()
- object():基礎類別
- property([fget[, fset[, fdel[, doc]]]]):屬性存取的包裝類,設定後可以通過c.x=value等來存取setter和getter
- reduce(function, iterable[, initializer]):合併操作,從第一個開始是前兩個參數,然後是前兩個的結果與第三個合併進行處理,以此類推
- reload(module):重新載入模組
- setattr(object, name, value):設定屬性值
- repr(object):將一個物件變幻爲可列印的格式
- slice(start, stop[, step]):實現切片物件
- staticmethod():宣告靜態方法,是個註解
- super(type[, object-or-type]):參照父類別
- type(object):返回該object的型別
- vars([object]):返回物件的變數,若無參數與dict()方法類似
- bytearray([source [, encoding [, errors]]]):返回一個byte陣列。如果source爲整數,則返回一個長度爲source的初始化陣列;如果source爲字串,則按照指定的encoding將字串轉換爲位元組序列;如果source爲可迭代型別,則元素必須爲[0 ,255]中的整數;如果source爲與buffer介面一致的物件,則此物件也可以被用於初始化bytearray
- zip([iterable, …]):用於合併列表
5.IO操作
- file(filename [, mode [, bufsize]]):file型別的建構函式,用於開啓一個檔案
- input([prompt]):獲取使用者輸入
- open(name[, mode[, buffering]]):開啓檔案
- print():列印函數
- raw_input([prompt]):設定輸入,輸入都是作爲字串處理
#!/usr/bin/python
指令碼第一行很多時候會有 #!/usr/bin/python 語句,這條語句只對 Linux/Unix 使用者適用,用來指定本指令碼用什麼直譯器來執行
- #!/usr/bin/python 是告訴操作系統執行這個指令碼的時候,呼叫 /usr/bin 下的 python 直譯器。#!/usr/bin/python 相當於寫死了 python 路徑
- #!/usr/bin/env python 這種用法是爲了防止操作系統使用者沒有將 python 裝在預設的 /usr/bin 路徑裡。當系統看到這一行的時候,首先會到 env 設定裡查詢 python 的安裝路徑,再呼叫對應路徑下的直譯器程式完成操作。#!/usr/bin/env python 會去環境設定尋找 python 目錄,可以增強程式碼的可移植性,推薦這種寫法
如果呼叫 python 指令碼時,使用 python script.py,則#!/usr/bin/python 被忽略,等同於註釋
如果呼叫python指令碼時,使用 ./script.py ,則#!/usr/bin/python 指定直譯器的路徑
命令列選項
- python -h 或 python --help:列印幫助
- python -V 或 python --version:列印版本
- python -O:開啓基本優化。這將編譯(位元組碼)檔案的副檔名從.pyc更改爲.pyo
- python -R:開啓hash隨機化
- python -u:強制stdion、stdout和stderr完全無緩衝
- python -W:警告控制
- python -W ignore:忽略所有警告
- python -W default:明確請求預設行爲(每個原始碼行列印每個警告一次)
- python -W all:每次發生警告時都會發出警告(如果對同一原始碼行重複觸發警告(例如回圈內),則可能會生成許多訊息)
- python -W module:僅在每個模組中第一次出現時纔列印每個警告
- python -W once:僅在程式中第一次出現時纔列印每個警告
- python -W error:引發異常而不是列印警告訊息
- python -c <command>:在命令中執行Python程式碼。 命令可以是一個或多個由換行符分隔的語句,與正常模組程式碼一樣具有顯着的前導空白。如果給出了這個選項,第一個元素sys.argv將是 「-c」,並且當前目錄將被新增到開始處 sys.path(允許將該目錄中的模組作爲頂級模組匯入)。如python -c 「import time; print(time.strftime(’%Y-%m-%d %H:%M:%S’,time.localtime(time.time())))」 爲利用python列印當前時間
- python -m <module-name>:搜尋sys.path指定的模組並將其內容作爲__main__模組執行。由於參數是模組名稱,因此不得提供副檔名(.py)。module-name應該是一個有效的Python模組名稱。包名稱也是允許的。當提供包名稱而不是普通模組時,直譯器將<pkg>.__main__作爲主模組執行。 如果給出該選項,則第一個元素sys.argv將是模組檔案的完整路徑。與-c選項一樣,當前目錄將被新增到開始sys.path