为什么在Python中从列表末尾索引时,索引从-1开始(而不是从0开始)?

92
list = ["a", "b", "c", "d"]
print(list[3]) # Number 3 is "d"

print(list[-4]) # Number -4 is "a"

73
不要将“list”用作变量名,它是一个标准类的名称。 - Barmar
10
它不是从1开始,而是从-1开始。?!? - Thomas Weller
7
这个页面应该在某处提到模算术... - Nacht
6
应该写成“而不是-0”吗?由于从起始位置进行索引时从0开始,因此很明显它不能从末尾处为0,所以我认为作者的意思是-0。 - Raimund Krämer
2
你有尝试访问索引0吗? - jpmc26
有一个非常简单的方法可以理解为什么“-1”是字符串的最后一个字符的引用。字符串的结尾指向字符串的右侧(超过了最后一个字符),因此“-1”向左移动一个字符,现在指向最后一个字符的开头。这与“0”引用字符串的开头完全对称,“0”指向第一个字符的左侧,因此使用索引0获取下一个字符,即字符串的第一个字符。 - david marcus
7个回答

185

换句话来说,因为-0等于0,如果从0开始向后索引,会让解释器感到模棱两可。


如果你对-感到困惑,想要另一种更易理解的方式向后索引,可以尝试使用~,它是正向索引的镜像:

arr = ["a", "b", "c", "d"]
print(arr[~0])   # d
print(arr[~1])   # c

~ 的典型用法是像“交换镜像节点”或“在排序列表中找到中位数”:

"""swap mirror node"""
def reverse(arr: List[int]) -> None:
    for i in range(len(arr) // 2):
        arr[i], arr[~i] = arr[~i], arr[i]

"""find median in a sort list"""
def median(arr: List[float]) -> float:
    mid = len(arr) // 2
    return (arr[mid] + arr[~mid]) / 2

"""deal with mirror pairs"""
# verify the number is strobogrammatic, strobogrammatic number looks the same when rotated 180 degrees
def is_strobogrammatic(num: str) -> bool:
    return all(num[i] + num[~i] in '696 00 11 88' for i in range(len(num) // 2 + 1))

~实际上是一种反码和补码的数学技巧,在某些情况下更容易理解。


关于是否应该使用像~这样的Python技巧的讨论:

我认为,如果是自己维护的代码,可以使用任何技巧来避免潜在的错误或更轻松地实现目标,因为可能具有更高的可读性和可用性。但在团队合作中,避免使用“过于聪明”的代码,可能会给同事们带来麻烦。

例如,这里是Stefan Pochmann的一个简洁代码,用于解决这个问题。我从他的代码中学到了很多东西。但有些只是为了好玩,太过hackish难以使用。

# a strobogrammatic number is a number that looks the same when rotated 180 degrees (looked at upside down)
# find all strobogrammatic numbers that are of length = n
def findStrobogrammatic(self, n):
    nums = n % 2 * list('018') or ['']
    while n > 1:
        n -= 2
        # n < 2 is so genius here
        nums = [a + num + b for a, b in '00 11 88 69 96'.split()[n < 2:] for num in nums]
    return nums

如果你感兴趣,我已经总结了类似这样的Python技巧


评论不适合进行长时间的讨论;此对话已被移至聊天室 - Samuel Liew
1
你能提供一下 Stefan 代码解决的问题的描述吗?我不想在 LeetCode 上注册才能访问那些信息。答案包含所有相关细节也是很好的。 - Konrad
2
问题描述:一个镜像数是指在旋转180度后看起来与原来相同的数字(倒过来看)。找出所有长度为n的镜像数。Stefan代码的关键点:第一行,如果nums是奇数,则中间将为'018',我们将在while循环中添加左侧和右侧的对,但我们应该考虑数字不能以'0'开头,因此在此处使用n<2。只需5行即可解决复杂问题。@Konrad - recnac
1
使用者不应该使用这种方法。你仍然需要理解从末尾进行索引的负数索引的基本用法。这只是更进一步的混淆。 - Paddy3118

175
list[-1]

是“short hand”的简写形式:

list[len(list)-1]

len(list) 部分是隐含的。这就是为什么 -1 是最后一个元素。对于任何负数索引都适用——从 len(list) 减去的部分总是隐含的。


13
我认为这个答案比被采纳的那个更好。 - user10658544
10
请注意,list[-n]和list[len(list)-n]仅在n的取值范围为1到len(list)之间时是等效的。当进行切片而不是索引时,这一点变得尤为重要。 - plugwash

28

这是我使用的助记方法。它只是一种解决方法,但它有效。


不要把它们看作索引,而是将它们视为一个循环列表上的偏移量。

我们以列表 x = [a,b,c,d,e,f,g,h] 为例,考虑 x[2] 和 x[-2]:

enter image description here

你从偏移量零开始。如果你向前移动两步,你会从 a 移动到 b (0 到 1),然后从 b 移动到 c (1 到 2)。

如果你向后移动两步,你会从 a 移动到 h (0 到 -1),然后从 h 移动到 g (-1 到 -2)。


考虑a[2]和a[-2],a是列表还是列表的一个元素? - detly
1
"a" 是一个假设列表,其中包含值 a-h!我会澄清的! - T. Sar
请注意与模算术的相似之处。 - Code-Apprentice

24
因为在Python中-0等于0
0可以获取列表的第一个元素,
-1可以获取列表的最后一个元素。
list = ["a", "b", "c", "d"]
print(list[0]) # "a"
print(list[-1]) # d
你也可以将其视为“list [len(list)-x]”的速记,其中x是从后面计算元素位置。仅当0 < -(- x)< len(list)时,此方法才有效。
print(list[-1]) # d
print(list[len(list) - 1]) # d
print(list[-5]) # list index out of range
print(list[len(list) - 5]) # a

11
我认为在大多数情况下,“-0”就等同于“0”。 - Koray Tugay
10
除了浮点数外,其他情况都适用。 - Barmar
我猜现在不再是 0 了。@Barmar - Koray Tugay
7
@KorayTugay 是的。二进制表示法是全0偶数。浮点数也只是多了一个0,其中符号位为1。 - curiousdannii
3
有些架构中,即使对于整数值(符号和大小),0和-0也是不同的值。然而我不认为目前市面上有任何处理器使用这种表示方法。 - Martin Bonner supports Monica

14

这个习语可以使用模算法进行证明。我们可以将索引看作是指向列表中一个单元格的索引,该列表是通过向前步进 i 个元素而获得的-1 指向列表的最后一个元素,是这种自然推广的一种形式,因为如果我们从列表开头向后走一步,就会到达列表的最后一个元素。

对于任何列表xs和索引i,无论是正数还是负数,表达式

xs[i]

将与下面的表达式具有相同的值或产生 IndexError:

xs[i % len(xs)]

最后一个元素的索引是 -1 + len(xs),它与 len(xs) 取模等于 -1。例如,在长度为 12 的数组中,最后一个元素的索引为 11,11 对于 12 取模等于 -1。

在 Python 中,数组更常用作 线性 数据结构而不是 环形 数据结构,所以索引大于 -1 + len(xs) 或小于 -len(xs) 都会越界,因为很少需要,并且如果数组的大小发生变化,效果会非常反直觉。


8

另一个解释:

你的手指指向第一个元素。索引决定了你将手指向右移动多少个位置。如果索引是负数,则将手指向左移动。

当然,你不能从第一个元素向左移动,所以向左的第一步会绕到最后一个元素。


6
您可以这样直观地理解它
steps= ["a", "b", "c", "d"]

假设你从 a 开始走到 d,a 是你的起点(或你的家),所以标记为 0(因为你还没有移动)。
第一步到 b,第二步到 c,最后到达 d。
那么现在你从 d 回到 a(或从办公室回家)。你的家是 0,因为你的家人住在那里,所以你的办公室不能是 0。它是你的最后一个停留地点。
所以当你回家时,d 是你离开家的最后一个停靠点,c 是前一个停靠点...

“d是最后的第一个起点,c是最后的第二个。” 这句话有点难理解,您能详细说明一下吗? - Raimund Krämer
这里的 last 指的是结束或最终,而不是最近的。@RaimundKrämer - AbstProcDo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接