GitHub 上有一个名为《What the f*ck Python!》的项目,这个有趣的项目意在收集 Python 中那些难以理解和反人类直觉的例子以及鲜为人知的功能特性,并尝试讨论这些现象背后真正的原理!
原版地址:https://github.com/satwikkansal/wtfpython
最近,一位名为“暮晨”的贡献者将其翻译成了中文。
中文版地址:https://github.com/leisurelicht/wtfpython-cn
我将所有代码都亲自试过了,加入了一些自己的理解和例子,所以会和原文稍有不同
1. Strings can be tricky sometimes①
>>> a = '!' >>> b = '!' >>> a is b True②
>>> a = 'some_string' >>> id(a) 140420665652016 >>> id('some' + '_' + 'string') # 注意两个的id值是相同的. 140420665652016③
>>> a = 'wtf' >>> b = 'wtf' >>> a is b True >>> a = 'wtf!' >>> b = 'wtf!' >>> a is b False >>> a, b = 'wtf!', 'wtf!' >>> a is b True④
>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa' True >>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa' False说明:
这些行为是由于 Cpython 在编译优化时,某些情况下会尝试使用已经存在的不可变对象而不是每次都创建一个新对象。(这种行为被称作字符串的驻留[string interning])。发生驻留之后, 许多变量可能指向内存中的相同字符串对象从而节省内存。
有一些方法可以用来猜测字符串是否会被驻留:
所有长度为 0 和长度为 1 的字符串都被驻留(①中字符串被驻留)
字符串在编译时被实现('wtf' 将被驻留,但是 ''.join(['w', 't', 'f'] 将不会被驻留)
字符串中只包含字母、数字或下划线时将会驻留,所以 'wtf!' 由于包含 '!' 而未被驻留
当在同一行将 a 和 b 的值设置为 "wtf!" 的时候,Python 解释器会创建一个新对象,然后两个变量同时指向这个对象。如果你在不同的行上进行赋值操作,它就不会“知道”已经有一个 wtf! 对象(因为 "wtf!" 不是按照上面提到的方式被隐式驻留的)。
常量折叠(constant folding)是 Python 中的一种窥孔优化(peephole optimization)技术。这意味着在编译时表达式 'a' * 20 会被替换为 'aaaaaaaaaaaaaaaaaaaa' 以减少运行时的时钟周期。只有长度小于 20 的字符串才会发生常量折叠。(为啥?想象一下由于表达式 'a' * 10 ** 10 而生成的 .pyc 文件的大小)。
如果你在 .py 文件中尝试这个例子,则不会看到相同的行为,因为文件是一次性编译的。
2. Time for some hash brownies! >>> some_dict = {} >>> some_dict[5.5] = "Ruby" >>> some_dict[5.0] = "JavaScript" >>> some_dict[5] = "Python" >>> some_dict[5.5] "Ruby" >>> some_dict[5.0] "Python" >>> some_dict[5] "Python"说明:
Python 字典检查键值是否相等是通过比较哈希值是否相等来确定的。如果两个对象在比较的时候是相等的,那它们的散列值必须相等,否则散列表就不能正常运行了。例如,如果 1 == 1.0 为真,那么 hash(1) == hash(1.0) 必须也为真,但其实两个数字(整数和浮点数)的内部结构是完全不一样的。
Output:
>>> some_func() 'from_finally'说明:
函数的返回值由最后执行的 return 语句决定。由于 finally 子句一定会执行,所以 finally 子句中的 return 将始终是最后执行的语句。
Output:
>>> WTF() == WTF() # 两个不同的对象应该不相等 False >>> WTF() is WTF() # 也不相同 False >>> hash(WTF()) == hash(WTF()) # 哈希值也应该不同 True >>> id(WTF()) == id(WTF()) True说明:
当调用 id 函数时,Python 创建了一个 WTF 类的对象并传给 id 函数,然后 id 函数获取其 id 值(也就是内存地址),然后丢弃该对象,该对象就被销毁了。
当我们连续两次进行这个操作时,Python会将相同的内存地址分配给第二个对象,因为在 CPython 中 id 函数使用对象的内存地址作为对象的id值,所以两个对象的id值是相同的。
综上,对象的 id 值仅仅在对象的生命周期内唯一,在对象被销毁之后或被创建之前,其他对象可以具有相同的id值。
class WTF(object): def __init__(self): print("I") def __del__(self): print("D")Output:
>>> WTF() is WTF() I I D D False >>> id(WTF()) == id(WTF()) I D I D True正如你所看到的,对象销毁的顺序是造成所有不同之处的原因。
5. For what? >>> some_string = "wtf" >>> some_dict = {} >>> for i, some_dict[i] in enumerate(some_string): pass >>> some_dict {0: 'w', 1: 't', 2: 'f'}