摘要:常用的模块方法接收参数为一个代码块可以是模块,类,方法,函数,或者是对象,可以得到这个代码块对应的字节码指令序列。具体验证可以分析下两种方法的字节码指令。
交换两个变量的值,大家最常见的写法是这样的:
>>> temp = x >>> x = y >>> y = temp
但其实更 Pythonic 的写法是这样的:
>>> x, y = y, x
大家有没有想过为什么在 Python 中可以这样交换两个变量的值?
Python 代码是先解释(这里的解释是相对编译而言的,Python 不同与 C/C++ 之类的编译型语言,是需要从源文件编译成机器指令)成 Python 字节码(byte code, .pyc文件主要是用来存储这些字节码的)之后,再由 Python 解释器来执行这些字节码的。一般来说,Python 语句会对应若干字节码指令,Python 的字节码类似于汇编指令的中间语言,但是一个字节码并不只是对应一个机器指定。
内置模块 dis 可以用来分析字节码。DOC
The dis module supports the analysis of CPython bytecode by disassembling it. The CPython bytecode which this module takes as an input is defined in the file Include/opcode.h and used by the compiler and the interpreter.
常用的 dis 模块方法: dis.dis([bytesource])
dis.dis([bytesource])
Disassemble the bytesource object. bytesource can denote either a module, a class, a method, a function, or a code object. For a module, it disassembles all functions. For a class, it disassembles all methods. For a single code sequence, it prints one line per bytecode instruction. If no object is provided, it disassembles the last traceback.
dis.dis 接收参数为一个代码块(可以是模块,类,方法,函数,或者是对象),可以得到这个代码块对应的字节码指令序列。
>>> import dis >>> def test(): ... a = 1 ... ... >>> dis.dis(test) 3 0 LOAD_CONST 1 (1) 3 STORE_FAST 0 (a) 6 LOAD_CONST 0 (None) 9 RETURN_VALUE
输出的格式分别是:行号,地址,指令,操作参数, 参数解释(识别变量名称,常量值等)
切入正题, 我们直接来看下第二种写法的字节码指令:
swap_2.py
x = 1 y = 3 x, y = y, x
python -m dis swap_2.py
1 0 LOAD_CONST 0 (1) 3 STORE_NAME 0 (x) 2 6 LOAD_CONST 1 (3) 9 STORE_NAME 1 (y) 3 12 LOAD_NAME 1 (y) 15 LOAD_NAME 0 (x) 18 ROT_TWO 19 STORE_NAME 0 (x) 22 STORE_NAME 1 (y) 25 LOAD_CONST 2 (None) 28 RETURN_VALUE
部分字节码指令如下,具体的指令请移步官网:
LOAD_CONST(consti)
Pushes co_consts[consti] onto the stack.
STORE_NAME(namei)
Implements name = TOS. namei is the index of name in the attribute co_names of the code object. The compiler tries to use STORE_FAST or STORE_GLOBAL if possible.
LOAD_NAME(namei)
Pushes the value associated with co_names[namei] onto the stack.
ROT_TWO()
Swaps the two top-most stack items.
解释下上面的字节码指令:
第一行执行两个字节码指令, 分别是LOAD_CONST 和 STORE_NAME,执行的动作是将 co_consts[0] 压栈(也就是常量表的第一个常量,整数1压入栈中),然后获取co_names[0]的变量名x(变量名表的第一个名字),栈顶元素(整数1)出栈和co_names[0]存储到f->f_locals。
第二行的执行方式如同第一行。
co_consts[0] = 1 co_names[0] = x f->f_locals["x"] = 1 co_consts[1] = 3 co_names[1] = y f->f_locals["y"] = 3
重点在第三行,前两行的计算顺序都是从友往左进行的(一般情况下, Python 表达式的计算顺序是从左到右,但是在表达式赋值的时候,表达式右边的操作数优先于左边),也就是说,第四行是这样执行的,先创建元组(y, x),执行的动作是两个 LOAD_NAME,会依次搜索local,global,builtin名字空间中的co_names[1](对应变量名y)和co_names[0](对应变量名x)并把相对应的值压栈。接下去执行的动作是交换ROT_TWO, 交换栈顶的两个元素位置。
从下一个执行指令就可以看出来,先获取co_names[0]的变量名x,栈顶元素(现在是原先y的值)出栈并储存,两次存储就实现了交换两个变量的值。
第二种方法不借助任何中间变量并且能够获得更好的性能。我们可以简单测试下:
>>> from timeit import Timer >>> Timer("temp = x;x = y;y = temp", "x=2;y=3").timeit() 0.030814170837402344 >>> Timer("x, y = y, x", "x=2;y=3").timeit() 0.027340173721313477
为什么第二种方法消耗的时间更少呢?可以猜测一下,是中间变量赋值引起的耗时。具体验证可以分析下两种方法的字节码指令。
Life such short,be Pythonic .
Blog : JunNplus
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/37631.html
摘要:本次分享讲简单聊聊函数的用法,希望能给读者一点启示和参考。在最后的语句中,我们给出了的值,并且值重复,函数接收后面一个值,且值传递不起作用,因此输出结果为本次分享到此结束。本文介绍了几个函数使用的例子,希望能抛砖引玉,也欢迎大家多多交流 在Python中,exec()是一个十分有趣且实用的内置函数,不同于eval()函数只能执行计算数学表达式的结果的功能,exec()能够动态地执行...
摘要:而引用类型值是指那些保存堆内存中的对象,意思是变量中保存的实际上只是一个指针,这个指针指向内存中的另一个位置,该位置保存对象。而堆内存主要负责对象这种变量类型的存储。我们需要明确一点,深拷贝与浅拷贝的概念只存在于引用类型。 深拷贝和浅拷贝 说起深拷贝和浅拷贝,首先我们来看两个栗子 // 栗子1 var a = 1,b=a; console.log(a); console.log(b) ...
摘要:返回值是一个经过排序的可迭代类型,与一样。注一般来说,和可以使用表达式。与的不同在于,是在原位重新排列列表,而是产生一个新的列表。 我们需要对List进行排序,Python提供了两个方法 对给定的List L进行排序,方法1.用List的成员函数sort进行排序方法2.用built-in函数sorted进行排序(从2.4开始) ----------------------------...
摘要:但是实际写程序中,我们经常会写出许多繁杂的丑陋的代码。特别推荐,许多代码让我获益匪浅,比如这里对的使用。用可以写出很简单直观的代码,如下当然,上面不考虑效率,这里有一个利用分治法思想的高效的方法。更多文章更多阅读中参数的用法高级编程技巧 用 Python 时间也算不短了,但总感觉自己在用写 C++ 代码的思维写 Python,没有真正用到其作为脚本语言的优势。之前刷 LeetCode ...
阅读 1292·2021-11-16 11:44
阅读 3758·2021-10-09 10:01
阅读 1744·2021-09-24 10:31
阅读 3832·2021-09-04 16:41
阅读 2510·2021-08-09 13:45
阅读 1209·2019-08-30 14:08
阅读 1775·2019-08-29 18:32
阅读 1640·2019-08-26 12:12