python是否有一个列表构造函数,如OCaml(cons
)(或lisp)中的::
,它带有head
元素和tail
列表,并返回一个新的列出head::tail
?
我搜索了python list构造函数,最后找到了关于__init__
的其他内容。见例如Creating a list in Python- something sneaky going on?
为了澄清,我正在寻找的是Python found in this question中以下列表分解的反转:
head, *tail = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
这给出了:
>>>head
1
>>> tail
[1, 2, 3, 5, 8, 13, 21, 34, 55]
我正在寻找一个列表构造函数,例如cons
或::
,以便
head :: tail => original list
答案 0 :(得分:11)
要回答你的问题,没有直接相当于你通常会在所谓的“功能”语言(lisp,OCaml,Haskell等)中找到的缺点。
那是因为有两种竞争模型来表示编程语言中的元素列表。
您似乎熟悉的那个称为链表。
链表由缺陷单元组成,每个单元包含两个引用:
因为列表很少是无限的,所以最后一个const单元通常指向一个特殊值,即空列表,有时称为 nil 。
如果您想将列表保存在变量中以供将来参考,您可以保留对第一个cons-cell的引用。
Here's a visual representation from Wikipedia
在这个模型中,每个列表都必须通过创建一个新的cons-cell向前面添加元素来构造,指向新元素作为其头部,并指向先前构建的子列表作为其尾部。这就是为什么cons运算符有时被称为列表构造函数。
这是Python等命令式语言通常首选的模型。在此模型中,列表只是对内存范围的引用。
我们假设您创建一个列表:
l = [1, 2, 3]
无论何时创建一个列表,Python都会为其分配一小段内存来存储元素,以及一些额外的空间,以防您以后想要添加元素。要存储它,您只需存储对第一个元素的引用,以及存储区域的大小,就像这样:
l <-- your variable
| ___ ___ ___ ___ ___ ___ ___ ___ ___
|-> | | | | | | | | | |
| 1 | 2 | 3 | | | | | | |
|___|___|___|___|___|___|___|___|___|
如果您决定在列表末尾添加元素,则可以使用append
l.append(4)
导致以下列表:
___ ___ ___ ___ ___ ___ ___ ___ ___
| | | | | | | | | |
| 1 | 2 | 3 | 4 | | | | | |
|___|___|___|___|___|___|___|___|___|
现在,假设您忘记了初始0,现在您希望将其添加到前面。你可以很好地使用insert方法(插入位置为0):
l.insert(0, 0)
但是列表的开头没有空格! Python别无选择,只能采用每个元素,并在右侧的位置一次复制一个元素:
___ ___ ___ ___ ___ ___ ___ ___ ___
| | | | | | | | | |
| 1 | 2 | 3 | 4 | | | | | |
|___|___|___|___|___|___|___|___|___|
| | |__ |___
| |___ | | First, Python has to copy the four elements
|___ | | | one space to the right
___ _\/ _\/ \/_ _\/ ___ ___ ___ ___
| | | | | | | | | |
| | 1 | 2 | 3 | 4 | | | | |
|___|___|___|___|___|___|___|___|___|
Only then can it insert the 0 at the beginning
___ ___ ___ ___ ___ ___ ___ ___ ___
| | | | | | | | | |
| 0 | 1 | 2 | 3 | | | | | |
|___|___|___|___|___|___|___|___|___|
对于如此小的阵列来说可能看起来不多,但想象你的阵列要大得多,而且你多次重复这个操作:你会花很多时间来建立你的列表!
这就是为什么你不会在像Python这样的列表中使用数组的语言中找到列表构造函数。
您现在可能想知道为什么不同的语言会更喜欢不同的列表模型,以及两种模型中的一种是否优越。
这是因为这两种数据结构在不同的上下文中具有不同的性能。两个例子:
假设您想获得列表的第五个元素。
在链接列表中,您需要获取:
因此,你必须通过5个参考文献!
使用数组,这更简单:你知道第一个元素的引用。你只需要访问右边的参考4个点,因为元素都在一个连续的内存范围内!
如果您需要多次访问非常大的列表的随机元素,那么数组会更好。
想象一下,你现在想在中间插入一个元素。
使用链接列表:
使用数组,就像在中间添加元素一样,您需要将插入点右侧的每个元素复制到右侧一个空格!
在这种情况下,这是明显优越的链表。
答案 1 :(得分:6)
Python列表是可变的,与OCaml相比,它们具有不可变的值语义,但如果a
是一个项目而b
是一个列表,则可以获得所需的结果[a] + b
将返回包含a
的新列表,后跟b
中的项目,而不会修改a
或b
。但是,建立列表的一种更常见的模式是append
或extend
就地。
答案 2 :(得分:6)
有些人建议你这样做:
a = 1
b = [2, 3, 4, 5]
c = [a] + b
如果b
的类型为list
,则该工作正常。但是,通常您会发现自己使用的是列表或元组或(在此处插入您喜欢的可迭代对象)的对象。在这种情况下,你可能更值得做:
a = 1
b = (2, 3, 4, 5)
c = [a]
c.extend(b)
这使整个事物与b
的类型无关(这使得你的代码更加“迷人”,这可能有点好)。 。
当然,还有其他选择。 。 。例如,您可以选择覆盖itertools
:
import itertools
a = 1
b = [2, 3, 4, 5]
lazy_c = itertools.chain([a], b)
我们再次受益于不关心什么类型的可迭代b
,和我们选择了懒惰迭代的附带好处 - 我们不会制作新列表或元组。我们只是创建一个对象,当你迭代它时会产生同样的东西。这可以节省内存(有时可以节省CPU的周期),但如果b
也是一种类似于生成器的对象,同时在其他地方进行迭代,也可能产生意想不到的后果(不会经常偶然发生。)
答案 3 :(得分:1)
您可以使用列表连接
head = 1
tail = [2,3,4]
lst = [head] + tail # [1,2,3,4]