Модули
Модули в Julia помогают организовать код в связные единицы. Они разделяются синтаксически внутри module NameOfModule ... end, и обладают следующими особенностями:
Модули являются отдельными именованными пространствами, каждый из которых вводит новую глобальную область видимости. Это полезно, потому что позволяет использовать одно и то же имя для разных функций или глобальных переменных без конфликтов, если они находятся в отдельных модулях.
Модули обладают средствами для подробного управления именованными пространствами: каждый определяет набор имён, которые он
exportет, и может импортировать имена из других модулей с помощьюusingиimport(мы объясним их ниже).Модули могут быть предварительно скомпилированы для более быстрого загрузки и могут содержать код для инициализации во время выполнения.
Обычно в больших пакетах Julia вы увидите код модулей, организованный в файлы, например
module SomeModule
# export, 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 и 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, Statistics
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 WARNING: both B and A export "f"; uses of it in module Main must be qualified ERROR: UndefVarError: `f` not defined
Здесь 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, за исключением того, что их не нужно явно устанавливать. Например, если вы хотите выполнить некоторые модульные тесты, вы можете загрузить стандартную библиотеку 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 end y = 1 end
где Sub пытается использовать TestPackage.y до его определения, поэтому у него нет значения.
По аналогичным причинам нельзя использовать циклический порядок:
module A module B using ..C # ERROR: UndefVarError: `C` not defined 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) в файлах модуля.
Для зависимостей от файлов изменение определяется путём проверки, не изменилось ли время изменения (mtime) каждого файла, загруженного include или явно добавленного include_dependency, или равно ли оно времени изменения, усечённому до ближайшей секунды (чтобы учитывать системы, которые не могут копировать 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} позволяет переключать предварительную компиляцию модулей включить/выключить. При запуске Julia с --compiled-modules=no сериализованные модули в кэше компиляции игнорируются при загрузке модулей и зависимостей от модулей. Более тонкий контроль доступен с помощью --pkgimages=no, который подавляет только хранение кода нативных языков во время предварительной компиляции. 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.10/manual/modules/