รับฟังก์ชั่นที่มาทั้งหมด


11

ใน R ฉันใช้source()เพื่อโหลดบางฟังก์ชั่น:

source("functions.R")

เป็นไปได้ไหมที่จะรับรายการฟังก์ชั่นทั้งหมดที่กำหนดไว้ในไฟล์นี้? เป็นชื่อฟังก์ชั่น (บางทีsource()ตัวเองสามารถส่งคืนได้)

PS: ทางเลือกสุดท้ายคือการโทรsource()ครั้งที่สองlocal({ source(); })แล้วทำls()ข้างในแล้วกรองฟังก์ชั่น แต่มันซับซ้อนเกินไป - มีวิธีแก้ปัญหาที่ง่ายกว่าและน้อยกว่าหรือไม่


1
สิ่งนี้ไม่ได้ใช้source()แต่หัวข้อเก่านี้อาจเป็นที่สนใจของคุณ
แอนดรู

1
@ แอนดรูว์ขอบคุณฉันได้ตรวจสอบวิธีแก้ไขปัญหาที่เสนอแล้ว แต่มันฟังดูบ้ากว่าวิธีสุดท้ายที่ฉันนำเสนอมาในคำถาม :)
TMS

2
ฉันไม่ทราบว่าวิธีนี้แก้ปัญหาได้ยากขึ้น:envir <- new.env() source("functions.R", local=envir) lsf.str(envir)
LocoGris

2
ทำแพ็กเกจจากไฟล์ต้นฉบับของคุณ จากนั้นคุณจะได้รับข้อดีทั้งหมดรวมถึงแพ็คเกจเนมสเปซ
Roland

@TMS เข้าใจผิดคำถามของคุณ / ไม่ได้อ่านว่าคุณต้องการฟังก์ชั่นที่กำหนดไว้ ขอโทษ!
แอนดรู

คำตอบ:


7

ฉันคิดว่าวิธีที่ดีที่สุดคือการหาแหล่งไฟล์ในสภาพแวดล้อมชั่วคราว ค้นหาสภาพแวดล้อมนั้นสำหรับฟังก์ชั่นทั้งหมดแล้วคัดลอกค่าเหล่านั้นไปยังสภาพแวดล้อมหลัก

my_source <- function(..., local=NULL) {
  tmp <- new.env(parent=parent.frame())
  source(..., local = tmp)
  funs <- names(tmp)[unlist(eapply(tmp, is.function))]
  for(x in names(tmp)) {
    assign(x, tmp[[x]], envir = parent.frame())
  }
  list(functions=funs)
}

my_source("script.R")

ขอบคุณวิธีแก้ปัญหานี้ดูมีแนวโน้มเป็นเพียงหนึ่งเดียวในตอนนี้! น่าประหลาดใจที่มี upvotes น้อยที่สุด มันเป็นสิ่งที่ฉันพูดถึงว่าเป็นทางเลือกสุดท้าย แต่ใช้new.env()แทนความหรูหราlocal({ })ซึ่งฉันไม่แน่ใจว่ามันจะทำงานกับassignเฟรมหลักหรือไม่
TMS

1) คุณคิดว่ามันจะใช้งานได้local()หรือไม่? และ BTW, 2) สิ่งที่คุณทำในการวนซ้ำ: ไม่มีฟังก์ชั่นการรวมสภาพแวดล้อมบางอย่างใช่ไหม
TMS

1
@TMS มันอาจใช้ได้กับ local แม้ว่าฉันจะไม่ได้ลองก็ตาม ฉันไม่รู้วิธีอื่นในการคัดลอกตัวแปรทั้งหมดจากสภาพแวดล้อมหนึ่งไปยังอีกสภาพแวดล้อมหนึ่ง มันไม่ใช่การดำเนินการทั่วไป
MrFlick

ฉันคิดว่าattachสามารถนำมาใช้เพื่อดีแนบสภาพแวดล้อมหนึ่งไปยังอีก แม้ว่าคุณจะต้องใช้อาร์กิวเมนต์มากกว่าระบุpos parent.frameและมันจะทำงานได้ดีสำหรับการคัดลอกสภาพแวดล้อมทั้งหมดforห่วงของ MrFlick ช่วยให้คุณคัดลอกเฉพาะฟังก์ชั่น
Gregor Thomas

5

มันค่อนข้างวุ่นวาย แต่คุณสามารถดูการเปลี่ยนแปลงในวัตถุก่อนและหลังการsourceโทรเช่นนี้

    # optionally delete all variables
    #rm(list=ls())

    before <- ls()
    cat("f1 <- function(){}\nf2 <- function(){}\n", file = 'define_function.R')
    # defines these
    #f1 <- function() {}
    #f2 <- function() {}
    source('define_function.R')
    after <- ls()

    changed <- setdiff(after, before)
    changed_objects <- mget(changed, inherits = T)
    changed_function <- do.call(rbind, lapply(changed_objects, is.function))
    new_functions <- changed[changed_function]

    new_functions
    # [1] "f1" "f2"

