Spec-Zone.ru › Julia 1.3

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

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

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

Конструкции области действия

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

Обратите внимание, что в этой таблице отсутствуют блоки 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

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

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–2020 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.3.1/manual/variables-and-scoping/

Spec-Zone.ru

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