Почему я могу использовать переменную в функции до ее определения в Python?

В моем коде ниже я определяю две функции. main и cube . Я хотел, чтобы main было началом моей программы, поэтому я назвал cube внутри main :

 >>> def main(): number = int(input('Enter a number: ')) cubed_number = cube(number) print("The number cubed is: ", cubed_number) >>> def cube(number): return number * number * number >>> 

Но я определяю cube после main , поэтому я думал, что мой код поднимет NameError . Однако, вместо того, чтобы возбуждаемое исключение, Python отлично выполнил мой код:

 >>> main() Enter a number: 5 The number cubed is: 125 >>> 

Что случилось? Почему Python смог запустить мой код, не зная, как еще был определен cube ? Почему не был NameError ?

Даже незнакомец в том, что, когда я пытался делать что-то с классами, Python действительно повышал NameError :

 >>> class Main: cubed_number() Traceback (most recent call last): File "<pyshell#27>", line 1, in <module> class Main: File "<pyshell#27>", line 2, in Main cubed_number() NameError: name 'cubed_number' is not defined >>> 

Что тут происходит?


Примечание. Это не дубликат. Почему я могу вызвать функцию перед ее определением только с предупреждением? , потому что ответы там действительно не объясняют, почему и как это поведение работает в Python. Я создал это Q & A, потому что текущие ответы на такие вопросы различаются по различным вопросам. Я также считаю, что было бы полезно показать, что происходит за кулисами, чтобы это работало. Не стесняйтесь редактировать и улучшать Q & A.

One Solution collect form web for “Почему я могу использовать переменную в функции до ее определения в Python?”

Чтобы понять, что происходит, нужно понять различие, которое Python делает между определением функции и выполнением функции.

Определение против исполнения

Когда Python встречает определение функции, он компилирует эту функцию в объект кода.

Объект кода – это внутренняя структура, используемая Python для хранения байт-кода, связанного с определенным исполняемым блоком кода. Он также содержит другую информацию, на которую Python должен выполнить байт-код, такой как константы и имена локальных переменных. Документация дает гораздо более обширный обзор того, какие объекты кода .

Затем объект кода используется для построения объекта функции . Объект кода объекта объекта затем используется для выполнения функции, когда она позже вызывается. Python не выполнил бы эту функцию, он только скомпилировал бы эту функцию в объект, который может быть использован позже для выполнения. Единственный раз, когда Python выполняет функцию, это когда вызывается функция .

Вот соответствующая часть из документации, в которой упоминается следующее :

Определение функции – исполняемый оператор. Его выполнение связывает имя функции в текущем локальном пространстве имен с объектом функции (оболочкой вокруг исполняемого кода для функции). Этот функциональный объект содержит ссылку на текущее глобальное пространство имен как глобальное пространство имен, которое будет использоваться при вызове функции.

Определение функции не выполняет тело функции; это выполняется только при вызове функции.

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

Вот пример. Мы определяем функцию func которая объединяет две переменные; a и b :

 >>> def func(): ... return a + b 

Как вы можете видеть, Python не вызывает ошибок. Это потому, что он просто скомпилировал func . Он не пытался выполнить функцию, поэтому он не видит, что a и b не определены.

Мы можем дизассемблировать объект кода func и посмотреть, как выглядит байт-код с помощью этого модуля. Это расскажет нам больше о том, что делает Python:

 >>> from dis import dis >>> dis(func) 2 0 LOAD_GLOBAL 0 (a) 2 LOAD_GLOBAL 1 (b) 4 BINARY_ADD 6 RETURN_VALUE 

Python закодировал две команды LOAD_GLOBAL в LOAD_GLOBAL коде. Аргументами команд являются имена переменных a и b соответственно.

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

Посмотрим, что произойдет, когда мы попытаемся выполнить байт-код для func , вызвав его:

 >>> func() Traceback (most recent call last): File "<pyshell#15>", line 1, in <module> func() File "<pyshell#14>", line 2, in func return a + b NameError: name 'a' is not defined 

Как вы можете видеть, Python поднял NameError . Это связано с тем, что он попытался выполнить две команды LOAD_GLOBAL , но обнаружил, что имена, которые не определены в глобальной области.

