-
Notifications
You must be signed in to change notification settings - Fork 326
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add py_func()
to wrap R function in a Python function with correct signature
#195
Conversation
Awesome! Let me take a closer look at this and see if there is a lower level way to do this that doesn't rely on the string interpolation (e.g. some of the approaches described here: https://stackoverflow.com/questions/11291242/python-dynamically-create-function-at-runtime) |
After doing some further research, I think that you have taken the only feasible approach here. I think it would be okay to rename |
Thanks for taking the time to do that! I've done some experiments using other approaches but no luck (either not fully working or hard to customize). I agree that string interpolation is not ideal but I think it should support most of the use cases already and easy to customize. There's definitely a lot of space for improvements and additional test cases. |
Is there some well-defined set of functions that we can / should be able to correctly wrap? I think the following examples might fail in the current implementation: function(a = 1, b) {}
function(x = NA) {}
function(a.b) {}
function(a = "abc") {}
function(a = list()) {}
function(a = NULL) {} Do any of these need to be handled? |
R/wrap_fn.R
Outdated
signature <- "" | ||
sig_names <- names(sigs) | ||
for(k in sig_names) { | ||
if (sigs[[k]] == "") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To check if an argument is missing, you want to use the (rather esoteric) construct:
identical(sigs[[k]], quote(expr =))
R/wrap_fn.R
Outdated
signature <- paste0(signature, k) | ||
else { | ||
# arg with default | ||
signature <- paste0(signature, k, "=", as.character(r_to_py(sigs[[k]]))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For things that aren't just literals, R will preserve the call object used in the formals, e.g.
> f <- function(a = c(1, 2, 3)) {}
> class(formals(f)$a)
[1] "call"
I'm guessing that R's language objects may not marshal as well in these cases. Do we need to explicitly evaluate these defaults before attempting to convert them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'll fix this
R/wrap_fn.R
Outdated
} | ||
# if this is not the last arg, append a comma | ||
if (k != sig_names[length(sig_names)]) | ||
signature <- paste0(signature, ", ") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, rather than growing the signature over time, you could string-ify each formal independently and then paste when you're done, e.g.
strings <- lapply(sigs, function(x) { <convert to Python string equivalent> }
paste(strings, collapse = ", ")
@kevinushey Thanks. I'll try fix those cases.
|
That just reflects the fact that R argument names can contain a |
@kevinushey Yea I would expect users to use more normal argument names. Should we |
Something like |
@kevinushey I think you meant Perhaps we should just point out in the docs that the function signature has to be in Python style that meets Python's requirements? |
@kevinushey Or wrap the whole thing in a |
This can definitely tolerate some imperfections and failure modes because it is going to be on opt-in (by default you will get the equivalent of |
Oh, I see -- you meant a regex for capturing the bad case? You could just use e.g. |
Given the limited scope I think the main thing to fix for the PR is the handling of some more common R default arguments ( |
Remember also that callback functions being type-checked by Python
libraries aren't that likely to have default arguments (since these are
library not user level interfaces)
…On Thu, Mar 29, 2018 at 2:27 PM, Kevin Ushey ***@***.***> wrote:
Given the limited scope I think the main thing to fix for the PR is the
handling of some more common R default arguments (NULL, character
strings, atomic vectors, maybe lists?)
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#195 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAGXxzFFvLGpvZ50y6RzuGsAbFtWmm2Iks5tjVHJgaJpZM4S7aWk>
.
|
@jjallaire That's a good point actually. I just pushed some fixes for those additional test cases. Any suggested better name for this function? |
@kevinushey I was trying to apply your suggested R syntactical enhancements but for some reason I was hitting issues on those so I'll probably keep this as it is for now. I think the rest of your comments should have been addressed already except that we need a better name for the function + export it. |
I think it should be name |
@jjallaire Done renaming. Let me know if there's anything else I missed. |
py_func()
to wrap R function in a Python function with correct signature
R/py_func.R
Outdated
return fn | ||
", func_signature, func_pass_args)) | ||
wrap_fn_util$wrap_fn(f) | ||
}, error = function(e) r_to_py(f)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the tryCatch
with fallback to the default behavior is really helping us here. The user thinks they got a complete Python signature but instead they end up getting the default ...
style signature. Why is this better than an error? (I'm presuming that the only reason the user is calling py_func
is because they require the correct signature for use with a library that checks callbacks -- silently giving them an unexpected return value seems bad).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. I’ll change this
Let's add a |
This is to address #141, basically a generalized version of
as_model_fn
in tfestimators.Note that this function is experimental and not exported since currently only simple function is supported (e.g. no
...
or other complicated cases).