Customizer des fonctions d'un package.

Postez ici vos questions, réponses, commentaires ou suggestions - Les sujets seront ultérieurement répartis dans les archives par les modérateurs

Modérateur : Groupe des modérateurs

Didier Plat
Messages : 225
Enregistré le : 02 Nov 2009, 16:47
Contact :

Re: Customizer des fonctions d'un package.

Messagepar Didier Plat » 26 Sep 2019, 15:47

La première étape n'a besoin d'être effectuée qu'une fois (et les deux fonctions peuvent être dans le même fichier). Le .First commence donc avec source("nom du fichier avec le code des nouvelles fonctions"), puis le chargement de la librairie survival, puis autant de assignInNamespace() qu'il y a de fonctions à remplacer et, effectivement, vous pouvez terminer "proprement" avec un rm() pour supprimer les codes des fonctions de l'environnement global.
Pour autant, une fois que le source() a été lancé, les nouvelles fonctions sont présentes dans l'environnement global, donc directement utilisables comme le notaient Facundo et Maxime.
L'avantage de la solution avec assignInNamespace est simplement d'éviter d'avoir des fonctions print.xxx qui restent dans l'environnement global en permanence. A vous de juger si cela en vaut vraiment la peine.

Eric Wajnberg
Messages : 778
Enregistré le : 11 Aoû 2008, 15:37
Contact :

Re: Customizer des fonctions d'un package.

Messagepar Eric Wajnberg » 26 Sep 2019, 18:30

Oui, tout ceci correspond bien à ce que j'avais compris. Merci. Je fais ceci dès que possible.

Cordialement, Eric.

Logez Maxime
Messages : 3138
Enregistré le : 26 Sep 2006, 11:35

Re: Customizer des fonctions d'un package.

Messagepar Logez Maxime » 26 Sep 2019, 20:52

re,

il y a quand même une différence majeure, c'est qu'en utilisant assignInNamespace, quelque part tu écrases la fonction originelle, alors qu'avant tu avais deux fonctions mais l'une avait la priorité sur l'autre si je puis dire.
Si tu crées un package avec tes fonctions customisées et en assignant les méthodes dans le namespace du package alors tu retrouves cette notion de priorité, de la même manière que lorsque tu charges deux packages qui ont des fonctions aux noms identiques, tu obtiens le message comme qui la fonction xxx du package yyy est masquée. Les deux fonctions cohabitent et tu peux te servir de la fonction originelle du package survival si tu le souhaites. Dans le cas du assignInNamespace, je pense qu'il est plus difficile d'utiliser de nouveau la fonction initiale, si ce n'est de recharger le package ?

Après c'est sur que ce n'est pas le même boulot, mais des packages comme roxygen2 devrait aider.

Cordialement,
Maxime

Eric Wajnberg
Messages : 778
Enregistré le : 11 Aoû 2008, 15:37
Contact :

Re: Customizer des fonctions d'un package.

Messagepar Eric Wajnberg » 27 Sep 2019, 05:38

Merci Maxime pour ces précisions, mais c'est bien ce que j'avais compris. Dans ce cas en tout cas, je m'en fous d'écraser les versions originales des fonctions du package survival que je souhaite remplacer. Je ne les utilise plus depuis des années de toute façon.

Par ailleurs, faute d'en avoir eu besoin, je n'ai jamais écrit de package moi-même, et - même si ça doit pas être bien compliqué - je ne souhaite pour l'instant pas trop investir du temps pour ça.

Je vais faire des essais pour voir si je m'en sort. En principe tout est très simple, mais j'ai un environnement de travail sur ma machine avec plusieurs packages à customiser comme ça, et pas de mal de .First() différents selon les situations. Tout ceci risque de me prendre un peu de temps à installer.

Encore merci pour ton aide et ta disponibilité.

Eric.

Eric Wajnberg
Messages : 778
Enregistré le : 11 Aoû 2008, 15:37
Contact :

Re: Customizer des fonctions d'un package.

Messagepar Eric Wajnberg » 27 Sep 2019, 10:32

Bon, j'ai pas mal progressé.

J'arrive à pratiquement tout faire (pour info, comme expliqué dans ?Startup, seul le package base est chargé au démarrage; il faut allez explicitement chercher assignInNamespace() dans utils).

Il ne me reste qu'un détail à régler :

Après avoir déclaré une fonction (avec source()) dans un .First() (et évidement après l'avoir "injectée" dans un package avec assignInNamespace()), il ne m'est pas possible de la détruire avec rm(). L'objet n'est pas trouvé, alors qu'il est bien là à la fin de l’exécution de la séquence de démarrage.

La fonction rm() admet un argument envir qui défini l'environnement dans lequel l'objet doit être détuit, et j'imagine que la solution est ici. Mais quel est l’environnement dans lequel se trouvent les objets dans la séquence de démarrage - et notamment dans lequel le .First() s'exécute ? Impossible de trouver cette information.

Y aurait-il une fonction R qui affiche l'environnement dans lequel les choses sont en cours d’exécution, et que je pourrais mettre dans le .First() afin de savoir où je dois détruire les objets dont je n'ai plus le besoin ? Je n'ai rien trouvé en cherchant dans cette direction.

Encore donc un dernier détail qui reste à résoudre, et toute aide sur ce point supplémentaire serait la bienvenue.

Encore merci pour votre aide (à tous).

Cordialement, Eric

Didier Plat
Messages : 225
Enregistré le : 02 Nov 2009, 16:47
Contact :

Re: Customizer des fonctions d'un package.

Messagepar Didier Plat » 27 Sep 2019, 11:04

J'aurais pensé que les objets étaient dans l'environnement global ! Peut-être que rajouter un print(getAnywhere("print.coxph")) dans le .First permettrait de voir dans quel environnement ils sont stockés et donc de configurer ensuite correctement le rm() ?
Pour reprendre le fil de Maxime, plutôt que de priorité dans le cas de deux packages, je parlerai d'usage implicite (la deuxième fonction chargée) et d'usage explicite (la première fonction qui doit être "explicitement" appelée par survival:::print.coxph). Utiliser assignInNamespace écrase effectivement l'ancienne version de la fonction qui n'est donc récupérable que via un nouveau chargement du package. Mais il est toujours possible d'affecter cette ancienne version à un objet, par exemple print.coxph.old, avant d'effectuer la substitution au sein du namespace. On aura alors ultérieurement un usage implicite qui sera bien celui correspondant à la nouvelle version de la fonction, et un usage explicite qui pourra être obtenu par un appel à print.coxph.old. Au bout du compte, ce n'est peut-être pas si différent...

Eric Wajnberg
Messages : 778
Enregistré le : 11 Aoû 2008, 15:37
Contact :

Re: Customizer des fonctions d'un package.

Messagepar Eric Wajnberg » 27 Sep 2019, 11:50

getAnywhere() est une fonction du package utils. On doit donc - dans le .First(), endroit où utils n'est pas encore chargé - l'invoquer plutôt avec utils::getAnywhere.

Le problème est que cette fonction invoque elle-même getS3method() qui n'est pas dans le package base, mais est également dans utils. Cette fonction n'est donc pas trouvée. Ca se complique.

Créer un objet dans le .First() pour le détruire ensuite après l'avoir utilisé ne doit pas être une chose rare, et je doute que ce problème n'ait pas déjà été résolu.

Encore une fois, toute aide sur ce point serait utile..

Encore merci, Eric.

Logez Maxime
Messages : 3138
Enregistré le : 26 Sep 2006, 11:35

Re: Customizer des fonctions d'un package.

Messagepar Logez Maxime » 27 Sep 2019, 12:41

Bonjour,

la fonction pour connaitre un environnement donné est environment().
Tu as essayé de faire :

Code : Tout sélectionner

envi <- environment()
...
rm(..., envir = envi)

De ce que je vois de la fonction rm normalement elle s'applique dans l'environnement dans lequel tu te trouves donc c'est étonnant que tu n'y arrives pas directement. Ca doit être plus complexe que ça.

Cordialement,
Maxime

Eric Wajnberg
Messages : 778
Enregistré le : 11 Aoû 2008, 15:37
Contact :

Re: Customizer des fonctions d'un package.

Messagepar Eric Wajnberg » 27 Sep 2019, 12:44

Merci Maxime,

J'ai déjà essayé ceci, directement avec :

Code : Tout sélectionner

rm(..., envir = environment())

J'ai le même message d'erreur. L'objet n'est pas trouvé..

Je continue à chercher.

Eric.

Logez Maxime
Messages : 3138
Enregistré le : 26 Sep 2006, 11:35

Re: Customizer des fonctions d'un package.

Messagepar Logez Maxime » 27 Sep 2019, 12:49

en faisant comme tu fais tu risques de tomber dans un environnement local :

Code : Tout sélectionner

ted <- function(x, pos = -1, envir = environment()) { print(envir) }
ted(2)
<environment: 0x000000001f1266e0>
Maxime

Didier Plat
Messages : 225
Enregistré le : 02 Nov 2009, 16:47
Contact :

Re: Customizer des fonctions d'un package.

Messagepar Didier Plat » 27 Sep 2019, 13:04

En créant le script suivant :

Code : Tout sélectionner

.First <- function()
{
.First.sys() # pour conserver les packages dans l'ordre habituel (et charger utils)
library(survival)
OldFun <-new.env() # pour stocker les anciennes fonctions si besoin
attach(OldFun, pos=2)
assign("print.coxph.old", survival:::print.coxph, pos=2)
print.coxph <- function(x) cat("essai\n") # définition nouvelle fonction
assignInNamespace("print.coxph", print.coxph, "survival") # remplacement dans survival
}

puis en l'exécutant à l'ouverture de R et en sauvegardant immédiatement l'environnement de travail, voici ce que j'obtiens en relançant ensuite R :

Code : Tout sélectionner

[Sauvegarde de la session précédente restaurée]

> ls()
character(0)
> search()
 [1] ".GlobalEnv"        "OldFun"            "package:survival"
 [4] "package:stats"     "package:graphics"  "package:grDevices"
 [7] "package:utils"     "package:datasets"  "package:methods" 
[10] "Autoloads"         "package:base"     
> ls(pos=2)
[1] "print.coxph.old"
> x <- 3
> class(x) <-"coxph"
> print(x)
essai
> print.coxph.old(x)
Erreur : $ operator is invalid for atomic vectors
>

L'environnement global est "propre" et les anciennes fonctions sont stockées en position 2 en cas de besoin. N'est-ce pas ce que vous souhaitez ?

Eric Wajnberg
Messages : 778
Enregistré le : 11 Aoû 2008, 15:37
Contact :

Re: Customizer des fonctions d'un package.

Messagepar Eric Wajnberg » 27 Sep 2019, 13:06

Effectivement !

J'ai donc récupéré l’environnement en dehors de rm() :

Code : Tout sélectionner

envir1=environment()
rm(print.survdiff, print.coxph,envir=envir1)

Même problème (objets non trouvés).

De fait, si je lance :

Code : Tout sélectionner

print(ls(envir=envir1))

après avoir déclaré envir1, je n'ai qu'un seul objet qui est listé, et qui est évidement envir1, et ceux que je veux virer ne sont pas là.

On dirait qu'une solution possible serait de prendre la main sur un environnement dans lequel les choses doivent se passer. Le créer, y importer des objects (source()), les utiliser (assignInNamespace()) et virer in fine cet environnement. J'ai trouvé comment dire à assignInNamespace() quel environnement utilisé, pas je n'ai pas trouvé comme faire ceci avec source()..

On avance, et toute aide sur ce point reste uile..

Décidément, le problème semble plus compliqué qu'il n'y paraissait.

Eric.

Eric Wajnberg
Messages : 778
Enregistré le : 11 Aoû 2008, 15:37
Contact :

Re: Customizer des fonctions d'un package.

Messagepar Eric Wajnberg » 27 Sep 2019, 13:14

Ma réponse était pour Maxime.

Pour ce qui est de la réponse de Didier, je crois comprendre que ça correspond à ce que je viens de dire (i.e., créer un nouvel environnement, etc.).

Deux choses :

1) Je n'ai pas besoin de conserver les anciennes fonctions que je change.

2) Les fonctions en question restent assez longues, et l'utilisation de source() semble préférable, plutôt que les déclarer "à la volée" dans le .First() (et puis j'en ai bien plus deux deux). Comment sourcer du code dans un environnement donné ? (c'est la question que je pose ci-dessus).

Mais on avance.

Eric.

Logez Maxime
Messages : 3138
Enregistré le : 26 Sep 2006, 11:35

Re: Customizer des fonctions d'un package.

Messagepar Logez Maxime » 27 Sep 2019, 13:22

il te suffit de mettre local = TRUE dans la fonction source à laquelle tu fais appelle dans la fonction .First et tu n'as pas besoin du rm après assignInNamespace.
sinon essaie avec rm(..., envir = globalenv())

Maxime

Eric Wajnberg
Messages : 778
Enregistré le : 11 Aoû 2008, 15:37
Contact :

Re: Customizer des fonctions d'un package.

Messagepar Eric Wajnberg » 27 Sep 2019, 13:32

Logez Maxime a écrit :il te suffit de mettre local = TRUE dans la fonction source à laquelle tu fais appelle dans la fonction .First et tu n'as pas besoin du rm après assignInNamespace.

local=TRUE est la solution pour la fonction source(), merci.

Problème résolu !

Je peux à présent continuer à installer tout ceci dans mes différents environnements de travail sur ma machine.

Merci infiniment pour votre disponibilité et votre ténacité.

Eric.


Retourner vers « Questions en cours »

Qui est en ligne

Utilisateurs parcourant ce forum : Aucun utilisateur enregistré et 1 invité