Python有什么不为人知的坑?

Python日志

共 5094字,需浏览 11分钟

 · 2021-05-28

Python有什么不为人知的坑?
TO MY CHILDREN
一、首先,一些关于字符串的“基本”操作

1.


  1. >>> a = "some_string"

  2. >>> id(a)

  3. 140420665652016

  4. >>> id("some" + "_" + "string") # 注意两个的id值是相同的.

  5. 140420665652016

2.


  1. >>> a = "wtf"

  2. >>> b = "wtf"

  3. >>> a is b

  4. True

  5. >>> a = "wtf!"

  6. >>> b = "wtf!"

  7. >>> a is b

  8. False

  9. >>> a, b = "wtf!", "wtf!"

  10. >>> a is b

  11. True # 3.7 版本返回结果为 False.

3.


  1. >>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'

  2. True

  3. >>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'

  4. False # 3.7 版本返回结果为 True
说明:

这些行为是由于 Cpython 在编译优化时, 某些情况下会尝试使用已经存在的不可变对象而不是每次都创建一个新对象. (这种行为被称作字符串的驻留[string interning])

发生驻留之后, 许多变量可能指向内存中的相同字符串对象. (从而节省内存)

在上面的代码中, 字符串是隐式驻留的. 何时发生隐式驻留则取决于具体的实现. 这里有一些方法可以用来猜测字符串是否会被驻留:所有长度为 0 和长度为 1 的字符串都被驻留.

字符串在编译时被实现 ('wtf' 将被驻留, 但是 ''.join(['w', 't', 'f']) 将不会被驻留)

字符串中只包含字母,数字或下划线时将会驻留. 所以 'wtf!' 由于包含 ! 而未被驻留.

当在同一行将 a 和 b 的值设置为 "wtf!" 的时候, Python 解释器会创建一个新对象, 然后同时引用第二个变量(译: 仅适用于3.7以下, 详细情况请看这里). 如果你在不同的行上进行赋值操作, 它就不会“知道”已经有一个 wtf! 对象 (因为 "wtf!" 不是按照上面提到的方式被隐式驻留的). 它是一种编译器优化, 特别适用于交互式环境.

常量折叠(constant folding) 是 Python 中的一种窥孔优化(peephole optimization)技术. 这意味着在编译时表达式 'a'*20 会被替换为 'aaaaaaaaaaaaaaaaaaaa' 以减少运行时的时钟周期. 只有长度小于 20 的字符串才会发生常量折叠.

二、关于函数的返回值


  1. def some_func():

  2. try:

  3. return 'from_try'

  4. finally:

  5. return 'from_finally'

输出:


  1. >>> some_func()

  2. 'from_finally'
说明:

当在 "try...finally" 语句的 try 中执行 return, break 或 continue 后, finally 子句依然会执行.

函数的返回值由最后执行的 return 语句决定. 由于 finally 子句一定会执行, 所以 finally 子句中的 return 将始终是最后执行的语句.

三、关于类的本质


  1. class WTF:

  2. pass

输出:


  1. >>> WTF() == WTF() # 两个不同的对象应该不相等

  2. False

  3. >>> WTF() is WTF() # 也不相同

  4. False

  5. >>> hash(WTF()) == hash(WTF()) # 哈希值也应该不同

  6. True

  7. >>> id(WTF()) == id(WTF())

  8. True
说明:

当调用 id 函数时, Python 创建了一个 WTF 类的对象并传给 id 函数. 然后 id 函数获取其id值 (也就是内存地址), 然后丢弃该对象. 该对象就被销毁了.

当我们连续两次进行这个操作时, Python会将相同的内存地址分配给第二个对象. 因为 (在CPython中) id 函数使用对象的内存地址作为对象的id值, 所以两个对象的id值是相同的.

综上, 对象的id值仅仅在对象的生命周期内唯一. 在对象被销毁之后, 或被创建之前, 其他对象可以具有相同的id值.

那为什么 is 操作的结果为 False 呢? 这是由对象销毁的顺序造成的.

四、你了解Python中的for循环语句吗


  1. for i in range(4):

  2. print(i)

  3. i = 10

输出:


  1. 0

  2. 1

  3. 2

  4. 3
