Spec-Zone.ru › Julia 1.1

Область видимости переменных

Область видимости переменной — это область кода, в которой переменная видна. Область видимости переменных помогает избежать конфликтов имён переменных. Концепция интуитивна: две функции могут иметь аргументы, названные x , без того, чтобы два x ссылались на одну и ту же вещь. Аналогично, есть много других случаев, когда разные блоки кода могут использовать одно и то же имя, не ссылаясь на одну и ту же вещь. Правила, определяющие, ссылаются ли одинаковые имена переменных на одну и ту же вещь или нет, называются правилами области видимости; в этом разделе они подробно описаны.

Определённые конструкции языка вводят блоки области видимости, которые представляют собой области кода, которые могут быть областью видимости некоторого набора переменных. Область видимости переменной не может быть произвольным набором строк исходного кода; вместо этого она всегда будет соответствовать одному из этих блоков. Существуют два основных типа областей видимости в Julia: глобальная область видимости и локальная область видимости. Последняя может быть вложенной. Конструкции, вводящие блоки области видимости, следующие:

Конструкции области видимости

Конструкция Тип области видимости Вложенные блоки области видимости
module, baremodule глобальная глобальная
интерактивный приглашающий оператор (REPL) глобальная глобальная
(изменяемый) struct, macro локальная глобальная
for, while, try-catch-finally, let локальная глобальная или локальная
функции (любой синтаксис, анонимные и блоки do) локальная глобальная или локальная
генераторы, объединение операций рассылания локальная глобальная или локальная

В этой таблице отсутствуют блоки begin и блоки if, которые не вводят новые области видимости. Оба типа областей видимости следуют несколько отличающимся правилам, которые будут объяснены ниже.

Julia использует лексическую область видимости, что означает, что область видимости функции не наследуется от области видимости её вызывающей функции, а от области видимости, в которой функция была определена. Например, в следующем коде x внутри foo относится к x в глобальной области видимости её модуля Bar:

julia> module Bar
           x = 1
           foo() = x
       end;

а не к x в области видимости, где foo используется:

julia> import .Bar

julia> x = -1;

julia> Bar.foo()
1

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

Глобальная область видимости

Каждый модуль вводит новую глобальную область видимости, отличную от глобальной области видимости всех других модулей; нет всеобъемлющей глобальной области видимости. Модули могут вводить переменные других модулей в свою область видимости через операторы using или import или через квалифицированный доступ с использованием обозначения точки, т. е. каждый модуль является так называемым пространством имён. Обратите внимание, что привязки переменных могут изменяться только в пределах их глобальной области видимости, а не из внешнего модуля.

julia> module A
           a = 1 # a global in A's scope
       end;

julia> module B
           module C
               c = 2
           end
           b = C.c    # can access the namespace of a nested global scope
                      # through a qualified access
           import ..A # makes module A available
           d = A.a
       end;

julia> module D
           b = a # errors as D's global scope is separate from A's
       end;
ERROR: UndefVarError: a not defined

julia> module E
           import ..A # make module A available
           A.a = 2    # throws below error
       end;
ERROR: cannot assign variables in other modules

Обратите внимание, что интерактивное приглашение (также известное как REPL) находится в глобальной области видимости модуля Main.

Локальная область видимости

Новая локальная область видимости вводится большинством блоков кода (см. выше таблицу таблицу для полного списка). Локальная область видимости наследует все переменные из родительской локальной области видимости, как для чтения, так и для записи. В отличие от глобальных областей видимости, локальные области видимости не являются пространствами имён, поэтому переменные во внутренней области видимости не могут быть получены из родительской области видимости каким-либо квалифицированным доступом.

Следующие правила и примеры относятся к локальным областям видимости. Новая переменная, введённая в локальной области видимости, не может быть обработана родительской областью видимости. Например, здесь $z$ не вводится в область видимости верхнего уровня:

julia> for i = 1:10
           z = i
       end

julia> z
ERROR: UndefVarError: z not defined
Примечание

В данном и всех последующих примерах предполагается, что их верхний уровень — это глобальная область видимости со чистой рабочей областью, например, только что запущенный REPL.

Однако внутренние локальные области видимости могут обновлять переменные в своих родительских областях видимости:

julia> for i = 1:1
           z = i
           for j = 1:1
               z = 0
           end
           println(z)
       end
0

Внутри локальной области видимости переменная может быть принудительно назначена новой локальной переменной с использованием ключевого слова local:

julia> for i = 1:1
           x = i + 1
           for j = 1:1
               local x = 0
           end
           println(x)
       end
2

Внутри локальной области видимости глобальная переменная может быть назначена с использованием ключевого слова global:

julia> for i = 1:10
           global z
           z = i
       end

julia> z
10

Расположение ключевых слов local и global внутри блока области видимости не имеет значения. Следующее эквивалентно последнему примеру (хотя и менее стильно):

julia> for i = 1:10
           z = i
           global z
       end

julia> z
10

Ключевые слова local и global также могут применяться к присваиваниям с деструктурированием, например, local x, y = 1, 2. В этом случае ключевое слово влияет на все перечисленные переменные.

