Skip to content

Commit a534cf4

Browse files
author
Christopher Doris
committed
tweak conversion to python
1 parent 4ed73f6 commit a534cf4

File tree

3 files changed

+80
-24
lines changed

3 files changed

+80
-24
lines changed

docs/src/conversion-to-julia.md

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,17 @@ From Python, the arguments to a Julia function will be converted according to th
5858

5959
See below for an explanation of the `Py*` types (`PyList`, `PyIO`, etc).
6060

61-
To add new conversion rules, see further below.
61+
### [Custom rules](@id py2jl-conversion-custom)
62+
63+
To add a custom conversion rule, you must define a function to do the conversion and call
64+
`pyconvert_add_rule` to register it.
65+
66+
You must not do this while precompiling, so these calls will normally be in the `__init__`
67+
function of your module.
68+
69+
```@docs
70+
PythonCall.pyconvert_add_rule
71+
```
6272

6373
## [Wrapper types](@id python-wrappers)
6474

@@ -79,14 +89,21 @@ PyObjectArray
7989
PyException
8090
```
8191

82-
## [Adding conversion rules](@id py2jl-conversion-custom)
92+
### [Custom wrappers](@id python-wrappers-custom)
8393

84-
To add a custom conversion rule, you must define a function to do the conversion and call
85-
`pyconvert_add_rule` to register it.
94+
Here is a minimal example of defining a wrapper type. You may add methods, fields and a
95+
supertype to the type to specialise its behaviour. See any of the above wrapper types for
96+
examples.
8697

87-
You must not do this while precompiling, so these calls will normally be in the `__init__`
88-
function of your module.
98+
```julia
99+
# The new type with a field for the Python object being wrapped.
100+
struct MyType
101+
py::Py
102+
end
89103

90-
```@docs
91-
PythonCall.pyconvert_add_rule
104+
# Says that the object is a wrapper.
105+
ispy(x::MyType) = true
106+
107+
# Says how to access the underlying Python object.
108+
getpy(x::MyType) = x.py
92109
```

docs/src/conversion-to-python.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ From Python, this occurs when converting the return value of a Julia function.
3434

3535
See below for an explanation of the `juliacall.*Value` types.
3636

37+
### [Custom rules](@id jl2py-conversion-custom)
38+
39+
You may define a new conversion rule for your new type `T` by overloading `getpy(::T)`.
40+
41+
```@docs
42+
PythonCall.getpy
43+
PythonCall.ispy
44+
```
45+
3746
## [Wrapper types](@id julia-wrappers)
3847

3948
Apart from a few fundamental immutable types, all Julia values are by default converted into Python to some [`AnyValue`](#juliacall.AnyValue) object, which wraps the original value, but giving it a Pythonic interface.

src/Py.jl

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ getptr(x) = getptr(getpy(x)::Py)
2929
Py(x)
3030
3131
Convert `x` to a Python object.
32+
33+
Do not overload this function. To define a new conversion, overload [`getpy`](@ref).
3234
"""
3335
mutable struct Py
3436
ptr :: C.PyPtr
@@ -132,23 +134,51 @@ macro autopy(args...)
132134
end)
133135
end
134136

137+
struct NewPy
138+
py::Py
139+
NewPy(py::Py) = new(py)
140+
end
141+
135142
Py(x::Py) = GC.@preserve x pynew(incref(getptr(x))) # copy, because Py must always return a new object
136-
Py(x::Nothing) = Py(pybuiltins.None)
137-
Py(x::Bool) = pybool(x)
138-
Py(x::Union{String, SubString{String}, Char}) = pystr(x)
139-
Py(x::Base.CodeUnits{UInt8, String}) = pybytes(x)
140-
Py(x::Base.CodeUnits{UInt8, SubString{String}}) = pybytes(x)
141-
Py(x::Tuple) = pytuple_fromiter(x)
142-
Py(x::Pair) = pytuple_fromiter(x)
143-
Py(x::Union{Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UInt64,UInt128,BigInt}) = pyint(x)
144-
Py(x::Rational{<:Union{Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UInt64,UInt128,BigInt}}) = pyfraction(x)
145-
Py(x::Union{Float16,Float32,Float64}) = pyfloat(x)
146-
Py(x::Complex{<:Union{Float16,Float32,Float64}}) = pycomplex(x)
147-
Py(x::AbstractRange{<:Union{Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UInt64,UInt128,BigInt}}) = pyrange_fromrange(x)
148-
Py(x::Date) = pydate(x)
149-
Py(x::Time) = pytime(x)
150-
Py(x::DateTime) = pydatetime(x)
151-
Py(x) = ispy(x) ? Py(getpy(x)) : pyjl(x)
143+
Py(x::NewPy) = x.py
144+
Py(x) = Py(getpy(x)::Union{Py,NewPy})
145+
146+
"""
147+
getpy(x)
148+
149+
Convert `x` to a `Py`.
150+
151+
Overload this function (not `Py`) to define a new conversion to Python.
152+
153+
If `x` is a simple wrapper around a Python object (such as `PyList` or `PyDict`) then
154+
`getpy(x)` should return the Python object. You should also define `ispy(x) = true`. This
155+
means that when `x` is passed back to Python, the underlying object is used directly.
156+
157+
### Optional optimization (for experts)
158+
159+
If `ispy(x)` is false and the returned Julia object `ans` is not referenced anywhere else,
160+
in the sense that `pydel!(ans)` would be safe, you may instead return `NewPy(ans)`.
161+
162+
This can avoid the Julia garbage collector in performance-critical code.
163+
164+
If `ispy(x)` is true, you **must** return a `Py`.
165+
"""
166+
getpy(x) = ispy(x) ? throw(MethodError(getpy, (x,))) : NewPy(pyjl(x))
167+
getpy(x::Nothing) = pybuiltins.None
168+
getpy(x::Bool) = NewPy(pybool(x))
169+
getpy(x::Union{String, SubString{String}, Char}) = NewPy(pystr(x))
170+
getpy(x::Base.CodeUnits{UInt8, String}) = NewPy(pybytes(x))
171+
getpy(x::Base.CodeUnits{UInt8, SubString{String}}) = NewPy(pybytes(x))
172+
getpy(x::Tuple) = NewPy(pytuple_fromiter(x))
173+
getpy(x::Pair) = NewPy(pytuple_fromiter(x))
174+
getpy(x::Union{Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UInt64,UInt128,BigInt}) = NewPy(pyint(x))
175+
getpy(x::Rational{<:Union{Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UInt64,UInt128,BigInt}}) = NewPy(pyfraction(x))
176+
getpy(x::Union{Float16,Float32,Float64}) = NewPy(pyfloat(x))
177+
getpy(x::Complex{<:Union{Float16,Float32,Float64}}) = NewPy(pycomplex(x))
178+
getpy(x::AbstractRange{<:Union{Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UInt64,UInt128,BigInt}}) = NewPy(pyrange_fromrange(x))
179+
getpy(x::Date) = NewPy(pydate(x))
180+
getpy(x::Time) = NewPy(pytime(x))
181+
getpy(x::DateTime) = NewPy(pydatetime(x))
152182

153183
Base.string(x::Py) = pyisnull(x) ? "<py NULL>" : pystr(String, x)
154184
Base.print(io::IO, x::Py) = print(io, string(x))

0 commit comments

Comments
 (0)