【问题标题】:How can I view the source code for a function?如何查看函数的源代码?
【发布时间】:2022-01-10 01:40:34
【问题描述】:

我想查看一个函数的源代码以了解它是如何工作的。我知道我可以通过在提示符处输入函数名称来打印函数:

> t
function (x) 
UseMethod("t")
<bytecode: 0x2332948>
<environment: namespace:base>

在这种情况下,UseMethod("t") 是什么意思?如何找到实际使用的源代码,例如:t(1:10)

当我看到UseMethod 和当我看到standardGenericshowMethods 时,和with 一样,有区别吗?

> with
standardGeneric for "with" defined from package "base"

function (data, expr, ...) 
standardGeneric("with")
<bytecode: 0x102fb3fc0>
<environment: 0x102fab988>
Methods may be defined for arguments: data
Use  showMethods("with")  for currently available ones.

在其他情况下,我可以看到正在调用 R 函数,但我找不到这些函数的源代码。

> ts.union
function (..., dframe = FALSE) 
.cbind.ts(list(...), .makeNamesTs(...), dframe = dframe, union = TRUE)
<bytecode: 0x36fbf88>
<environment: namespace:stats>
> .cbindts
Error: object '.cbindts' not found
> .makeNamesTs
Error: object '.makeNamesTs' not found

如何找到.cbindts.makeNamesTs 之类的函数?

在其他情况下,有一些 R 代码,但大部分工作似乎在其他地方完成。

> matrix
function (data = NA, nrow = 1, ncol = 1, byrow = FALSE, dimnames = NULL) 
{
    if (is.object(data) || !is.atomic(data)) 
        data <- as.vector(data)
    .Internal(matrix(data, nrow, ncol, byrow, dimnames, missing(nrow), 
        missing(ncol)))
}
<bytecode: 0x134bd10>
<environment: namespace:base>
> .Internal
function (call)  .Primitive(".Internal")
> .Primitive
function (name)  .Primitive(".Primitive")

如何找出.Primitive 函数的作用?同样,某些函数调用.C.Call.Fortran.External.Internal。我怎样才能找到这些的源代码?

【问题讨论】:

标签: r function r-faq


【解决方案1】:

UseMethod("t") 告诉您t() 是一个 (S3) 通用函数,它具有用于不同对象类的方法。

S3方法调度系统

对于 S3 类,您可以使用 methods 函数列出特定通用函数或类的方法。

> methods(t)
[1] t.data.frame t.default    t.ts*       

   Non-visible functions are asterisked
> methods(class="ts")
 [1] aggregate.ts     as.data.frame.ts cbind.ts*        cycle.ts*       
 [5] diffinv.ts*      diff.ts          kernapply.ts*    lines.ts        
 [9] monthplot.ts*    na.omit.ts*      Ops.ts*          plot.ts         