В локальной области видимости все переменные наследуются из родительского глобального блока области видимости, если:

  • присвоение приведет к изменению глобальной переменной, или
  • переменная явно помечена ключевым словом local.

Таким образом, глобальные переменные наследуются только для чтения, а не для записи:

julia> x, y = 1, 2;

julia> function foo()
           x = 2        # assignment introduces a new local
           return x + y # y refers to the global
       end;

julia> foo()
4

julia> x
1

Для присвоения глобальной переменной требуется явное global:

Избегание глобальных переменных

Многие программисты считают, что избегать изменения значений глобальных переменных — это лучший подход к программированию. Изменение значения глобальной переменной может вызвать «действие на расстоянии», что затрудняет понимание поведения программы. Вот почему в блоках области видимости, которые вводят локальную область видимости, требуется ключевое слово global для объявления намерения изменить глобальную переменную.

julia> x = 1;

julia> function foobar()
           global x = 2
       end;

julia> foobar();

julia> x
2

Обратите внимание, что вложенные функции могут изменять переменные их родительской области видимости:

julia> x, y = 1, 2;

julia> function baz()
           x = 2 # introduces a new local
           function bar()
               x = 10       # modifies the parent's x
               return x + y # y is global
           end
           return bar() + x # 12 + 10 (x is modified in call of bar())
       end;

julia> baz()
22

julia> x, y # verify that global x and y are unchanged
(1, 2)

Причина, по которой разрешается изменение локальных переменных родительских областей видимости вложенными функциями, заключается в возможности создания closures, имеющих приватное состояние, например, переменная state в следующем примере:

julia> let state = 0
           global counter() = (state += 1)
       end;

julia> counter()
1

julia> counter()
2

См. также замыкания в примерах в следующих двух разделах. Переменная, такая как x в первом примере и state во втором, которая наследуется от окружающего контекста внутренней функцией, иногда называется захваченной переменной. Захваченные переменные могут создавать проблемы с производительностью, о которых говорится в подсказках по производительности.

Различие между наследованием глобальной области видимости и вложенностью локальной области видимости может привести к некоторым небольшим различиям между функциями, определёнными в локальных и глобальных областях видимости для присвоения переменных. Рассмотрим модификацию последнего примера, перемещая bar в глобальную область видимости:

julia> x, y = 1, 2;

julia> function bar()
           x = 10 # local, no longer a closure variable
           return x + y
       end;

julia> function quz()
           x = 2 # local
           return bar() + x # 12 + 2 (x is not modified)
       end;

julia> quz()
14

julia> x, y # verify that global x and y are unchanged
(1, 2)

Обратите внимание, что вышеуказанные правила вложенности не относятся к определениям типов и макросов, так как они могут появляться только в глобальной области видимости. Существуют особые правила области видимости, касающиеся оценки аргументов функций по умолчанию и ключевых аргументов, которые описаны в разделе Функции.

Присваивание, вводящее переменную, используемую внутри определения функции, типа или макроса, не обязательно должно предшествовать её внутреннему использованию:

julia> f = y -> y + a;

julia> f(3)
ERROR: UndefVarError: a not defined
Stacktrace:
[...]

julia> a = 1
1

julia> f(3)
4

Это поведение может показаться немного странным для обычной переменной, но позволяет использовать именованные функции — которые являются просто обычными переменными, содержащими объекты функций — до их определения. Это позволяет определять функции в любом интуитивном и удобном порядке, а не принуждать к порядку снизу вверх или к необходимости предварительных объявлений, при условии, что они определены к моменту фактического вызова. В качестве примера, вот неэффективный, взаимно рекурсивный способ проверки, являются ли целые положительные числа чётными или нечётными:

julia> even(n) = (n == 0) ? true : odd(n - 1);

julia> odd(n) = (n == 0) ? false : even(n - 1);

julia> even(3)
false

julia> odd(3)
true

Julia предоставляет встроенные эффективные функции для проверки чётности и нечётности, называемые iseven и isodd, поэтому вышеприведённые определения следует рассматривать только как примеры области видимости, а не как примеры эффективного проектирования.

Блоки let

В отличие от присвоений локальным переменным, операторы let выделяют новые привязки переменных каждый раз при выполнении. Присвоение изменяет существующее местоположение значения, а let создаёт новые местоположения. Это различие обычно не существенно и обнаруживается только в случае переменных, которые переживают свою область видимости через замыкания. Синтаксис let принимает серию присвоений и имён переменных, разделённых запятыми:

julia> x, y, z = -1, -1, -1;

julia> let x = 1, z
           println("x: $x, y: $y") # x is local variable, y the global
           println("z: $z") # errors as z has not been assigned yet but is local
       end
x: 1, y: -1
ERROR: UndefVarError: z not defined

Присвоения оцениваются по порядку, с оценкой правой части в области видимости перед тем, как новая переменная в левой части будет введена. Поэтому имеет смысл написать что-то вроде let x = x , поскольку две переменные x различны и имеют отдельные места хранения. Вот пример, где нужно поведение let:

