Description
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. :-)