如何查看函数的源代码?

687

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

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

在这种情况下,UseMethod("t") 是什么意思?例如:t(1:10)使用的实际源代码在哪里可以找到?
当我看到UseMethodstandardGeneric以及showMethods时,是否存在差异,就像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。我该如何找到这些函数的源代码?


2
请参见https://dev59.com/LnM_5IYBdhLWcg3wTRLL。 - Richie Cotton
1
请参见https://dev59.com/BWYr5IYBdhLWcg3wAFg0#14035586,了解有关R内部或原始函数源代码的信息。 - Ben Bolker
请参见https://dev59.com/PGDVa4cB1Zd3GeqPf7M_。 - Mark Miller
13个回答

639

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.makeNamesTs是来自stats命名空间的未导出函数。您可以使用:::运算符或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 代码。上面输出中的 <bytecode: 0x294e410> 行表示函数已被字节编译,您仍然可以从 R 命令行查看源代码。

调用 .C.Call.Fortran.External.Internal.Primitive 的函数是在编译代码中调用入口点,因此如果您想完全理解该函数,就必须查看编译代码的源代码。这个 R 源代码的 GitHub 镜像是一个不错的起点。函数 pryr::show_c_source 可以是一个有用的工具,因为它会直接带您到 .Internal.Primitive 调用的 GitHub 页面。包可能使用 .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外部完成,也可以使用untar()函数在R中完成。可以将下载和扩展步骤合并为一次调用(请注意,只能以这种方式下载和解压缩一个软件包):

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

如果软件包的开发是公开托管的(例如通过GitHubR-ForgeRForge.net),您可以在网上浏览源代码。

基本软件包中的编译代码

某些软件包被认为是“基本”软件包。这些软件包与R一起发布,并且其版本锁定为R的版本。例如,它们包括basecompilerstatsutils。因此,它们不像上面描述的那样作为单独可下载的软件包在CRAN上提供。相反,它们是R源树中的个别软件包目录下的一部分,在/src/library/下。如何访问R源在下一节中介绍。

内置于R解释器中的编译代码

如果要查看内置于R解释器中的代码,则需要下载/解压缩R源代码;或者您可以通过R Subversion存储库Winston Chang的github镜像在线查看源代码。

