工作中遇到的 Python 问题总结
/ / 阅读数:1157摘要 :Python 是一个简洁优雅的高级编程语言,它容易上手的同时,也隐藏了一些难以理解和甚至反人类直觉的坑。本文列出一些我们线上代码实际遇到过的一些编码问题。
一、不要混用 Tab 和空格
如上图中的代码,return n
那行代码的是Tab缩进,而其他行是4 个空格,当编辑器设置Tab显示宽带为 4 个空格宽时,就会出现逻辑和直觉相悖的情况。
解决办法:
- 要求团队成员遵循 PEP 8 代码规范 ,统一采用 4 个空格缩进代码
- 项目根目录配置 .editorconfig ,Python 文件采用 4 个空格缩进
- 配置 IDE 编辑器,显示特殊控制字符
二、不要使用可变默认参数
class Blog(object): def __init__(self, blog_id, tags=[]): self.blog_id = blog_id self.tags = tags blog1 = Blog(1) blog2 = Blog(2) blog1.tags.append('Python') blog2.tags.append('Java') print(blog1.tags, blog2.tags) # Output: ['Python', 'Java'], ['Python', 'Java'] |
Python 函数的默认参数在定义时确定,如果调用函数时不显示传递默认参数,那么每次调用都相同对象。如果该对象时可变类型(比如list
),则容易出现上面的问题。
解决办法:
不要使用可变变量做默认参数,可以使用None
,然后代码里面判断如果是None
就初始化一个新对象。
class Blog(object): def __init__(self, blog_id, tags=None): self.blog_id = blog_id self.tags = [] if tags is None else tags |
三、注意 is 和 == 的区别
>>> a = 256 >>> b = 256 >>> a is b True >>> a = 257 >>> b = 257 >>> a is b False >>> a, b = 257, 257 >>> a is b True |
is
和 ==
的区别:
is
运算符检查两个运算对象是否引用自同一对象(即,它检查两个对象的id
是否相同)==
运算符比较两个运算对象的值是否相等
256
是一个已经存在的对象,而257
不是。为什么?
实际编程过程中,-5 到 256 之间的数字经常会用到,Python 为了避免频繁创建和销毁内存,对 -5 到 256 之间的所有整数会预先分配一个全局整数对象数组, 当需要创建一个该范围内的整数时,Python 会直接返回现有对象的引用。其他编程语言(比如 Java)也有类似的缓存机制。
对于字面量字符串也有类似缓存优化,另外也可以手动使用 intern 将字符串缓存 。
四、生成器执行时间的差异
array = [1, 8, 15] g = (x for x in array if array.count(x) > 0) array = [2, 8, 22] |
输出:
>>> print(list(g)) [8] |
原因:
(x for x in array)
返回的是一个 生成器 (而[x for x in array]
返回的是一个list
)- 在一个生成器表达式里,
in
的操作是在声明时求值的,而if
是在运行期求值的 - 所以
x
枚举的是声明时的列表[1, 8, 15]
,而if
判断的是被重新赋值列表[2, 8, 22]
- 对于
[1, 8, 15]
中的每个数,只有 8 在新的array
中个数大于 0,因此生成器只生成 8
五、for 循环中修改循环变量
如下代码,在for
循环体修改循环变量i
的值:
for i in range(4): print(i) i = 10 |
输出:
0 1 2 3 |
原因:
Python 的for
循环机制是每次迭代到下一项的时候都会解包并分配一次;即range(4)
生成的四个值在每次迭代的时候都会解包一次并赋值给i
;所以赋值操作i = 10
对迭代没有影响。
六、一个 += 的谜题
>>> t = (1, 2, [30, 40]) >>> t[2] += [50, 60] |
到底会发生下面 4 种情况中的哪一种?
t
变成(1, 2, [30, 40, 50, 60])
。- 因为
tuple
不支持对它的元素赋值,所以会抛出TypeError
异常。 - 以上两个都不是。
- 1 和 2 都是对的。
你可能会选 2,但答案是 4:
>>> t = (1, 2, [30, 40]) >>> t[2] += [50, 60] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment >>> t (1, 2, [30, 40, 50, 60]) |
s[a] = b
背后的字节码:
>>> dis.dis('s[a] += b') 1 0 LOAD_NAME 0(s) 3 LOAD_NAME 1(a) 6 DUP_TOP_TWO 7 BINARY_SUBSCR ➊ 8 LOAD_NAME 2(b) 11 INPLACE_ADD ➋ 12 ROT_THREE 13 STORE_SUBSCR ➌ 14 LOAD_CONST 0(None) 17 RETURN_VALUE |
- ➊ 将 s [a] 的值存入 TOS(Top Of Stack,栈的顶端)。
- ➋ 计算
TOS += b
,这一步能够完成,是因为 TOS 指向的是一个可变对象。 - ➌
s [a] = TOS
赋值。这一步失败,是因为 s 是不可变的元组。
PS,如果将t[2] += [50, 60]
改成t[2] = t[2] + [50, 60]
又该选哪个答案呢?