专栏名称: Python之美
《Python web开发实战》作者的公众号。发现Python之美,主要包含Web开发、Python进阶、架构设计、Python开发招聘信息等方面内容
目录
相关文章推荐
Python开发者  ·  OpenAI ... ·  昨天  
Python开发者  ·  清华大学:DeepSeek + ... ·  昨天  
Python爱好者社区  ·  梁文锋和杨植麟,论文撞车了!! ·  3 天前  
Python爱好者社区  ·  付费上班终于成为了现实。 ·  4 天前  
51好读  ›  专栏  ›  Python之美

wtfPython: 一组有趣的、微妙的、复杂的Python代码片段

Python之美  · 公众号  · Python  · 2017-09-08 23:59

正文

wtfPython就是「What the f*ck Python? 」的意思,这个项目列举了一些代码片段,可能结果和你想到的是不一致的,并且作者会告诉你为什么。本来将展示最有意义的一部分:

混合Tab和空格

  1. def square(x):

  2.    sum_so_far = 0

  3.    for counter in range(x):

  4.        sum_so_far = sum_so_far + x

  5.    return sum_so_far

  6. print(square(10))

结果是10??不是应该100么?

其实这种错误的结果的原因,所有书籍和开发者都说过,就是不要混Tab和空格,源代码你可以看项目中的mixed_tabs_and_spaces.py。

字典键的隐式转换

  1. In [1]: some_dict = {}

  2.   ...: some_dict[5.5] = "Ruby"

  3.   ...: some_dict[5.0] = "JavaScript"

  4.   ...: some_dict[5] = "Python"

  5.   ...:

  6. In [2]: some_dict[5.5]

  7. Out[2]: 'Ruby'

  8. In [3]: some_dict[5.0]

  9. Out[3]: 'Python'

  10. In [4]: some_dict[5]

  11. Out[4]: 'Python'

这样的原因是键被隐式的转换了:

  1. In [5]: hash(5) == hash(5.0)

  2. Out[5]: True

生成器执行时间的差异

  1. In [6]: array = [1, 8, 15]

  2.   ...: g = (x for x in array if array.count(x) > 0)

  3.   ...: array = [2, 8, 22]

  4.   ...:

  5. In [7]: print(list(g))

  6. [8]

这种隐式的非预期结果在实际开发中是可能出现的。原因是in的操作是在申明时求值的,而if是在运行期求值的。

在字典迭代时修改该字典

  1. In [8]: x = {0: None}

  2.   ...:

  3.   ...: for i in x:

  4.   ...:     del x[i]

  5.   ...:     x[i+1] = None

  6.   ...:     print(i)

  7.   ...:

  8. 0

  9. 1

  10. 2

  11. 3

  12. 4

首先说的时候在迭代过程中是不能修改字典的长度的:

  1. In [13]: for i in x:

  2.    ...:     del x[i]

  3.     ...:

  4. ---------------------------------------------------------------------------

  5. RuntimeError                              Traceback (most recent call last)

  6. <ipython-input-13-a5c6e73be64f> in <module>()

  7. ----> 1 for i in x:

  8.      2     del x[i]

  9.      3

  10. RuntimeError : dictionary changed size during iteration

但是删掉一个添加一个是可以的。运行了5次才结束是因为字典会定期重新设置以便接受更多的键,但是和项目中的运行8次是不一样的。

在列表迭代时删除条目

  1. In [14]: list_1 = [1, 2, 3, 4]

  2.    ...: list_2 = [1, 2, 3, 4]

  3.    ...: list_3 = [1, 2, 3, 4]

  4.    ...: list_4 = [1, 2, 3, 4]

  5.    ...:

  6.    ...: for idx, item in enumerate(list_1):

  7.    ...:     del item

  8.    ...:

  9.    ...: for idx, item in enumerate(list_2):

  10.    ...:     list_2.remove(item)

  11.     ...:

  12.    ...: for idx, item in enumerate(list_3[:]):

  13.    ...:     list_3.remove(item)

  14.    ...:

  15.    ...: for idx, item in enumerate(list_4):

  16.    ...:     list_4.pop(idx)

  17.    ...:

  18. In [15]: list_1, list_2

  19. Out[15]: ([1, 2, 3, 4], [2, 4])

  20. In [16]: list_3, list_4

  21. Out[16]: ([], [2, 4])

其中只有list_3是正确的行为。但是为什么会出现[2, 4]的结果呢?第一次删掉了index是0的1,就剩[2, 3, 4],然后移除index 1, 就是3,剩下了[2, 4],但是现在只有2个元素,循环就结束了。

is

  1. >>> a = 256

  2. >>> b = 256

  3. >>> a is b

  4. True

  5. >>> a = 257

  6. >>> b = 257

  7. >>> a is b

  8. False

  9. >>> a = 257; b = 257

  10. >>> a is b

  11. True

is 用来对比身份,而 == 用来对比值。通常is为True,==就是True,但是反之不一定:

  1. >>> [] == []

  2. True

  3. >>> [] is [] # 2个列表使用了不同的内存位置

  4. False

上面的例子中,-5 - 256由于太经常使用,所以设计成固定存在的对象:

  1. >>> id(256)

  2. 10922528

  3. >>> a = 256

  4. >>> b = 256

  5. >>> id(a)

  6. 10922528

  7. >>> id(b)

  8. 10922528

  9. >>> id(257)

  10. 140084850247312

  11. >>> x = 257

  12. >>> y = 257

  13. >>> id(x)

  14. 140084850247440

  15. >>> id(y)

  16. 140084850247344

