Python学习之列表的内部实现详解

本文和大家分享的主要是列表在 CPython中的实现,一起来看看吧,希望对大家学习python有所帮助。

Python 中的列表非常强大,看看它的内部实现机制是怎么样的,一定非常有趣。

下面是一段 Python 脚本,在列表中添加几个整数,然后打印列表。

>>> l = []

>>> l.append(1)

>>> l.append(2)

>>> l.append(3)

>>> l

[1, 2, 3]

>>> for e in l:

... print e

...

可以发现,列表是一个迭代器。

列表对象的 C 语言结构体

Cpython 中的列表实现类似于下面的 C 结构体。ob_item 是指向列表对象的指针数组。allocated 是申请内存的槽的个数。

typedef struct {

PyObject_VAR_HEAD

PyObject **ob_item;

Py_ssize_t allocated;

} PyListObject;

列表初始化

看看初始化一个空列表的时候发生了什么,例如:l = []。

arguments: size of the list = 0

returns: list object = []

PyListNew:

nbytes = size * size of global Python object = 0

allocate new list object

allocate list of pointers (ob_item) of size nbytes = 0

clear ob_item

set list’s allocated var to 0 = 0 slots

return list object

要分清列表大小和分配的槽大小,这很重要。列表的大小和 len(l) 的大小相同。分配槽的大小是指已经在内存中分配了的槽空间数。通常分配的槽的大小要大于列表大小,这是为了避免每次列表添加元素的时候都调用分配内存的函数。下面会具体介绍。

Append 操作

向列表添加一个整数:l.append(1) 时发生了什么?调用了底层的 C 函数 app1()。

加一个整数:l.append(1) 时发生了什么?调用了底层的 C 函数 app1()。

arguments: list object, new element

returns: 0 if OK, -1 if not

app1:

n = size of list

call list_resize() to resize the list to size n+1 = 0 + 1 = 1

list[n] = list[0] = new element

return 0

下面是 list_resize() 函数。它会多申请一些内存,避免频繁调用 list_resize() 函数。列表的增长模式为:0,4,8,16,25,35,46,58,72,88……

arguments: list object, new size

returns: 0 if OK, -1 if not

list_resize:

new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6) = 3

new_allocated += newsize = 3 + 1 = 4

resize ob_item (list of pointers) to size new_allocated

return 0

现在分配了 4 个用来装列表元素的槽空间,并且第一个空间中为整数 1。如下图显示 l[0] 指向我们新添加的整数对象。虚线的方框表示已经分配但没有使用的槽空间。

列表追加元素操作的平均复杂度为 O(1)。

继续添加新的元素:l.append(2)。调用 list_resize 函数,参数为 n+1 = 2, 但是因为已经申请了 4 个槽空间,所以不需要再申请内存空间。再添加两个整数的情况也是一样的:l.append(3),l.append(4)。下图显示了我们现在的情况。

Insert 操作

在列表偏移量 1 的位置插入新元素,整数 5:l.insert(1,5),内部调用ins1() 函数。

arguments: list object, where, new element

returns: 0 if OK, -1 if not

ins1:

resize list to size n+1 = 5 -> 4 more slots will be allocated

starting at the last element up to the offset where, right shift each element

set new element at offset where

return 0

虚线的方框依旧表示已经分配但没有使用的槽空间。现在分配了 8 个槽空间,但是列表的大小却只是 5。

列表插入操作的平均复杂度为 O(n)。

Pop 操作

取出列表最后一个元素 即l.pop(),调用了 listpop() 函数。在 listpop() 函数中会调用 list_resize 函数,如果取出元素后列表的大小小于分配的槽空间数的一半,将会缩减列表的大小。

arguments: list object

returns: element popped

listpop:

if list empty:

return null

resize list with size 5 - 1 = 4. 4 is not less than 8/2 so no shrinkage

set list object size to 4

return last element

列表 pop 操作的平均复杂度为 O(1)。

可以看到 pop 操作后槽空间 4 依然指向原先的整数对象,但是最为关键的是现在列表的大小已经变为 4。

继续 pop 一个元素。在 list_resize() 函数中,size – 1 = 4 – 1 = 3 已经小于所分配的槽空间大小的一半,所以缩减分配的槽空间为 6,同时现在列表的大小为 3。

可以看到槽空间 3 和 4 依然指向原先的整数,但是现在列表的大小已经变为 3。

Remove 操作

Python 的列表对象有个方法,删除指定的元素: l.remove(5)。底层调用 listremove() 函数。

arguments: list object, element to remove

returns none if OK, null if not

listremove:

loop through each list element:

if correct element:

slice list between element’s slot and element’s slot + 1

return none

return null

为了做列表的切片并且删除元素,调用了 list_ass_slice() 函数,它的实现方法比较有趣。我们在删除列表位置 1 的元素 5 的时候,低位的偏移量为 1 同时高位的偏移量为 2.

