2014-03-04 5 views
13

[편집 :.이 해결 방법을 묻는 문제는 R 3.1.0 이후 수정되었습니다]R : 함수가 메모리에서 매개 변수를 복사하지 않고 줄임표 (...)를 사용하여 변수 인수를 수락하는 방법은 무엇입니까?

나는 자기 대답 질문으로 이것을 게시 할 다른 질문을 받았다.

는 R 함수 생략 아규먼트 파라미터의 임의의 번호에 액세스 할 수있는 일반적인 방법을 허용

list(...)를 사용 :

f <- function(...) { 
    dots <- list(...) 

    # Let's print them out. 
    for (i in seq_along(dots)) { 
    cat(i, ": name=", names(dots)[i], "\n", sep="") 
    print(dots[[i]]) 
    } 
} 

> f(10, a=20) 
1: name= 
[1] 10 
2: name=a 
[1] 20 

을하지만, (v3.0.2 기준) R 깊은 사본 모든 list 요소 : 당신이 메모리 프로파일을 사용할 경우

> x <- 10 
> .Internal(inspect(x)) 
@10d85ca68 14 REALSXP g0c1 [MARK,NAM(2),TR] (len=1, tl=0) 10 

> x2 <- x 
> .Internal(inspect(x2)) # Not copied. 
@10d85ca68 14 REALSXP g0c1 [MARK,NAM(2),TR] (len=1, tl=0) 10 

> y <- list(x) 
> .Internal(inspect(y[[1]])) # x was copied to a different address: 
@10dd45e88 14 REALSXP g0c1 [MARK,NAM(1),TR] (len=1, tl=0) 10 

> z <- list(y) 
> .Internal(inspect(z)) # y was deep-copied: 
@10d889ed8 19 VECSXP g0c1 [MARK,NAM(1)] (len=1, tl=0) 
    @10d889f38 19 VECSXP g0c1 [MARK,TR] (len=1, tl=0) 
    @10d889f68 14 REALSXP g0c1 [MARK] (len=1, tl=0) 10 

당신은뿐만 아니라 tracemem으로이를 확인할 수 있습니다.

큰 개체를 list에 저장 했습니까? 복사 됨. list(...)을 호출하는 함수에 전달합니까? 복사 됨 :

> g <- function(...) for (x in list(...)) .Internal(inspect(x)) 
> g(z) # Copied. 
@10dd45e58 19 VECSXP g0c1 [] (len=1, tl=0) 
    @10dd35fa8 19 VECSXP g0c1 [] (len=1, tl=0) 
    @10dd36068 19 VECSXP g0c1 [] (len=1, tl=0) 
     @10dd36158 14 REALSXP g0c1 [] (len=1, tl=0) 10 
> g(z) # ...copied again. 
@10dd32268 19 VECSXP g0c1 [] (len=1, tl=0) 
    @10d854c68 19 VECSXP g0c1 [] (len=1, tl=0) 
    @10d8548d8 19 VECSXP g0c1 [] (len=1, tl=0) 
     @10d8548a8 14 REALSXP g0c1 [] (len=1, tl=0) 10 

아직 끔찍하지 않습니까? R 라이브러리 소스에서 grep -l "list(\.\.\.)" *.R을 시도하십시오. 내가 가장 좋아하는 단어는 mapply/Map으로, GB의 데이터를 자주 호출하고 메모리가 부족한 이유가 궁금합니다. 적어도 lapply이면 좋습니다.

따라서 ... 개의 변수를 가진 함수를 작성하고 복사하지 않으려면 어떻게해야합니까?

+0

목록을 처리 할 때 요소 복사가 눈치 챘지만 새로운 "기능"임을 알지 못했습니다. – BrodieG

+0

나는 그것이 새로운 것 같지 않습니다. 나는 버전이 미래에 바뀔 경우를 대비해 언급했다. – codeola

+2

실제로 3.1.0에서 수정되었습니다 – hadley

답변

14

... 인수를 match.call을 사용하여 확장 한 다음 인수를 평가하고 값을 복사하지 않는 environment에 저장하여 저장할 수 있습니다. environment 객체는 모든 요소에 이름을 요구하고 순서를 유지하지 않으므로 (선택적) 형식 인수 이름 외에 순서가 지정된 태그 이름의 별도 벡터를 저장해야합니다. 여기에 구현하여 속성 : 이제

argsenv <- function(..., parent=parent.frame()) { 
    cl <- match.call(expand.dots=TRUE) 

    e <- new.env(parent=parent) 
    pf <- parent.frame() 
    JJ <- seq_len(length(cl) - 1) 
    tagnames <- sprintf(".v%d", JJ) 
    for (i in JJ) e[[tagnames[i]]] <- eval(cl[[i+1]], envir=pf) 

    attr(e, "tagnames") <- tagnames 
    attr(e, "formalnames") <- names(cl)[-1] 
    class(e) <- c("environment", "argsenv") 
    e 
} 

우리는 우리의 기능을 대신 list(...)에서 사용할 수 있습니다 :

그래서 작동
f <- function(...) { 
    dots <- argsenv(...) 

    # Let's print them out. 
    for (i in seq_along(attr(dots, "tagnames"))) { 
    cat(i, ": name=", attr(dots, "formalnames")[i], "\n", sep="") 
    print(dots[[attr(dots, "tagnames")[i]]]) 
    } 
} 

