在Python中,什么时候应该使用列表,什么时候使用元组?
有时你没有选择,例如如果你有
1
| "hello %s you are %s years old" % x |
那么x必须是一个元组。
但是如果我是设计API并选择数据类型的人,那么指导原则是什么?
- 不幸的是,这个问题被错误地标记为副本。在没有充分理由的情况下,经常会看到列表被用于元组之上的一个重要原因是可读性。在Python中,圆括号用于许多事情,但方括号仅用于与列表相关的事情。例如,当传递一个列表作为参数时,发现它比传递一个元组要容易得多:my_func([e1, e2])对my_func((e1, e2))。
元组本质上是固定大小的,而列表是动态的。换句话说,tuple是不变的,而list是可变的。
不能向元组添加元素。元组没有append或extend方法。
不能从元组中移除元素。元组没有remove或pop方法。
您可以在元组中找到元素,因为这不会改变元组。
还可以使用in运算符检查tuple中是否存在元素。
元组比列表快。如果您要定义一组常量值,而您所要做的就是遍历它,那么使用一个元组而不是一个列表。
如果您"写保护"不需要更改的数据,它会使您的代码更安全。使用元组而不是列表就像拥有一个隐含的断言语句,这个数据是常量,需要特殊的思想(和特定的函数)来覆盖它。
有些元组可以用作字典键(特别是包含不可变值的元组,如字符串、数字和其他元组)。列表不能用作字典键,因为列表不是不可变的。
来源:进入python 3
- "写保护"类比只到目前为止:一个元组的成员不能改变,但是一个元组的可变元素可以改变:l=list();t=(l,l);l.append(1)
- 什么使你认为元组比列表更快?
- 元组并不比列表快。在[220]中:t_range=tuple(range(100000))在[221]中:99999在t_range out[221]中:true在[222]中:l_range=range(100000)在[223]中:99999在l_range out[223]中:true在[224]中:%:timeit…..:99999在l_range…..:100个循环中,在[225]中每个循环中最好3:2.97 ms:%:timeit…..:99999在t_range…..:100个循环中,在3:3.01 ms/循环中最好环
- @Kracekumar:这是因为在您的示例中,您首先创建了一个列表,然后将其设置为元组。显然是慢了。看这个:$ python -m timeit"for x in xrange(10000):"" ''.join( ['a','b','c','d','e','f','g'] )" 1000 loops, best of 3: 1.91 msec per loop $ python -m timeit"for x in xrange(10000):"" ''.join( ('a','b','c','d','e','f','g') )" 1000 loops, best of 3: 1.17 msec per loop。
- @克蒙索那有什么问题?不是真的吗?
- @嗯,我是鲁特。我笑着想到了"自然"的不同内涵。没什么大不了的…
- 我可以称之为定义"常量列表"的方法吗?我在这里得到了答案:jtauber.com/blog/2006/04/15/…
- 由于编辑被拒绝,并且无法将其作为答案添加,下面是添加更多信息的注释:复制或克隆元组不如列表或字典简单。例如,可以使用list2 = list1[:]克隆列表。同样,也可以使用dict2 = dict1.copy()克隆字典,但元组本身没有这种方法。如果需要,可以使用copy模块中的deepcopy方法。
- @除了你可以用tuple2 = tuple1[:]克隆一个元组外,vishnunarang还可以吗?尽管这样做没有意义,因为结果对象与第一个对象都是a)不可变的和b)相同的。唯一有意义的时间是当tuple有可变的子级时,但是不管它是dict、list还是tuple,都必须进行deepcopy。
- @jldupont在我看来,对列表进行迭代比对元组进行迭代更快:my tuple=(range(100))my tuple=[range(100)]timeit"for item in my tuple:none"结果为2.59 us,而timeit"for item in my u list:none"结果为118 ns。我是否遗漏了一些测试内容,或者你的陈述不完全准确?
- @jldupont ok i to update我在上面的注释中发现了错误;((range(100))仍然是range对象,而不是tuple。用tuple(range(100))vs list(range(100))结果重复上述操作是相当相同的结果;即使我用"item"上的某些操作替换"none"。因此,在这一点上,我确信如果您"迭代一组常量值",那么每个变量之间没有速度差。如果您要循环构建一个容器,请在元组上清楚列出速度度量的规则,这将是使可变容器位于第一位(速度与安全性)的原因。
对于异类集合,有一种很强的元组文化,类似于在C中使用struct,列表用于同种集合,类似于数组。但我从来没有把这和其他答案中提到的易变性问题做过比较。易变性有它的牙齿(实际上你不能改变一个元组),而同质性是不强制的,因此似乎是一个不那么有趣的区别。
- @所以,假设你想实现一个点类。你会用一个元组或一个列表来保存x,y,z坐标吗?您希望更改值(使用list),但同时顺序和位置是有意义的和一致的(使用tuple?).
- 与struct的错误特性相比,您缺少了它的语义。当位置具有相关性时,元组很有用——最好的例子来自数学中的坐标,甚至使用相同的语法:(x, y, z)。
- 我以前从来没有听说过这个,也没有把它看作代码中的模式(我读过很多Python代码)。
- 撇开易变性不谈,我们为什么不总是用听写代替元组呢?这将给字段命名带来优势,这将使访问单个元素更具可读性…
- 阿伦:可变点实际上是个很坏的主意。坚持不变的观点,创造新的观点,它们足够便宜。如果要处理数百万个点,请使用flyweight模式并保存最近使用的点的缓存。
- 所以,考虑数组的只读锁状态。如果您真的需要编辑一个元组,您不需要,您可以从中创建一个数组。作为@izkata点,coors和其他一些数据可以是静态的。例如,一个以10个元素开始的流程不需要编辑。只需开始保存一个元组,然后按照需要的方式创建数组。只需指向元组或数组,就可以访问这两个数据。这听起来可能令人困惑,但事实并非如此。
- @greenasjade:这就是collections.namedtuple的用途(与dict不同,唯一的成本是定义namedtuple;这些实例在内存中的大小与一个同样长的tuple完全相同,但可以由.name和索引访问。作为一个异构的轻量级"对象",tuple的经典用例都可以用collections.namedtuple替换,但在许多情况下是不必要的(zip上的循环可以直接解包到命名变量,因此不能通过名称访问元组,但一开始就不能直接使用tuple。
- @约翰科万——这并不是说他们是个坏主意——而是说他们没有道理。点在逻辑上是不可变的,就像整数一样——你不能通过给5加1来给它赋一个新的值——你有一个新的整数6。一个点也是如此,因为它只是两个整数。如果我改变了它的值,我就不会改变积分的标识——我有一个不同的点,具有不同的标识。可变对象在不更改其标识的情况下更改其值,但不可变对象的值是其标识。
- 它们对于向量和其他可以被归类为数据的东西很有用,但是这样的结构往往会吸引行为。这时,我们不妨定义一个适当的数据类型。
- 一个非常愚蠢的答案。如果您不需要更改它或者不需要任何其他特殊的列表方法,请使用元组。它们占用的内存更少,效率更高。
- @巴楚微观优化是选择数据结构的错误原因。你应该做得更好:)
- @只有在没有任何更好的理由的情况下,Nedbatchelder才是,并且感知到的"约定"并不算是这样。列表更常用于同质对象的原因不是约定,而是可变的,这意味着它们可以很容易地在循环中创建或用于排列事物,而元组通常用于将属于一起的事物分组。这并不意味着,将不同类型的对象放入列表或创建同质对象的元组有任何问题,当您在创建元组的点上有可用的对象时。使用你需要的套房
我相信(而且我很难精通python),主要的区别是元组是不可变的(在赋值后不能在适当的位置进行更改),列表是可变的(可以追加、更改、减去等)。
所以,我倾向于做我的tuples,作业后不应该改变的东西,列出可以改变的东西。
- 好吧,即使你不打算变异,你为什么不使用一个列表呢?
- @这篇文章中的其他几个答案解释了为什么人们可能会选择使用元组而不是列表。
它必须是可变的吗?使用列表。一定不能是可变的吗?使用元组。
否则,这是一个选择的问题。
对于异类对象的集合(如分为名称、街道、城市、州和zip的地址),我更喜欢使用元组。它们总是可以很容易地升级为命名的元组。
同样,如果要迭代集合,我更喜欢列表。如果它只是一个容器,可以将多个对象作为一个容器来保存,那么我更喜欢使用元组。
- 这是唯一的好答案。它陈述了真相,而不仅仅是每个人都知道的可变/不变的东西。
- 为什么要用一个元组,甚至一个命名的元组,而不是一个dict?
- 一些偏好的理由会很好。为什么你更喜欢用tuples来表示异构对象?如果您想迭代,为什么您更喜欢一个列表?
- @格林纳斯杰德,我知道这是旧的,但原因是,图普尔仍然是一个无可辩驳的人,而秩序问题(口述,秩序没有保证)。
- "为什么你更喜欢用tuples来表示异构对象呢?如果您想迭代,为什么您更喜欢一个列表?"因为如果我只关心迭代,我通常不关心列表的长度。添加一个项目;删除一个;代码不重要。迭代过程中调用的代码与项目数无关,因此没有理由强制按对象类型对其进行修复。这不是一个强有力的理由,而是一种很好的气味。您可能在排序的地方有列表,但是如果您有每个元素不同的列表,那么避免定义真正的类是一种懒惰的方法。
- 列表具有类方法,如sort(),这些方法仅对同类类型有用。如果在列表中插入数字和字符串,则会中断sort()。显然,除此之外,还有其可变性。
您需要决定的第一件事是数据结构是否需要是可变的。如前所述,列表是可变的,元组不是。这也意味着元组可以用于字典键,而列表则不能。
在我的经验中,元组通常用于顺序和位置有意义且一致的地方。例如,在为ChooseYourAdventure游戏创建数据结构时,我选择使用元组而不是列表,因为元组中的位置很有意义。下面是该数据结构的一个示例:
1 2 3 4
| pages = {'foyer': {'text' :"some text",
'choices' : [('open the door', 'rainbow'),
('go left into the kitchen', 'bottomless pit'),
('stay put','foyer2')]},} |
元组中的第一个位置是用户玩游戏时显示给他们的选择,第二个位置是选择要转到的页面的键,这对于所有页面都是一致的。
元组的内存效率也比列表高,不过我不确定什么时候这种优势变得明显。
还可以查看think python中关于列表和元组的章节。
- 是什么让你认为元组比列表更节省内存?
- +1-虽然易变性/不变性是一个重要考虑因素,但位置相关性是主要原因-数学甚至对坐标系使用相同的语法:(x, y, z)。
- 你为什么不用dict代替有命名条目的元组呢?我不清楚为什么这个职位对你来说很重要:一个是行动,另一个是结果…我(读者)怎么能事先知道这是这些事情的顺序?在您的情况下,这是有争议的,但我看到更广泛的元组使用了很多,它们的访问变成了next_thing = result_tuple[5]。5?真的?为什么是5?说"以东"不是更好吗?
But if I am the one who designs the API and gets to choose the data types, then what are the guidelines?
对于输入参数,最好接受执行所需操作的最通用的接口。它很少只是一个元组或列表——更常见的是它是序列、可切片甚至是不可分割的。python的duck类型通常是免费的,除非您显式地检查输入类型。除非绝对不可避免,否则不要这样做。
对于您生成的数据(输出参数),只返回最方便的数据,例如返回您保留的任何数据类型或帮助函数返回的任何数据类型。
要记住的一件事是避免返回一个列表(或任何其他可变的)这是您的状态的一部分,例如。
1 2 3 4 5 6 7 8 9
| class ThingsKeeper
def __init__(self):
self.__things = []
def things(self):
return self.__things #outside objects can now modify your state
def safer(self):
return self.__things[:] #it's copy-on-write, shouldn't hurt performance |
- 所以,如果你有一个函数,它有一个可怕的默认方法参数(比如,一个列表),并且你返回了这个参数,那么外部的代码现在可以和列表混淆了吗?
- @罗伯特格兰特说得对!另一个使用"none作为函数序言中if的默认值"模式的原因。它可能更冗长,但可以避免像这样令人讨厌的意外。
- 是的,这是完全有道理的(我想我认为这消除了默认参数按照它们的方式工作的必要性,但随后我会被吼到:)。谢谢。
与元组相比,列表的一个次要但显著的优势是列表更易于移植。标准工具不太可能支持元组。例如,JSON没有元组类型。Yaml可以,但是它的语法与它的列表语法相比很难看,这非常好。
在这些情况下,您可能希望在内部使用元组,然后将其转换为列表作为导出过程的一部分。或者,您可能希望在任何地方使用列表来保持一致性。
- 标准JSON编码器将元组转换为数组。
- 谢谢你指出这一点。不过,我的评论是泛泛的。就个人而言,我和JSON一样经常导出到yaml,在这种情况下,必须手动将元组转换为列表。