Подтвердить что ты не робот

Пространства имен без пакетов

При реорганизации моего идентификатора базы кода, как для очистки моего механизма обмена кодами. Пока что я использую source для множества небольших, в основном автономных модулей функциональности.

Однако этот подход страдает рядом проблем, среди них

  • отсутствие тестов для округлости (случайные круговые цепочки source),
  • сложный синтаксис, необходимый для правильного указания включенных путей (аргумент chdir=TRUE, жестко закодированные пути),
  • потенциал конфликтов имен (при переопределении объектов).

В идеале Id нравится что-то похожее на механизм модуля Python. Механизм пакета R будет здесь излишним: Я не хочу генерировать иерархию вложенных путей, несколько файлов с множеством метаданных и вручную создавать пакет, чтобы получить небольшой автономный модуль многократного использования.

Теперь Im использует фрагмент кода, который позволяет мне решить первые две проблемы, упомянутые выше. Синтаксис включения таков:

import(functional)
import(io)
import(strings)

... и модуль определяется как простой исходный файл, который находится в локальном пути. Определение import прост, но я не могу решить третий вопрос: я хочу импортировать модуль в отдельное пространство имен, но из того, что вижу механизм поиска пространства имен довольно жестко связан с пакетами. Правда, я мог бы переопределить `::` или getExportedValue и, возможно, asNamespace и isNamespace, но это очень грязно и может нарушить другие пакеты.

4b9b3361

Ответ 1

Здесь функция, которая полностью автоматизирует создание, компиляцию и перезагрузку пакетов. Как отмечали другие, функции утилиты package.skeleton() и devtools::load_all() уже добираются почти до конца. Это просто объединяет их функциональность, используя package.skeleton() для создания исходного каталога в каталоге temp, который очищается, когда load_all() завершается обработка его.

Все, что вам нужно сделать, это указать исходные файлы, из которых вы хотите читать в функциях, и дать пакету имя: import() делает для вас все остальное.

import <- function(srcFiles, pkgName) {
    require(devtools)
    dd <- tempdir()
    on.exit(unlink(file.path(dd, pkgName), recursive=TRUE))
    package.skeleton(name=pkgName, path = dd, code_files=srcFiles)
    load_all(file.path(dd, pkgName))
}

## Create a couple of example source files
cat("bar <- function() {print('Hello World')}", file="bar.R")
cat("baz <- function() {print('Goodbye, cruel world.')}", file="baz.R")

## Try it out
import(srcFiles=c("bar.R", "baz.R"), pkgName="foo")

## Check that it worked
head(search())
# [1] ".GlobalEnv"        "package:foo"       "package:devtools"
# [4] "package:stats"     "package:graphics"  "package:grDevices"
bar()
# [1] "Hello World"
foo::baz()
# [1] "Goodbye, cruel world."

Ответ 2

Конрад, со всей серьезностью, ответ на требование

чтобы получить небольшой, автономный модуль многократного использования

- создать пакет. Это Евангелие неоднократно повторялось здесь, на SO, и в других местах. Фактически вы можете создавать минимальные пакеты с минимальным пухом.

Кроме того, после запуска

 setwd("/tmp")
 package.skeleton("konrad")

и удалив один временный файл, я остаюсь с

 [email protected]:/tmp$ tree konrad/
 konrad/
 ├── DESCRIPTION
 ├── man
 │   └── konrad-package.Rd
 └── NAMESPACE

 1 directory, 3 files
 [email protected]:/tmp$ 

Является ли это действительно обременительным?

Ответ 3

Пакет - это просто соглашение о том, где хранить файлы (файлы R в R/, docs в man/, скомпилированный код в src, данные в data/): если у вас больше, чем несколько файлы, лучше всего придерживаться установленной конвенции. Другими словами, использование пакета проще, чем использование пакета, потому что вам не нужно думать: вы можете просто использовать существующие соглашения, и каждый пользователь R поймет, что происходит.

Для всего минимального пакета необходим файл DESCRIPTION, в котором говорится, что делает пакет, кто может его использовать (лицензия) и с кем связаться, если есть проблемы (сопровождающий). Это немного накладные расходы, но это не главное. Как только вы это написали, вы просто заполняете дополнительные каталоги по мере необходимости - не нужно неуклюжие package.skeleton().