julia> Fs = Vector{Any}(undef, 2); i = 1;

julia> while i <= 2
           Fs[i] = ()->i
           global i += 1
       end

julia> Fs[1]()
3

julia> Fs[2]()
3

Здесь мы создаём и храним два замыкания, которые возвращают переменную i. Однако всегда это одна и та же переменная i, поэтому два замыкания ведут себя одинаково. Мы можем использовать let для создания новой привязки для i:

julia> Fs = Vector{Any}(undef, 2); i = 1;

julia> while i <= 2
           let i = i
               Fs[i] = ()->i
           end
           global i += 1
       end

julia> Fs[1]()
1

julia> Fs[2]()
2

Поскольку конструкция begin не вводит новую область видимости, она может быть полезной для использования нуль-аргументного let для ввода нового блока области видимости без создания новых привязок:

julia> let
           local x = 1
           let
               local x = 2
           end
           x
       end
1

Поскольку let вводит новый блок области видимости, внутренняя локальная переменная x отличается от внешней локальной переменной x.

Циклы for и генераторы

for циклы, while циклы и генераторы ведут себя следующим образом: любые новые переменные, введённые в их телах циклов, выделяются заново для каждой итерации цикла, как будто тело цикла было окружено блоком let:

julia> Fs = Vector{Any}(undef, 2);

julia> for j = 1:2
           Fs[j] = ()->j
       end

julia> Fs[1]()
1

julia> Fs[2]()
2

Переменная итерации цикла for или генератора всегда является новой переменной:

julia> function f()
           i = 0
           for i = 1:3
           end
           return i
       end;

julia> f()
0

Однако иногда полезно повторно использовать существующую локальную переменную в качестве переменной итерации. Это можно удобно сделать, добавив ключевое слово outer:

julia> function f()
           i = 0
           for outer i = 1:3
           end
           return i
       end;

julia> f()
3

Константы

Распространённое применение переменных — присвоение им конкретных неизменяемых значений. Такие переменные присваиваются только один раз. Это намерение можно передать компилятору, используя ключевое слово const:

julia> const e  = 2.71828182845904523536;

julia> const pi = 3.14159265358979323846;

Несколько переменных можно объявить в одном const операторе:

julia> const a, b = 1, 2
(1, 2)

Объявление const должно использоваться только в глобальной области для глобальных переменных. Компилятору сложно оптимизировать код, содержащий глобальные переменные, так как их значения (или даже их типы) могут меняться практически в любой момент. Если глобальная переменная не будет меняться, добавление объявления const решает эту проблему производительности.

Локальные константы существенно отличаются. Компилятор может автоматически определить, когда локальная переменная является константой, поэтому объявления локальных констант не требуются, а на данный момент и не поддерживаются.

Специальные задания на верхнем уровне, такие как те, что выполняются с помощью ключевых слов function и struct, по умолчанию являются константами.

Обратите внимание, что const влияет только на привязку переменной; переменная может быть привязана к изменяемому объекту (например, массиву), и этот объект по-прежнему может быть изменён. Кроме того, когда вы пытаетесь присвоить значение переменной, объявленной как константа, возможны следующие сценарии:

  • если новое значение имеет тип, отличный от типа константы, то генерируется ошибка:
julia> const x = 1.0
1.0

julia> x = 1
ERROR: invalid redefinition of constant x
  • если новое значение имеет тот же тип, что и константа, то выводится предупреждение:
julia> const y = 1.0
1.0

julia> y = 2.0
WARNING: redefining constant y
2.0
  • если присвоение не приведёт к изменению значения переменной, то сообщение не выводится:
julia> const z = 100
100

julia> z = 100
100

Последнее правило применяется к неизменяемым объектам, даже если привязка переменной изменится, например:

julia> const s1 = "1"
"1"

julia> s2 = "1"
"1"

julia> pointer.([s1, s2], 1)
2-element Array{Ptr{UInt8},1}:
 Ptr{UInt8} @0x00000000132c9638
 Ptr{UInt8} @0x0000000013dd3d18

julia> s1 = s2
"1"

julia> pointer.([s1, s2], 1)
2-element Array{Ptr{UInt8},1}:
 Ptr{UInt8} @0x0000000013dd3d18
 Ptr{UInt8} @0x0000000013dd3d18

Однако для изменяемых объектов предупреждение выводится, как ожидалось:

julia> const a = [1]
1-element Array{Int64,1}:
 1

julia> a = [1]
WARNING: redefining constant a
1-element Array{Int64,1}:
 1

Обратите внимание, что, хотя иногда это возможно, изменение значения переменной const настоятельно не рекомендуется и предназначено только для удобства при интерактивном использовании. Изменение констант может вызвать различные проблемы или неожиданное поведение. Например, если метод ссылается на константу, и он уже скомпилирован до изменения константы, то он может продолжать использовать старое значение:

julia> const x = 1
1

julia> f() = x
f (generic function with 1 method)

julia> f()
1

julia> x = 2
WARNING: redefining constant x
2

julia> f()
1

© 2009–2019 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.1.1/manual/variables-and-scoping/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API