8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

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

Shyam Patel 2月前

96 0

我知道当我使用 range([start], stop[, step]) 或 split([start], stop[, step]) 时,停止值不包含在范围或切片中。但为什么它会这样工作呢?例如范围(...

我知道当我使用 range([start], stop[, step]) 或 时 slice([start], stop[, step]) stop 不包含 在范围或切片中。

为什么 会这样呢?

是否例如 a range(0, x) range(x) 将包含 x 许多元素?

它是否与 C 的 for 循环习语并行,即 for i in range(start, stop): 表面上类似 for (i = start ; i < stop; i++) {


See also 使用索引向后循环 for a case study: setting the stop and step values properly can be a bit tricky when trying to get values in descending order.

帖子版权声明 1、本帖标题:为什么 Python 的切片和范围上限是独有的?
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由Shyam Patel在本站《list》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 不管它们为什么这样,如果您非常需要该功能,您始终可以编写自己的类似的包容性功能。

  • 以下是 Edsger Dijkstra 手写的精彩解释,解释了为什么半开零基间隔约定是计算机编程的最佳选择:

  • 文档 暗示 它具有一些有用的属性:

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

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

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

    我认为我们可以假设范围函数对于一致性起着相同的作用。

  • 让我困惑的一件事是,对于数组 x,x[-1] 指的是最后一个元素,而 x[-2:-1] 并不是指最后 2 个元素,而只是指倒数第二个元素。对于 Ruby 程序员来说,这是一个常见的陷阱,因为你习惯于将 -1 作为最后一个元素,而 .. 符号是包含的,即 x[-2..-1] 返回最后 2 个元素。python 冒号 ':' 实际上是 ruby​​ 的三点 '...'

  • 以下是 Guido van Rossum 的 观点

    [...] 我被半开区间的优雅所吸引。尤其是当两个切片相邻时,第一个切片的结束索引是第二个切片的起始索引,这个不变性实在太美妙了,不容忽视。例如,假设您将一个字符串拆分为索引 i 和 j 处的三个部分——这些部分将是 a[:i]、a[i:j] 和 a[j:]。

    [Google+ 已关闭,因此链接不再有效。 这是存档链接 。]

  • 这是我见过的唯一一个让我感觉好一点的解释。这种优雅是一种非任意性的原因,终于让我感到安心。毕竟,这被称为切片,这清楚地表明其目的就是切片,而不仅仅是子集选择。谢谢。

  • 这个解释也让我感觉好了一点;但是,对于一种旨在可读的语言来说,这可能仍然是不可原谅的......

  • 优雅 VS 明显

    说实话,我认为 Python 中的切片方式相当违反直觉,它实际上是 用更多的脑力处理来 优雅 这篇 StackOverflow 文章 有超过 2K 个赞,我想这是因为很多人最初并不理解它。

    举个例子,下面的代码已经让很多 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 的切片方式对大脑更有益。

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

    # 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]
    

    显然,代码不太优雅,但这里不需要进行太多的脑力处理。

    结论

    归根结底,这其实是一个优雅与显而易见的问题,而 Python 的设计者更喜欢优雅而不是显而易见。为什么?因为 Python 之禅 认为 美丽胜过丑陋 .

  • 我同意零基索引 (ZBI) 一开始并不明显。我记得很多(很多 - 不,很多)几十年前,当我第一次学习编程时,我对 ZBI 感到有些困惑。问题不在于独有的上限概念,而在于没有解释该概念的用途。但是一旦我弄清楚了这一点,它的用途就变得显而易见了!所以也许“显而易见”取决于旁观者的眼睛,或者换一种(更优雅 :-) 的说法:显而易见的东西只有在有人简单地表达它时才会被看到。如果 Python 教科书和教程能简单地表达这一点就好了。

  • 我更喜欢基于一的索引和闭区间……它非常简单,而且是真正的索引。具有半开区间的基于零的事物只是偏移量。适用于某些情况,例如指针算术等。不适合数组的正常使用(大多数高级 pr. 语言甚至不能进行指针算术,所以这只是不必要的头痛)

  • 引用 12

    如果你想要从 arr = [1,2,3,4,5,6,7,8] 中获取第二到第四个元素,那么你需要切片 arr[2:4],或者如果你想要第三个元素,那么切片 arr[3]。而不是 arr[2] 或 arr[1:4],其中 1 表示第二,4 表示第五,但不包括在内 -> 这既不优雅,又很愚蠢

  • @JsonKody 在我看来,您实际上是在谈论计数,而不是索引。但是如果您“更喜欢基于一的索引和闭区间”……请使用 BASIC。更好的是,Pascal;它可以让您分别决定每个数组的起始索引。零?一?-57?所有有效选择。

  • 引用 14

    虽然这个问题有点晚了,但是,这试图回答 你的问题的 为什么

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

    最简单的例子就是数组。将“包含 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。地址是使用 基址 + 偏移量 ,因此第一个项目位于 基本内存位置 100 + 偏移量 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 语言之类的语言编程时,你会更直接地处理指针,或者更直接地处理汇编,这种基址+偏移量方案变得更加明显。

    由于上述原因,许多语言结构自动使用从 开始 长度-1 的 .

    从零开始编号的 这篇文章 很有趣,还有 软件工程 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]);
    
  • 这或许可以解释为什么 range(num) 不包括上限,因为您可以说 num 只是基于 0 的范围量。这并不能解释为什么 range(lower,upper) 不包括它,因为我们特别要求上限

  • @YonatanNir 出于相同的原因,并且为了保持一致性。否则,您将拥有具有相同名称的函数,这些函数的行为会根据是否提供默认值而有所不同。例如,对于增加的范围,range(num) 实际上与 range(0, num) 和 range(0, 1, num) 相同。对于 API 开发人员和使用 API 的人来说,拥有一致的行为更容易。

  • 以下是独占上限是更明智方法的另一个原因:

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

    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 独占上限。尝试使其使用包含上限仍然会出现我之前提到的一些问题。寓意是包含上限通常很麻烦。)

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

返回
作者最近主题: