Replies: 5 comments 9 replies
-
There's nothing to easily achieve this right now. You can do You could write your own |
Beta Was this translation helpful? Give feedback.
-
I started working on this. Would be nice, if someone with deeper knowledge could comment. using PythonCall
const PYTYPES = setdiff(getindex.(getfield.(getfield.(methods(ispy), :sig), :types), 2), [Any])
function pyconvert_native(x)
ispy(x) && PythonCall.pyisinstance(x, pybuiltins.dict) && return pyconvert_dict(x)
types = setdiff(getfield.(PythonCall._pyconvert_get_rules(pytype(x)), :type), PYTYPES)
for T in types
ans = PythonCall.pytryconvert(T, x)
PythonCall.pyconvert_isunconverted(ans) || return PythonCall.pyconvert_result(T, ans)
end
PythonCall.pyconvert_unconverted()
end
function pyconvert_dict(::Type{T}, d) where T
jd = pyconvert(T, d)
kk = pyconvert_native.(keys(jd))
vv = [ispy(v) ? PythonCall.pyisinstance(v, pybuiltins.dict) ? pyconvert_dict(T, v) : pyconvert_native(v) : v for v in values(jd)]
T(zip(kk, vv))
end
pyconvert_dict(d) = pyconvert_dict(Dict, d) which allows us to write the following code snippet julia> pl = pylist([1, 2, 3]);
julia> ps = pystr("H");
julia> pd = @py dict({"a":"bb", "c": dict({"e": "f"})});
julia> pyconvert_native(pl)
3-element Vector{Int64}:
1
2
3
julia> pyconvert_native(ps)
"H"
julia> pyconvert_native(pd)
Dict{String, Any} with 2 entries:
"c" => Dict("e"=>"f")
"a" => "bb"
julia> pyconvert_dict(d)
Dict{String, Any} with 2 entries:
"c" => Dict("e"=>"f")
"a" => "bb"
julia> pyconvert_dict(Dict{String, Any}, d)
Dict{String, Any} with 2 entries:
"c" => Dict{String, Any}("e"=>"f")
"a" => "bb"
julia> pyconvert_dict(Dict{Any, Any}, d)
Dict{Any, Any} with 2 entries:
"c" => Dict{Any, Any}("e"=>"f")
"a" => "bb" |
Beta Was this translation helpful? Give feedback.
-
We can also switch to a similar issue #172 for further discussion. |
Beta Was this translation helpful? Give feedback.
-
I implemented caching, so my current code looks like this: using PythonCall
const PYTYPES = push!(setdiff(getindex.(getfield.(getfield.(methods(ispy), :sig), :types), 2), [Any]), PyArray)
const PYCONVERT_NATIVE_TYPES_CACHE = Dict{PythonCall.C.PyPtr, Vector{Type}}()
function pyconvert_get_native_ruletypes(x::Py)
tptr = PythonCall.C.Py_Type(PythonCall.getptr(x))
get!(PYCONVERT_NATIVE_TYPES_CACHE , tptr) do
pytype = PythonCall.pynew(PythonCall.incref(tptr))
ans = setdiff(getfield.(PythonCall._pyconvert_get_rules(pytype), :type), PYTYPES)
PythonCall.pydel!(pytype)
ans
end
end
function pyconvert_native(x)
ispy(x) || return x
PythonCall.pyisinstance(x, pybuiltins.dict) && return pyconvert_dict(x)
types = pyconvert_get_native_ruletypes(x)
for T in types
ans = PythonCall.pytryconvert(T, x)
PythonCall.pyconvert_isunconverted(ans) || return PythonCall.pyconvert_result(T, ans)
end
PythonCall.pyconvert_unconverted()
end
function pyconvert_dict(::Type{T}, d) where T
jd = pyconvert(T, d)
kk = pyconvert_native.(keys(jd))
vv = [ispy(v) ? PythonCall.pyisinstance(v, pybuiltins.dict) ? pyconvert_dict(T, v) : pyconvert_native(v) : v for v in values(jd)]
T(zip(kk, vv))
end
pyconvert_dict(d) = pyconvert_dict(Dict, d) If this approach looks ok, I would try to cover arrays in a similar manner to dicts. |
Beta Was this translation helpful? Give feedback.
-
With this in place, the following two lines add support for (automatic) DataFrame conversion: # add DataFrame conversion rule
using DataFrames
PythonCall.pyconvert_rule_pandasdataframe(::Type{DataFrame}, x::Py) = DataFrame(PyTable(x))
PythonCall.pyconvert_add_rule("pandas.core.frame:DataFrame", DataFrame, PythonCall.pyconvert_rule_pandasdataframe, PythonCall.PYCONVERT_PRIORITY_NORMAL) so julia> pd_df = pd.DataFrame([[1, 2, 3], [4, 5, 6]], columns = pylist("ABC"))
Python DataFrame:
A B C
0 1 2 3
1 4 5 6
julia> pyconvert_native(pd_df)
2×3 DataFrame
Row │ A B C
│ Int64 Int64 Int64
─────┼─────────────────────
1 │ 1 2 3
2 │ 4 5 6 |
Beta Was this translation helpful? Give feedback.
-
I really appreciate how PythonCall.jl avoids automatic conversions, but I was wondering if there might also be a way to enable this conditionally?
The case I have been running into is that a colleague sends me some pickle or npz file with a bunch of nested, heterogeneous Python objects which I would then like to feed into my Julia pipeline. I've been sprinkling
pyconvert
s orPy
objects in multiple places before passing the inputs to various Julia functions to get the expected behavior, but wasn't sure if I was approaching this the right way.I know it would be a hit in performance in the front-end, but is there a way to accomplish this for all python objects that I load (perhaps with auto conversion rules that we specify), or maybe some workflow more in line with this package's philosophy that I am overlooking?
As a basic example, if I have the following pickle:
is there a way to load it in Julia as the following native object?
instead of wrapping with:
So sort of like the PythonCall.jl inverse of
py"<>"o
for returning rawPyObjects
in PyCall.jl?Beta Was this translation helpful? Give feedback.
All reactions