Реализация методов загрузки и сохранения
Принцип работы: квалификация модуля
Когда пакет FileIO обнаруживает, что файл или поток должны обрабатываться определенным пакетом, он пытается вызвать частные методы из этого пакета для обработки запроса. Например, предположим, что вы создали пакет MyFileFormat
для обработки файлов определенного формата. При вызове load("somefile.myfmt")
для подходящего файла пакет FileIO выполнит следующие действия:
-
попытается загрузить ваш пакет
MyFileFormat
с помощьюBase.require(id::PkgId)
, гдеPkgId
— это сочетание имени иUUID
, которые вы предоставили посредствомadd_format
; -
вызовет
MyFileFormat.load(file)
, гдеfile
— это объектFile
.
Ключевым моментом является то, что MyFileFormat.load
не расширяет FileIO.load
: это частная функция, определенная в модуле MyFileFormat
. Это необходимо для того, чтобы один формат мог поддерживаться несколькими пакетами; если бы в двух или более пакетах метод File.load
специализировался для file::File{format"MYFORMAT"})
, то инструкция
using Pkg1, Pkg2 # два пакета, неправильно расширяющие FileIO.load
привела бы к обработке всех операций загрузки пакетом Pkg2
, но инструкция
using Pkg2, Pkg1
привела бы к их обработке пакетом Pkg1
. Это сделало бы загрузку очень ненадежной. По этой причине важно, чтобы метод load
был частным методом пакета, а пакет FileIO вызывал его посредством квалификации модуля.
То же самое относится к методам save
, loadstreaming
и savestreaming
.
Если происходит конфликт именования функций load
и save
(например, в вашем пакете уже есть другая функция с одним из этих имен), вы можете назвать методы загрузки fileio_load
, fileio_save
и т. д. Обратите внимание, что смешивать эти стили нельзя: либо все методы загрузки должны называться load
, либо все они должны называться fileio_load
. Сочетать оба варианта в одном модуле нельзя.
Единовременный ввод-вывод: реализация load
и save
В пакете напишите код наподобие следующего:
module MyFileFormat
using FileIO
# Еще раз напомним, что это *частная* функция `load`, она не расширяет `FileIO.load`!
function load(f::File{format"PNG"})
open(f) do s
skipmagic(s) # пропускаем магические байты
# Вы можете просто вызвать приведенный ниже метод `load(::Stream)...
ret = load(s)
# ...или же реализовать все здесь
end
end
# Вы можете обеспечить поддержку потоков и добавить именованные аргументы:
function load(s::Stream{format"PNG"}; keywords...)
# s уже находится в позиции после магических байтов
# Инструкции для считывания файла PNG
chunklength = read(s, UInt32)
...
end
function save(f::File{format"PNG"}, data)
open(f, "w") do s
# Не забудьте записать магические байты!
write(s, magic(format"PNG"))
# Остальные инструкции, необходимые для сохранения в формате PNG
end
end
end # module MyFileFormat
Методы load(::File)
и save(::File)
должны закрывать открытые ими потоки. (При использовании синтаксиса do
это происходит автоматически, даже если код в области do
выдает ошибку.) Напротив, методы load(::Stream)
и save(::Stream)
не должны закрывать переданные в аргументе потоки.
Реализация потокового ввода-вывода
Методы loadstreaming
и savestreaming
используют тот же механизм запросов, но возвращают декодированный поток, который пользователи могут прочитать (read
) или записать (write
). В типе метода чтения или записи следует также реализовать метод close
. Как и в случае с load
и save
, если пользователь предоставил имя файла, метод close
должен отвечать за закрытие всех потоков, открытых с целью чтения или записи файла. Если предоставлен поток (Stream
), метод close
должен выполнять только очистку для типа метода чтения или записи, но не закрывать поток.
struct WAVReader
io::IO
ownstream::Bool
end
function Base.read(reader::WAVReader, frames::Int)
# считываем и декодируем звуковые фрагменты из reader.io
end
function Base.close(reader::WAVReader)
# выполняем очистку, необходимую после считывания данных
reader.ownstream && close(reader.io)
end
# В FileIO есть альтернативные функции, которые выполняют эти задачи с использованием синтаксиса `do`
# и автоматически вызывают `close` для возвращенного объекта.
loadstreaming(f::File{format"WAV"}) = WAVReader(open(f), true)
loadstreaming(s::Stream{format"WAV"}) = WAVReader(s, false)
Если вы решите реализовать loadstreaming
и savestreaming
в своем пакете, то можете легко добавить методы save
и load
в следующей форме:
function save(q::Formatted{format"WAV"}, data, args...; kwargs...)
savestreaming(q, args...; kwargs...) do stream
write(stream, data)
end
end
function load(q::Formatted{format"WAV"}, args...; kwargs...)
loadstreaming(q, args...; kwargs...) do stream
read(stream)
end
end
где Formatted
— это абстрактный супертип типов File
и Stream
.