Почему я могу использовать переменную в функции до ее определения в 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 
  • смещение букв с помощью ord и chr
  • Имя аргумента функции списка Python
  • Как получить доступ к переменным, объявленным внутри функций в python
  • Как выполнять одновременно две функции
  • Почему functools.partial не возвращает реальную функцию (и как ее создать)?
  • Поменяйте 2 значения 2 переменных без использования третьей переменной; питон
  • Python сон, не мешая скрипту?
  • Как добавить функцию в openERP 7?
  •  
    Interesting Posts for Van-Lav

    В любое время в Python переходите к интерпретатору

    Tkinter динамически меняет цвет текста повторяющихся строк в текстовом поле

    python open () метод IOError: Нет такого файла или каталога:

    Как получить числовые данные при веб-очистке?

    Как я могу сказать Phusion Passenger, какой питон использовать?

    Пользовательская функция «использования» в argparse?

    boost :: python: компиляция завершается с ошибкой, поскольку конструктор копирования является закрытым

    Как нажимать for-loop до numpy

    Python server «Разрешено только одно использование каждого адреса сокета»

    Категория Pandas groupby, рейтинг, получить максимальную ценность от каждой категории?

    Вызов функции модуля из строки с именем функции

    Ошибка утверждения: Django-rest-Framework

    Новый массив numpy из атрибута объектов в другой массив numpy

    Круглые столбцы в кадре данных панд

    Как я могу остановить запуск python.exe сразу после получения результата?

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