python编程时常见陷阱

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_36528804/article/details/82262639

1、可变对象(mutable)作为默认参数引发的陷阱
  • 不可变对象:该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。例如:数值类型(int和float)、字符串str、元组tuple、None都是不可变类型。
  • 可变对象:该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的出地址,通俗点说就是原地改变。列表list、字典dict、集合set都是可变类型。
    下面以list作为默认参数为例,看看能引发什么错误:
>>> def test(L = []):
    L.append('end')
    return L
>>> test(['A','B'])    # 结果是想要的
['A', 'B', 'end']
>>> test([1,2])    # 结果也是想要的
[1, 2, 'end']
>>> test()    # 结果还是想要的
['end']
>>> test()    # 结果是......
['end', 'end']
>>> test()    # eeeeee
['end', 'end', 'end']
>>> test(['a','b','c'])    # 结果正常
['a', 'b', 'c', 'end']

好像只有用默认参数时才会出现非预想的结果,好像test()记住了第一次创建的默认参数L一样,导致后面每次调用test()时都会在[]中加一个'end'
解释:Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。
解决办法:用不可变对象代替。代码如下:

>>> def test(L = None):
    if L is None:
        L = []
    L.append('end')
    return L
>>> test()
['end']
>>> test()
['end']

2、x = x + yx += y的小区别

x = x + yx += y在大多数情况下是等价的:
例如:

>>> x = 1
>>> x = x + 1
>>> print(x)
2
>>> x = 1
>>> x += 1
>>> print(x)
2
# 好像没什么区别,再看一个例子
>>> x = [1]
>>> x = x + [2]
>>> print(x)
[1, 2]
>>> x = [1]
>>> x += [2]
>>> print(x)
[1, 2]
# 好像还是没事么区别,好像打脸了!!!接着往下看:
>>> x = [1]
>>> print(id(x))    # id(x):返回对象x的内存地址,别问我为什么我的地址和你们的不一样!!!
2841626172744
>>> x = x + [2]
>>> print(id(x))    # 为什么两次x的地址不一样???
2841626172616
# 再试试另一个
>>> x = [1]
>>> print(id(x))
2841626173384
>>> x += [2]
>>> print(id(x))   # 两次x的地址一样,好像发现了什么!!!
2841626173384

解释:首先,当x是不可变对象时,这两者运算方式确实没什么区别,都会返回一个新地址。但当x是可变对象时,就有点区别:程序执行x = x + y时,是先开辟一段新内存,将计算x + y的结果放入其中,再让等号左边的x指向这段新内存。程序执行x += y时,是在原来x的地址上直接改成x + y计算后的结果。


3、小括号()引发的错误

这个错误大家在学习tuple时应该会讲到,就不细说,见如下代码段:

>>> type((1,2))
<class 'tuple'>
>>> a = (1,2)
>>> type(a)
<class 'tuple'>
>>> a = (1)
>>> type(a)
<class 'int'>
>>> a = (1,)
>>> type(a)
<class 'tuple'>

解释:在python中,(1,2)tuple类型,只有一个元素的tuple(1,),而不是(1)(1)在python中会优先执行数学运算中的小括号的含义,即a = (1)a = 1是等价


4、列表的乘法

列表的加法大家应该都用过,例如:

>>> [1,2,3]+[4,5]
[1, 2, 3, 4, 5]

对于乘法不知大家是否用过:

>>> a = [1] * 3
>>> a
[1, 1, 1]
>>> a = [None] * 5
>>> a
[None, None, None, None, None]
# 貌似还挺好用,没什么问题。
>>> a = [[]]*5
>>> a
[[], [], [], [], []]
>>> a[0].append(1)
>>> a[0]
[1]
>>> a
[[1], [1], [1], [1], [1]]    # 一定是错觉,再试一次
>>> a[0].append(2)
>>> a
[[1, 2], [1, 2], [1, 2], [1, 2], [1, 2]]    # eeeeeee

解释:上述的写法中,a中的每一个元素(空的list[])其实都是指向同一个可变对象[],所以当一个变了的时候,就都变了。
解决办法:

  • 法一:列表内的元素不用可变对象,改用不可变对象。
  • 法二:使用列表生成式,代码如下:
>>> a = [[] for x in range(5)]
>>> a
[[], [], [], [], []]
>>> a[0].append(1)
>>> a
[[1], [], [], [], []]

5、访问列表的同时修改列表

最容易出现这种错误的情景是:一边遍历着列表,一边筛选着列表元素,并将不满足条件的删除。
例如:

# test函数作用是:若L中的元素是偶数,则删除该元素。
>>> def test(L):
    for index, value in enumerate(L):
        """
        enumerate(sequence, [start=0]) 
        函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
        例如:
        >>>seasons = ['Spring', 'Summer', 'Fall', 'Winter']
        >>> list(enumerate(seasons)
        [(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
        """
        if 0 == value%2:
            L.pop(index)
    print(L)
# 测试一下
>>> L = [0,1,2,3,4,5]
>>> test(L)
[1, 3, 5]    # 好像还不错
>>> L = [0,1,2,2,3,4,4,5]
>>> test(L)
[1, 2, 3, 4, 5]    # 2和4,eeeee!!!!!

解释:简单来说一句话,“list长度在变短的同时index在增加,所以错位了”。不再多解释,自己去体会。
解决办法:使用列表生成式,如下:

>>> L = [0,1,2,3,4,5]
>>> [x for x in L if 0!=x%2]
[1, 3, 5]
>>> L = [0,1,2,2,3,4,4,5]
>>> [x for x in L if 0!=x%2]
[1, 3, 5]
6、闭包问题

起初,我还没注意到这个问题,有一次当我需要写一个输出2~1000之间的素数时,发现了可以用埃拉托色尼筛选法求解,链接的地址上也有python实现源码,至于我有什么疑问,最终如何解决的,暂且先不谈。看完这节后,大家应该就知道了。好了,不多说,先看一段程序:

>>> def test():
        # 函数本意是想将L中的元素过滤一遍,将2、3、5的倍数删掉。
        L = range(2,11)     # L = [2,3,4,5,6,7,8,9,10]
        for n in [2,3,5]:
            L = filter(lambda x: x%n > 0, L)
        print(list(L))
>>> test()
[2, 3, 4, 6, 7, 8, 9]   # 但结果,好像只是把5的倍数去掉了。

解释:要明白上面程序为何会这样运行,需明白以下几个知识(多余的我就不解释了,自己体会):

  1. python中的闭包、迟绑定问题
  2. python中的LEGB问题
  3. python中的lambda()filter()

解决办法:

  • 法一:改变变量的作用域(详情见代码):
>>> def test():
        L = range(2,11)     # L = [2,3,4,5,6,7,8,9,10]
        for n in [2,3,5]:
            L = filter(lambda x, n=n: x%n > 0, L) # 此处有改变
        print(list(L))
>>> test()    # 结果正确
[7]
  • 法二:再创建一个函数,绑定循环变量(不如上一个方法来的简单,详情见代码):
>>> def f(n):
    return lambda x: x%n > 0
>>> def test():
        L = range(2,11)     # L = [2,3,4,5,6,7,8,9,10]
        for n in [2,3,5]:
            L = filter(f(n), L)
        print(list(L))
>>> test()    # 结果也正确
[7]

猜你喜欢

转载自blog.csdn.net/qq_36528804/article/details/82262639