Nhận tất cả các chức năng nguồn


11

Trong R, tôi đang sử dụng source() để tải một số chức năng:

source("functions.R")

Có thể lấy danh sách tất cả các chức năng được xác định trong tệp này? Như tên hàm. (Có lẽsource() chính nó bằng cách nào đó có thể trả lại nó?).

Tái bút: Phương án cuối cùng là gọi source()lần thứ hai như local({ source(); })sau đó thực hiện các ls()chức năng bên trong và lọc, nhưng điều đó quá phức tạp - có giải pháp nào dễ dàng và ít vụng về hơn không?


1
Điều này không sử dụng source(), nhưng chủ đề cũ này có thể được bạn quan tâm.
Andrew

1
@ Cảm ơn, tôi đã kiểm tra các giải pháp được đề xuất nhưng nghe có vẻ điên rồ hơn giải pháp cuối cùng tôi đã trình bày trong câu hỏi :)
TMS

2
Tôi không biết rằng giải pháp này thật điên rồ:envir <- new.env() source("functions.R", local=envir) lsf.str(envir)
LocoGris

2
Tạo một gói ra khỏi các tập tin nguồn của bạn. Sau đó, bạn nhận được tất cả các lợi thế bao gồm một không gian tên gói.
Roland

@TMS, hiểu nhầm câu hỏi của bạn / không đọc rằng bạn muốn các hàm được xác định . Xin lỗi!
Andrew

Câu trả lời:


7

Tôi nghĩ cách tốt nhất sẽ là nguồn tệp vào một môi trường tạm thời. Truy vấn môi trường đó cho tất cả các hàm, sau đó sao chép các giá trị đó vào môi trường cha.

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")

cảm ơn, giải pháp này có vẻ đầy hứa hẹn, là người duy nhất ngay bây giờ! Đáng ngạc nhiên, một trong những ít upvote. Đó là cái tôi đã đề cập như là phương sách cuối cùng, nhưng sử dụng new.env()thay vì thanh lịch local({ })mà tôi không chắc liệu nó có hoạt động với assignkhung chính hay không.
TMS

1) bạn có nghĩ rằng nó sẽ làm việc với local()? Và BTW, 2) những gì bạn làm trong vòng lặp for: không có chức năng nào đó để hợp nhất các môi trường?
TMS

1
@TMS Nó có thể hoạt động với local, mặc dù tôi chưa thử. Tôi không biết một cách khác để sao chép tất cả các biến từ môi trường này sang môi trường khác. Đây không phải là một hoạt động phổ biến.
MrFlick

Tôi nghĩ rằng attachcó thể được sử dụng để, tốt, gắn một môi trường này với một môi trường khác. Mặc dù bạn phải sử dụng posđối số chứ không chỉ định parent.frame. Và nó sẽ chỉ hoạt động tốt để sao chép toàn bộ môi trường, forvòng lặp của MrFlick cho phép bạn chỉ sao chép các chức năng.
Gregor Thomas

5

Hơi khó hiểu một chút nhưng bạn có thể nhìn vào những thay đổi trong các đối tượng trước và sau sourcecuộc gọi như thế này.

    # 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"

Cảm ơn! Tôi cũng có ý tưởng này nhưng nó không hoạt động vì một lý do rất đơn giản - nếu gói đã được tải (điều này xảy ra mọi lúc khi tôi gỡ lỗi mã, tôi chỉ lấy lại nguồn), sau đó nó không trả về gì cả.
TMS

3

Tôi nghĩ rằng regex này nắm bắt hầu hết mọi loại hàm hợp lệ (toán tử nhị phân, hàm gán) và mọi ký tự hợp lệ trong một tên hàm, nhưng tôi có thể đã bỏ lỡ một trường hợp cạnh.

# 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 Tôi nghĩ rằng đây không thực sự là một giải pháp tốt nhưng nó chắc chắn là một giải pháp thú vị . Tôi có thể sẽ chuyển đổi tập tin thành một gói nếu tôi thực sự cần thông tin này.
alan ocallaghan

