为什么切片和范围上限是独占的?

时间:2012-07-06 14:49:36

标签: python language-design slice

免责声明:我不是在询问 stopslice()的上限range()参数是否为独占或如何使用这些功能。

调用rangeslice函数以及切片表示法[start:stop]都是指整数集。

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

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

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

stop等于0或被省略时,是否使start等于所表示的整数集中的元素数?

是否有:

for i in range(start, stop):

看起来像下面的C代码?

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

6 个答案:

答案 0 :(得分:32)

documentation意味着它有一些有用的属性:

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

以下是切片操作的有用不变量:s[:i] + s[i:]等于s

     

对于非负索引,切片的长度是索引的差异,如果两者都在边界内。例如,word[1:3]的长度为2

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

答案 1 :(得分:11)

以下是某些Google+用户的opinion

  

[...]我被半开的间隔的优雅所左右。特别是   不变的,当两个切片相邻时,第一个切片的结束   index是第二个切片的起始索引太漂亮了   忽视。例如,假设您将字符串拆分为三个部分   索引i和j - 部分将是[:i],a [i:j]和a [j:]。

答案 2 :(得分:8)

这个问题有点晚了,但是,这会尝试回答你问题的 为什么 - 部分:

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

最简单的例子是一个数组。可以将“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是最后一个位置。

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

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

或在Python中:

for i in range(LIMIT):

使用C语言进行编程时,处理指针 这个基础+偏移方案更直接,或更集合 变得更加明显。

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

您可能会发现维基百科上Zero-based numbering上的这篇文章很有趣,还有this question from Software Engineering SE

实施例

在C中,例如,如果你有一个数组ar,你将其下标为ar[3],这实际上等同于获取数组ar的(基本)地址并添加{{1 }} to it =&gt; 3这可以导致这样的代码打印数组的内容,显示简单的基本+偏移方法:

*(ar+3)

真的相当于

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

答案 3 :(得分:6)

这是另一个理由,即独占上限是一种更为理智的方法:

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

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视为独占上限。尝试使其使用包含上限仍会有一些我之前提到的问题。道德是包容性的上限通常是麻烦的。)

(大部分改编自an old post of mine about inclusive upper-bounds in another scripting language。)

答案 4 :(得分:4)

优雅与显而易见

老实说,我认为在Python中切片的方式是非常反直觉的,它实际上是通过更多脑处理来交换所谓的 elegant-ness ,这就是为什么你可以看到this StackOverflow article有超过2K的upvotes,我认为这是因为有很多人不理解它。

例如,以下代码已经引起了许多Python新手的头痛。

        RingerBroadcastReceiver receiver = new RingerBroadcastReceiver();
        IntentFilter filter = new IntentFilter(
                        AudioManager.RingerModeChangedAction);
        RegisterReceiver(receiver, filter);

不仅难以处理,也难以正确解释,例如,上面代码的解释是将第0个元素带到第一个元素之前的元素。

现在看看使用上限的Ruby。

x = [1,2,3,4]
print(x[0:1])
# Output is [1]
坦率地说,我真的认为Ruby的切片方式对大脑来说更好。

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

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

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

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

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

结论

最后,它真的是关于优雅的VS Obvious-ness的问题,而Python的设计师更喜欢优雅而不是明显的优势。为什么?因为Zen of Python表示 Beautiful比丑陋更好。

答案 5 :(得分:-1)

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