摘要: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 "", line 1, in

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]又该选哪个答案呢?

扩展资料