Tôi đã bỏ lỡ hai trường hợp cạnh! Các chức năng có thể bắt đầu bằng .và gán các chức năng ( `foo<-`<- function(x, value)tồn tại.
alan ocallaghan 15/11/19

Tôi sử dụng =để chuyển nhượng, điều này sẽ không nắm bắt được bất kỳ chức năng nào của tôi ...
Gregor Thomas

Bắt tốt - chỉnh sửa. Tôi sẽ lưu ý rằng R cho phép bạn làm những điều ngớ ngẩn như ` d d` <- function(x)hiện không bị bắt. Tôi không muốn regex trở nên quá ngớ ngẩn, mặc dù tôi có thể xem lại.
alan ocallaghan

Ngoài ra, bạn có thể gán chức năng với assign, <<-, và ->. Và sẽ rất khó để làm cho cách tiếp cận này giải thích cho các hàm được xác định trong các hàm, nhưng thực tế không phải trong môi trường có nguồn gốc. Câu trả lời của bạn sẽ hoạt động rất tốt cho các trường hợp tiêu chuẩn, nhưng bạn thực sự không muốn viết một trình phân tích cú pháp R ra khỏi regex.
Gregor Thomas

1

Nếu đây là tập lệnh của riêng bạn để bạn có quyền kiểm soát cách nó được định dạng thì một quy ước đơn giản sẽ là đủ. Chỉ cần đảm bảo rằng mỗi tên hàm bắt đầu từ ký tự đầu tiên trên dòng của nó và từ đó functioncũng xuất hiện trên dòng đó. Bất kỳ việc sử dụng nào khác của từ functionsẽ xuất hiện trên một dòng bắt đầu bằng khoảng trắng hoặc tab. Sau đó, một giải pháp một dòng là:

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

Ưu điểm của phương pháp này là

  • nó là rất đơn giản . Các quy tắc được nêu đơn giản và chỉ có một dòng mã R đơn giản cần thiết để trích xuất các tên hàm. Regex cũng đơn giản và đối với một tệp hiện có, rất dễ kiểm tra - chỉ cần grep từ functionvà kiểm tra xem mỗi lần xuất hiện được hiển thị có theo quy tắc hay không.

  • không cần chạy nguồn. Nó hoàn toàn tĩnh .

  • trong nhiều trường hợp, bạn sẽ không cần phải thay đổi tệp nguồn và trong những trường hợp khác sẽ có những thay đổi tối thiểu. Nếu bạn đang viết kịch bản từ đầu với ý nghĩ này thì việc sắp xếp lại càng dễ dàng hơn.

Có nhiều lựa chọn thay thế khác cùng với ý tưởng về các quy ước. bạn có thể có một biểu thức chính quy phức tạp hơn hoặc bạn có thể thêm # FUNCTIONvào cuối dòng đầu tiên của bất kỳ định nghĩa hàm nào nếu bạn đang viết tập lệnh từ đầu và sau đó rút ra cụm từ đó và trích xuất từ ​​đầu tiên trên dòng nhưng dường như gợi ý chính ở đây đặc biệt hấp dẫn do tính đơn giản của nó và các lợi thế khác được liệt kê.

Kiểm tra

# 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))sẽ phá vỡ điều này
alan ocallaghan

@alan ocallaghan, Ví dụ của bạn vi phạm các quy tắc đã nêu nên không thể xảy ra hợp lệ. Để viết điều này và vẫn nằm trong các quy tắc, người ta sẽ phải bắt đầu hoạt động trên một dòng mới được thụt lề hoặc người ta có thể phải thụt lề một cách chậm chạp.
G. Grothendieck

Tôi nghĩ rằng tiện ích bị xuống cấp ồ ạt nếu bạn yêu cầu định dạng cụ thể, vì điều đó có thể yêu cầu thay đổi tệp - trong trường hợp đó, bạn cũng có thể đề nghị người dùng đọc tên hàm theo cách thủ công
alan ocallaghan

1
Đó chỉ là một sự cân nhắc nếu bạn không kiểm soát tệp nhưng chúng tôi đã loại trừ khả năng đó. Sử dụng quy ước là rất phổ biến trong lập trình. Tôi thường đặt # TODOtrong suốt mã của mình để tôi có thể grep tôi làm, chẳng hạn. Một khả năng khác dọc theo cùng một dòng sẽ được ghi # FUNCTIONở cuối dòng đầu tiên của bất kỳ định nghĩa hàm nào.
G. Grothendieck

1
cố gắng phân tích cú pháp với regex là đường đến địa ngục ....
TMS

0

Điều này điều chỉnh mã được sử dụng trong bài đăng từ nhận xét của tôi để tìm kiếm một chuỗi mã thông báo (ký hiệu, toán tử gán, sau đó là hàm) và nó sẽ lấy bất kỳ hàm xác định nào. Tôi không chắc liệu nó có mạnh mẽ như câu trả lời của MrFlick không, nhưng đó là một lựa chọn khác:

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"]]
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.