共计 4377 个字符,预计需要花费 11 分钟才能阅读完成。
相比于子弹工夫和火爆场景,我更喜爱《黑客帝国》故事背景的假如 —— 人们相熟的世界是虚构的,是机器给人大脑输出的幻象,而幻象是不完满的,存在一些不合乎自然规律的中央,这些中央或多或少的展现了幻象世界的漏洞和真实世界的样子,如果你看过《黑客帝国》动画版《超过极限》和《世界纪录》,将会有更粗浅的感触
咱们相熟的、赖以生存的 Python 世界,其实也是个虚构的,这个虚拟世界展现给咱们无比灿烂的场景和性能的同时,也存在一些超乎常理和认知的中央,明天就带你一起探寻一些那些超自然点,以及它背地的真实世界
神奇的海象操作符
海象操作符 := 是 Python3.8 引入的一个新个性,意为节俭一次长期变量的赋值操作,例如:
a = [1,2,3,4,5]
n = len(a)
if n > 4:
print(n)
意思是,如果列表 a 的长度大于 4,则打印 a 的长度,为了防止对列表长度的两次求解,利用变量 n 存储 a 的长度,荒诞不经
如果用 海象操作符(:=),会是这样:
a = [1,2,3,4,5]
if n := len(n) > 4:
print(n)
能够看到,省去了长期变量 n 的定义,通过海象操作符,两全其美
不得不说,Python 为能让咱们提高效率,真是处心积虑,刚刚公布的 正式版 Python3.9,也是为晋升效率做了多处改善
海象的表演
不过,看上面的代码
>>> a = "wtf_walrus"
>>> a
'wtf_walrus'
>>> a := "wtf_walrus" # 报错!File "<stdin>", line 1
a := 'wtf_walrus'
^
SyntaxError: invalid syntax
>>> (a := "wtf_walrus") # 奇观产生,居然通过了!'wtf_walrus'
>>> a
'wtf_walrus'
再来一段
>>> a = 6, 9 # 元组赋值
>>> a # 后果失常
(6, 9)
>>> (a := 6, 9) # 海象赋值,表达式后果失常
(6, 9)
>>> a # 长期变量居然不同
6
>>> a, b = 6, 9 # 解包赋值
>>> a, b
(6, 9)
>>> (a, b = 16, 19) # Oh no!File "<stdin>", line 1
(a, b = 6, 9)
^
SyntaxError: invalid syntax
>>> (a, b := 16, 19) # 这里居然打印出三员元组!(6, 16, 19)
>>> a # 问题是 a 居然没变
6
>>> b
16
解密海象
- 非括号表达式的海象赋值操作海象操作符(:=)实用于一个表达式外部的作用域,没有括号,相当于全局作业域,是会受到编译器限度的
- 括号里的赋值操作相应的,赋值操作符(=)不能放在括号里,因为它须要在全局作用域中执行,而非在一个表达式内
- 海象操作符的实质海象操作符的语法模式为 Name := expr,Name 为失常的标识符,expr 为失常的表达式,因而可迭代的 装包 和 解包 体现的后果会和冀望不同
(a := 6, 9) 实际上会被解析为 ((a := 6), 9),最终,a 的值为 6,验证一下:
>>> (a := 6, 9) == ((a := 6), 9)
True
>>> x = (a := 696, 9)
>>> x
(696, 9)
>>> x[0] is a # 援用了雷同的值
True
同样的,(a, b := 16, 19) 相当于 (a, (b := 16), 19,原来如此
不安分的字符串
先建设一个感知意识
>>> a = "some_string"
>>> id(a)
140420665652016
>>> id("some" + "_" + "string") # 不同形式创立的字符串本质是一样的.
140420665652016
奇异的事件行将产生
>>> a = "wtf"
>>> b = "wtf"
>>> a is b
True
>>> a = "wtf!"
>>> b = "wtf!"
>>> a is b # 什么鬼!False
如果将这段代码写入脚本文件,用 Python 运行,后果却是对的:
a = "wtf!"
b = "wtf!"
print(a is b) # 将打印出 True
还有更神奇的,在 Python3.7 之前的版本中,会有上面的景象:
>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False
20 个字符 a 组合起来等于 20 个 a 的字符串,而 21 个就不相等
揭秘字符串
计算机世界里,任何奇异的景象都有其必然起因
这些字符串行为,是因为 Cpython 在编译优化时,某些状况下会对不可变的对象做存储,新建时之间建设援用,而不是创立新的,这种技术被称作 字符串驻留(string interning),这样做能够节俭内存和提高效率
下面代码中,字符串被隐式驻留了,什么状况下才会被驻留呢?
- 所有长度为 0 和 1 的字符串都会被驻留
- 字符串在编译时被驻留,运算中不会(”wtf” 会驻留,””.join(“w”, “t”, “f”) 则不会)
- 只有蕴含了字母、数值和下划线的字符串才会被驻留,这就是为什么 “wtf!” 不会被驻留的起因(其中还有字符 !)
如果 a 和 b 的赋值 “wtf!” 语句在同一行,Python 解释器会创立一个对象,而后让两个变量指向这个对象。如果在不同行,解释器就不晓得曾经有了 “wtf!” 对象,所以会创立新的(起因是 “wtf!” 不会被驻留)
像 IPython 这样的交互环境中,语句是单行执行的,而脚本文件中,代码是被同时编译的,具备雷同的编译环境,所以就能了解,代码文件中不同行的不被驻留字符串援用同一个对象的景象
常量折叠(constant folding)是 Python 的一个优化技术:窥孔优化(peephole optimization),如 a = “a”20,这样的语句会在编译时开展,以缩小运行时的运行耗费,为了不至于让 pyc 文件过大,将开展字符限度在 20 个以内,不然想想这个 “a”10**100 语句将产生多大的 pyc 文件
留神 Python3.7 当前的版本中 常量折叠 的问题失去了改善,不过还不分明具体起因(矩阵变的更简单了)
小心链式操作
来一段骚操作
>>> (False == False) in [False] # 合乎常理
False
>>> False == (False in [False]) # 也没问题
False
>>> False == False in [False] # 当初感觉如何?
True
>>> True is False == False
False
>>> False is False is False
True
>>> 1 > 0 < 1
True
>>> (1 > 0) < 1
False
>>> 1 > (0 < 1)
False
不知到你看到这段代码的感触,反正我看第一次到时,狐疑我学的 Python 是混充的~
到底产生了什么
依照 Python 官网文档,表达式章节,值比拟大节的形容:
通常状况下,如果 a、b、c、…、y、z 是表达式,op1、op2、…、opN 是比拟运算符,那么 a op1 b op2 c … y opN z 等价于 a op1 b and b op2 c and … y opN z,除了每个表达式只被计算一次的个性
基于以上认知,咱们从新扫视一下下面看起来让人蛊惑的语句:
- False is False is False 等价于 (False is False) and (False is False)
- True is False == False 等价于 True is False and False == False,当初能够看出,第一局部 True is False 的求值为 False,所以整个表达式的值为 False
- 1 > 0 < 1 等价于 1 > 0 and 0 < 1,所以表达式求值为 True
- 表达式 (1 > 0) < 1 等价于 True < 1,另外 int(True) 的值为 1,且 True + 1 的值为 2,那么 1 < 1 就是 False 了
到底 is(是) 也不 is(是)
我间接被上面的代码惊到了
>>> a = 256
>>> b = 256
>>> a is b
True
>>> a = 257
>>> b = 257
>>> a is b
False
再来一个
>>> a = []
>>> b = []
>>> a is b
False
>>> a = tuple()
>>> b = tuple()
>>> a is b
True
同样是数字,同样是对象,待遇咋就不一样尼……
咱们逐个了解下
is 和 == 的区别
- is 操作符用于查看两个变量援用的是否同一个对象实例
- == 操作符用于查看两个变量援用对象的值是否相等
- 所以 is 用于援用比拟,== 用于值比拟,上面的代码能更分明的阐明这一点:
>>> class A: pass
>>> A() is A() # 因为两个对象实例在不同的内存空间里,所以表达式值为 False
False
256 是既存对象,而 257 不是
这个小标题让人很无语,还的确对象和对象不一样
在 Python 里 从 -5 到 256 范畴的数值,是事后初始化好的,如果值为这个范畴内的数字,会间接建设援用,否则就会创立
这就解释了为啥 同样的 257,内存对象不同的景象了
为什么要这样做?官网的解释为,这个范畴的数值比拟罕用
不可变的空元组
和 -5 到 256 数值事后创立一样,对于不可变的对象,Python 解释器也做了事后创立,例如 对空的 Tuple 对象
这就能解释为什么 空列表对象之间援用不同,而空元组之间的援用的确雷同的景象了
被吞噬的 Javascript
先看看过程
some_dict = {}
some_dict[5.5] = "Ruby"
some_dict[5.0] = "JavaScript"
some_dict[5] = "Python"
print(some_dict[5.5]) # Ruby
print(some_dict[5.0]) # Python Javascript 去哪了?
背地的起因
Python 字典对象的索引,是通过键值是否相等和比拟键的哈希值来进行查找的
具备雷同值的不可变对象的哈希值是雷同的
>>> 5 == 5.0
True
>>> hash(5) == hash(5.0)
True
于是咱们就能了解,当执行 some_dict[5] = “Python” 时,会笼罩掉后面定义的 some_dict[5.0] = “Javascript”,因为 5 和 5.0 具备雷同的哈希值
须要留神的是:有可能不同的值具备雷同的哈希值,这种景象被称作 哈希抵触
对于字典对象应用哈希值作为索引运算的更深层次的起因,有趣味的同学能够参考 StackOverflow 上的答复,解释的很精彩,网址是:https://stackoverflow.com/que…
总结
限于篇幅(精力)起因,明天就介绍这几个 Python 宇宙中的异常现象,更多的异常现象,收录在 satwikkansal 的 wtfpython 中(https://github.com/satwikkans…)。
任何富丽美妙的背地都是各种智慧、技巧、斗争、辛苦的撑持,不是有那么一句话嘛:如果你感觉轻松自如,比方有人在负重前行。而这个人就是咱们青睐的 Python 及其 编译器,要更好的了解一个货色,须要理解它背地的概念原理和理念,冀望通过这篇短文对你有所启发
以上就是本次分享的所有内容,想要理解更多 python 常识欢送返回公众号:Python 编程学习圈,发送“J”即可收费获取,每日干货分享