Модули
Модули в Julia помогают организовать код в связные единицы. Они разграничены синтаксически внутри module NameOfModule ... end, и обладают следующими особенностями:
Модули являются отдельными именованными пространствами, каждый из которых вводит новую глобальную область видимости. Это полезно, потому что позволяет использовать одно и то же имя для различных функций или глобальных переменных без конфликтов, при условии, что они находятся в разных модулях.
Модули имеют средства для подробного управления именованными пространствами: каждый определяет набор имён, которые он
exportи помечает какpublic, и может импортировать имена из других модулей с помощью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
Управление именованным пространством
Управление именованным пространством относится к средствам, предоставляемым языком для обеспечения доступности имён в одном модуле в других модулях. Мы подробно обсудим связанные понятия и функциональность ниже.
Квалифицированные имена
Имена функций, переменных и типов в глобальной области видимости, такие как sin, ARGS, и UnitRange всегда принадлежат модулю, называемому родительским модулем, который можно найти интерактивно с помощью parentmodule, например
julia> parentmodule(UnitRange) Base
Эти имена также можно ссылаться за пределами родительского модуля, предваряя их именем своего модуля, например Base.UnitRange. Это называется квалифицированным именем. Родительский модуль может быть доступен с помощью цепочки подмодулей, как Base.Math.sin, где Base.Math называется путь модуля. Из-за синтаксических неоднозначностей квалификация имени, содержащего только символы, например оператор, требует вставки двоеточия, например Base.:+. Небольшое количество операторов дополнительно требуют скобок, например Base.:(==).
Если имя квалифицировано, оно всегда доступно, и в случае функции к нему также можно добавить методы, используя квалифицированное имя как имя функции.
Внутри модуля имя переменной может быть «зарезервировано» без присвоения ему значения, объявив её как global x. Это предотвращает конфликты имён для глобальных переменных, инициализированных после загрузки. Синтаксис M.x = y не работает для присвоения глобальной переменной в другом модуле; глобальное присваивание всегда локально для модуля.
Списки экспорта
Имена (функций, типов, глобальных переменных и констант) могут быть добавлены в список экспорта модуля с помощью export: это символы, которые импортируются при using модуля. Обычно они находятся в начале или около начала определения модуля, чтобы читатели исходного кода могли легко их найти, как в
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.
Самостоятельные using и import
Для интерактивного использования наиболее распространенный способ загрузки модуля — using ModuleName. Это загружает код, связанный с 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 и import со специфическими идентификаторами и добавлением методов
Когда 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.
В Julia есть две формы для, по-видимому, одинаковых действий, потому что только 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, модуль не может создать свою собственную переменную с тем же именем. Импортированные переменные являются только для чтения; присвоение значения глобальной переменной всегда влияет на переменную, принадлежащую текущему модулю, или же вызывает ошибку.
Переименование с 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.
Смешивание нескольких using и import операторов
При использовании нескольких операторов using или import любых из вышеперечисленных форм, их эффект комбинируется в порядке их появления. Например,
julia> using .NiceStuff # exported names and the module name julia> import .NiceStuff: nice # allows adding methods to unqualified functions
внесёт в область видимости все экспортированные имена NiceStuff и само имя модуля, а также позволит добавлять методы к nice без префикса имени модуля.
Обработка конфликтов имён
Рассмотрим ситуацию, когда два (или более) пакета экспортируют одно и то же имя, как в примере
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.
В этом случае Julia не может определить, к какому f вы обращаетесь, поэтому вам необходимо сделать выбор. Обычно используются следующие решения:
Просто используйте квалифицированные имена, например,
A.fиB.f. Это делает контекст ясным для читателя вашего кода, особенно еслиfпросто совпадает по имени, но имеет разное значение в разных пакетах. Например,degreeиспользуется в математике, естественных науках и в повседневной жизни, и эти значения следует различать.-
Используйте ключевое слово
asдля переименования одного или обоих идентификаторов, напримерjulia> using .A: f as f julia> using .B: f as g
сделает
B.fдоступным какg. Здесь мы предполагаем, что вы не использовалиusing Aранее, что привело быfв пространство имён. Если имена действительно имеют общее значение, обычно один модуль импортирует его из другого или существует лёгкий «базовый» пакет, единственная функция которого — определение интерфейса, например, который может использоваться другими пакетами. Обычно имена таких пакетов оканчиваются на
...Base(что не имеет никакого отношения к модулюBaseJulia).
По умолчанию определения верхнего уровня и модули без инициализации
Модули автоматически содержат 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
Стандартные модули
Существует три важных стандартных модуля:
-
Coreсодержит всю функциональность, "встроенную" в язык. -
Baseсодержит базовые функции, полезные практически во всех случаях. -
Mainявляется модулем верхнего уровня и текущим модулем при запуске Julia.
По умолчанию Julia поставляется с некоторыми стандартными модулями библиотеки. Они ведут себя как обычные пакеты Julia, за исключением того, что их не нужно устанавливать явно. Например, если вы хотели выполнить некоторые unit-тесты, вы могли бы загрузить стандартную библиотеку Test следующим образом:
using Test
Подмодули и относительные пути
Модули могут содержать подмодули, вложенные с тем же синтаксисом module ... end. Они могут использоваться для создания отдельных пространств имён, что полезно для организации сложных кодовых баз. Обратите внимание, что каждый module создаёт своё собственное пространство имён, поэтому подмодули не «унаследуют» имена автоматически от своих родительских модулей.
Рекомендуется, чтобы подмодули ссылались на другие модули внутри содержащего родительского модуля (включая последний) с использованием относительных квалификаторов модулей в операторах 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
Однако это работает через загрузку кода и, следовательно, работает только если 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
Инициализация и предварительная компиляция модулей
Большие модули могут занимать несколько секунд для загрузки, так как выполнение всех операторов в модуле часто предполагает компиляцию большого объёма кода. 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, пути, который создал файл предварительной компиляции. Также учитывается набор зависимостей, уже загруженных в текущий процесс, и не будет перекомпилировать эти модули, даже если их файлы изменятся или исчезнут, чтобы избежать создания несовместимостей между работающей системой и кэшем предварительной компиляции. Наконец, учитываются изменения в параметрах компиляции.
Если вы знаете, что модуль не безопасен для предварительной компиляции (например, по одной из причин, описанных ниже), вам следует поместить __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, а не автономный интерпретатор, который также генерирует скомпилированный код.
Другие известные потенциальные сценарии сбоев включают:
-
Глобальные счётчики (например, для попытки уникальной идентификации объектов). Рассмотрим следующий фрагмент кода:
mutable struct UniquedById myid::Int let counter = 0 UniquedById() = new(counter += 1) end endхотя цель этого кода заключалась в присвоении каждому экземпляру уникального идентификатора, значение счётчика фиксируется в конце компиляции. Все последующие использования этого инкрементально скомпилированного модуля будут начинаться с того же значения счётчика.
Обратите внимание, что
objectid(который работает путём хеширования указателя в памяти) имеет аналогичные проблемы (см. заметки об использованииDictниже).Одним из вариантов является использование макроса для захвата
@__MODULE__и сохранения его вместе с текущим значениемcounter, однако может быть лучше переработать код, чтобы он не зависел от этого глобального состояния. Ассоциативные коллекции (такие как
DictиSet) должны быть перехешированы в__init__. (В будущем может быть предоставлен механизм для регистрации функции инициализации.)Зависимость от побочных эффектов во время компиляции, сохраняющихся во время загрузки. Примеры включают: изменение массивов или других переменных в других модулях 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 в разделе «Изображения пакетов».
© 2009–2024 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.11/manual/modules/