57

免责声明:我不是在问and的上界stop参数是否是独占的或如何使用这些函数。slice()range()

rangeandslice函数的调用以及切片表示法[start:stop]都引用整数集。

range([start], stop[, step])
slice([start], stop[, step])

在所有这些中,stop整数被排除在外。

我想知道为什么语言是这样设计的。

是等于0还是省略stop时等于表示的整数集合中的元素个数?start

是否有:

for i in range(start, stop):

看起来像下面的 C 代码?

for (i = start ; i < stop; i++) {
4

6 回答 6

51

文档暗示这有一些有用的属性:

word[:2]    # The first two characters
word[2:]    # Everything except the first two characters

这是一个有用的切片操作不变量:s[:i] + s[i:]equals s

对于非负索引,如果两个索引都在范围内,则切片的长度是索引的差。例如, 的长度word[1:3]2

我认为我们可以假设范围函数的行为相同以保持一致性。

于 2012-07-06T14:59:44.627 回答
28

以下是一些 Google+ 用户的意见:

[...] 我被半开音程的优雅所左右。尤其是当两个切片相邻时,第一个切片的结束索引是第二个切片的开始索引的不变量实在是太漂亮了,不容忽视。例如,假设您在索引 i 和 j 处将字符串分成三个部分——这些部分将是 a[:i]、a[i:j] 和 a[j:]。

Google+ 已关闭,因此链接不再可用。剧透警报:那是 Guido van Rossum。

于 2014-01-31T13:59:43.537 回答
18

优雅 VS 明显

老实说,我认为 Python 中的切片方式非常违反直觉,它实际上是在用更多的大脑处理来交换所谓的优雅,这就是为什么你可以看到这篇 StackOverflow 文章有超过 2Ks 的赞成票,我想是因为很多人一开始并不理解。

举个例子,下面的代码已经让很多 Python 新手头疼了。

x = [1,2,3,4]
print(x[0:1])
# Output is [1]

不仅难以处理,而且难以正确解释,例如上面代码的解释是取第零个元素直到第一个元素之前的元素

现在看看使用上限包容性的 Ruby。

x = [1,2,3,4]
puts x[0..1]
# Output is [1,2]

坦率地说,我真的认为 Ruby 的切片方式对大脑更好。

当然,当您根据索引将列表拆分为 2 部分时,独占上限方法会产生更好看的代码。

# Python
x = [1,2,3,4]
pivot = 2
print(x[:pivot]) # [1,2]
print(x[pivot:]) # [3,4]

现在让我们看看包容性上限方法

# Ruby
x = [1,2,3,4]
pivot = 2
puts x[0..(pivot-1)] # [1,2]
puts x[pivot..-1] # [3,4]

显然,代码不那么优雅,但这里没有太多的大脑处理要做。

结论

归根结底,这真的是优雅 VS 明显的问题,Python 的设计者更喜欢优雅而不是明显。为什么?因为Python 的禅宗Beautiful 比丑陋好

于 2018-05-07T08:09:23.303 回答
11

然而,这个问题有点晚了,这试图回答你问题的为什么- 部分:

部分原因是因为我们在寻址内存时使用从零开始的索引/偏移量。

最简单的例子是数组。将“6 个项目的数组”视为存储 6 个数据项的位置。如果这个数组的起始位置在内存地址 100,那么数据,比如 6 个字符 'apple\0',存储如下:

memory/
array      contains
location   data
 100   ->   'a'
 101   ->   'p'
 102   ->   'p'
 103   ->   'l'
 104   ->   'e'
 105   ->   '\0'

所以对于 6 个项目,我们的索引从 100 到 105。地址是使用base + offset生成的,所以第一个项目在基本内存位置100 + offset 0(即 100 + 0),第二个在 100 + 1,第三个在 100 + 2, ...,直到 100 + 5 是最后一个位置。

for这是我们使用基于零的索引并导致语言结构(例如C 中的循环)的主要原因:

for (int i = 0; i < LIMIT; i++)

或在 Python 中:

for i in range(LIMIT):

当您使用像 C 这样的语言进行编程时,您可以更直接地处理指针,或者更直接地处理汇编,这种 base+offset 方案变得更加明显。

由于上述原因,许多语言结构会自动使用从startlength-1 的范围。

您可能会发现这篇关于 Wikipedia 上基于零的编号的文章很有趣,还有来自 Software Engineering SE 的这个问题

示例

例如,在 C 中,如果你有一个数组ar并且你给它下标,因为ar[3]这实际上等同于获取数组的(基)地址ar并将其添加3到它 =>*(ar+3)这可能导致像这样的代码打印数组的内容,显示简单基数+偏移量方法:

for(i = 0; i < 5; i++)
   printf("%c\n", *(ar + i));

真的相当于

for(i = 0; i < 5; i++)
   printf("%c\n", ar[i]);
于 2012-07-06T18:04:41.747 回答
9

这是独占上限是一种更明智的方法的另一个原因:

假设您希望编写一个函数,将一些变换应用于列表中的项目子序列。如果间隔按照您的建议使用包含上限,您可能会天真地尝试将其写为:

def apply_range_bad(lst, transform, start, end):
     """Applies a transform on the elements of a list in the range [start, end]"""
     left = lst[0 : start-1]
     middle = lst[start : end]
     right = lst[end+1 :]
     return left + [transform(i) for i in middle] + right

乍一看,这似乎是直截了当和正确的,但不幸的是,它是微妙的错误。

如果出现以下情况会发生什么:

  • start == 0
  • end == 0
  • end < 0

? 一般来说,您可能应该考虑更多的边界情况。谁愿意浪费时间考虑所有这些?(这些问题的出现是因为通过使用包容性的下限和上限,没有固有的方式来表示空区间。)

相反,通过使用上界互斥的模型,将列表分成单独的切片更简单、更优雅,因此更不容易出错

def apply_range_good(lst, transform, start, end):
     """Applies a transform on the elements of a list in the range [start, end)"""
     left = lst[0:start]
     middle = lst[start:end]
     right = lst[end:]
     return left + [transform(i) for i in middle] + right

(请注意,apply_range_good它不会转换lst[end];它也将其end视为独占上界。试图使其使用包容性上界仍然会遇到我前面提到的一些问题。寓意是包容性上界通常很麻烦。 )

(主要改编自我的一篇关于另一种脚本语言的包容性上限的旧帖子。)

于 2015-01-24T21:42:34.930 回答
-1

这种上限排除极大地提高了代码理解。我希望它涉及其他语言。

于 2021-02-25T12:05:02.120 回答