> f(10, a=20) 
1: name= 
[1] 10 
2: name=a 
[1] 20 

하지만 복사를 방지합니까?

g1 <- function(...) { 
    dots <- list(...) 
    for (x in dots) .Internal(inspect(x)) 
} 

> z <- 10 
> .Internal(inspect(z)) 
@10d854908 14 REALSXP g0c1 [NAM(2)] (len=1, tl=0) 10 
> g1(z) 
@10dcdaba8 14 REALSXP g0c1 [NAM(2)] (len=1, tl=0) 10 
> g1(z, z) 
@10dcbb558 14 REALSXP g0c1 [NAM(2)] (len=1, tl=0) 10 
@10dcd53d8 14 REALSXP g0c1 [NAM(2)] (len=1, tl=0) 10 
> 

g2 <- function(...) { 
    dots <- argsenv(...); 
    for (x in attr(dots, "tagnames")) .Internal(inspect(dots[[x]])) 
} 

> .Internal(inspect(z)) 
@10d854908 14 REALSXP g0c1 [MARK,NAM(2)] (len=1, tl=0) 10 
> g2(z) 
@10d854908 14 REALSXP g0c1 [MARK,NAM(2)] (len=1, tl=0) 10 
> g2(z, z) 
@10d854908 14 REALSXP g0c1 [MARK,NAM(2)] (len=1, tl=0) 10 
@10d854908 14 REALSXP g0c1 [MARK,NAM(2)] (len=1, tl=0) 10 
당신은 대신 속성의 슬롯 S4에서이를 구현 그것을위한 방법 ( length, [, [[, c 등)의 모든 종류를 정의하고, 본격적인 범용으로 바꿀 수

list의 복사 금지 대체. 그러나 그것은 또 다른 게시물입니다.

사이드 참고 : lapply(seq_along(v1) function(i) FUN(v1[[i]], v2[[i]], ... )으로 이러한 모든 호출을 다시 작성하여 mapply/Map을 피할 수 있지만, 많은 작업이고 우아함과 가독성에 코드를 어떤 호의를하지 않습니다.base 버전을 섀도/전역 네임 스페이스

mapply2 <- function(FUN, ..., MoreArgs=NULL, SIMPLIFY=TRUE, USE.NAMES=TRUE) { 
    FUN <- match.fun(FUN) 

    args <- argsenv(...) 
    tags <- attr(args, "tagnames") 
    iexpr <- quote(.v1[[i]]) 
    iargs <- lapply(tags, function(x) { iexpr[[2]] <- as.name(x); iexpr }) 
    names(iargs) <- attr(args, "formalnames") 
    iargs <- c(iargs, as.name("...")) 
    icall <- quote(function(i, ...) FUN())[-4] 
    icall[[3]] <- as.call(c(quote(FUN), iargs)) 
    ifun <- eval(icall, envir=args) 

    lens <- sapply(tags, function(x) length(args[[x]])) 
    maxlen <- if (length(lens) == 0) 0 else max(lens) 
    if (any(lens != maxlen)) stop("Unequal lengths; recycle not implemented") 

    answer <- do.call(lapply, c(list(seq_len(maxlen), ifun), MoreArgs)) 

    # The rest is from the original mapply code. 

    if (USE.NAMES && length(tags)) { 
    arg1 <- args[[tags[1L]]] 
    if (is.null(names1 <- names(arg1)) && is.character(arg1)) names(answer) <- arg1 
    else if (!is.null(names1)) names(answer) <- names1 
    } 

    if (!identical(SIMPLIFY, FALSE) && length(answer)) 
     simplify2array(answer, higher = (SIMPLIFY == "array")) 
    else answer 
} 

# Original Map code, but calling mapply2 instead. 
Map2 <- function (f, ...) { 
    f <- match.fun(f) 
    mapply2(FUN=f, ..., SIMPLIFY=FALSE) 
} 

당신은 심지어 그들에게 패키지에 mapply/Map의 이름을 수하지 : 대신, 우리는 정확히 내부를 할 argsenv 일부 표현 조작을 사용하여 mapply/Map 기능을 다시 작성할 수 있습니다 나머지 코드는 수정해야합니다. 여기 구현은 원하는 경우 불필요한 길이의 재활용 기능을 누락시킵니다. 원하는 경우 추가 할 수 있습니다.

+2

'eval (substitute (alist (...)) ')로 도트를 잡는 것이 약간 안전합니다 - 경우가 있습니다 (즉, 인수를 ...로 취하는 다른 함수에서 호출 할 때)'match.call()'이 차선의 결과를 낸다. – hadley

+0

@hadley : 이것은 내 코드에서 반대의 효과가있다. 인수는 이제'... '의 캐스케이드 (cascade) 시작 부분에서 전달 된 값의 심볼 이름이'argsenv'까지 호출되어 발견 될 수 없으므로 도착합니다. 대조적으로,'match.call'는'..1, ..2' 등을 반환합니다. 내가 뭘 놓칠 수 있니? – codeola

+0

오, 흠, 내 유스 케이스에 짜증나지만 어쩌면 여기에 필요한 것 일 수도 있습니다. – hadley