理解该程序输出,Python精进一步

前言

某日,某君抛给我一段python代码:

python 复制代码
seq = [1, 2, -1, 0]
seq[0], seq[seq.index(max(seq))] = seq[seq.index(max(seq))], seq[0]
seq[len(seq) - 1], seq[seq.index(min(seq))] = seq[seq.index(min(seq))], seq[len(seq) - 1]
print(seq)

某君不理解程序的输出,想要我解释一下。

再继续阅读之前,读者不妨思考一下程序的输出。

程序的输出

程序输出了[1, 2, 0, -1]。

某君的目的

某君要实现的目的很简单:将一个序列里面最大的元素和第一个元素进行交换,将最小的元素和最后一个元素进行交换(暂不考虑效率)。

某君的困惑

某君发现上面的代码不能交换最大的元素但是可以交换最小的元素,这是某君最大的困惑,因为在某君看来,其认为两行代码的逻辑完全一致,要是能够成功运行,那么两行代码应该都成功或者都失败,绝不可能是一个成功一个失败。当某君问我的时候,我也觉得其说的在理。但是能明显的感觉到正确的写法,应该是先记录最大和最小元素的下标然后在进行交换,即正确代码应该是形如下面的代码:

python 复制代码
seq = [1, 2, -1, 0]
minElement = seq.index(min(seq))
maxElement = seq.index(max(seq))
seq[0], seq[maxElement] = seq[maxElement], seq[0]
seq[len(seq) - 1], seq[minElement] = seq[minElement], seq[len(seq) - 1]
print(seq)

上面的代码能够输出预期结果[2, 1, 0, -1]。

踏破铁鞋无觅处

对于上面的代码我们需要搞清楚各个语句的执行顺序,对于等号与逗号表达式,其各自的执行顺序如下:

  • 对于赋值运算符而言:从右到左依次执行,有多个等号的时候,从最右边的等号开始执行。
  • 逗号表达式:从左到右依次执行

我们需要考虑上面的代码做了什么,上面的代码实际上有三步:

  1. 获取赋值运算符右边的值,将结果打包成一个元组;
  2. 将元组的值依次赋值给左边的变量。

问题的关键就在于依次 两字,如果是一次性 赋值给左边的话,那么一开始就会对左边的变量计算出来,以交换最大值为例,如果是一次性 赋值,那么上面会变成:seq[0], seq[1] = seq[1], seq[0],即能够进行正常的交换。而如果是依次那么问题就变了:

  1. 计算出赋值运算符右边并包装成元组,即(seq[1], seq[0])也就是(2, 1);
  2. 依次进行赋值,首先是seq[0] = 2,完成该赋值后seq = [2, 2, -1, 0];
  3. 接下来执行seq[s.index(max(seq))] = 1,由于seq已经发生变化此时s.index(max(seq))会返回第一个等于2的元素下标即0,即执行seq[0] = 1,完成赋值后seq = [1, 2, -1, 0]。

可以发现上面的代码实际上确实发生了两次交换,只是最后又给换回去了。

接下来我们分析为什么能够成功交换最小值和最后一个元素:

  1. 首先计算出赋值运算符右边并包装成元组,即(seq[2], seq[3])也就是(-1, 0);
  2. 第一次赋值操作seq[3] = -1,完成赋值后seq = [1, 2, -1, -1];
  3. 第二次赋值操作seq[s.index(min(seq))] = 0,由于s.index()返回的是第一个找到的元素,所以其返回值为2,即执行seq[2] = 0,完成赋值后seq = [1, 2, 0, -1]。

如果你成功理解了上面的过程可以试着分析如下的代码:

python 复制代码
# 1
seq = [1, 2, -1, 0]
seq[seq.index(max(seq))], seq[0] = seq[0], seq[seq.index(max(seq))]
print(seq)

# 2
seq = [1, 2, -1, 0]
seq[seq.index(min(seq))], seq[len(seq) - 1] = seq[len(seq) - 1], seq[seq.index(min(seq))]
print(seq)

上面代码的输出:

复制代码
[2, 1, -1, 0]
[1, 2, 0, -1]

良好的编程习惯

发生上面的错误时在变化中引入变化。即在上面的过程中同时引入了两个变化的过程,另外的常见错误时,在遍历一个容器的同时,向容器中增加元素,以C++为例,初学者同样容易发生如下的错误:

cpp 复制代码
vector<int> v{0, 1, 2, 3};
for (int i = 0; i < v.size(); i++) { v.push_back(i); }

上面代码的本意是希望在v后面再追加{0, 1, 2, 3}但是由于每一次push_back都会导致v.size()增加,因此上面的代码会发生死循环,正确的如下:

cpp 复制代码
vector<int> v{0, 1, 2, 3};
int n = (int)v.size();
for (int i = 0; i < n; i++) { v.push_back(i); }

在编写代码的时候不应该在变化中引入变化,这样的代码不仅不容易阅读,而且容易出现bug。

相关推荐
wangchunyu1145 小时前
JAVA CompletableFuture异步编程实战
java·开发语言·python
2501_944521597 小时前
Immer 跨端三方库仓颉适配全流程
python·eclipse·django
信誓旦旦的程序猿7 小时前
【Python量化实战宝典 #26】止损与回撤控制手册:移动止损、最大回撤(Python 可跑)
python·1024程序员节·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口
2501_944521597 小时前
Deepmerge 跨端三方库仓颉适配全流程
java·python·eclipse·github
Patrick在香港7 小时前
同一行 strftime,两台机器跑出两种日期
python·datetime·时区·locale·日期处理
2601_967987377 小时前
用Python+OpenCV+YOLO写个游戏脚本:从自动瞄准到自动采集的保姆级教程
python·opencv·yolo·目标检测·游戏自动化
meiying88387 小时前
快递单号批量查询Python脚本:从手动到自动化的完整实现
开发语言·python·自动化
人工智能导论实践课7 小时前
【GUI桌面开发3】PyCharm配置外部工具
ide·python·pycharm
一千柯橘8 小时前
LangChain 的实时输出处理: stream_events 和 Streaming
python·langchain·agent
贾公子8 小时前
图片 AIGC 检测接入实战——URL 与 Base64 的取舍、体积校验与「你送过去的可能不是那张图」
python·aigc