How to make variable available to namespace at loading time
Asked Answered
T

2

15

In one of my packages I use the .onAttach hook to run some R code and then use assign to make the value available as one of the package variables. I do it because variable depends on the content of some file, which can change between one session and the other. The code I use is like:

.onAttach <- function(libname, pkgname) {
   variable <- some_function()
   assign("variable", variable, envir = as.environment("package:MyRPackage"))
}

When I attach the package with library(MyRpackage) I can use variable.

However it is not possible to do something like MyRPackage::variable (unless I have already attached the package with library(MyRpackage).

I know this is because I should put that code in the .onLoad hook, however I can't make the assignment so that it works.

I have tried

.onLoad <- function(libname, pkgname) {
   variable <- some_function()
   assign("variable", variable, envir = as.environment("namesoace:MyRPackage"))
}

and

.onLoad <- function(libname, pkgname) {
   variable <- some_function()
   assign("variable", variable, envir = asNamespace("MyRPackage"))
}

but both of them fail with some error when I run MyRPackage:::variable without using library to attach the package.

What is the correct to do the assignment in the .onLoad hook?

Twilatwilight answered 1/3, 2018 at 18:46 Comment(7)
Can you explain why you want to recompute variable each time during loading?Sackett
can you export some_function so that you can call MyRPackage::some_function()?Bink
@Sackett it has to read data from a fileTwilatwilight
@Twilatwilight is that file going to be changing every session or something?Sackett
yes Dason, it can change between one session and the otherTwilatwilight
I'm not sure you can use MyRPackage:::variable without loading the package before because the variable is assigned only when you actually load the package. Something I don't understand?Meningitis
F. Privé I want to load the package.. I don't want to necessarily attach it.. for all the other exported functions you can do pkg::function, but for the one created in .onLoad I didn't manage to find a wayTwilatwilight
M
20

Following the approach in this answer to a related question, you can change your .onLoad() function like so:

.onLoad <- function(libname, pkgname) {
    variable <- some_function()
    assign("variable", variable, envir = parent.env(environment()))
}

Then you can access variable without attaching the package using MyRPackage:::variable. I don't know what you do with some_function(), so I tried the following with a dummy package:

.onLoad <- function(libname, pkgname) {
    variable <- 42
    assign("variable", variable, envir = parent.env(environment()))
}

And in a fresh R session the result was

> MyRPackage:::variable
[1] 42

Further explanation

From Hadley Wickham's Advanced R:

There are four special environments:

...

  • The environment() is the current environment.

...

You can list the bindings in the environment’s frame with ls() and see its parent with parent.env().

So, if we modify the .onLoad() function further, we can see this in action:

.onLoad <- function(libname, pkgname) {
    print(environment()) # For demonstration purposes only;
    print(parent.env(environment())) # Don't really do this.
    variable <- 42
    assign("variable", variable, envir = parent.env(environment()))
}

Then starting an R session results in

<environment: 0x483da88>
<environment: namespace:MyRPackage>

being printed to the console at the start of the session. This allows you to assign variable in the environment namespace:MyRPackage even though trying assign("variable", variable, envir = namespace:MyRPackage) would result in the error

Error: package or namespace load failed for ‘MyRPackage’:

  .onLoad failed in loadNamespace() for 'MyRPackage', details:

  call: get(name, envir = ns, inherits = FALSE)

  error: object 'namespace' not found

when installing the package.

Mainsheet answered 4/3, 2018 at 11:37 Comment(4)
thanks duckymar, let me make a couple of tests and I'll let you know whether it worked or notTwilatwilight
it worked perfectly thanks :) could you improve just slightly the answer by explaining briefly what the parent.env( environment()) is actually doing?Twilatwilight
@Twilatwilight I will edit later today to add more detailMainsheet
@Twilatwilight Some further explanation is added.Mainsheet
D
5

There are essentially three ways, from easiest to most complex:

  1. via direct assignment: ns$name = value
  2. via assign(…, envir = topenv())
  3. via assignInMyNamespace(…)

Although option 3 seems to be quite widespread, it requires writing more code, in multiple places, because you’ll first need to create a variable before you can overwrite it via assignInMyNamespace():

myvar = NULL

.onLoad = function (libname, pkgname) {
    assignInMyNamespace('myvar', value)
}

Failure to pre-declare the variable will lead to an error.

By contrast, assign is perfectly capable of creating a new variable which hasn’t been declared before. We just have to tell R into which environment to assign the variable, and the function topenv() provides this.

.onLoad = function (libname, pkgname) {
    assign('myvar', value, envir = topenv())
}

And of course we don’t need assign() (or assignInMyNamespace()) if we define a namespace object and subset-assign into it:

.onLoad = function (libname, pkgname) {
    ns = topenv()
    ns$myvar = value
}

For my own code I am gravitating towards that option.

Deferred answered 23/5, 2021 at 22:13 Comment(4)
R doc says: assignInNamespace should not be used in final code, and will in future throw an error if called from a package. Already certain uses are disallowed. For now, the option 1 is not used by almost all the packages.Gretchen
@LiangZhang assignInMyNamespaceassignInNamespace! Anyway, I personally don’t recommend using that option for the reasons mentioned in my answer, despite its relatively widespread usage. I always use the direct assignment.Deferred
Ah, I am sorry to ignore that My word! Thank you very much. I love your ns$name = value method.Gretchen
By the way, it is possibly misleading in the doc of memoise::memoise(): it adds example with statement of fun <<- memoise::memoise(fun). Maybe it could be corrected as your method here.Gretchen

© 2022 - 2024 — McMap. All rights reserved.