PyCall.jl icon indicating copy to clipboard operation
PyCall.jl copied to clipboard

not a Python function

Open bjarthur opened this issue 9 years ago • 5 comments

sorry if i'm being dumb about this, but functions with multiple input arguments don't seem to be recognized as functions:

julia> using PyCall

julia> @pyimport scipy.optimize as so

julia> so.curve_fit((x,m,b)->m*x+b, 1:10, 2:2:20)
ERROR: PyError (:PyObject_Call) <type 'exceptions.TypeError'>
TypeError('<PyCall.jlwrap (anonymous function)> is not a Python function',)
  File "/home/arthurb/.julia/v0.4/Conda/deps/usr/lib/python2.7/site-packages/scipy/optimize/minpack.py", line 604, in curve_fit
    args, varargs, varkw, defaults = _getargspec(f)
  File "/home/arthurb/.julia/v0.4/Conda/deps/usr/lib/python2.7/site-packages/scipy/_lib/_util.py", line 289, in getargspec_no_self
    argspec = inspect.getargspec(func)
  File "/home/arthurb/.julia/v0.4/Conda/deps/usr/lib/python2.7/inspect.py", line 816, in getargspec
    raise TypeError('{!r} is not a Python function'.format(func))

 [inlined code] from /home/arthurb/.julia/v0.4/PyCall/src/exception.jl:81
 in pycall at /home/arthurb/.julia/v0.4/PyCall/src/PyCall.jl:356
 in call at /home/arthurb/.julia/v0.4/PyCall/src/PyCall.jl:372

julia> so.newton(x -> cos(x) - x, 1)
0.7390851332151607

bjarthur avatar May 12 '16 13:05 bjarthur

You're doing it fine, it's a bug. optimize is trying to count how many arguments the function has, assuming it's a regular Python function, but PyCall is not producing native Python functions. It's not trivial to fix. In the meantime, this works:

using PyCall

funner3 = pyeval("""lambda f: lambda a, b, c: f(a, b, c)""")

@pyimport scipy.optimize as so

so.curve_fit(funner3((x,m,b)->m*x+b), 1:10, 2:2:20)

cstjean avatar May 13 '16 15:05 cstjean

Looking at the inspect source code in Python, it seems like we need a couple of things to make this work:

  • The Julia function-wrapper type should be a subtype of types.FunctionType
  • As discussed on julia-users, have the PyCall function wrapper add a func_code (or __code__ in Py3) attribute that returns an object with at least some of the attributes of the Python func_code, e.g. co_argcount (the number of arguments), co_filename (the file where it is defined), co_firstlineno (the line number), co_name (the name of the function).
  • Implement those attributes by looping over the method list and picking some reasonable definition, e.g. defining co_argcount to be the maximum number of arguments over all methods. (This is a little bit ambiguous because Julia method overloading semantics are so different from Python.) ** In particular, inspect.getargs needs the co_argcount and co_varnames and co_flags, and co_code should be an empty list. ** It also needs func_code to be a subtype of types.CodeType.

All possible in principle, although we may have to throw an error if the method overloading makes the answers too ambiguous, but a nontrivial amount of work to implement.

stevengj avatar May 13 '16 16:05 stevengj

thanks for the workaround. that's all in need for now.

bjarthur avatar May 16 '16 12:05 bjarthur

Hello

Is there any update regarding this? I don't have the know-how to implement what @stevengj. I could try if somebody could point to how to start working on this but I understand that may itself be similar to the amount of work needed to fix it. Just wanted to know if there is an update because I learned that LsqFit.jl is not very reliable, so only reliable easy-to-use curve fitting method is provided by scipy.optimize.curve_fit at the moment.

cocoa1231 avatar Jul 24 '22 18:07 cocoa1231

The workaround is to wrap the Julia function in a Python lambda as noted above by @cstjean.

We actually do this internally in some cases with PyCall.jlfun2pyfun, so you could do the same:

so = pyimport("scipy.optimize")
f = PyCall.jlfun2pyfun((x,m,b)->m*x+b)
so.curve_fit(f, 1:10, 2:2:20)

stevengj avatar Jul 26 '22 12:07 stevengj