r - 为什么惰性求值会“丢失”函数参数?
问题描述
我正在使用 SQL 和用户输入。所以我使用这个glue
库来处理参数化查询。
但是,为了保持清洁,我将所有内容包装在一个函数中:
safeQuery <- function(con, sql, ...) {
sql = glue_sql(sql, ..., .con=con)
query <- dbSendQuery(con, sql)
out <- dbFetch(query)
dbClearResult(query)
return(out)
}
glue_sql
因此,我只需使用连接、SQL 代码和适当绑定的 SQL 代码的参数列表来调用该函数。
这工作得很好。
现在,我有一个特定的 SQL 调用,我经常以一种或另一种方式使用它,但参数不同。
所以我决定为此创建一个函数:
get_data <- function(con, params) {
safeQuery(con,
"SELECT *
FROM foo
WHERE bar IN ({vars*})",
vars=params)
}
p = c(1, 2)
get_data(con, p)
因此,用户数据(在本例中c(1, 2)
)将被传递给get_data
,这将与 SQL 调用一起传递给safeQuery
,其中glue_sql
将处理绑定。
但是,如果我真的尝试运行get_data
,我会得到一个错误
object 'params' not found
谷歌搜索和 SO'ing 已经清楚地表明这与 R 的惰性评估有关。
事实上,get_data
改为
get_data <- function(con, params) {
do.call("safeQuery",
list(con,
"SELECT *
FROM foo
WHERE bar IN ({vars*})",
vars=params)
}
(正如这个答案所推荐的)工作得很好,因为do.call
在将它们发送到之前评估列表中的参数safeQuery
。
我不明白为什么这首先是必要的。毕竟, 的值params
不会在任何一步被修改到glue_sql
,所以它应该仍然可用。
链接的答案讨论了使用substitute
(我还阅读了有关该主题的这篇 R-bloggers 帖子)将参数的名称替换为调用者的名称(或者如果直接给出参数值,则使用其实际值),但这并没有在我的情况下不起作用。修改get_data
使用substitute
:
get_data <- function(con, params) {
do.call("safeQuery",
list(con,
"SELECT *
FROM foo
WHERE bar IN ({vars*})",
vars=substitute(params))
}
导致以下 SQL 来自glue_sql
:
SELECT *
FROM foo
WHERE bar IN (params)
而不是 的实际值params
。我无法在其中尝试相同的方法,safeQuery
因为参数隐藏在...
其中并且substitute(...)
不起作用。我试过了。
我也尝试过force(params)
在开头调用get_data
,但这给出了同样的object not found
错误。
get_data <- function(con, params) {
force(params)
do.call("safeQuery",
list(con,
"SELECT *
FROM foo
WHERE bar IN ({vars*})",
vars=params)
}
那么,为什么会params
在标准调用中“迷失”呢?为什么do.call
有效,但无效force(params)
?是否可以使用标准评估来完成这项工作?
而且我不会撒谎:这种经历让我对如何编写函数和处理它们的参数感到困惑(我正在考虑do.call
从现在开始只使用)。如果可以在不过度扩展这个问题的范围的情况下给出提示,我将非常感激。
解决方案
我不完全清楚为什么会这样,但确实如此。
safeQuery <- function(con, sql, ...) {
dots = list(...)
dots
}
然后当你打电话时get_data("foo_con", params = 1:3)
,你会得到:
$`vars` [1] 1 2 3
所以现在我们在一个命名列表中有参数,这意味着你应该使用glue_data
(或glue_data_sql
):
safeQuery <- function(con, sql, ...) {
dots = list(...)
glue_data_sql(.x = dots, sql, .con=con)
# More code...
}
现在当你打电话时get_data("foo_con", params = 1:3)
,你会得到:
<SQL> SELECT * FROM foo WHERE bar IN (1, 2, 3)
替代版本:
问题在于正在评估参数的环境。强制执行此操作的一种方法是传递您想要的环境:
safeQuery <- function(con, sql, ..., .envir = parent.frame()) {
dots = list(...)
glue_sql(sql, ..., .con=con, .envir = .envir)
# More code...
}
get_data <- function(con, params) {
env <- environment()
safeQuery(con,
"SELECT *
FROM foo
WHERE bar IN ({vars*})",
vars=params, .envir = env)
}
get_data("foo_con", params = 1:3)
<SQL> SELECT * FROM foo WHERE bar IN (1, 2, 3)
推荐阅读
- typescript - css-loader、style-loader 和 typescript
- git - 我知道 .gitignore 在一个方向上工作,但它在相反方向上工作吗?
- php - 保留数组的最后 15 个元素
- javascript - 无法获取 Draft-js 修饰符的 applyInlineStyle 函数来应用内联样式
- android - Android 应用程序中的 Firebase 云消息传递错误
- spring-integration - 使用 MappingJackson2MessageConverter 进行意外的消息转换
- amazon-dynamodb - 按类型从 DynamoDB 中删除项目
- php - 将 cURL 命令行转换为带有 URL 编码的 cURL PHP
- android - (三遍印刷)外部原生构建android studio(Android NDK)
- winapi - 在 32 位进程中使用 GetProcessAffinityMask 寻找 32 cpu 限制的解决方法