[13] print.ts         time.ts*         [<-.ts*          [.ts*           
[17] t.ts*            window<-.ts*     window.ts*      

   Non-visible functions are asterisked

“不可见的函数带有星号”表示该函数未从其包的命名空间中导出。您仍然可以通过::: 函数(即stats:::t.ts)或使用getAnywhere() 查看其源代码。 getAnywhere() 很有用,因为您不必知道函数来自哪个包。

> getAnywhere(t.ts)
A single object matching ‘t.ts’ was found
It was found in the following places
  registered S3 method for t from namespace stats
  namespace:stats
with value

function (x) 
{
    cl <- oldClass(x)
    other <- !(cl %in% c("ts", "mts"))
    class(x) <- if (any(other)) 
        cl[other]
    attr(x, "tsp") <- NULL
    t(x)
}
<bytecode: 0x294e410>
<environment: namespace:stats>

S4方法调度系统

S4 系统是一种较新的方法调度系统,是 S3 系统的替代品。以下是 S4 函数的示例:

> library(Matrix)
Loading required package: lattice
> chol2inv
standardGeneric for "chol2inv" defined from package "base"

function (x, ...) 
standardGeneric("chol2inv")
<bytecode: 0x000000000eafd790>
<environment: 0x000000000eb06f10>
Methods may be defined for arguments: x
Use  showMethods("chol2inv")  for currently available ones.

输出已经提供了很多信息。 standardGeneric 是 S4 功能的指示器。提供查看已定义 S4 方法的方法很有帮助:

> showMethods(chol2inv)
Function: chol2inv (package base)
x="ANY"
x="CHMfactor"
x="denseMatrix"
x="diagonalMatrix"
x="dtrMatrix"
x="sparseMatrix"

getMethod可以查看其中一种方法的源码:

> getMethod("chol2inv", "diagonalMatrix")
Method Definition:

function (x, ...) 
{
    chk.s(...)
    tcrossprod(solve(x))
}
<bytecode: 0x000000000ea2cc70>
<environment: namespace:Matrix>

Signatures:
        x               
target  "diagonalMatrix"
defined "diagonalMatrix"

还有每个方法的签名更复杂的方法,例如

require(raster)
showMethods(extract)
Function: extract (package raster)
x="Raster", y="data.frame"
x="Raster", y="Extent"
x="Raster", y="matrix"
x="Raster", y="SpatialLines"
x="Raster", y="SpatialPoints"
x="Raster", y="SpatialPolygons"
x="Raster", y="vector"

要查看这些方法之一的源代码,必须提供整个签名,例如

getMethod("extract" , signature = c( x = "Raster" , y = "SpatialPolygons") )

提供部分签名是不够的

getMethod("extract",signature="SpatialPolygons")
#Error in getMethod("extract", signature = "SpatialPolygons") : 
#  No method found for function "extract" and signature SpatialPolygons

调用未导出函数的函数

对于ts.union.cbindts.makeNamesTsstats 命名空间中未导出的函数。您可以使用:::运算符或getAnywhere查看未导出函数的源代码。

> stats:::.makeNamesTs
function (...) 
{
    l <- as.list(substitute(list(...)))[-1L]
    nm <- names(l)
    fixup <- if (is.null(nm)) 
        seq_along(l)
    else nm == ""
    dep <- sapply(l[fixup], function(x) deparse(x)[1L])
    if (is.null(nm)) 
        return(dep)
    if (any(fixup)) 
        nm[fixup] <- dep
    nm
}
<bytecode: 0x38140d0>
<environment: namespace:stats>

调用编译代码的函数

请注意,“已编译”不是指由 compiler 包创建的字节编译的 R 代码。上面输出中的&lt;bytecode: 0x294e410&gt;行表示该函数是字节编译的,仍然可以从R命令行查看源代码。

调用 .C.Call.Fortran.External.Internal.Primitive 的函数正在调用编译代码中的入口点,因此您必须查看编译代码的源代码如果您想完全了解该功能。 This R 源代码的 GitHub 镜像是一个不错的起点。 pryr::show_c_source 函数可能是一个有用的工具,因为它会直接将您带到 GitHub 页面以进行 .Internal.Primitive 调用。包可以使用.C.Call.Fortran.External;但不是.Internal.Primitive,因为它们用于调用内置于R 解释器中的函数。

对上述某些函数的调用可能会使用对象而不是字符串来引用编译后的函数。在这些情况下,对象属于"NativeSymbolInfo""RegisteredNativeSymbol""NativeSymbol" 类;打印对象会产生有用的信息。例如,optim 调用.External2(C_optimhess, res$par, fn1, gr1, con)(注意这是C_optimhess,而不是"C_optimhess")。 optim 位于 stats 包中,因此您可以键入 stats:::C_optimhess 以查看有关正在调用的编译函数的信息。

包中的编译代码

如果你想查看一个包中的编译代码,你需要下载/解压包源。安装的二进制文件不够用。包的源代码可从最初安装包的同一 CRAN(或 CRAN 兼容)存储库中获得。 download.packages()函数可以为你获取包源。

download.packages(pkgs = "Matrix", 
                  destdir = ".",
                  type = "source")

这将下载Matrix包的源代码版本并将对应的.tar.gz文件保存在当前目录中。编译函数的源代码可以在未压缩和未去皮文件的src 目录中找到。解压缩和去皮步骤可以在R 之外完成,也可以在R 内使用untar() 函数完成。可以将下载和扩展步骤合并到一个调用中(注意,这种方式一次只能下载和解压一个包):

untar(download.packages(pkgs = "Matrix",
                        destdir = ".",
                        type = "source")[,2])

或者,如果包开发是公开托管的(例如通过GitHubR-ForgeRForge.net),您可能可以在线浏览源代码。

基础包中的编译代码

某些软件包被视为“基础”软件包。这些软件包随 R 一起提供,并且它们的版本被锁定为 R 的版本。示例包括 basecompilerstatsutils。因此,如上所述,它们不能作为 CRAN 上的单独可下载包提供。相反,它们是 /src/library/ 下各个包目录中 R 源代码树的一部分。下一节将介绍如何访问 R 源代码。

R 解释器内置的编译代码

如果您想查看 R 解释器内置的代码,您需要下载/解压 R 源代码;或者您可以通过 R Subversion repositoryWinston Chang's github mirror 在线查看源代码。

Uwe Ligges 的 R news article (PDF)(第 43 页)是一个很好的通用参考,可以帮助您查看 .Internal.Primitive 函数的源代码。基本步骤是先在src/main/names.c中查找函数名称,然后在src/main/*中的文件中查找“C-entry”名称。

【讨论】:

  • 如果您使用RStudio,如果您按下F2 键,它将尝试拉取文本光标所在函数的源代码。
  • @Ari B. Friedman 抱歉这个迟到的问题。 RStudio 是否还会为函数或仅为用 R 编写的函数提取 C 源代码?谢谢
  • @Samir 我相信这只是 R 源代码。
  • 模仿是最真诚的奉承形式 我认为这个答案/wiki 排在第一位 :) 在此之前rfaqs.com/source-code-of-r-method
  • getMethod() 已弃用且不再可用。替换它的findMethods() 的帮助文件没有显示如何获取 S4 方法的源代码。
【解决方案2】:

除了关于此问题的其他答案及其重复项之外,这是获取包函数源代码的好方法,而无需知道它在哪个包中。 例如说我们是否想要randomForest::rfcv()的来源:

在弹出窗口中查看/编辑

edit(getAnywhere('rfcv'), file='source_rfcv.r')

View(getAnywhere('rfcv'), file='source_rfcv.r')

注意edit() 打开一个文本编辑器(由用户选择),而 View() 调用电子表格样式的数据查看器。

  • View() 非常适合浏览(多列)数据,但对于玩具长度以外的任何代码来说通常很糟糕。
  • 所以当只想查看代码时,edit() 实际上比 View() 好得多,因为使用 edit() 您可以折叠/隐藏/虚拟出所有 arg-parsing/检查/默认/错误消息逻辑,它可能占用 R 函数的 70%,并且只到达函数实际操作执行某些操作的部分(!),它的返回类型是什么类型的对象,是否以及它是如何递归的等等。

重定向到一个单独的文件(这样你就可以在你最喜欢的 IDE/编辑器中调出代码/用 grep/etc 处理它):

capture.output(getAnywhere('rfcv'), file='source_rfcv.r')

【讨论】:

  • 诚然,getAnywhere 是另一个古怪的 R 名称选择,它应该被称为 findOnSearchPath 或类似名称。
  • 我会赞成这个答案,因为它让我接近了我想要的。在 RStudio 中,我真正想要的是 View(foo);其中foo 是来自已加载包的函数。
  • @Sigfried: edit() opens a text editor (of user's choice),而View() opens an Excel-type spreadsheet viewer for data,后者适用于浏览(多列)数据,但对于玩具长度以外的任何代码通常都很糟糕。例如,正如我所暗示的那样,通常在浏览函数时我想做的第一件事是跳过/折叠/虚拟出所有 arg 解析和默认操作逻辑,以查看函数实际上 做了什么.
  • @Sigfried:已更新以包含所有这些评论/提示。
【解决方案3】:

当您使用 debug() 函数进行调试时,它会显示出来。 假设您想查看 t() 转置函数中的底层代码。只是输入“t”,并没有透露太多信息。

>t 
function (x) 
UseMethod("t")
<bytecode: 0x000000003085c010>
<environment: namespace:base>

但是,使用“debug(functionName)”,它揭示了底层代码,没有内部结构。

> debug(t)
> t(co2)
debugging in: t(co2)
debug: UseMethod("t")
Browse[2]> 
debugging in: t.ts(co2)
debug: {
    cl <- oldClass(x)
    other <- !(cl %in% c("ts", "mts"))
    class(x) <- if (any(other)) 
        cl[other]
    attr(x, "tsp") <- NULL
    t(x)
}
Browse[3]> 
debug: cl <- oldClass(x)
Browse[3]> 
debug: other <- !(cl %in% c("ts", "mts"))
Browse[3]> 
debug: class(x) <- if (any(other)) cl[other]
Browse[3]>  
debug: attr(x, "tsp") <- NULL
Browse[3]> 
debug: t(x)

编辑: debugonce() 无需使用 undebug() 即可完成相同的操作

【讨论】:

  • 与接受的答案中给出的方法相比,此方法的缺点是您需要一个工作函数调用(指定所有必要的参数,可以接受);而且,除了初始代码块之外,您还可以在运行时获取每个块。这对于调试非常有用,但对于仅获取源代码来说并不是最佳选择。
  • 是的,它不是最优的。但如果你很聪明,你可以快速而肮脏地获取源代码,尤其是内置函数。
  • 我还建议在这种情况下使用debugonce 而不是debug
【解决方案4】:

对于非原始函数,R base 包含一个名为body() 的函数,它返回函数体。比如print.Date()函数的源码可以查看:

body(print.Date)

会产生这个:

{
    if (is.null(max)) 
        max <- getOption("max.print", 9999L)
    if (max < length(x)) {
        print(format(x[seq_len(max)]), max = max, ...)
        cat(" [ reached getOption(\"max.print\") -- omitted", 
            length(x) - max, "entries ]\n")
    }
    else print(format(x), max = max, ...)
    invisible(x)
}

如果你在一个脚本中工作并且想要函数代码作为一个字符向量,你可以得到它。

capture.output(print(body(print.Date)))

会得到你:

[1] "{"                                                                   
[2] "    if (is.null(max)) "                                              
[3] "        max <- getOption(\"max.print\", 9999L)"                      
[4] "    if (max < length(x)) {"                                          
[5] "        print(format(x[seq_len(max)]), max = max, ...)"              
[6] "        cat(\" [ reached getOption(\\\"max.print\\\") -- omitted\", "
[7] "            length(x) - max, \"entries ]\\n\")"                      
[8] "    }"                                                               
[9] "    else print(format(x), max = max, ...)"                           
[10] "    invisible(x)"                                                    
[11] "}"     

我为什么要做这样的事情?我正在基于列表创建一个自定义 S3 对象(x,其中class(x) = "foo")。列表成员之一(名为“fun”)是一个函数,我希望 print.foo() 显示缩进的函数源代码。所以我最终在print.foo() 中得到了以下sn-p:

sourceVector = capture.output(print(body(x[["fun"]])))
cat(paste0("      ", sourceVector, "\n"))

缩进并显示与x[["fun"]]相关的代码。

编辑 2020-12-31

获得相同的character 源代码向量的一种不那么迂回的方法是:

sourceVector = deparse(body(x$fun))

【讨论】:

    【解决方案5】:

    没有看到这如何适合主要答案的流程,但它让我难了一段时间,所以我在这里添加它:

    中缀运算符

    要查看一些基本中缀运算符的源代码(例如,%%%*%%in%),请使用getAnywhere,例如:

    getAnywhere("%%")
    # A single object matching ‘%%’ was found
    # It was found in the following places
    #   package:base
    #   namespace:base
    #  with value
    #
    # function (e1, e2)  .Primitive("%%")
    

    主要的答案是如何使用镜像进行更深入的挖掘。

    【讨论】:

    • smci's answer 推荐getAnywhere。或者,如果您已经知道运算符的名称,则可以只使用反引号:`%in%`
    • @JoshuaUlrich 不知道你可以使用反引号!谢谢。 getAnywhere 在您的回答中也提到了,但我认为对中缀的具体引用对于将来参考这个答案很有用——我已经读过这个页面很多次了,但仍然有点困惑试图找到这样的代码功能一段时间 - 我认为它不适合其他任何一个答案的流程(它们都将getAnywhere用于另一个目的)。
    【解决方案6】:

    在 RStudio 中,有(至少)3 种方式:

    1. 当光标在任何功能上时按 F2 键。
    2. 按住的同时单击函数名称 Ctrl 或 Command
    3. View(function_name)(如上所述)

    将打开一个包含源代码的新窗格。如果您到达 .Primitive 或 .C,您将需要另一种方法,抱歉。

    【讨论】:

      【解决方案7】:

      R中有一个很方便的函数edit

      new_optim <- edit(optim)
      

      它将使用R的options中指定的编辑器打开optim的源代码,然后您可以对其进行编辑并将修改后的功能分配给new_optim。我非常喜欢这个功能来查看代码或调试代码,例如打印一些消息或变量,甚至将它们分配给全局变量以进行进一步调查(当然你可以使用debug)。

      如果你只是想查看源代码,不想在你的控制台上打印烦人的长源代码,你可以使用

      invisible(edit(optim))
      

      显然,这不能用于查看 C/C++ 或 Fortran 源代码。

      顺便说一句,edit 可以打开其他对象,如列表、矩阵等,然后显示带有属性的数据结构。函数de 可用于打开类似excel 的编辑器(如果GUI 支持)来修改矩阵或数据框并返回新的。这有时很方便,但在通常情况下应避免使用,尤其是当矩阵很大时。

      【讨论】:

      • 这种方法只提供打印函数给出的相同函数源(即与问题中的相同)。比这更进一步/更深入就是这个问题的意义所在。
      • @BrianDiggs 是的,你是对的。我并不是要回答这个问题,因为约书亚已经给出了一个相当完整的答案。我只是尝试添加一些与主题相关的内容,有趣且可能对了解有用。
      • 对不起,我在 7 个月前发布了这个。不过,invisible(edit(...)) 的使用是一个很好的提示,还有“不适用于 C/C++ 或 Fortran”的备注。
      【解决方案8】:

      只要函数是用纯 R 而不是 C/C++/Fortran 编写的,就可以使用以下内容。否则最好的方法是调试并使用“jump into”:

      > functionBody(functionName)
      

      【讨论】:

      • 这与body 相同。 identical(functionBody, body)TRUE
      • base::bodymethods::functionBody,尽管它们不太可能被删除。 body 也可以被覆盖:rdocumentation.org/search?q=body
      【解决方案9】:

      View(function_name) - 例如。 View(mean) 确保使用大写 [V]。只读代码将在编辑器中打开。

      【讨论】:

      • ?View 需要一个类似对象的数据框,它不接受函数(在基础 R 中)。您所描述的是 RStudio 修改。
      【解决方案10】:

      您也可以尝试使用print.function(),它是 S3 通用的,在控制台中写入函数。

      【讨论】:

      • print.function() 是一种 S3 方法。通用是print()。而且直接调用方法通常不是一个好主意。这违背了泛型函数和方法分派的全部目的。
      【解决方案11】:

      要查看函数的源代码,请使用 print()

      f <- function(x){
           x * 2
       }
      
      print(f)
      
      function(x){
          x * 2
      }
      

      【讨论】:

        猜你喜欢
        • 2018-10-02
        • 2012-12-11
        相关资源
        最近更新 更多