我想查看一个函数的源代码,看看它是如何工作的。我知道我可以通过在提示符处输入函数名称来打印函数:
> t
function (x)
UseMethod("t")
<bytecode: 0x2332948>
<environment: namespace:base>
在这种情况下,UseMethod("t")
是什么意思?如何找到实际使用的源代码,例如:t(1:10)
?
看到 UseMethod
和看到 standardGeneric
和 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
。我怎样才能找到这些的源代码?
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
的函数正在调用已编译代码中的入口点,因此如果需要,您必须查看已编译代码的源代码充分了解功能。 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])
或者,如果包开发是公开托管的(例如通过 GitHub、R-Forge 或 RForge.net),您可能可以在线浏览源代码。
基础包中的编译代码
某些软件包被视为“基本”软件包。这些软件包随 R 一起提供,并且它们的版本被锁定为 R 的版本。示例包括 base
、compiler
、stats
和 utils
。因此,如上所述,它们不能作为 CRAN 上的单独可下载包提供。相反,它们是 /src/library/
下各个包目录中 R 源代码树的一部分。下一节将介绍如何访问 R 源代码。
编译后的代码内置到 R 解释器中
如果要查看 R 解释器内置的代码,则需要下载/解压缩 R 源代码;或者您可以通过 R Subversion repository 或 Winston Chang's github mirror 在线查看源代码。
Uwe Ligges 的 R news article (PDF) (p. 43) 是关于如何查看 .Internal
和 .Primitive
函数的源代码的很好的一般参考。基本步骤是首先在 src/main/names.c
中查找函数名称,然后在 src/main/*
中的文件中搜索“C-entry”名称。
除了关于这个问题的其他答案及其重复项之外,这里有一个很好的方法来获取包函数的源代码,而无需知道它在哪个包中。例如,如果我们想要 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/etc 处理它):
capture.output(getAnywhere('rfcv'), file='source_rfcv.r')
View(foo)
;其中 foo
是来自已加载包的函数。
edit()
opens a text editor (of user's choice),而 View()
opens an Excel-type spreadsheet viewer for data,后者适用于浏览(多列)数据,但对于玩具长度以外的任何代码通常都很糟糕。例如,正如我所暗示的那样,通常在浏览函数时我想做的第一件事是跳过/折叠/虚拟出所有 arg-parsing 和默认操作逻辑,以查看该函数实际上 做了什么。
当您使用 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
。
对于非原始函数,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()
中得到了以下代码段:
sourceVector = capture.output(print(body(x[["fun"]])))
cat(paste0(" ", sourceVector, "\n"))
它缩进并显示与 x[["fun"]]
关联的代码。
编辑 2020-12-31
获得相同的源代码 character
向量的一种不那么迂回的方法是:
sourceVector = deparse(body(x$fun))
没有看到这如何适合主要答案的流程,但它让我难过一段时间,所以我在这里添加它:
中缀运算符
要查看一些基本中缀运算符(例如,%%
、%*%
、%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("%%")
主要答案包括如何使用镜像进行更深入的挖掘。
getAnywhere
在您的回答中也提到了,但我认为对中缀的特定参考对于将来参考此答案很有用 - 我已经多次阅读此页面,但在尝试查找此类代码时仍然有点困惑功能一段时间 - 我认为它不适合其他任何一个答案的流程(它们都将 getAnywhere
用于另一个目的)。
在 RStudio 中,(至少)有 3 种方式:
当光标在任何功能上时按 F2 键。按住 Ctrl 或 Command View(function_name) 的同时单击函数名称(如上所述)
将打开一个包含源代码的新窗格。如果您达到 .Primitive 或 .C ,您将需要另一种方法,抱歉。
R edit
中有一个非常方便的函数
new_optim <- edit(optim)
它将使用 R 的 options
中指定的编辑器打开 optim
的源代码,然后您可以对其进行编辑并将修改后的函数分配给 new_optim
。我非常喜欢这个功能来查看代码或调试代码,例如,打印一些消息或变量,甚至将它们分配给全局变量以便进一步调查(当然你可以使用debug
)。
如果你只想查看源代码,不想在控制台上打印烦人的长源代码,你可以使用
invisible(edit(optim))
显然,这不能用于查看 C/C++ 或 Fortran 源代码。
顺便说一句,edit
可以打开其他对象,如列表、矩阵等,然后也显示带有属性的数据结构。函数 de
可用于打开类似 excel 的编辑器(如果 GUI 支持)来修改矩阵或数据框并返回新的。有时这很方便,但在通常情况下应避免使用,尤其是当矩阵很大时。
invisible(edit(...))
是一个很好的提示,还有“不适用于 C/C++ 或 Fortran”的注释。
只要该函数是用纯 R 而不是 C/C++/Fortran 编写的,就可以使用以下内容。否则最好的方法是调试并使用“跳转”:
> functionBody(functionName)
body
相同。 identical(functionBody, body)
是 TRUE
。
View(function_name)
- 例如。 View(mean)
确保使用大写 [V]。只读代码将在编辑器中打开。
您还可以尝试使用 S3 通用的 print.function()
来获取在控制台中写入的函数。
print.function()
是一种 S3 方法。泛型是 print()
。而且直接调用方法通常不是一个好主意。这违背了泛型函数和方法分派的全部目的。
要查看函数的源代码,请使用 print()
f <- function(x){
x * 2
}
print(f)
function(x){
x * 2
}
首先,尝试运行不带 () 的函数
示例:让我们获取 cat()
函数的源代码:
cat
if (is.character(file))
if (file == "")
file <- stdout()
else if (startsWith(file, "|")) {
file <- pipe(substring(file, 2L), "w")
on.exit(close(file))
}
else {
file <- file(file, ifelse(append, "a", "w"))
on.exit(close(file))
}
.Internal(cat(list(...), file, sep, fill, labels, append))
但有时会返回“UseMethod”而不是源代码
如果我们尝试获取 read_xml()
的源代码:
library(xml2)
read_xml
# UseMethod("read_xml")
这对我们没有多大用处!在这种情况下,请看一下方法:
methods("read_xml")
# read_xml.character* read_xml.connection* read_xml.raw* read_xml.response*
并在上面的值上使用 getAnywhere 来查看源代码:
getAnywhere("read_xml.character")
另一个例子
让我们尝试查看 qqnorm()
的源代码:
qqnorm
# UseMethod("qqnorm") # Not very useful
methods("qqnorm")
# [1] qqnorm.default* # Making progress...
getAnywhere("qqnorm.default") # Shows source code!
qqnorm
时没有出现 qqnorm()
的源代码,所以参考了这个问题/答案,但无法快速弄清楚,所以决定留下我自己的答案,给一小撮的 MRE(尽管有限,没有展示更复杂的情况,例如调用 c/c++/fortran 代码等)。如果我遗漏了什么,或者我错了,请指出,我会修改。
不定期副业成功案例分享
RStudio
,如果您按F2
键,它将尝试为您的文本光标所在的函数拉取源代码。getMethod()
已弃用且不再可用。替换它的findMethods()
的帮助文件没有显示如何获取 S4 方法的源代码。