ขอบคุณ! ฉันมีความคิดนี้เช่นกัน แต่มันก็ไม่ได้ผลเพราะเหตุผลง่ายๆ - ถ้าแพ็กเกจถูกโหลดแล้ว (ซึ่งเกิดขึ้นตลอดเวลาเมื่อฉันทำการดีบั๊กโค้ดฉันแค่หาแหล่งที่มาใหม่) แล้วมันจะไม่ส่งคืนอะไรเลย
TMS

3

ฉันคิดว่า regex นี้จับเกือบทุกประเภทของฟังก์ชั่นที่ถูกต้อง (ตัวดำเนินการไบนารีฟังก์ชั่นการมอบหมาย) และทุกตัวละครที่ถูกต้องในชื่อฟังก์ชั่น แต่ฉันอาจจะพลาดกรณีขอบ

# lines <- readLines("functions.R")

lines <- c(
  "`%in%` <- function",
  "foo <- function",
  "foo2bar <- function",
  "`%in%`<-function",
  "foo<-function",
  ".foo <-function",
  "foo2bar<-function",
  "`foo2bar<-`<-function",
  "`foo3bar<-`=function",
  "`foo4bar<-` = function",
  "` d d` <- function", 
  "lapply(x, function)"
)
grep("^`?%?[.a-zA-Z][._a-zA-Z0-9 ]+%?(<-`)?`?\\s*(<-|=)\\s*function", lines)
#>  [1]  1  2  3  4  5  6  7  8  9 10
funs <- grep("^`?%?[.a-zA-Z][._a-zA-Z0-9 ]+%?(<-`)?`?\\s*(<-|=)\\s*function", lines, value = TRUE)
gsub("^(`?%?[.a-zA-Z][._a-zA-Z0-9 ]+%?(<-`)?`?).*", "\\1", funs)
#>  [1] "`%in%`"      "foo "        "foo2bar "    "`%in%`"      "foo"        
#>  [6] ".foo "       "foo2bar"     "`foo2bar<-`" "`foo3bar<-`" "`foo4bar<-`"

1
fyi ฉันคิดว่านี่ไม่ใช่ทางออกที่ดีแต่มันเป็นทางออกที่สนุกแน่นอน ฉันอาจจะแปลงไฟล์เป็นแพ็คเกจหากฉันต้องการข้อมูลนี้จริงๆ
alan ocallaghan

