【问题标题】:How and when does Python determine the data type of a variable?Python 如何以及何时确定变量的数据类型?
【发布时间】:2019-04-26 05:01:19
【问题描述】:

我试图弄清楚 Python 3(使用 CPython 作为解释器)是如何执行其程序的。我发现步骤是:

  1. 由 CPython 编译器将 Python 源代码(.py 文件)编译为 Python 字节码 (.pyc) 文件。在导入任何模块的情况下,会保存 .pyc 文件,在运行一个 main.py Python 脚本的情况下,它们不会被保存。

  2. Python 虚拟机将字节码解释为特定于硬件的机器码。

在这里找到的一个很好的答案 https://stackoverflow.com/a/1732383/8640077 说,与 JVM 相比,Python 虚拟机运行其字节码需要更长的时间,因为 Java 字节码包含有关数据类型的信息,而 Python 虚拟机会逐行解释,并且必须确定数据类型。

我的问题是 Python 虚拟机如何确定数据类型,它是在解释机器代码期间还是在单独的过程(例如,会产生另一个中间代码)期间发生的?

【问题讨论】:

  • 为什么你认为 Python 需要“确定数据类型”? Python 是一种动态类型语言;只有在您特别询问时才会检查类型,并且可以在变量的整个生命周期内非常持续地进行检查。我非常怀疑 Python 和 Java 之间的执行时间差异是由于运行时类型检查造成的。
  • 所以即使在从字节码到机器码的翻译过程中,Python 也不知道变量的类型? AD 2:那么 Python 和 Java 在执行时间上的最大差异是什么?
  • 不行,怎么可能?程序本身可以随时改变变量的类型,这就是动态类型的意思。此代码完全合法:a = 'mystring'; a = MyClassThatIsNotAString()a的类型是什么?
  • 我在寻找您问题的答案时找到了this resource。也许你会发现它很有用。
  • this article我还没有完全看懂,但它要短得多,我想它回答了你的问题。

标签: python python-3.x virtual-machine cpython


【解决方案1】:

CPython 的动态、运行时分派(与 Java 的静态、编译时分派相比)只是 Java 比纯 CPython 更快的原因之一:Java 中有 jit-compilation,不同的垃圾收集策略,诸如 intdouble 等原生类型的存在与 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

我们可以看到在字节码级别上,xy 是整数还是浮点数或其他东西没有区别 - 解释器不在乎。

Java 中的情况完全不同:

int add(int x, int y) {return x+y;}

float add(float x, float y) {return x+y;}

将导致完全不同的操作码,调用调度将在编译时发生 - 根据编译时已知的静态类型选择正确的版本。

CPython 解释器通常不需要知道参数的确切类型:内部有一个基本的“类/接口”(显然 C 中没有类,所以它被称为“协议”,但对于某些人来说谁知道 C++/Java“接口”可能是正确的心智模型),所有其他“类”都是从它派生的。这个基“类”称为PyObjecthere is the description of its protocol.。因此,只要该函数是该协议/接口的一部分,CPython 解释器就可以调用它,而无需知道确切的类型,并且调用将被分派到正确的实现(很像 C++ 中的“虚拟”函数)。

在纯 Python 方面,变量似乎没有类型:

a=1
a="1"

然而,a 内部有一个类型 - 它是 PyObject* 并且此引用可以绑定到整数 (1) 和 unicode 字符串 ("1") - 因为它们都“继承”来自PyObject

CPython 解释器有时会尝试找出正确的引用类型,对于上面的示例也是如此 - 当它看到 BINARY_ADD-opcode 时,会执行 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 对象,请参阅SO-answer ) 使用,否则工作分派到PyNumber-protocol。

显然,解释器还必须知道创建对象时的确切类型,例如a="1"a=1 使用了不同的“类”——但正如我们所见,它不是唯一的一个地方。

所以解释器会在运行时干扰类型,但大多数时候它不必这样做——可以通过动态调度来达到目标​​。

【讨论】:

    【解决方案2】:

    避免在 Python 中考虑“变量”可能有助于您的理解。与必须将类型与变量、类成员或函数参数相关联的静态类型语言相比,Python 只处理对象的“标签”或名称。

    所以在sn-p中,

    a = "a string"
    a = 5 # a number
    a = MyClass() # an object of type MyClass
    

    标签a 从来没有类型。它只是一个在不同时间指向不同对象的名称(实际上非​​常相似,在其他语言中指向“指针”)。另一方面,对象(字符串、数字)总是有一个类型。这种类型的性质可能会改变,因为您可以动态更改类的定义,但它始终是确定的,即由语言解释器知道。

    所以回答这个问题:Python 从不确定变量的类型(标签/名称),它只使用它来引用一个对象并且该对象具有类型。

    【讨论】:

      【解决方案3】:

      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 解释器检查 xy 是否定义了 __mul__ 方法,并尝试“使其工作”并返回结果。此外,Python 字节码永远不会被翻译成机器码。它在 CPython 解释器中执行。 JVM 和 CPython 虚拟机之间的一个主要区别是,JVM 可以随时将 Java 字节码编译为机器码以获得性能提升(JIT 编译),而 CPython 虚拟机只按原样运行字节码。

      【讨论】:

      • 你的意思是“Python 字节码永远不会被翻译成机器码。它会在 CPython 解释器中执行。”。你能详细说明一下吗?
      • 机器码通常是指你的电脑可以执行的代码。例如,当您在计算机中编译 C++ 程序时,它会被编译为特定于您计算机 CPU 架构的机器代码。您的 CPU 理解这些指令并可以运行它们。所以在某种程度上,你的 CPU 就是这里的解释器。只需将 Python 字节码视为 CPython 虚拟机的机器代码。它们只是 CPython 虚拟机的指令。 CPython 虚拟机可以运行这些指令,而无需将它们翻译成其他东西
      猜你喜欢
      • 1970-01-01
      • 2010-09-28
      • 1970-01-01
      • 1970-01-01
      • 2017-01-05
      • 2014-09-15
      • 1970-01-01
      • 2021-03-04
      • 1970-01-01
      相关资源
      最近更新 更多