diff --git a/NEWS.md b/NEWS.md index 751442932..b642f2cb2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,20 +2,22 @@ - Interrupting Python no longer leads to segfaults (#1601, fixed in #1602). +- Print method for Python callables now includes the callable’s signature (#1605) + - `virtualenv_starter()` no longer warns when encountering broken symlinks (#1598). # reticulate 1.36.1 -- Fix issue where `py_to_r()` method for Pandas DataFrames would error - if `py_to_r()` S3 methods were defined for Pandas subtypes, +- Fix issue where `py_to_r()` method for Pandas DataFrames would error + if `py_to_r()` S3 methods were defined for Pandas subtypes, (as done by {anndata}) (#1591). - + - "Python Dependencies" vignette edits (@salim-b, #1586) -- Added an option for extra command-line arguments in +- Added an option for extra command-line arguments in `conda_create()` and `conda_install()` (#1585). -- Fixed issue where `conda_install()` would ignore user-specified +- Fixed issue where `conda_install()` would ignore user-specified channels during Python installation (#1594). # reticulate 1.36.0 diff --git a/R/python.R b/R/python.R index 1839a9709..1f9b3ccc5 100644 --- a/R/python.R +++ b/R/python.R @@ -1,11 +1,72 @@ #' @export print.python.builtin.object <- function(x, ...) { - writeLines(py_repr(x)) + formatted <- if (py_is_callable(x)) + py_format_callable(x, ...) + else + py_repr(x) + + writeLines(formatted) invisible(x) } +py_format_callable <- function(x, ...) { + + type <- py_to_r(py_get_attr(py_get_attr(x, "__class__"), "__name__")) + + name <- py_to_r(py_get_attr(x, "__qualname__", TRUE) %||% + py_get_attr(x, "__name__", TRUE)) + + module <- py_to_r(py_get_attr(x, "__module__", TRUE)) + if (!is.null(module)) + name <- paste0(c(module, name), collapse = ".") + + inspect <- import("inspect") + + get_formatted_signature <- function(x, drop_first = FALSE) { + tryCatch({ + sig <- inspect$signature(x) + if (drop_first) { + # i.e., drop first positional arg, most typically: 'self' + # + # We only need to do this if inspect.signature() errored on the the + # callable itself, but succeeded on callable.__init__. This can happen + # for some built-in-C class where methods are slot wrappers. + # E.g., builtin exceptions like 'RuntimeError'. + sig <- inspect$Signature( + parameters = iterate(sig$parameters$values())[-1], + return_annotation = sig$return_annotation + ) + } + + formatted <- py_str_impl(sig) + + # split long signatures across multiple lines, so they're readable + if (py_len(sig$parameters) >= 4L) { + for (formatted_arg in iterate(sig$parameters$values(), py_str_impl)) + formatted <- sub(formatted_arg, + paste0("\n ", formatted_arg), + formatted, fixed = TRUE) + + formatted <- sub(", /,", ",\n /,", formatted, fixed = TRUE) # positional only separator + formatted <- sub(", *,", ",\n *,", formatted, fixed = TRUE) # kw-only separator + formatted <- sub("\\)($| ->)", "\n)\\1", formatted) # final closing parens ) + } + formatted + }, + error = function(e) NULL) + } + + formatted_sig <- get_formatted_signature(x) %||% + get_formatted_signature(py_get_attr(x, "__init__", TRUE), TRUE) %||% + get_formatted_signature(py_get_attr(x, "__new__", TRUE), TRUE) %||% + "(?)" + + sprintf("<%s %s%s>", type, name, formatted_sig) +} + + #' @importFrom utils str #' @export str.python.builtin.object <- function(object, ...) { @@ -1761,4 +1822,3 @@ format_py_exception_traceback_with_clickable_filepaths <- function(etb) { cli::col_silver(hint) } -