В компьютерном программировании область действия привязки имени ( ассоциация имени с сущностью, такой как переменная ) — это часть программы , где привязка имени допустима; то есть, где имя может использоваться для ссылки на сущность. В других частях программы имя может ссылаться на другую сущность (оно может иметь другую привязку) или вообще ни на что не ссылаться (оно может быть несвязанным). Область действия помогает предотвратить конфликты имен , позволяя одному и тому же имени ссылаться на разные объекты — при условии, что имена имеют отдельные области действия. Область действия привязки имени также известна как видимость сущности , особенно в старой или более технической литературе — это относится к упоминаемой сущности, а не к ссылающемуся имени.
Термин «область действия» также используется для обозначения набора всех привязок имен, которые действительны в пределах части программы или в заданной точке программы, которую правильнее называть контекстом или средой . [a]
Строго говоря [b] и на практике для большинства языков программирования «часть программы» относится к части исходного кода (области текста) и известна как лексическая область видимости . В некоторых языках, однако, «часть программы» относится к части времени выполнения (период во время выполнения ) и известна как динамическая область видимости . Оба эти термина несколько вводят в заблуждение — они неправильно используют технические термины, как обсуждалось в определении, — но само различие является точным и точным, и это стандартные соответствующие термины. Лексическая область видимости является основным предметом этой статьи, при этом динамическая область видимости понимается в отличие от лексической области видимости.
В большинстве случаев разрешение имен на основе лексической области действия относительно просто в использовании и реализации, поскольку при использовании можно читать в обратном порядке исходный код, чтобы определить, к какой сущности относится имя, а при реализации можно поддерживать список имен и контекстов при компиляции или интерпретации программы. Трудности возникают при маскировке имен , предварительных объявлениях и подъеме , в то время как значительно более тонкие возникают с нелокальными переменными , особенно в замыканиях .
Строгое определение (лексической) "области действия" имени ( идентификатора ) однозначно: лексическая область действия - это "часть исходного кода, в которой применяется привязка имени к сущности". Это практически не отличается от определения 1960 года в спецификации ALGOL 60. Ниже приведены представительные спецификации языка:
Чаще всего «область действия» относится к ситуации, когда заданное имя может ссылаться на заданную переменную (когда объявление имеет эффект), но может также применяться к другим сущностям, таким как функции, типы, классы, метки , константы и перечисления.
Фундаментальное различие в области действия заключается в том, что означает «часть программы». В языках с лексической областью действия (также называемой статической областью действия ) разрешение имени зависит от местоположения в исходном коде и лексического контекста (также называемого статическим контекстом ), который определяется тем, где определена именованная переменная или функция. Напротив, в языках с динамической областью действия разрешение имени зависит от состояния программы , когда встречается имя, которое определяется контекстом выполнения (также называемым контекстом времени выполнения , контекстом вызова или динамическим контекстом ). На практике с лексической областью действия имя разрешается путем поиска в локальном лексическом контексте, а затем, если это не удается, путем поиска во внешнем лексическом контексте и т. д.; тогда как с динамической областью действия имя разрешается путем поиска в локальном контексте выполнения, а затем, если это не удается, путем поиска во внешнем контексте выполнения и т. д., продвигаясь вверх по стеку вызовов. [4]
Большинство современных языков используют лексическую область видимости для переменных и функций, хотя в некоторых языках используется динамическая область видимости, в частности, в некоторых диалектах Lisp, некоторых «скриптовых» языках и некоторых языках шаблонов . [c] Perl 5 предлагает как лексическую, так и динамическую область видимости. Даже в языках с лексической областью видимости область видимости замыканий может сбивать с толку непосвященных, [ требуется цитата ], поскольку они зависят от лексического контекста, в котором определяется замыкание, а не от того, где оно вызывается.
Лексическое разрешение может быть определено во время компиляции и также известно как раннее связывание , в то время как динамическое разрешение в общем случае может быть определено только во время выполнения и, таким образом, известно как позднее связывание .
В объектно-ориентированном программировании динамическая диспетчеризация выбирает метод объекта во время выполнения, хотя то, выполняется ли фактическое связывание имени во время компиляции или во время выполнения, зависит от языка. Фактически динамическая область действия распространена в макроязыках , которые не выполняют разрешение имен напрямую, а вместо этого расширяются на месте.
Некоторые программные фреймворки, такие как AngularJS, используют термин «область действия» для обозначения чего-то совершенно иного, чем то, как он используется в этой статье. В этих фреймворках область действия — это просто объект языка программирования, который они используют ( JavaScript в случае AngularJS), который определенным образом используется фреймворком для эмуляции динамической области действия в языке, который использует лексическую область действия для своих переменных. Эти области действия AngularJS сами по себе могут находиться в контексте или не находиться в контексте (используя обычное значение термина) в любой заданной части программы, следуя обычным правилам области действия переменных языка, как и любой другой объект, и используя свои собственные правила наследования и трансклюзии . В контексте AngularJS иногда используется термин «$scope» (со знаком доллара), чтобы избежать путаницы, но использование знака доллара в именах переменных часто не приветствуется руководствами по стилю. [5]
Область действия является важным компонентом разрешения имен , [d], которое, в свою очередь, является основополагающим для семантики языка . Разрешение имен (включая область действия) различается между языками программирования, а в пределах языка программирования различается в зависимости от типа сущности; правила для области действия называются правилами области действия (или правилами области действия ). Вместе с пространствами имен правила области действия имеют решающее значение в модульном программировании , поэтому изменение в одной части программы не нарушает несвязанную часть.
При обсуждении области действия существуют три основных понятия: область действия, степень и контекст. В частности, часто путают «область действия» и «контекст»: область действия — это свойство привязки имени, в то время как контекст — это свойство части программы, которая является либо частью исходного кода ( лексический контекст или статический контекст ), либо частью времени выполнения ( контекст выполнения, контекст времени выполнения, контекст вызова или динамический контекст ). Контекст выполнения состоит из лексического контекста (в текущей точке выполнения) и дополнительного состояния времени выполнения, такого как стек вызовов . [e] Строго говоря, во время выполнения программа входит и выходит из областей действия различных привязок имен, и в какой-то момент выполнения привязки имен находятся «в контексте» или «не в контексте», поэтому привязки имен «входят в контекст» или «выходят из контекста», когда выполнение программы входит в область действия или выходит из нее. [f] Однако на практике использование гораздо свободнее.
Область действия — это концепция уровня исходного кода и свойство привязок имен, в частности привязок имен переменных или функций (имена в исходном коде являются ссылками на сущности в программе) и является частью поведения компилятора или интерпретатора языка. Таким образом, проблемы области действия аналогичны указателям , которые являются типом ссылки, используемым в программах в более общем смысле. Использование значения переменной, когда имя находится в контексте, но переменная не инициализирована, аналогично разыменованию (доступу к значению) дикого указателя , поскольку он не определен. Однако, поскольку переменные не уничтожаются, пока не выйдут из контекста, аналога висячего указателя не существует.
Для таких сущностей, как переменные, область действия является подмножеством времени жизни (также известного как протяженность ) — имя может ссылаться только на переменную, которая существует (возможно, с неопределенным значением), но существующие переменные не обязательно видны: переменная может существовать, но быть недоступной (значение хранится, но не ссылается на него в данном контексте), или доступной, но не через заданное имя, в этом случае она не находится в контексте (программа находится «вне области действия имени»). В других случаях «время жизни» не имеет значения — метка (именованная позиция в исходном коде) имеет время жизни, идентичное программе (для статически скомпилированных языков), но может находиться в контексте или нет в заданной точке программы, и аналогично для статических переменных — статическая глобальная переменная находится в контексте для всей программы, в то время как статическая локальная переменная находится только в контексте внутри функции или другого локального контекста, но обе имеют время жизни для всего выполнения программы.
Определение сущности, к которой относится имя, известно как разрешение имен или связывание имен (особенно в объектно-ориентированном программировании ) и различается в зависимости от языка. При наличии имени язык (точнее, компилятор или интерпретатор) проверяет все сущности, которые находятся в контексте, на совпадения; в случае неоднозначности (две сущности с одинаковым именем, например, глобальная и локальная переменная с одинаковым именем) правила разрешения имен используются для их различения. Чаще всего разрешение имен опирается на правило «внутреннего-внешнего контекста», например, правило Python LEGB (локальный, охватывающий, глобальный, встроенный): имена неявно разрешаются в наиболее узкий релевантный контекст. В некоторых случаях разрешение имен может быть явно указано, например, с помощью ключевых слов global
и nonlocal
в Python; в других случаях правила по умолчанию не могут быть переопределены.
Когда два одинаковых имени находятся в контексте одновременно, ссылаясь на разные сущности, говорят, что происходит маскировка имен , где имя с более высоким приоритетом (обычно самое внутреннее) «маскирует» имя с более низким приоритетом. На уровне переменных это известно как затенение переменных . Из-за возможности возникновения логических ошибок из-за маскировки некоторые языки запрещают или препятствуют маскировке, выдавая ошибку или предупреждение во время компиляции или выполнения.
Различные языки программирования имеют различные правила области действия для различных видов объявлений и имен. Такие правила области действия оказывают большое влияние на семантику языка и, следовательно, на поведение и корректность программ. В таких языках, как C++ , доступ к несвязанной переменной не имеет четко определенной семантики и может привести к неопределенному поведению , подобному ссылке на висячий указатель ; а объявления или имена, используемые за пределами их области действия, будут генерировать синтаксические ошибки .
Области действия часто связаны с другими языковыми конструкциями и определяются неявно, но многие языки также предлагают конструкции, специально предназначенные для управления областью действия.
Область действия может варьироваться от одного выражения до всей программы, с множеством возможных градаций между ними. Простейшее правило области действия — глобальная область действия — все сущности видны во всей программе. Самое базовое правило модульной области действия — двухуровневая область действия с глобальной областью действия в любом месте программы и локальной областью действия внутри функции. Более сложное модульное программирование допускает отдельную область действия модуля, где имена видны внутри модуля (частные для модуля), но не видны за его пределами. В пределах функции некоторые языки, такие как C, позволяют области действия блока ограничивать область действия подмножеством функции; другие, в частности функциональные языки, позволяют области действия выражения ограничивать область действия одним выражением. Другие области действия включают область действия файла (в частности, в C), которая ведет себя аналогично области действия модуля, и область действия блока за пределами функций (в частности, в Perl).
Тонкая проблема заключается в том, когда именно начинается и заканчивается область действия. В некоторых языках, таких как C, область действия имени начинается с объявления имени, и, таким образом, разные имена, объявленные в данном блоке, могут иметь разные области действия. Это требует объявления функций перед использованием, хотя не обязательно их определения, и требует предварительного объявления в некоторых случаях, особенно для взаимной рекурсии. В других языках, таких как Python, область действия имени начинается с начала соответствующего блока, где объявлено имя (например, начало функции), независимо от того, где оно определено, поэтому все имена в данном блоке имеют одну и ту же область действия. В JavaScript область действия имени, объявленного с помощью let
или , const
начинается с объявления имени, а область действия имени, объявленного с помощью , var
начинается с начала функции, где объявлено имя, что известно как подъем переменной . Поведение имен в контексте, которые имеют неопределенное значение, отличается: в Python использование неопределенных имен приводит к ошибке времени выполнения, в то время как в JavaScript неопределенные имена, объявленные с помощью , var
можно использовать во всей функции, поскольку они неявно привязаны к значению undefined
.
Областью действия привязки имени является выражение , которое известно как область действия выражения . Область действия выражения доступна во многих языках, особенно в функциональных языках, которые предлагают функцию, называемую выражениями let, позволяющую области действия объявления быть одним выражением. Это удобно, если, например, для вычисления требуется промежуточное значение. Например, в Standard ML , если f()
возвращает 12
, то это выражение, которое вычисляется как , используя временную переменную с именем, чтобы избежать двойного вызова. Некоторые языки с областью действия блока приближаются к этой функциональности, предлагая синтаксис для блока, который должен быть встроен в выражение; например, вышеупомянутое выражение Standard ML может быть записано в Perl как , или в GNU C как .let val x = f() in x * x end
144
x
f()
do { my $x = f(); $x * $x }
({ int x = f(); x * x; })
В Python вспомогательные переменные в выражениях генератора и списковых генераторах (в Python 3) имеют область действия выражения.
В языке C имена переменных в прототипе функции имеют область действия выражения, известную в этом контексте как область действия протокола функции . Поскольку имена переменных в прототипе не упоминаются (они могут отличаться в фактическом определении) — они просто пустышки — их часто опускают, хотя они могут использоваться, например, для создания документации.
Областью действия привязки имени является блок , который известен как область действия блока . Область действия блока доступна во многих, но не во всех, языках программирования с блочной структурой. Это началось с ALGOL 60 , где "[e]very Declaration ... действителен только для этого блока.", [6] и сегодня особенно ассоциируется с языками семейств и традиций Pascal и C. Чаще всего этот блок содержится внутри функции, тем самым ограничивая область действия частью функции, но в некоторых случаях, таких как Perl, блок может не находиться внутри функции.
unsigned int sum_of_squares ( const unsigned int N ) { unsigned int ret = 0 ; for ( unsigned int n = 1 ; n <= N ; n ++ ) { const unsigned int n_squared = n * n ; ret += n_squared ; } return ret ; }
Типичным примером использования области действия блока является показанный здесь код C, в котором две переменные ограничены циклом: переменная цикла n , которая инициализируется один раз и увеличивается на каждой итерации цикла, и вспомогательная переменная n_squared , которая инициализируется на каждой итерации. Цель состоит в том, чтобы избежать добавления в область действия функции переменных, которые имеют отношение только к определенному блоку, например, это предотвращает ошибки, когда общая переменная цикла i случайно уже была установлена в другое значение. В этом примере выражение, n * n
как правило, не будет назначено вспомогательной переменной, а тело цикла будет просто записано, ret += n * n
но в более сложных примерах вспомогательные переменные полезны.
Блоки в основном используются для управления потоком, например, с циклами if, while и for, и в этих случаях область действия блока означает, что область действия переменной зависит от структуры потока выполнения функции. Однако языки с областью действия блока обычно также позволяют использовать «голые» блоки, единственной целью которых является обеспечение тонкого управления областью действия переменной. Например, вспомогательная переменная может быть определена в блоке, затем использована (например, добавлена к переменной с областью действия функции) и отброшена по завершении блока, или цикл while может быть заключен в блок, который инициализирует переменные, используемые внутри цикла, который должен быть инициализирован только один раз.
Тонкостью нескольких языков программирования, таких как Algol 68 и C (продемонстрированной в этом примере и стандартизированной с C99 ), является то, что переменные области действия блока могут быть объявлены не только внутри тела блока, но и внутри оператора управления, если таковой имеется. Это аналогично параметрам функции, которые объявляются в объявлении функции (до начала блока тела функции) и в области действия для всего тела функции. Это в основном используется в циклах for , которые имеют оператор инициализации, отдельный от условия цикла, в отличие от циклов while, и является распространенной идиомой.
Область действия блока может использоваться для затенения. В этом примере внутри блока вспомогательная переменная также могла бы называться n , затеняя имя параметра, но это считается плохим стилем из-за возможности возникновения ошибок. Кроме того, некоторые потомки C, такие как Java и C#, несмотря на поддержку области действия блока (в том смысле, что локальная переменная может выйти из контекста до окончания функции), не позволяют одной локальной переменной скрывать другую. В таких языках попытка объявления второго n приведет к синтаксической ошибке, и одну из n переменных придется переименовать.
Если блок используется для установки значения переменной, область действия блока требует, чтобы переменная была объявлена вне блока. Это усложняет использование условных операторов с одиночным присваиванием . Например, в Python, который не использует область действия блока, можно инициализировать переменную следующим образом:
если с : а = "foo" иначе : а = ""
где a
доступен после if
оператора.
В Perl, который имеет область действия блока, для этого требуется объявить переменную перед блоком:
мой $a ; если ( c ) { $a = 'foo' ; } иначе { $a = '' ; }
Часто это переписывается с использованием множественного присваивания, инициализируя переменную значением по умолчанию. В Python (где это не обязательно) это будет выглядеть так:
а = "" если с : а = "foo"
в то время как в Perl это будет выглядеть так:
мой $a = '' ; если ( c ) { $a = 'foo' ; }
В случае назначения одной переменной альтернативой является использование тернарного оператора , чтобы избежать блока, но это, как правило, невозможно для назначения нескольких переменных и трудночитаемо в случае сложной логики.
Это более существенная проблема в языке C, особенно для присваивания строк, поскольку инициализация строки может автоматически выделять память, в то время как присваивание строки уже инициализированной переменной требует выделения памяти, копирования строки и проверки их успешности.
{ мой $counter = 0 ; под инкремент_счетчик { return ++ $counter ; } }
Некоторые языки позволяют применять концепцию области действия блока в той или иной степени за пределами функции. Например, в фрагменте Perl справа — $counter
имя переменной с областью действия блока (из-за использования my
ключевого слова), а increment_counter
— имя функции с глобальной областью действия. Каждый вызов increment_counter
увеличит значение $counter
на единицу и вернет новое значение. Код за пределами этого блока может вызвать increment_counter
, но не может иным образом получить или изменить значение $counter
. Эта идиома позволяет определять замыкания в Perl.
Когда область действия переменных, объявленных внутри функции, не выходит за пределы этой функции, это называется областью действия функции . [7] Область действия функции доступна в большинстве языков программирования, которые предлагают способ создания локальной переменной в функции или подпрограмме : переменной, область действия которой заканчивается (которая выходит из контекста), когда функция возвращается. В большинстве случаев время жизни переменной равно продолжительности вызова функции — это автоматическая переменная , создаваемая при запуске функции (или объявлении переменной), уничтожаемая при возврате функции — в то время как область действия переменной находится внутри функции, хотя значение «внутри» зависит от того, является ли область действия лексической или динамической. Однако некоторые языки, такие как C, также предусматривают статические локальные переменные , где время жизни переменной составляет все время жизни программы, но переменная находится в контексте только тогда, когда находится внутри функции. В случае статических локальных переменных переменная создается при инициализации программы и уничтожается только при завершении программы, как в случае со статической глобальной переменной , но находится только в контексте внутри функции, как автоматическая локальная переменная.
Важно отметить, что в лексической области видимости переменная с областью действия функции имеет область действия только в пределах лексического контекста функции: она выходит из контекста, когда внутри функции вызывается другая функция, и возвращается в контекст, когда функция возвращает значение — вызываемые функции не имеют доступа к локальным переменным вызывающих функций, а локальные переменные находятся только в контексте внутри тела функции, в которой они объявлены. Напротив, в динамической области действия область действия распространяется на контекст выполнения функции: локальные переменные остаются в контексте , когда вызывается другая функция, выходя из контекста только при завершении определяющей функции, и, таким образом, локальные переменные находятся в контексте функции, в которой они определены , и всех вызываемых функций . В языках с лексической областью действия и вложенными функциями локальные переменные находятся в контексте для вложенных функций, поскольку они находятся в том же лексическом контексте, но не для других функций, которые не являются лексически вложенными. Локальная переменная охватывающей функции известна как нелокальная переменная для вложенной функции. Область действия функции также применима к анонимным функциям .
def square ( n ): возвращает n * ndef sum_of_squares ( n ): total = 0 i = 0 while i <= n : total += square ( i ) i += 1 return total
Например, в фрагменте кода Python справа определены две функции: square
и sum_of_squares
. square
вычисляет квадрат числа; sum_of_squares
вычисляет сумму всех квадратов вплоть до указанного числа. (Например, square(4)
is 4 2 = 16
, и sum_of_squares(4)
is 0 2 + 1 2 + 2 2 + 3 2 + 4 2 = 30
.)
Каждая из этих функций имеет переменную с именем n , которая представляет аргумент функции. Эти две переменные n полностью отделены и не связаны между собой, несмотря на то, что имеют одинаковое имя, поскольку они являются лексически ограниченными локальными переменными с областью действия функции: областью действия каждой из них является ее собственная, лексически отдельная функция, и, таким образом, они не перекрываются. Следовательно, sum_of_squares
может вызываться без изменения square
ее собственного nsum_of_squares
. Аналогично, имеет переменные с именами total и i ; эти переменные из-за своей ограниченной области действия не будут мешать любым переменным с именами total или i , которые могут принадлежать любой другой функции. Другими словами, нет риска конфликта имен между этими именами и любыми несвязанными именами, даже если они идентичны.
Никакой маскировки имени не происходит: только одна переменная с именем n находится в контексте в любой момент времени, поскольку области действия не перекрываются. Напротив, если бы аналогичный фрагмент был написан на языке с динамической областью действия, n в вызывающей функции оставалось бы в контексте в вызываемой функции — области действия перекрывались бы — и было бы замаскировано («затенено») новым n в вызываемой функции.
Область действия функции значительно усложняется, если функции являются объектами первого класса и могут быть созданы локально для функции, а затем возвращены. В этом случае любые переменные во вложенной функции, которые не являются локальными для нее (несвязанные переменные в определении функции, которые разрешаются в переменные во включающем контексте), создают замыкание , поскольку не только сама функция, но и ее контекст (переменных) должны быть возвращены, а затем потенциально вызваны в другом контексте. Это требует значительно большей поддержки со стороны компилятора и может усложнить анализ программы.
Областью действия привязки имени является файл, который известен как область действия файла . Область действия файла в значительной степени характерна для C (и C++), где область действия переменных и функций, объявленных на верхнем уровне файла (не внутри какой-либо функции), составляет весь файл — или, скорее, для C, от объявления до конца исходного файла или, точнее, единицы перевода (внутренней компоновки). Это можно рассматривать как форму области действия модуля, где модули идентифицируются с файлами, и в более современных языках заменяется явной областью действия модуля. Из-за наличия операторов include, которые добавляют переменные и функции во внутренний контекст и могут сами вызывать дополнительные операторы include, может быть сложно определить, что находится в контексте в теле файла.
В приведенном выше фрагменте кода C имя функции sum_of_squares
имеет глобальную область действия (в C — внешняя ссылка). Добавление static
к сигнатуре функции приведет к файловой области действия (внутренняя ссылка).
Областью действия привязки имени является модуль, который известен как область действия модуля . Область действия модуля доступна в модульных языках программирования , где модули (которые могут охватывать различные файлы) являются базовой единицей сложной программы, поскольку они позволяют скрывать информацию и открывать ограниченный интерфейс. Область действия модуля была впервые использована в семействе языков Modula , и Python (на который повлияла Modula) является типичным современным примером.
В некоторых объектно-ориентированных языках программирования, в которых отсутствует прямая поддержка модулей, например, в C++ до C++20, [8] подобная структура вместо этого предоставляется иерархией классов, где классы являются базовой единицей программы, а класс может иметь закрытые методы. Это правильно понимается в контексте динамической диспетчеризации , а не разрешения имен и области действия, хотя они часто играют аналогичные роли. В некоторых случаях доступны оба эти средства, например, в Python, в котором есть и модули, и классы, а организация кода (как функция уровня модуля или традиционно закрытый метод) является выбором программиста.
Областью действия привязки имени является вся программа, которая известна как глобальная область действия . Имена переменных с глобальной областью действия — называемые глобальными переменными — часто считаются плохой практикой, по крайней мере в некоторых языках, из-за возможности конфликтов имен и непреднамеренного маскирования, а также плохой модульности, и область действия функции или область действия блока считаются предпочтительными. Однако глобальная область действия обычно используется (в зависимости от языка) для различных других видов имен, таких как имена функций, имена классов и имена других типов данных . В этих случаях для предотвращения конфликтов используются такие механизмы, как пространства имен .
Использование локальных переменных — имен переменных с ограниченной областью действия, которые существуют только внутри определенной функции — помогает избежать риска конфликта имен между двумя переменными с одинаковыми именами. Однако есть два совершенно разных подхода к ответу на этот вопрос: что значит быть «внутри» функции?
В лексической области видимости (или лексическом охвате ; также называемом статической областью видимости или статической областью видимости ), если областью видимости имени переменной является определенная функция, то ее областью видимости является текст программы определения функции: внутри этого текста имя переменной существует и связано со значением переменной, но вне этого текста имя переменной не существует. Напротив, в динамической области видимости (или динамической области видимости ), если областью видимости имени переменной является определенная функция, то ее областью видимости является период времени, в течение которого функция выполняется: пока функция выполняется, имя переменной существует и связано со своим значением, но после возврата функции имя переменной не существует. Это означает, что если функция f
вызывает отдельно определенную функцию g
, то в лексической области видимости функция g
не имеет доступа к f
локальным переменным (при условии, что текст g
не находится внутри текста f
), тогда как в динамической области видимости функция g
имеет доступ к f
локальным переменным (поскольку g
вызывается во время вызова f
).
$ # язык bash $ x = 1 $ function g () { echo $x ; x = 2 ; } $ function f () { local x = 3 ; g ; } $ f # это выведет 1 или 3? 3 $ echo $x # это выведет 1 или 2? 1
Рассмотрим, например, программу справа. Первая строка, , создает глобальную переменную и инициализирует ее как . Вторая строка, , определяет функцию , которая выводит («отображает») текущее значение , а затем устанавливает его в (перезаписывая предыдущее значение). Третья строка, определяет функцию , которая создает локальную переменную (скрывая одноименную глобальную переменную) и инициализирует ее как , а затем вызывает . Четвертая строка, , вызывает . Пятая строка, , выводит текущее значение .x=1
x
1
function g() { echo $x ; x=2 ; }
g
x
x
2
function f() { local x=3 ; g ; }
f
x
3
g
f
f
echo $x
x
Итак, что именно печатает эта программа? Это зависит от правил области действия. Если язык этой программы использует лексическую область действия, то g
печатает и изменяет глобальную переменную x
(потому что g
определена снаружи f
), поэтому программа печатает 1
и затем 2
. Напротив, если этот язык использует динамическую область действия, то g
печатает и изменяет f
локальную переменную x
(потому что g
вызывается изнутри f
), поэтому программа печатает 3
и затем 1
. (Как оказалось, язык программы — Bash , который использует динамическую область действия; поэтому программа печатает 3
и затем 1
. Если бы тот же код был запущен с помощью ksh93 , который использует лексическую область действия, результаты были бы другими.)
С лексической областью видимости имя всегда ссылается на свой лексический контекст. Это свойство текста программы, и реализация языка делает его независимым от стека вызовов времени выполнения. Поскольку для этого сопоставления требуется только анализ статического текста программы, этот тип области видимости также называется статической областью видимости . Лексическая область видимости является стандартной во всех языках на основе ALGOL , таких как Pascal , Modula-2 и Ada , а также в современных функциональных языках, таких как ML и Haskell . Она также используется в языке C и его синтаксических и семантических родственниках, хотя и с различными видами ограничений. Статическая область видимости позволяет программисту рассуждать об объектных ссылках, таких как параметры, переменные, константы, типы, функции и т. д., как о простых заменах имен. Это значительно упрощает создание модульного кода и рассуждать о нем, поскольку локальную структуру именования можно понять изолированно. Напротив, динамическая область видимости заставляет программиста предвидеть все возможные контексты выполнения, в которых может быть вызван код модуля.
программа А ; вар я : целое число ; К : чар ; процедура Б ; вар К : настоящий ; Л : целое число ; процедура C ; var M : real ; начало (*область действия A+B+C*) конец ; (*область действия A+B*) конец ; (*область действия А*) конец .
Например, Pascal имеет лексическую область видимости. Рассмотрим фрагмент программы Pascal справа. Переменная I
видна во всех точках, потому что она никогда не скрыта другой переменной с тем же именем. Переменная char
видна K
только в основной программе, потому что она скрыта переменной real
, K
видимой в процедуре B
и C
только. Переменная L
также видна только в процедуре B
и , C
но она не скрывает никакую другую переменную. Переменная M
видна только в процедуре C
и поэтому недоступна ни из процедуры B
, ни из основной программы. Кроме того, процедура C
видна только в процедуре B
и поэтому не может быть вызвана из основной программы.
В программе могла быть C
объявлена другая процедура вне процедуры B
. Место в программе, где C
упоминается " ", определяет, какую из двух названных процедур C
она представляет, таким образом, точно аналогично области действия переменных.
Правильная реализация лексической области видимости в языках с первоклассными вложенными функциями нетривиальна, поскольку требует, чтобы каждое значение функции несла с собой запись значений переменных, от которых она зависит (пара функции и этого контекста называется замыканием ) . В зависимости от реализации и архитектуры компьютера поиск переменных может стать немного неэффективным [ требуется ссылка ] при использовании очень глубоко лексически вложенных функций, хотя существуют хорошо известные методы для смягчения этого. [9] [10] Кроме того, для вложенных функций, которые ссылаются только на свои собственные аргументы и (непосредственно) локальные переменные, все относительные местоположения могут быть известны во время компиляции . Таким образом, при использовании этого типа вложенных функций не возникает никаких накладных расходов. То же самое относится к определенным частям программы, где вложенные функции не используются, и, естественно, к программам, написанным на языке, где вложенные функции недоступны (например, на языке C).
Лексическая область действия была впервые использована в начале 1960-х годов для императивного языка ALGOL 60 и с тех пор была подхвачена в большинстве других императивных языков. [4]
Такие языки, как Pascal и C, всегда имели лексическую область действия, поскольку на них обоих повлияли идеи, вошедшие в ALGOL 60 и ALGOL 68 (хотя C не включал лексически вложенные функции ).
Perl — это язык с динамической областью действия, к которому впоследствии добавилась статическая область действия.
Оригинальный интерпретатор Lisp (1960) использовал динамическую область видимости. Глубокое связывание , которое приближается к статической (лексической) области видимости, было введено около 1962 года в LISP 1.5 (через устройство Funarg , разработанное Стивом Расселом , работавшим под руководством Джона Маккарти ).
Все ранние Lisp использовали динамическую область видимости, когда основывались на интерпретаторах. В 1982 году Гай Л. Стил-младший и Common LISP Group опубликовали An Overview of Common LISP [ 11] — краткий обзор истории и различных реализаций Lisp до того момента, а также обзор функций, которые должна иметь реализация Common Lisp . На странице 102 мы читаем:
Большинство реализаций LISP внутренне противоречивы, поскольку по умолчанию интерпретатор и компилятор могут назначать разную семантику правильным программам; это происходит в первую очередь из-за того, что интерпретатор предполагает, что все переменные имеют динамическую область видимости, в то время как компилятор предполагает, что все переменные являются локальными, если только его не заставляют предполагать иное. Это было сделано ради удобства и эффективности, но может привести к очень тонким ошибкам. Определение Common LISP позволяет избежать таких аномалий, явно требуя от интерпретатора и компилятора налагать идентичную семантику на правильные программы.
Реализации Common LISP, таким образом, должны были иметь лексическую область действия . Опять же, из Обзора Common LISP :
Кроме того, Common LISP предлагает следующие возможности (большинство из которых заимствованы из MacLisp, InterLisp или Lisp Machines Lisp): (...) Полностью лексически ограниченные переменные. Так называемая " проблема FUNARG " [12] [13] полностью решена, как в нисходящем, так и в восходящем случаях.
К тому же году, когда был опубликован An Overview of Common LISP (1982), были опубликованы начальные проекты (также Гаем Л. Стилом-младшим) компилируемого, лексически ограниченного Lisp, называемого Scheme , и предпринимались попытки реализации компилятора. В то время лексическая область действия в Lisp обычно считалась неэффективной для реализации. В A History of T [ 14] Олин Шиверс пишет:
Все серьезные Lisp в производственном использовании в то время имели динамическую область видимости. Никто, кто не прочитал внимательно тезис Rabbit [15] (написанный Гаем Льюисом Стилом-младшим в 1978 году), не верил, что лексическая область видимости будет работать; даже те немногие, кто ее прочитал , немного поверили, что это будет работать в серьезном производственном использовании.
Термин «лексическая область действия» появился как минимум в 1967 году [16], тогда как термин «лексическая область действия» появился как минимум в 1970 году, когда он использовался в проекте MAC для описания правил области действия диалекта Lisp MDL (тогда известного как «Muddle»). [17]
В динамической области видимости имя ссылается на контекст выполнения. С технической точки зрения это означает, что каждое имя имеет глобальный стек привязок. Введение локальной переменной с именем x
помещает привязку в глобальный x
стек (который мог быть пустым), который выталкивается, когда поток управления покидает область видимости. Оценка x
в любом контексте всегда дает верхнюю привязку. Обратите внимание, что это невозможно сделать во время компиляции, поскольку стек привязок существует только во время выполнения , поэтому этот тип области видимости называется динамической областью видимости.
Динамическая область действия нечасто встречается в современных языках. [4]
Обычно определенные блоки определяются для создания привязок, время жизни которых равно времени выполнения блока; это добавляет некоторые особенности статической области действия к процессу динамической области действия. Однако, поскольку раздел кода может быть вызван из множества различных мест и ситуаций, может быть сложно определить с самого начала, какие привязки будут применяться при использовании переменной (или существуют ли они вообще). Это может быть полезно; применение принципа наименьшего знания предполагает, что код избегает зависимости от причин (или обстоятельств) значения переменной, а просто использует значение в соответствии с определением переменной. Такая узкая интерпретация общих данных может обеспечить очень гибкую систему для адаптации поведения функции к текущему состоянию (или политике) системы. Однако это преимущество основано на тщательном документировании всех переменных, используемых таким образом, а также на тщательном избегании предположений о поведении переменной и не предоставляет никакого механизма для обнаружения помех между различными частями программы. Некоторые языки, такие как Perl и Common Lisp , позволяют программисту выбирать статическую или динамическую область действия при определении или переопределении переменной. Примерами языков, использующих динамическую область действия, являются Logo , Emacs Lisp , LaTeX и языки оболочек bash , dash и PowerShell .
Динамическую область видимости довольно легко реализовать. Чтобы найти значение имени, программа может пройти по стеку времени выполнения, проверяя каждую запись активации (кадр стека каждой функции) на наличие значения для имени. На практике это делается более эффективно с помощью списка ассоциаций , который представляет собой стек пар имя/значение. Пары помещаются в этот стек всякий раз, когда делаются объявления, и извлекаются всякий раз, когда переменные выходят из контекста. [18] Неглубокое связывание — это альтернативная стратегия, которая значительно быстрее, использующая центральную справочную таблицу , которая связывает каждое имя с его собственным стеком значений. Это позволяет избежать линейного поиска во время выполнения для поиска конкретного имени, но следует позаботиться о том, чтобы правильно поддерживать эту таблицу. [18] Обратите внимание, что обе эти стратегии предполагают порядок «последним пришел — первым вышел» ( LIFO ) для привязок для любой одной переменной; на практике все привязки упорядочены таким образом.
Еще более простая реализация — представление динамических переменных с помощью простых глобальных переменных. Локальное связывание выполняется путем сохранения исходного значения в анонимном месте в стеке, которое невидимо для программы. Когда эта область связывания завершается, исходное значение восстанавливается из этого места. Фактически, динамическая область действия возникла таким образом. Ранние реализации Lisp использовали эту очевидную стратегию для реализации локальных переменных, и эта практика сохранилась в некоторых диалектах, которые все еще используются, таких как GNU Emacs Lisp. Лексическая область действия была введена в Lisp позже. Это эквивалентно вышеприведенной схеме поверхностного связывания, за исключением того, что центральная справочная таблица — это просто контекст связывания глобальной переменной, в котором текущее значение переменной — ее глобальное значение. Поддержание глобальных переменных несложно. Например, объект-символ может иметь выделенный слот для своего глобального значения.
Динамическая область видимости обеспечивает превосходную абстракцию для локального хранилища потока , но если она используется таким образом, то не может основываться на сохранении и восстановлении глобальной переменной. Возможная стратегия реализации заключается в том, чтобы каждая переменная имела локальный ключ потока. При доступе к переменной локальный ключ потока используется для доступа к локальному расположению памяти потока (с помощью кода, сгенерированного компилятором, который знает, какие переменные являются динамическими, а какие — лексическими). Если локальный ключ потока не существует для вызывающего потока, то используется глобальное расположение. Когда переменная локально связана, предыдущее значение сохраняется в скрытом расположении в стеке. Локальное хранилище потока создается под ключом переменной, и новое значение сохраняется там. Дальнейшие вложенные переопределения переменной в этом потоке просто сохраняют и восстанавливают это локальное расположение потока. Когда исходный, самый внешний контекст переопределения завершается, локальный ключ потока удаляется, снова открывая глобальную версию переменной этому потоку.
При ссылочной прозрачности динамическая область действия ограничивается только стеком аргументов текущей функции и совпадает с лексической областью действия.
В современных языках макрорасширение в препроцессоре является ключевым примером фактической динамической области действия. Сам макроязык преобразует только исходный код, не разрешая имена, но поскольку расширение выполняется на месте, когда имена в расширенном тексте затем разрешаются (в частности, свободные переменные), они разрешаются на основе того, где они расширяются (в общем смысле «вызываются»), как если бы имела место динамическая область действия.
Препроцессор C , используемый для расширения макроса , имеет де-факто динамическую область действия, поскольку он сам по себе не выполняет разрешение имен и не зависит от того, где определен макрос. Например, макрос:
#определить ADD_A(x) x + a
будет расширяться для добавления a
к переданной переменной, с этим именем, которое позже будет разрешено компилятором на основе того, где макрос ADD_A
«вызывается» (правильно, расширяется). Правильно, препроцессор C выполняет только лексический анализ , расширяя макрос на этапе токенизации, но не разбирая его в синтаксическое дерево или выполняя разрешение имен.
Например, в следующем коде имя a
в макросе разрешается (после расширения) в локальную переменную в месте расширения:
#определить ADD_A(x) x + avoid add_one ( int * x ) { const int a = 1 ; * x = ADD_A ( * x ); } void add_two ( int * x ) { const int a = 2 ; * x = ADD_A ( * x ); }
Как мы видели, одна из основных причин области действия заключается в том, что она помогает предотвратить конфликты имен, позволяя идентичным именам ссылаться на различные вещи, с ограничением, что имена должны иметь отдельные области действия. Иногда это ограничение неудобно; когда много разных вещей должны быть доступны в программе, им всем обычно нужны имена с глобальной областью действия, поэтому требуются разные методы для предотвращения конфликтов имен.
Чтобы решить эту проблему, многие языки предлагают механизмы для организации глобальных имен. Детали этих механизмов и используемые термины зависят от языка; но общая идея заключается в том, что группе имен может быть присвоено имя — префикс — и, при необходимости, на сущность можно ссылаться с помощью квалифицированного имени, состоящего из имени и префикса. Обычно такие имена будут иметь, в некотором смысле, два набора областей: область (обычно глобальная область), в которой видно квалифицированное имя, и одну или несколько более узких областей, в которых также видно неквалифицированное имя (без префикса). И обычно эти группы сами могут быть организованы в группы; то есть они могут быть вложенными .
Хотя многие языки поддерживают эту концепцию, детали сильно различаются. В некоторых языках есть механизмы, такие как пространства имен в C++ и C# , которые служат почти исключительно для того, чтобы глобальные имена можно было организовать в группы. В других языках есть механизмы, такие как пакеты в Ada и структуры в Standard ML , которые объединяют это с дополнительной целью, позволяющей некоторым именам быть видимыми только для других членов их группы. А объектно-ориентированные языки часто позволяют классам или объектам-одиночкам выполнять эту цель (независимо от того, есть ли у них также механизм, для которого это является основной целью). Более того, языки часто объединяют эти подходы; например, пакеты Perl во многом похожи на пространства имен C++, но опционально дублируются как классы для объектно-ориентированного программирования; а Java организует свои переменные и функции в классы, но затем организует эти классы в пакеты, подобные Ada.
Далее следуют правила области действия для репрезентативных языков.
В языке C область действия традиционно известна как связывание или видимость , особенно для переменных. C — это язык с лексической областью действия с глобальной областью действия (известной как внешняя связь ), формой области действия модуля или файловой области действия (известной как внутренняя связь ) и локальной областью действия (внутри функции); внутри функции области действия могут быть дополнительно вложены через область действия блока. Однако стандартный C не поддерживает вложенные функции.
Время жизни и видимость переменной определяются ее классом хранения . В языке C есть три типа времени жизни: статическое (выполнение программы), автоматическое (выполнение блока, выделенного в стеке) и ручное (выделенное в куче). Для переменных поддерживаются и обрабатываются компилятором только статическое и автоматическое, в то время как вручную выделенная память должна отслеживаться вручную по разным переменным. В языке C есть три уровня видимости: внешняя связь (глобальная), внутренняя связь (примерно файл) и область видимости блока (которая включает функции); области видимости блока могут быть вложенными, и возможны различные уровни внутренней связи с помощью включений. Внутренняя связь в языке C — это видимость на уровне единицы трансляции , а именно исходного файла после обработки препроцессором C , в частности, включая все соответствующие включения.
Программы на языке C компилируются как отдельные объектные файлы , которые затем связываются в исполняемый файл или библиотеку через компоновщик . Таким образом, разрешение имен разделено между компилятором, который разрешает имена в пределах единицы трансляции (более свободно, «единица компиляции», но это, по сути, другая концепция), и компоновщиком, который разрешает имена между единицами трансляции; см. связывание для дальнейшего обсуждения.
В языке C переменные с областью действия блока входят в контекст при объявлении (не в верхней части блока), выходят из контекста, если внутри блока вызывается какая-либо (не вложенная) функция, возвращаются в контекст при возврате функции и выходят из контекста в конце блока. В случае автоматических локальных переменных они также выделяются при объявлении и освобождаются в конце блока, в то время как для статических локальных переменных они выделяются при инициализации программы и освобождаются при завершении программы.
Следующая программа демонстрирует переменную с областью действия блока, входящую в контекст в середине блока, а затем выходящую из контекста (и фактически освобождающуюся) по завершении блока:
#include <stdio.h> int main ( void ) { char x = 'm' ; printf ( "%c \n " , x ); { printf ( "%c \n " , x ); char x = 'b' ; printf ( "%c \n " , x ); } printf ( "%c \n " , x ); }
Программа выводит:
ммбм
В языке C есть и другие уровни области действия. [19] Имена переменных, используемые в прототипе функции, имеют видимость прототипа функции и контекст выхода в конце прототипа функции. Поскольку имя не используется, это бесполезно для компиляции, но может быть полезно для документирования. Имена меток для оператора GOTO имеют область действия функции.
Все переменные, которые мы собираемся использовать в программе, должны быть объявлены с их спецификатором типа в более ранней точке кода, как мы это делали в предыдущем коде в начале тела функции main, когда мы объявили, что a, b и result имеют тип int. Переменная может быть как глобальной, так и локальной области действия. Глобальная переменная — это переменная, объявленная в основном теле исходного кода, за пределами всех функций, в то время как локальная переменная — это переменная, объявленная внутри тела функции или блока.
Современные версии допускают вложенную лексическую область действия.
В Swift для областей действия существует похожее правило с C++, но оно содержит другие модификаторы доступа.
Go имеет лексическую область видимости, используя блоки. [3]
Java имеет лексическую область видимости.
Класс Java имеет несколько видов переменных: [20]
В общем, набор скобок определяет определенную область действия, но переменные на верхнем уровне в классе могут отличаться по своему поведению в зависимости от ключевых слов модификатора, используемых в их определении. В следующей таблице показан доступ к членам, разрешенный каждым модификатором. [21]
JavaScript имеет простые правила области действия [22] , но инициализация переменных и правила разрешения имен могут вызывать проблемы, а широкое использование замыканий для обратных вызовов означает, что лексический контекст функции при определении (который используется для разрешения имен) может сильно отличаться от лексического контекста при ее вызове (который не имеет значения для разрешения имен). Объекты JavaScript имеют разрешение имен для свойств, но это отдельная тема.
JavaScript имеет лексическую область действия [23], вложенную на уровне функций, при этом глобальный контекст является самым внешним контекстом. Эта область действия используется как для переменных, так и для функций (имея в виду объявления функций, в отличие от переменных типа функции ). [24] Область действия блока с ключевыми словами let
и const
является стандартной, начиная с ECMAScript 6. Область действия блока может быть создана путем обертывания всего блока в функцию и ее последующего выполнения; это известно как шаблон немедленно вызываемого функционального выражения (IIFE).
Хотя область действия JavaScript проста — лексическая, на уровне функций — связанные с ней правила инициализации и разрешения имен являются причиной путаницы. Во-первых, присвоение имени, не находящемуся в области действия, по умолчанию создает новую глобальную переменную, а не локальную. Во-вторых, для создания новой локальной переменной необходимо использовать var
ключевое слово ; затем переменная создается в верхней части функции со значением undefined
, и переменной присваивается ее значение, когда достигается выражение присваивания:
Это известно как подъем переменной [26] — объявление, но не инициализация, поднимается наверх функции. В-третьих, доступ к переменным до инициализации приводит к undefined
, а не к синтаксической ошибке. В-четвертых, для объявлений функций объявление и инициализация поднимаются наверх функции, в отличие от инициализации переменной. Например, следующий код создает диалог с выводомнеопределенный, так как объявление локальной переменной поднято, затеняя глобальную переменную, но инициализация — нет, поэтому переменная не определена при использовании:
а = 1 ; функция f () { alert ( a ); var a = 2 ; } f ();
Кроме того, поскольку функции являются объектами первого класса в JavaScript и часто назначаются как обратные вызовы или возвращаются из функций, при выполнении функции разрешение имени зависит от того, где она была изначально определена (лексический контекст определения), а не от лексического контекста или контекста выполнения, где она вызывается. Вложенные области действия конкретной функции (от наиболее глобальной до наиболее локальной) в JavaScript, в частности замыкания, используемого как обратный вызов, иногда называют цепочкой областей действия , по аналогии с цепочкой прототипов объекта.
Замыкания могут быть созданы в JavaScript с помощью вложенных функций, поскольку функции являются объектами первого класса. [27] Возврат вложенной функции из охватывающей функции включает локальные переменные охватывающей функции как (нелокальный) лексический контекст возвращаемой функции, что приводит к замыканию. Например:
function newCounter () { // возвращает счетчик, который увеличивается при вызове (начиная с 0) // и который возвращает свое новое значение var a = 0 ; var b = function () { a ++ ; return a ; }; return b ; } c = newCounter (); alert ( c () + ' ' + c ()); // выводит "1 2"
Замыкания часто используются в JavaScript, поскольку используются для обратных вызовов. Действительно, любое подключение функции в локальном контексте в качестве обратного вызова или возврат ее из функции создает замыкание, если в теле функции есть какие-либо несвязанные переменные (с контекстом замыкания, основанным на вложенных областях текущего лексического контекста, или «цепочке областей»); это может быть случайным. При создании обратного вызова на основе параметров параметры должны храниться в замыкании, в противном случае оно случайно создаст замыкание, ссылающееся на переменные в охватывающем контексте, который может измениться. [28]
Разрешение имен свойств объектов JavaScript основано на наследовании в дереве прототипов (путь к корню в дереве называется цепочкой прототипов ) и отделено от разрешения имен переменных и функций.
В диалектах Lisp существуют различные правила для области действия.
В оригинальном Lisp использовалась динамическая область видимости; именно Scheme , вдохновленная ALGOL , ввела статическую (лексическую) область видимости в семейство Lisp.
Maclisp использовал динамическую область видимости по умолчанию в интерпретаторе и лексическую область видимости по умолчанию в скомпилированном коде, хотя скомпилированный код мог получить доступ к динамическим привязкам с помощью SPECIAL
объявлений для определенных переменных. [29] Однако Maclisp рассматривал лексическую привязку скорее как оптимизацию, чем можно было бы ожидать в современных языках, и она не имела функции замыкания , которую можно было бы ожидать от лексической области видимости в современных Lisp. Была доступна отдельная операция, *FUNCTION
, чтобы несколько неуклюже обойти некоторые из этих проблем. [30]
Common Lisp перенял лексическую область действия из Scheme [31] , как и Clojure .
ISLISP имеет лексическую область действия для обычных переменных. Он также имеет динамические переменные, но они во всех случаях явно обозначены; они должны быть определены defdynamic
специальной формой, связаны dynamic-let
специальной формой и доступны посредством явной dynamic
специальной формы. [32]
Некоторые другие диалекты Lisp, такие как Emacs Lisp , по-прежнему используют динамическую область видимости по умолчанию. Emacs Lisp теперь имеет лексическую область видимости, доступную на основе буфера. [33]
Для переменных в Python есть область действия функции, область действия модуля и глобальная область действия. Имена входят в контекст в начале области действия (функции, модуля или глобальной области действия) и выходят из контекста, когда вызывается не вложенная функция или область действия заканчивается. Если имя используется до инициализации переменной, это вызывает исключение времени выполнения. Если к переменной просто обращаются (не назначают), разрешение имен следует правилу LEGB (локальное, включающее, глобальное, встроенное), которое разрешает имена в наиболее узкий релевантный контекст. Однако, если назначается переменная, по умолчанию объявляется переменная, область действия которой начинается в начале уровня (функции, модуля или глобальной), а не при назначении. Оба эти правила можно переопределить с помощью объявления global
or nonlocal
(в Python 3) перед использованием, что позволяет получать доступ к глобальным переменным, даже если есть маскирующая нелокальная переменная, и назначать глобальные или нелокальные переменные.
В качестве простого примера функция помещает переменную в глобальную область видимости:
>>> def f (): ... print ( x ) ... >>> x = "глобальный" >>> f () глобальный
Обратите внимание, что x
is определен до того, f
как вызывается, поэтому ошибка не возникает, даже если он определен после своей ссылки в определении f
. Лексически это прямая ссылка , которая разрешена в Python.
Здесь присваивание создает новую локальную переменную, которая не изменяет значение глобальной переменной:
>>> def f (): ... x = "f" ... print ( x ) ... >>> x = "глобальный" >>> print ( x ) глобальный >>> f () f >>> print ( x ) глобальный
Присвоение переменной внутри функции приводит к тому, что она объявляется локальной по отношению к функции, следовательно, ее областью действия является вся функция, и, таким образом, ее использование до этого присвоения вызывает ошибку. Это отличается от C, где область действия локальной переменной начинается с ее объявления. Этот код вызывает ошибку:
>>> def f (): ... print ( x ) ... x = "f" ... >>> x = "global" >>> f () Трассировка (последний вызов был последним): Файл "<stdin>" , строка 1 , в <module> Файл "<stdin>" , строка 2 , в f UnboundLocalError : ссылка на локальную переменную 'x' сделана до назначения
Правила разрешения имен по умолчанию можно переопределить с помощью ключевых слов global
or nonlocal
(в Python 3). В приведенном ниже коде global x
объявление в g
означает, что x
разрешается в глобальную переменную. Таким образом, к нему можно получить доступ (так как он уже определен), а присваивание назначает глобальную переменную, а не объявляет новую локальную переменную. Обратите внимание, что global
объявление в не требуется f
— поскольку оно не назначает переменной, по умолчанию разрешается в глобальную переменную.
>>> def f (): ... print ( x ) ... >>> def g (): ... global x ... print ( x ) ... x = "g" ... >>> x = "global" >>> f () global >>> g () global >>> f () g
global
может также использоваться для вложенных функций. Помимо возможности присваивания глобальной переменной, как в невложенной функции, это также может использоваться для доступа к глобальной переменной при наличии нелокальной переменной:
>>> def f (): ... def g (): ... глобальный x ... print ( x ) ... x = "f" ... g () ... >>> x = "глобальный" >>> f () глобальный
Для вложенных функций также существует nonlocal
объявление для присвоения нелокальной переменной, аналогично использованию global
в невложенной функции:
>>> def f (): ... def g (): ... нелокальный x # только для Python 3 ... x = "g" ... x = "f" ... g () ... print ( x ) ... >>> x = "global" >>> f () g >>> print ( x ) глобальный
R — это язык с лексической областью действия, в отличие от других реализаций S , где значения свободных переменных определяются набором глобальных переменных, тогда как в R они определяются контекстом, в котором была создана функция. [34] Доступ к контекстам области действия можно получить с помощью различных функций (таких как parent.frame()
), которые могут имитировать опыт динамической области действия, если программист этого пожелает.
Область действия блока отсутствует:
а <- 1 { а <- 2 } сообщение ( а ) ## 2
Функции имеют доступ к области действия, в которой они были созданы:
а <- 1 f <- функция () { сообщение ( а ) } f () ## 1
Переменные, созданные или измененные внутри функции, остаются там:
а <- 1 f <- функция () { сообщение ( а ) а <- 2 сообщение ( а ) } f () ## 1 ## 2 сообщение ( а ) ## 1
Переменные, созданные или измененные внутри функции, остаются там, если явно не запрошено назначение охватывающей области действия:
а <- 1 f <- функция () { сообщение ( а ) а <<- 2 сообщение ( а ) } f () ## 1 ## 2 сообщение ( а ) ## 2
Хотя R по умолчанию имеет лексическую область действия, области действия функций можно изменить:
a <- 1 f <- function () { message ( a ) } my_env <- new.env () my_env $ a <- 2 f () ## 1 environment ( f ) <- my_env f () ## 2
Переменные, определенные внутри функции, не могут быть доступны из любого места за пределами функции, поскольку переменная определена только в области действия функции. Однако функция может получить доступ ко всем переменным и функциям, определенным внутри области действия, в которой она определена.
Если переменная, которая должна быть связана, была объявлена как специальная, привязка компилируется как код, имитирующий способ, которым интерпретатор связывает переменные
предназначен для решения «
проблемы фунаргов
», однако работает только в некоторых простых случаях.
*FUNCTION
*FUNCTION
MacLisp улучшил понятие специальных переменных Lisp 1.5 ... Основное влияние на Common Lisp оказали Lisp Machine Lisp, MacLisp, NIL, S-1 Lisp, Spice Lisp и Scheme.
Динамические привязки устанавливаются и доступны с помощью отдельного механизма (т. е.
,
, и
).
defdynamic
dynamic-let
dynamic
Emacs 24 есть необязательная лексическая привязка, которую можно включить для каждого буфера.