[Python]那些让你迷惑的代码

810 阅读2分钟

1.一个关于+=的谜题

#增量赋值:a+=b会调用__iadd_方法(就地加法),a=a+b会调用__add__方法
>>> t=(1,2,[2,3])
>>> t[2]+=[4,5]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [2, 3, 4, 5])
'''增量赋值不是原子操作
将t[2]+=[4,5]分解为2步:
1⃣️temp=[2,3],temp+=[4,5],可变对象可以执行此操作
2⃣️t[2]=temp,由于t为元组,不可变对象所以会报错。
'''解决办法
>>> t[2].extend([4,5])
>>> t
(1, 2, [2, 3, 4, 5])
>>> t=(1,2,[2,3])
>>> t[2].append([4,5])
>>> t
(1, 2, [2, 3, [4, 5]])

增量赋值运算符+=、*=。对于增量加法会先去调用__iadd__,若没有该方法则调用__add__。str不可变对象是例外,因为对字符串做+=太普遍,所以Cpython对它做了优化,为str初始化内存的时候,程序会为它留出额外的可扩展空间,因此进行增量操作的时候,并不会涉及复制原有字符 串到新位置这类操作。

2.变量作用域

>>> b=6
>>> def f1(a):
...    print a
...    print b
...    b=9  
...
>>> f1(3)
3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in f1
UnboundLocalError: local variable 'b' referenced before assignment
-------------------------------------------
为什么会报错,而不是正常输出3  6  
python在编译函数定义体时,由于在函数体中b被赋值,所以被判断为局部变量。在赋值之前引用变量b,所以会报错。如果在函数体内没有给b赋值,则b被视为全局变量。

3.避免可变对象作为参数默认值

# -*- coding: utf-8 -*-
def ad(nam,abs=[]):
    if abs==None:
        abs=[]
    abs.append(nam)
    return abs

print ad('xxx')
print ad('cx')
-----------------------out
['xxx']
['xxx', 'cx']  #期待输出['cx']
第一个输出会和第二个输出共享同一个列表。要想得到期望的结果,将参数abs默认为None

4.推导式

def demo():
    for i in range(4):
        yield i

g=demo()

g1=(i for i in g)
g2=(i for i in g1)

print(list(g1))
print(list(g2))
------------------out----why
[0, 1, 2, 3]
[]
----------------分割线
def add(n,i):
    return n+i
def test():
    for i in range(2):
        yield i
g=test()
for n in [1,2]:
    g=(add(n,i) for i in g)
print(list(g))
----------out------why
[45]   
-----------------------------分割线   (延迟绑定)
def create_multipliers():
    return [lambda x : i * x for i in range(5)]
for multiplier in create_multipliers():
    print(multiplier(2))
---------------------out
实际:8 8 8 8 8
期望:0 2 4 6 8
--------------------解决
def create_multipliers():
    return [lambda x,i=i: i * x for i in range(5)]
for multiplier in create_multipliers():
    print(multiplier(2))

5. 数据叠加

 
>>> lists = [[]] * 3

>>> lists

[[], [], []]

>>> lists[0].append(3)

>>> lists

[[3], [3], [3]]

这样叠加出来的数据其实是对数据的多次引用,并不是拷贝

>>> lists = [[] for i in range(3)]

>>> lists[0].append(3)

>>> lists[1].append(5)

>>> lists[2].append(7)

>>> lists

[[3], [5], [7]]