目錄
吝嗇的初始化
总结
首頁 後端開發 Python教學 Python列表的長度調節方法(附程式碼)

Python列表的長度調節方法(附程式碼)

Dec 12, 2018 am 10:24 AM
python

這篇文章帶給大家的內容是關於Python清單的長度調節方法(附程式碼),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

Python 的清單(list)是一個非常靈活的數組,可以隨意調整長度。正是因為這種便利,使得我們會不由自主地去修改陣列以滿足我們的需求,其中相比於insert, pop 等等而言, append 用法更常見。

有像這樣使用:

>>> test = []
>>> test.append(1)
>>> test.append({2})
>>> test.append([3])
>>> print test

# 输出 
[1, set([2]), [3]]
登入後複製

也有像這樣使用的:

test = []

for i in range(4):
    test.append(i)
print test

# 输出 
[0, 1, 2, 3]
登入後複製

這樣用很開心,也很滿足。

但其實只要遇到能夠動態修改資料長度場景,我們都應該馬上反應過來一點,那就是記憶體管理的問題。

如果運作效率和便利性同時滿足的話,那簡直就是大大的福音呀。

然而,上帝為你開啟一扇窗的同時肯定也已經關上了一扇門了!

吝嗇的初始化

深受預分配知識的薰陶,我們也是覺得list 在初始化是有分配一定的長度的,要不然每次都申請內存那得多」low“啊。

然後實際上list 真的就是這麼」low「:

import sys

test = []
test_1 = [1]
print sys.getsizeof(test)
print sys.getsizeof(test_1) - sys.getsizeof(test)

# 输出 
72     # 空列表内存大小,也是 list 对象的总大小
8       # 代表增加一个成员,list 增加的大小
登入後複製

我們的猜測是,list 在定義之後,會預先分配好一個一定大小的池用來塞數據,以避免動不動就申請記憶體。

但是在上面的實驗看出,一個成員的列表,比一個空列表,長度僅僅只是大了8 字節,如果真的存在這樣一個預先分配的池,那麼在預分配個數之內添加成員,兩者的記憶體大小應該是保持不變才對。

所以可以猜測這塊 list 應該是沒有這樣的一個預先分配記憶體池。這裡需要來個實錘

PyObject *
PyList_New(Py_ssize_t size)
{
    PyListObject *op;
    size_t nbytes;

    if (size < 0) {
        PyErr_BadInternalCall();
        return NULL;
    }
    /* Check for overflow without an actual overflow,
     *  which can cause compiler to optimise out */
    if ((size_t)size > PY_SIZE_MAX / sizeof(PyObject *))
        return PyErr_NoMemory();
        
    // list对象指针的缓存
    if (numfree) {
        numfree--;
        op = free_list[numfree];
        _Py_NewReference((PyObject *)op);
    } else {
        op = PyObject_GC_New(PyListObject, &PyList_Type);
        if (op == NULL)
            return NULL;
    }
    
    // list 成员的内存申请
    nbytes = size * sizeof(PyObject *);
    if (size <= 0)
        op->ob_item = NULL;
    else {
        op->ob_item = (PyObject **) PyMem_MALLOC(nbytes);
        if (op->ob_item == NULL) {
            Py_DECREF(op);
            return PyErr_NoMemory();
        }
        memset(op->ob_item, 0, nbytes);
    }
    Py_SIZE(op) = size;
    op->allocated = size;
    _PyObject_GC_TRACK(op);
    return (PyObject *) op;
}
登入後複製

當我們在執行test = [1] 時,實際上只做了兩件事:

根據成員的數目,建立對應長度的空列表;(上述程式碼)

一個個將這些成員塞進去;

可能有童鞋會覺得,在塞成員的那一步,說不定會觸發什麼機制使它變大?

很可惜,因為初始化用的方法是PyList_SET_ITEM, 所以這裡是木有的觸發什麼機制,只是簡單的陣列成員賦值而已:

#define PyList_SET_ITEM(op, i, v) (((PyListObject *)(op))->ob_item[i] = (v))
登入後複製

所以整個list 的初始化,還真的就是木有預先分配的記憶體池,直接按需申請,一個蘿蔔一個坑,實在得狠;

可變長的關鍵

##初始化過程是這樣還可以理解,如果運行中還這樣的話,那就有點說不過去了。

試想下,在文章開頭用 append 的例子中,如果每append 一個元素就申請一次內存,那麼list 可能要被吐槽到懷疑人生了, 所以很明顯,在對於內存的申請,它還是有自己的套路的。

在 list 裡面,不管是 insert 、pop 還是 append,都會遇到 list_resize,故名思義,這個函數就是用來調整 list 物件的記憶體佔用的。

static int
list_resize(PyListObject *self, Py_ssize_t newsize)
{
    PyObject **items;
    size_t new_allocated;
    Py_ssize_t allocated = self->allocated;

    /* Bypass realloc() when a previous overallocation is large enough
       to accommodate the newsize.  If the newsize falls lower than half
       the allocated size, then proceed with the realloc() to shrink the list.
    */
    if (allocated >= newsize && newsize >= (allocated >> 1)) {
        assert(self->ob_item != NULL || newsize == 0);
        Py_SIZE(self) = newsize;
        return 0;
    }

    /* This over-allocates proportional to the list size, making room
     * for additional growth.  The over-allocation is mild, but is
     * enough to give linear-time amortized behavior over a long
     * sequence of appends() in the presence of a poorly-performing
     * system realloc().
     * The growth pattern is:  0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
     */
    # 确定新扩展之后的占坑数
    new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6);

    /* check for integer overflow */
    if (new_allocated > PY_SIZE_MAX - newsize) {
        PyErr_NoMemory();
        return -1;
    } else {
        new_allocated += newsize;
    }

    if (newsize == 0)
        new_allocated = 0;

    # 申请内存
    items = self->ob_item;
    if (new_allocated <= (PY_SIZE_MAX / sizeof(PyObject *)))
        PyMem_RESIZE(items, PyObject *, new_allocated);
    else
        items = NULL;
    if (items == NULL) {
        PyErr_NoMemory();
        return -1;
    }
    self->ob_item = items;
    Py_SIZE(self) = newsize;
    self->allocated = new_allocated;
    return 0;
}
登入後複製
在上面的程式碼中,頻繁看到兩個名詞:newsize 和 new_allocated, 這裡需要解釋下,newsize 並不是 增加/減少 的個數,而是 增加/減少 之後的成員總數目。比方說:

a = [1, 2, 3]
a.append(1)
登入後複製
上面的 append 觸發list_resize 時, newsize 是 3 1, 而不是 1;這邊比較重要,因為在 pop 這類減少列表成員時候,就是傳入縮減後的總數目。

在list 的結構定義中,關於長度的定義有兩個,分別是ob_size(實際的成員數),allocated(總成員數)

它們之間的關係就是:

 0 <= ob_size <= allocated
 len(list) == ob_size
登入後複製

所以new_allocated 就很好理解了,這就是新的總坑數。

當名詞意義理解得差不多時,我們就能順藤摸瓜知道一個列表在list_resize 之後,大小會變成怎樣?

方法其實從上面註解和程式碼都說得很明白了,這裡再簡單整理下:

#先確定一個基數:new_allocated = (newsize >> 3) (newsize < ; 9 ? 3 : 6);

判斷下new_allocated newsize 有沒有超過 PY_SIZE_MAX, 如果超過了,直接報錯;

最終確定新的總坑數是:new_allocated newsize, 如果newsizeize是0, 那麼總坑數直接為0 ;

下面示範下:

#coding: utf8
import sys

test = []
raw_size = sys.getsizeof(test)

test.append(1)
print "1 次 append 减去空列表的内存大小:%s " % (sys.getsizeof(test) - raw_size)

test.append(1)
print "2 次 append 减去空列表的内存大小:%s " % (sys.getsizeof(test) - raw_size)

test.append(1)
print "3 次 append 减去空列表的内存大小:%s " % (sys.getsizeof(test) - raw_size)

test.append(1)
print "4 次 append 减去空列表的内存大小:%s " % (sys.getsizeof(test) - raw_size)

test.append(1)
print "5 次 append 减去空列表的内存大小:%s " % (sys.getsizeof(test) - raw_size)

test.append(1)
print "6 次 append 减去空列表的内存大小:%s " % (sys.getsizeof(test) - raw_size)
登入後複製
# 输出结果
1 次 append 减去空列表的内存大小:32
2 次 append 减去空列表的内存大小:32
3 次 append 减去空列表的内存大小:32
4 次 append 减去空列表的内存大小:32
5 次 append 减去空列表的内存大小:64
6 次 append 减去空列表的内存大小:64
登入後複製
開始簡單的代入法一步步算:

#其中:

new_allocated =  (newsize >> 3) (newsize < 9 ? 3 : 6) newsize (因為下面的newsize > 0)

當原allocated >= newsize 並且newsize >=  原

當原allocated >= newsize 並且newsize >=  原allocated  / 2 時,不改變 allocated 不申請記憶體直接回傳

第 n 次 append 列表原长度 新增成员数 原 allocated newsize new_allocated
1 0 1 0 0 + 1 = 1 3 + 1 = 4
2 1 1 4 1 + 1 = 2 无需改变
3 2 1 4 2 + 1 = 3 无需改变
4 3 1 4 3 + 1 = 4 无需改变
5 4 1 4 4 + 1 = 5 3 + 5 = 8
6 5 1 8 5 + 1 = 6 无需改变

通过上面的表格,应该比较清楚看到什么时候会触发改变 allocated,并且当触发时它们是如何计算的。为什么我们需要这样关注 allocated?理由很简单,因为这个值决定了整个 list 的动态内存的占用大小;

