Skip to content

Commit 00cf9d8

Browse files
author
Christopher Doris
committed
Merge branch 'main' into pandas
2 parents 261d443 + 1158024 commit 00cf9d8

File tree

16 files changed

+352
-16
lines changed

16 files changed

+352
-16
lines changed

CITATION.bib

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@misc{PythonCall.jl,
2+
author = {Rowley, Christopher},
3+
title = {PythonCall.jl: Python and Julia in harmony},
4+
year = {2022},
5+
url = {https://github.com/cjdoris/PythonCall.jl},
6+
}

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ MacroTools = "0.5"
2121
Requires = "1"
2222
Tables = "1"
2323
UnsafePointers = "1"
24-
julia = "1"
24+
julia = "1.6"
2525

2626
[extras]
2727
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"

docs/src/pycall.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ On this page, we give some tips for migrating between the two modules and a comp
66

77
## Tips
88

9-
- You can use both PyCall and PythonCall in the same Julia session (this might be platform dependent).
10-
- To force PythonCall to use the same Python interpreter as PyCall, set the environment variable `JULIA_PYTHONCALL_EXE` to `"@PyCall"`.
9+
- You can use both PyCall and PythonCall in the same Julia session. This is platform-dependent:
10+
- On Unix (Linux, Mac, etc.) the Python interpreter used by PythonCall and PyCall must be the same (see below).
11+
- On Windows, it appears to be possible for PythonCall and PyCall to use different interpreters.
12+
- To force PythonCall to use the same Python interpreter as PyCall, set the environment variable `JULIA_PYTHONCALL_EXE` to `"@PyCall"`. Note that this will opt out of automatic dependency management using CondaPkg.
13+
- Alternatively, to force PyCall to use the same interpreter as PythonCall, set the environment variable `PYTHON` to `PythonCall.C.CTX.exe_path` and then `Pkg.build("PyCall")`. You will need to do this each time you change project, because PythonCall by default uses a different Python for each project.
1114

1215
## Comparison
1316

docs/src/pythoncall-reference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ pyexec
5151
pygetattr
5252
pygetitem
5353
pyhasattr
54+
pyhasitem
5455
pyhash
5556
pyhelp
5657
pyimport

docs/src/releasenotes.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Release Notes
22

3+
## Unreleased
4+
* Adds `pyhasitem` and 3-arg `pygetitem`.
5+
* Extends `Base.get`, `Base.get!`, `Base.haskey` and 2-arg `Base.hash` for `Py`.
6+
* `PyArray` can now have any element type when the underlying array is of Python objects.
7+
* Bug fixes.
8+
39
## v0.8.0 (2022-03-17)
410
* **Breaking:** Removes `pymethod` and `pyclass`. In the future, `pyclass` may become sugar
511
for `types.new_class` (namely you can specify a metaclass).

src/Py.jl

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,9 @@ Base.showable(mime::MIME, o::Py) = pyshowable(mime, o)
313313
Base.getproperty(x::Py, k::Symbol) = pygetattr(x, string(k))
314314
Base.getproperty(x::Py, k::String) = pygetattr(x, k)
315315

316+
Base.hasproperty(x::Py, k::Symbol) = pyhasattr(x, string(k))
317+
Base.hasproperty(x::Py, k::String) = pyhasattr(x, k)
318+
316319
Base.setproperty!(x::Py, k::Symbol, v) = pysetattr(x, string(k), v)
317320
Base.setproperty!(x::Py, k::String, v) = pysetattr(x, k, v)
318321

@@ -350,6 +353,25 @@ Base.setindex!(x::Py, v, i...) = (pysetitem(x, i, v); x)
350353

351354
Base.delete!(x::Py, i) = (pydelitem(x, i); x)
352355

356+
Base.haskey(x::Py, i) = pyhasitem(x, i)
357+
358+
Base.get(x::Py, i, d) = pygetitem(x, i, d)
359+
360+
function Base.get(f::Base.Callable, x::Py, i)
361+
v = pygetitem(x, i, nothing)
362+
v === nothing ? f() : v
363+
end
364+
365+
Base.get!(x::Py, i, d) = get(x, i) do
366+
pysetitem(x, i, d)
367+
pygetitem(x, i)
368+
end
369+
370+
Base.get!(f::Base.Callable, x::Py, i) = get(x, i) do
371+
pysetitem(x, i, f())
372+
pygetitem(x, i)
373+
end
374+
353375
Base.eltype(::Type{Py}) = Py
354376

355377
Base.IteratorSize(::Type{Py}) = Base.SizeUnknown()
@@ -366,7 +388,7 @@ end
366388

367389
Base.in(v, x::Py) = pycontains(x, v)
368390

369-
Base.hash(x::Py) = reinterpret(UInt, Int(pyhash(x)))
391+
Base.hash(x::Py, h::UInt) = reinterpret(UInt, Int(pyhash(x))) - 3h
370392

371393
(f::Py)(args...; kwargs...) = pycall(f, args...; kwargs...)
372394

@@ -464,7 +486,7 @@ Base.powermod(x::Number, y::Py, z::Number) = pypow(x, y, z)
464486
Base.powermod(x::Py, y::Number, z::Number) = pypow(x, y, z)
465487

466488
# documentation
467-
function Base.Docs.getdoc(x::Py)
489+
function Base.Docs.getdoc(x::Py, @nospecialize(sig))
468490
parts = []
469491
inspect = pyimport("inspect")
470492
# head line
@@ -498,4 +520,5 @@ function Base.Docs.getdoc(x::Py)
498520
end
499521
return Markdown.MD(parts)
500522
end
523+
Base.Docs.doc(x::Py, sig::Type=Union{}) = Base.Docs.getdoc(x, sig)
501524
Base.Docs.Binding(x::Py, k::Symbol) = getproperty(x, k)

src/abstract/object.jl

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,24 @@ export pyascii
3030
pyhasattr(x, k)
3131
3232
Equivalent to `hasattr(x, k)` in Python.
33+
34+
Tests if `getattr(x, k)` raises an `AttributeError`.
3335
"""
34-
pyhasattr(x, k) = errcheck(@autopy x k C.PyObject_HasAttr(getptr(x_), getptr(k_))) == 1
36+
# pyhasattr(x, k) = errcheck(@autopy x k C.PyObject_HasAttr(getptr(x_), getptr(k_))) == 1
37+
function pyhasattr(x, k)
38+
ptr = @autopy x k C.PyObject_GetAttr(getptr(x_), getptr(k_))
39+
if iserrset(ptr)
40+
if errmatches(pybuiltins.AttributeError)
41+
errclear()
42+
return false
43+
else
44+
pythrow()
45+
end
46+
else
47+
decref(ptr)
48+
return true
49+
end
50+
end
3551
export pyhasattr
3652

3753
"""
@@ -45,8 +61,12 @@ pygetattr(x, k) = pynew(errcheck(@autopy x k C.PyObject_GetAttr(getptr(x_), getp
4561
function pygetattr(x, k, d)
4662
ptr = @autopy x k C.PyObject_GetAttr(getptr(x_), getptr(k_))
4763
if iserrset(ptr)
48-
errclear()
49-
return d
64+
if errmatches(pybuiltins.AttributeError)
65+
errclear()
66+
return d
67+
else
68+
pythrow()
69+
end
5070
else
5171
return pynew(ptr)
5272
end
@@ -118,11 +138,48 @@ pylen(x) = errcheck(@autopy x C.PyObject_Length(getptr(x_)))
118138
export pylen
119139

120140
"""
121-
pygetitem(x, k)
141+
pyhasitem(x, k)
142+
143+
Test if `pygetitem(x, k)` raises a `KeyError` or `AttributeError`.
144+
"""
145+
function pyhasitem(x, k)
146+
ptr = @autopy x k C.PyObject_GetItem(getptr(x_), getptr(k_))
147+
if iserrset(ptr)
148+
if errmatches(pybuiltins.KeyError) || errmatches(pybuiltins.IndexError)
149+
errclear()
150+
return false
151+
else
152+
pythrow()
153+
end
154+
else
155+
decref(ptr)
156+
return true
157+
end
158+
end
159+
export pyhasitem
160+
161+
"""
162+
pygetitem(x, k, [d])
163+
164+
Equivalent `x[k]` in Python.
122165
123-
Equivalent to `getitem(x, k)` or `x[k]` in Python.
166+
If `d` is specified, it is returned if the item does not exist (i.e. if `x[k]` raises a
167+
`KeyError` or `IndexError`).
124168
"""
125169
pygetitem(x, k) = pynew(errcheck(@autopy x k C.PyObject_GetItem(getptr(x_), getptr(k_))))
170+
function pygetitem(x, k, d)
171+
ptr = @autopy x k C.PyObject_GetItem(getptr(x_), getptr(k_))
172+
if iserrset(ptr)
173+
if errmatches(pybuiltins.KeyError) || errmatches(pybuiltins.IndexError)
174+
errclear()
175+
return d
176+
else
177+
pythrow()
178+
end
179+
else
180+
return pynew(ptr)
181+
end
182+
end
126183
export pygetitem
127184

128185
"""

src/concrete/range.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ function pyconvert_rule_range(::Type{R}, x::Py, ::Type{StepRange{T0,S0}}=Utils._
2323
end
2424

2525
function pyconvert_rule_range(::Type{R}, x::Py, ::Type{UnitRange{T0}}=Utils._type_lb(R), ::Type{UnitRange{T1}}=Utils._type_ub(R)) where {R<:UnitRange,T0,T1}
26-
b = @pyconvert(Utils._typeintersect(Integer, S1), x.step)
27-
b == 1 && return pyconvert_unconverted()
26+
b = @pyconvert(Int, x.step)
27+
b == 1 || return pyconvert_unconverted()
2828
a = @pyconvert(Utils._typeintersect(Integer, T1), x.start)
2929
c = @pyconvert(Utils._typeintersect(Integer, T1), x.stop)
3030
a′, c′ = promote(a, c - oftype(c, 1))

src/convert.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@ function init_pyconvert()
444444
pyconvert_add_rule("io:IOBase", PyIO, pyconvert_rule_io, priority)
445445
pyconvert_add_rule("_io:_IOBase", PyIO, pyconvert_rule_io, priority)
446446
pyconvert_add_rule("pandas.core.frame:DataFrame", PyPandasDataFrame, pyconvert_rule_pandasdataframe, priority)
447+
pyconvert_add_rule("pandas.core.arrays.base:ExtensionArray", PyList, pyconvert_rule_sequence, priority)
447448
pyconvert_add_rule("builtins:tuple", Tuple, pyconvert_rule_iterable, priority)
448449
pyconvert_add_rule("datetime:datetime", DateTime, pyconvert_rule_datetime, priority)
449450
pyconvert_add_rule("datetime:date", Date, pyconvert_rule_date, priority)

src/jlwrap/any.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,11 @@ end
134134

135135
function pyjlany_help(self, mime_::Py)
136136
mime = pyconvertarg(Union{Nothing,String}, mime_, "mime")
137-
x = Utils.ExtraNewline(Docs.doc(self))
137+
doc = Docs.getdoc(self)
138+
if doc === nothing
139+
doc = Docs.doc(self)
140+
end
141+
x = Utils.ExtraNewline(doc)
138142
if mime === nothing
139143
display(x)
140144
else

0 commit comments

Comments
 (0)