ฉันพลาดเคสสองรุ่นแล้ว! ฟังก์ชั่นสามารถเริ่มต้นด้วย.และฟังก์ชั่นการมอบหมาย ( `foo<-`<- function(x, value)มีอยู่จริง
alan ocallaghan

ฉันใช้=สำหรับการมอบหมายนี่จะไม่จับฟังก์ชั่นของฉันเลย ...
Gregor Thomas

จับได้ดี - แก้ไข ฉันจะทราบว่า R ช่วยให้คุณทำสิ่งที่โง่เหมือน` d d` <- function(x)ที่ไม่ได้จับในขณะนี้ ฉันไม่ต้องการให้ regex โง่เกินไปแม้ว่าฉันจะกลับมาใหม่
alan ocallaghan

นอกจากนี้คุณยังสามารถกำหนดฟังก์ชั่นที่มีassign, และ<<- ->และมันจะยากมากที่จะทำให้วิธีการนี้เป็นบัญชีสำหรับฟังก์ชั่นที่กำหนดไว้ในฟังก์ชั่น แต่ไม่ได้อยู่ในสภาพแวดล้อมที่มาจริง คำตอบของคุณควรทำงานได้ดีสำหรับกรณีมาตรฐาน แต่คุณไม่ต้องการเขียนตัวแยกวิเคราะห์ R ออกจาก regex
Gregor Thomas

1

หากนี่เป็นสคริปต์ของคุณเองเพื่อให้คุณสามารถควบคุมวิธีการจัดรูปแบบการประชุมที่เรียบง่ายน่าจะเพียงพอ เพียงตรวจสอบให้แน่ใจว่าชื่อฟังก์ชั่นแต่ละตัวเริ่มต้นที่ตัวอักษรตัวแรกของบรรทัดและคำfunctionนั้นจะปรากฏบนบรรทัดนั้น การใช้คำอื่น ๆfunctionควรปรากฏบนบรรทัดที่ขึ้นต้นด้วยเว้นวรรคหรือแท็บ จากนั้นโซลูชันหนึ่งบรรทัดคือ:

sub(" .*", "", grep("^\\S.*function", readLines("myscript.R"), value = TRUE))

ข้อดีของวิธีนี้คือ

  • มันเป็นอย่างเรียบง่าย มีการระบุกฎอย่างง่าย ๆ และมีรหัส R บรรทัดเดียวที่จำเป็นสำหรับแยกชื่อฟังก์ชัน Regex นั้นง่ายและสำหรับไฟล์ที่มีอยู่มันง่ายมากในการตรวจสอบ - เพียงแค่พิมพ์คำfunctionและตรวจสอบว่าแต่ละเหตุการณ์ที่ปรากฏตามกฎ

  • ไม่จำเป็นต้องเรียกใช้แหล่งที่มา มันเป็นทั้งแบบคงที่

  • ในหลายกรณีคุณไม่จำเป็นต้องเปลี่ยนไฟล์ต้นฉบับเลยและในกรณีอื่น ๆ จะมีการเปลี่ยนแปลงเล็กน้อย หากคุณกำลังเขียนสคริปต์ตั้งแต่เริ่มต้นโดยคำนึงถึงสิ่งนี้มันจะง่ายกว่าในการจัดการ

มีทางเลือกอื่น ๆ อีกมากมายตามแนวความคิดของการประชุม คุณอาจมี regex ที่ซับซ้อนมากขึ้นหรือคุณสามารถเพิ่ม# FUNCTIONที่ท้ายบรรทัดแรกของนิยามฟังก์ชันใด ๆ หากคุณกำลังเขียนสคริปต์ตั้งแต่เริ่มต้นและจากนั้น grep out วลีนั้นและแยกคำแรกในบรรทัด แต่คำแนะนำหลักที่นี่ดูเหมือน น่าสนใจเป็นพิเศษเนื่องจากความเรียบง่ายและข้อดีอื่น ๆ ที่ระบุไว้

ทดสอบ

# generate test file
cat("f <- function(x) x\nf(23)\n", file = "myscript.R") 

sub(" .*", "", grep("^\\S.*function", readLines("myscript.R"), value = TRUE))
## [1] "f"

lapply(x, function(y) dostuff(y))จะทำลายสิ่งนี้
alan ocallaghan

@alan ocallaghan ตัวอย่างของคุณละเมิดกฎที่ระบุไว้ดังนั้นจึงไม่สามารถเกิดขึ้นได้อย่างถูกต้อง หากต้องการเขียนสิ่งนี้และยังคงอยู่ภายในกฎหนึ่งจะต้องเริ่มทำงานในบรรทัดใหม่ที่มีการเยื้องหรืออย่างใดอย่างหนึ่งอาจต้องเยื้อง lapply
G. Grothendieck

ฉันคิดว่ายูทิลิตี้จะลดลงอย่างมากหากคุณต้องการการจัดรูปแบบเฉพาะเนื่องจากอาจต้องเปลี่ยนไฟล์ - ในกรณีนี้คุณอาจแนะนำให้ผู้ใช้อ่านชื่อฟังก์ชั่นด้วยตัวเอง
alan ocallaghan

1
นี่เป็นข้อควรพิจารณาหากคุณไม่ได้ควบคุมไฟล์ แต่เราได้แยกความเป็นไปได้ดังกล่าว การใช้ระเบียบเป็นเรื่องธรรมดามากในการเขียนโปรแกรม ฉันมักจะใส่# TODOรหัสของฉันเพื่อที่ฉันสามารถ grep สิ่งที่ต้องทำของฉัน ความเป็นไปได้อื่น ๆ ในบรรทัดเดียวกันจะถูกเขียน# FUNCTIONที่ท้ายบรรทัดแรกของนิยามฟังก์ชันใด ๆ
G. Grothendieck

1
การพยายามแยกวิเคราะห์ด้วย regex เป็น
TMS

0

สิ่งนี้จะปรับรหัสที่ใช้ในการโพสต์จากความคิดเห็นของฉันเพื่อค้นหาลำดับของโทเค็น (สัญลักษณ์ผู้ประกอบการที่ได้รับมอบหมายจากนั้นฟังก์ชั่น) และมันควรจะคว้าฟังก์ชั่นที่กำหนด ฉันไม่แน่ใจว่าคำตอบของ MrFlick นั้นแข็งแกร่งหรือไม่ แต่เป็นตัวเลือกอื่น:

source2 <- function(file, ...) {
  source(file, ...)
  t_t <- subset(getParseData(parse(file)), terminal == TRUE)
  subset(t_t, token == "SYMBOL" & 
           grepl("ASSIGN", c(tail(token, -1), NA), fixed = TRUE) & 
           c(tail(token, -2), NA, NA) == "FUNCTION")[["text"]]
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.