Переадресация/перехват вызовов функций внутри функции пакета

Предположим, что я вызываю функцию PackageFuncA, которая существует внутри стороннего пакета (т.е. библиотеки из CRAN). PackageFuncA, в свою очередь, вызывает PackageFuncB в одном и том же пакете сторонних производителей. Есть ли способ вызвать PackageFuncA таким образом, что когда он вызывает PackageFuncB, он фактически вызовет мою собственную импликацию PackageFuncB? Другими словами, могу ли я перехватить вызов PackageFuncB?

Я думаю, что решение включает в себя создание моей собственной функции PackageFuncB, а затем вызов PackageFuncA в той же среде, а не в среде PackageFuncA, но я не мог заставить ее работать с do.call и eval.

Ответ 1

Вот один лайнер, который делает это. Здесь PackageFuncA есть stats::acf, а PackageFuncB - stats:::plot.acf, который мы хотим заменить на my.plot.acf. my.plot.acf печатает "Hello", а затем вызывает реальный stats:::plot.acf.

# we want this to run in place of stats:::plot.acf
my.plot.acf <- function(x, ...) { cat("Hello\n"); stats:::plot.acf(x, ...) }

# this does it
library(proto)
acf <- with(proto(environment(acf), acf = stats::acf, plot.acf = my.plot.acf), acf)

# test
acf(1:10)

Прото-объект - это среда, в которой любая функция, вставленная в объект с помощью функции proto, автоматически присваивает этому объекту среду reset. Первым аргументом proto() является родительский элемент прото-объекта.

В приведенном выше примере его настройка была настроена так, чтобы переменная acf ссылалась на версию acf, которая была вставлена ​​в прото-объект (который совпадает с оригиналом, за исключением того, что его среда была изменена, чтобы быть прото-объект). Когда запускается новая функция acf plot.acf - это свободная переменная (т.е. Не определена в acf), поэтому она просматривается в родительском acf и является средой в прото-объекте, где находит новый plot.acf. acf может иметь другие свободные переменные, но в тех случаях, когда они не найдены в прото-объекте, он смотрит на родителя прото-объекта, который является исходной средой исходного acf. В терминах диаграмм мы имеем это, где <- означает, что левая сторона является родителем правой стороны:

environment(stats::acf) <- proto object <- revised acf

и прото-объект содержит как plot.acf, так и пересмотренный acf.

Мы также установили среду нового plot.acf для прото-объекта. Мы можем или не должны были этого делать. Во многих случаях это не имеет значения. Если было бы важно не устанавливать среду нового plot.acf, тогда это будет сделано так, потому что proto никогда не устанавливает среду функций, вставленных с помощью [[...]]:

acf <- with(p <- proto(environment(acf), acf = stats::acf), acf)
p[["plot.acf"]] <- my.plot.acf

В этом примере работают оба подхода.

Было бы возможно сделать все это в простых средах за счет использования нескольких строк кода:

# create new environment whose parent is the original acf parent
e <- new.env(parent = environment(stats::acf))

# the next statement is only need to overwrite any acf you already might have from
# trying other code.  If you were sure there was no revised acf already defined 
# then the next line could be omitted.  Its a bit safer to include it.
acf <- stats::acf

# This sets the environment of the new acf.  If there were no acf already here 
# then it would copy it from stats::acf .
environment(acf) <- e

# may or may not need next statement.  In this case it doesn't matter.
environment(my.plot.acf) <- e

e$plot.acf <- my.plot.acf

acf(1:10)

В этом случае мы не разместили пересмотренный acf в e, как в примере proto, а только установили его родительский. Фактически, размещение пересмотренного acf в e или прото-объект не является строго необходимым, а сделано только в случае прото, потому что proto имеет побочный эффект сброса среды, и это был тот побочный эффект, который мы наблюдали. С другой стороны, необходимо поставить пересмотренный plot.acf в e или прото-объект, чтобы он встречался до исходного.

Возможно, вы захотите прочитать эту статью и, в частности, раздел о Proxies, начиная с 21, так как приведенная здесь методика является примером прокси-объект.

Ответ 2

Сделайте новую копию PackageFuncA, reset своей среды и напишите свою собственную версию PackageFuncB.

environment(PackageFuncA) <- globalenv()  # makes a new copy of PackageFuncA

PackageFuncB <- function(...) ....   # will be called from your new PackageFuncA

Возможно, вам придется немного отредактировать, если PackageFuncA использует неэкспортируемые функции из своего исходного пакета. Кроме того, если вы не хотите, чтобы новый PackageFuncB использовался в другом месте, вы можете обернуть его внутри своего нового PackageFuncA вместо его размещения в глобальной среде.