说明:

由于循环在Python中工作方式, 赋值语句 i = 10 并不会影响迭代循环, 在每次迭代开始之前, 迭代器(这里指 range(4)) 生成的下一个元素就被解包并赋值给目标列表的变量(这里指 i)了.


五、“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 运算符检查两个运算对象是否引用自同一对象 (即, 它检查两个运算对象是否相同).

== 运算符比较两个运算对象的值是否相等.

因此 is 代表引用相同, == 代表值相等. 还有一个例子可以用来说明这一点——


  1. >>> [] == []

  2. True

  3. >>> [] is [] # 这两个空列表位于不同的内存地址.

  4. False
256 是一个已经存在的对象, 而 257 不是

当你启动Python 的时候, 数值为 -5 到 256 的对象就已经被分配好了. 这些数字因为经常被使用, 所以会被提前准备好.

Python 通过这种创建小整数池的方式来避免小整数频繁的申请和销毁内存空间.

六、is not … is not is (not …) 你在说绕口令吗?


  1. >>> 'something' is not None

  2. True

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

  4. False
说明:

is not 是个单独的二元运算符, 与分别使用 is 和 not 不同.

如果操作符两侧的变量指向同一个对象, 则 is not 的结果为 False, 否则结果为 True.

七、三个引号


  1. >>> print('wtfpython''')

  2. wtfpython

  3. >>> print("wtfpython""")

  4. wtfpython

  5. >>> # 下面的语句会抛出 `SyntaxError` 异常

  6. >>> # print('''wtfpython')

  7. >>> # print("""wtfpython")
说明:

Python 提供隐式的字符串连接, 例如:

>>> print("wtf" "python")

wtfpython

>>> print("wtf" "") # or "wtf"""

wtf

''' 和 """ 在 Python中也是字符串定界符, Python 解释器在先遇到三个引号的的时候会尝试再寻找三个终止引号作为定界符, 如果不存在则会导致 SyntaxError 异常.


八、假作真时真亦假
TO MY CHILDREN

  1. True = False

  2. if True == False:

  3. print("I've lost faith in truth!")

输出:

I've lost faith in truth!
说明:

最初, Python 并没有 bool 型 (人们用0表示假值, 用非零值比如1作为真值). 后来他们添加了 True, False, 和 bool 型, 但是, 为了向后兼容, 他们没法把 True 和 False 设置为常量, 只是设置成了内置变量.

Python 3 由于不再需要向后兼容, 终于可以修复这个问题了, 所以这个例子无法在 Python 3.x 中执行!


九、骗过你的眼睛
TO MY CHILDREN

  1. >>> value = 11

  2. >>> valuе = 32

  3. >>> value

  4. 11
说明:

一些非西方字符虽然看起来和英语字母相同, 但会被解释器识别为不同的字母.


  1. >>> ord('е') # 西里尔语的 'e' (Ye)

  2. 1077

  3. >>> ord('e') # 拉丁语的 'e', 用于英文并使用标准键盘输入

  4. 101

  5. >>> 'е' == 'e'

  6. False

  7. >>> value = 42 # 拉丁语 e

  8. >>> valuе = 23 # 西里尔语 'e', Python 2.x 的解释器在这会抛出 `SyntaxError` 异常

  9. >>> value

  10. 42
十、奇怪的加号

1.


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

  2. b = a

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

输出:


  1. >>> a

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

  3. >>> b

  4. [1, 2, 3, 4]

2.


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

  2. b = a

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

输出:


  1. >>> a

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

  3. >>> b

  4. [1, 2, 3, 4, 5, 6, 7, 8]
说明:

a += b 并不总是与 a = a + b 表现相同. 类实现 op= 运算符的方式 也许 是不同的, 列表就是这样做的.

表达式 a = a + [5,6,7,8] 会生成一个新列表, 并让 a 引用这个新列表, 同时保持 b 不变.

表达式 a += [5,6,7,8] 实际上是使用的是 "extend" 函数, 所以 a 和 b 仍然指向已被修改的同一列表.

十一、最后,再来一条超极机密

import antigravity

别问,自己试一试就知道了!

一键,三连


有一种幸福叫关注~
那是肯定的表现~



浏览 12
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报