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

2025-01-22 08:45:00
admin
原创
93
摘要:问题描述:我知道当我使用range([start], stop[, step])或 时slice([start], stop[, step]),stop值不包含在范围或切片中。但为什么会这样呢?是否例如 arange(0, x)或range(x)将包含x许多元素?它是否与 C 的 for 循环习语并行,即fo...

问题描述:

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

为什么会这样呢?

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

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


另请参阅使用索引向后循环的案例研究:当尝试按降序获取值时,正确设置stop和值可能会有点棘手。step


解决方案 1:

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

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

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

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

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

解决方案 2:

以下是Guido van Rossum 的观点:

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

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

解决方案 3:

优雅 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 之禅说,美丽胜过丑陋

解决方案 4:

这试图回答你的问题的为什么部分:

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

最简单的例子是数组。将“包含 6 个项目的数组”视为存储 6 个数据项的位置。如果此数组的起始位置位于内存地址 100,则数据(假设为 6 个字符“apple\0”)的存储方式如下:

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

因此对于 6 个项目,我们的索引从 100 到 105。地址是使用基数 + 偏移量生成的,因此第一个项目位于基本内存位置100 +偏移量0(即 100 + 0),第二个项目位于 100 + 1,第三个项目位于 100 + 2,...,直到 100

  • 5 是最后一个地点。

这是我们使用从零开始的索引的主要原因,并导致了诸如forC 中的循环之类的语言构造:

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

或者用 Python 来写:

for i in range(LIMIT):

当您使用 C 语言等更直接地处理指针的语言进行编程时,或者使用汇编语言进行编程时,这种基址+偏移量方案变得更加明显。

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

您可能会发现 Wikipedia 上的这篇有关从零开始的编号的文章很有趣,并且还有来自软件工程 SE 的这个问题。

例子

例如在 C 语言中如果你有一个数组ar并且你对它进行下标,ar[3]这实际上相当于获取数组的(基)地址ar并添加3到它 =>*(ar+3)这会导致像这样的代码打印数组的内容,显示简单的基数+偏移量方法:

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

确实相当于

for(i = 0; i < 5; i++)
   printf("%c
", ar[i]);

解决方案 5:

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

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

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

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

解决方案 6:

埃兹格·迪杰斯特拉 (Edsger Dijkstra) 笔记摘要《为什么编号应该从零开始》

范围约定

有 4 种惯例来表示自然数 aa + 1 、 … 、b的范围:

  1. a≤i < b + 1 。

  2. a − 1 < ib

  3. a≤i≤b。​

  4. a − 1 < i < b + 1。

像范围惯例 2 和 4 中那样排除下限会给出下限 -1(不是自然数),以表示从 0 开始的范围(惯例 2:-1 < ib;惯例 4:-1 < i < b + 1)。像范围惯例 2 和 3 中那样包括上限会给出负上限(不是自然数),以表示从 0 开始且已缩小到空范围的范围(惯例 2:-1 < ib,其中b < 0;惯例 3:0 ≤ ib,其中b < 0)。像范围约定 1 中一样包含下限并排除上限,将给出一个下限 0(自然数),表示从 0 开始的范围(0 ≤ i < b + 1),以及一个上限 0(自然数),表示从 0 开始缩小到空范围(0 ≤ i < 0)。因此,范围约定 1 应为首选。

索引约定

索引n 个元素有两种约定:

  1. 从 0 开始。

  2. 从 1 开始。

像索引约定 2 中那样从 1 开始,则索引范围为 1 ≤ i < n + 1,遵循首选范围约定。像索引约定 1 中那样从 0 开始,则索引范围为 0 ≤ i < n,遵循首选范围约定,这更好。因此,索引约定 1 应为首选。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用