arguments: list object, low offset, high offset

returns: 0 if OK

list_ass_slice:

copy integer 5 to recycle list to dereference it

shift elements from slot 2 to slot 1

resize list to 5 slots

return 0

列表 remove 操作的复杂度为 O(n)。

来源:火龙果软件工程

时间: 2024-08-06 16:05:32

Python学习之列表的内部实现详解的相关文章

Python学习之os模块的使用详解

本文和大家分享的主要是python 中os 模块相关使用方法详解,一起来看看吧,希望对大家 学习python 有所帮助. os模块调用操作系统接口的模块 相关方法或属性: getcwd() ---  获取当前的操作目录,等同于 linux 中的 pwd 命令. 调用:os.getcwd() chdir() ---  改变 python 脚本的工作目录. 调用:os.chdir(path) (path 以字符串形式传入 ) 例如: >>> os.getcwd() 'C:\\Users\\B

Python学习之Argparse 解析脚本参数详解

Argparse 是 Python 标准库中推荐的命令行解析模块,经常需要解析脚本参数的话这是个方便的工具模块,摆脱万年手动 system.argv .本文和大家分享的就是python中Argparse解析脚本参数相关内容,一起来看看吧,希望对大家学习python有所帮助. 引入 import argparse parser = argparse.ArgumentParser(description='描述说明,可用于 Help 输出说明', add_help=True) parser.pars

Python学习之通用序列类型数据详解

本文和大家分享的主要是python通用序列类型数据的相关操作,一起来看看吧,希望对大家学习python有所帮助. Python的序列,Python有6种内建的序列,包括:列表.元组.字符串.Unicode字符串.buffer对象和xrange对象.其中最为常用的是Python的列表和元组. Python的序列 Python有6种内建的序列,包括:列表.元组.字符串.Unicode字符串.buffer对象和xrange对象.其中最为常用的是Python的列表和元组. Python序列的应用 Pyt

Python学习之异常重试解决方法详解

本文和大家分享的是在使用python 进行数据抓取中,异常重试相关解决办法,一起来看看吧,希望对大家学习python有所帮助. 在做数据抓取的时候,经常遇到由于网络问题导致的程序保存,先前只是记录了错误内容,并对错误内容进行后期处理. 原先的流程: defcrawl_page(url): pass deflog_error(url): pass url = "" try: crawl_page(url) except: log_error(url) 改进后的流程: attempts =

Python学习笔记5:函数参数详解

一.函数的定义格式: def 函数名(参数列表): 函数体 def fun1(a, b, c): return a + b + c 二.位置传递:位置对应 print(fun1(3 ,2 ,1)) 输出: 6 三.关键字传递:位置参数要出现在关键字参数之前 print(fun1(3 ,c = 1, b = 2)) 输出: 6 四.参数默认值:可以给参数赋予默认值(default) def fun2(a, b, c = 100): return a + b + c print(fun2(1, 10

PYTHON学习0045:函数---random模块详解--2019-8-11

1.取随机数:random.randint(1,100)从1到100取一个随机整数,包含100.2.random.randrange(1,100)和上一个类似,只不过这个不包含100.3.random.random():生成随机浮点数4.random.choice("sdfsf2323d23")从字符串里返回随机元素:5.random.sample("sdfsf2323d23",3)从给定字符串里随机返回3个元素,以列表形式展示:6.生成随机字符串:首先导入一个模块

python学习第四周之内置方法详解

1.python的内置方法有很多,用的时候可以自行百度,我只写几个我感兴趣的(任性.) 2.(1)bin(),将十进制转变为二进制 >>> bin(2) '0b10' (2)chr(),查看数字所对应的字母, >>> chr(98) 'b' (3)ord(),查看字母对应的数字 >>> ord('a') 97 (4)hex(),转换成十六进制 >>> hex(255) '0xff' (5)oct(),转成成八进制 >>&g

PYTHON学习0043:函数---time模块详解--2019-8-10

要使用time模块需先导入:import time1.time.time():打印当前时间戳表示从1970年至今经过的秒速.2.time.localtime(secs):将一个时间戳转换为当前时区的struct_time,secs为提供时,则以当前时间为准.可在括号里加数字,则返回数字对应时间: 可以看到包含了很多变量,可以采用拼接形式表示时间了.比如:先把time.time()赋值给变量a3.time.gmtime():和localtime()方法类似,gmtime()方法是返回UTC时间,即

PYTHON学习0044:函数---datetime模块详解--2019-8-11

1.datetime.datetime.now():返回当前的datetime的日期类型:2.datetime.date.fromtimestamp():吧一个时间戳转为datetime日期类型:3.时间运算:datetime.datetime.now()+datetime.timedelta(4):datetime.timedelta()括号里默认单位为"天"4.时间替换:用replace函数: 原文地址:https://blog.51cto.com/13543767/2428482