前言
前段时间看到一个GitHub项目叫wtfpython专门收录一些鲜为人知的Python特性,看起来可能比较简单,但是背后的关于Python的原理还是值得深思,而且理解之后也会对你的Python水平有所提高
这是简介
An interesting collection of surprising snippets and lesser-known Python features.
嘿嘿看不懂吧,反正我英语水平一般看的不是很懂,这里还好有大哥整了一版中文版的wtfpython-cn
于是本来打算一天看个几条,但是发现坚持了一两天,flag就倒了,好在灵机一动,觉得拿出来跟大家一起学习可能效果会好点!(可能有人看完了都...)
这里就来记录一下,粗略估计有51个,每次来学习5个,循序渐进(别说我水!)
另外该项目还提供了PDF可供下载,我也下载下来了--传送门
注:以下示例在Python3中运行可以通过
正文
Strings can be tricky sometimes/微妙的字符串 *
先来看几个例子
>>> a = "yuan_shi"
>>> id(a) # id() 函数用于获取对象的内存地址
17240856
>>> id("yuan" + "_" + "shi") # 这里可以发现两个id值是相同的
17240856
>>> a = "yuanshi"
>>> b = "yuanshi"
>>> a is b
True
>>> a = "yuanshi!"
>>> b = "yuanshi!"
>>> a is b
False
>>> a, b = "yuanshi!", "yuanshi!"
>>> a is b
True
>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False
解释:
这些行为是由于 Cpython 在编译优化时, 某些情况下会尝试使用已经存在的不可变对象而不是每次都创建一个新对象. (这种行为被称作字符串的驻留[string interning]
)
发生驻留之后, 许多变量可能指向内存中的相同字符串对象. (从而节省内存)
在上面的代码中, 字符串是隐式驻留的. 何时发生隐式驻留则取决于具体的实现. 这里有一些方法可以用来猜测字符串是否会被驻留:
所有长度为 0 和长度为 1 的字符串都被驻留.
字符串在编译时被实现 (
'yuanshi'
将被驻留, 但是''.join(['y', 'u', 'a', 'n', 's', 'h', 'i']
将不会被驻留)字符串中只包含字母,数字或下划线时将会驻留. 所以
'yuanshi!'
由于包含!
而未被驻留. 可以在这里找到 CPython 对此规则的实现.当在同一行将
a
和b
的值设置为"yuanshi!"
的时候, Python 解释器会创建一个新对象, 然后同时引用第二个变量. 如果你在不同的行上进行赋值操作, 它就不会“知道”已经有一个yuanshi!
对象 (因为"yuanshi!"
不是按照上面提到的方式被隐式驻留的). 它是一种编译器优化, 特别适用于交互式环境.常量折叠(constant folding) 是 Python 中的一种 窥孔优化(peephole optimization) 技术. 这意味着在编译时表达式
'a'*20
会被替换为'aaaaaaaaaaaaaaaaaaaa'
以减少运行时的时钟周期. 只有长度小于 20 的字符串才会发生常量折叠. (为啥? 想象一下由于表达式'a'\*10\*\*10
而生成的.pyc
文件的大小). 相关的源码实现在这里.
Time for some hash brownies!/是时候来点蛋糕了!
hash brownie指一种含有大麻成分的蛋糕, 所以这里是句双关
虽然我并没有看出双关在哪里...
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 字典通过检查键值是否相等和比较哈希值来确定两个键是否相同.
-
具有相同值的不可变对象在Python中始终具有相同的哈希值.
>>> 5 == 5.0 True >>> hash(5) == hash(5.0) True
注意: 具有不同值的对象也可能具有相同的哈希值(哈希冲突).
当执行
some_dict[5] = "Python"
语句时, 因为Python将5
和5.0
识别为some_dict
的同一个键, 所以已有值 "JavaScript" 就被 "Python" 覆盖了.这个 StackOverflow的 回答 漂亮的解释了这背后的基本原理.
Return return everywhere!/到处返回!
def some_func():
try:
return 'from_try'
finally:
return 'from_finally'
输出:
>>> some_func()
'from_finally'
解释:
- 当在 "try...finally" 语句的
try
中执行return
,break
或continue
后,finally
子句依然会执行. - 函数的返回值由最后执行的
return
语句决定. 由于finally
子句一定会执行, 所以finally
子句中的return
将始终是最后执行的语句.
Deep down, we're all the same./本质上,我们都一样.
class WTF:
pass
输出:
>>> 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值.
那为什么
is
操作的结果为False
呢? 让我们看看这段代码.
class WTF(object):
def __init__(self): print("I")
def __del__(self): print("D")
输出:
>>> WTF() is WTF()
I
I
D
D
False
>>> id(WTF()) == id(WTF())
I
D
I
D
True
正如你所看到的, 对象销毁的顺序是造成所有不同之处的原因.
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'}
解释:
Python 语法 中对 for
的定义是:
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
其中 exprlist
指分配目标. 这意味着对可迭代对象中的每一项都会执行类似 {exprlist} = {next_value}
的操作.
一个有趣的例子说明了这一点:
for i in range(4):
print(i)
i = 10
输出:
0
1
2
3
你可曾觉得这个循环只会运行一次?
由于循环在Python中工作方式, 赋值语句
i = 10
并不会影响迭代循环, 在每次迭代开始之前, 迭代器(这里指range(4)
) 生成的下一个元素就被解包并赋值给目标列表的变量(这里指i
)了.在每一次的迭代中,
enumerate(some_string)
函数就生成一个新值i
(计数器增加) 并从some_string
中获取一个字符. 然后将字典some_dict
键i
(刚刚分配的) 的值设为该字符. 本例中循环的展开可以简化为:
>>> i, some_dict[i] = (0, 'w')
>>> i, some_dict[i] = (1, 't')
>>> i, some_dict[i] = (2, 'f')
>>> some_dict
今日份就到这里,之后可能会单独发,也可能是跟在别的文章之后,注意品尝~
print('微信公众号搜索 "猿狮的单身日常" ,Java技术升级、虫师修炼,我们 不见不散!')
print('也可以扫下方二维码哦~')