Skip to content
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

Truthiness of Julia callables in Python #323

Closed
dingraha opened this issue Jun 5, 2023 · 2 comments · Fixed by #327
Closed

Truthiness of Julia callables in Python #323

dingraha opened this issue Jun 5, 2023 · 2 comments · Fixed by #327
Labels
enhancement New feature or request

Comments

@dingraha
Copy link
Contributor

dingraha commented Jun 5, 2023

Is your feature request related to a problem? Please describe.
In my application I'd like to pass a Julia callable to a Python library. The Python library checks if the callable is passed (it's an optional argument) with something like:

if f:
    # f is here, use it
else:
    # do something else

When I try to pass a Julia callable I get an error about __len__ not being implemented. I think this is because, in Python, the truthiness of a class is controlled by the __bool__ method, if present, and the __len__ method if not, and then finally is always True if the class implements neither. In PythonCall, a Julia function is treated as an AnyValue in Python, which doesn't implement __bool__ but does implement __len__. But the AnyValue __len__ eventually calls the Julia length function, which isn't implemented for Julia functions and triggers an error.

I believe in Python a plain function is always considered True.

Here is an example of a Julia REPL session that shows the error:

julia> is_it_truthy = PythonCall.@pyeval `lambda f: "yes!" if f else "no!"`
Python: <function <lambda> at 0x7fb2c9799ee0>

julia> is_it_truthy(8)
Python: 'yes!'

julia> is_it_truthy(0)
Python: 'no!'

julia> is_it_truthy("foo")
Python: 'yes!'

julia> is_it_truthy("bar")
Python: 'yes!'

julia> is_it_truthy("")
Python: 'no!'

julia> myfunc(x) = x+1
myfunc (generic function with 1 method)

julia> myfunc(8)
9

julia> is_it_truthy(myfunc)
ERROR: Python: TypeError: Julia: MethodError: no method matching length(::typeof(myfunc))
Closest candidates are:
  length(!Matched::Union{Base.KeySet, Base.ValueIterator}) at abstractdict.jl:58
  length(!Matched::Union{LinearAlgebra.Adjoint{T, S}, LinearAlgebra.Transpose{T, S}} where {T, S}) at ~/local/julia/1.8.5/share/julia/st
dlib/v1.8/LinearAlgebra/src/adjtrans.jl:172
  length(!Matched::Union{Tables.AbstractColumns, Tables.AbstractRow}) at ~/.julia/packages/Tables/AcRIE/src/Tables.jl:180
  ...
Python stacktrace:
 [1] __len__
   @ ~/projects/pythoncall_openmdao/dev/PythonCall.jl/src/jlwrap/any.jl:210
 [2] <lambda>
   @ REPL[14]:1:1
Stacktrace:
 [1] pythrow()
   @ PythonCall ~/projects/pythoncall_openmdao/dev/PythonCall.jl/src/err.jl:94
 [2] errcheck
   @ ~/projects/pythoncall_openmdao/dev/PythonCall.jl/src/err.jl:10 [inlined]
 [3] pycallargs(f::PythonCall.Py, args::PythonCall.Py)
   @ PythonCall ~/projects/pythoncall_openmdao/dev/PythonCall.jl/src/abstract/object.jl:210
 [4] pycall(f::PythonCall.Py, args::Function; kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
   @ PythonCall ~/projects/pythoncall_openmdao/dev/PythonCall.jl/src/abstract/object.jl:228
 [5] pycall
   @ ~/projects/pythoncall_openmdao/dev/PythonCall.jl/src/abstract/object.jl:218 [inlined]
 [6] #_#11
   @ ~/projects/pythoncall_openmdao/dev/PythonCall.jl/src/Py.jl:341 [inlined]
 [7] (::PythonCall.Py)(args::Function)
   @ PythonCall ~/projects/pythoncall_openmdao/dev/PythonCall.jl/src/Py.jl:341
 [8] top-level scope
   @ REPL[22]:1

shell> 

Describe the solution you'd like
It would be cool if a Julia function/callable could be considered True in Python, similar to Python functions.

Describe alternatives you've considered
I have a workaround where I wrap the Julia function I'm passing to Python in a Julia struct, then implement the Julia length function for that as something like Base.length(::MyStruct) = 1. That works fine, but it would be great if I didn't have to do it. :-)

@dingraha dingraha added the enhancement New feature or request label Jun 5, 2023
@cjdoris
Copy link
Collaborator

cjdoris commented Jun 15, 2023

Ok thanks, should be easy to fix.

@dingraha
Copy link
Contributor Author

This fixes my issue. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants