Pythonでは以下のように関数内にドキュメントを記述できます。
def add_one(x: float):
"""Add one to x.
Parameter
---------
x: float
Return
------
int
x + 1
"""
return x + 1
これを記述しておくとJupyter Notebookなんかでは、?add_one
とするだけでドキュメントが見られて便利です。
RでもRmdファイル内で同じようなことがしたいなーという方、できますよ。
関数のbody先頭にある文字列を取り出す
まずは.add_one
関数として、先頭にroxygen2風のドキュメントを記述した関数を用意してみましょう。自由ドキュメント内で引用符や括弧などをエスケープせずに使えるように、
r"---(文字列)---"
の記法を使ってます。これについてはhelp('"')
を参照してください。
.add_one <- function(x) {
r"---(Add one.
@param x: A numeric vector.
@value
x + 1
)---"
x + 1
}
この関数の処理内容を取り出すには、body関数を使います。
body(.add_one)
## {
## "Add one.\n \n @param x: A numeric vector.\n \n @value\n x + 1\n "
## x + 1
## }
str
関数で構造を確認するとlanguage
オブジェクトです。特殊。
str(body(.add_one))
## language { "Add one.\n \n @param x: A numeric vector.\n \n @value\n x + 1\n "; x + 1 }
## - attr(*, "srcref")=List of 3
## ..$ : 'srcref' int [1:8] 1 25 1 25 25 25 1 1
## .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x55d19c96eec8>
## ..$ : 'srcref' int [1:8] 2 3 8 7 3 7 2 8
## .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x55d19c96eec8>
## ..$ : 'srcref' int [1:8] 9 3 9 7 3 7 9 9
## .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x55d19c96eec8>
## - attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x55d19c96eec8>
## - attr(*, "wholeSrcref")= 'srcref' int [1:8] 1 0 10 1 0 1 1 10
## ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x55d19c96eec8>
でもリスト化してあげると、ASTっぽい感じに処理が保存されてる感じだと分かります。
as.list(body(.add_one))
## [[1]]
## `{`
##
## [[2]]
## [1] "Add one.\n \n @param x: A numeric vector.\n \n @value\n x + 1\n "
##
## [[3]]
## x + 1
x + 1
の部分をリスト化するとより分かりやすいですね。
as.list(body(.add_one)[[3L]])
## [[1]]
## `+`
##
## [[2]]
## x
##
## [[3]]
## [1] 1
というわけで、別にas.list
しなくても2番目の要素を取り出せば、先頭の処理を抽出できます
body(.add_one)[[2L]]
## [1] "Add one.\n \n @param x: A numeric vector.\n \n @value\n x + 1\n "
あとはcat
してやると良い感じに表示できますね。
cat(body(.add_one)[[2L]])
## Add one.
##
## @param x: A numeric vector.
##
## @value
## x + 1
##
docstringを扱いやすくする
.add_one
関数のままでは、ドキュメントの抽出にcat(body(.add_one)[[2L]])
とせねばならず、直感的ではありません。
ちょっと改造したadd_one
関数を定義して、help
関数で良い感じに表示できるようにしてみましょう。
関数から直感的にdocstringを取り出せるようにする
まずはwith_docstring
関数を用意し、関数の処理の先頭が文字列なら、該当部分を関数自身の__doc__
属性に保存するwith_docstring
関数を定義してみましょう。ついでに、後々のことを考えてこの文字列にはdocstring
クラスを与え、関数にはwith_docstring
クラスを与えておきます。
これでattr(add_one, "__doc__")
すればヘルプを取り出せるようになり、取り出し方が直感的になります。
with_docstring <- function(f) {
doc <- body(f)[2L][[1L]]
attr(f, "__doc__") <- structure(
`if`(is.character(doc), doc, ""),
class = "docstring"
)
class(f) <- c("with_docstring", class(f))
return(f)
}
add_one <- with_docstring(.add_one)
attr(add_one, "__doc__")
## [1] "Add one.\n \n @param x: A numeric vector.\n \n @value\n x + 1\n "
## attr(,"class")
## [1] "docstring"
.add_one
関数を定義してからadd_one
関数にするのが面倒であれば、最初から関数定義をwith_docstring
関数でラップしましょう。
Pythonで言うところのデコレータ的発想ですね。
add_one <- with_docstring(function(x) {
r"---(Add one.
@param x: A numeric vector.
@value
x + 1
)---"
x + 1
})
docstringを良い感じにprintする
docstringの抽出が簡単になりましたが、表示がイマイチです。
attr(add_one, "__doc__")
## [1] "Add one.\n \n @param x: A numeric vector.\n \n @value\n x + 1\n "
## attr(,"class")
## [1] "docstring"
というわけでdocstring
クラス用print
メソッドを実装してあげましょう。単にcat
するだけでOKです。
print.docstring <- function(x, ...) {
cat(x)
invisible(x)
}
attr(add_one, "__doc__")
## Add one.
##
## @param x: A numeric vector.
##
## @value
## x + 1
##
良い感じになりましたね。
help関数をマスクする
あとはhelp
関数を実装するだけです。色んな方法が考えられますが、一番シンプルな方法はhelp
関数を総称関数にする手です。そして、help.with_docstring
メソッドでwith_docstring
クラスオブジェクトだけの専用help
を用意します。ついでにhelp.default
を用意すれば、その他のオブジェクトに対しては従来のutils::help
関数を呼べます。
以下ではhelp
とhelp.with_docstring
関数にたいして引数を定義せず、後からutils::help
関数の引数をformals
関数を使ってパクってます。このあたりについてはJapan.R 2018で発表した「関数魔改造講座 (formals編)」を参照してください。
help <- function() {
UseMethod("help")
}
help.default <- utils::help
help.with_docstring <- function() {
print(attr(topic, "__doc__", exact = TRUE))
}
formals(help) <- formals(help.with_docstring) <- formals(utils::help)
help(add_one)
## Add one.
##
## @param x: A numeric vector.
##
## @value
## x + 1
##
printr
パッケージを使えばR Markdown内でヘルプを使えるので、docstringがない関数についてもちゃんとヘルプを呼べます。
library(printr)
## Registered S3 method overwritten by 'printr':
## method from
## knit_print.data.frame rmarkdown
help(identity)
identity | R Documentation |
Identity Function
Description
A trivial identity function returning its argument.
Usage
identity(x)
Arguments
x | an R object. |
See Also
diag
creates diagonal matrices, including identity ones.
Enjoy more?
__doc__
属性に加える時に文字列をうまく編集すれば、色んなことができます。
- roxygen2として処理してちゃんとしたヘルプっぽくする
- マークダウンなどとして処理して太字とかを表現する
など。
楽しいですねー。