Как и когда Python определяет тип данных переменной? - PullRequest
0 голосов
/ 23 ноября 2018

Я пытался выяснить, как именно Python 3 (используя CPython в качестве интерпретатора) выполняет свою программу.Я обнаружил, что шаги:

  1. Компиляция исходного кода Python (файл .py) с помощью компилятора CPython в файл байт-кода Python (.pyc).В случае импорта каких-либо модулей файлы .pyc сохраняются, в случае запуска одного сценария Python main.py они не сохраняются.

  2. Интерпретация байт-кода Python Virtual Machine вМашинный код, специфичный для аппаратного обеспечения.

Здесь найден отличный ответ https://stackoverflow.com/a/1732383/8640077 говорит, что виртуальной машине Python требуется больше времени для запуска своего байт-кода по сравнению с JVM, поскольку байт-код java содержит информациюо типах данных, в то время как Python Virtual Machine интерпретирует строки одну за другой и должна определять типы данных.

Мой вопрос: как виртуальная машина Python определяет тип данных и происходит ли это при интерпретации машинного кода или во время отдельного процесса (который, например, приведет к созданию другого промежуточного кода)?

Ответы [ 3 ]

0 голосов
/ 24 ноября 2018

Динамическая диспетчеризация CPython во время выполнения (по сравнению со статической диспетчеризацией Java во время компиляции) является лишь одной из причин того, почему Java быстрее чистого CPython: в Java есть jit-компиляция, другая сборка мусорастратегии, наличие нативных типов, таких как int, double против неизменяемых структур данных в CPython и т. д.

Мои предыдущие поверхностные эксперименты показали, что динамическая диспетчеризация толькона него приходится около 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 целыми числами илиfloat или что-то еще - интерпретатору все равно.

Ситуация в Java совершенно другая:

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

и

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

wouЭто привело бы к совершенно разным кодам операций, и диспетчеризация вызова произошла бы во время компиляции - правильная версия выбирается в зависимости от статических типов, известных во время компиляции.

Довольно часто CPython-интерпретатор не имеетчтобы знать точный тип аргументов: внутренне существует базовый «класс / интерфейс» (очевидно, что в C нет классов, поэтому он называется «протокол», но для тех, кто знает C ++ / Java, «интерфейс», вероятно, является правильнымментальная модель), из которой происходят все остальные «классы».Этот базовый «класс» называется PyObject, а - вот описание его протокола. .Таким образом, пока функция является частью этого протокола / интерфейса, интерпретатор CPython может вызывать ее, не зная точного типа, и вызов будет отправлен в правильную реализацию (во многом как «виртуальные» функции в C ++).

На чисто Python стороне кажется, что переменные не имеют типов:

a=1
a="1"

однако, внутренне a имеет тип - это PyObject*, и эта ссылка может быть связанав целое число (1) и в Unicode-строку ("1") - потому что они оба «наследуют» от PyObject.

Время от времени интерпретатор CPython пытается найти правильноетип ссылки, также для приведенного выше примера - когда он видит BINARY_ADD -опод, выполняется следующий C-код :

    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 ),otherwisРабота отправляется в PyNumber -protocol.

Очевидно, что интерпретатор также должен знать точный тип при создании объекта, например, для a="1" или a=1 различные "классы"используется - но, как мы видели, это не единственное место.

Таким образом, интерпретатор взаимодействует с типами во время выполнения, но большую часть времени ему не нужно это делать - цель можетбыть достигнутым посредством динамической отправки.

0 голосов
/ 26 ноября 2018

Это может быть полезно для вашего понимания, чтобы не думать о «переменных» в Python.По сравнению со статически типизированными языками, которые должны ассоциировать тип с переменной, членом класса или аргументом функции, Python имеет дело только с «метками» или именами объектов.

Так во фрагменте,

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

метка a никогда не имеет типа.Это просто имя, которое указывает на разные объекты в разное время (на самом деле очень похоже на «указатели» в других языках).С другой стороны, объекты (строка, число) всегда имеют тип.Эта природа этого типа может измениться, поскольку вы можете динамически изменять определение класса, но оно всегда будет определяться, то есть известно интерпретатору языка.

Итак, чтобы ответить на вопрос: Python никогда не определяет типпеременной (метка / имя), он использует ее только для ссылки на объект, и этот объект имеет тип.

0 голосов
/ 23 ноября 2018

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 запускает байт-код только таким, какой он есть.

...