社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

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

Python之美 • 7 年前 • 487 次点击  

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 + X是重新创建一个对象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():

  2.    ...:     global a

  3.    ...:     a += 1

  4.    ...:     return a

  5.    ...:

  6. In [18]: another_func()

  7. Out[18]: 2

使用finally的return

  1. In [19]: def some_func():

  2.    ...:     try:

  3.    ...:         return 'from_try'

  4.    ...:     finally:

  5.    ...:         return 'from_finally'

  6.    ...:

  7. In [20]: some_func()

  8. Out[20]: 'from_finally'

try…finally这种写法里面,finally中的return语句永远是最后一个执行

忽略类范围的名称解析

  1. In [21]: x = 5

  2.    ...: class SomeClass:

  3.    ...:     x = 17

  4.    ...:     y = (x for i in range(10))

  5.    ...:

  6. In [22]: list(SomeClass.y)[0]

  7. Out[22]: 5

  8. In [23]: x = 5

  9.    ...: class SomeClass:

  10.    ...:     x = 17

  11.    ...:     y = [x for i in range(10)]

  12.    ...:

  13. In [24]: SomeClass.y[0]

  14. Out[24]: 5

这是由于类范围的名称解析被忽略了,而生成器有它自己的本地范围,而在Python3中列表解析也有自己的范围,所以x的值是5。不过,第二个例子在Python2中SomeClass.y[0]的值是17。

列表中的布尔值

  1. In [34]: mixed_list = [False, 1.0, "some_string", 3, True, [], False]

  2.    ...: integers_found_so_far = 0

  3.    ...: booleans_found_so_far = 0

  4.    ...:

  5.    ...: for item in mixed_list:

  6.    ...:     if isinstance(item, int):

  7.    ...:         integers_found_so_far += 1

  8.    ...:     elif isinstance(item, bool):

  9.    ...:         booleans_found_so_far += 1

  10.    ...:

  11. In [35]: booleans_found_so_far

  12. Out[35]: 0

  13. In [36]: integers_found_so_far

  14. Out[36]: 4

这是由于布尔也是int的子类:

  1. In [41]: isinstance(True, int)

  2. Out[41]: True

#

  1. In [42]: a, b = a[b ] = {}, 5

  2.    ...:

  3. In [43]: a, b

  4. Out[43]: ({5: ({...}, 5)}, 5)

看起来有点懵吧,我们拆一下:

  1. In [44]: a[b] = {}, 5

  2. In [47]: a, b

  3. Out[47]: ({5: ({}, 5)}, 5)

这样b是5,而a[5]的值是({}, 5),所以a是{5: ({}, 5)。接着看:

  1. In [48]: a[b] = a, b

  2. In [49]: a

  3. Out[49]: {5: ({...}, 5)}

这其实是一个对自己的「自引用」,看个例子:

  1. In [50]: a = {}

  2. In [51]: a[5] = a

  3. In [52]: a

  4. Out[52]: {5: {...}}

  5. In [53]: a[5] == a

  6. Out[53]: True

  7. In [54]: a[5][5][5]

  8. Out[54]: {5: {...}}

看,a[5]就是a,这可以是一个永久循环,Python用...来表示了


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/jv5uVLCne2
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/3346
 
487 次点击