Modules
Модули в Julia помогают организовать код в согласованные единицы. Они синтаксически ограничены внутри module NameOfModule ... end
и имеют следующие особенности:
- Модули являются отдельными пространствами имен, каждое из которых вводит новую глобальную область видимости. Это полезно, потому что позволяет использовать одно и то же имя для различных функций или глобальных переменных без конфликта, при условии, что они находятся в отдельных модулях.
- Модули имеют средства для детального управления пространством имен: каждый из них определяет набор имен, которые он
экспортирует
и помечает какпубличные
, и может импортировать имена из других модулей с помощьюusing
иimport
(мы объясним это ниже). - Модули могут быть предварительно скомпилированы для более быстрой загрузки и могут содержать код для инициализации во время выполнения.
Обычно в крупных пакетах Julia вы увидите код модуля, организованный в файлы, например
module SomeModule
# export, public, using, import statements are usually here; we discuss these below
include("file1.jl")
include("file2.jl")
end
Файлы и имена файлов в основном не связаны с модулями; модули ассоциируются только с выражениями модуля. Один модуль может содержать несколько файлов, и несколько модулей может быть в одном файле. include
ведет себя так, как будто содержимое исходного файла было оценено в глобальной области видимости включающего модуля. В этой главе мы используем короткие и упрощенные примеры, поэтому мы не будем использовать include
.
Рекомендуемый стиль заключается в том, чтобы не делать отступы в теле модуля, так как это обычно приводит к тому, что целые файлы оказываются с отступами. Также обычно используется UpperCamelCase
для имен модулей (так же, как и для типов), и используется множественная форма, если это применимо, особенно если модуль содержит идентификатор с аналогичным именем, чтобы избежать конфликтов имен. Например,
module FastThings
struct FastThing
...
end
end
Namespace management
Управление пространством имен относится к возможностям, которые язык предлагает для того, чтобы имена в модуле были доступны в других модулях. Мы подробно обсудим связанные концепции и функциональность ниже.
Qualified names
Имена функций, переменных и типов в глобальной области, такие как sin
, ARGS
и UnitRange
, всегда принадлежат модулю, называемому родительским модулем, который можно найти интерактивно с помощью parentmodule
, например
julia> parentmodule(UnitRange)
Base
Можно также ссылаться на эти имена вне их родного модуля, предваряя их именем модуля, например, Base.UnitRange
. Это называется квалифицированным именем. Родной модуль может быть доступен через цепочку подмодулей, таких как Base.Math.sin
, где Base.Math
называется путем модуля. Из-за синтаксических неоднозначностей квалификация имени, которое содержит только символы, такие как оператор, требует вставки двоеточия, например, Base.:+
. Небольшое количество операторов дополнительно требует скобок, например, Base.:(==)
.
Если имя квалифицировано, то оно всегда доступно, и в случае функции к нему также могут быть добавлены методы, используя квалифицированное имя в качестве имени функции.
Внутри модуля имя переменной может быть "зарезервировано" без присвоения ему значения, объявив его как global x
. Это предотвращает конфликты имен для глобальных переменных, инициализированных после времени загрузки. Синтаксис M.x = y
не работает для присвоения глобальной переменной в другом модуле; присвоение глобальной переменной всегда локально для модуля.
Export lists
Имена (относящиеся к функциям, типам, глобальным переменным и константам) могут быть добавлены в список экспорта модуля с помощью export
: это символы, которые импортируются при использовании модуля. Обычно они находятся в верхней части определения модуля, чтобы читатели исходного кода могли легко их найти, как в
julia> module NiceStuff
export nice, DOG
struct Dog end # singleton type, not exported
const DOG = Dog() # named instance, exported
nice(x) = "nice $x" # function, exported
end;
но это всего лишь предложение по стилю — модуль может иметь несколько операторов export
в произвольных местах.
Обычно экспортируются имена, которые являются частью API (интерфейса программирования приложений). В приведенном выше коде список экспорта предполагает, что пользователи должны использовать nice
и DOG
. Однако, поскольку квалифицированные имена всегда делают идентификаторы доступными, это всего лишь вариант организации API: в отличие от других языков, в Julia нет средств для истинного скрытия внутренних модулей.
Также некоторые модули вообще не экспортируют имена. Это обычно делается, если они используют общие слова, такие как derivative
, в своем API, что может легко конфликтовать со списками экспортов других модулей. Мы увидим, как управлять конфликтами имен ниже.
Чтобы пометить имя как публичное, не экспортируя его в пространство имен людей, которые используют using NiceStuff
, можно использовать public
вместо export
. Это помечает публичное имя(имена) как часть публичного API, но не имеет никаких последствий для пространства имен. Ключевое слово public
доступно только в Julia 1.11 и выше. Чтобы поддерживать совместимость с Julia 1.10 и ниже, используйте макрос @compat
из пакета Compat.
Standalone using
and import
Для интерактивного использования самым распространенным способом загрузки модуля является using ModuleName
. Это loads код, связанный с ModuleName
, и приносит
- имя модуля
- и элементы списка экспорта в окружающее глобальное пространство имен.
С технической точки зрения, оператор using ModuleName
означает, что модуль с именем ModuleName
будет доступен для разрешения имен по мере необходимости. Когда встречается глобальная переменная, которая не имеет определения в текущем модуле, система будет искать ее среди переменных, экспортируемых ModuleName
, и использовать ее, если она будет найдена там. Это означает, что все использования этой глобальной переменной в текущем модуле будут разрешаться к определению этой переменной в ModuleName
.
Чтобы загрузить модуль из пакета, можно использовать оператор using ModuleName
. Чтобы загрузить модуль из локально определенного модуля, необходимо добавить точку перед именем модуля, например using .ModuleName
.
Чтобы продолжить с нашим примером,
julia> using .NiceStuff
загрузит приведенный выше код, сделав NiceStuff
(имя модуля), DOG
и nice
доступными. Dog
не находится в списке экспорта, но к нему можно получить доступ, если имя квалифицировано путем модуля (который здесь просто имя модуля) как NiceStuff.Dog
.
Важно, using ModuleName
— это единственная форма, для которой списки экспорта имеют значение.
В отличие от,
julia> import .NiceStuff
вводит только имя модуля в область видимости. Пользователям нужно будет использовать NiceStuff.DOG
, NiceStuff.Dog
и NiceStuff.nice
, чтобы получить доступ к его содержимому. Обычно import ModuleName
используется в контекстах, когда пользователь хочет сохранить пространство имен чистым. Как мы увидим в следующем разделе, import .NiceStuff
эквивалентно using .NiceStuff: NiceStuff
.
Вы можете объединить несколько операторов using
и import
одного типа в выражении, разделенном запятыми, например
julia> using LinearAlgebra, Random
using
and import
with specific identifiers, and adding methods
Когда using ModuleName:
или import ModuleName:
сопровождается запятой, разделяющим список имен, модуль загружается, но только эти конкретные имена попадают в пространство имен с помощью этого оператора. Например,
julia> using .NiceStuff: nice, DOG
импортируем имена nice
и DOG
.
Важно, что имя модуля NiceStuff
не будет в пространстве имен. Если вы хотите сделать его доступным, вам нужно явно указать его, как
julia> using .NiceStuff: nice, DOG, NiceStuff
Когда два или более пакета/модуля экспортируют имя, и это имя не ссылается на одно и то же в каждом из пакетов, и пакеты загружаются с помощью using
без явного списка имен, то ссылка на это имя без квалификации является ошибкой. Поэтому рекомендуется, чтобы код, предназначенный для совместимости с будущими версиями его зависимостей и Julia, например, код в выпущенных пакетах, перечислял имена, которые он использует из каждого загруженного пакета, например, using Foo: Foo, f
, а не using Foo
.
Юлия имеет две формы для, казалось бы, одной и той же вещи, потому что только import ModuleName: f
позволяет добавлять методы к f
без указания пути к модулю. То есть, следующий пример вызовет ошибку:
julia> using .NiceStuff: nice
julia> struct Cat end
julia> nice(::Cat) = "nice 😸"
ERROR: invalid method definition in Main: function NiceStuff.nice must be explicitly imported to be extended
Stacktrace:
[1] top-level scope
@ none:0
[2] top-level scope
@ none:1
Эта ошибка предотвращает случайное добавление методов к функциям в других модулях, которые вы намеревались использовать только.
Существует два способа справиться с этим. Вы всегда можете квалифицировать имена функций с помощью пути модуля:
julia> using .NiceStuff
julia> struct Cat end
julia> NiceStuff.nice(::Cat) = "nice 😸"
В качестве альтернативы вы можете import
конкретное имя функции:
julia> import .NiceStuff: nice
julia> struct Cat end
julia> nice(::Cat) = "nice 😸"
nice (generic function with 2 methods)
Какой из них вы выберете, зависит от стиля. Первая форма ясно показывает, что вы добавляете метод к функции в другом модуле (не забывайте, что импорты и определение метода могут находиться в отдельных файлах), в то время как вторая форма короче, что особенно удобно, если вы определяете несколько методов.
Как только переменная становится видимой через using
или import
, модуль не может создать свою собственную переменную с тем же именем. Импортированные переменные являются только для чтения; присваивание глобальной переменной всегда влияет на переменную, принадлежащую текущему модулю, или вызывает ошибку.
Renaming with as
Идентификатор, введенный в область видимости с помощью import
или using
, может быть переименован с помощью ключевого слова as
. Это полезно для обхода конфликтов имен, а также для сокращения имен. Например, Base
экспортирует имя функции read
, но пакет CSV.jl также предоставляет CSV.read
. Если мы собираемся многократно вызывать чтение CSV, было бы удобно избавиться от квалификатора CSV.
. Но тогда неясно, ссылаемся ли мы на Base.read
или CSV.read
:
julia> read;
julia> import CSV: read
WARNING: ignoring conflicting import of CSV.read into Main
Переименование предоставляет решение:
julia> import CSV: read as rd
Импортированные пакеты также могут быть переименованы:
import BenchmarkTools as BT
as
работает с using
только тогда, когда в область видимости вводится единственный идентификатор. Например, using CSV: read as rd
работает, но using CSV as C
не работает, так как это применяется ко всем экспортированным именам в CSV
.
Mixing multiple using
and import
statements
Когда используются несколько операторов using
или import
любой из приведенных выше форм, их эффект комбинируется в порядке их появления. Например,
julia> using .NiceStuff # exported names and the module name
julia> import .NiceStuff: nice # allows adding methods to unqualified functions
это приведет к тому, что все экспортированные имена NiceStuff
и само имя модуля будут доступны в области видимости, а также позволит добавлять методы к nice
без префикса с именем модуля.
Handling name conflicts
Рассмотрим ситуацию, когда два (или более) пакета экспортируют одно и то же имя, как в
julia> module A
export f
f() = 1
end
A
julia> module B
export f
f() = 2
end
B
Утверждение using .A, .B
работает, но когда вы пытаетесь вызвать f
, вы получаете ошибку с подсказкой.
julia> using .A, .B
julia> f
ERROR: UndefVarError: `f` not defined in `Main`
Hint: It looks like two or more modules export different bindings with this name, resulting in ambiguity. Try explicitly importing it from a particular module, or qualifying the name with the module it should come from.
Здесь Юлия не может решить, на какую f
вы ссылаетесь, поэтому вам нужно сделать выбор. Следующие решения часто используются:
Просто используйте квалифицированные имена, такие как
A.f
иB.f
. Это делает контекст ясным для читателя вашего кода, особенно еслиf
случайно совпадает, но имеет разное значение в различных пакетах. Например,degree
имеет различные значения в математике, естественных науках и в повседневной жизни, и эти значения должны быть разделены.Используйте ключевое слово
as
выше, чтобы переименовать один или оба идентификатора, например```jldoctest module_manual julia> using .A: f as f
julia> using .B: f as g
```
это сделает
B.f
доступным какg
. Здесь мы предполагаем, что вы не использовалиusing A
ранее, что привело бы к тому, чтоf
попал бы в пространство имен.Когда имена в вопросе действительно имеют общее значение, обычно один модуль импортирует его из другого или имеет легковесный "базовый" пакет с единственной функцией определения интерфейса, подобного этому, который могут использовать другие пакеты. Обычно такие имена пакетов заканчиваются на
...Base
(что не имеет ничего общего с модулемBase
в Julia).
Default top-level definitions and bare modules
Модули автоматически содержат using Core
, using Base
и определения функций eval
и include
, которые оценивают выражения/файлы в глобальной области видимости этого модуля.
Если эти стандартные определения не нужны, модули можно определить с помощью ключевого слова baremodule
вместо этого (заметьте: Core
все еще импортируется). В терминах baremodule
стандартный module
выглядит так:
baremodule Mod
using Base
eval(x) = Core.eval(Mod, x)
include(p) = Base.include(Mod, p)
...
end
Если даже Core
не нужен, модуль, который ничего не импортирует и не определяет никаких имен, можно определить с помощью Module(:YourNameHere, false, false)
, и код можно оценить в него с помощью @eval
или Core.eval
:
julia> arithmetic = Module(:arithmetic, false, false)
Main.arithmetic
julia> @eval arithmetic add(x, y) = $(+)(x, y)
add (generic function with 1 method)
julia> arithmetic.add(12, 13)
25
Standard modules
Существует три важных стандартных модуля:
Core
содержит все функции, "встроенные" в язык.Base
содержит базовую функциональность, которая полезна почти во всех случаях.Main
является модулем верхнего уровня и текущим модулем, когда Julia запускается.
По умолчанию Julia поставляется с некоторыми модулями стандартной библиотеки. Они ведут себя как обычные пакеты Julia, за исключением того, что вам не нужно устанавливать их явно. Например, если вы хотите провести модульное тестирование, вы можете загрузить стандартную библиотеку Test
следующим образом:
using Test
Submodules and relative paths
Модули могут содержать подмодули, используя ту же синтаксис module ... end
. Они могут быть использованы для введения отдельных пространств имен, что может быть полезно для организации сложных кодовых баз. Обратите внимание, что каждый module
вводит свой собственный scope, поэтому подмодули не «унаследуют» имена от своих родительских модулей автоматически.
Рекомендуется, чтобы подсистемы ссылались на другие модули внутри родительского модуля (включая последний) с использованием относительных квалификаторов модуля в операторах using
и import
. Относительный квалификатор модуля начинается с точки (.
), которая соответствует текущему модулю, и каждая последующая .
ведет к родителю текущего модуля. Это должно быть дополнено модулями, если это необходимо, и в конечном итоге фактическим именем для доступа, все разделенные .
.
Рассмотрим следующий пример, где подмодуль SubA
определяет функцию, которая затем расширяется в его "соседнем" модуле:
julia> module ParentModule
module SubA
export add_D # exported interface
const D = 3
add_D(x) = x + D
end
using .SubA # brings `add_D` into the namespace
export add_D # export it from ParentModule too
module SubB
import ..SubA: add_D # relative path for a “sibling” module
struct Infinity end
add_D(x::Infinity) = x
end
end;
Вы можете увидеть код в пакетах, который в аналогичной ситуации использует
julia> import .ParentModule.SubA: add_D
Однако это работает через code loading, и, следовательно, работает только если ParentModule
находится в пакете. Лучше использовать относительные пути.
Обратите внимание, что порядок определений также имеет значение, если вы оцениваете значения. Рассмотрите
module TestPackage
export x, y
x = 0
module Sub
using ..TestPackage
z = y # ERROR: UndefVarError: `y` not defined in `Main`
end
y = 1
end
где Sub
пытается использовать TestPackage.y
до того, как он был определен, поэтому у него нет значения.
По аналогичным причинам вы не можете использовать циклический порядок:
module A
module B
using ..C # ERROR: UndefVarError: `C` not defined in `Main.A`
end
module C
using ..B
end
end
Module initialization and precompilation
Большие модули могут загружаться несколько секунд, потому что выполнение всех операторов в модуле часто требует компиляции большого объема кода. Julia создает предварительно скомпилированные кэши модуля, чтобы сократить это время.
Предкомпилированные файлы модулей (иногда называемые "файлы кэша") создаются и используются автоматически, когда import
или using
загружает модуль. Если файлы кэша еще не существуют, модуль будет скомпилирован и сохранен для повторного использования в будущем. Вы также можете вручную вызвать Base.compilecache(Base.identify_package("modulename"))
, чтобы создать эти файлы без загрузки модуля. Полученные файлы кэша будут храниться в подпапке compiled
пути DEPOT_PATH[1]
. Если ничего в вашей системе не изменится, такие файлы кэша будут использоваться, когда вы загружаете модуль с помощью import
или using
.
Файлы кэша предварительной компиляции хранят определения модулей, типов, методов и констант. Они также могут хранить специализации методов и сгенерированный для них код, но это обычно требует от разработчика добавления явных precompile
директив или выполнения рабочих нагрузок, которые заставляют компиляцию во время сборки пакета.
Однако, если вы обновите зависимости модуля или измените его исходный код, модуль автоматически перекомпилируется при using
или import
. Зависимости — это модули, которые он импортирует, сборка Julia, файлы, которые он включает, или явные зависимости, объявленные include_dependency(path)
в файле(ах) модуля.
Для зависимостей файлов, загружаемых с помощью include
, изменение определяется путем проверки, изменился ли размер файла (fsize
) или содержимое (сжато в хэш). Для зависимостей файлов, загружаемых с помощью include_dependency
, изменение определяется путем проверки, изменилось ли время модификации (mtime
) или равно ли оно времени модификации, округленному до ближайшей секунды (чтобы учесть системы, которые не могут копировать mtime с точностью до долей секунды). Также учитывается, совпадает ли путь к файлу, выбранный логикой поиска в require
, с путем, который создал файл предварительной компиляции. Также учитывается набор зависимостей, уже загруженных в текущий процесс, и эти модули не будут перекомпилированы, даже если их файлы изменятся или исчезнут, чтобы избежать создания несовместимостей между работающей системой и кэшем предварительной компиляции. Наконец, учитываются изменения в любом compile-time preferences.
Если вы знаете, что модуль не безопасен для предварительной компиляции (например, по одной из причин, описанных ниже), вы должны поместить __precompile__(false)
в файл модуля (обычно размещается вверху). Это приведет к тому, что Base.compilecache
вызовет ошибку и заставит using
/ import
загружать его непосредственно в текущий процесс, пропуская предварительную компиляцию и кэширование. Это также предотвращает импорт модуля любым другим предварительно скомпилированным модулем.
Вам может потребоваться знать о некоторых поведениях, присущих созданию инкрементальных общих библиотек, которые могут потребовать осторожности при написании вашего модуля. Например, внешнее состояние не сохраняется. Чтобы учесть это, явно разделите любые шаги инициализации, которые должны происходить во время выполнения, от шагов, которые могут происходить во время компиляции. Для этой цели Julia позволяет вам определить функцию __init__()
в вашем модуле, которая выполняет любые шаги инициализации, которые должны происходить во время выполнения. Эта функция не будет вызываться во время компиляции (--output-*
). Фактически, вы можете предположить, что она будет выполнена ровно один раз за время жизни кода. Вы, конечно, можете вызвать ее вручную, если это необходимо, но по умолчанию предполагается, что эта функция занимается вычислением состояния для локальной машины, которое не нужно – или даже не должно быть – захвачено в скомпилированном образе. Она будет вызвана после загрузки модуля в процесс, включая случай, если он загружается в инкрементальную компиляцию (--output-incremental=yes
), но не если он загружается в процесс полной компиляции.
В частности, если вы определите function __init__()
в модуле, то Julia вызовет __init__()
немедленно после загрузки модуля (например, с помощью import
, using
или require
) во время выполнения в первый раз (т.е. __init__
вызывается только один раз и только после того, как все операторы в модуле были выполнены). Поскольку он вызывается после полного импорта модуля, любые подмодули или другие импортированные модули имеют свои функции __init__
, вызываемые до __init__
окружающего модуля.
Два типичных использования __init__
заключаются в вызове функций инициализации времени выполнения внешних C библиотек и инициализации глобальных констант, которые включают указатели, возвращаемые внешними библиотеками. Например, предположим, что мы вызываем C библиотеку libfoo
, которая требует от нас вызова функции инициализации foo_init()
во время выполнения. Предположим, что мы также хотим определить глобальную константу foo_data_ptr
, которая хранит возвращаемое значение функции void *foo_data()
, определенной в libfoo
– эта константа должна быть инициализирована во время выполнения (а не на этапе компиляции), потому что адрес указателя будет изменяться от запуска к запуску. Вы можете достичь этого, определив следующую функцию __init__
в вашем модуле:
const foo_data_ptr = Ref{Ptr{Cvoid}}(0)
function __init__()
ccall((:foo_init, :libfoo), Cvoid, ())
foo_data_ptr[] = ccall((:foo_data, :libfoo), Ptr{Cvoid}, ())
nothing
end
Обратите внимание, что вполне возможно определить глобальную переменную внутри функции, такой как __init__
; это одно из преимуществ использования динамического языка. Но сделав её константой на глобальном уровне, мы можем гарантировать, что тип известен компилятору и позволить ему генерировать более оптимизированный код. Очевидно, что любые другие глобальные переменные в вашем модуле, которые зависят от foo_data_ptr
, также должны быть инициализированы в __init__
.
Константы, касающиеся большинства объектов Julia, которые не создаются с помощью ccall
, не нужно помещать в __init__
: их определения могут быть предварительно скомпилированы и загружены из кэшированного изображения модуля. Это включает в себя сложные объекты, выделенные в куче, такие как массивы. Однако любая процедура, которая возвращает значение сырого указателя, должна вызываться во время выполнения, чтобы предварительная компиляция работала (Ptr
объекты превратятся в нулевые указатели, если они не скрыты внутри объекта isbits
). Это включает в себя возвращаемые значения функций Julia @cfunction
и pointer
.
Типы словарей и множеств, или, в общем, все, что зависит от вывода метода hash(key)
, являются более сложным случаем. В обычном случае, когда ключи представляют собой числа, строки, символы, диапазоны, Expr
или их комбинации (через массивы, кортежи, множества, пары и т. д.), их безопасно предварительно компилировать. Однако для некоторых других типов ключей, таких как Function
или DataType
, а также для обобщенных пользовательских типов, для которых вы не определили метод hash
, метод hash
по умолчанию зависит от адреса памяти объекта (через его objectid
) и, следовательно, может изменяться от запуска к запуску. Если у вас есть один из этих типов ключей, или если вы не уверены, для безопасности вы можете инициализировать этот словарь из вашей функции __init__
. В качестве альтернативы вы можете использовать тип словаря IdDict
, который специально обрабатывается при предварительной компиляции, так что его безопасно инициализировать на этапе компиляции.
При использовании предварительной компиляции важно четко понимать различие между фазой компиляции и фазой выполнения. В этом режиме будет гораздо более очевидно, что Julia является компилятором, который позволяет выполнять произвольный код Julia, а не отдельным интерпретатором, который также генерирует скомпилированный код.
Другие известные потенциальные сценарии отказа включают:
Глобальные счетчики (например, для попытки уникально идентифицировать объекты). Рассмотрим следующий фрагмент кода:
julia mutable struct UniquedById myid::Int let counter = 0 UniquedById() = new(counter += 1) end end
в то время как целью этого кода было присвоить каждому экземпляру уникальный идентификатор, значение счетчика фиксируется в конце компиляции. Все последующие использования этого инкрементально скомпилированного модуля будут начинаться с того же значения счетчика.
Обратите внимание, что
objectid
(который работает путем хеширования указателя памяти) имеет аналогичные проблемы (см. примечания по использованиюDict
ниже).Одной из альтернатив является использование макроса для захвата
@__MODULE__
и хранения его отдельно с текущим значениемcounter
, однако, возможно, лучше перепроектировать код, чтобы он не зависел от этого глобального состояния.Ассоциативные коллекции (такие как
Dict
иSet
) необходимо повторно хешировать в__init__
. (В будущем может быть предоставлен механизм для регистрации функции инициализации.)В зависимости от побочных эффектов времени компиляции, сохраняющихся на этапе загрузки. Примеры включают: модификацию массивов или других переменных в других модулях Julia; поддержание дескрипторов для открытых файлов или устройств; хранение указателей на другие системные ресурсы (включая память);
Создание случайных "копий" глобального состояния из другого модуля, ссылаясь на него напрямую, а не через его путь поиска. Например, (в глобальной области):
```julia #mystdout = Base.stdout #= will not work correctly, since this will copy Base.stdout into this module =#
instead use accessor functions:
getstdout() = Base.stdout #= best option =#
or move the assignment into the runtime:
init() = global mystdout = Base.stdout #= also works =# ```
На операции, которые можно выполнять при предварительной компиляции кода, накладываются несколько дополнительных ограничений, чтобы помочь пользователю избежать других ситуаций неправильного поведения:
- Вызов
eval
для вызова побочного эффекта в другом модуле. Это также вызовет предупреждение, когда установлен флаг инкрементальной предварительной компиляции. global const
операторы из локальной области видимости после начала__init__()
(см. проблему #12010 для планов по добавлению ошибки для этого)- Замена модуля является ошибкой времени выполнения при выполнении инкрементальной предварительной компиляции.
Несколько других моментов, о которых стоит помнить:
- Не выполняется перезагрузка кода / недействительность кэша после внесения изменений в исходные файлы, (включая
Pkg.update
), и очистка не выполняется послеPkg.rm
- Поведение совместного использования памяти измененного массива игнорируется при предварительной компиляции (каждый вид получает свою собственную копию)
- Ожидание, что файловая система останется неизменной между временем компиляции и временем выполнения, например,
@__FILE__
/source_path()
для поиска ресурсов во время выполнения, или макрос BinDeps@checked_lib
. Иногда это неизбежно. Однако, когда это возможно, хорошей практикой может быть копирование ресурсов в модуль на этапе компиляции, чтобы их не нужно было искать во время выполнения. - Объекты
WeakRef
и финализаторы в настоящее время не обрабатываются должным образом сериализатором (это будет исправлено в одном из будущих релизов). - Обычно лучше избегать захвата ссылок на экземпляры внутренних объектов метаданных, таких как
Method
,MethodInstance
,MethodTable
,TypeMapLevel
,TypeMapEntry
и поля этих объектов, так как это может запутать сериализатор и не привести к желаемому результату. Это не обязательно ошибка, но вам просто нужно быть готовым к тому, что система попытается скопировать некоторые из них и создать единственный уникальный экземпляр других.
Иногда во время разработки модуля полезно отключить инкрементальную предкомпиляцию. Флаг командной строки --compiled-modules={yes|no|existing}
позволяет вам включать и отключать предкомпиляцию модулей. Когда Julia запускается с --compiled-modules=no
, сериализованные модули в кэше компиляции игнорируются при загрузке модулей и зависимостей модулей. В некоторых случаях вы можете захотеть загрузить существующие предкомпилированные модули, но не создавать новые. Это можно сделать, запустив Julia с --compiled-modules=existing
. Более детальный контроль доступен с помощью --pkgimages={yes|no|existing}
, который влияет только на хранение нативного кода во время предкомпиляции. Base.compilecache
все еще можно вызывать вручную. Состояние этого флага командной строки передается в Pkg.build
, чтобы отключить автоматическую активацию предкомпиляции при установке, обновлении и явной сборке пакетов.
Вы также можете отлаживать некоторые ошибки предварительной компиляции с помощью переменных окружения. Установка JULIA_VERBOSE_LINKING=true
может помочь решить проблемы с линковкой общих библиотек скомпилированного нативного кода. См. раздел Документация для разработчиков в руководстве Julia, где вы найдете дополнительные детали в разделе, документирующем внутренности Julia в разделе "Образы пакетов".