#interpreter
#интерпретатор
Вопрос:
Рассмотрим псевдокод:
function test()
print ('First Method')
test()
Когда интерпретатор считывает этот код, он вводит символ ‘test’ в таблицу символов, а затем компилирует функцию test () в байт-код
Когда дело доходит до test() , он сначала ищет test в таблице символов, находит его и выдает код, чтобы поместить этот символ в стек виртуальной машины. Затем он видит () и выдает байт-код ‘call’ . В итоге мы получаем следующую последовательность вызовов
push test
call
Что произойдет, если у нас также будет второй тест, объявленный в тех же сценариях, например:
function test()
print ('First Method')
test()
function test()
print ('Second Method')
Байтовый код:
push test
call
push test <- but which test?
Когда интерпретатор обнаруживает вторую функцию, у нее то же имя, что и у первой функции, что мы делаем? Если мы заменим запись таблицы символов новой тестовой функцией, мы потеряем запись, на которую полагался вызов test() . Я заметил, что интерпретаторы, такие как R и Python, сохраняют вызов исходной тестовой функции.
Мой вопрос в том, как это делается? Единственный способ, о котором я могу думать, это то, что происходит какое-то определение области видимости. Таким образом, первая функция и вызов test() находятся в одной области со своей собственной таблицей символов, а вторая функция находится в отдельной области и имеет свою собственную таблицу символов. Вторая область видимости также должна находиться внутри первой области. Например
function test()
print ('First Method')
test()
x = "This is in the first scope"
function test()
print ('Second Method')
print (x)
Сначала print (x) будет искать символ x в локальной области, а затем во внешней области.
Комментарии:
1. В языках с такой семантикой имена функций являются изменяемыми переменными, а определения функций — присваиваниями. Таким образом, вы бы обработали переопределение так
test
же, как и переназначение любой другой переменной. Кстати, пользователь может также написатьtest = someOtherFunction
или дажеtest = 42
и последующие (но не предыдущие) вызовыtest()
будут затронуты этим назначением (то есть в Python и R — хотите ли вы, чтобы ваш язык вел себя так же, конечно, зависит от вас).2. Согласен, что имена функций действуют как любая другая переменная. Итак, если я переопределю тестовую переменную, объявив вторую функцию с тем же именем, когда байт-код выполняется и сталкивается с первым тестом, этот тест был переназначен второй функции. Но это не то поведение, которое я вижу в python или R.
3.Я не уверен, что вы имеете в виду — почему он должен был быть переназначен второй функции, если вы вызываете
test()
перед указанным переназначением? При запускеx = 42; print(x); x = 23
на Python будет выведено 42, а не 23, верно? Аналогично, если вы пишетеf = lambda: print(42); f(); f = lambda: print(23)
, он все равно будет печатать 42. И если вы замените лямбда-присваивание на adef
, оно все равно будет печатать 42, потому что, по сути, adef
ничем не отличается от назначения лямбда-выражения (за исключением того, что вы не можете записать все как лямбда-выражение в Python).4. Я посмотрел на байт-код python о том, как он обрабатывает функции, и я вижу, что python связывает объект функции с именем символа во время выполнения, используя код операции MAKE_FUNCTION .
5. «Однако, когда он собирает байтовый код, второй f = lambda: print (23) перезапишет запись таблицы символов для первого лямбда» Назначения не должны перезаписывать записи таблицы символов (если бы они это сделали,
x=42; print(x); x=23
пример в конечном итоге напечатал бы 23), они должны переводиться в хранилища, которыевыполняется во время выполнения. Таблица символов просто сообщает нам, где присваивание должно хранить присвоенное значение (и, возможно, другую статическую информацию, такую как типы, в случае статически типизированных языков).