PythonCall array indexing

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)