para - poligono en r
Espacios de nombres sin paquetes (6)
Al reorganizar mi base de código me gustaría limpiar mi mecanismo de intercambio de código. Hasta ahora estoy usando la source
de muchos módulos de funcionalidad pequeños, en gran parte independientes.
Sin embargo, este enfoque tiene una serie de problemas, entre ellos
- la falta de pruebas de circularidad (cadenas de
source
circulares accidentales), - se requiere una sintaxis compleja para especificar correctamente las rutas de inclusión (
chdir=TRUE
argumentochdir=TRUE
, rutas codificadas), - potencial de choques de nombre (al redefinir objetos).
Idealmente, me gustaría obtener algo parecido al mecanismo del módulo Python. El mecanismo del paquete R sería excesivo aquí: no quiero generar jerarquías de rutas anidadas, múltiples archivos con toneladas de metadatos y compilar manualmente el paquete solo para obtener un módulo de código pequeño, autónomo y reutilizable.
Por ahora estoy usando un fragmento de código que me permite resolver los dos primeros problemas mencionados anteriormente. La sintaxis para la inclusión es así:
import(functional)
import(io)
import(strings)
... y un módulo se define como un archivo fuente simple que reside en la ruta local. La definición de import
es sencilla, pero no puedo resolver el tercer punto: quiero importar el módulo a un espacio de nombres separado, pero por lo que veo, el mecanismo de búsqueda de espacios de nombres está bastante conectado a los paquetes. Es cierto que podría anular `::`
o getExportedValue
y quizás como asNamespace
y isNamespace
pero eso se siente muy sucio y tiene el potencial de romper otros paquetes.
Aquí hay una función que automatiza completamente la creación, compilación y recarga de paquetes. Como otros lo han señalado, las funciones de utilidad package.skeleton()
y devtools::load_all()
ya lo llevan casi todo el camino. Esto simplemente combina su funcionalidad, utilizando package.skeleton()
para crear el directorio de origen en un directorio temporal que se limpia cuando load_all()
procesarlo.
Todo lo que necesita hacer es apuntar a los archivos de origen desde los que desea leer las funciones y darle un nombre al paquete: import()
hace el resto por usted.
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."
He implementado una solución integral y la publiqué como un paquete, ‹modules› .
Internamente, los módulos utilizan un enfoque similar a los paquetes; es decir, carga el código dentro de un entorno de espacio de nombres dedicado y luego exporta (= copia) los símbolos seleccionados a un entorno de módulo que se devuelve al usuario y se adjunta opcionalmente.
El uso del paquete se describe en detalle en su sitio web.
Konrad, con toda seriedad, la respuesta a la demanda.
para obtener un módulo de código pequeño, autónomo y reutilizable
Es crear un paquete. Ese evangelio ha sido repetido varias veces aquí en SO, y en otros lugares. De hecho, puedes crear paquetes mínimos con un mínimo de fuzz.
Además, después de correr
setwd("/tmp")
package.skeleton("konrad")
y eliminando el único archivo temporal, me quedo con
edd@max:/tmp$ tree konrad/
konrad/
├── DESCRIPTION
├── man
│ └── konrad-package.Rd
└── NAMESPACE
1 directory, 3 files
edd@max:/tmp$
¿Es eso realmente tan oneroso?
Mi comentario a la pregunta del OP no fue del todo correcto, pero creo que esta reescritura de la función de import
funciona. foo.R
y bar.R
son archivos en el directorio de trabajo actual que contienen una sola función ( baz
) que imprime la salida que se muestra a continuación.
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"
Tenga en cuenta que baz()
por sí mismo no se encontrará, pero el OP parecía querer la explicación de ::
todos modos.
Soy totalmente simpático con la respuesta de @ Dirk. La pequeña sobrecarga involucrada en hacer un paquete mínimo parece valer para ajustarse a una "forma estándar".
Sin embargo, una cosa que me vino a la mente es local
argumento local
source
, que le permite acceder a un environment
, que podría usar como un espacio de nombres, por ejemplo
assign(module, new.env(parent=baseenv()), envir=topenv())
source(filename, local=get(module, topenv()), chdir = TRUE)
Para acceder a estos entornos importados con una sintaxis simple, asigne a estos entornos de importación una nueva clase (por ejemplo, ''importar''), y haga que ::
genérico, el valor getExportedValue
cuando pkg
no existe.
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)
}
Actualizar
A continuación se muestra una opción más segura que evitaría errores en el caso de que un paquete cargado contenga un objeto con el mismo nombre que un paquete al que se accede con ::
.
''::'' <- 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)
}
Esto daría el resultado correcto, por ejemplo, si cargó data.table
y posteriormente intentó acceder a uno de sus objetos con ::
.
Un paquete es solo una convención donde almacenar archivos (archivos R en R/
, docs en man/
, código compilado en src
, datos en data/
): si tiene más de un puñado de archivos, es mejor que se quede con convención establecida. En otras palabras, usar un paquete es más fácil que no usarlo, porque no necesita pensar: simplemente puede aprovechar las convenciones existentes y cada usuario R comprenderá lo que está pasando.
Todo lo que un paquete mínimo realmente necesita es un archivo de DESCRIPTION
, que diga lo que hace el paquete, quién puede usarlo (la licencia) y con quién ponerse en contacto si hay problemas (el mantenedor). Esto es un poco de una sobrecarga, pero no es importante. Una vez que hayas escrito eso, solo debes completar los directorios adicionales a medida que los necesites, sin la necesidad de utilizar el package.skeleton()
.
Dicho esto, las herramientas integradas para trabajar con paquetes son incómodas: tiene que volver a compilar / reinstalar el paquete, reiniciar R y volver a cargar el paquete. Ahí es donde devtools::load_all()
y Rstudio''s build & reload: usan la misma especificación para un paquete, pero proporcionan formas más fáciles de actualizar un paquete desde la fuente. Por supuesto, puede usar los fragmentos de código proporcionados por las otras respuestas, pero ¿por qué no usar un código bien probado que es usado por cientos (bueno, decenas al menos) de desarrolladores de R?