Python学习之旅—Day04
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python学习之旅—Day04相关的知识,希望对你有一定的参考价值。
前言:
前面三篇博客对Python的基础知识点进行了相关总结和整理,今天的博客主要专注于解决前面一些知识点的疑难点,并在此基础上补充一些知识点,以此来加深对相关知识点的理解。
1.列表和字典结合for循环使用时所引发的下标越界和字典大小改变错误。
我们往往需要结合for循环来遍历列表和字典,然后实现相应的逻辑。但由于列表和字典本身是有大小限制的,我们可以通过len(dict)或者len(list)来获取其长度,如果处理不得当,很容易造成下标越界错误。我们来看看一个实际的需求:已知列表li = [11, 22,33,44,66],要求删除索引值为奇数值的元素,并输出删除后的列表。有很多方法可以实现如上需求,我们来看看如下的几种实现方法:
for i in range(0,len(li)): # 4,0
del li[i]
print(li)
直接执行如上代码会直接报错:IndexError: list assignment index out of range。刚开始也没注意到这个错误,后面经过仔细分析,才发现里面的道道。来跟随笔者一起来分析下。
i取值可以为0,1,2,3,4,li = [11, 22,33,44,66]。当i = 0时,删除li[0]后的列表为:[22,33,44,66],此时li的长度为4,此时i+1,遍历li中第二个元素:33,删除元素后的列表变为:
[22,44,66].i继续+1,遍历li中第三个元素:66,删除元素后的列表为:[22,44]。i继续+1,遍历li中第4个元素,此时发现列表中仅剩下2个元素。即使再使用li[3],此时就会报列表索引越界的错误,我们可以在Pycharm中通过debug来测试我们的想法。
产生上述错误的原因在于我们忽略了列表是一个可变对象的容器,在我们对原始列表进行删除元素时,它的长度是动态改变的。因此很有可能会导致索引越界的错误。我们再来看卡如下的例子:
li = [11, 22, 33, 44, 66] # 0, 1, 2, 3, 4
for num in range(0, len(li)):
if num % 2 == 1:
del li[num]
print(li)
#运行结果为:[11,33,44]
上面的代码可以在Pycharm中运行成功,但是运行结果确是错误的,依然是i因为动态删除列表中的元素会导致列表的长度发生变化,虽然没有出现索引下标越界异常,但是执行的结果确是错误的。回到上面题目的需求,我们提供了几种不同的解体思路,最优解当然得用到切片。看看如下几种解法:
li = [11, 22, 33, 44, 66] # 0,1,2,3,4
new_list = []
for i in range(0, len(li)):
if i % 2 == 0:
new_list.append(li[i])
print(new_list)
li = [11, 22, 33, 44, 66] # 0,1,2,3,4
for i in range(len(li)-1, -1, -1):
if i % 2 == 1:
del li[i]
print(li)
我们可以看到前面两种解法都可以达到目的,优先推荐上面的第一种,因为代码简洁明了,容易明白。但我们还有一个最简单的方法,那就是使用切片,一句话搞定:
li = [11, 22, 33, 44, 66] # 0,1,2,3,4
del li[1::2]
print(li)
2.关于元组tuple,这里有一个重要的知识点补充:当元组中的元素只有一个时,无比在元素后面加上逗号,要不然会造成意想不到的错误。我们来用代码说话:
>>> tu = (1,2,3,4,[11,22,33])
>>> print(tu,type(tu))
(1, 2, 3, 4, [11, 22, 33]) <class ‘tuple‘>
>>> tu = (1,)
>>> print(tu,type(tu))
(1,) <class ‘tuple‘>
>>> tu = (1)
>>> print(tu,type(tu))
1 <class ‘int‘>
从代码运行的结果可知,如果元组中的元素只有一个,如果不加逗号,那么它其实相当于一个整数。这点事大家普遍忽略的,无比要记牢。
3.第三点就是关于for循环和字典的遍历一起使用而引发的。
我们还是来一个实际需求:已知字典dic = {‘k1‘: ‘v1‘, ‘y2‘: ‘v2‘, ‘k11‘: ‘v3‘},现在要求删除字典key中包含字符k的键值对,然后打印输出最终的字典。初学者拿到手,肯定是这样的思路:
dic = {‘k1‘: ‘v1‘, ‘y2‘: ‘v2‘, ‘k11‘: ‘v3‘}
for item in dic.keys():
if ‘k‘ in item:
del dic[item]
print(dic)
但实际上这样做会报如下的错误:RuntimeError: dictionary changed size during iteration。这是因为我们在字典的遍历过程,删除了字典中的元素,使得字典的大小发生了改变。
这会在Python中引发RuntimeError。所以要想实现上述的需求,这边提供两种解决方法。如下代码:
dic = {‘k1‘: ‘v1‘, ‘y2‘: ‘v2‘, ‘k11‘: ‘v3‘, ‘w1‘: ‘y2‘}
dic1 = {}
for item in dic.keys():
if ‘k‘ in item:
continue
else:
dic1[item] = dic[item]
print(dic1)
这种思路采用的是新建一个字典,将不符合条件的筛选出来,然后打印输出,而不对原始字典做删减操作,以此避免改变原始字典的长度。我们一起来看看第二种解法:
dic = {‘k1‘: ‘v1‘, ‘y2‘: ‘v2‘, ‘k11‘: ‘v3‘, ‘w1‘: ‘y2‘}
dic_key_list = [] # 用来存储字典键的集合
for key in dic:
dic_key_list.append(key)
for row in dic_key_list:
if ‘k‘ in row:
del dic[row]
print(dic)
这种思路的出发点也是在不改变字典大小的情况下,对字典进行静态删除。而不是结合for循环对字典进行动态删除,这在Python中是不允许的。作为这种解法,还有一种更加Pythonic的方式,如下:
for k in list(dic.keys()):
if ‘k‘ in k:
del dic[k]
print(dic)
笔者在学习过程中,曾经碰到如下的解法,通过这种解法引出了for循环和else语句的结合使用,在effective Python这本书中,作者推荐不要这样使用。但是既然作为初学者,我们有必要了解下,代码如下:
dic = {‘k1‘: ‘v1‘, ‘y2‘: ‘v2‘, ‘k11‘: ‘v3‘, ‘w1‘: ‘y2‘}
while True:
for k in dic:
if ‘k‘ in k:
del dic[k]
break
else:
break
print(dic)
要想更好的理解这种思路,建议通过debug去观察,这里主要明白for......else的用法。当k第一次取k11时,删除键值对‘k11‘: ‘v3‘成功后,会执行break语句,跳出里面的for循环。按照正常理解,此时应该继续执行else语句块。但是这里要注意,else语句块要执行,必须等到前面的for循环里面的其他元素遍历完毕后才能够执行。所以通过这种方式,我们可以在遍历字典的情况下,不动态改变字典的长度来达到删除元素的目的,也就相当于我删除完字典中满足条件的一个键值对后,退出for循环,然后再次进入for循环。这就保证了没有动态修改字典的大小。这里主要要明白for.....else语句块的执行流程,如果还不懂,可以在else语句块后面再加一个break,如下:
dic = {‘k1‘: ‘v1‘, ‘y2‘: ‘v2‘, ‘k11‘: ‘v3‘, ‘w1‘: ‘y2‘}
while True:
for k in dic:
if ‘k‘ in k:
del dic[k]
break
else:
break
break
print(dic)
#打印结果:{‘y2‘: ‘v2‘, ‘w1‘: ‘y2‘, ‘k11‘: ‘v3‘}
打印结果与实际不符合,这也就印证for...else的执行流程。
4.关于第四点笔者在这里想探讨的是如何往空字典里面循环加入数据。我们还是来看一个实际需求:有如下值集合 [11,22,33,44,55,66,77,88,99,90,10],将所有大于 66 的值保存至字典的第一个key中,将小于 66 的值保存至第二个key的值中。即: {‘k1‘: 大于66的所有值, ‘k2‘: 小于66的所有值}.要求:只能创建一个空子典dic={}。
这个需求实际考察我们如何动态往字典里面添加元素。我们先来看看初学者的做法:
li = [11, 22, 33, 44, 55, 66, 77, 88, 99, 90]
dic = {} # dic = {‘k1‘:[], ‘k2‘:[]}
for i in li:
if i > 66:
dic[‘k1‘] = [i, ]
elif i < 66:
dic[‘k2‘] = [i, ]
else:
continue
print(dic)
#打印结果为:{‘k2‘: [55], ‘k1‘: [90]}
很明显上面的结果没有满足我们的需求,通过分析可知,第一步dic[‘k1‘] = [i, ]虽然能够往空子典中加入一个元素,该元素的key为k1,value为一个列表。比如{‘k1‘: [90],}但是第二次如果遍历的元素是88,那么执行dic[‘k1‘] = [i, ]后,我们发现变成了这样:{‘k1‘: [88],}.我们知道,当字典中存在某个key时,如果第二次再往该字典中添加相同key的键值对,那么字典会认为是对这个字典中的key所对应的value值进行了更新。也就是由90变成了88.所以直到我们遍历完所有的元素,最后,列表中只有一个元素。
通过上面的分析可知,关键问题在于解决如何保证key相同,二次也能加进去。最后的思路为:如果key第一次存在,那么直接dic[‘k1‘] = [i, ],否则我们直接在列表后面添加元素即可。代码如下:
li = [11, 22, 33, 44, 55, 66, 77, 88, 99, 90]
dic = {} # dic = {‘k1‘:[], ‘k2‘:[]}
for i in li:
if i > 66:
if ‘k1‘ not in dic:
dic[‘k1‘] = [i, ]
else:
dic[‘k1‘].append(i)
elif i < 66:
if ‘k2‘ not in dic:
dic[‘k2‘] = [i, ]
else:
dic[‘k2‘].append(i)
else:
continue
print(dic)
5.关于第五点,这里有一个要注意的地方,就是查看字典中value是否在其中。这涉及到循环遍历字典中的value,同时要主要区别in和==的区别。看如下的代码:
循环实现,检查“v1”是否在字典 dic = {‘k1‘: ‘v1‘,‘k2‘: ‘v2‘} 的值中。初学者的做法往往是这样的:
dic = {‘k1‘: ‘v1‘, ‘k2‘: ‘v2‘}
for item in dic.values():
if ‘v1‘ == item:
print(‘True‘)
break
else:
print(‘False‘)
#通过执行我们发现结果有时为:True,而有时同时打印False和True.
思考:为什么会遇到这种情况?!究其原因我们发现,由于字典是无序的,所以如果第一次比较的value值是v2,那么它会打印完毕Fasle后,再去拿v1做比较,然后打印True.
如果我们运气好,那么第一次比较的是v1,所以会直接打印False.我们即使将for循环变为 for item in list(dic.values()),即将dic.values封装为一个list,结果也是和上面一样。因为从字典中取value时就具有随机性,不知道先取到哪个。所以list(dic.values())的值有两种情况:[‘v1‘, ‘v2‘]和[‘v2‘, ‘v1‘]。那具体怎么解决呢?主要有两种方法可以解决,一是通过for.....else语句改写代码,如下:
dic = {‘k1‘: ‘v3‘, ‘k2‘: ‘v2‘}
for item in dic.values():
if ‘v1‘ == item:
print(‘True‘)
break
else:
print(‘False‘)
第二种解法是设置一个标识符,代码如下:
result = False
dic = {‘k1‘: ‘v1‘, ‘k2‘: ‘v2‘}
for item in dic.values():
if ‘v1‘ == item:
result = True
break
print(result)
另外关于本题,要注意的是in和==的区别,例如,如果value中有的值为k111,那么使用in条件判断时,‘k1‘ in k111,则成立。如果我们想判断的是值是否相等,要使用==。
以上就是第4次博客的主要内容,先暂时讲解这么多!后期会陆续更新和增加!
---恢复内容结束---
以上是关于Python学习之旅—Day04的主要内容,如果未能解决你的问题,请参考以下文章