Тем не менее, встроенные инструменты для работы с пакетами громоздки - вам нужно перестроить/переустановить пакет, перезапустить R и перезагрузить пакет. Что там, где devtools::load_all() и Rstudio build and reload входят - они используют ту же спецификацию для пакета, но предоставляют более простые способы обновления пакета из исходного кода. Вы можете, конечно, использовать фрагменты кода, предоставленные другими ответами, но почему бы не использовать хорошо протестированный код, который использовался сотнями (ну, по меньшей мере, десятками) разработчиков R?

Ответ 4

Мой комментарий к вопросу OP был не совсем правильным, но я думаю, что эта перезапись функции import делает трюк. foo.R и bar.R - это файлы в текущем рабочем каталоге, которые содержат одну функцию (baz), которая выводит результат, показанный ниже.

import <- function (module) {
  module <- as.character(substitute(module))
  # Search path handling omitted for simplicity.
  filename <- paste(module, 'R', sep = '.')
  # create imports environment if it doesn't exist
  if ("imports" %in% search())
    imports <- as.environment(match("imports",search()))
  # otherwise get the imports environment
  else
    imports <- attach(NULL, name="imports")
  if (module %in% ls("imports"))
    return()
  # create a new environment (imports as parent)
  env <- new.env(parent=imports)
  # source file into env
  sys.source(filename, env)
  # ...and assign env to imports as "module name"
  assign(module, env, imports)
}
setwd(".")
import(foo)
import(bar)
foo$baz()
# [1] "Hello World"
bar$baz()
# [1] "Buh Bye"

Обратите внимание, что baz() сам по себе не будет найден, но OP, казалось, все равно нуждался в объяснении ::.

Ответ 5

Я полностью сочувствую ответу @Dirk. Небольшие накладные расходы, связанные с созданием минимального пакета, кажутся подходящими для "стандартного способа".

Однако одна вещь, которая пришла на ум, - это аргумент source local, позволяющий вам вводить исходный код в environment, который можно использовать как пространство имен, например

assign(module, new.env(parent=baseenv()), envir=topenv())
source(filename, local=get(module, topenv()), chdir = TRUE)

Чтобы получить доступ к этим импортированным средам с помощью простого синтаксиса, дайте этим средам импорта новый класс (скажем, "импорт" ) и создайте :: generic, по умолчанию getExportedValue, если pkg не существует.

import <- function (module) {
    module <- as.character(substitute(module))
    # Search path handling omitted for simplicity.
    filename <- paste(module, 'R', sep = '.')

    e <- new.env(parent=baseenv())
    class(e) <- 'import'
    assign(module, e, envir=topenv())
    source(filename, local=get(module, topenv()), chdir = TRUE)
}

'::.import' <- function(env, obj) get(as.character(substitute(obj)), env)
'::' <- function(pkg, name) {
    pkg <- as.character(substitute(pkg))
    name <- as.character(substitute(name))
    if (exists(pkg)) UseMethod('::')
    else getExportedValue(pkg, name)
}

Update

Ниже приведен более безопасный вариант, который предотвращает ошибки в том случае, если загруженный пакет содержит объект с тем же именем, что и пакет, к которому обращаются с помощью ::.

'::' <- function(pkg, name) {
    pkg.chr <- as.character(substitute(pkg))
    name.chr <- as.character(substitute(name))
    if (exists(pkg.chr)) {
        if (class(pkg) == 'import')
            return(get(name.chr, pkg))
    }
    getExportedValue(pkg.chr, name.chr)
}

Это даст правильный результат, скажем, если вы загрузили data.table и впоследствии попытались получить доступ к одному из своих объектов с помощью ::.

Ответ 6

Ive внедрила комплексное решение и опубликовала его как пакет, .

Внутренне модули используют подход, похожий на пакеты; то есть он загружает код внутри выделенной среды пространства имен, а затем export ( = копии) выбранные символы в среде модуля, которые возвращаются пользователю и необязательно прикреплены.

Использование пакета подробно описано на его веб-сайте.