Как я могу просмотреть исходный код функции?

551

Я хочу посмотреть на исходный код функции, чтобы увидеть, как она работает. Я знаю, что могу напечатать функцию, введя ее имя в командной строке:

> 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. Как я могу найти исходный код для тех?

Джошуа Ульрих
источник
2
См. Также stackoverflow.com/q/1439348/134830
Ричи Коттон
1
см. также stackoverflow.com/questions/14035506/…
Бен Болкер
См. Также stackoverflow.com/questions/9385411/…
Марк Миллер

Ответы:

518

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>

Функции, которые вызывают скомпилированный код

Обратите внимание, что «скомпилированный» не ссылается на байтово-скомпилированный R-код, созданный пакетом компилятора . <bytecode: 0x294e410>Строка в выводе выше показывает , что функция байт скомпилированные, и вы все еще можете просмотреть исходный код из командной строки R.

Функции, вызов .C, .Call, .Fortran, .External, .Internal, или .Primitiveвызывают точки входа в скомпилированном коде, так что вы должны смотреть на источниках скомпилированного кода , если вы хотите , чтобы полностью понять функцию. Это зеркало GitHub исходного кода R - хорошее место для начала. Эта функция 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:::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, как описано выше. Скорее, они являются частью дерева исходных текстов R в отдельных каталогах пакетов /src/library/. Как получить доступ к источнику R, описано в следующем разделе.

Скомпилированный код, встроенный в интерпретатор R

Если вы хотите просмотреть код, встроенный в интерпретатор R, вам необходимо скачать / распаковать исходные коды R; или вы можете просматривать источники онлайн через репозиторий R Subversion или зеркало github Уинстона Чанга .

