R 中的闭包,如 Python

Closures in R like Python

提问人:Masoud 提问时间:9/25/2022 最后编辑:Hack-RMasoud 更新时间:9/26/2022 访问量:74

问:

首先考虑以下 Python 代码,这些代码计算函数被调用的次数:

def counter(fn):
    count = 0
    def inner(*args, **kwargs):
        nonlocal count
        count +=1
        print('Function {0} was called {1} times'.format(fn.__name__, count))
        return fn(*args, **kwargs)
    return inner

def add(a,b):
    return a+b
def mult(a,b):
    return a*b
add = counter(add)
mult = counter(mult)
add(1,2)
add(2,3)
mult(1,5)
#output
Function add was called 1 times
Function add was called 2 times
Function mult was called 1 times

现在我正在尝试在 R 中执行相同的方法,如下所示:

counter <- function(fn) {
  cnt <- 0
  inner <- function(...) {
    cnt <<- cnt + 1
    print(paste("Function", match.call(), "was called", cnt, "times\n"))
    return(fn(...))
  }
  return(inner)
  
}
add <- function(a, b) a + b
mult <- function(a, b) a*b
cnt_add <- counter(add)
cnt_add(1, 4) 
cnt_add(3, 9)
[1] "Function cnt_add was called 1 times\n"
[2] "Function 1 was called 1 times\n"   #<---- !!!!!!!!!!!!!!  L1  
[3] "Function 4 was called 1 times\n"   #<---- !!!!!!!!!!!!!!  L2 
[1] 5
[1] "Function cnt_add was called 2 times\n"
[2] "Function 3 was called 2 times\n"   #<---- !!!!!!!!!!!!!!  L3 
[3] "Function 9 was called 2 times\n"   #<---- !!!!!!!!!!!!!!   
[1] 12
cnt_mult<-counter(mult)
cnt_mult(1,6) 
[1] "Function cnt_mult was called 1 times\n"
[2] "Function 1 was called 1 times\n"   #<---- !!!!!!!!!!!!!!  L4  
[3] "Function 6 was called 1 times\n"   #<---- !!!!!!!!!!!!!!  L5  
[1] 6

a) 我期望“功能?被称为?次\n“但为什么要打印 L1,L2,L3,L4,L5?

b) 当我尝试时(就像在 python 中一样)

add <- counter(add)
add(3, 4)

我收到一个错误:错误:评估嵌套太深...。

c) 为了避免 b 中的错误,我尝试了以下操作,但仍然出现错误

cnt_add <- counter(add)
add <- cnt_add
add(6, 8)

我发现如果我调用cnt_add函数一次,就不会发生错误(控制台中的额外两行除外):

cnt_add <- counter(add)
cnt_add(1, 8)
[1] "Function cnt_add was called 1 times\n"
[2] "Function 1 was called 1 times\n"      
[3] "Function 8 was called 1 times\n"      
[1] 9
add <- cnt_add
add(6, 8)
[1] "Function add was called 2 times\n" "Function 6 was called 2 times\n"  
[3] "Function 8 was called 2 times\n"  
[1] 14

但是为什么“函数添加被调用了 2 次”,我调用了一次!为什么它需要至少一次调用才能工作?

如何解决这些问题?我不想要其他方法,因为这只是闭包的一种做法。

r

评论

0赞 Hack-R 9/26/2022
回滚编辑,因为如果您要对答案发表评论,则应将其作为评论添加到答案中。

答:

3赞 user2554330 9/25/2022 #1

a) 为您提供整个调用,而不仅仅是您调用的函数的名称。用它来获得它。但是给你一个字符串版本,你传递的内容,所以它可能更好。(我的选择给出的是原始函数名称,而不是修改后的函数名称。如果你想要修改的那个,请坚持下去。match.callmatch.call()[[1]]deparse(substitute(fn))fnmatch.call()[[1]]

b) 你被懒惰的评价咬了一口。调用 的定义 。问题是从不计算,所以它作为一个承诺,直到你第一次调用时需要它。但是在这一点上,定义已经改变,所以你得到了无限循环。使用强制确定承诺的价值。force(fn)countercounterfnaddaddforce(fn)

counter<-function(fn){
  force(fn)
  name <- deparse(substitute(fn))
  cnt <- 0
  inner <- function(...){
    cnt <<- cnt+1
    print(paste("Function",name,"was called",cnt,"times\n"))
    return(fn(...))
  }
  return(inner)
  
}
add  <- function(a,b) a+b
mult <- function(a,b) a*b
add  <-counter(add)
add(1,4)
#> [1] "Function add was called 1 times\n"
#> [1] 5
add(3,9)
#> [1] "Function add was called 2 times\n"
#> [1] 12

创建于 2022-09-25 with reprex v2.0.2

3赞 jay.sf 9/26/2022 #2

使用它更相似。 可能更适合这种输出。归功于@user2554330,我把它包裹起来。sprintfmessagematch.call()[[1]]as.character()

counter <- function(fn) {
  cnt <- 0
  inner <- function(...) {
    cnt <<- cnt + 1
    message(sprintf("Function %s was called %s times", as.character(match.call()[[1]]), cnt))
    return(fn(...))
  }
  return(inner)
}

add <- function(a, b) a + b
mult <- function(a, b) a*b
cnt_add <- counter(add)

cnt_add(1, 4)
# Function cnt_add was called 1 times 
# [1] 5

cnt_add(3, 9)
# Function cnt_add was called 3 times
# [1] 12

评论

1赞 user2554330 9/26/2022
这并不能解决问题的 b) 部分。问题是 no in evaluate ,所以只有你第一次调用它才会被评估。但是,如果您将其命名为 b),则将使用错误的函数。counterfncnt_addadd
0赞 jay.sf 9/26/2022
@user2554330 使用 和 user2554330 的精彩答案中所述。从我的:)中取出force(fn)deparse(substitute))message(sprintf(.))
1赞 B. Christian Kamgang 9/26/2022 #3

fname存储函数名称。 查找传递给 的函数。get(fname, mode="function")counter

counter = function(fn) {
  i = 0
  fname = deparse(substitute(fn))
  fn = get(fname, mode="function")
  function(...) {
    i <<- i+1
    cat(sprintf("Function %s was called %d times", fname, i), "\n")
    return(fn(...))
  }
}


add = function(a, b) a+b
add = counter(add)
add(1, 6)
Function add was called 1 times 
[1] 7
add(7, 6)
Function add was called 2 times 
[1] 13
 
mult = function(a, b) a*b
mult = counter(mult)
mult(2, 3)
Function mult was called 1 times 
[1] 6
mult(5, 3)
Function mult was called 2 times 
[1] 15

评论

0赞 user2554330 9/26/2022
如果表达式作为 传递,这将不起作用,例如 。你应该让 R 评估,你不应该使用 .fnadd <- counter(function(a, b) a + b)fnget()