wtfPython 一组有趣的、微妙的、复杂的Python代码片段
wtfPython就是「What the f*ck Python? 」的意思,这个项目列举了一些代码片段,可能结果和你想到的是不一致的,并且作者会告诉你为什么。本来将展示最有意义的一部分:
混合Tab和空格
def square(x): sum_so_far = 0 for counter in range(x): sum_so_far = sum_so_far + x return sum_so_far print(square(10))
结果是10??不是应该100么?
其实这种错误的结果的原因,所有书籍和开发者都说过,就是不要混Tab和空格,源代码你可以看项目中的mixed_tabs_and_spaces.py。
字典键的隐式转换
In [1]: some_dict = {} ...: some_dict[5.5] = "Ruby" ...: some_dict[5.0] = "JavaScript" ...: some_dict[5] = "Python" ...: In [2]: some_dict[5.5] Out[2]: 'Ruby' In [3]: some_dict[5.0] Out[3]: 'Python' In [4]: some_dict[5] Out[4]: 'Python'
这样的原因是键被隐式的转换了:
In [5]: hash(5) == hash(5.0) Out[5]: True
生成器执行时间的差异
In [6]: array = [1, 8, 15] ...: g = (x for x in array if array.count(x) > 0) ...: array = [2, 8, 22] ...: In [7]: print(list(g)) [8]
这种隐式的非预期结果在实际开发中是可能出现的。原因是in的操作是在申明时求值的,而if是在运行期求值的。
在字典迭代时修改该字典
In [8]: x = {0: None} ...: ...: for i in x: ...: del x[i] ...: x[i+1] = None ...: print(i) ...: 0 1 2 3 4
首先说的时候在迭代过程中是不能修改字典的长度的:
In [13]: for i in x: ...: del x[i] ...: --------------------------------------------------------------------------- RuntimeError Traceback (most recent call last)in () ----> 1 for i in x: 2 del x[i] 3 RuntimeError: dictionary changed size during iteration
但是删掉一个添加一个是可以的。运行了5次才结束是因为字典会定期重新设置以便接受更多的键,但是和项目中的运行8次是不一样的。
在列表迭代时删除条目
In [14]: list_1 = [1, 2, 3, 4] ...: list_2 = [1, 2, 3, 4] ...: list_3 = [1, 2, 3, 4] ...: list_4 = [1, 2, 3, 4] ...: ...: for idx, item in enumerate(list_1): ...: del item ...: ...: for idx, item in enumerate(list_2): ...: list_2.remove(item) ...: ...: for idx, item in enumerate(list_3[:]): ...: list_3.remove(item) ...: ...: for idx, item in enumerate(list_4): ...: list_4.pop(idx) ...: In [15]: list_1, list_2 Out[15]: ([1, 2, 3, 4], [2, 4]) In [16]: list_3, list_4 Out[16]: ([], [2, 4])
其中只有list_3是正确的行为。但是为什么会出现[2, 4]的结果呢?第一次删掉了index是0的1,就剩[2, 3, 4],然后移除index 1, 就是3,剩下了[2, 4],但是现在只有2个元素,循环就结束了。
is
>>> a = 256 >>> b = 256 >>> a is b True >>> a = 257 >>> b = 257 >>> a is b False >>> a = 257; b = 257 >>> a is b True
is用来对比身份,而 ==用来对比值。通常is为True,==就是True,但是反之不一定:
>>> [] == [] True >>> [] is [] # 2个列表使用了不同的内存位置 False
上面的例子中,-5 – 256由于太经常使用,所以设计成固定存在的对象:
>>> id(256) 10922528 >>> a = 256 >>> b = 256 >>> id(a) 10922528 >>> id(b) 10922528 >>> id(257) 140084850247312 >>> x = 257 >>> y = 257 >>> id(x) 140084850247440 >>> id(y) 140084850247344
is not … 和 is (not …)
>>> 'something' is not None True >>> 'something' is (not None) False
其中(not None)优先执行,最后其实变成了 ‘something’ is False
循环中的函数也会输出到相同的输出
In [17]: funcs = [] ...: results = [] ...: for x in range(7): ...: def some_func(): ...: return x ...: funcs.append(some_func) ...: results.append(some_func()) ...: ...: funcs_results = [func() for func in funcs] ...: In [18]: results, funcs_results 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里面去:
In [19]: funcs = [] ...: for x in range(7): ...: def some_func(x=x): ...: return x ...: funcs.append(some_func) ...: In [20]: [func() for func in funcs] Out[20]: [0, 1, 2, 3, 4, 5, 6]
循环中的局部变量泄露
>>> x = 1 >>> print([x for x in range(5)]) [0, 1, 2, 3, 4] >>> print(x, ': x in global') (4, ': x in global')
在Python 2中x的值在一个循环执行之后被改变了。不过再Python 3这个问题解决了。
可变默认参数
In [1]: def some_func(default_arg=[]): ...: default_arg.append("some_string") ...: return default_arg ...: In [2]: some_func() Out[2]: ['some_string'] In [3]: some_func() Out[3]: ['some_string', 'some_string'] In [4]: some_func([]) Out[4]: ['some_string'] In [5]: some_func() Out[5]: ['some_string', 'some_string', 'some_string']
在Expert-Python(https://github.com/dongweiming/Expert-Python)这个PPT中同样介绍过。Python是引用传递,上面例子的参数是一个列表,它所指向的对象可以被修改。通用的解决办法是在函数内判断:
def some_func(default_arg=None): if not default_arg: default_arg = [] default_arg.append("some_string") return default_arg
+ 和 +=的差别
>>> a = [1, 2, 3, 4] >>> b = a >>> a = a + [5, 6, 7, 8] >>> a, b ([1, 2, 3, 4, 5, 6, 7, 8], [1, 2, 3, 4]) >>> a = [1, 2, 3, 4] >>> b = a >>> a += [5, 6, 7, 8] >>> a, b ([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操作。
元组赋值
In [6]: another_tuple = ([1, 2], [3, 4], [5, 6]) ...: In [7]: another_tuple[2].append(1000) In [8]: another_tuple Out[8]: ([1, 2], [3, 4], [5, 6, 1000]) In [9]: another_tuple[2] += [99, 999] --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-9-d07c65f24a63> in <module>() ----> 1 another_tuple[2] += [99, 999] TypeError: 'tuple' object does not support item assignment In [10]: another_tuple Out[10]: ([1, 2], [3, 4], [5, 6, 1000, 99, 999])
在我们的印象里面元组是不可变的呀?其实我之前还专门写过一篇Python元组的赋值谜题讲这个问题,简单的说对list的赋值成功了,但是赋值失败了,不过由于值是引用的,所以才会出现这个执行失败实际成功的效果。
使用在范围内未定义的变量
In [11]: a = 1 ...: def some_func(): ...: return a ...: ...: def another_func(): ...: a += 1 ...: return a ...: In [12]: some_func() Out[12]: 1 In [13]: another_func() --------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) <ipython-input-13-703bd168975f> in <module>() ----> 1 another_func() <ipython-input-11-cff7ceae4600> in another_func() 4 5 def another_func(): ----> 6 a += 1 7 return a UnboundLocalError: local variable 'a' referenced before assignment
这是由于在another_func中的赋值操作会把a变成一个本地变量,但是在相同范围内并没有初始化它。如果希望它能正确运行可以加global:
In [17]: def another_func(): ...: global a ...: a += 1 ...: return a ...: In [18]: another_func() Out[18]: 2
使用finally的return
In [19]: def some_func(): ...: try: ...: return 'from_try' ...: finally: ...: return 'from_finally' ...: In [20]: some_func() Out[20]: 'from_finally'
try…finally这种写法里面,finally中的return语句永远是最后一个执行
忽略类范围的名称解析
In [21]: x = 5 ...: class SomeClass: ...: x = 17 ...: y = (x for i in range(10)) ...: In [22]: list(SomeClass.y)[0] Out[22]: 5 In [23]: x = 5 ...: class SomeClass: ...: x = 17 ...: y = [x for i in range(10)] ...: In [24]: SomeClass.y[0] Out[24]: 5
这是由于类范围的名称解析被忽略了,而生成器有它自己的本地范围,而在Python3中列表解析也有自己的范围,所以x的值是5。不过,第二个例子在Python2中SomeClass.y[0]的值是17。
列表中的布尔值
In [34]: mixed_list = [False, 1.0, "some_string", 3, True, [], False] ...: integers_found_so_far = 0 ...: booleans_found_so_far = 0 ...: ...: for item in mixed_list: ...: if isinstance(item, int): ...: integers_found_so_far += 1 ...: elif isinstance(item, bool): ...: booleans_found_so_far += 1 ...: In [35]: booleans_found_so_far Out[35]: 0 In [36]: integers_found_so_far Out[36]: 4
这是由于布尔也是int的子类:
In [41]: isinstance(True, int) Out[41]: True
#
In [42]: a, b = a[b] = {}, 5 ...: In [43]: a, b Out[43]: ({5: ({...}, 5)}, 5)
看起来有点懵吧,我们拆一下:
In [44]: a[b] = {}, 5 In [47]: a, b Out[47]: ({5: ({}, 5)}, 5)
这样b是5,而a[5]的值是({}, 5),所以a是{5: ({}, 5)。接着看:
In [48]: a[b] = a, b In [49]: a Out[49]: {5: ({...}, 5)}
这其实是一个对自己的「自引用」,看个例子:
In [50]: a = {} In [51]: a[5] = a In [52]: a Out[52]: {5: {...}} In [53]: a[5] == a Out[53]: True In [54]: a[5][5][5] Out[54]: {5: {...}}
看,a[5]就是a,这可以是一个永久循环,Python用…来表示了
董伟明 Python之美
相关推荐
-
【python】99%的人做过这道题都哭了,我试过,是真的 python基础
2019-8-25
-
Pyhton介绍、发展趋势、安装 python基础
2019-9-17
-
初识分布式计算:从MapReduce到Yarn&Fuxi python基础
2019-5-15
-
设计模式之策略模式和状态模式(strategy pattern & state pattern) python基础
2019-9-2
-
OpenCV中几何形状识别与测量 python基础
2019-9-14
-
亲和性分析在商品推荐中的应用 python基础
2019-8-25
-
Mac OS下 Anaconda Python2 和 Python3 配置 python基础
2019-8-25
-
TensorFlow端到端旋风教程 python基础
2019-8-29
-
Kaggle HousePrice : LB 0.11666(排名前15%), 用搭积木的方式(1.原理) python基础
2019-2-22
-
进程和线程是什么? python基础
2019-8-29