Hello! In trying to use PythonCall in place of PyCall I find a strange behaviour when indexing a matrix in Python using only one index. As an example, I have the python file pythoncall_test.py
:
def test(q):
return q[0]
If I send a vector to this function, I expect the first element. If I send a matrix, I expect the first row. However:
julia> using PythonCall
julia> pyimport("pythoncall_test").test(zeros(3))
Python float: 0.0
julia> pyimport("pythoncall_test").test([1 2 3; 4 5 6; 7 8 9])
ERROR: Python: Julia: an error occurred while setting an error
Python stacktrace:
[1] __getitem__
@ ~/.julia/packages/PythonCall/dsECZ/src/jlwrap/array.jl:308
[2] test
@ pythoncall_test ~/git/vibdamp-22/pythoncall_test.py:2
Stacktrace:
[1] pythrow()
@ PythonCall ~/.julia/packages/PythonCall/dsECZ/src/err.jl:94
[2] errcheck
@ ~/.julia/packages/PythonCall/dsECZ/src/err.jl:10 [inlined]
[3] pycallargs(f::Py, args::Py)
@ PythonCall ~/.julia/packages/PythonCall/dsECZ/src/abstract/object.jl:210
[4] pycall(f::Py, args::Matrix{Int64}; kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
@ PythonCall ~/.julia/packages/PythonCall/dsECZ/src/abstract/object.jl:228
[5] pycall
@ ~/.julia/packages/PythonCall/dsECZ/src/abstract/object.jl:218 [inlined]
[6] #_#11
@ ~/.julia/packages/PythonCall/dsECZ/src/Py.jl:352 [inlined]
[7] (::Py)(args::Matrix{Int64})
@ PythonCall ~/.julia/packages/PythonCall/dsECZ/src/Py.jl:352
[8] top-level scope
@ REPL[4]:1
Running the same code with PyCall instead provides the expected result:
julia> using PyCall
julia> pyimport("pythoncall_test").test(zeros(3))
0.0
julia> pyimport("pythoncall_test").test([1 2 3; 4 5 6; 7 8 9])
3-element Vector{Int64}:
1
2
3
Why does this happen? Is this a known limitation of PythonCall or is there a fix?
I’m not sure if you figured this out, but as I understand PythonCall does not do automatic conversions as readily as PyCall.
See Julia to Python · PythonCall & JuliaCall for the conversion rules, where Matrix
does not have an explicit conversion. See also Coming from PyCall/PyJulia? · PythonCall & JuliaCall
To do what you seemed to expect coming from PyCall, you can use the pyrowlist
function to do the type conversion to Python:
julia> ct = pyimport("call_test")
Python module: <module 'call_test' from ....>
julia> M
3×3 Matrix{Int64}:
1 2 3
4 5 6
7 8 9
julia> ct.test(pyrowlist(M))
Python list: [1, 2, 3]
julia> ct.my_test(pyrowlist(M)) |> collect
3-element Vector{Py}:
1
2
3
This solves the issue, thanks!
However I now run into another issue which again PyCall doesn’t have a problem with. I want to find the rows in a matrix for which at least one element has a value above a given threshold. PythonCall
doesn’t seem to like mask indexing. “pythoncall_test.py”:
import numpy as np
def test(q, thres):
q_ok = np.any(np.abs(q) > thres, axis=1)
return q[q_ok]
In Julia:
julia> using PythonCall
julia> pyimport("pythoncall_test").test(pyrowlist([1 2 3; 4 5 6; 7 8 9]), 5)
ERROR: Python: TypeError: only integer scalar arrays can be converted to a scalar index
Python stacktrace:
[1] test
@ pythoncall_test ~/git/vibdamp-22/pythoncall_test.py:5
Stacktrace:
[1] pythrow()
@ PythonCall ~/.julia/packages/PythonCall/dsECZ/src/err.jl:94
[2] errcheck
@ ~/.julia/packages/PythonCall/dsECZ/src/err.jl:10 [inlined]
[3] pycallargs(f::Py, args::Py)
@ PythonCall ~/.julia/packages/PythonCall/dsECZ/src/abstract/object.jl:210
[4] pycall(::Py, ::Py, ::Vararg{Any}; kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
@ PythonCall ~/.julia/packages/PythonCall/dsECZ/src/abstract/object.jl:228
[5] pycall(::Py, ::Py, ::Vararg{Any})
@ PythonCall ~/.julia/packages/PythonCall/dsECZ/src/abstract/object.jl:218
[6] (::Py)(::Py, ::Vararg{Any}; kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
@ PythonCall ~/.julia/packages/PythonCall/dsECZ/src/Py.jl:352
[7] (::Py)(::Py, ::Vararg{Any})
@ PythonCall ~/.julia/packages/PythonCall/dsECZ/src/Py.jl:352
[8] top-level scope
@ REPL[3]:1
Again, PyCall
doesn’t struggle:
julia> using PyCall
julia> pyimport("pythoncall_test").test([1 2 3; 4 5 6; 7 8 9], 5)
2×3 Matrix{Int64}:
4 5 6
7 8 9
Any ideas here?
See FAQ & Troubleshooting · PythonCall & JuliaCall
julia> using PythonCall
julia> M = [1 2 3; 4 5 6; 7 8 9]
julia> pyimport("pythoncall_test").test(Py(M).to_numpy(), 5)