扩容是这样,缩容也是照猫画虎。反正都是算出新的 allocated, 然后由 PyMem_RESIZE 来处理。

总结

综上所述,在一些明确列表成员或者简单处理再塞入列表的情况下,我们不应该再用下面的方式:

test = []

for i in range(4):
    test.append(i)
print test
登入後複製

而是应该用更加 pythonic 和 更加高效的列表推导式:test = [i for i in range(4)]。

以上是Python列表的長度調節方法(附程式碼)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

PHP和Python:代碼示例和比較 PHP和Python:代碼示例和比較 Apr 15, 2025 am 12:07 AM

PHP和Python各有優劣,選擇取決於項目需求和個人偏好。 1.PHP適合快速開發和維護大型Web應用。 2.Python在數據科學和機器學習領域佔據主導地位。

CentOS上如何進行PyTorch模型訓練 CentOS上如何進行PyTorch模型訓練 Apr 14, 2025 pm 03:03 PM

在CentOS系統上高效訓練PyTorch模型,需要分步驟進行,本文將提供詳細指南。一、環境準備:Python及依賴項安裝:CentOS系統通常預裝Python,但版本可能較舊。建議使用yum或dnf安裝Python3併升級pip:sudoyumupdatepython3(或sudodnfupdatepython3),pip3install--upgradepip。 CUDA與cuDNN(GPU加速):如果使用NVIDIAGPU,需安裝CUDATool

docker原理詳解 docker原理詳解 Apr 14, 2025 pm 11:57 PM

Docker利用Linux內核特性,提供高效、隔離的應用運行環境。其工作原理如下:1. 鏡像作為只讀模板,包含運行應用所需的一切;2. 聯合文件系統(UnionFS)層疊多個文件系統,只存儲差異部分,節省空間並加快速度;3. 守護進程管理鏡像和容器,客戶端用於交互;4. Namespaces和cgroups實現容器隔離和資源限制;5. 多種網絡模式支持容器互聯。理解這些核心概念,才能更好地利用Docker。

CentOS上PyTorch的GPU支持情況如何 CentOS上PyTorch的GPU支持情況如何 Apr 14, 2025 pm 06:48 PM

在CentOS系統上啟用PyTorchGPU加速,需要安裝CUDA、cuDNN以及PyTorch的GPU版本。以下步驟將引導您完成這一過程:CUDA和cuDNN安裝確定CUDA版本兼容性:使用nvidia-smi命令查看您的NVIDIA顯卡支持的CUDA版本。例如,您的MX450顯卡可能支持CUDA11.1或更高版本。下載並安裝CUDAToolkit:訪問NVIDIACUDAToolkit官網,根據您顯卡支持的最高CUDA版本下載並安裝相應的版本。安裝cuDNN庫:前

Python vs. JavaScript:社區,圖書館和資源 Python vs. JavaScript:社區,圖書館和資源 Apr 15, 2025 am 12:16 AM

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

minio安裝centos兼容性 minio安裝centos兼容性 Apr 14, 2025 pm 05:45 PM

MinIO對象存儲:CentOS系統下的高性能部署MinIO是一款基於Go語言開發的高性能、分佈式對象存儲系統,與AmazonS3兼容。它支持多種客戶端語言,包括Java、Python、JavaScript和Go。本文將簡要介紹MinIO在CentOS系統上的安裝和兼容性。 CentOS版本兼容性MinIO已在多個CentOS版本上得到驗證,包括但不限於:CentOS7.9:提供完整的安裝指南,涵蓋集群配置、環境準備、配置文件設置、磁盤分區以及MinI

CentOS下PyTorch版本怎麼選 CentOS下PyTorch版本怎麼選 Apr 14, 2025 pm 02:51 PM

在CentOS下選擇PyTorch版本時,需要考慮以下幾個關鍵因素:1.CUDA版本兼容性GPU支持:如果你有NVIDIAGPU並且希望利用GPU加速,需要選擇支持相應CUDA版本的PyTorch。可以通過運行nvidia-smi命令查看你的顯卡支持的CUDA版本。 CPU版本:如果沒有GPU或不想使用GPU,可以選擇CPU版本的PyTorch。 2.Python版本PyTorch

centos如何安裝nginx centos如何安裝nginx Apr 14, 2025 pm 08:06 PM

CentOS 安裝 Nginx 需要遵循以下步驟:安裝依賴包,如開發工具、pcre-devel 和 openssl-devel。下載 Nginx 源碼包,解壓後編譯安裝,並指定安裝路徑為 /usr/local/nginx。創建 Nginx 用戶和用戶組,並設置權限。修改配置文件 nginx.conf,配置監聽端口和域名/IP 地址。啟動 Nginx 服務。需要注意常見的錯誤,如依賴問題、端口衝突和配置文件錯誤。性能優化需要根據具體情況調整,如開啟緩存和調整 worker 進程數量。

See all articles