其實就表面感官來講,元組和列表的樣子大同小異,面試中常常會遇到的,tuple
和list
有什麼區別?這種問題幾乎都問爛了,大部分人能夠回答的七七八八了,什麼tuple
不能變,list
能夠進行增刪改;tuple
建立是經過()
,list
是經過[]
,短短兩句話道盡其功能與生成,然而道不盡其本質與性能,其豐富的內涵還須要細細展開與推演。html
首先來分析list
列表,它的具體結構以下所示:python
typedef struct { PyObject_VAR_HEAD /* Vector of pointers to list elements. list[0] is ob_item[0], etc. */ PyObject **ob_item; /* ob_item contains space for 'allocated' elements. The number * currently in use is ob_size. * Invariants: * 0 <= ob_size <= allocated * len(list) == ob_size * ob_item == NULL implies ob_size == allocated == 0 * list.sort() temporarily sets allocated to -1 to detect mutations. * * Items must normally not be NULL, except during construction when * the list is not yet visible outside the function that builds it. */ Py_ssize_t allocated; } PyListObject;
有興趣的讀者,可直接閱讀 list 列表實現的源碼文件listobject.h 和 listobject.c。git
在最近的一篇文章中咱們分析到list
本質上是一個長度可變的連續數組,其中ob_item
是一個指針列表,裏邊的每個指針都指向列表中的元素,而allocated
則用於存儲該列表目前已被分配的空間大小。須要注意的是allocated
和列表的實際空間大小不一樣,列表實際空間大小,指的是len(list)
返回的結果,也就是上邊代碼中註釋中的 ob_size
,表示該列表總共存儲了多少個元素,而在實際狀況中,爲了優化存儲結構,避免每次增長元素都要從新分配內存,列表預分配的空間allocated
每每會大於ob_size
。github
所以allocated
和ob_size
的關係是:allocated >= len(list) = ob_size >= 0
。面試
若是當前列表分配的空間已滿(即allocated == len(list)
),則會向系統請求更大的內存空間,並把原來的元素所有拷貝過去。c#
接下來再分析元組,以下所示爲 Python 3.7 tuple 元組的具體結構:數組
typedef struct { PyObject_VAR_HEAD PyObject *ob_item[1]; /* ob_item contains space for 'ob_size' elements. * Items must normally not be NULL, except during construction when * the tuple is not yet visible outside the function that builds it. */ } PyTupleObject;
有興趣的讀者,可閱讀tuple
元組實現的源碼文件 tupleobject.h 和 tupleobject.c。緩存
他的memory layout
以下所示數據結構
咱們能夠看到,PyTupleObject
只存儲了兩個對象,分別是:app
PyObject_VAR_HEAD
: cpython中容器對象的頭部ob_item
: 一個長度爲 1 的存儲內容爲 PyObject * 的數組tuple 和 list 類似,本質也是一個數組,可是空間大小固定。不一樣於通常數組,Python 的 tuple 作了許多優化,來提高在程序中的效率。
舉個例子,爲了提升效率,避免頻繁的調用系統函數free
和malloc
向操做系統申請和釋放空間,tuple
在文件Objects/tupleobject.c
中第 28 行定義了一個free_list
:
static PyTupleObject *free_list[PyTuple_MAXSAVESIZE];
全部申請過的,小於必定大小的元組,在釋放的時候會被放進這個free_list
中以供下次使用。也就是說,若是之後須要再去建立一樣的tuple
,Python 就能夠直接從緩存中載入。
free_list[0]
用於存儲長度爲 0 的tuple
對象,整個解釋器的生命週期裏面只有一個長度爲 0 的tuple
對象實例free_list[1]
用於存儲長度爲 1 的tuple
對象,能夠經過tuple
對象的ob_item[0]
指針遍歷到下一個長度爲 1 的tuple
對象free_list[2]
用於存儲長度爲 2 的tuple
對象,上圖畫的free_list[2]
中只有一個對象free_list
每一格最多能存儲PyTuple_MAXFREELIST
個tuple
鏈表長度,這裏定義的是 2000咱們來看下PyTuple_New
函數
/* Objects/tupleobject.c 79 - 136 行 */ PyObject * PyTuple_New(Py_ssize_t size) { PyTupleObject *op; Py_ssize_t i; if (size < 0) { PyErr_BadInternalCall(); return NULL; } /* 若是啓動了 free_list 存儲 */ #if PyTuple_MAXSAVESIZE > 0 if (size == 0 && free_list[0]) { /* 若是 size 爲 0, 則返回 free_list 的第一個元素 */ op = free_list[0]; Py_INCREF(op); #ifdef COUNT_ALLOCS _Py_tuple_zero_allocs++; #endif return (PyObject *) op; } if (size < PyTuple_MAXSAVESIZE && (op = free_list[size]) != NULL) { /* 若是 size 在 free_list 範圍內,而且 free_list[size] 存有以前回收過的對應size的tuple 對象 把 free_list[size] 指向 op 的下一個鏈表地址 */ free_list[size] = (PyTupleObject *) op->ob_item[0]; numfree[size]--; #ifdef COUNT_ALLOCS _Py_fast_tuple_allocs++; #endif /* Inline PyObject_InitVar */ #ifdef Py_TRACE_REFS Py_SIZE(op) = size; Py_TYPE(op) = &PyTuple_Type; #endif _Py_NewReference((PyObject *)op); } else #endif { /* free_list 未找到對應大小的 tuple 而且 size 不爲 0,向操做系統分配 */ /* Check for overflow */ if ((size_t)size > ((size_t)PY_SSIZE_T_MAX - sizeof(PyTupleObject) - sizeof(PyObject *)) / sizeof(PyObject *)) { return PyErr_NoMemory(); } op = PyObject_GC_NewVar(PyTupleObject, &PyTuple_Type, size); if (op == NULL) return NULL; } /* 把 tuple 裏面的元素設置爲空指針 */ for (i=0; i < size; i++) op->ob_item[i] = NULL; #if PyTuple_MAXSAVESIZE > 0 if (size == 0) { free_list[0] = op; ++numfree[0]; Py_INCREF(op); /* extra INCREF so that this is never freed */ } #endif #ifdef SHOW_TRACK_COUNT count_tracked++; #endif _PyObject_GC_TRACK(op); return (PyObject *) op; }
從函數名也大概能夠猜出功能了
嘿嘿,這倒不必定,好比說進行如下操做
>>> t = ([1,2,3,4],[5,4,5,6,4]) >>> t ([1, 2, 3, 4], [5, 4, 5, 6, 4]) >>> t[1].append("hello yerik") >>> t ([1, 2, 3, 4], [5, 4, 5, 6, 4, 'hello yerik']) >>> t[1].pop() 'hello yerik' >>> t[1].pop(2) 5 >>> t ([1, 2, 3, 4], [5, 4, 6, 4])
這個tuple定義的時候有2個元素,都是兩個list。不是說tuple一旦定義後就不可變了嗎?怎麼後來又變了?
別急別急,案例中的t包含兩個元素,都是list
當咱們咱們對tuple中的元素進行修改的時候,表面上tuple中的元素確實變化了,然而這並不影響tuple中的元素指針指向list,咱們修改list中的元素也並無變成別的list。tuple中的不變,指的是元素指針指向的元素地址起始位置不變,而元素地址對應的數據結構是可變仍是不可變的數據類型都是沒有關係的。經過這個咱們其實能夠根據list和tuple來進行組合了,好比說咱們在list中嵌入tuple作成一串只能添加不能修改的核心不變數據集;經過在tuple中嵌入list構建成可變元素的定長數據集。