Теперь давайте посмотрим, что произойдет, если мы определили две переменные a и b перед вызовом func :

 >>> a = 1 >>> b = 2 >>> >>> func() 3 

Причина, по которой это работает, заключается в том, что когда Python выполнял байт-код func , он смог найти глобальные переменные a и b и использовать их для выполнения функции.

То же самое относится и к этому примеру. Когда main был скомпилирован, Python «видел», мы пытались вызвать переменную cube и сгенерировали инструкцию для получения значения cube . Но он не пытался найти вызываемый объект с именем cube до тех пор, пока инструкции не будут выполнены. И к тому времени, когда был запущен main байтовый код (например, main был вызван), была определена функция cube , поэтому Python не вызвал ошибку.

Если мы попытаемся вызвать main до того, как будет определен куб, мы получим ошибку имени по тем же причинам в приведенном выше примере:

 >>> def main(): ... number = int(input('Enter a number: ')) ... cubed_number = cube(number) ... print("The number cubed is: ", cubed_number) ... >>> main() Enter a number: 23 Traceback (most recent call last): File "<pyshell#23>", line 1, in <module> main() File "<pyshell#22>", line 3, in main cubed_number = cube(number) NameError: name 'cube' is not defined 

Что относительно класса?

Python обрабатывает определения классов несколько иначе, чем определения функций.

Когда Python встречает определение класса, он создает объект класса для класса, как и для функции. Однако Python также позволяет классам иметь пространства имен, которые выполняются во время определения класса. Python не ждет выполнения пространства имен классов, поскольку любые переменные, определенные должны принадлежать классу. Таким образом, любые имена, используемые внутри пространства имен классов, должны быть определены для использования во время определения класса.

Документация для определений классов затрагивает следующее :

Затем пакет класса выполняется в новом кадре выполнения (см. Именование и привязка), используя недавно созданное локальное пространство имен и исходное глобальное пространство имен. (Обычно набор содержит в основном определения функций.) Когда пакет класса завершает выполнение, его кадр выполнения отбрасывается, но его локальное пространство имен сохраняется.

Однако это не относится к методам. Python рассматривает неопределенные имена в методах как с функциями, и позволяет использовать их при определении метода:

 >>> class Class: ... def method(self): ... return var ... >>> var = 10 >>> cls = Class() >>> cls.method() 10 
  • Как распечатать код на Python функции на терминал?
  • Язык Python: атрибуты объекта () vs Функция
  • Как тайм-аут функции в python, тайм-аут меньше секунды
  • Альтернативы Python для глобальных переменных
  • Python: получение ссылки на функцию изнутри себя
  • Имя аргумента функции списка Python
  • Назначение оператора печати переменной в функции в Python 2.7
  • Как передать случайную функцию в качестве аргумента?
  • Аргументы функции (например, в Python)
  • Как сравнить функции python с точки зрения производительности?
  • Python: Почему параметры функции int & list обрабатываются по-разному?
  •  
    Interesting Posts for Van-Lav

    Импорт файлов javascript с jinja из статической папки

    Модуль, импортированный несколько раз

    Как сделать кнопки в python / pygame?

    Как преобразовать целочисленный формат даты в YYYYMMDD?

    Проблема с запуском скрипта python как cgi под apache (ubuntu 12)

    Измените количество тиков x-оси на морских участках

    Метрики сходства строк в Python

    Python 3 urllib создает TypeError: данные POST должны быть байтами или итерируемыми байтами. Это не может быть типа str

    Может ли Pandas читать и изменять один рабочий лист Excel (вкладка) без изменения остальной части файла?

    Значение истинности серии неоднозначно. Используйте команды a.empty, a.bool (), a.item (), a.any () или a.all ()

    Почему unicodedata не распознает определенные символы?

    DeadlineExceededErrors с обновляемым токеном доступа GAE / Google API

    Sublime Text Build System с опциями

    Python преобразует список в dict со значением 1 для каждой клавиши

    Каков приоритет оператора при написании двойного неравенства в Python (явно в коде и как это можно переопределить для массивов?)

    Python - лучший язык программирования в мире.