is not ... 和 is (not ...)

  1. >>> 'something' is not None

  2. True

  3. >>> 'something' is (not None)

  4. False

其中(not None)优先执行,最后其实变成了 'something' is False

循环中的函数也会输出到相同的输出

  1. In [17]: funcs = []

  2.    ...: results = []

  3.    ...: for x in range(7):

  4.     ...:     def some_func():

  5.    ...:         return x

  6.    ...:     funcs.append(some_func)

  7.    ...:     results.append(some_func())

  8.    ...:

  9.     ...: funcs_results = [func() for func in funcs]

  10.    ...:

  11. In [18]: results, funcs_results

  12. Out[18]: ([0, 1, 2, 3, 4, 5, 6], [6, 6, 6, 6, 6, 6, 6])

我之前在Expert-Python( https://github.com/dongweiming/Expert-Python ) 这个PPT中介绍过「开发陷阱,闭包变量绑定」,其实这个例子就是因为这个问题。解决方法就是把循环的变量传到some_func里面去:

  1. In [19]: funcs = []

  2.    ...: for x in range(7):

  3.     ...:     def some_func(x=x):

  4.    ...:         return x

  5.    ...:     funcs.append(some_func)

  6.    ...:

  7. In [20]: [func() for func in funcs]

  8. Out[20]: [0, 1, 2, 3, 4, 5, 6]

循环中的局部变量泄露

  1. >>> x = 1

  2. >>> print([x for x in range(5)])

  3. [0, 1, 2, 3, 4]

  4. >>> print(x, ': x in global')

  5. ( 4, ': x in global')

在Python 2中x的值在一个循环执行之后被改变了。不过再Python 3这个问题解决了。

可变默认参数

  1. In [1]: def some_func(default_arg=[]):

  2.   ...:     default_arg.append("some_string")

  3.   ...:     return default_arg

  4.   ...:

  5. In [2]: some_func()

  6. Out [2]: ['some_string']

  7. In [3]: some_func()

  8. Out[3]: ['some_string', 'some_string']

  9. In [4]: some_func([])

  10. Out[4]: ['some_string']

  11. In [5]: some_func()

  12. Out[5]: ['some_string', 'some_string', 'some_string']

Expert-Python( https://github.com/dongweiming/Expert-Python ) 这个PPT中同样介绍过。Python是引用传递,上面例子的参数是一个列表,它所指向的对象可以被修改。通用的解决办法是在函数内判断:

  1. def some_func(default_arg=None):

  2.    if not default_arg:

  3.        default_arg = []

  4.    default_arg .append("some_string")

  5.    return default_arg

+ 和 +=的差别

  1. >>> a = [1, 2, 3, 4]

  2. >>> b = a

  3. >>> a = a + [5, 6, 7, 8]

  4. >>> a, b

  5. ([1, 2, 3, 4, 5, 6, 7, 8], [1, 2, 3, 4])

  6. >>> a = [1, 2, 3, 4]

  7. >>> b = a

  8. >>> a += [5, 6, 7, 8]

  9. >>> a, b

  10. ([1, 2, 3, 4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7, 8])

通常的运算过程,区别就是a = a + X 和 a += X。这是因为 a = a + X 是重新创建一个对象a,而 a += X 是在a这个list上面做extend操作。

元组赋值

  1. In [6]: another_tuple = ([1, 2], [3, 4], [5, 6])

  2.   ...:

  3. In [7]: another_tuple[2].append(1000)

  4. In [8]: another_tuple

  5. Out[8]: ([1, 2], [3, 4], [5, 6, 1000])

  6. In [9]: another_tuple[2] += [99, 999]

  7. ---------------------------------------------------------------------------

  8. TypeError                                 Traceback (most recent call last)

  9. <ipython-input-9-d07c65f24a63> in <module>()

  10. ----> 1 another_tuple[2] += [99, 999]

  11. TypeError: 'tuple' object does not support item assignment

  12. In [10]: another_tuple

  13. Out[10]: ([ 1, 2], [3, 4], [5, 6, 1000, 99, 999])

在我们的印象里面元组是不可变的呀?其实我之前还专门写过一篇 Python元组的赋值谜题 讲这个问题,简单的说对list的赋值成功了,但是赋值失败了,不过由于值是引用的,所以才会出现这个执行失败实际成功的效果。

使用在范围内未定义的变量

  1. In [11]: a = 1

  2.    ...: def some_func():

  3.     ...:     return a

  4.    ...:

  5.    ...: def another_func():

  6.     ...:     a += 1

  7.    ...:     return a

  8.     ...:

  9. In [12]: some_func()

  10. Out [12]: 1

  11. In [13]: another_func()

  12. ---------------------------------------------------------------------------

  13. UnboundLocalError                         Traceback (most recent call last)

  14. < ipython-input-13-703bd168975f> in <module>()

  15. ----> 1 another_func()

  16. <ipython-input-11-cff7ceae4600> in another_func()

  17.       4

  18.      5 def another_func():

  19. ----> 6     a += 1

  20.      7     return a

  21. UnboundLocalError : local variable 'a' referenced before assignment

这是由于在another_func中的赋值操作会把a变成一个本地变量,但是在相同范围内并没有初始化它。如果希望它能正确运行可以加global:

  1. In [17]: def another_func







请到「今天看啥」查看全文