Uwe Ligges的R新闻文章(PDF)(第43页)是查看.Internal.Primitive函数源代码的良好参考。基本步骤是首先在src/main/names.c中查找函数名称,然后在src/main/*文件中搜索“C-entry”名称。


94
如果你使用 RStudio,按下 F2 键时它会尝试获取光标所在的函数的源代码。 - Ari B. Friedman
1
@Ari B. Friedman 对于这个晚了的问题表示抱歉。RStudio会拉取C源代码函数吗,还是只针对用R编写的函数?谢谢。 - Sunny
4
我认为这只是R源代码。 - Ari B. Friedman
2
模仿是最真诚的赞美,我假设这个答案/维基是先出现的 :) 在此之前http://rfaqs.com/source-code-of-r-method - JimLohse
2
哎呀,getMethod()已经被弃用并不再可用。替代它的findMethods()的帮助文件没有展示如何获取S4方法的源代码。 - Eli Holmes
显示剩余5条评论

114

除了此问题及其重复的其他答案外,以下是一种很好的获取程序包函数源代码的方法,无需知道它属于哪个程序包。

例如,如果我们想要randomForest::rfcv()的源代码:

在弹出窗口中查看/编辑它的方法:

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

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

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

  • View()非常适合浏览(多列)数据,但通常对于任何不是玩具长度的代码来说都很糟糕。
  • 因此,当只想要查看代码时,edit()实际上比View()更好,在使用edit()时,您可以折叠/隐藏/虚拟出所有arg-parsing/checking/default/error-message逻辑,这可以占用R函数的70%,然后只需进入函数实际执行操作的部分(!),以及它返回的对象类型是什么,是否以及如何递归等。

如果要重定向到单独的文件(以便可以在喜欢的IDE /编辑器中打开代码/使用grep等进行处理):

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

不可否认,getAnywhere 是另一个荒谬的 R 名称选择,本应该被称为 findOnSearchPath 或类似名称。 - smci
1
我会点赞这个答案,因为它让我接近了我想要的东西。实际上,在RStudio中,我想要的是View(foo);其中foo是已经加载的包中的一个函数。 - Sigfried
1
@Sigfried:edit()打开一个文本编辑器(由用户选择),而View()打开一个类似Excel的电子表格浏览器,用于浏览数据,后者适用于浏览(多列)数据,但通常对除了玩具长度之外的任何代码都非常糟糕。例如,正如我所提示的那样,通常当我浏览一个函数时,我想做的第一件事就是跳过/折叠/虚拟化所有参数解析和默认操作逻辑,以查看该函数实际上执行的操作。 - smci
@Sigfried:已更新以包含所有那些评论/提示。 - smci

34

对于非原始函数,R基础包中包括一个名为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()中,我最终得到了以下代码片段:

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

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

编辑2020-12-31

获取相同的character源代码向量的更简单方法是:

sourceVector = deparse(body(x$fun))

31

当您使用debug()函数进行调试时,它会被揭示出来。假设您想查看t()转置函数中的基础代码。只是输入't'并不能揭示太多信息。

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

但是,使用“debug(函数名称)”可以显示底层代码,但不包含内部细节。

> 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()函数来达到同样的目的。


1
与接受答案中提供的方法相比,这种方法的缺点是你需要一个可用的函数调用(所有必要参数都已指定,且符合要求);此外,除了初始代码块外,你还会在每次运行时获取每个代码块。这对于调试非常有用,但不适合仅获取源代码。 - Brian Diggs
是的,这并不是最优解。但如果你聪明的话,你可以快速而肮脏地获取源代码,特别是对于内置函数。 - Selva
5
在这种情况下,我建议使用debugonce而不是debug - Joshua Ulrich

22

虽然我不确定这段内容如何与主要答案联系起来,但它曾经使我困扰了一段时间,所以我在这里添加一些内容:

中缀运算符

要查看一些基本中缀运算符(例如%%%*%%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("%%")

主要答案涵盖了如何使用镜像来深入挖掘。


8
smci的回答推荐使用getAnywhere。如果您已经知道运算符的名称,也可以使用反引号:\%in%``。 - Joshua Ulrich
3
@JoshuaUlrich不知道你也可以使用反引号!谢谢。你的回答中也提到了getAnywhere,但我认为针对infix的具体参考对于将来参考这个答案是有用的——我已经多次阅读了这个页面,试图找到这些函数的代码,但仍然感到困惑——而且我认为它不适合放在其他回答的流程中(它们都在使用getAnywhere来实现其他目的)。 - MichaelChirico

15

在RStudio中,至少有三种方法:

  1. 光标处于任何函数时按F2键。
  2. 按住Ctrl或Command单击函数名称
  3. View(function_name)(如上所述)

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


14

R中有一个非常方便的函数edit

new_optim <- edit(optim)

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

如果您只想查看源代码而不想在控制台上打印冗长的源代码,请使用

invisible(edit(optim))

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

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


3
这种方法只会呈现与打印函数相同的函数源代码(也就是与问题中所示相同)。超越这一点,正是这个问题所探讨的内容。 - Brian Diggs
2
@BrianDiggs 是的,你说得对。我并不是想回答这个问题,因为Joshua已经给出了一个相当完整的答案。我只是试图添加一些与主题相关的有趣且可能有用的信息。 - Eric
对不起,我7个月前发布了这个帖子。使用invisible(edit(...))是一个好的提示,还有备注“在C/C++或Fortran上不起作用”。 - smci

9
只要函数是用纯R编写的而不是C/C++/Fortran,就可以使用以下方法。否则,最好的方法是调试并使用“跳转到”。
> functionBody(functionName)

2
这与 body 相同。identical(functionBody, body)TRUE - Joshua Ulrich
1
base::bodymethods::functionBody,虽然它们不太可能被分离。body也可以被覆盖:https://www.rdocumentation.org/search?q=body - moodymudskipper

6

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


视图需要类似数据框的对象,它不接受函数(在基本R中)。您所描述的是RStudio的修改。 - Bryan Hanson

5

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


5
print.function() 是一个 S3 方法。其通用函数是 print()。直接调用方法通常不是一个好主意,这破坏了通用函数和方法分派的整个目的。 - Joshua Ulrich

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接