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

2025-01-22 08:45:00
admin
原创
117
摘要:问题描述:我知道当我使用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大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2796  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1710  
  PLM系统在企业项目管理中扮演着至关重要的角色,尤其是在项目采购管理方面,能够通过一系列策略提升采购效率、降低成本并保障质量。通过深入解析相关策略,企业可以更好地利用PLM系统优化采购流程,实现项目的顺利推进与整体目标的达成。需求精准定义策略在项目采购中,明确需求是首要任务。PLM系统可助力企业精准定义采购需求。首先,...
plm是什么意思   8  
  在企业的运营过程中,跨部门数据共享一直是一个关键且颇具挑战的问题。不同部门之间由于业务差异、系统不兼容等多种因素,常常形成信息孤岛,导致数据无法顺畅流通,影响企业整体的决策效率和协同效果。而 PLM 系统作为一种先进的管理工具,为解决这一难题提供了有效的途径。通过其一系列强大的核心功能,能够打破部门之间的数据壁垒,实现...
plm系统   7  
  PLM(产品生命周期管理)项目涉及产品从概念设计到退役的全流程管理,其复杂性和长期性要求高效的项目进度管理工具。甘特图作为一种直观且实用的项目进度可视化工具,在PLM项目中发挥着关键作用。通过甘特图,项目团队成员能够清晰地了解项目任务的时间安排、进度状态以及各项任务之间的关系,从而更好地协调工作、分配资源,确保项目按计...
plm流程是什么   6  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

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

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

内置subversion和git源码管理

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

免费试用