Как проверить, соответствуют ли предоставленные пользователем аргументы формальным аргументам метода по умолчанию для функции? - PullRequest
0 голосов
/ 26 февраля 2020

Я программирую на R и мне нужно проверить, являются ли все аргументы, предоставленные пользователем в списке params, действительными аргументами для функции f. Функция должна вернуть ошибку, если какие-либо именованные элементы params не соответствуют именам аргументов f. В настоящее время он реализован так:

if (is.null(names(params)) || any(!names(params) %in% names(formals(f)))) {
        stop("names of params must match arguments of f")
    }

Я столкнулся с проблемой при тестировании этого кода с помощью функции caret::train. Функция train имеет только один формальный аргумент, x, поэтому names(formals(caret::train)) возвращает c('x','...'). Однако метод S3 по умолчанию для caret::train имеет дополнительные формальные аргументы. Как я могу программно проверить, совпадает ли именованный список, введенный пользователем, с аргументами функции, если они являются только аргументами для одного из методов, а не для самой функции? Это должно быть общее решение, которое должно работать для любой функции, а не только для train.

Воспроизводимый пример

my_fun <- function(f, params) {

    if (is.null(names(params)) || any(!names(params) %in% names(formals(f)))) {
        stop("names of params must match arguments of f")
    }

    do.call(f, params) 

}

library(caret)

# my_fun returns error: names of params must match arguments of f
my_fun(f = caret::train, params = list(x = data.frame(x = 1:10), y = rep(1,10), method = 'rf'))

# caret::train returns error: argument "y" is missing, with no default
my_fun(f = caret::train, params = list(x = data.frame(x = 1:10)))

Ответы [ 2 ]

2 голосов
/ 26 февраля 2020

Я думаю, это будет нелегко. Вы можете проверить S3, как я делаю ниже, и это в основном работает. Однако это не учитывает другие OOP системы, которые есть в R. И даже эта реализация не так стабильна, например, она не будет работать, если вы используете нотацию :: (то есть train работает, но caret::train не работает). Последний бит объясняется тем, что getS3method не работает с нотацией ::, понятия не имею, почему.

my_fun <- function(f, params, env = parent.frame()) {
  # check for S3 generic
  if (isS3stdGeneric(f)) {
    s <- deparse(substitute(f))
    dispatch_arg <- formalArgs(f)[1]
    classes_to_check <- c(class(params[[dispatch_arg]]), 'default')
    for (i in seq_along(classes_to_check)) {
      f <- getS3method(s, classes_to_check[i], optional = TRUE, parent.frame(n = 2))
      if (is.function(f)) break
    }
  }
  if (is.null(names(params)) || !all(names(params) %in% formalArgs(f))) {
    stop("names of params must match arguments of f", call. = FALSE)
  }
  do.call(f, params)
}

Примеры:

library(caret)

my_fun(f = train, params = list(x = data.frame(x = 1:10), y = rep(1,10), method = 'rf'))
# works

my_fun(f = train, params = list(x = data.frame(x = 1:10)))
# argument "y" is missing, with no default
1 голос
/ 26 февраля 2020

Вопрос:

Как проверить, соответствуют ли аргументы, предоставленные пользователем, формальным аргументам метода по умолчанию для функции?

Ответ:

Вы можете получить метод по умолчанию для функции, переданный с использованием функции getS3method.


my_fun <- function(f, params) {

  # Look for a default method first
  f.default <- try(getS3method(deparse(substitute(f)), "default"), silent=TRUE)

  if(class(f.default)=="try-error")
    stop("There is no default method for f.")

  # Get the formals for the default method
  f.args <- formalArgs(f.default)

  # Excessive arguments 
  excessive.pargs <- setdiff(names(params), f.args)
  if (length(excessive.pargs)>0) 
    stop("You have extra arguments that don't match arguments of f: ", excessive.pargs)

  # Continue with no error (except for missing arguments)
  do.call(f, params) 
}

Проверьте это:

library(caret)

my_fun(f = train, params = list(x = data.frame(x = 1:10)))
#Error in my_fun(f = train, params = list(x = data.frame(x = 1:10))) : 
  #argument "y" is missing, with no no default

my_fun(f = train, params = list(x = data.frame(x = 1:10), z="Invalid argument"))
#Error in my_fun(f = train, params = list(x = data.frame(x = 1:10), z = "Invalid argument")) : 
  #You have extra arguments that don't match arguments of f:  z

my_fun(f = train, params = list(x = data.frame(x = 1:10), y = rep(1,10), method = 'rf'))
# Works

Проверьте это на функции без метода по умолчанию:

my_fun(f = lm, params = list(data=iris))
#Error in my_fun(f = lm, params = list(data = iris)) : 
  #There is no default method for f.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...