我试图弄清楚Python 3(使用CPython作为解释器)如何执行其程序。我发现这些步骤是:
CPython编译器将Python源代码(.py文件)编译为Python字节码(.pyc)文件。在导入任何模块的情况下,将保存.pyc文件,在运行一个main.py Python脚本的情况下,将不保存它们。
Python虚拟机将字节码解释为特定于硬件的机器码。
这里https://stackoverflow.com/a/1732383/8640077的一个很好的回答是,与JVM相比,Python虚拟机运行其字节码需要更长的时间,因为Java字节码包含有关数据类型的信息,而Python虚拟机则逐行解释行,并且必须确定数据类型。
我的问题是Python虚拟机如何确定数据类型,它是在对机器代码的解释期间还是在单独的过程中发生的(例如,将产生另一个中间代码)?
答案 0 :(得分:3)
CPython的动态,运行时分派(与Java的静态,编译时分派相比)只是Java比纯CPython更快的原因之一:Java中存在jit编译,不同的垃圾回收策略,int
,double
之类的本机类型与CPython中不可变数据结构的存在等等。
我以前的superficial experiments曾表明,动态调度仅负责运行的大约30%-您不能用它解释某些幅度因子的速度差异。
为使这个答案不太抽象,让我们看一个例子:
def add(x,y):
return x+y
查看字节码:
import dis
dis.dis(add)
给出:
2 0 LOAD_FAST 0 (x)
2 LOAD_FAST 1 (y)
4 BINARY_ADD
6 RETURN_VALUE
我们可以在字节码的级别上看到x
和y
是整数还是浮点数或其他任何东西都没有区别-解释器不在乎。
Java中的情况完全不同:
int add(int x, int y) {return x+y;}
和
float add(float x, float y) {return x+y;}
将导致完全不同的操作码,并且在编译时会发生调用调度-根据编译时已知的静态类型选择正确的版本。
CPython解释器通常不必知道确切的参数类型:内部有一个基本的“类/接口”(显然,C中没有类,因此它被称为“协议”,但是对于某些人谁知道C ++ / Java“接口”可能是正确的思维模型),就可以从中得出所有其他“类”。此基本“类”称为PyObject
和here is the description of its protocol.。因此,只要该函数是该协议/接口的一部分,CPython解释器就可以调用该函数,而无需知道确切的类型,并且该调用将被分派到正确的实现(很像C ++中的“虚拟”函数)。
在纯Python方面,似乎变量没有类型:
a=1
a="1"
但是,内部a
的类型为PyObject*
,此引用可以绑定到整数(1
)和unicode字符串("1"
)-因为它们都从PyObject
“继承”。
对于上面的示例,CPython解释器有时会尝试找出正确的引用类型-当它看到BINARY_ADD
-操作码时,就会执行following C-code:
case TARGET(BINARY_ADD): {
PyObject *right = POP();
PyObject *left = TOP();
PyObject *sum;
...
if (PyUnicode_CheckExact(left) &&
PyUnicode_CheckExact(right)) {
sum = unicode_concatenate(left, right, f, next_instr);
/* unicode_concatenate consumed the ref to left */
}
else {
sum = PyNumber_Add(left, right);
Py_DECREF(left);
}
Py_DECREF(right);
SET_TOP(sum);
if (sum == NULL)
goto error;
DISPATCH();
}
在这里,解释器查询两个对象是否都是unicode字符串,如果是这种情况,则使用一种特殊方法(可能会更有效,因为事实上它尝试就地更改不可变的unicode-object,请参见{{3 }}),否则工作将分派到PyNumber
-协议。
很显然,在创建对象时,解释器还必须知道确切的类型,例如,对于a="1"
或a=1
使用了不同的“类”-但是我们已经知道,它不是只有一个地方。
因此,解释器会在运行时干预类型,但是大多数时候不必这样做-可以通过动态调度来达到目标。
答案 1 :(得分:0)
Python是根据鸭子输入的原理构建的。即使在运行时也不会进行显式类型检查。例如,
>>> x = 5
>>> y = "5"
>>> '__mul__' in dir(x)
>>> True
>>> '__mul__' in dir(y)
>>> True
>>> type(x)
>>> <class 'int'>
>>> type(y)
>>> <class 'str'>
>>> type(x*y)
>>> <class 'str'>
CPython解释器检查x
和y
是否已定义__mul__
方法,并尝试“使其工作”并返回结果。而且,Python字节码永远不会转换为机器代码。它在CPython解释器内部执行。 JVM和CPython虚拟机之间的主要区别在于,JVM可以随时将Java字节码编译为机器代码以提高性能(JIT编译),而CPython VM仅按原样运行字节码。
答案 2 :(得分:0)
避免理解Python中的“变量”可能对您的理解很有用。与必须将类型与变量,类成员或函数参数关联的静态类型语言相比,Python仅处理对象的“标签”或名称。
在摘要中,
a = "a string"
a = 5 # a number
a = MyClass() # an object of type MyClass
标签a
从来没有类型。它只是在不同时间指向不同对象的名称(实际上,非常相似,也就是其他语言中的“指针”)。另一方面,对象(字符串,数字)始终具有类型。这种类型的性质可能会改变,因为您可以动态更改类的定义,但是它总是可以确定的,即由语言解释器知道。
所以要回答这个问题:Python永远不会确定变量的类型(标签/名称),它仅使用它来引用对象,并且该对象具有类型。