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的主要内容,如果未能解决你的问题,请参考以下文章

Python学习之旅—Day03

Python学习之旅—Day05(文件操作)

Python学习之旅—Day06(字符串+字典+列表重点方法)

Python学习之旅—Day07(生成器与迭代器)

Python之旅Day6 模块应用

Python之旅Day1