Новостная статья Уве Лиггеса R (PDF) (стр. 43) является хорошим общим справочным материалом о том, как просматривать исходный код .Internalи .Primitiveфункции. Основные шаги - сначала поиск имени функции в, src/main/names.cа затем поиск имени «C-entry» в файлах в src/main/*.

Joshua Ulrich
источник
71
Если вы используете RStudio, он попытается получить источник для функции, над которой находится текстовый курсор, если вы нажмете F2клавишу.
Ари Б. Фридман
1
@ Ари Б. Фридман Извините за этот поздний вопрос. Будет ли RStudio также получать исходный код C для функции или только для функций, написанных на R? Спасибо
Солнечный
3
@ Самир, я считаю, что это просто источник R
Ари Б. Фридман
@ AriB.Friedman - спасибо, Ари, это удобно. В моем случае мне все еще нужны были знания, показанные в ответе (то scaleесть S3 - я получил, UseMethod("scale")а затем использовал getAnywhere(scale.default)). Но простые функции работают просто отлично.
Томаш Гандор
2
Подражание - самая искренняя форма лести, я предполагаю, что этот ответ / вики появился первым :) До этого rfaqs.com/source-code-of-r-method
JimLohse
94

В дополнение к другим ответам на этот вопрос и его дубликатам, есть хороший способ получить исходный код для функции пакета без необходимости знать, в каком пакете он находится. Например, если нам нужен источник для randomForest::rfcv() :

Чтобы просмотреть / отредактировать его во всплывающем окне:

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

Чтобы перенаправить в отдельный файл :

capture.output(getAnywhere('rfcv'), file='source_rfcv.r')
SMCI
источник
По общему признанию, getAnywhere - еще один дурацкий R-выбор имени для чего-то, что должно было называться findOnSearchPath или подобным.
smci
1
Я поддержу этот ответ, потому что он приблизил меня к тому, что я хотел. То, что я действительно хотел в RStudio, было View(foo); где fooбыла функция из уже загруженного пакета.
Зигфрид
1
@Sigfried: edit()открывает текстовый редактор (по выбору пользователя) , в то время как View()открывает зритель электронной таблицы Excel-типа для данных , последний хорошо подходит для просмотра данных (мульти-столбчатых), но , как правило , ужасно для кода ничего, кроме длины игрушек. Например, как я намекаю, обычно первое, что я хочу сделать при просмотре функции, - это пропустить / свернуть / перебрать всю логику разбора аргументов и действия по умолчанию, чтобы увидеть, что на самом деле делает функция .
smci
25

Это раскрывается при отладке с использованием функции 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 ()

Selva
источник
Недостатки этого метода по сравнению с теми, которые приведены в принятом ответе, это то, что вам нужен рабочий вызов функции (все необходимые параметры указаны, приемлемо); и что в дополнение к начальному блоку кода вы также получаете каждый блок во время его выполнения. Это отлично подходит для отладки, но не оптимально для получения исходного кода.
Брайан Диггс
Да, это не оптимально. Но если вы умны, вы можете получить источник быстро и грязно, особенно для встроенных функций.
Сельва
2
Я также рекомендовал бы использовать debugonceвместо этого debugв этом случае.
Джошуа Ульрих
20

Для не примитивных функций 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"]].

Джеффри Пул
источник
18

Не видел, как это вписывается в поток основного ответа, но это меня озадачило, поэтому я добавляю его сюда:

Инфиксные операторы

Чтобы увидеть исходный код некоторых операторов базовых инфиксных (например, %%, %*%, %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("%%")

Основной ответ охватывает, как затем использовать зеркала, чтобы копать глубже.

MichaelChirico
источник
6
Ответ smci рекомендуется getAnywhere. Или вы могли бы просто использовать кавычку , если вы уже знаете имя оператора: `%in%`.
Джошуа Ульрих
3
@JoshuaUlrich не знал, что ты можешь использовать обратные трюки! Спасибо. getAnywhereупоминается и в вашем ответе, но я думаю, что конкретная ссылка на infix полезна для будущей ссылки на этот ответ - я много раз читал эту страницу и все еще был немного озадачен, пытаясь найти код для таких функций для в то время как - и я не думаю, что это вписывается в поток любого другого ответа (которые оба используют getAnywhereдля другой цели).
MichaelChirico
10

В R есть очень удобная функция edit

new_optim <- edit(optim)

Он откроет исходный код optimиспользования редактора, указанного в R options, а затем вы сможете отредактировать его и назначить измененную функцию new_optim. Мне очень нравится эта функция, чтобы просматривать код или отлаживать код, например, печатать некоторые сообщения или переменные или даже назначать их глобальным переменным для дальнейшего исследования (конечно, вы можете использоватьdebug ).

Если вы просто хотите просмотреть исходный код и не хотите, чтобы раздражающий длинный исходный код печатался на вашей консоли, вы можете использовать

invisible(edit(optim))

Ясно, что это нельзя использовать для просмотра исходного кода на C / C ++ или Fortran.

Кстати, editможет открывать другие объекты, такие как список, матрица и т. Д., Которые затем отображают структуру данных с атрибутами. Функцию deможно использовать для открытия редактора, похожего на Excel (если GUI поддерживает его), для изменения матрицы или фрейма данных и возврата нового. Иногда это удобно, но в обычном случае этого следует избегать, особенно если у вас большая матрица.

Эрик
источник
3
Этот подход вызывает только тот же источник функции, который дает печать функции (то есть то же, что и в вопросе). Вопрос в том, о чем идет дальше / глубже.
Брайан Диггс
2
@BrianDiggs Да, вы правы. Я не хотел давать ответ на вопрос, поскольку Иисус Навин дал довольно полный ответ. Я просто пытаюсь добавить что-то связанное с темой, интересное и может быть полезно знать.
Эрик
8

Пока функция написана на чистом R, а не на C / C ++ / Fortran, можно использовать следующее. В противном случае лучший способ - это отладка и использование « перехода в »:

> functionBody(functionName)
MCH
источник
2
Это так же, как body. identical(functionBody, body)есть TRUE.
Джошуа Ульрих
1
base::bodyи methods::functionBody, хотя они не любят отделяться. bodyтакже может быть переопределено: rdocumentation.org/search?q=body
Moody_Mudskipper
7

В RStudio есть (как минимум) 3 способа:

  1. Нажмите клавишу F2, когда курсор находится на любой функции.
  2. Нажмите на название функции, удерживая Ctrl или Command
  3. View(имя_функции) (как указано выше)

Откроется новая панель с исходным кодом. Если вы достигнете .Primitive или .C, вам понадобится другой метод, извините.

Артур Йип
источник
5

View([function_name])- например View(mean)Обязательно используйте прописные буквы [V]. Код только для чтения откроется в редакторе.

Koo
источник
5

Вы также можете попытаться использовать print.function(), что является универсальным S3, чтобы получить функцию записи в консоли.

strboul
источник
3
print.function()это метод S3 . Универсальный есть print(). И вообще не стоит вызывать методы напрямую. Это наносит ущерб всей цели универсальных функций и методов отправки.
Джошуа Ульрих