From 7e8d373f1ce8d02a06f0d46217f0621f12c91a71 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Sat, 4 May 2024 08:53:43 +0100 Subject: [PATCH 01/39] change julia wrapper hierarchy --- docs/src/conversion-to-julia.md | 2 +- docs/src/conversion-to-python.md | 4 ++-- docs/src/juliacall-reference.md | 22 +++++++++++----------- docs/src/juliacall.md | 2 +- pytest/test_all.py | 2 +- src/JlWrap/C.jl | 6 +++--- src/JlWrap/JlWrap.jl | 2 +- src/JlWrap/any.jl | 10 +++++----- src/JlWrap/array.jl | 2 +- src/JlWrap/base.jl | 8 ++++---- src/JlWrap/callback.jl | 2 +- src/JlWrap/dict.jl | 2 +- src/JlWrap/io.jl | 2 +- src/JlWrap/iter.jl | 2 +- src/JlWrap/module.jl | 4 ++-- src/JlWrap/number.jl | 2 +- src/JlWrap/raw.jl | 6 +++--- src/JlWrap/set.jl | 2 +- src/JlWrap/type.jl | 2 +- 19 files changed, 42 insertions(+), 42 deletions(-) diff --git a/docs/src/conversion-to-julia.md b/docs/src/conversion-to-julia.md index e08a5d19..3cf71459 100644 --- a/docs/src/conversion-to-julia.md +++ b/docs/src/conversion-to-julia.md @@ -11,7 +11,7 @@ From Python, the arguments to a Julia function will be converted according to th | From | To | | :----------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------- | | **Top priority (wrapped values).** | | -| `juliacall.AnyValue` | `Any` | +| `juliacall.Jl` | `Any` | | **Very high priority (arrays).** | | | Objects satisfying the buffer or array interface (inc. `bytes`, `bytearray`, `array.array`, `numpy.ndarray`) | `PyArray` | | **High priority (canonical conversions).** | | diff --git a/docs/src/conversion-to-python.md b/docs/src/conversion-to-python.md index bb38ec5a..d95ad269 100644 --- a/docs/src/conversion-to-python.md +++ b/docs/src/conversion-to-python.md @@ -30,7 +30,7 @@ From Python, this occurs when converting the return value of a Julia function. | `IO` | `juliacall.BufferedIOValue` | | `Module` | `juliacall.ModuleValue` | | `Type` | `juliacall.TypeValue` | -| Anything else | `juliacall.AnyValue` | +| Anything else | `juliacall.Jl` | See [here](@ref julia-wrappers) for an explanation of the `juliacall.*Value` wrapper types. @@ -46,7 +46,7 @@ PythonCall.ispy ``` Alternatively, if you define a wrapper type (a subtype of -[`juliacall.AnyValue`](#juliacall.AnyValue)) then you may instead define `pyjltype(::T)` to +[`juliacall.Jl`](#juliacall.Jl)) then you may instead define `pyjltype(::T)` to be that type. ```@docs diff --git a/docs/src/juliacall-reference.md b/docs/src/juliacall-reference.md index c62a480b..6a1b40c5 100644 --- a/docs/src/juliacall-reference.md +++ b/docs/src/juliacall-reference.md @@ -45,19 +45,19 @@ A new module with the given name. ## [Wrapper types](@id julia-wrappers) 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 +Python to some [`Jl`](#juliacall.Jl) object, which wraps the original value, but giving it a Pythonic interface. -Subclasses of [`AnyValue`](#juliacall.AnyValue) provide additional Python semantics. For +Subclasses of [`Jl`](#juliacall.Jl) provide additional Python semantics. For example a Julia vector is converted to a [`VectorValue`](#juliacall.VectorValue) which satisfies the Python sequence interface and behaves very similar to a list. There is also a [`RawValue`](#juliacall.RawValue) object, which gives a stricter -"Julia-only" interface, documented below. These types all inherit from `ValueBase`: +"Julia-only" interface, documented below. These types all inherit from `JlBase`: -- `ValueBase` +- `JlBase` - [`RawValue`](#juliacall.RawValue) - - [`AnyValue`](#juliacall.AnyValue) + - [`Jl`](#juliacall.Jl) - [`NumberValue`](#juliacall.NumberValue) - `ComplexValue` - `RealValue` @@ -74,7 +74,7 @@ There is also a [`RawValue`](#juliacall.RawValue) object, which gives a stricter - [`TypeValue`](#juliacall.TypeValue) `````@customdoc -juliacall.AnyValue - Class +juliacall.Jl - Class Wraps any Julia object, giving it some basic Python semantics. Subtypes provide extra semantics. @@ -84,7 +84,7 @@ comparisons, `len(x)`, `a in x`, `dir(x)`. Calling, indexing, attribute access, etc. will convert the result to a Python object according to [this table](@ref jl2py). This is typically a builtin Python type (for -immutables) or a subtype of `AnyValue`. +immutables) or a subtype of `Jl`. Attribute access can be used to access Julia properties as well as normal class members. In the case of a name clash, the class member will take precedence. For convenience with Julia @@ -181,7 +181,7 @@ There are also subtypes `BinaryIOValue` and `TextIOValue`, which are subclasses juliacall.ModuleValue - Class This wraps any Julia `Module` value. -It is the same as [`AnyValue`](#juliacall.AnyValue) except for one additional convenience +It is the same as [`Jl`](#juliacall.Jl) except for one additional convenience method: - `seval([module=self], code)`: Evaluates the given code (a string) in the given module. ````` @@ -191,7 +191,7 @@ juliacall.TypeValue - Class This wraps any Julia `Type` value. -It is the same as [`AnyValue`](#juliacall.AnyValue) except that indexing is used to access +It is the same as [`Jl`](#juliacall.Jl) except that indexing is used to access Julia's "curly" syntax for specifying parametric types: ```python @@ -208,13 +208,13 @@ Wraps any Julia value with a rigid interface suitable for generic programming. Supports `repr(x)`, `str(x)`, attributes (`x.attr`), calling (`x(a,b)`), `len(x)`, `dir(x)`. -This is very similar to [`AnyValue`](#juliacall.AnyValue) except that indexing, calling, +This is very similar to [`Jl`](#juliacall.Jl) except that indexing, calling, etc. will always return a `RawValue`. Indexing with a tuple corresponds to indexing in Julia with multiple values. To index with a single tuple, it will need to be wrapped in another tuple. ###### Members -- `_jl_any()`: Convert to a [`AnyValue`](#juliacall.AnyValue) (or subclass). (See also +- `_jl_any()`: Convert to a [`Jl`](#juliacall.Jl) (or subclass). (See also [`pyjl`](@ref).) ````` diff --git a/docs/src/juliacall.md b/docs/src/juliacall.md index a16b71f2..d111a0c9 100644 --- a/docs/src/juliacall.md +++ b/docs/src/juliacall.md @@ -64,7 +64,7 @@ jl.cor(x, y) ``` What to read next: -- The main functionality of this package is in `AnyValue` objects, which represent Julia +- The main functionality of this package is in `Jl` objects, which represent Julia objects, [documented here](@ref julia-wrappers). - If you need to install Julia packages, [read here](@ref julia-deps). - When you call a Julia function, such as `jl.rand(...)` in the above example, its diff --git a/pytest/test_all.py b/pytest/test_all.py index c6cff009..e531e321 100644 --- a/pytest/test_all.py +++ b/pytest/test_all.py @@ -14,7 +14,7 @@ def test_convert(): jl = juliacall.Main for (x, t) in [(None, jl.Nothing), (True, jl.Bool), ([1,2,3], jl.Vector)]: y = juliacall.convert(t, x) - assert isinstance(y, juliacall.AnyValue) + assert isinstance(y, juliacall.Jl) assert jl.isa(y, t) def test_interactive(): diff --git a/src/JlWrap/C.jl b/src/JlWrap/C.jl index e90dc478..1451a1dd 100644 --- a/src/JlWrap/C.jl +++ b/src/JlWrap/C.jl @@ -251,7 +251,7 @@ function _pyjl_deserialize(t::C.PyPtr, v::C.PyPtr) end end -const _pyjlbase_name = "juliacall.ValueBase" +const _pyjlbase_name = "juliacall.JlBase" const _pyjlbase_type = fill(C.PyTypeObject()) const _pyjlbase_isnull_name = "_jl_isnull" const _pyjlbase_callmethod_name = "_jl_callmethod" @@ -311,7 +311,7 @@ function init_c() o = PyJuliaBase_Type[] = C.PyPtr(pointer(_pyjlbase_type)) if C.PyType_Ready(o) == -1 C.PyErr_Print() - error("Error initializing 'juliacall.ValueBase'") + error("Error initializing 'juliacall.JlBase'") end end @@ -344,7 +344,7 @@ end PyJuliaValue_New(t::C.PyPtr, @nospecialize(v)) = begin if C.PyType_IsSubtype(t, PyJuliaBase_Type[]) != 1 - C.PyErr_SetString(C.POINTERS.PyExc_TypeError, "Expecting a subtype of 'juliacall.ValueBase'") + C.PyErr_SetString(C.POINTERS.PyExc_TypeError, "Expecting a subtype of 'juliacall.JlBase'") return C.PyNULL end o = C.PyObject_CallObject(t, C.PyNULL) diff --git a/src/JlWrap/JlWrap.jl b/src/JlWrap/JlWrap.jl index 498cc382..7614b053 100644 --- a/src/JlWrap/JlWrap.jl +++ b/src/JlWrap/JlWrap.jl @@ -1,7 +1,7 @@ """ module PythonCall.JlWrap -Defines the Python object wrappers around Julia objects (`juliacall.AnyValue` etc). +Defines the Python object wrappers around Julia objects (`juliacall.Jl` etc). """ module JlWrap diff --git a/src/JlWrap/any.jl b/src/JlWrap/any.jl index 92a53a2b..cddec22c 100644 --- a/src/JlWrap/any.jl +++ b/src/JlWrap/any.jl @@ -191,7 +191,7 @@ function init_any() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class AnyValue(ValueBase): + class Jl(JlBase): __slots__ = () def __repr__(self): if self._jl_isnull(): @@ -210,7 +210,7 @@ function init_any() return self._jl_callmethod($(pyjl_methodnum(pyjlany_getattr)), k) def __setattr__(self, k, v): try: - ValueBase.__setattr__(self, k, v) + JlBase.__setattr__(self, k, v) except AttributeError: if k.startswith("__") and k.endswith("__"): raise @@ -218,7 +218,7 @@ function init_any() return self._jl_callmethod($(pyjl_methodnum(pyjlany_setattr)), k, v) def __dir__(self): - return ValueBase.__dir__(self) + self._jl_callmethod($(pyjl_methodnum(pyjlany_dir))) + return JlBase.__dir__(self) + self._jl_callmethod($(pyjl_methodnum(pyjlany_dir))) def __call__(self, *args, **kwargs): return self._jl_callmethod($(pyjl_methodnum(pyjlany_call)), args, kwargs) def __bool__(self): @@ -325,7 +325,7 @@ function init_any() def _repr_mimebundle_(self, include=None, exclude=None): return self._jl_callmethod($(pyjl_methodnum(pyjlany_mimebundle)), include, exclude) """, @__FILE__(), "exec"), jl.__dict__) - pycopy!(pyjlanytype, jl.AnyValue) + pycopy!(pyjlanytype, jl.Jl) end """ @@ -348,7 +348,7 @@ export pyjl """ pyjltype(x) -The subtype of `juliacall.AnyValue` which the Julia object `x` is wrapped as by `pyjl(x)`. +The subtype of `juliacall.Jl` which the Julia object `x` is wrapped as by `pyjl(x)`. Overload `pyjltype(::T)` to define a custom conversion for your type `T`. """ diff --git a/src/JlWrap/array.jl b/src/JlWrap/array.jl index 2cb33bbc..cb27c07b 100644 --- a/src/JlWrap/array.jl +++ b/src/JlWrap/array.jl @@ -295,7 +295,7 @@ function init_array() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class ArrayValue(AnyValue): + class ArrayValue(JlBase): __slots__ = () _jl_buffer_info = $(pyjl_methodnum(pyjlarray_buffer_info)) @property diff --git a/src/JlWrap/base.jl b/src/JlWrap/base.jl index 08793e63..6d97412c 100644 --- a/src/JlWrap/base.jl +++ b/src/JlWrap/base.jl @@ -9,7 +9,7 @@ pyjl(t, v) = pynew(errcheck(@autopy t Cjl.PyJuliaValue_New(getptr(t_), v))) """ pyisjl(x) -Test whether `x` is a wrapped Julia value, namely an instance of `juliacall.ValueBase`. +Test whether `x` is a wrapped Julia value, namely an instance of `juliacall.JlBase`. """ pyisjl(x) = pytypecheck(x, pyjlbasetype) export pyisjl @@ -18,7 +18,7 @@ pyjlisnull(x) = @autopy x begin if pyisjl(x_) Cjl.PyJuliaValue_IsNull(getptr(x_)) else - error("Expecting a 'juliacall.ValueBase', got a '$(pytype(x_).__name__)'") + error("Expecting a 'juliacall.JlBase', got a '$(pytype(x_).__name__)'") end end @@ -38,11 +38,11 @@ export pyjlvalue function init_base() setptr!(pyjlbasetype, incref(Cjl.PyJuliaBase_Type[])) - pyjuliacallmodule.ValueBase = pyjlbasetype + pyjuliacallmodule.JlBase = pyjlbasetype # conversion rule priority = PYCONVERT_PRIORITY_WRAP - pyconvert_add_rule("juliacall:ValueBase", Any, pyconvert_rule_jlvalue, priority) + pyconvert_add_rule("juliacall:JlBase", Any, pyconvert_rule_jlvalue, priority) end pyconvert_rule_jlvalue(::Type{T}, x::Py) where {T} = pyconvert_tryconvert(T, _pyjl_getvalue(x)) diff --git a/src/JlWrap/callback.jl b/src/JlWrap/callback.jl index c25c10dd..1c82b455 100644 --- a/src/JlWrap/callback.jl +++ b/src/JlWrap/callback.jl @@ -39,7 +39,7 @@ function init_callback() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class CallbackValue(ValueBase): + class CallbackValue(JlBase): __slots__ = () def __repr__(self): if self._jl_isnull(): diff --git a/src/JlWrap/dict.jl b/src/JlWrap/dict.jl index 4d01674a..6df2cc0b 100644 --- a/src/JlWrap/dict.jl +++ b/src/JlWrap/dict.jl @@ -35,7 +35,7 @@ function init_dict() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class DictValue(AnyValue): + class DictValue(JlBase): __slots__ = () _jl_undefined_ = object() def __bool__(self): diff --git a/src/JlWrap/io.jl b/src/JlWrap/io.jl index 85785fee..1dfe3e6c 100644 --- a/src/JlWrap/io.jl +++ b/src/JlWrap/io.jl @@ -205,7 +205,7 @@ function init_io() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class IOValueBase(AnyValue): + class IOValueBase(JlBase): __slots__ = () def close(self): return self._jl_callmethod($(pyjl_methodnum(pyjlio_close))) diff --git a/src/JlWrap/iter.jl b/src/JlWrap/iter.jl index 90d24537..499afcf7 100644 --- a/src/JlWrap/iter.jl +++ b/src/JlWrap/iter.jl @@ -29,7 +29,7 @@ function init_iter() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class IteratorValue(AnyValue): + class IteratorValue(JlBase): __slots__ = () def __iter__(self): return self diff --git a/src/JlWrap/module.jl b/src/JlWrap/module.jl index 8d879ae5..02257658 100644 --- a/src/JlWrap/module.jl +++ b/src/JlWrap/module.jl @@ -17,10 +17,10 @@ function init_module() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class ModuleValue(AnyValue): + class ModuleValue(JlBase): __slots__ = () def __dir__(self): - return ValueBase.__dir__(self) + self._jl_callmethod($(pyjl_methodnum(pyjlmodule_dir))) + return JlBase.__dir__(self) + self._jl_callmethod($(pyjl_methodnum(pyjlmodule_dir))) def seval(self, expr): return self._jl_callmethod($(pyjl_methodnum(pyjlmodule_seval)), expr) """, @__FILE__(), "exec"), jl.__dict__) diff --git a/src/JlWrap/number.jl b/src/JlWrap/number.jl index 1ed4013c..0f43c64a 100644 --- a/src/JlWrap/number.jl +++ b/src/JlWrap/number.jl @@ -87,7 +87,7 @@ function init_number() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class NumberValue(AnyValue): + class NumberValue(JlBase): __slots__ = () def __bool__(self): return not self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(iszero)))) diff --git a/src/JlWrap/raw.jl b/src/JlWrap/raw.jl index a531df37..1fd56330 100644 --- a/src/JlWrap/raw.jl +++ b/src/JlWrap/raw.jl @@ -85,7 +85,7 @@ function init_raw() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class RawValue(ValueBase): + class RawValue(JlBase): __slots__ = () def __repr__(self): if self._jl_isnull(): @@ -104,7 +104,7 @@ function init_raw() return self._jl_callmethod($(pyjl_methodnum(pyjlraw_getattr)), k) def __setattr__(self, k, v): try: - ValueBase.__setattr__(self, k, v) + JlBase.__setattr__(self, k, v) except AttributeError: if k.startswith("__") and k.endswith("__"): raise @@ -112,7 +112,7 @@ function init_raw() return self._jl_callmethod($(pyjl_methodnum(pyjlraw_setattr)), k, v) def __dir__(self): - return ValueBase.__dir__(self) + self._jl_callmethod($(pyjl_methodnum(pyjlraw_dir))) + return JlBase.__dir__(self) + self._jl_callmethod($(pyjl_methodnum(pyjlraw_dir))) def __call__(self, *args, **kwargs): return self._jl_callmethod($(pyjl_methodnum(pyjlraw_call)), args, kwargs) def __len__(self): diff --git a/src/JlWrap/set.jl b/src/JlWrap/set.jl index 38c9b82d..2694d13a 100644 --- a/src/JlWrap/set.jl +++ b/src/JlWrap/set.jl @@ -77,7 +77,7 @@ function init_set() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class SetValue(AnyValue): + class SetValue(JlBase): __slots__ = () def __bool__(self): return bool(len(self)) diff --git a/src/JlWrap/type.jl b/src/JlWrap/type.jl index ef079f5c..050605e4 100644 --- a/src/JlWrap/type.jl +++ b/src/JlWrap/type.jl @@ -15,7 +15,7 @@ function init_type() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class TypeValue(AnyValue): + class TypeValue(JlBase): __slots__ = () def __getitem__(self, k): return self._jl_callmethod($(pyjl_methodnum(pyjltype_getitem)), k) From a3a2a91eec137738125e7db18bb2ebac9b060de7 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Sat, 4 May 2024 10:00:30 +0100 Subject: [PATCH 02/39] remove RawValue, TypeValue and ModuleValue from juliacall --- docs/src/releasenotes.md | 7 ++ src/C/C.jl | 1 - src/JlWrap/C.jl | 2 + src/JlWrap/JlWrap.jl | 8 --- src/JlWrap/any.jl | 85 ++++++++++++++++------- src/JlWrap/base.jl | 4 +- src/JlWrap/module.jl | 30 -------- src/JlWrap/raw.jl | 143 --------------------------------------- src/JlWrap/type.jl | 30 -------- src/PythonCall.jl | 2 +- 10 files changed, 74 insertions(+), 238 deletions(-) delete mode 100644 src/JlWrap/module.jl delete mode 100644 src/JlWrap/raw.jl delete mode 100644 src/JlWrap/type.jl diff --git a/docs/src/releasenotes.md b/docs/src/releasenotes.md index d50f76a4..2e2a14c8 100644 --- a/docs/src/releasenotes.md +++ b/docs/src/releasenotes.md @@ -2,6 +2,13 @@ ## Unreleased (v1) * `PythonCall.GC` is now more like `Base.GC`: `enable(true)` replaces `enable()`, `enable(false)` replaces `disable()`, and `gc()` is added. +* Breaking changes to Julia wrapper types: + * Classes renamed: `ValueBase` to `JlBase`, `AnyValue` to `Jl`, `ArrayValue` to `JlArray`, etc. + * Classes removed: `RawValue`, `ModuleValue`, `TypeValue`. + * `Jl` now behaves similar to how `RawValue` behaved before. In particular, most methods on `Jl` now return a `Jl` instead of an arbitrary Python object. + * `juliacall.Pkg` removed. + * Methods renamed: `_jl_display()` to `jl_display()`, `_jl_help()` to `jl_help()`, etc. + * Methods removed: `_jl_raw()` ## 0.9.20 (2024-05-01) * The IPython extension is now automatically loaded upon import if IPython is detected. diff --git a/src/C/C.jl b/src/C/C.jl index 4ceda829..01bc7a4c 100644 --- a/src/C/C.jl +++ b/src/C/C.jl @@ -8,7 +8,6 @@ module C using Base: @kwdef using UnsafePointers: UnsafePtr using CondaPkg: CondaPkg -using Pkg: Pkg using Requires: @require using Libdl: dlpath, dlopen, dlopen_e, dlclose, dlsym, dlsym_e, RTLD_LAZY, RTLD_DEEPBIND, RTLD_GLOBAL diff --git a/src/JlWrap/C.jl b/src/JlWrap/C.jl index 1451a1dd..a7a23db1 100644 --- a/src/JlWrap/C.jl +++ b/src/JlWrap/C.jl @@ -321,6 +321,8 @@ function __init__() end end +PyJuliaValue_Check(o::C.PyPtr) = C.PyObject_IsInstance(o, PyJuliaBase_Type[]) + PyJuliaValue_IsNull(o::C.PyPtr) = UnsafePtr{PyJuliaValueObject}(o).value[] == 0 PyJuliaValue_GetValue(o::C.PyPtr) = PYJLVALUES[UnsafePtr{PyJuliaValueObject}(o).value[]] diff --git a/src/JlWrap/JlWrap.jl b/src/JlWrap/JlWrap.jl index 7614b053..f9b90534 100644 --- a/src/JlWrap/JlWrap.jl +++ b/src/JlWrap/JlWrap.jl @@ -11,18 +11,14 @@ using ..Core: C, Utils, pynew, @autopy, incref, decref, setptr!, getptr, pyjulia using ..Convert: pyconvert, @pyconvert, PYCONVERT_PRIORITY_WRAP, pyconvert_add_rule, pyconvert_tryconvert, pyconvertarg, pyconvert_result using ..GC: GC -using Pkg: Pkg using Base: @propagate_inbounds, allocatedinline import ..Core: Py include("C.jl") include("base.jl") -include("raw.jl") include("any.jl") include("iter.jl") -include("type.jl") -include("module.jl") include("io.jl") include("number.jl") include("objectarray.jl") @@ -35,11 +31,8 @@ include("callback.jl") function __init__() Cjl.C.with_gil() do init_base() - init_raw() init_any() init_iter() - init_type() - init_module() init_io() init_number() init_array() @@ -52,7 +45,6 @@ function __init__() jl.Core = Base.Core jl.Base = Base jl.Main = Main - jl.Pkg = Pkg jl.PythonCall = PythonCall end end diff --git a/src/JlWrap/any.jl b/src/JlWrap/any.jl index cddec22c..3ec88f86 100644 --- a/src/JlWrap/any.jl +++ b/src/JlWrap/any.jl @@ -1,20 +1,26 @@ const pyjlanytype = pynew() +pyjlany(x) = pyjl(pyjlanytype, x) + # pyjlany_repr(self) = Py("<jl $(repr(self))>") function pyjlany_repr(self) str = repr(MIME("text/plain"), self; context=IOContext(devnull, :limit=>true, :displaysize=>(23,80))) # type = self isa Function ? "Function" : self isa Type ? "Type" : nameof(typeof(self)) sep = '\n' in str ? '\n' : ' ' - Py("Julia:$sep$str") + Py("Julia:$sep$str"::String) end # Note: string(self) doesn't always return a String -pyjlany_str(self) = Py(sprint(print, self)) +pyjlany_str(self) = Py(sprint(print, self)::String) + +pyjl_attr_py2jl(k::String) = replace(k, r"_[b]+$" => (x -> "!"^(length(x) - 1))) + +pyjl_attr_jl2py(k::String) = replace(k, r"!+$" => (x -> "_" * "b"^length(x))) function pyjlany_getattr(self, k_::Py) k = Symbol(pyjl_attr_py2jl(pyconvert(String, k_))) pydel!(k_) - Py(getproperty(self, k)) + pyjlany(getproperty(self, k)) end pyjl_handle_error_type(::typeof(pyjlany_getattr), self, exc) = pybuiltins.AttributeError @@ -27,18 +33,29 @@ function pyjlany_setattr(self, k_::Py, v_::Py) end pyjl_handle_error_type(::typeof(pyjlany_setattr), self, exc) = pybuiltins.AttributeError -pyjlany_dir(self) = pylist(pyjl_attr_jl2py(string(k)) for k in propertynames(self, true)) +function pyjlany_dir(self) + ks = Symbol[] + if self isa Module + append!(ks, names(self, all = true, imported = true)) + for m in ccall(:jl_module_usings, Any, (Any,), self)::Vector + append!(ks, names(m)) + end + else + append!(propertynames(self, true)) + end + pylist(pyjl_attr_jl2py(string(k)) for k in ks) +end function pyjlany_call(self, args_::Py, kwargs_::Py) if pylen(kwargs_) > 0 args = pyconvert(Vector{Any}, args_) kwargs = pyconvert(Dict{Symbol,Any}, kwargs_) - ans = Py(self(args...; kwargs...)) + ans = pyjlany(self(args...; kwargs...)) elseif pylen(args_) > 0 args = pyconvert(Vector{Any}, args_) - ans = Py(self(args...)) + ans = pyjlany(self(args...)) else - ans = Py(self()) + ans = pyjlany(self()) end pydel!(args_) pydel!(kwargs_) @@ -47,13 +64,24 @@ end pyjl_handle_error_type(::typeof(pyjlany_call), self, exc) = exc isa MethodError && exc.f === self ? pybuiltins.TypeError : PyNULL function pyjlany_getitem(self, k_::Py) - if pyistuple(k_) - k = pyconvert(Vector{Any}, k_) - pydel!(k_) - Py(self[k...]) + if self isa Type + if pyistuple(k_) + k = pyconvert(Vector{Any}, k_) + pydel!(k_) + pyjlany(self{k...}) + else + k = pyconvert(Any, k_) + pyjlany(self{k}) + end else - k = pyconvert(Any, k_) - Py(self[k]) + if pyistuple(k_) + k = pyconvert(Vector{Any}, k_) + pydel!(k_) + pyjlany(self[k...]) + else + k = pyconvert(Any, k_) + pyjlany(self[k]) + end end end pyjl_handle_error_type(::typeof(pyjlany_getitem), self, exc) = exc isa BoundsError ? pybuiltins.IndexError : exc isa KeyError ? pybuiltins.KeyError : PyNULL @@ -85,7 +113,7 @@ function pyjlany_delitem(self, k_::Py) end pyjl_handle_error_type(::typeof(pyjlany_delitem), self, exc) = exc isa BoundsError ? pybuiltins.IndexError : exc isa KeyError ? pybuiltins.KeyError : PyNULL -pyjlany_contains(self, v::Py) = Py(@pyconvert(eltype(self), v, return Py(false)) in self) +pyjlany_contains(self, v::Py) = Py((@pyconvert(eltype(self), v, return Py(false)) in self)::Bool) pyjl_handle_error_type(::typeof(pyjlany_contains), self, exc) = exc isa MethodError && exc.f === in ? pybuiltins.TypeError : PyNULL struct pyjlany_op{OP} @@ -96,7 +124,7 @@ function (op::pyjlany_op)(self, other_::Py) if pyisjl(other_) other = pyjlvalue(other_) pydel!(other_) - Py(op.op(self, other)) + pyjlany(op.op(self, other)) else pybuiltins.NotImplemented end @@ -107,7 +135,7 @@ function (op::pyjlany_op)(self, other_::Py, other2_::Py) other2 = pyjlvalue(other2_) pydel!(other_) pydel!(other2_) - Py(op.op(self, other, other2)) + pyjlany(op.op(self, other, other2)) else pybuiltins.NotImplemented end @@ -121,7 +149,7 @@ function (op::pyjlany_rev_op)(self, other_::Py) if pyisjl(other_) other = pyjlvalue(other_) pydel!(other_) - Py(op.op(other, self)) + pyjlany(op.op(other, self)) else pybuiltins.NotImplemented end @@ -132,14 +160,14 @@ function (op::pyjlany_rev_op)(self, other_::Py, other2_::Py) other2 = pyjlvalue(other2_) pydel!(other_) pydel!(other2_) - Py(op.op(other, self, other2)) + pyjlany(op.op(other, self, other2)) else pybuiltins.NotImplemented end end pyjl_handle_error_type(op::pyjlany_rev_op, self, exc) = exc isa MethodError && exc.f === op.op ? pybuiltins.TypeError : PyNULL -pyjlany_name(self) = Py(string(nameof(self))) +pyjlany_name(self) = Py(string(nameof(self))::String) pyjl_handle_error_type(::typeof(pyjlany_name), self, exc) = exc isa MethodError && exc.f === nameof ? pybuiltins.AttributeError : PyNULL function pyjlany_display(self, mime_::Py) @@ -187,6 +215,17 @@ function pyjlany_mimebundle(self, include::Py, exclude::Py) return ans end +function pyjlany_eval(self, expr::Py) + if self isa Module + Py(Base.eval(self, Meta.parseall(strip(pyconvert(String, expr))))) + else + throw(ArgumentError("self must be a Julia 'Module', got a '$(typeof(self))'")) + end +end + +pyjl_handle_error_type(::typeof(pyjlany_eval), self, exc) = exc isa ArgumentError ? pybuiltins.TypeError : PyNULL + + function init_any() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" @@ -316,12 +355,12 @@ function init_any() @property def __name__(self): return self._jl_callmethod($(pyjl_methodnum(pyjlany_name))) - def _jl_raw(self): - return self._jl_callmethod($(pyjl_methodnum(pyjlraw))) - def _jl_display(self, mime=None): + def jl_display(self, mime=None): return self._jl_callmethod($(pyjl_methodnum(pyjlany_display)), mime) - def _jl_help(self, mime=None): + def jl_help(self, mime=None): return self._jl_callmethod($(pyjl_methodnum(pyjlany_help)), mime) + def jl_eval(self, expr): + return self._jl_callmethod($(pyjl_methodnum(pyjlany_eval)), expr) def _repr_mimebundle_(self, include=None, exclude=None): return self._jl_callmethod($(pyjl_methodnum(pyjlany_mimebundle)), include, exclude) """, @__FILE__(), "exec"), jl.__dict__) diff --git a/src/JlWrap/base.jl b/src/JlWrap/base.jl index 6d97412c..0e6e8e46 100644 --- a/src/JlWrap/base.jl +++ b/src/JlWrap/base.jl @@ -91,7 +91,7 @@ function Cjl._pyjl_callmethod(f, self_::C.PyPtr, args_::C.PyPtr, nargs::C.Py_ssi if in_f return pyjl_handle_error(f, self, exc) else - errset(pyJuliaError, pytuple((pyjlraw(exc), pyjlraw(catch_backtrace())))) + errset(pyJuliaError, pytuple((pyjlany(exc), pyjlany(catch_backtrace())))) return C.PyNULL end catch @@ -107,7 +107,7 @@ function pyjl_handle_error(f, self, exc) t = pyjl_handle_error_type(f, self, exc)::Py if pyisnull(t) # NULL => raise JuliaError - errset(pyJuliaError, pytuple((pyjlraw(exc), pyjlraw(catch_backtrace())))) + errset(pyJuliaError, pytuple((pyjlany(exc), pyjlany(catch_backtrace())))) return C.PyNULL elseif pyistype(t) # Exception type => raise this type of error diff --git a/src/JlWrap/module.jl b/src/JlWrap/module.jl deleted file mode 100644 index 02257658..00000000 --- a/src/JlWrap/module.jl +++ /dev/null @@ -1,30 +0,0 @@ -const pyjlmoduletype = pynew() - -function pyjlmodule_dir(self::Module) - ks = Symbol[] - append!(ks, names(self, all = true, imported = true)) - for m in ccall(:jl_module_usings, Any, (Any,), self)::Vector - append!(ks, names(m)) - end - pylist(pyjl_attr_jl2py(string(k)) for k in ks) -end - -function pyjlmodule_seval(self::Module, expr::Py) - Py(Base.eval(self, Meta.parseall(strip(pyconvert(String, expr))))) -end - -function init_module() - jl = pyjuliacallmodule - pybuiltins.exec(pybuiltins.compile(""" - $("\n"^(@__LINE__()-1)) - class ModuleValue(JlBase): - __slots__ = () - def __dir__(self): - return JlBase.__dir__(self) + self._jl_callmethod($(pyjl_methodnum(pyjlmodule_dir))) - def seval(self, expr): - return self._jl_callmethod($(pyjl_methodnum(pyjlmodule_seval)), expr) - """, @__FILE__(), "exec"), jl.__dict__) - pycopy!(pyjlmoduletype, jl.ModuleValue) -end - -pyjltype(::Module) = pyjlmoduletype diff --git a/src/JlWrap/raw.jl b/src/JlWrap/raw.jl deleted file mode 100644 index 1fd56330..00000000 --- a/src/JlWrap/raw.jl +++ /dev/null @@ -1,143 +0,0 @@ -const pyjlrawtype = pynew() - -pyjlraw_repr(self) = Py("<jl $(repr(self))>") - -pyjlraw_str(self) = Py(sprint(print, self)) - -pyjl_attr_py2jl(k::String) = replace(k, r"_[b]+$" => (x -> "!"^(length(x) - 1))) - -pyjl_attr_jl2py(k::String) = replace(k, r"!+$" => (x -> "_" * "b"^length(x))) - -function pyjlraw_getattr(self, k_::Py) - k = Symbol(pyjl_attr_py2jl(pyconvert(String, k_))) - pydel!(k_) - pyjlraw(getproperty(self, k)) -end - -function pyjlraw_setattr(self, k_::Py, v_::Py) - k = Symbol(pyjl_attr_py2jl(pyconvert(String, k_))) - pydel!(k_) - v = pyconvert(Any, v_) - setproperty!(self, k, v) - Py(nothing) -end - -pyjlraw_dir(self) = pylist(pyjl_attr_jl2py(string(k)) for k in propertynames(self, true)) - -function pyjlraw_call(self, args_::Py, kwargs_::Py) - if pylen(kwargs_) > 0 - args = pyconvert(Vector{Any}, args_) - kwargs = pyconvert(Dict{Symbol,Any}, kwargs_) - ans = pyjlraw(self(args...; kwargs...)) - elseif pylen(args_) > 0 - args = pyconvert(Vector{Any}, args_) - ans = pyjlraw(self(args...)) - else - ans = pyjlraw(self()) - end - pydel!(args_) - pydel!(kwargs_) - ans -end - -pyjlraw_len(self) = Py(length(self)) - -function pyjlraw_getitem(self, k_::Py) - if pyistuple(k_) - k = pyconvert(Vector{Any}, k_) - pydel!(k_) - pyjlraw(self[k...]) - else - k = pyconvert(Any, k_) - pyjlraw(self[k]) - end -end - -function pyjlraw_setitem(self, k_::Py, v_::Py) - v = pyconvert(Any, v_) - if pyistuple(k_) - k = pyconvert(Vector{Any}, k_) - pydel!(k_) - self[k...] = v - else - k = pyconvert(Any, k_) - self[k] = v - end - Py(nothing) -end - -function pyjlraw_delitem(self, k_::Py) - if pyistuple(k_) - k = pyconvert(Vector{Any}, k_) - pydel!(k_) - delete!(self, k...) - else - k = pyconvert(Any, k_) - delete!(self, k) - end - Py(nothing) -end - -pyjlraw_bool(self::Bool) = Py(self) -pyjlraw_bool(self) = (errset(pybuiltins.TypeError, "Only Julia 'Bool' can be tested for truthyness"); PyNULL) - -function init_raw() - jl = pyjuliacallmodule - pybuiltins.exec(pybuiltins.compile(""" - $("\n"^(@__LINE__()-1)) - class RawValue(JlBase): - __slots__ = () - def __repr__(self): - if self._jl_isnull(): - return "<jl NULL>" - else: - return self._jl_callmethod($(pyjl_methodnum(pyjlraw_repr))) - def __str__(self): - if self._jl_isnull(): - return "NULL" - else: - return self._jl_callmethod($(pyjl_methodnum(pyjlraw_str))) - def __getattr__(self, k): - if k.startswith("__") and k.endswith("__"): - raise AttributeError(k) - else: - return self._jl_callmethod($(pyjl_methodnum(pyjlraw_getattr)), k) - def __setattr__(self, k, v): - try: - JlBase.__setattr__(self, k, v) - except AttributeError: - if k.startswith("__") and k.endswith("__"): - raise - else: - return - self._jl_callmethod($(pyjl_methodnum(pyjlraw_setattr)), k, v) - def __dir__(self): - return JlBase.__dir__(self) + self._jl_callmethod($(pyjl_methodnum(pyjlraw_dir))) - def __call__(self, *args, **kwargs): - return self._jl_callmethod($(pyjl_methodnum(pyjlraw_call)), args, kwargs) - def __len__(self): - return self._jl_callmethod($(pyjl_methodnum(pyjlraw_len))) - def __getitem__(self, k): - return self._jl_callmethod($(pyjl_methodnum(pyjlraw_getitem)), k) - def __setitem__(self, k, v): - self._jl_callmethod($(pyjl_methodnum(pyjlraw_setitem)), k, v) - def __delitem__(self, k): - self._jl_callmethod($(pyjl_methodnum(pyjlraw_delitem)), k) - def __bool__(self): - return self._jl_callmethod($(pyjl_methodnum(pyjlraw_bool))) - def _jl_any(self): - return self._jl_callmethod($(pyjl_methodnum(pyjl))) - """, @__FILE__(), "exec"), jl.__dict__) - pycopy!(pyjlrawtype, jl.RawValue) -end - -""" - pyjlraw(v) - -Create a Python object wrapping the Julia object `x`. - -It has type `juliacall.RawValue`. This has a much more rigid "Julian" interface than `pyjl(v)`. -For example, accessing attributes or calling this object will always return a `RawValue`. -""" -pyjlraw(v) = pyjl(pyjlrawtype, v) -export pyjlraw diff --git a/src/JlWrap/type.jl b/src/JlWrap/type.jl deleted file mode 100644 index 050605e4..00000000 --- a/src/JlWrap/type.jl +++ /dev/null @@ -1,30 +0,0 @@ -const pyjltypetype = pynew() - -function pyjltype_getitem(self::Type, k_) - if pyistuple(k_) - k = pyconvert(Vector{Any}, k_) - pydel!(k_) - Py(self{k...}) - else - k = pyconvert(Any, k_) - Py(self{k}) - end -end - -function init_type() - jl = pyjuliacallmodule - pybuiltins.exec(pybuiltins.compile(""" - $("\n"^(@__LINE__()-1)) - class TypeValue(JlBase): - __slots__ = () - def __getitem__(self, k): - return self._jl_callmethod($(pyjl_methodnum(pyjltype_getitem)), k) - def __setitem__(self, k, v): - raise TypeError("not supported") - def __delitem__(self, k): - raise TypeError("not supported") - """, @__FILE__(), "exec"), jl.__dict__) - pycopy!(pyjltypetype, jl.TypeValue) -end - -pyjltype(::Type) = pyjltypetype diff --git a/src/PythonCall.jl b/src/PythonCall.jl index fdd1774f..36a605f4 100644 --- a/src/PythonCall.jl +++ b/src/PythonCall.jl @@ -38,7 +38,7 @@ for k in [:event_loop_on, :event_loop_off, :fix_qt_plugin_path] end # not API but used in tests -for k in [:pyjlanytype, :pyjlarraytype, :pyjlvectortype, :pyjlbinaryiotype, :pyjltextiotype, :pyjldicttype, :pyjlmoduletype, :pyjlintegertype, :pyjlrationaltype, :pyjlrealtype, :pyjlcomplextype, :pyjlsettype, :pyjltypetype] +for k in [:pyjlanytype, :pyjlarraytype, :pyjlvectortype, :pyjlbinaryiotype, :pyjltextiotype, :pyjldicttype, :pyjlintegertype, :pyjlrationaltype, :pyjlrealtype, :pyjlcomplextype, :pyjlsettype] @eval using .JlWrap: $k end From 35e24244a86122b01994ac8c8f70c8cb87c86774 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Sat, 4 May 2024 16:44:18 +0100 Subject: [PATCH 03/39] add JlBase.__init__ and store nothing/true/false specially --- src/JlWrap/C.jl | 116 +++++++++++++++++++++++++++++++++-------- src/JlWrap/any.jl | 10 +--- src/JlWrap/base.jl | 20 +------ src/JlWrap/callback.jl | 10 +--- 4 files changed, 98 insertions(+), 58 deletions(-) diff --git a/src/JlWrap/C.jl b/src/JlWrap/C.jl index a7a23db1..a4ac547b 100644 --- a/src/JlWrap/C.jl +++ b/src/JlWrap/C.jl @@ -2,6 +2,8 @@ module Cjl using ...C: C using ...Utils: Utils +using ...Core: incref, pynew +using ...Convert: pyconvert using Base: @kwdef using UnsafePointers: UnsafePtr using Serialization: serialize, deserialize @@ -30,7 +32,7 @@ end function _pyjl_dealloc(o::C.PyPtr) idx = UnsafePtr{PyJuliaValueObject}(o).value[] - if idx != 0 + if idx >= 1 PYJLVALUES[idx] = nothing push!(PYJLFREEVALUES, idx) end @@ -39,6 +41,59 @@ function _pyjl_dealloc(o::C.PyPtr) nothing end +function _getany(ptr::C.PyPtr) + if PyJuliaValue_Check(ptr) == 1 + PyJuliaValue_GetValue(ptr) + else + pyconvert(Any, pynew(incref(ptr))) + end +end + +function _getany(::Type{T}, ptr::C.PyPtr) where {T} + if PyJuliaValue_Check(ptr) == 1 + convert(T, PyJuliaValue_GetValue(ptr))::T + else + pyconvert(T, pynew(incref(ptr)))::T + end +end + +function _pyjl_init(xptr::C.PyPtr, argsptr::C.PyPtr, kwargsptr::C.PyPtr) + if kwargsptr != C.PyNULL && C.PyDict_Size(kwargsptr) != 0 + errset(pybuiltins.TypeError, "keyword arguments not allowed") + return Cint(-1) + end + if argsptr == C.PyNULL + return Cint(0) + end + nargs = C.PyTuple_Size(argsptr) + if nargs == 0 + return Cint(0) + elseif nargs > 2 + errset(pybuiltins.TypeError, "__init__() takes up to 2 arguments ($nargs given)") + return Cint(-1) + end + vptr = C.PyTuple_GetItem(argsptr, 0) + try + if nargs == 1 + v = _getany(vptr) + else + tptr = C.PyTuple_GetItem(argsptr, 1) + t = _getany(tptr) + if !isa(t, Type) + C.PyErr_SetString(C.POINTERS.PyExc_TypeError, "type argument must be a Julia 'Type', not '$(typeof(t))'") + return Cint(-1) + end + v = _getany(t, vptr) + end + PyJuliaValue_SetValue(xptr, v) + Cint(0) + catch exc + C.PyErr_SetString(C.POINTERS.PyExc_Exception, "error during __init__()") + @debug "exception" exc + Cint(-1) + end +end + const PYJLMETHODS = Vector{Any}() function PyJulia_MethodNum(f) @@ -47,12 +102,6 @@ function PyJulia_MethodNum(f) return length(PYJLMETHODS) end -function _pyjl_isnull(o::C.PyPtr, ::C.PyPtr) - ans = PyJuliaValue_IsNull(o) ? C.POINTERS._Py_TrueStruct : C.POINTERS._Py_FalseStruct - C.Py_IncRef(ans) - ans -end - function _pyjl_callmethod(o::C.PyPtr, args::C.PyPtr) nargs = C.PyTuple_Size(args) @assert nargs > 0 @@ -253,7 +302,6 @@ end const _pyjlbase_name = "juliacall.JlBase" const _pyjlbase_type = fill(C.PyTypeObject()) -const _pyjlbase_isnull_name = "_jl_isnull" const _pyjlbase_callmethod_name = "_jl_callmethod" const _pyjlbase_reduce_name = "__reduce__" const _pyjlbase_serialize_name = "_jl_serialize" @@ -269,11 +317,6 @@ function init_c() meth = @cfunction(_pyjl_callmethod, C.PyPtr, (C.PyPtr, C.PyPtr)), flags = C.Py_METH_VARARGS, ), - C.PyMethodDef( - name = pointer(_pyjlbase_isnull_name), - meth = @cfunction(_pyjl_isnull, C.PyPtr, (C.PyPtr, C.PyPtr)), - flags = C.Py_METH_NOARGS, - ), C.PyMethodDef( name = pointer(_pyjlbase_reduce_name), meth = @cfunction(_pyjl_reduce, C.PyPtr, (C.PyPtr, C.PyPtr)), @@ -298,13 +341,11 @@ function init_c() _pyjlbase_type[] = C.PyTypeObject( name = pointer(_pyjlbase_name), basicsize = sizeof(PyJuliaValueObject), - # new = C.POINTERS.PyType_GenericNew, new = @cfunction(_pyjl_new, C.PyPtr, (C.PyPtr, C.PyPtr, C.PyPtr)), dealloc = @cfunction(_pyjl_dealloc, Cvoid, (C.PyPtr,)), + init = @cfunction(_pyjl_init, Cint, (C.PyPtr, C.PyPtr, C.PyPtr)), flags = C.Py_TPFLAGS_BASETYPE | C.Py_TPFLAGS_HAVE_VERSION_TAG, weaklistoffset = fieldoffset(PyJuliaValueObject, 3), - # getattro = C.POINTERS.PyObject_GenericGetAttr, - # setattro = C.POINTERS.PyObject_GenericSetAttr, methods = pointer(_pyjlbase_methods), as_buffer = pointer(_pyjlbase_as_buffer), ) @@ -323,13 +364,44 @@ end PyJuliaValue_Check(o::C.PyPtr) = C.PyObject_IsInstance(o, PyJuliaBase_Type[]) -PyJuliaValue_IsNull(o::C.PyPtr) = UnsafePtr{PyJuliaValueObject}(o).value[] == 0 +function PyJuliaValue_GetValue(o::C.PyPtr) + v = UnsafePtr{PyJuliaValueObject}(o).value[] + if v == 0 + nothing + elseif v > 0 + PYJLVALUES[v] + elseif v == -1 + false + elseif v == -2 + true + end +end + +function PyJuliaValue_SetValue(o::C.PyPtr, v::Union{Nothing,Bool}) + idx = UnsafePtr{PyJuliaValueObject}(o).value[] + if idx >= 1 + PYJLVALUES[idx] = nothing + push!(PYJLFREEVALUES, idx) + end + if v === nothing + idx = 0 + elseif v === false + idx = -1 + elseif v === true + idx = -2 + else + @assert false + end + UnsafePtr{PyJuliaValueObject}(o).value[] = idx + nothing +end -PyJuliaValue_GetValue(o::C.PyPtr) = PYJLVALUES[UnsafePtr{PyJuliaValueObject}(o).value[]] -PyJuliaValue_SetValue(o::C.PyPtr, @nospecialize(v)) = begin +function PyJuliaValue_SetValue(o::C.PyPtr, @nospecialize(v)) idx = UnsafePtr{PyJuliaValueObject}(o).value[] - if idx == 0 + if idx >= 1 + PYJLVALUES[idx] = v + else if isempty(PYJLFREEVALUES) push!(PYJLVALUES, v) idx = length(PYJLVALUES) @@ -338,13 +410,11 @@ PyJuliaValue_SetValue(o::C.PyPtr, @nospecialize(v)) = begin PYJLVALUES[idx] = v end UnsafePtr{PyJuliaValueObject}(o).value[] = idx - else - PYJLVALUES[idx] = v end nothing end -PyJuliaValue_New(t::C.PyPtr, @nospecialize(v)) = begin +function PyJuliaValue_New(t::C.PyPtr, @nospecialize(v)) if C.PyType_IsSubtype(t, PyJuliaBase_Type[]) != 1 C.PyErr_SetString(C.POINTERS.PyExc_TypeError, "Expecting a subtype of 'juliacall.JlBase'") return C.PyNULL diff --git a/src/JlWrap/any.jl b/src/JlWrap/any.jl index 3ec88f86..91c33418 100644 --- a/src/JlWrap/any.jl +++ b/src/JlWrap/any.jl @@ -233,15 +233,9 @@ function init_any() class Jl(JlBase): __slots__ = () def __repr__(self): - if self._jl_isnull(): - return "<jl NULL>" - else: - return self._jl_callmethod($(pyjl_methodnum(pyjlany_repr))) + return self._jl_callmethod($(pyjl_methodnum(pyjlany_repr))) def __str__(self): - if self._jl_isnull(): - return "NULL" - else: - return self._jl_callmethod($(pyjl_methodnum(pyjlany_str))) + return self._jl_callmethod($(pyjl_methodnum(pyjlany_str))) def __getattr__(self, k): if k.startswith("__") and k.endswith("__"): raise AttributeError(k) diff --git a/src/JlWrap/base.jl b/src/JlWrap/base.jl index 0e6e8e46..63caf20f 100644 --- a/src/JlWrap/base.jl +++ b/src/JlWrap/base.jl @@ -14,26 +14,12 @@ Test whether `x` is a wrapped Julia value, namely an instance of `juliacall.JlBa pyisjl(x) = pytypecheck(x, pyjlbasetype) export pyisjl -pyjlisnull(x) = @autopy x begin - if pyisjl(x_) - Cjl.PyJuliaValue_IsNull(getptr(x_)) - else - error("Expecting a 'juliacall.JlBase', got a '$(pytype(x_).__name__)'") - end -end - """ pyjlvalue(x) Extract the value from the wrapped Julia value `x`. """ -pyjlvalue(x) = @autopy x begin - if pyjlisnull(x_) - error("Julia value is NULL") - else - _pyjl_getvalue(x_) - end -end +pyjlvalue(x) = @autopy x _pyjl_getvalue(x_) export pyjlvalue function init_base() @@ -49,10 +35,6 @@ pyconvert_rule_jlvalue(::Type{T}, x::Py) where {T} = pyconvert_tryconvert(T, _py function Cjl._pyjl_callmethod(f, self_::C.PyPtr, args_::C.PyPtr, nargs::C.Py_ssize_t) @nospecialize f - if Cjl.PyJuliaValue_IsNull(self_) - errset(pybuiltins.TypeError, "Julia object is NULL") - return C.PyNULL - end in_f = false self = Cjl.PyJuliaValue_GetValue(self_) try diff --git a/src/JlWrap/callback.jl b/src/JlWrap/callback.jl index 1c82b455..f018cc31 100644 --- a/src/JlWrap/callback.jl +++ b/src/JlWrap/callback.jl @@ -42,15 +42,9 @@ function init_callback() class CallbackValue(JlBase): __slots__ = () def __repr__(self): - if self._jl_isnull(): - return "<jl NULL>" - else: - return self._jl_callmethod($(pyjl_methodnum(pyjlcallback_repr))) + return self._jl_callmethod($(pyjl_methodnum(pyjlcallback_repr))) def __str__(self): - if self._jl_isnull(): - return "NULL" - else: - return self._jl_callmethod($(pyjl_methodnum(pyjlcallback_str))) + return self._jl_callmethod($(pyjl_methodnum(pyjlcallback_str))) def __call__(self, *args, **kwargs): return self._jl_callmethod($(pyjl_methodnum(pyjlcallback_call)), args, kwargs) """, @__FILE__(), "exec"), jl.__dict__) From a3b7d5b85ff3428cd02b1321eeba4c6a39bbede2 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Sat, 4 May 2024 17:20:41 +0100 Subject: [PATCH 04/39] more general numeric operators --- src/JlWrap/any.jl | 80 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 22 deletions(-) diff --git a/src/JlWrap/any.jl b/src/JlWrap/any.jl index 91c33418..e4db6cbe 100644 --- a/src/JlWrap/any.jl +++ b/src/JlWrap/any.jl @@ -119,26 +119,28 @@ pyjl_handle_error_type(::typeof(pyjlany_contains), self, exc) = exc isa MethodEr struct pyjlany_op{OP} op :: OP end -(op::pyjlany_op)(self) = Py(op.op(self)) +(op::pyjlany_op)(self) = pyjlany(op.op(self)) function (op::pyjlany_op)(self, other_::Py) if pyisjl(other_) other = pyjlvalue(other_) pydel!(other_) - pyjlany(op.op(self, other)) else - pybuiltins.NotImplemented + other = pyconvert(Any, other_) end + pyjlany(op.op(self, other)) end function (op::pyjlany_op)(self, other_::Py, other2_::Py) - if pyisjl(other_) && pyisjl(other2_) + if pyisjl(other_) other = pyjlvalue(other_) - other2 = pyjlvalue(other2_) pydel!(other_) - pydel!(other2_) - pyjlany(op.op(self, other, other2)) else - pybuiltins.NotImplemented + other = pyconvert(Any, other) end + if pyisjl(other2_) + other2 = pyjlvalue(other2_) + pydel!(other2_) + end + pyjlany(op.op(self, other, other2)) end pyjl_handle_error_type(op::pyjlany_op, self, exc) = exc isa MethodError && exc.f === op.op ? pybuiltins.TypeError : PyNULL @@ -149,21 +151,23 @@ function (op::pyjlany_rev_op)(self, other_::Py) if pyisjl(other_) other = pyjlvalue(other_) pydel!(other_) - pyjlany(op.op(other, self)) else - pybuiltins.NotImplemented + other = pyconvert(Any, other_) end + pyjlany(op.op(other, self)) end function (op::pyjlany_rev_op)(self, other_::Py, other2_::Py) - if pyisjl(other_) && pyisjl(other2_) + if pyisjl(other_) other = pyjlvalue(other_) - other2 = pyjlvalue(other2_) pydel!(other_) - pydel!(other2_) - pyjlany(op.op(other, self, other2)) else - pybuiltins.NotImplemented + other = pyconvert(Any, other) + end + if pyisjl(other2_) + other2 = pyjlvalue(other2_) + pydel!(other2_) end + pyjlany(op.op(other, self, other2)) end pyjl_handle_error_type(op::pyjlany_rev_op, self, exc) = exc isa MethodError && exc.f === op.op ? pybuiltins.TypeError : PyNULL @@ -215,16 +219,38 @@ function pyjlany_mimebundle(self, include::Py, exclude::Py) return ans end -function pyjlany_eval(self, expr::Py) - if self isa Module - Py(Base.eval(self, Meta.parseall(strip(pyconvert(String, expr))))) +pyjlany_eval(self::Module, expr::Py) = Py(Base.eval(self, Meta.parseall(strip(pyconvert(String, expr))))) + +pyjl_handle_error_type(::typeof(pyjlany_eval), self, exc) = exc isa MethodError ? pybuiltins.TypeError : PyNULL + +pyjlany_int(self) = pyint(convert(Integer, self)) + +pyjl_handle_error_type(::typeof(pyjlany_int), self, exc) = exc isa MethodError ? pybuiltins.TypeError : PyNULL + +pyjlany_float(self) = pyfloat(convert(AbstractFloat, self)) + +pyjl_handle_error_type(::typeof(pyjlany_float), self, exc) = exc isa MethodError ? pybuiltins.TypeError : PyNULL + +pyjlany_complex(self) = pycomplex(convert(Complex, self)) + +pyjl_handle_error_type(::typeof(pyjlany_complex), self, exc) = exc isa MethodError ? pybuiltins.TypeError : PyNULL + +function pyjlany_index(self) + if self isa Integer + pyint(self) else - throw(ArgumentError("self must be a Julia 'Module', got a '$(typeof(self))'")) + errset(pybuiltins.TypeError, "Only Julia 'Integer' values can be used as Python indices, not '$(typeof(self))'") end end -pyjl_handle_error_type(::typeof(pyjlany_eval), self, exc) = exc isa ArgumentError ? pybuiltins.TypeError : PyNULL - +function pyjlany_bool(self) + if self isa Bool + pybool(self) + else + errset(pybuiltins.TypeError, "Only Julia 'Bool' values can be tested for truthyness, not '$(typeof(self))'") + PyNULL + end +end function init_any() jl = pyjuliacallmodule @@ -255,7 +281,7 @@ function init_any() def __call__(self, *args, **kwargs): return self._jl_callmethod($(pyjl_methodnum(pyjlany_call)), args, kwargs) def __bool__(self): - return True + return self._jl_callmethod($(pyjl_methodnum(pyjlany_bool))) def __len__(self): return self._jl_callmethod($(pyjl_methodnum(pyjlany_op(length)))) def __getitem__(self, k): @@ -276,6 +302,8 @@ function init_any() return self._jl_callmethod($(pyjl_methodnum(pyjlany_op(-)))) def __abs__(self): return self._jl_callmethod($(pyjl_methodnum(pyjlany_op(abs)))) + def abs(self): + return self._jl_callmethod($(pyjl_methodnum(pyjlany_op(abs)))) def __invert__(self): return self._jl_callmethod($(pyjl_methodnum(pyjlany_op(~)))) def __add__(self, other): @@ -346,6 +374,14 @@ function init_any() return self._jl_callmethod($(pyjl_methodnum(pyjlany_op(>))), other) def __hash__(self): return self._jl_callmethod($(pyjl_methodnum(pyjlany_op(hash)))) + def __int__(self): + return self._jl_callmethod($(pyjl_methodnum(pyjlany_int))) + def __float__(self): + return self._jl_callmethod($(pyjl_methodnum(pyjlany_float))) + def __complex__(self): + return self._jl_callmethod($(pyjl_methodnum(pyjlany_complex))) + def __index__(self): + return self._jl_callmethod($(pyjl_methodnum(pyjlany_index))) @property def __name__(self): return self._jl_callmethod($(pyjl_methodnum(pyjlany_name))) From 7691d9972d78f71a358198425aafa95c1779af2a Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Sat, 4 May 2024 17:46:55 +0100 Subject: [PATCH 05/39] rounding methods, jl_to_py, remove jlwrap numbers --- src/JlWrap/JlWrap.jl | 2 - src/JlWrap/any.jl | 43 ++++++-- src/JlWrap/number.jl | 233 ------------------------------------------- 3 files changed, 35 insertions(+), 243 deletions(-) delete mode 100644 src/JlWrap/number.jl diff --git a/src/JlWrap/JlWrap.jl b/src/JlWrap/JlWrap.jl index f9b90534..6701e27a 100644 --- a/src/JlWrap/JlWrap.jl +++ b/src/JlWrap/JlWrap.jl @@ -20,7 +20,6 @@ include("base.jl") include("any.jl") include("iter.jl") include("io.jl") -include("number.jl") include("objectarray.jl") include("array.jl") include("vector.jl") @@ -34,7 +33,6 @@ function __init__() init_any() init_iter() init_io() - init_number() init_array() init_vector() init_dict() diff --git a/src/JlWrap/any.jl b/src/JlWrap/any.jl index e4db6cbe..b3a9fa42 100644 --- a/src/JlWrap/any.jl +++ b/src/JlWrap/any.jl @@ -220,26 +220,23 @@ function pyjlany_mimebundle(self, include::Py, exclude::Py) end pyjlany_eval(self::Module, expr::Py) = Py(Base.eval(self, Meta.parseall(strip(pyconvert(String, expr))))) - -pyjl_handle_error_type(::typeof(pyjlany_eval), self, exc) = exc isa MethodError ? pybuiltins.TypeError : PyNULL +pyjl_handle_error_type(::typeof(pyjlany_eval), self, exc::MethodError) = pybuiltins.TypeError pyjlany_int(self) = pyint(convert(Integer, self)) - -pyjl_handle_error_type(::typeof(pyjlany_int), self, exc) = exc isa MethodError ? pybuiltins.TypeError : PyNULL +pyjl_handle_error_type(::typeof(pyjlany_int), self, exc::MethodError) = pybuiltins.TypeError pyjlany_float(self) = pyfloat(convert(AbstractFloat, self)) - -pyjl_handle_error_type(::typeof(pyjlany_float), self, exc) = exc isa MethodError ? pybuiltins.TypeError : PyNULL +pyjl_handle_error_type(::typeof(pyjlany_float), self, exc::MethodError) = pybuiltins.TypeError pyjlany_complex(self) = pycomplex(convert(Complex, self)) - -pyjl_handle_error_type(::typeof(pyjlany_complex), self, exc) = exc isa MethodError ? pybuiltins.TypeError : PyNULL +pyjl_handle_error_type(::typeof(pyjlany_complex), self, exc::MethodError) = pybuiltins.TypeError function pyjlany_index(self) if self isa Integer pyint(self) else errset(pybuiltins.TypeError, "Only Julia 'Integer' values can be used as Python indices, not '$(typeof(self))'") + PyNULL end end @@ -252,6 +249,23 @@ function pyjlany_bool(self) end end +pyjlany_trunc(self) = pyint(trunc(Integer, self)) +pyjl_handle_error_type(::typeof(pyjlany_trunc), self, exc::MethodError) = pybuiltins.TypeError + +pyjlany_floor(self) = pyint(floor(Integer, self)) +pyjl_handle_error_type(::typeof(pyjlany_floor), self, exc::MethodError) = pybuiltins.TypeError + +pyjlany_ceil(self) = pyint(ceil(Integer, self)) +pyjl_handle_error_type(::typeof(pyjlany_ceil), self, exc::MethodError) = pybuiltins.TypeError + +pyjlany_round(self) = pyint(round(Integer, self)) +function pyjlany_round(self, ndigits_::Py) + ndigits = pyconvertarg(Int, ndigits_, "ndigits") + pydel!(ndigits_) + pyjlany(round(self; digits = ndigits)) +end +pyjl_handle_error_type(::typeof(pyjlany_round), self, exc::MethodError) = pybuiltins.TypeError + function init_any() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" @@ -382,6 +396,17 @@ function init_any() return self._jl_callmethod($(pyjl_methodnum(pyjlany_complex))) def __index__(self): return self._jl_callmethod($(pyjl_methodnum(pyjlany_index))) + def __trunc__(self): + return self._jl_callmethod($(pyjl_methodnum(pyjlany_trunc))) + def __floor__(self): + return self._jl_callmethod($(pyjl_methodnum(pyjlany_floor))) + def __ceil__(self): + return self._jl_callmethod($(pyjl_methodnum(pyjlany_ceil))) + def __round__(self, ndigits=None): + if ndigits is None: + return self._jl_callmethod($(pyjl_methodnum(pyjlany_round))) + else: + return self._jl_callmethod($(pyjl_methodnum(pyjlany_round)), ndigits) @property def __name__(self): return self._jl_callmethod($(pyjl_methodnum(pyjlany_name))) @@ -391,6 +416,8 @@ function init_any() return self._jl_callmethod($(pyjl_methodnum(pyjlany_help)), mime) def jl_eval(self, expr): return self._jl_callmethod($(pyjl_methodnum(pyjlany_eval)), expr) + def jl_to_py(self): + return self._jl_callmethod($(pyjl_methodnum(Py))) def _repr_mimebundle_(self, include=None, exclude=None): return self._jl_callmethod($(pyjl_methodnum(pyjlany_mimebundle)), include, exclude) """, @__FILE__(), "exec"), jl.__dict__) diff --git a/src/JlWrap/number.jl b/src/JlWrap/number.jl deleted file mode 100644 index 0f43c64a..00000000 --- a/src/JlWrap/number.jl +++ /dev/null @@ -1,233 +0,0 @@ -const pyjlnumbertype = pynew() -const pyjlcomplextype = pynew() -const pyjlrealtype = pynew() -const pyjlrationaltype = pynew() -const pyjlintegertype = pynew() - -struct pyjlnumber_op{OP} - op :: OP -end -(op::pyjlnumber_op)(self) = Py(op.op(self)) -function (op::pyjlnumber_op)(self, other_::Py) - if pyisjl(other_) - other = pyjlvalue(other_) - pydel!(other_) - else - other = @pyconvert(Number, other_, return pybuiltins.NotImplemented) - end - Py(op.op(self, other)) -end -function (op::pyjlnumber_op)(self, other_::Py, other2_::Py) - if pyisjl(other_) - other = pyjlvalue(other_) - pydel!(other_) - else - other = @pyconvert(Number, other_, return pybuiltins.NotImplemented) - end - if pyisjl(other2_) - other2 = pyjlvalue(other2_) - pydel!(other2_) - else - other2 = @pyconvert(Number, other2_, return pybuiltins.NotImplemented) - end - Py(op.op(self, other, other2)) -end -pyjl_handle_error_type(op::pyjlnumber_op, self, exc) = exc isa MethodError && exc.f === op.op ? pybuiltins.TypeError : PyNULL - -struct pyjlnumber_rev_op{OP} - op :: OP -end -function (op::pyjlnumber_rev_op)(self, other_::Py) - if pyisjl(other_) - other = pyjlvalue(other_) - pydel!(other_) - else - other = @pyconvert(Number, other_, return pybuiltins.NotImplemented) - end - Py(op.op(other, self)) -end -function (op::pyjlnumber_rev_op)(self, other_::Py, other2_::Py) - if pyisjl(other_) - other = pyjlvalue(other_) - pydel!(other_) - else - other = @pyconvert(Number, other_, return pybuiltins.NotImplemented) - end - if pyisjl(other2_) - other2 = pyjlvalue(other2_) - pydel!(other2_) - else - other2 = @pyconvert(Number, other2_, return pybuiltins.NotImplemented) - end - Py(op.op(other, self, other2)) -end -pyjl_handle_error_type(op::pyjlnumber_rev_op, self, exc) = exc isa MethodError && exc.f === op.op ? pybuiltins.TypeError : PyNULL - -pyjlreal_trunc(self::Real) = Py(trunc(Integer, self)) -pyjl_handle_error_type(::typeof(pyjlreal_trunc), self, exc::MethodError) = exc.f === trunc ? pybuiltins.TypeError : PyNULL - -pyjlreal_floor(self::Real) = Py(floor(Integer, self)) -pyjl_handle_error_type(::typeof(pyjlreal_floor), self, exc::MethodError) = exc.f === floor ? pybuiltins.TypeError : PyNULL - -pyjlreal_ceil(self::Real) = Py(ceil(Integer, self)) -pyjl_handle_error_type(::typeof(pyjlreal_ceil), self, exc::MethodError) = exc.f === ceil ? pybuiltins.TypeError : PyNULL - -function pyjlreal_round(self::Real, ndigits_::Py) - ndigits = pyconvertarg(Union{Int,Nothing}, ndigits_, "ndigits") - pydel!(ndigits_) - if ndigits === nothing - Py(round(Integer, self)) - else - Py(round(self; digits = ndigits)) - end -end -pyjl_handle_error_type(::typeof(pyjlreal_round), self, exc::MethodError) = exc.f === round ? pybuiltins.TypeError : PyNULL - -function init_number() - jl = pyjuliacallmodule - pybuiltins.exec(pybuiltins.compile(""" - $("\n"^(@__LINE__()-1)) - class NumberValue(JlBase): - __slots__ = () - def __bool__(self): - return not self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(iszero)))) - def __add__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(+))), other) - def __sub__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(-))), other) - def __mul__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(*))), other) - def __truediv__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(/))), other) - def __floordiv__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(÷))), other) - def __mod__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(%))), other) - def __pow__(self, other, modulo=None): - if modulo is None: - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(^))), other) - else: - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(powermod))), other, modulo) - def __lshift__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(<<))), other) - def __rshift__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(>>))), other) - def __and__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(&))), other) - def __xor__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(⊻))), other) - def __or__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(|))), other) - def __radd__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_rev_op(+))), other) - def __rsub__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_rev_op(-))), other) - def __rmul__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_rev_op(*))), other) - def __rtruediv__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_rev_op(/))), other) - def __rfloordiv__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_rev_op(÷))), other) - def __rmod__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_rev_op(%))), other) - def __rpow__(self, other, modulo=None): - if modulo is None: - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_rev_op(^))), other) - else: - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_rev_op(powermod))), other, modulo) - def __rlshift__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_rev_op(<<))), other) - def __rrshift__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_rev_op(>>))), other) - def __rand__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_rev_op(&))), other) - def __rxor__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_rev_op(⊻))), other) - def __ror__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_rev_op(|))), other) - def __eq__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(==))), other) - def __ne__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(!=))), other) - def __le__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(≤))), other) - def __lt__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(<))), other) - def __ge__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(≥))), other) - def __gt__(self, other): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(>))), other) - class ComplexValue(NumberValue): - __slots__ = () - def __complex__(self): - return self._jl_callmethod($(pyjl_methodnum(pycomplex))) - @property - def real(self): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(real)))) - @property - def imag(self): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(imag)))) - def conjugate(self): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(conj)))) - class RealValue(ComplexValue): - __slots__ = () - def __float__(self): - return self._jl_callmethod($(pyjl_methodnum(pyfloat))) - @property - def real(self): - return self - @property - def imag(self): - return 0 - def conjugate(self): - return self - def __complex__(self): - return complex(float(self)) - def __trunc__(self): - return self._jl_callmethod($(pyjl_methodnum(pyjlreal_trunc))) - def __floor__(self): - return self._jl_callmethod($(pyjl_methodnum(pyjlreal_floor))) - def __ceil__(self): - return self._jl_callmethod($(pyjl_methodnum(pyjlreal_ceil))) - def __round__(self, ndigits=None): - return self._jl_callmethod($(pyjl_methodnum(pyjlreal_round)), ndigits) - class RationalValue(RealValue): - __slots__ = () - @property - def numerator(self): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(numerator)))) - @property - def denominator(self): - return self._jl_callmethod($(pyjl_methodnum(pyjlnumber_op(denominator)))) - class IntegerValue(RationalValue): - __slots__ = () - def __int__(self): - return self._jl_callmethod($(pyjl_methodnum(pyint))) - def __index__(self): - return self.__int__() - @property - def numerator(self): - return self - @property - def denominator(self): - return 1 - import numbers - numbers.Number.register(NumberValue) - numbers.Complex.register(ComplexValue) - numbers.Real.register(RealValue) - numbers.Rational.register(RationalValue) - numbers.Integral.register(IntegerValue) - del numbers - """, @__FILE__(), "exec"), jl.__dict__) - pycopy!(pyjlnumbertype, jl.NumberValue) - pycopy!(pyjlcomplextype, jl.ComplexValue) - pycopy!(pyjlrealtype, jl.RealValue) - pycopy!(pyjlrationaltype, jl.RationalValue) - pycopy!(pyjlintegertype, jl.IntegerValue) -end - -pyjltype(::Number) = pyjlnumbertype -pyjltype(::Complex) = pyjlcomplextype -pyjltype(::Real) = pyjlrealtype -pyjltype(::Rational) = pyjlrationaltype -pyjltype(::Integer) = pyjlintegertype From 6b6d3dee74e20354b39fedbd070d366d85cb8d94 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Sat, 4 May 2024 17:48:20 +0100 Subject: [PATCH 06/39] update release notes --- docs/src/releasenotes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/releasenotes.md b/docs/src/releasenotes.md index 2e2a14c8..77158e67 100644 --- a/docs/src/releasenotes.md +++ b/docs/src/releasenotes.md @@ -4,7 +4,7 @@ * `PythonCall.GC` is now more like `Base.GC`: `enable(true)` replaces `enable()`, `enable(false)` replaces `disable()`, and `gc()` is added. * Breaking changes to Julia wrapper types: * Classes renamed: `ValueBase` to `JlBase`, `AnyValue` to `Jl`, `ArrayValue` to `JlArray`, etc. - * Classes removed: `RawValue`, `ModuleValue`, `TypeValue`. + * Classes removed: `RawValue`, `ModuleValue`, `TypeValue`, `NumberValue`, `ComplexValue`, `RealValue`, `RationalValue`, `IntegerValue`. * `Jl` now behaves similar to how `RawValue` behaved before. In particular, most methods on `Jl` now return a `Jl` instead of an arbitrary Python object. * `juliacall.Pkg` removed. * Methods renamed: `_jl_display()` to `jl_display()`, `_jl_help()` to `jl_help()`, etc. From bfc84e7961e2a1510780c79112fe23414bb95014 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Sat, 4 May 2024 17:56:31 +0100 Subject: [PATCH 07/39] remove references to nonexistent types --- src/PythonCall.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PythonCall.jl b/src/PythonCall.jl index 36a605f4..f904b63b 100644 --- a/src/PythonCall.jl +++ b/src/PythonCall.jl @@ -38,7 +38,7 @@ for k in [:event_loop_on, :event_loop_off, :fix_qt_plugin_path] end # not API but used in tests -for k in [:pyjlanytype, :pyjlarraytype, :pyjlvectortype, :pyjlbinaryiotype, :pyjltextiotype, :pyjldicttype, :pyjlintegertype, :pyjlrationaltype, :pyjlrealtype, :pyjlcomplextype, :pyjlsettype] +for k in [:pyjlanytype, :pyjlarraytype, :pyjlvectortype, :pyjlbinaryiotype, :pyjltextiotype, :pyjldicttype, :pyjlsettype] @eval using .JlWrap: $k end From 6b47a0eb79443baa13ab4a706d76ab6c983d94a9 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Sat, 4 May 2024 17:56:41 +0100 Subject: [PATCH 08/39] add jl_callback --- src/JlWrap/any.jl | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/JlWrap/any.jl b/src/JlWrap/any.jl index b3a9fa42..c6859ce9 100644 --- a/src/JlWrap/any.jl +++ b/src/JlWrap/any.jl @@ -63,6 +63,23 @@ function pyjlany_call(self, args_::Py, kwargs_::Py) end pyjl_handle_error_type(::typeof(pyjlany_call), self, exc) = exc isa MethodError && exc.f === self ? pybuiltins.TypeError : PyNULL +function pyjlany_callback(self, args_::Py, kwargs_::Py) + if pylen(kwargs_) > 0 + args = pyconvert(Vector{Py}, args_) + kwargs = pyconvert(Dict{Symbol,Py}, kwargs_) + ans = Py(self(args...; kwargs...)) + elseif pylen(args_) > 0 + args = pyconvert(Vector{Py}, args_) + ans = Py(self(args...)) + else + ans = Py(self()) + end + pydel!(args_) + pydel!(kwargs_) + ans +end +pyjl_handle_error_type(::typeof(pyjlany_callback), self, exc::MethodError) = exc.f === self ? pybuiltins.TypeError : PyNULL + function pyjlany_getitem(self, k_::Py) if self isa Type if pyistuple(k_) @@ -418,6 +435,8 @@ function init_any() return self._jl_callmethod($(pyjl_methodnum(pyjlany_eval)), expr) def jl_to_py(self): return self._jl_callmethod($(pyjl_methodnum(Py))) + def jl_callback(self, *args, **kwargs): + return self._jl_callmethod($(pyjl_methodnum(pyjlany_callback)), args, kwargs) def _repr_mimebundle_(self, include=None, exclude=None): return self._jl_callmethod($(pyjl_methodnum(pyjlany_mimebundle)), include, exclude) """, @__FILE__(), "exec"), jl.__dict__) From f34317d44c99af31d61e728261d0a8dae32a4e13 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Sat, 4 May 2024 18:08:13 +0100 Subject: [PATCH 09/39] simpler iteration --- src/JlWrap/JlWrap.jl | 2 -- src/JlWrap/any.jl | 44 +++++++++++++++++++++++++++++++++++++++++++- src/JlWrap/iter.jl | 42 ------------------------------------------ 3 files changed, 43 insertions(+), 45 deletions(-) delete mode 100644 src/JlWrap/iter.jl diff --git a/src/JlWrap/JlWrap.jl b/src/JlWrap/JlWrap.jl index 6701e27a..ee814967 100644 --- a/src/JlWrap/JlWrap.jl +++ b/src/JlWrap/JlWrap.jl @@ -18,7 +18,6 @@ import ..Core: Py include("C.jl") include("base.jl") include("any.jl") -include("iter.jl") include("io.jl") include("objectarray.jl") include("array.jl") @@ -31,7 +30,6 @@ function __init__() Cjl.C.with_gil() do init_base() init_any() - init_iter() init_io() init_array() init_vector() diff --git a/src/JlWrap/any.jl b/src/JlWrap/any.jl index c6859ce9..9aeebe9e 100644 --- a/src/JlWrap/any.jl +++ b/src/JlWrap/any.jl @@ -283,6 +283,46 @@ function pyjlany_round(self, ndigits_::Py) end pyjl_handle_error_type(::typeof(pyjlany_round), self, exc::MethodError) = pybuiltins.TypeError +mutable struct Iterator + value::Any + state::Any + started::Bool + finished::Bool +end + +Iterator(x) = Iterator(x, nothing, false, false) +Iterator(x::Iterator) = x + +function Base.iterate(x::Iterator, ::Nothing=nothing) + if x.finished + s = nothing + elseif x.started + s = iterate(x.value, x.state) + else + s = iterate(x.value) + end + if s === nothing + x.finished = true + nothing + else + x.started = true + x.state = s[2] + (s[1], nothing) + end +end + +function pyjlany_next(self) + s = iterate(self) + if s === nothing + errset(pybuiltins.StopIteration) + PyNULL + else + pyjlany(s[1]) + end +end + +pyjlany_hash(self) = pyint(hash(self)) + function init_any() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" @@ -323,6 +363,8 @@ function init_any() self._jl_callmethod($(pyjl_methodnum(pyjlany_delitem)), k) def __iter__(self): return self._jl_callmethod($(pyjl_methodnum(pyjlany_op(Iterator)))) + def __next__(self): + return self._jl_callmethod($(pyjl_methodnum(pyjlany_next))) def __reversed__(self): return self._jl_callmethod($(pyjl_methodnum(pyjlany_op(reverse)))) def __contains__(self, v): @@ -404,7 +446,7 @@ function init_any() def __gt__(self, other): return self._jl_callmethod($(pyjl_methodnum(pyjlany_op(>))), other) def __hash__(self): - return self._jl_callmethod($(pyjl_methodnum(pyjlany_op(hash)))) + return self._jl_callmethod($(pyjl_methodnum(pyjlany_hash))) def __int__(self): return self._jl_callmethod($(pyjl_methodnum(pyjlany_int))) def __float__(self): diff --git a/src/JlWrap/iter.jl b/src/JlWrap/iter.jl deleted file mode 100644 index 499afcf7..00000000 --- a/src/JlWrap/iter.jl +++ /dev/null @@ -1,42 +0,0 @@ -mutable struct Iterator - val::Any - st::Any -end -Iterator(x) = Iterator(x, nothing) -Base.length(x::Iterator) = length(x.val) - -const pyjlitertype = pynew() - -function pyjliter_next(self::Iterator) - val = self.val - st = self.st - if st === nothing - z = iterate(val) - else - z = iterate(val, something(st)) - end - if z === nothing - errset(pybuiltins.StopIteration) - PyNULL - else - r, newst = z - self.st = Some(newst) - Py(r) - end -end - -function init_iter() - jl = pyjuliacallmodule - pybuiltins.exec(pybuiltins.compile(""" - $("\n"^(@__LINE__()-1)) - class IteratorValue(JlBase): - __slots__ = () - def __iter__(self): - return self - def __next__(self): - return self._jl_callmethod($(pyjl_methodnum(pyjliter_next))) - """, @__FILE__(), "exec"), jl.__dict__) - pycopy!(pyjlitertype, jl.IteratorValue) -end - -pyjltype(::Iterator) = pyjlitertype From a96820f200f45a6db6e94b07ec50c2bb736d5952 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Sun, 5 May 2024 09:17:16 +0100 Subject: [PATCH 10/39] simplify callbacks (pyfunc etc) --- src/JlWrap/JlWrap.jl | 1 - src/JlWrap/callback.jl | 82 ++++++++++-------------------------------- 2 files changed, 19 insertions(+), 64 deletions(-) diff --git a/src/JlWrap/JlWrap.jl b/src/JlWrap/JlWrap.jl index ee814967..d79885ba 100644 --- a/src/JlWrap/JlWrap.jl +++ b/src/JlWrap/JlWrap.jl @@ -35,7 +35,6 @@ function __init__() init_vector() init_dict() init_set() - init_callback() # add packages to juliacall jl = pyjuliacallmodule jl.Core = Base.Core diff --git a/src/JlWrap/callback.jl b/src/JlWrap/callback.jl index f018cc31..2070a57c 100644 --- a/src/JlWrap/callback.jl +++ b/src/JlWrap/callback.jl @@ -1,58 +1,6 @@ const pywrapcallback = pynew() -const pyjlcallbacktype = pynew() -pyjlcallback_repr(self) = Py("<jl $(repr(self))>") - -pyjlcallback_str(self) = Py(sprint(print, self)) - -function pyjlcallback_call(self, args_::Py, kwargs_::Py) - if pylen(kwargs_) > 0 - args = pyconvert(Vector{Py}, args_) - kwargs = pyconvert(Dict{Symbol,Py}, kwargs_) - ans = Py(self(args...; kwargs...)) - elseif (nargs = pylen(args_)) > 0 - args = pyconvert(Vector{Py}, args_) - @assert length(args) == nargs - if nargs == 1 - ans = Py(self(args[1])) - elseif nargs == 2 - ans = Py(self(args[1], args[2])) - elseif nargs == 3 - ans = Py(self(args[1], args[2], args[3])) - elseif nargs == 4 - ans = Py(self(args[1], args[2], args[3], args[4])) - elseif nargs == 5 - ans = Py(self(args[1], args[2], args[3], args[4], args[5])) - else - ans = Py(self(args...)) - end - else - ans = Py(self()) - end - pydel!(args_) - pydel!(kwargs_) - ans -end -pyjl_handle_error_type(::typeof(pyjlcallback_call), self, exc::MethodError) = exc.f === self ? pybuiltins.TypeError : PyNULL - -function init_callback() - jl = pyjuliacallmodule - pybuiltins.exec(pybuiltins.compile(""" - $("\n"^(@__LINE__()-1)) - class CallbackValue(JlBase): - __slots__ = () - def __repr__(self): - return self._jl_callmethod($(pyjl_methodnum(pyjlcallback_repr))) - def __str__(self): - return self._jl_callmethod($(pyjl_methodnum(pyjlcallback_str))) - def __call__(self, *args, **kwargs): - return self._jl_callmethod($(pyjl_methodnum(pyjlcallback_call)), args, kwargs) - """, @__FILE__(), "exec"), jl.__dict__) - pycopy!(pyjlcallbacktype, jl.CallbackValue) - pycopy!(pywrapcallback, pybuiltins.eval("lambda f: lambda *args, **kwargs: f(*args, **kwargs)", pydict())) -end - -pyjlcallback(f) = pyjl(pyjlcallbacktype, f) +pyjlcallback(f) = pyjlany(f).jl_callback """ pyfunc(f; [name], [qualname], [doc], [signature]) @@ -60,25 +8,33 @@ pyjlcallback(f) = pyjl(pyjlcallbacktype, f) Wrap the callable `f` as an ordinary Python function. The name, qualname, docstring or signature can optionally be set with `name`, `qualname`, -`doc` or `signature`. +`doc` or `signature`. If either of `name` or `qualname` are given, the other is inferred. Unlike `Py(f)` (or `pyjl(f)`), the arguments passed to `f` are always of type `Py`, i.e. they are never converted. """ -function pyfunc(f; name=nothing, qualname=name, doc=nothing, signature=nothing, wrap=pywrapcallback) +function pyfunc(f; name=nothing, qualname=nothing, doc=nothing, signature=nothing, wrap=pywrapcallback) f2 = ispy(f) ? f : pyjlcallback(f) if wrap isa Pair wrapargs, wrapfunc = wrap else wrapargs, wrapfunc = (), wrap end + if wrapfunc === pywrapcallback && pyisnull(pywrapcallback) + pycopy!(pywrapcallback, pybuiltins.eval("lambda f: lambda *args, **kwargs: f(*args, **kwargs)", pydict())) + end if wrapfunc isa AbstractString f3 = pybuiltins.eval(wrapfunc, pydict())(f2, wrapargs...) else f3 = wrapfunc(f2, wrapargs...) end + if name === nothing && qualname !== nothing + name = split(qualname, '.')[end] + elseif name !== nothing && qualname === nothing + qualname = name + end f3.__name__ = name === nothing ? "<lambda>" : name - f3.__qualname__ = name === nothing ? "<lambda>" : qualname + f3.__qualname__ = qualname === nothing ? "<lambda>" : qualname if doc !== nothing f3.__doc__ = doc end @@ -114,8 +70,8 @@ pystaticmethod(f; kw...) = pybuiltins.staticmethod(ispy(f) ? f : pyfunc(f; kw... export pystaticmethod """ - pyproperty(; get=nothing, set=nothing, del=nothing, doc=nothing) - pyproperty(get) + pyproperty(; get=nothing, set=nothing, del=nothing, doc=nothing, ...) + pyproperty(get, set=nothing, del=nothing; doc=nothing, ...) Create a Python `property` with the given getter, setter and deleter. @@ -123,12 +79,12 @@ If `get`, `set` or `del` is not a Python object (e.g. if it is a `Function`) the converted to one with [`pyfunc`](@ref PythonCall.pyfunc). In particular this means the arguments passed to it are always of type `Py`. """ -pyproperty(; get=nothing, set=nothing, del=nothing, doc=nothing) = +pyproperty(; get=nothing, set=nothing, del=nothing, doc=nothing, kw...) = pybuiltins.property( - fget = ispy(get) || get === nothing ? get : pyfunc(get), - fset = ispy(set) || set === nothing ? set : pyfunc(set), - fdel = ispy(del) || del === nothing ? del : pyfunc(del), + fget = ispy(get) || get === nothing ? get : pyfunc(get; kw...), + fset = ispy(set) || set === nothing ? set : pyfunc(set; kw...), + fdel = ispy(del) || del === nothing ? del : pyfunc(del; kw...), doc = doc, ) -pyproperty(get) = pyproperty(get=get) +pyproperty(get, set=nothing, del=nothing; doc=nothing, kw...) = pyproperty(; get=get, set=set, del=del, doc=doc, kw...) export pyproperty From 4931e79c42bcff311fb481a1940538e432a189ea Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Sun, 5 May 2024 09:59:23 +0100 Subject: [PATCH 11/39] change to pyjl(x) --- docs/src/conversion-to-python.md | 8 ----- docs/src/pythoncall-reference.md | 4 ++- docs/src/releasenotes.md | 8 +++-- pysrc/juliacall/__init__.py | 9 ----- src/JlWrap/any.jl | 57 ++++++++++---------------------- src/JlWrap/array.jl | 19 +++++++++-- src/JlWrap/base.jl | 4 +-- src/JlWrap/callback.jl | 2 +- src/JlWrap/dict.jl | 10 +++++- src/JlWrap/io.jl | 2 +- src/JlWrap/set.jl | 10 +++++- src/JlWrap/vector.jl | 2 +- 12 files changed, 66 insertions(+), 69 deletions(-) diff --git a/docs/src/conversion-to-python.md b/docs/src/conversion-to-python.md index d95ad269..ac121518 100644 --- a/docs/src/conversion-to-python.md +++ b/docs/src/conversion-to-python.md @@ -44,11 +44,3 @@ object, then also define `ispy(::T) = true`. ```@docs PythonCall.ispy ``` - -Alternatively, if you define a wrapper type (a subtype of -[`juliacall.Jl`](#juliacall.Jl)) then you may instead define `pyjltype(::T)` to -be that type. - -```@docs -PythonCall.pyjltype -``` diff --git a/docs/src/pythoncall-reference.md b/docs/src/pythoncall-reference.md index 75065f50..e0a42d4b 100644 --- a/docs/src/pythoncall-reference.md +++ b/docs/src/pythoncall-reference.md @@ -88,11 +88,13 @@ conversion to Python, unless the value is immutable and has a corresponding Pyth ```@docs pyjl -pyjlraw pyisjl pyjlvalue pybinaryio pytextio +pyjlarray +pyjlset +pyjldict ``` ## Arithmetic diff --git a/docs/src/releasenotes.md b/docs/src/releasenotes.md index 77158e67..55526456 100644 --- a/docs/src/releasenotes.md +++ b/docs/src/releasenotes.md @@ -6,9 +6,13 @@ * Classes renamed: `ValueBase` to `JlBase`, `AnyValue` to `Jl`, `ArrayValue` to `JlArray`, etc. * Classes removed: `RawValue`, `ModuleValue`, `TypeValue`, `NumberValue`, `ComplexValue`, `RealValue`, `RationalValue`, `IntegerValue`. * `Jl` now behaves similar to how `RawValue` behaved before. In particular, most methods on `Jl` now return a `Jl` instead of an arbitrary Python object. - * `juliacall.Pkg` removed. + * `juliacall.Pkg` removed (you can import it yourself). + * `juliacall.convert` removed (use `juliacall.Jl` instead). * Methods renamed: `_jl_display()` to `jl_display()`, `_jl_help()` to `jl_help()`, etc. - * Methods removed: `_jl_raw()` + * Methods removed: `_jl_raw()`. + * `pyjl(x)` now always returns a `juliacall.Jl` (it used to select a wrapper type if possible). + * `pyjltype(x)` removed. +* New functions: `pyjlarray`, `pyjldict`, `pyjlset`. ## 0.9.20 (2024-05-01) * The IPython extension is now automatically loaded upon import if IPython is detected. diff --git a/pysrc/juliacall/__init__.py b/pysrc/juliacall/__init__.py index 66bc34e5..286ce5b9 100644 --- a/pysrc/juliacall/__init__.py +++ b/pysrc/juliacall/__init__.py @@ -12,15 +12,6 @@ def newmodule(name): _newmodule = Main.seval("name -> (n1=Symbol(name); n2=gensym(n1); Main.@eval(module $n2; module $n1; end; end); Main.@eval $n2.$n1)") return _newmodule(name) -_convert = None - -def convert(T, x): - "Convert x to a Julia T." - global _convert - if _convert is None: - _convert = PythonCall.JlWrap.seval("pyjlcallback((T,x)->pyjl(pyconvert(pyjlvalue(T)::Type,x)))") - return _convert(T, x) - def interactive(enable=True): "Allow the Julia event loop to run in the background of the Python REPL." if enable: diff --git a/src/JlWrap/any.jl b/src/JlWrap/any.jl index 9aeebe9e..1b5390cb 100644 --- a/src/JlWrap/any.jl +++ b/src/JlWrap/any.jl @@ -1,7 +1,5 @@ const pyjlanytype = pynew() -pyjlany(x) = pyjl(pyjlanytype, x) - # pyjlany_repr(self) = Py("<jl $(repr(self))>") function pyjlany_repr(self) str = repr(MIME("text/plain"), self; context=IOContext(devnull, :limit=>true, :displaysize=>(23,80))) @@ -20,7 +18,7 @@ pyjl_attr_jl2py(k::String) = replace(k, r"!+$" => (x -> "_" * "b"^length(x))) function pyjlany_getattr(self, k_::Py) k = Symbol(pyjl_attr_py2jl(pyconvert(String, k_))) pydel!(k_) - pyjlany(getproperty(self, k)) + pyjl(getproperty(self, k)) end pyjl_handle_error_type(::typeof(pyjlany_getattr), self, exc) = pybuiltins.AttributeError @@ -50,12 +48,12 @@ function pyjlany_call(self, args_::Py, kwargs_::Py) if pylen(kwargs_) > 0 args = pyconvert(Vector{Any}, args_) kwargs = pyconvert(Dict{Symbol,Any}, kwargs_) - ans = pyjlany(self(args...; kwargs...)) + ans = pyjl(self(args...; kwargs...)) elseif pylen(args_) > 0 args = pyconvert(Vector{Any}, args_) - ans = pyjlany(self(args...)) + ans = pyjl(self(args...)) else - ans = pyjlany(self()) + ans = pyjl(self()) end pydel!(args_) pydel!(kwargs_) @@ -85,19 +83,19 @@ function pyjlany_getitem(self, k_::Py) if pyistuple(k_) k = pyconvert(Vector{Any}, k_) pydel!(k_) - pyjlany(self{k...}) + pyjl(self{k...}) else k = pyconvert(Any, k_) - pyjlany(self{k}) + pyjl(self{k}) end else if pyistuple(k_) k = pyconvert(Vector{Any}, k_) pydel!(k_) - pyjlany(self[k...]) + pyjl(self[k...]) else k = pyconvert(Any, k_) - pyjlany(self[k]) + pyjl(self[k]) end end end @@ -136,7 +134,7 @@ pyjl_handle_error_type(::typeof(pyjlany_contains), self, exc) = exc isa MethodEr struct pyjlany_op{OP} op :: OP end -(op::pyjlany_op)(self) = pyjlany(op.op(self)) +(op::pyjlany_op)(self) = pyjl(op.op(self)) function (op::pyjlany_op)(self, other_::Py) if pyisjl(other_) other = pyjlvalue(other_) @@ -144,7 +142,7 @@ function (op::pyjlany_op)(self, other_::Py) else other = pyconvert(Any, other_) end - pyjlany(op.op(self, other)) + pyjl(op.op(self, other)) end function (op::pyjlany_op)(self, other_::Py, other2_::Py) if pyisjl(other_) @@ -157,7 +155,7 @@ function (op::pyjlany_op)(self, other_::Py, other2_::Py) other2 = pyjlvalue(other2_) pydel!(other2_) end - pyjlany(op.op(self, other, other2)) + pyjl(op.op(self, other, other2)) end pyjl_handle_error_type(op::pyjlany_op, self, exc) = exc isa MethodError && exc.f === op.op ? pybuiltins.TypeError : PyNULL @@ -171,7 +169,7 @@ function (op::pyjlany_rev_op)(self, other_::Py) else other = pyconvert(Any, other_) end - pyjlany(op.op(other, self)) + pyjl(op.op(other, self)) end function (op::pyjlany_rev_op)(self, other_::Py, other2_::Py) if pyisjl(other_) @@ -184,7 +182,7 @@ function (op::pyjlany_rev_op)(self, other_::Py, other2_::Py) other2 = pyjlvalue(other2_) pydel!(other2_) end - pyjlany(op.op(other, self, other2)) + pyjl(op.op(other, self, other2)) end pyjl_handle_error_type(op::pyjlany_rev_op, self, exc) = exc isa MethodError && exc.f === op.op ? pybuiltins.TypeError : PyNULL @@ -279,7 +277,7 @@ pyjlany_round(self) = pyint(round(Integer, self)) function pyjlany_round(self, ndigits_::Py) ndigits = pyconvertarg(Int, ndigits_, "ndigits") pydel!(ndigits_) - pyjlany(round(self; digits = ndigits)) + pyjl(round(self; digits = ndigits)) end pyjl_handle_error_type(::typeof(pyjlany_round), self, exc::MethodError) = pybuiltins.TypeError @@ -317,7 +315,7 @@ function pyjlany_next(self) errset(pybuiltins.StopIteration) PyNULL else - pyjlany(s[1]) + pyjl(s[1]) end end @@ -486,28 +484,9 @@ function init_any() end """ - pyjl([t=pyjltype(x)], x) - -Create a Python object wrapping the Julia object `x`. - -If `x` is mutable, then mutating the returned object also mutates `x`, and vice versa. - -Its Python type is normally inferred from the type of `x`, but can be specified with `t`. - -For example if `x` is an `AbstractVector` then the object will have type `juliacall.VectorValue`. -This object will satisfy the Python sequence interface, so for example uses 0-up indexing. + pyjl(x) -To define a custom conversion for your type `T`, overload `pyjltype(::T)`. +Create a Python `juliacall.Jl` object wrapping the Julia object `x`. """ -pyjl(v) = pyjl(pyjltype(v), v) +pyjl(v) = pyjl(pyjlanytype, v) export pyjl - -""" - pyjltype(x) - -The subtype of `juliacall.Jl` which the Julia object `x` is wrapped as by `pyjl(x)`. - -Overload `pyjltype(::T)` to define a custom conversion for your type `T`. -""" -pyjltype(::Any) = pyjlanytype -export pyjltype diff --git a/src/JlWrap/array.jl b/src/JlWrap/array.jl index cb27c07b..016e2cb0 100644 --- a/src/JlWrap/array.jl +++ b/src/JlWrap/array.jl @@ -266,8 +266,8 @@ pytypestrdescr(::Type{T}) where {T} = get!(PYTYPESTRDESCR, T) do end end -pyjlarray_array__array(x::AbstractArray) = x isa Array ? Py(nothing) : pyjl(Array(x)) -pyjlarray_array__pyobjectarray(x::AbstractArray) = pyjl(PyObjectArray(x)) +pyjlarray_array__array(x::AbstractArray) = x isa Array ? Py(nothing) : pyjlarray(Array(x)) +pyjlarray_array__pyobjectarray(x::AbstractArray) = pyjlarray(PyObjectArray(x)) function pyjlarray_array_interface(x::AbstractArray{T,N}) where {T,N} if pyjlarray_isarrayabletype(eltype(x)) @@ -342,4 +342,17 @@ function init_array() pycopy!(pyjlarraytype, jl.ArrayValue) end -pyjltype(::AbstractArray) = pyjlarraytype +""" + pyjlarray(x::AbstractArray) + +Wrap `x` as a Python array-like object. + +This object can be converted to a Numpy array with `numpy.array(v)`, `v.to_numpy()` or +`v.__array__()` and supports these Numpy attributes: `ndim`, `shape`, `copy`, `reshape`. + +If `x` is one-dimensional (an `AbstractVector`) then it also behaves as a `list`. +""" +pyjlarray(x::AbstractArray) = pyjl(pyjlarraytype, x) +export pyjlarray + +Py(x::AbstractArray) = pyjlarray(x) diff --git a/src/JlWrap/base.jl b/src/JlWrap/base.jl index 63caf20f..9b61abef 100644 --- a/src/JlWrap/base.jl +++ b/src/JlWrap/base.jl @@ -73,7 +73,7 @@ function Cjl._pyjl_callmethod(f, self_::C.PyPtr, args_::C.PyPtr, nargs::C.Py_ssi if in_f return pyjl_handle_error(f, self, exc) else - errset(pyJuliaError, pytuple((pyjlany(exc), pyjlany(catch_backtrace())))) + errset(pyJuliaError, pytuple((pyjl(exc), pyjl(catch_backtrace())))) return C.PyNULL end catch @@ -89,7 +89,7 @@ function pyjl_handle_error(f, self, exc) t = pyjl_handle_error_type(f, self, exc)::Py if pyisnull(t) # NULL => raise JuliaError - errset(pyJuliaError, pytuple((pyjlany(exc), pyjlany(catch_backtrace())))) + errset(pyJuliaError, pytuple((pyjl(exc), pyjl(catch_backtrace())))) return C.PyNULL elseif pyistype(t) # Exception type => raise this type of error diff --git a/src/JlWrap/callback.jl b/src/JlWrap/callback.jl index 2070a57c..4b7fcb57 100644 --- a/src/JlWrap/callback.jl +++ b/src/JlWrap/callback.jl @@ -1,6 +1,6 @@ const pywrapcallback = pynew() -pyjlcallback(f) = pyjlany(f).jl_callback +pyjlcallback(f) = pyjl(f).jl_callback """ pyfunc(f; [name], [qualname], [doc], [signature]) diff --git a/src/JlWrap/dict.jl b/src/JlWrap/dict.jl index 6df2cc0b..1590800e 100644 --- a/src/JlWrap/dict.jl +++ b/src/JlWrap/dict.jl @@ -107,4 +107,12 @@ function init_dict() pycopy!(pyjldicttype, jl.DictValue) end -pyjltype(::AbstractDict) = pyjldicttype +""" + pyjldict(x::AbstractDict) + +Wrap `x` as a Python `dict`-like object. +""" +pyjldict(x::AbstractDict) = pyjl(pyjldicttype, x) +export pyjldict + +Py(x::AbstractDict) = pyjldict(x) diff --git a/src/JlWrap/io.jl b/src/JlWrap/io.jl index 1dfe3e6c..14099569 100644 --- a/src/JlWrap/io.jl +++ b/src/JlWrap/io.jl @@ -319,4 +319,4 @@ Wrap `io` as a Python text IO object. pytextio(v::IO) = pyjl(pyjltextiotype, v) export pytextio -pyjltype(::IO) = pyjlbinaryiotype +Py(x::IO) = pybinaryio(x) diff --git a/src/JlWrap/set.jl b/src/JlWrap/set.jl index 2694d13a..a2157bc4 100644 --- a/src/JlWrap/set.jl +++ b/src/JlWrap/set.jl @@ -122,4 +122,12 @@ function init_set() pycopy!(pyjlsettype, jl.SetValue) end -pyjltype(::AbstractSet) = pyjlsettype +""" + pyjlset(x::AbstractSet) + +Wrap `x` as a Python `set`-like object. +""" +pyjlset(x::AbstractSet) = pyjl(pyjlsettype, x) +export pyjlset + +Py(x::AbstractSet) = pyjlset(x) diff --git a/src/JlWrap/vector.jl b/src/JlWrap/vector.jl index 9f728625..45b9ca63 100644 --- a/src/JlWrap/vector.jl +++ b/src/JlWrap/vector.jl @@ -154,4 +154,4 @@ function init_vector() pycopy!(pyjlvectortype, jl.VectorValue) end -pyjltype(::AbstractVector) = pyjlvectortype +pyjlarray(x::AbstractVector) = pyjl(pyjlvectortype, x) From 398c4ff5646d11c33178e936ab771ca44fda933e Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Sun, 5 May 2024 10:14:53 +0100 Subject: [PATCH 12/39] rename ArrayValue to JlArray etc --- docs/src/conversion-to-python.md | 6 +- docs/src/faq.md | 2 +- docs/src/juliacall-reference.md | 123 ++++++------------------------- docs/src/pycall.md | 2 +- src/JlWrap/array.jl | 4 +- src/JlWrap/dict.jl | 6 +- src/JlWrap/io.jl | 18 ++--- src/JlWrap/set.jl | 6 +- src/JlWrap/vector.jl | 6 +- 9 files changed, 48 insertions(+), 125 deletions(-) diff --git a/docs/src/conversion-to-python.md b/docs/src/conversion-to-python.md index ac121518..8333d900 100644 --- a/docs/src/conversion-to-python.md +++ b/docs/src/conversion-to-python.md @@ -24,9 +24,9 @@ From Python, this occurs when converting the return value of a Julia function. | `Date`, `Time`, `DateTime` (from `Dates`) | `date`, `time`, `datetime` (from `datetime`) | | `Second`, `Millisecond`, `Microsecond`, `Nanosecond` (from `Dates`) | `timedelta` (from `datetime`) | | `Number` | `juliacall.NumberValue`, `juliacall.ComplexValue`, etc. | -| `AbstractArray` | `juliacall.ArrayValue`, `juliacall.VectorValue` | -| `AbstractDict` | `juliacall.DictValue` | -| `AbstractSet` | `juliacall.SetValue` | +| `AbstractArray` | `juliacall.JlArray`, `juliacall.JlVector` | +| `AbstractDict` | `juliacall.JlDict` | +| `AbstractSet` | `juliacall.JlSet` | | `IO` | `juliacall.BufferedIOValue` | | `Module` | `juliacall.ModuleValue` | | `Type` | `juliacall.TypeValue` | diff --git a/docs/src/faq.md b/docs/src/faq.md index 1cbcb207..1d3532c8 100644 --- a/docs/src/faq.md +++ b/docs/src/faq.md @@ -20,7 +20,7 @@ Related issues: [#201](https://github.com/JuliaPy/PythonCall.jl/issues/201), [#2 ## Issues when Numpy arrays are expected -When a Julia array is passed to Python, it is wrapped as a [`ArrayValue`](#juliacall.ArrayValue). +When a Julia array is passed to Python, it is wrapped as a [`JlArray`](#juliacall.JlArray). This type satisfies the Numpy array interface and the buffer protocol, so can be used in most places where a numpy array is valid. diff --git a/docs/src/juliacall-reference.md b/docs/src/juliacall-reference.md index 6a1b40c5..916af967 100644 --- a/docs/src/juliacall-reference.md +++ b/docs/src/juliacall-reference.md @@ -20,18 +20,6 @@ The modules `Base`, `Core` and `PythonCall` are also available. ## Utilities -`````@customdoc -juliacall.convert - Function - -```python -convert(T, x) -``` - -Convert `x` to a Julia object of type `T`. - -You can use this to pass an argument to a Julia function of a specific type. -````` - `````@customdoc juliacall.newmodule - Function @@ -45,39 +33,27 @@ A new module with the given name. ## [Wrapper types](@id julia-wrappers) Apart from a few fundamental immutable types, all Julia values are by default converted into -Python to some [`Jl`](#juliacall.Jl) object, which wraps the original value, but -giving it a Pythonic interface. - -Subclasses of [`Jl`](#juliacall.Jl) provide additional Python semantics. For -example a Julia vector is converted to a [`VectorValue`](#juliacall.VectorValue) which -satisfies the Python sequence interface and behaves very similar to a list. +Python to a [`Jl`](#juliacall.Jl) object, which wraps the original value and gives it a +Pythonic interface. -There is also a [`RawValue`](#juliacall.RawValue) object, which gives a stricter -"Julia-only" interface, documented below. These types all inherit from `JlBase`: +Other wrapper classes provide more specific Python semantics. For example a Julia vector can +be converted to a [`JlVector`](#juliacall.JlVector) which satisfies the Python sequence +interface and behaves very similar to a list. - `JlBase` - - [`RawValue`](#juliacall.RawValue) - [`Jl`](#juliacall.Jl) - - [`NumberValue`](#juliacall.NumberValue) - - `ComplexValue` - - `RealValue` - - `RationalValue` - - `IntegerValue` - - [`ArrayValue`](#juliacall.ArrayValue) - - [`VectorValue`](#juliacall.VectorValue) - - [`DictValue`](#juliacall.DictValue) - - [`SetValue`](#juliacall.SetValue) - - [`IOValue`](#juliacall.IOValue) - - `BinaryIOValue` - - `TextIOValue` - - [`ModuleValue`](#juliacall.ModuleValue) - - [`TypeValue`](#juliacall.TypeValue) + - [`JlArray`](#juliacall.JlArray) + - [`JlVector`](#juliacall.JlVector) + - [`JlDict`](#juliacall.JlDict) + - [`JlSet`](#juliacall.JlSet) + - [`JlIOBase`](#juliacall.JlIOBase) + - `JlBinaryIO` + - `JlTextIO` `````@customdoc juliacall.Jl - Class -Wraps any Julia object, giving it some basic Python semantics. Subtypes provide extra -semantics. +Wraps any Julia object, giving it some basic Python semantics. Supports `repr(x)`, `str(x)`, attributes (`x.attr`), calling (`x(a,b)`), iteration, comparisons, `len(x)`, `a in x`, `dir(x)`. @@ -92,24 +68,13 @@ naming conventions, `_b` at the end of an attribute is replaced with `!` and `_b replaced with `!!`. ###### Members -- `_jl_raw()`: Convert to a [`RawValue`](#juliacall.RawValue). (See also [`pyjlraw`](@ref).) -- `_jl_display()`: Display the object using Julia's display mechanism. -- `_jl_help()`: Display help for the object. +- `jl_raw()`: Convert to a [`RawValue`](#juliacall.RawValue). (See also [`pyjlraw`](@ref).) +- `jl_display()`: Display the object using Julia's display mechanism. +- `jl_help()`: Display help for the object. ````` `````@customdoc -juliacall.NumberValue - Class - -This wraps any Julia `Number` value. It is a subclass of `numbers.Number` and behaves -similar to other Python numbers. - -There are also subtypes `ComplexValue`, `RealValue`, `RationalValue`, `IntegerValue` which -wrap values of the corresponding Julia types, and are subclasses of the corresponding -`numbers` ABC. -````` - -`````@customdoc -juliacall.ArrayValue - Class +juliacall.JlArray - Class This wraps any Julia `AbstractArray` value. It is a subclass of `collections.abc.Collection`. @@ -136,9 +101,9 @@ copy of the original array. ````` `````@customdoc -juliacall.VectorValue - Class +juliacall.JlVector - Class -This wraps any Julia `AbstractVector` value. It is a subclass of `juliacall.ArrayValue` and +This wraps any Julia `AbstractVector` value. It is a subclass of `juliacall.JlArray` and `collections.abc.MutableSequence` and behaves similar to a Python `list`. ###### Members @@ -156,65 +121,23 @@ This wraps any Julia `AbstractVector` value. It is a subclass of `juliacall.Arra ````` `````@customdoc -juliacall.DictValue - Class +juliacall.JlDict - Class This wraps any Julia `AbstractDict` value. It is a subclass of `collections.abc.Mapping` and behaves similar to a Python `dict`. ````` `````@customdoc -juliacall.SetValue - Class +juliacall.JlSet - Class This wraps any Julia `AbstractSet` value. It is a subclass of `collections.abc.Set` and behaves similar to a Python `set`. ````` `````@customdoc -juliacall.IOValue - Class +juliacall.JlIOBase - Class This wraps any Julia `IO` value. It is a subclass of `io.IOBase` and behaves like Python files. -There are also subtypes `BinaryIOValue` and `TextIOValue`, which are subclasses of +There are also subtypes `JlBinaryIO` and `JlTextIO`, which are subclasses of `io.BufferedIOBase` (buffered bytes) and `io.TextIOBase` (text). ````` - -`````@customdoc -juliacall.ModuleValue - Class -This wraps any Julia `Module` value. - -It is the same as [`Jl`](#juliacall.Jl) except for one additional convenience -method: -- `seval([module=self], code)`: Evaluates the given code (a string) in the given module. -````` - -`````@customdoc -juliacall.TypeValue - Class - -This wraps any Julia `Type` value. - -It is the same as [`Jl`](#juliacall.Jl) except that indexing is used to access -Julia's "curly" syntax for specifying parametric types: - -```python -from juliacall import Main as jl -# equivalent to Vector{Int}() in Julia -jl.Vector[jl.Int]() -``` -````` - -`````@customdoc -juliacall.RawValue - Class - -Wraps any Julia value with a rigid interface suitable for generic programming. - -Supports `repr(x)`, `str(x)`, attributes (`x.attr`), calling (`x(a,b)`), `len(x)`, `dir(x)`. - -This is very similar to [`Jl`](#juliacall.Jl) except that indexing, calling, -etc. will always return a `RawValue`. - -Indexing with a tuple corresponds to indexing in Julia with multiple values. To index with a -single tuple, it will need to be wrapped in another tuple. - -###### Members -- `_jl_any()`: Convert to a [`Jl`](#juliacall.Jl) (or subclass). (See also - [`pyjl`](@ref).) -````` diff --git a/docs/src/pycall.md b/docs/src/pycall.md index e96f89dd..740b89ff 100644 --- a/docs/src/pycall.md +++ b/docs/src/pycall.md @@ -26,7 +26,7 @@ Furthermore, `pyconvert` can be extended to support more types, whereas `convert Both packages allow conversion of Julia values to Python: `PyObject(x)` in PyCall, `Py(x)` in PythonCall. -Whereas both packages convert numbers, booleans, tuples and strings to their Python counterparts, they differ in handling other types. For example PyCall converts `AbstractVector` to `list` whereas PythonCall converts `AbstractVector` to `juliacall.VectorValue` which is a sequence type directly wrapping the Julia value - this has the advantage that mutating the Python object also mutates the original Julia object. +Whereas both packages convert numbers, booleans, tuples and strings to their Python counterparts, they differ in handling other types. For example PyCall converts `AbstractVector` to `list` whereas PythonCall converts `AbstractVector` to `juliacall.JlVector` which is a sequence type directly wrapping the Julia value - this has the advantage that mutating the Python object also mutates the original Julia object. Hence with PyCall the following does not mutate the original array `x`: ```julia diff --git a/src/JlWrap/array.jl b/src/JlWrap/array.jl index 016e2cb0..83181a0d 100644 --- a/src/JlWrap/array.jl +++ b/src/JlWrap/array.jl @@ -295,7 +295,7 @@ function init_array() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class ArrayValue(JlBase): + class JlArray(JlBase): __slots__ = () _jl_buffer_info = $(pyjl_methodnum(pyjlarray_buffer_info)) @property @@ -339,7 +339,7 @@ function init_array() import numpy return numpy.array(self, dtype=dtype, copy=copy, order=order) """, @__FILE__(), "exec"), jl.__dict__) - pycopy!(pyjlarraytype, jl.ArrayValue) + pycopy!(pyjlarraytype, jl.JlArray) end """ diff --git a/src/JlWrap/dict.jl b/src/JlWrap/dict.jl index 1590800e..a843550a 100644 --- a/src/JlWrap/dict.jl +++ b/src/JlWrap/dict.jl @@ -35,7 +35,7 @@ function init_dict() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class DictValue(JlBase): + class JlDict(JlBase): __slots__ = () _jl_undefined_ = object() def __bool__(self): @@ -101,10 +101,10 @@ function init_dict() def copy(self): return self._jl_callmethod($(pyjl_methodnum(Py ∘ copy))) import collections.abc - collections.abc.MutableMapping.register(DictValue) + collections.abc.MutableMapping.register(JlDict) del collections """, @__FILE__(), "exec"), jl.__dict__) - pycopy!(pyjldicttype, jl.DictValue) + pycopy!(pyjldicttype, jl.JlDict) end """ diff --git a/src/JlWrap/io.jl b/src/JlWrap/io.jl index 14099569..0642f20e 100644 --- a/src/JlWrap/io.jl +++ b/src/JlWrap/io.jl @@ -205,7 +205,7 @@ function init_io() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class IOValueBase(JlBase): + class JlIOBase(JlBase): __slots__ = () def close(self): return self._jl_callmethod($(pyjl_methodnum(pyjlio_close))) @@ -256,7 +256,7 @@ function init_io() return line else: raise StopIteration - class BinaryIOValue(IOValueBase): + class JlBinaryIO(JlIOBase): __slots__ = () def detach(self): raise ValueError("Cannot detach '{}'.".format(type(self))) @@ -272,7 +272,7 @@ function init_io() return self.readinto(b) def write(self, b): return self._jl_callmethod($(pyjl_methodnum(pyjlbinaryio_write)), b) - class TextIOValue(IOValueBase): + class JlTextIO(JlIOBase): __slots__ = () @property def encoding(self): @@ -289,14 +289,14 @@ function init_io() def write(self, s): return self._jl_callmethod($(pyjl_methodnum(pyjltextio_write)), s) import io - io.IOBase.register(IOValueBase) - io.BufferedIOBase.register(BinaryIOValue) - io.TextIOBase.register(TextIOValue) + io.IOBase.register(JlIOBase) + io.BufferedIOBase.register(JlBinaryIO) + io.TextIOBase.register(JlTextIO) del io """, @__FILE__(), "exec"), jl.__dict__) - pycopy!(pyjliobasetype, jl.IOValueBase) - pycopy!(pyjlbinaryiotype, jl.BinaryIOValue) - pycopy!(pyjltextiotype, jl.TextIOValue) + pycopy!(pyjliobasetype, jl.JlIOBase) + pycopy!(pyjlbinaryiotype, jl.JlBinaryIO) + pycopy!(pyjltextiotype, jl.JlTextIO) end pyiobase(v::IO) = pyjl(pyjliobasetype, v) diff --git a/src/JlWrap/set.jl b/src/JlWrap/set.jl index a2157bc4..624e33ce 100644 --- a/src/JlWrap/set.jl +++ b/src/JlWrap/set.jl @@ -77,7 +77,7 @@ function init_set() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class SetValue(JlBase): + class JlSet(JlBase): __slots__ = () def __bool__(self): return bool(len(self)) @@ -116,10 +116,10 @@ function init_set() def update(self, other): return self._jl_callmethod($(pyjl_methodnum(pyjlset_update)), other) import collections.abc - collections.abc.MutableSet.register(SetValue) + collections.abc.MutableSet.register(JlSet) del collections """, @__FILE__(), "exec"), jl.__dict__) - pycopy!(pyjlsettype, jl.SetValue) + pycopy!(pyjlsettype, jl.JlSet) end """ diff --git a/src/JlWrap/vector.jl b/src/JlWrap/vector.jl index 45b9ca63..18f76ba6 100644 --- a/src/JlWrap/vector.jl +++ b/src/JlWrap/vector.jl @@ -121,7 +121,7 @@ function init_vector() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class VectorValue(ArrayValue): + class JlVector(JlArray): __slots__ = () def resize(self, size): return self._jl_callmethod($(pyjl_methodnum(pyjlvector_resize)), size) @@ -148,10 +148,10 @@ function init_vector() def count(self, value): return self._jl_callmethod($(pyjl_methodnum(pyjlvector_count)), value) import collections.abc - collections.abc.MutableSequence.register(VectorValue) + collections.abc.MutableSequence.register(JlVector) del collections """, @__FILE__(), "exec"), jl.__dict__) - pycopy!(pyjlvectortype, jl.VectorValue) + pycopy!(pyjlvectortype, jl.JlVector) end pyjlarray(x::AbstractVector) = pyjl(pyjlvectortype, x) From f82be5695a7732a7acd3db0c9b5f94be5ee66a75 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Sun, 5 May 2024 11:28:20 +0100 Subject: [PATCH 13/39] __init__ for all --- src/JlWrap/C.jl | 25 ++++++++++++++++++++++--- src/JlWrap/array.jl | 2 ++ src/JlWrap/dict.jl | 4 ++++ src/JlWrap/io.jl | 2 ++ src/JlWrap/set.jl | 2 ++ src/JlWrap/vector.jl | 4 ++++ 6 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/JlWrap/C.jl b/src/JlWrap/C.jl index a4ac547b..e4215ac3 100644 --- a/src/JlWrap/C.jl +++ b/src/JlWrap/C.jl @@ -15,6 +15,7 @@ using Serialization: serialize, deserialize end const PyJuliaBase_Type = Ref(C.PyNULL) +const PyJuliaBase_New = Ref(C.PyNULL) # we store the actual julia values here # the `value` field of `PyJuliaValueObject` indexes into here @@ -88,8 +89,9 @@ function _pyjl_init(xptr::C.PyPtr, argsptr::C.PyPtr, kwargsptr::C.PyPtr) PyJuliaValue_SetValue(xptr, v) Cint(0) catch exc - C.PyErr_SetString(C.POINTERS.PyExc_Exception, "error during __init__()") - @debug "exception" exc + errtype = exc isa MethodError ? C.POINTERS.PyExc_TypeError : C.POINTERS.PyExc_Exception + errmsg = sprint(showerror, exc) + C.PyErr_SetString(errtype, errmsg) Cint(-1) end end @@ -354,6 +356,12 @@ function init_c() C.PyErr_Print() error("Error initializing 'juliacall.JlBase'") end + n = PyJuliaBase_New[] = C.PyObject_GetAttrString(o, "__new__") + if n == C.PyNULL + C.PyErr_Print() + error("Error accessing 'juliacall.JlBase.__new__'") + end + nothing end function __init__() @@ -419,7 +427,18 @@ function PyJuliaValue_New(t::C.PyPtr, @nospecialize(v)) C.PyErr_SetString(C.POINTERS.PyExc_TypeError, "Expecting a subtype of 'juliacall.JlBase'") return C.PyNULL end - o = C.PyObject_CallObject(t, C.PyNULL) + # All of this just to do JuliaBase.__new__(t). We do this to avoid calling `__init__` + # which itself sets the value, and so duplicates work. Some classes such as `JlArray` do + # not allow calling `__init__` with no args. + # TODO: it could be replaced with PyObject_CallOneArg(PyJuliaBase_New[], t) when we drop + # support for Python 3.8. + args = C.PyTuple_New(1) + args == C.PyNULL && return C.PyNULL + C.Py_IncRef(t) + err = C.PyTuple_SetItem(args, 0, t) + err == -1 && (C.Py_DecRef(args); return C.PyNULL) + o = C.PyObject_CallObject(PyJuliaBase_New[], args) + C.Py_DecRef(args) o == C.PyNULL && return C.PyNULL PyJuliaValue_SetValue(o, v) return o diff --git a/src/JlWrap/array.jl b/src/JlWrap/array.jl index 83181a0d..0fa525ae 100644 --- a/src/JlWrap/array.jl +++ b/src/JlWrap/array.jl @@ -298,6 +298,8 @@ function init_array() class JlArray(JlBase): __slots__ = () _jl_buffer_info = $(pyjl_methodnum(pyjlarray_buffer_info)) + def __init__(self, value): + JlBase.__init__(self, value, Base.AbstractArray) @property def ndim(self): return self._jl_callmethod($(pyjl_methodnum(Py ∘ ndims))) diff --git a/src/JlWrap/dict.jl b/src/JlWrap/dict.jl index a843550a..e5b69661 100644 --- a/src/JlWrap/dict.jl +++ b/src/JlWrap/dict.jl @@ -38,6 +38,10 @@ function init_dict() class JlDict(JlBase): __slots__ = () _jl_undefined_ = object() + def __init__(self, value=None): + if value is None: + value = Base.Dict() + JlBase.__init__(self, value, Base.AbstractDict) def __bool__(self): return bool(len(self)) def __iter__(self): diff --git a/src/JlWrap/io.jl b/src/JlWrap/io.jl index 0642f20e..c694186f 100644 --- a/src/JlWrap/io.jl +++ b/src/JlWrap/io.jl @@ -207,6 +207,8 @@ function init_io() $("\n"^(@__LINE__()-1)) class JlIOBase(JlBase): __slots__ = () + def __init__(self, value): + JlBase.__init__(self, value, Base.IO) def close(self): return self._jl_callmethod($(pyjl_methodnum(pyjlio_close))) @property diff --git a/src/JlWrap/set.jl b/src/JlWrap/set.jl index 624e33ce..249d4240 100644 --- a/src/JlWrap/set.jl +++ b/src/JlWrap/set.jl @@ -79,6 +79,8 @@ function init_set() $("\n"^(@__LINE__()-1)) class JlSet(JlBase): __slots__ = () + def __init__(self, value=None): + JlBase.__init__(self, value, Base.AbstractSet) def __bool__(self): return bool(len(self)) def add(self, value): diff --git a/src/JlWrap/vector.jl b/src/JlWrap/vector.jl index 18f76ba6..b78bd0e0 100644 --- a/src/JlWrap/vector.jl +++ b/src/JlWrap/vector.jl @@ -123,6 +123,10 @@ function init_vector() $("\n"^(@__LINE__()-1)) class JlVector(JlArray): __slots__ = () + def __init__(self, value=None): + if value is None: + value = Base.Vector() + JlBase.__init__(self, value, Base.AbstractVector) def resize(self, size): return self._jl_callmethod($(pyjl_methodnum(pyjlvector_resize)), size) def sort(self, reverse=False, key=None): From 747a3310e4259d67aaa020cb05a228e05556dbc3 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Sun, 5 May 2024 12:41:44 +0100 Subject: [PATCH 14/39] mixins for consistency --- src/JlWrap/any.jl | 44 +++++++++++++++++++++++++++++++++++++++----- src/JlWrap/array.jl | 6 +++--- src/JlWrap/dict.jl | 12 ++++-------- src/JlWrap/io.jl | 2 +- src/JlWrap/set.jl | 8 ++++---- 5 files changed, 51 insertions(+), 21 deletions(-) diff --git a/src/JlWrap/any.jl b/src/JlWrap/any.jl index 1b5390cb..4d2015e4 100644 --- a/src/JlWrap/any.jl +++ b/src/JlWrap/any.jl @@ -1,11 +1,12 @@ const pyjlanytype = pynew() +const pyjlitertype = pynew() # pyjlany_repr(self) = Py("<jl $(repr(self))>") function pyjlany_repr(self) str = repr(MIME("text/plain"), self; context=IOContext(devnull, :limit=>true, :displaysize=>(23,80))) # type = self isa Function ? "Function" : self isa Type ? "Type" : nameof(typeof(self)) sep = '\n' in str ? '\n' : ' ' - Py("Julia:$sep$str"::String) + Py("$(sep)$(str)"::String) end # Note: string(self) doesn't always return a String @@ -319,16 +320,48 @@ function pyjlany_next(self) end end +function pyjliter_next(self) + s = iterate(self) + if s === nothing + errset(pybuiltins.StopIteration) + PyNULL + else + Py(s[1]) + end +end + pyjlany_hash(self) = pyint(hash(self)) function init_any() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class Jl(JlBase): + class _JlReprMixin: __slots__ = () def __repr__(self): - return self._jl_callmethod($(pyjl_methodnum(pyjlany_repr))) + t = type(self) + if t is Jl: + name = "Julia" + else: + name = t.__name__ + return name + ":" + self._jl_callmethod($(pyjl_methodnum(pyjlany_repr))) + class _JlHashMixin: + __slots__ = () + def __hash__(self): + return self._jl_callmethod($(pyjl_methodnum(pyint ∘ hash))) + class JlIter(JlBase, _JlReprMixin, _JlHashMixin): + def __iter__(self): + return self + def __next__(self): + return self._jl_callmethod($(pyjl_methodnum(pyjliter_next))) + class _JlContainerMixin(_JlReprMixin, _JlHashMixin): + __slots__ = () + def __len__(self): + return self._jl_callmethod($(pyjl_methodnum(pyint ∘ length))) + def __bool__(self): + return self._jl_callmethod($(pyjl_methodnum(pybool ∘ !isempty))) + class Jl(JlBase, _JlReprMixin, _JlHashMixin): + __slots__ = () def __str__(self): return self._jl_callmethod($(pyjl_methodnum(pyjlany_str))) def __getattr__(self, k): @@ -443,8 +476,6 @@ function init_any() return self._jl_callmethod($(pyjl_methodnum(pyjlany_op(≥))), other) def __gt__(self, other): return self._jl_callmethod($(pyjl_methodnum(pyjlany_op(>))), other) - def __hash__(self): - return self._jl_callmethod($(pyjl_methodnum(pyjlany_hash))) def __int__(self): return self._jl_callmethod($(pyjl_methodnum(pyjlany_int))) def __float__(self): @@ -481,6 +512,7 @@ function init_any() return self._jl_callmethod($(pyjl_methodnum(pyjlany_mimebundle)), include, exclude) """, @__FILE__(), "exec"), jl.__dict__) pycopy!(pyjlanytype, jl.Jl) + pycopy!(pyjlitertype, jl.JlIter) end """ @@ -490,3 +522,5 @@ Create a Python `juliacall.Jl` object wrapping the Julia object `x`. """ pyjl(v) = pyjl(pyjlanytype, v) export pyjl + +pyjliter(x) = pyjl(pyjlitertype, x) diff --git a/src/JlWrap/array.jl b/src/JlWrap/array.jl index 0fa525ae..4af140c8 100644 --- a/src/JlWrap/array.jl +++ b/src/JlWrap/array.jl @@ -295,7 +295,7 @@ function init_array() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class JlArray(JlBase): + class JlArray(JlBase, _JlContainerMixin): __slots__ = () _jl_buffer_info = $(pyjl_methodnum(pyjlarray_buffer_info)) def __init__(self, value): @@ -310,14 +310,14 @@ function init_array() return self._jl_callmethod($(pyjl_methodnum(Py ∘ copy))) def reshape(self, shape): return self._jl_callmethod($(pyjl_methodnum(pyjlarray_reshape)), shape) - def __bool__(self): - return bool(len(self)) def __getitem__(self, k): return self._jl_callmethod($(pyjl_methodnum(pyjlarray_getitem)), k) def __setitem__(self, k, v): self._jl_callmethod($(pyjl_methodnum(pyjlarray_setitem)), k, v) def __delitem__(self, k): self._jl_callmethod($(pyjl_methodnum(pyjlarray_delitem)), k) + def __iter__(self): + return self._jl_callmethod($(pyjl_methodnum(pyjliter ∘ Iterator))) @property def __array_interface__(self): return self._jl_callmethod($(pyjl_methodnum(pyjlarray_array_interface))) diff --git a/src/JlWrap/dict.jl b/src/JlWrap/dict.jl index e5b69661..66586ddf 100644 --- a/src/JlWrap/dict.jl +++ b/src/JlWrap/dict.jl @@ -9,8 +9,6 @@ Base.iterate(x::DictPairSet, st) = Base.in(v::Pair, x::DictPairSet) = v in x.dict Base.in(v::Tuple{Any,Any}, x::DictPairSet) = Pair(v[1], v[2]) in x.dict -pyjldict_iter(x::AbstractDict) = Py(Iterator(keys(x))) - pyjldict_contains(x::AbstractDict, k::Py) = Py(haskey(x, @pyconvert(keytype(x), k, return Py(false)))) pyjldict_clear(x::AbstractDict) = (empty!(x); Py(nothing)) @@ -35,17 +33,15 @@ function init_dict() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class JlDict(JlBase): + class JlDict(JlBase, _JlContainerMixin): __slots__ = () _jl_undefined_ = object() def __init__(self, value=None): if value is None: value = Base.Dict() JlBase.__init__(self, value, Base.AbstractDict) - def __bool__(self): - return bool(len(self)) def __iter__(self): - return self._jl_callmethod($(pyjl_methodnum(pyjldict_iter))) + return self._jl_callmethod($(pyjl_methodnum(pyjliter ∘ Iterator ∘ keys))) def __contains__(self, key): return self._jl_callmethod($(pyjl_methodnum(pyjldict_contains)), key) def __getitem__(self, key): @@ -61,11 +57,11 @@ function init_dict() else: raise KeyError(key) def keys(self): - return self._jl_callmethod($(pyjl_methodnum(Py ∘ keys))) + return self._jl_callmethod($(pyjl_methodnum(pyset ∘ keys))) def values(self): return self._jl_callmethod($(pyjl_methodnum(Py ∘ values))) def items(self): - return self._jl_callmethod($(pyjl_methodnum(Py ∘ DictPairSet))) + return self._jl_callmethod($(pyjl_methodnum(pyset ∘ DictPairSet))) def get(self, key, default=None): if key in self: return self[key] diff --git a/src/JlWrap/io.jl b/src/JlWrap/io.jl index c694186f..a0d77afb 100644 --- a/src/JlWrap/io.jl +++ b/src/JlWrap/io.jl @@ -205,7 +205,7 @@ function init_io() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class JlIOBase(JlBase): + class JlIOBase(JlBase, _JlReprMixin, _JlHashMixin): __slots__ = () def __init__(self, value): JlBase.__init__(self, value, Base.IO) diff --git a/src/JlWrap/set.jl b/src/JlWrap/set.jl index 249d4240..a3e7b805 100644 --- a/src/JlWrap/set.jl +++ b/src/JlWrap/set.jl @@ -77,12 +77,12 @@ function init_set() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class JlSet(JlBase): + class JlSet(JlBase, _JlContainerMixin): __slots__ = () def __init__(self, value=None): JlBase.__init__(self, value, Base.AbstractSet) - def __bool__(self): - return bool(len(self)) + def __iter__(self): + return self._jl_callmethod($(pyjl_methodnum(pyjliter ∘ Iterator))) def add(self, value): return self._jl_callmethod($(pyjl_methodnum(pyjlset_add)), value) def discard(self, value): @@ -90,7 +90,7 @@ function init_set() def clear(self): return self._jl_callmethod($(pyjl_methodnum(pyjlset_clear))) def copy(self): - return self._jl_callmethod($(pyjl_methodnum(Py ∘ copy))) + return self._jl_callmethod($(pyjl_methodnum(pyjlset ∘ copy))) def pop(self): return self._jl_callmethod($(pyjl_methodnum(pyjlset_pop))) def remove(self, value): From 526f0449643a6c429cf24810de0b1d7835ca6947 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Sun, 12 May 2024 20:10:36 +0100 Subject: [PATCH 15/39] improve pymacro docstring --- src/PyMacro/PyMacro.jl | 132 +++++++++++++++++++++-------------------- 1 file changed, 69 insertions(+), 63 deletions(-) diff --git a/src/PyMacro/PyMacro.jl b/src/PyMacro/PyMacro.jl index 04bf9718..87087221 100644 --- a/src/PyMacro/PyMacro.jl +++ b/src/PyMacro/PyMacro.jl @@ -66,13 +66,13 @@ const PY_MACRO_UNOPS = Dict( :tuple => (pytuple, true), :type => (pytype, true), # builtins converting to julia - :jlascii => (x->pyascii(String,x), false), + :jlascii => (x -> pyascii(String, x), false), :jlbool => (pytruth, false), - :jlbytes => (x->pybytes(Base.CodeUnits,x), false), + :jlbytes => (x -> pybytes(Base.CodeUnits, x), false), :jlhash => (pyhash, false), :jllen => (pylen, false), - :jlrepr => (x->pyrepr(String,x), false), - :jlstr => (x->pystr(String,x), false), + :jlrepr => (x -> pyrepr(String, x), false), + :jlstr => (x -> pystr(String, x), false), # jlcomplex ) @@ -92,13 +92,13 @@ const PY_MACRO_BINOPS = Dict( :(⊻) => (pyxor, true), :(==) => (pyeq, true), :(!=) => (pyne, true), - :(≠ ) => (pyne, true), + :(≠) => (pyne, true), :(<=) => (pyle, true), - :(≤ ) => (pyle, true), - :(< ) => (pylt, true), + :(≤) => (pyle, true), + :(<) => (pylt, true), :(>=) => (pyge, true), - :(≥ ) => (pyge, true), - :(> ) => (pygt, true), + :(≥) => (pyge, true), + :(>) => (pygt, true), :(===) => (pyis, false), :(≡) => (pyis, false), :(!==) => (pyisnot, false), @@ -129,10 +129,10 @@ const PY_MACRO_TERNOPS = Dict( ) Base.@kwdef mutable struct PyMacroState - mod :: Module - src :: LineNumberNode - consts :: IdDict{Any,Py} = IdDict{Any,Py}() - inits :: Vector{Any} = [] + mod::Module + src::LineNumberNode + consts::IdDict{Any,Py} = IdDict{Any,Py}() + inits::Vector{Any} = [] end function py_macro_err(st, ex, msg=nothing) @@ -147,48 +147,51 @@ end py_macro_assign(body, ans, ex) = push!(body, :($ans = $ex)) -py_macro_del(body, var, tmp) = if tmp; push!(body, :($pydel!($var))); end +py_macro_del(body, var, tmp) = + if tmp + push!(body, :($pydel!($var))) + end ismacroexpr(ex, name) = isexpr(ex, :macrocall) && (ex.args[1] === Symbol(name) || ex.args[1] === GlobalRef(Base.Core, Symbol(name))) function py_macro_lower(st, body, ans, ex; flavour=:expr) # scalar literals - if ex isa Union{Nothing, String, Bool, Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UInt128, BigInt, Float16, Float32, Float64} + if ex isa Union{Nothing,String,Bool,Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UInt64,UInt128,BigInt,Float16,Float32,Float64} x = get!(pynew, st.consts, ex) py_macro_assign(body, ans, x) return false - # Int128 literals + # Int128 literals elseif ismacroexpr(ex, "@int128_str") value = parse(Int128, ex.args[3]) x = get!(pynew, st.consts, value) py_macro_assign(body, ans, x) return false - # UInt128 literals + # UInt128 literals elseif ismacroexpr(ex, "@uint128_str") value = parse(UInt128, ex.args[3]) x = get!(pynew, st.consts, value) py_macro_assign(body, ans, x) return false - # big integer literals + # big integer literals elseif ismacroexpr(ex, "@big_str") value = parse(BigInt, ex.args[3]) x = get!(pynew, st.consts, value) py_macro_assign(body, ans, x) return false - # __file__ + # __file__ elseif ex === :__file__ return py_macro_lower(st, body, ans, string(st.src.file)) - # __line__ + # __line__ elseif ex === :__line__ return py_macro_lower(st, body, ans, st.src.line) - # x + # x elseif ex isa Symbol if ex in BUILTINS py_macro_assign(body, ans, :($pybuiltins.$ex)) @@ -197,37 +200,37 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) end return false - # x:y:z - elseif flavour==:index && @capture(ex, ax_:ay_:az_) + # x:y:z + elseif flavour == :index && @capture(ex, ax_:ay_:az_) @gensym x y z - tx = py_macro_lower(st, body, x, ax===:_ ? :None : ax) - ty = py_macro_lower(st, body, y, ay===:_ ? :None : ay) - tz = py_macro_lower(st, body, z, az===:_ ? :None : az) + tx = py_macro_lower(st, body, x, ax === :_ ? :None : ax) + ty = py_macro_lower(st, body, y, ay === :_ ? :None : ay) + tz = py_macro_lower(st, body, z, az === :_ ? :None : az) py_macro_assign(body, ans, :($pyslice($x, $y, $z))) py_macro_del(body, x, tx) py_macro_del(body, y, ty) py_macro_del(body, z, tz) return true - # x:y - elseif flavour==:index && @capture(ex, ax_:ay_) + # x:y + elseif flavour == :index && @capture(ex, ax_:ay_) @gensym x y - tx = py_macro_lower(st, body, x, ax===:_ ? :None : ax) - ty = py_macro_lower(st, body, y, ay===:_ ? :None : ay) + tx = py_macro_lower(st, body, x, ax === :_ ? :None : ax) + ty = py_macro_lower(st, body, y, ay === :_ ? :None : ay) py_macro_assign(body, ans, :($pyslice($x, $y))) py_macro_del(body, x, tx) py_macro_del(body, y, ty) return true - # x + y + z + ... + # x + y + z + ... elseif @capture(ex, +(ax_, ay_, az_, args__)) - return py_macro_lower(st, body, ans, foldl((x, y)->:($x+$y), (ax, ay, az, args...))) + return py_macro_lower(st, body, ans, foldl((x, y) -> :($x + $y), (ax, ay, az, args...))) - # x * y * z * ... + # x * y * z * ... elseif @capture(ex, *(ax_, ay_, az_, args__)) - return py_macro_lower(st, body, ans, foldl((x, y)->:($x*$y), (ax, ay, az, args...))) + return py_macro_lower(st, body, ans, foldl((x, y) -> :($x * $y), (ax, ay, az, args...))) - # f(args...; kwargs...) + # f(args...; kwargs...) elseif isexpr(ex, :call) af = ex.args[1] # is it a special operator? @@ -333,7 +336,7 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) return true end - # (...) + # (...) elseif isexpr(ex, :tuple) if any(isexpr(arg, :...) for arg in ex.args) py_macro_err(st, ex, "splatting into tuples not implemented") @@ -341,14 +344,14 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) py_macro_assign(body, ans, :($pynulltuple($(length(ex.args))))) @gensym a for (i, aa) in enumerate(ex.args) - ta = py_macro_lower(st, body, a, aa, flavour = flavour==:index ? :index : :expr) - push!(body, :($pytuple_setitem($ans, $(i-1), $a))) + ta = py_macro_lower(st, body, a, aa, flavour=flavour == :index ? :index : :expr) + push!(body, :($pytuple_setitem($ans, $(i - 1), $a))) py_macro_del(body, a, ta) end return true end - # [...] + # [...] elseif isexpr(ex, :vect) if any(isexpr(arg, :...) for arg in ex.args) py_macro_err(st, ex, "splatting into tuples not implemented") @@ -357,13 +360,13 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) @gensym a for (i, aa) in enumerate(ex.args) ta = py_macro_lower(st, body, a, aa) - push!(body, :($pylist_setitem($ans, $(i-1), $a))) + push!(body, :($pylist_setitem($ans, $(i - 1), $a))) py_macro_del(body, a, ta) end return true end - # {...} + # {...} elseif isexpr(ex, :braces) # Like Python, we allow braces to be set or dict literals. # @@ -398,7 +401,7 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) push!(body, :($pydict_setitem($ans, $k, $v))) py_macro_del(body, k, tk) py_macro_del(body, v, tv) - elseif @capture(aa, ak_ : av_) + elseif @capture(aa, ak_:av_) tk = py_macro_lower(st, body, k, ak) tv = py_macro_lower(st, body, v, av) push!(body, :($pydict_setitem($ans, $k, $v))) @@ -423,7 +426,7 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) return true end - # x.k + # x.k elseif isexpr(ex, :.) ax, ak = ex.args @gensym x k @@ -438,7 +441,7 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) py_macro_del(body, k, tk) return true - # x[k] + # x[k] elseif @capture(ex, ax_[ak__]) @gensym x k tx = py_macro_lower(st, body, x, ax) @@ -452,13 +455,13 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) py_macro_del(body, k, tk) return true - # x = y + # x = y elseif @capture(ex, ax_ = ay_) ty = py_macro_lower(st, body, ans, ay) py_macro_lower_assign(st, body, ax, ans) return ty - # @del x, y, ... + # @del x, y, ... elseif @capture(ex, @del (args__,)) for arg in args @@ -477,7 +480,7 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) py_macro_del(body, x, tx) py_macro_del(body, k, tk) - # @del x[k] + # @del x[k] elseif @capture(arg, ax_[ak__]) @gensym x k tx = py_macro_lower(st, body, x, ax) @@ -497,18 +500,18 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) py_macro_assign(body, ans, nothing) return false - # @del x + # @del x elseif @capture(ex, @del arg_) return py_macro_lower(st, body, ans, :(@del ($arg,))) - # @jl x + # @jl x elseif @capture(ex, @jl ax_) y = py_macro_lower_jl(st, ax) py_macro_assign(body, ans, y) return false - # @compile code mode=mode ... - elseif @capture(ex, @compile code_String mode=mode_String args__) + # @compile code mode=mode ... + elseif @capture(ex, @compile code_String mode = mode_String args__) x = pynew() args = [isexpr(arg, :(=)) ? Expr(:kw, arg.args...) : arg for arg in args] filename = "$(st.src.file):$(st.src.line)" @@ -516,7 +519,7 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) py_macro_assign(body, ans, x) return false - # @exec code ... + # @exec code ... elseif @capture(ex, @exec code_String args__) args = [isexpr(arg, :(=)) ? Expr(:kw, arg.args...) : arg for arg in args] ex2 = Expr(:macrocall, Symbol("@compile"), st.src, code, :(mode = "exec")) @@ -527,14 +530,14 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) py_macro_assign(body, ans, nothing) return false - # @eval code ... + # @eval code ... elseif @capture(ex, @eval code_String args__) args = [isexpr(arg, :(=)) ? Expr(:kw, arg.args...) : arg for arg in args] ex2 = Expr(:macrocall, Symbol("@compile"), st.src, code, :(mode = "eval")) ex2 = Expr(:call, :eval, ex2, args...) return py_macro_lower(st, body, ans, ex2) - # begin; ...; end + # begin; ...; end elseif isexpr(ex, :block) if isempty(ex.args) py_macro_assign(body, ans, nothing) @@ -560,7 +563,7 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) @assert false end - # if x; ...; end + # if x; ...; end elseif isexpr(ex, :if, :elseif) if length(ex.args) == 2 return py_macro_lower(st, body, ans, Expr(ex.head, ex.args..., nothing)) @@ -581,7 +584,7 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) return t end - # x && y + # x && y elseif isexpr(ex, :&&) ax, ay = ex.args tx = py_macro_lower(st, body, ans, ax) @@ -597,7 +600,7 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) push!(body, Expr(:if, :($pytruth($ans)), Expr(:block, body2...), Expr(:block, body3...))) return t - # x || y + # x || y elseif isexpr(ex, :||) ax, ay = ex.args tx = py_macro_lower(st, body, ans, ax) @@ -613,7 +616,7 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) push!(body, Expr(:if, :($pytruth($ans)), Expr(:block, body2...), Expr(:block, body3...))) return t - # while x; ...; end + # while x; ...; end elseif isexpr(ex, :while) ax, ay = ex.args @gensym x y @@ -626,8 +629,10 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) py_macro_assign(body, ans, nothing) return false - # for x in y; ...; end - elseif @capture(ex, for ax_ in ay_; az_; end) + # for x in y; ...; end + elseif @capture(ex, for ax_ in ay_ + az_ + end) @gensym y i v z ty = py_macro_lower(st, body, y, ay) push!(body, :($i = $pyiter($y))) @@ -644,7 +649,7 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) py_macro_assign(body, ans, nothing) return false - # import ... + # import ... elseif isexpr(ex, :import) for aa in ex.args if isexpr(aa, :as) @@ -680,7 +685,7 @@ function py_macro_lower(st, body, ans, ex; flavour=:expr) py_macro_assign(body, ans, nothing) return false - # "...$foo..." + # "...$foo..." elseif isexpr(ex, :string) args = [a isa String ? a : :(str($a)) for a in ex.args] return py_macro_lower(st, body, ans, :("".join(($(args...),)))) @@ -795,8 +800,9 @@ Evaluate the given expression using Pythonic semantics. For example: - `f(x, y)` is translated to `pycall(f, x, y)` - `x + y` is translated to `pyadd(x, y)` -- `x === y` is translated to `pyis(x, y)` +- `x === y` is translated to `pyis(x, y)` (`x is y` in Python) - `x.foo` is translated to `pygetattr(x, "foo")` +- `import x: f as g` is translated to `g = pyimport("x" => "f")` (`from x import f as g` in Python) Compound statements such as `begin`, `if`, `while` and `for` are supported. From 49645fca94a35feada39431f963e32010574ba4d Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Mon, 10 Jun 2024 17:41:18 +0100 Subject: [PATCH 16/39] jlwrap tests --- src/JlWrap/any.jl | 16 ++--- test/Compat.jl | 2 +- test/JlWrap.jl | 178 +++++++++++++++++++++++----------------------- 3 files changed, 97 insertions(+), 99 deletions(-) diff --git a/src/JlWrap/any.jl b/src/JlWrap/any.jl index 4d2015e4..3e21cde7 100644 --- a/src/JlWrap/any.jl +++ b/src/JlWrap/any.jl @@ -3,7 +3,7 @@ const pyjlitertype = pynew() # pyjlany_repr(self) = Py("<jl $(repr(self))>") function pyjlany_repr(self) - str = repr(MIME("text/plain"), self; context=IOContext(devnull, :limit=>true, :displaysize=>(23,80))) + str = repr(MIME("text/plain"), self; context=IOContext(devnull, :limit => true, :displaysize => (23, 80))) # type = self isa Function ? "Function" : self isa Type ? "Type" : nameof(typeof(self)) sep = '\n' in str ? '\n' : ' ' Py("$(sep)$(str)"::String) @@ -35,12 +35,12 @@ pyjl_handle_error_type(::typeof(pyjlany_setattr), self, exc) = pybuiltins.Attrib function pyjlany_dir(self) ks = Symbol[] if self isa Module - append!(ks, names(self, all = true, imported = true)) + append!(ks, names(self, all=true, imported=true)) for m in ccall(:jl_module_usings, Any, (Any,), self)::Vector append!(ks, names(m)) end else - append!(propertynames(self, true)) + append!(ks, propertynames(self, true)) end pylist(pyjl_attr_jl2py(string(k)) for k in ks) end @@ -88,7 +88,7 @@ function pyjlany_getitem(self, k_::Py) else k = pyconvert(Any, k_) pyjl(self{k}) - end + end else if pyistuple(k_) k = pyconvert(Vector{Any}, k_) @@ -133,7 +133,7 @@ pyjlany_contains(self, v::Py) = Py((@pyconvert(eltype(self), v, return Py(false) pyjl_handle_error_type(::typeof(pyjlany_contains), self, exc) = exc isa MethodError && exc.f === in ? pybuiltins.TypeError : PyNULL struct pyjlany_op{OP} - op :: OP + op::OP end (op::pyjlany_op)(self) = pyjl(op.op(self)) function (op::pyjlany_op)(self, other_::Py) @@ -161,7 +161,7 @@ end pyjl_handle_error_type(op::pyjlany_op, self, exc) = exc isa MethodError && exc.f === op.op ? pybuiltins.TypeError : PyNULL struct pyjlany_rev_op{OP} - op :: OP + op::OP end function (op::pyjlany_rev_op)(self, other_::Py) if pyisjl(other_) @@ -224,7 +224,7 @@ function pyjlany_mimebundle(self, include::Py, exclude::Py) for m in mimes try io = IOBuffer() - show(IOContext(io, :limit=>true), MIME(m), self) + show(IOContext(io, :limit => true), MIME(m), self) v = take!(io) ans[m] = vo = istextmime(m) ? pystr(String(v)) : pybytes(v) pydel!(vo) @@ -278,7 +278,7 @@ pyjlany_round(self) = pyint(round(Integer, self)) function pyjlany_round(self, ndigits_::Py) ndigits = pyconvertarg(Int, ndigits_, "ndigits") pydel!(ndigits_) - pyjl(round(self; digits = ndigits)) + pyjl(round(self; digits=ndigits)) end pyjl_handle_error_type(::typeof(pyjlany_round), self, exc::MethodError) = pybuiltins.TypeError diff --git a/test/Compat.jl b/test/Compat.jl index 77deb310..a930a564 100644 --- a/test/Compat.jl +++ b/test/Compat.jl @@ -45,7 +45,7 @@ end @testitem "Serialization.jl" begin using Serialization @testset "Py" begin - for x in Py[Py(123), Py(1.23), Py("hello"), pylist([1, 2, 3]), pytuple([1, 2, 3]), Py(nothing), Py([1, 2, 3]), Py(:hello)] + @testset for x in Py[Py(123), Py(1.23), Py("hello"), pylist([1, 2, 3]), pytuple([1, 2, 3]), Py(nothing), Py([1, 2, 3]), Py(:hello)] io = IOBuffer() serialize(io, x) seekstart(io) diff --git a/test/JlWrap.jl b/test/JlWrap.jl index b6364ccd..72d54851 100644 --- a/test/JlWrap.jl +++ b/test/JlWrap.jl @@ -20,7 +20,7 @@ Base.powermod(x::Foo, y::Foo, z::Foo) = "powermod($(x.value), $(y.value), $(z.value))" (x::Foo)(args...; kw...) = "$(x.value)($args)$(length(kw))" Base.getindex(x::Foo, idx...) = "$(x.value)[$idx]" - Base.setindex!(x::Foo, v, idx...) = (x.value = v+sum(idx); x) + Base.setindex!(x::Foo, v, idx...) = (x.value = v + sum(idx); x) Base.delete!(x::Foo, idx...) = (x.value = -sum(idx); x) Base.in(v::Int, x::Foo) = x.value == v Base.nameof(x::Foo) = "nameof $(x.value)" @@ -30,10 +30,12 @@ @test pyis(pytype(pyjl(missing)), PythonCall.pyjlanytype) end @testset "bool" begin - @test pytruth(pyjl(Foo(0))) - @test pytruth(pyjl(Foo(1))) - @test pytruth(pyjl(nothing)) - @test pytruth(pyjl(missing)) + @test pytruth(pyjl(true)) + @test !pytruth(pyjl(false)) + @test_throws Exception pytruth(pyjl(Foo(0))) + @test_throws Exception pytruth(pyjl(Foo(1))) + @test_throws Exception pytruth(pyjl(nothing)) + @test_throws Exception pytruth(pyjl(missing)) end @testset "repr" begin @test pyrepr(String, pyjl(missing)) == "Julia: missing" @@ -105,7 +107,7 @@ @test pyconvert(String, z) == "1 + 2" end @testset "radd" begin - z = pyjlraw(Foo(1)) + pyjl(Foo(2)) + z = pyjl(Foo(2)).__radd__(pyjl(Foo(1))) @test pyconvert(String, z) == "1 + 2" end @testset "sub" begin @@ -113,7 +115,7 @@ @test pyconvert(String, z) == "1 - 2" end @testset "rsub" begin - z = pyjlraw(Foo(1)) - pyjl(Foo(2)) + z = pyjl(Foo(2)).__rsub__(pyjl(Foo(1))) @test pyconvert(String, z) == "1 - 2" end @testset "mul" begin @@ -121,7 +123,7 @@ @test pyconvert(String, z) == "1 * 2" end @testset "rmul" begin - z = pyjlraw(Foo(1)) * pyjl(Foo(2)) + z = pyjl(Foo(2)).__rmul__(pyjl(Foo(1))) @test pyconvert(String, z) == "1 * 2" end @testset "truediv" begin @@ -129,7 +131,7 @@ @test pyconvert(String, z) == "1 / 2" end @testset "rtruediv" begin - z = pyjlraw(Foo(1)) / pyjl(Foo(2)) + z = pyjl(Foo(2)).__rtruediv__(pyjl(Foo(1))) @test pyconvert(String, z) == "1 / 2" end @testset "floordiv" begin @@ -137,7 +139,7 @@ @test pyconvert(String, z) == "1 ÷ 2" end @testset "rfloordiv" begin - z = pyjlraw(Foo(1)) ÷ pyjl(Foo(2)) + z = pyjl(Foo(2)).__rfloordiv__(pyjl(Foo(1))) @test pyconvert(String, z) == "1 ÷ 2" end @testset "mod" begin @@ -145,15 +147,15 @@ @test pyconvert(String, z) == "1 % 2" end @testset "rmod" begin - z = pyjlraw(Foo(1)) % pyjl(Foo(2)) + z = pyjl(Foo(2)).__rmod__(pyjl(Foo(1))) @test pyconvert(String, z) == "1 % 2" end @testset "pow" begin - z = pyjl(Foo(1)) ^ pyjl(Foo(2)) + z = pyjl(Foo(1))^pyjl(Foo(2)) @test pyconvert(String, z) == "1 ^ 2" end @testset "rpow" begin - z = pyjlraw(Foo(1)) ^ pyjl(Foo(2)) + z = pyjl(Foo(2)).__rpow__(pyjl(Foo(1))) @test pyconvert(String, z) == "1 ^ 2" end @testset "lshift" begin @@ -161,7 +163,7 @@ @test pyconvert(String, z) == "1 << 2" end @testset "rlshift" begin - z = pyjlraw(Foo(1)) << pyjl(Foo(2)) + z = pyjl(Foo(2)).__rlshift__(pyjl(Foo(1))) @test pyconvert(String, z) == "1 << 2" end @testset "rshift" begin @@ -169,7 +171,7 @@ @test pyconvert(String, z) == "1 >> 2" end @testset "rrshift" begin - z = pyjlraw(Foo(1)) >> pyjl(Foo(2)) + z = pyjl(Foo(2)).__rrshift__(pyjl(Foo(1))) @test pyconvert(String, z) == "1 >> 2" end @testset "and" begin @@ -177,7 +179,7 @@ @test pyconvert(String, z) == "1 & 2" end @testset "rand" begin - z = pyjlraw(Foo(1)) & pyjl(Foo(2)) + z = pyjl(Foo(2)).__rand__(pyjl(Foo(1))) @test pyconvert(String, z) == "1 & 2" end @testset "or" begin @@ -185,7 +187,7 @@ @test pyconvert(String, z) == "1 | 2" end @testset "ror" begin - z = pyjlraw(Foo(1)) | pyjl(Foo(2)) + z = pyjl(Foo(2)).__ror__(pyjl(Foo(1))) @test pyconvert(String, z) == "1 | 2" end @testset "pow3" begin @@ -206,12 +208,12 @@ @test pycontains(z, "text/plain") end @testset "display" begin - pyjl(Foo(1))._jl_display() - pyjl(Foo(1))._jl_display(mime="text/plain") + pyjl(Foo(1)).jl_display() + pyjl(Foo(1)).jl_display(mime="text/plain") end @testset "help" begin - pyjl(Foo(1))._jl_help() - pyjl(Foo(1))._jl_help(mime="text/plain") + pyjl(Foo(1)).jl_help() + pyjl(Foo(1)).jl_help(mime="text/plain") end end @@ -247,16 +249,16 @@ end @test pyeq(Bool, x[-1], 5) @test pyeq(Bool, x[-2], 4) @test pyeq(Bool, x[-3], 3) - @test pyjlvalue(x[pyslice(3)]) == [1,2,3] - @test pyjlvalue(x[pyslice(2)]) == [1,2] - @test pyjlvalue(x[pyslice(1,2)]) == [2] - @test pyjlvalue(x[pyslice(2,2)]) == [] - @test pyjlvalue(x[pyslice(0,-1)]) == [1,2,3,4] - @test pyjlvalue(x[pyslice(-2,nothing)]) == [4,5] - @test pyjlvalue(x[pyslice(0, 3, 1)]) == [1,2,3] - @test pyjlvalue(x[pyslice(nothing,nothing,2)]) == [1,3,5] - @test pyjlvalue(x[pyslice(1,nothing,2)]) == [2,4] - @test pyjlvalue(x[pyslice(0,nothing,3)]) == [1,4] + @test pyjlvalue(x[pyslice(3)]) == [1, 2, 3] + @test pyjlvalue(x[pyslice(2)]) == [1, 2] + @test pyjlvalue(x[pyslice(1, 2)]) == [2] + @test pyjlvalue(x[pyslice(2, 2)]) == [] + @test pyjlvalue(x[pyslice(0, -1)]) == [1, 2, 3, 4] + @test pyjlvalue(x[pyslice(-2, nothing)]) == [4, 5] + @test pyjlvalue(x[pyslice(0, 3, 1)]) == [1, 2, 3] + @test pyjlvalue(x[pyslice(nothing, nothing, 2)]) == [1, 3, 5] + @test pyjlvalue(x[pyslice(1, nothing, 2)]) == [2, 4] + @test pyjlvalue(x[pyslice(0, nothing, 3)]) == [1, 4] x = pyjl([1 2; 3 4]) @test pyeq(Bool, x[0, 0], 1) @test pyeq(Bool, x[0, 1], 2) @@ -268,19 +270,19 @@ end @testset "setitem" begin x = [0 0; 0 0] y = pyjl(x) - y[0,0] = 1 + y[0, 0] = 1 @test x == [1 0; 0 0] - y[0,1] = 2 + y[0, 1] = 2 @test x == [1 2; 0 0] - y[1,0] = 3 + y[1, 0] = 3 @test x == [1 2; 3 0] - y[-1,0] = 4 + y[-1, 0] = 4 @test x == [1 2; 4 0] - y[-2,pyslice(nothing)] = 5 + y[-2, pyslice(nothing)] = 5 @test x == [5 5; 4 0] - y[pyslice(nothing),-1] = 6 + y[pyslice(nothing), -1] = 6 @test x == [5 6; 4 6] - y[pyslice(nothing),pyslice(nothing)] = 7 + y[pyslice(nothing), pyslice(nothing)] = 7 @test x == [7 7; 7 7] end @testset "delitem" begin @@ -292,7 +294,7 @@ end @test x == [2, 3, 5, 6, 7, 8] pydelitem(y, -3) @test x == [2, 3, 5, 7, 8] - pydelitem(y, pyslice(1,nothing,2)) + pydelitem(y, pyslice(1, nothing, 2)) @test x == [2, 5, 8] end @testset "reshape" begin @@ -309,7 +311,7 @@ end @test pyjlvalue(x) == pyjlvalue(y) @test typeof(pyjlvalue(x)) == typeof(pyjlvalue(y)) @test pyjlvalue(x) !== pyjlvalue(y) - x[0,0] = 0 + x[0, 0] = 0 @test pyjlvalue(x) == [0 2; 3 4] @test pyjlvalue(y) == [1 2; 3 4] end @@ -334,7 +336,7 @@ end @test pytruth(m.f_contiguous) @test pyeq(Bool, m.format, "f") @test pyeq(Bool, m.itemsize, 4) - @test pyeq(Bool, m.nbytes, 4*6) + @test pyeq(Bool, m.nbytes, 4 * 6) @test pyeq(Bool, m.ndim, 2) @test !pytruth(m.readonly) @test pyeq(Bool, m.shape, (2, 3)) @@ -358,7 +360,7 @@ end end @testset "bool" begin @test !pytruth(pyjl(Dict())) - @test pytruth(pyjl(Dict("one"=>1, "two"=>2))) + @test pytruth(pyjl(Dict("one" => 1, "two" => 2))) end end @@ -375,7 +377,7 @@ end end @testitem "iter" begin - x1 = [1,2,3,4,5] + x1 = [1, 2, 3, 4, 5] x2 = pyjl(x1) x3 = pylist(x2) x4 = pyconvert(Vector{Int}, x3) @@ -400,19 +402,19 @@ end @testset "type" begin @test pyis(pytype(pyjl(false)), PythonCall.pyjlintegertype) @test pyis(pytype(pyjl(0)), PythonCall.pyjlintegertype) - @test pyis(pytype(pyjl(0//1)), PythonCall.pyjlrationaltype) + @test pyis(pytype(pyjl(0 // 1)), PythonCall.pyjlrationaltype) @test pyis(pytype(pyjl(0.0)), PythonCall.pyjlrealtype) @test pyis(pytype(pyjl(Complex(0.0))), PythonCall.pyjlcomplextype) end @testset "bool" begin @test !pytruth(pyjl(false)) @test !pytruth(pyjl(0)) - @test !pytruth(pyjl(0//1)) + @test !pytruth(pyjl(0 // 1)) @test !pytruth(pyjl(0.0)) @test !pytruth(pyjl(Complex(0.0))) @test pytruth(pyjl(true)) @test pytruth(pyjl(3)) - @test pytruth(pyjl(5//2)) + @test pytruth(pyjl(5 // 2)) @test pytruth(pyjl(2.3)) @test pytruth(pyjl(Complex(1.2, 3.4))) end @@ -422,17 +424,13 @@ end end -@testitem "raw" begin - -end - @testitem "set" begin @testset "type" begin @test pyis(pytype(pyjl(Set())), PythonCall.pyjlsettype) end @testset "bool" begin @test !pytruth(pyjl(Set())) - @test pytruth(pyjl(Set([1,2,3]))) + @test pytruth(pyjl(Set([1, 2, 3]))) end end @@ -452,7 +450,7 @@ end @testset "bool" begin @test !pytruth(pyjl([])) @test pytruth(pyjl([1])) - @test pytruth(pyjl([1,2])) + @test pytruth(pyjl([1, 2])) end @testset "resize" begin x = pyjl([1, 2, 3, 4, 5]) @@ -469,91 +467,91 @@ end @test pyjlvalue(x) == [5, 6] end @testset "sort" begin - x = pyjl([4,6,2,3,7,6,1]) + x = pyjl([4, 6, 2, 3, 7, 6, 1]) x.sort() - @test pyjlvalue(x) == [1,2,3,4,6,6,7] - x = pyjl([4,6,2,3,7,6,1]) + @test pyjlvalue(x) == [1, 2, 3, 4, 6, 6, 7] + x = pyjl([4, 6, 2, 3, 7, 6, 1]) x.sort(reverse=true) - @test pyjlvalue(x) == [7,6,6,4,3,2,1] - x = pyjl([4,-6,2,-3,7,-6,1]) + @test pyjlvalue(x) == [7, 6, 6, 4, 3, 2, 1] + x = pyjl([4, -6, 2, -3, 7, -6, 1]) x.sort(key=abs) - @test pyjlvalue(x) == [1,2,-3,4,-6,-6,7] - x = pyjl([4,-6,2,-3,7,-6,1]) + @test pyjlvalue(x) == [1, 2, -3, 4, -6, -6, 7] + x = pyjl([4, -6, 2, -3, 7, -6, 1]) x.sort(key=abs, reverse=true) - @test pyjlvalue(x) == [7,-6,-6,4,-3,2,1] + @test pyjlvalue(x) == [7, -6, -6, 4, -3, 2, 1] end @testset "reverse" begin - x = pyjl([1,2,3,4,5]) + x = pyjl([1, 2, 3, 4, 5]) x.reverse() - @test pyjlvalue(x) == [5,4,3,2,1] + @test pyjlvalue(x) == [5, 4, 3, 2, 1] end @testset "clear" begin - x = pyjl([1,2,3,4,5]) - @test pyjlvalue(x) == [1,2,3,4,5] + x = pyjl([1, 2, 3, 4, 5]) + @test pyjlvalue(x) == [1, 2, 3, 4, 5] x.clear() @test pyjlvalue(x) == [] end @testset "reversed" begin - x = pyjl([1,2,3,4,5]) + x = pyjl([1, 2, 3, 4, 5]) y = pybuiltins.reversed(x) - @test pyjlvalue(x) == [1,2,3,4,5] - @test pyjlvalue(y) == [5,4,3,2,1] + @test pyjlvalue(x) == [1, 2, 3, 4, 5] + @test pyjlvalue(y) == [5, 4, 3, 2, 1] end @testset "insert" begin - x = pyjl([1,2,3]) + x = pyjl([1, 2, 3]) x.insert(0, 4) - @test pyjlvalue(x) == [4,1,2,3] + @test pyjlvalue(x) == [4, 1, 2, 3] x.insert(2, 5) - @test pyjlvalue(x) == [4,1,5,2,3] + @test pyjlvalue(x) == [4, 1, 5, 2, 3] x.insert(5, 6) - @test pyjlvalue(x) == [4,1,5,2,3,6] + @test pyjlvalue(x) == [4, 1, 5, 2, 3, 6] x.insert(-3, 7) - @test pyjlvalue(x) == [4,1,5,7,2,3,6] + @test pyjlvalue(x) == [4, 1, 5, 7, 2, 3, 6] @test_throws PyException x.insert(10, 10) end @testset "append" begin - x = pyjl([1,2,3]) + x = pyjl([1, 2, 3]) x.append(4) - @test pyjlvalue(x) == [1,2,3,4] + @test pyjlvalue(x) == [1, 2, 3, 4] x.append(5.0) - @test pyjlvalue(x) == [1,2,3,4,5] + @test pyjlvalue(x) == [1, 2, 3, 4, 5] @test_throws PyException x.append(nothing) @test_throws PyException x.append(1.2) @test_throws PyException x.append("2") end @testset "extend" begin - x = pyjl([1,2,3]) + x = pyjl([1, 2, 3]) x.extend(pylist()) - @test pyjlvalue(x) == [1,2,3] - x.extend(pylist([4,5])) - @test pyjlvalue(x) == [1,2,3,4,5] + @test pyjlvalue(x) == [1, 2, 3] + x.extend(pylist([4, 5])) + @test pyjlvalue(x) == [1, 2, 3, 4, 5] x.extend(pylist([6.0])) - @test pyjlvalue(x) == [1,2,3,4,5,6] + @test pyjlvalue(x) == [1, 2, 3, 4, 5, 6] end @testset "pop" begin - x = pyjl([1,2,3,4,5]) + x = pyjl([1, 2, 3, 4, 5]) @test pyeq(Bool, x.pop(), 5) - @test pyjlvalue(x) == [1,2,3,4] + @test pyjlvalue(x) == [1, 2, 3, 4] @test pyeq(Bool, x.pop(0), 1) - @test pyjlvalue(x) == [2,3,4] + @test pyjlvalue(x) == [2, 3, 4] @test pyeq(Bool, x.pop(1), 3) - @test pyjlvalue(x) == [2,4] + @test pyjlvalue(x) == [2, 4] @test pyeq(Bool, x.pop(-2), 2) @test pyjlvalue(x) == [4] @test_throws PyException x.pop(10) end @testset "remove" begin - x = pyjl([1,3,2,4,5,3,1]) - @test pyjlvalue(x) == [1,3,2,4,5,3,1] + x = pyjl([1, 3, 2, 4, 5, 3, 1]) + @test pyjlvalue(x) == [1, 3, 2, 4, 5, 3, 1] x.remove(3) - @test pyjlvalue(x) == [1,2,4,5,3,1] + @test pyjlvalue(x) == [1, 2, 4, 5, 3, 1] @test_throws PyException x.remove(0) @test_throws PyException x.remove(nothing) @test_throws PyException x.remove("2") - @test pyjlvalue(x) == [1,2,4,5,3,1] + @test pyjlvalue(x) == [1, 2, 4, 5, 3, 1] end @testset "index" begin - x = pyjl([1,3,2,4,5,2,1]) + x = pyjl([1, 3, 2, 4, 5, 2, 1]) @test pyeq(Bool, x.index(1), 0) @test pyeq(Bool, x.index(2), 2) @test pyeq(Bool, x.index(3), 1) @@ -566,7 +564,7 @@ end @test_throws PyException x.index("2") end @testset "count" begin - x = pyjl([1,2,3,4,5,1,2,3,1]) + x = pyjl([1, 2, 3, 4, 5, 1, 2, 3, 1]) @test pyeq(Bool, x.count(0), 0) @test pyeq(Bool, x.count(1), 3) @test pyeq(Bool, x.count(2), 2) From 4f67c769d87d9a14a5d822fa55b588fbab58a4fc Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Mon, 10 Jun 2024 17:47:51 +0100 Subject: [PATCH 17/39] remove old tests --- test/JlWrap.jl | 53 ++++++++------------------------------------------ 1 file changed, 8 insertions(+), 45 deletions(-) diff --git a/test/JlWrap.jl b/test/JlWrap.jl index 72d54851..61db90db 100644 --- a/test/JlWrap.jl +++ b/test/JlWrap.jl @@ -54,6 +54,9 @@ end @testset "dir" begin @test pycontains(pydir(pyjl(Foo(99))), "value") + @test pycontains(pydir(pyjl(Base)), "+") + @test pycontains(pydir(pyjl(Base)), "Type") + @test pycontains(pydir(pyjl(Base)), "nfields") end @testset "call" begin z = pyjl(Foo(1))(4, 5) @@ -215,6 +218,11 @@ pyjl(Foo(1)).jl_help() pyjl(Foo(1)).jl_help(mime="text/plain") end + @testset "eval" begin + m = pyjl(Main) + @test pyconvert(Any, m.jl_eval("1 + 1")) === 2 # Basic behavior + @test pyconvert(Any, m.jl_eval("1 + 1\n ")) === 2 # Trailing whitespace + end end @testitem "array" begin @@ -384,42 +392,6 @@ end @test x1 == x4 end -@testitem "module" begin - @testset "type" begin - @test pyis(pytype(pyjl(PythonCall)), PythonCall.pyjlmoduletype) - end - @testset "bool" begin - @test pytruth(pyjl(PythonCall)) - end - @testset "seval" begin - m = Py(Main) - @test pyconvert(Any, m.seval("1 + 1")) === 2 # Basic behavior - @test pyconvert(Any, m.seval("1 + 1\n ")) === 2 # Trailing whitespace - end -end - -@testitem "number" begin - @testset "type" begin - @test pyis(pytype(pyjl(false)), PythonCall.pyjlintegertype) - @test pyis(pytype(pyjl(0)), PythonCall.pyjlintegertype) - @test pyis(pytype(pyjl(0 // 1)), PythonCall.pyjlrationaltype) - @test pyis(pytype(pyjl(0.0)), PythonCall.pyjlrealtype) - @test pyis(pytype(pyjl(Complex(0.0))), PythonCall.pyjlcomplextype) - end - @testset "bool" begin - @test !pytruth(pyjl(false)) - @test !pytruth(pyjl(0)) - @test !pytruth(pyjl(0 // 1)) - @test !pytruth(pyjl(0.0)) - @test !pytruth(pyjl(Complex(0.0))) - @test pytruth(pyjl(true)) - @test pytruth(pyjl(3)) - @test pytruth(pyjl(5 // 2)) - @test pytruth(pyjl(2.3)) - @test pytruth(pyjl(Complex(1.2, 3.4))) - end -end - @testitem "objectarray" begin end @@ -434,15 +406,6 @@ end end end -@testitem "type" begin - @testset "type" begin - @test pyis(pytype(pyjl(Int)), PythonCall.pyjltypetype) - end - @testset "bool" begin - @test pytruth(pyjl(Int)) - end -end - @testitem "vector" begin @testset "type" begin @test pyis(pytype(pyjl([1, 2, 3, 4])), PythonCall.pyjlvectortype) From 157d725680cb25784aedd48ced47730e71dd6c6d Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Mon, 10 Jun 2024 17:49:02 +0100 Subject: [PATCH 18/39] pyjlset tests --- test/JlWrap.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/JlWrap.jl b/test/JlWrap.jl index 61db90db..f124e544 100644 --- a/test/JlWrap.jl +++ b/test/JlWrap.jl @@ -398,11 +398,11 @@ end @testitem "set" begin @testset "type" begin - @test pyis(pytype(pyjl(Set())), PythonCall.pyjlsettype) + @test pyis(pytype(pyjlset(Set())), PythonCall.pyjlsettype) end @testset "bool" begin - @test !pytruth(pyjl(Set())) - @test pytruth(pyjl(Set([1, 2, 3]))) + @test !pytruth(pyjlset(Set())) + @test pytruth(pyjlset(Set([1, 2, 3]))) end end From c271815830f2d52e0426659d5c520f48ea05c540 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Mon, 10 Jun 2024 17:49:47 +0100 Subject: [PATCH 19/39] pyjldict tests --- test/JlWrap.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/JlWrap.jl b/test/JlWrap.jl index f124e544..a0f3b599 100644 --- a/test/JlWrap.jl +++ b/test/JlWrap.jl @@ -364,11 +364,11 @@ end @testitem "dict" begin @testset "type" begin - @test pyis(pytype(pyjl(Dict())), PythonCall.pyjldicttype) + @test pyis(pytype(pyjldict(Dict())), PythonCall.pyjldicttype) end @testset "bool" begin - @test !pytruth(pyjl(Dict())) - @test pytruth(pyjl(Dict("one" => 1, "two" => 2))) + @test !pytruth(pyjldict(Dict())) + @test pytruth(pyjldict(Dict("one" => 1, "two" => 2))) end end From 5e79f89ccdef5f038bf5f06e10bf3c072ae67b31 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Mon, 10 Jun 2024 17:50:32 +0100 Subject: [PATCH 20/39] pyjlio tests --- test/JlWrap.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/test/JlWrap.jl b/test/JlWrap.jl index a0f3b599..7c18759a 100644 --- a/test/JlWrap.jl +++ b/test/JlWrap.jl @@ -374,7 +374,6 @@ end @testitem "io" begin @testset "type" begin - @test pyis(pytype(pyjl(devnull)), PythonCall.pyjlbinaryiotype) @test pyis(pytype(pybinaryio(devnull)), PythonCall.pyjlbinaryiotype) @test pyis(pytype(pytextio(devnull)), PythonCall.pyjltextiotype) end From 6b1cdd2e19d462311a85cc952d7de0ec087528e4 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Mon, 10 Jun 2024 17:54:44 +0100 Subject: [PATCH 21/39] pyjlarray tests --- test/JlWrap.jl | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/test/JlWrap.jl b/test/JlWrap.jl index 7c18759a..1fe928d8 100644 --- a/test/JlWrap.jl +++ b/test/JlWrap.jl @@ -227,30 +227,31 @@ end @testitem "array" begin @testset "type" begin - @test pyis(pytype(pyjl(fill(nothing))), PythonCall.pyjlarraytype) - @test pyis(pytype(pyjl([1 2; 3 4])), PythonCall.pyjlarraytype) + @test pyis(pytype(pyjlarray(fill(nothing))), PythonCall.pyjlarraytype) + @test pyis(pytype(pyjlarray([1, 2, 3])), PythonCall.pyjlvectortype) + @test pyis(pytype(pyjlarray([1 2; 3 4])), PythonCall.pyjlarraytype) end @testset "bool" begin - @test !pytruth(pyjl(fill(nothing, 0, 1))) - @test !pytruth(pyjl(fill(nothing, 1, 0))) - @test pytruth(pyjl(fill(nothing))) - @test pytruth(pyjl(fill(nothing, 1, 2))) - @test pytruth(pyjl(fill(nothing, 1, 2, 3))) + @test !pytruth(pyjlarray(fill(nothing, 0, 1))) + @test !pytruth(pyjlarray(fill(nothing, 1, 0))) + @test pytruth(pyjlarray(fill(nothing))) + @test pytruth(pyjlarray(fill(nothing, 1, 2))) + @test pytruth(pyjlarray(fill(nothing, 1, 2, 3))) end @testset "ndim" begin - @test pyeq(Bool, pyjl(fill(nothing)).ndim, 0) - @test pyeq(Bool, pyjl(fill(nothing, 1)).ndim, 1) - @test pyeq(Bool, pyjl(fill(nothing, 1, 1)).ndim, 2) - @test pyeq(Bool, pyjl(fill(nothing, 1, 1, 1)).ndim, 3) + @test pyeq(Bool, pyjlarray(fill(nothing)).ndim, 0) + @test pyeq(Bool, pyjlarray(fill(nothing, 1)).ndim, 1) + @test pyeq(Bool, pyjlarray(fill(nothing, 1, 1)).ndim, 2) + @test pyeq(Bool, pyjlarray(fill(nothing, 1, 1, 1)).ndim, 3) end @testset "shape" begin - @test pyeq(Bool, pyjl(fill(nothing)).shape, ()) - @test pyeq(Bool, pyjl(fill(nothing, 3)).shape, (3,)) - @test pyeq(Bool, pyjl(fill(nothing, 3, 5)).shape, (3, 5)) - @test pyeq(Bool, pyjl(fill(nothing, 3, 5, 2)).shape, (3, 5, 2)) + @test pyeq(Bool, pyjlarray(fill(nothing)).shape, ()) + @test pyeq(Bool, pyjlarray(fill(nothing, 3)).shape, (3,)) + @test pyeq(Bool, pyjlarray(fill(nothing, 3, 5)).shape, (3, 5)) + @test pyeq(Bool, pyjlarray(fill(nothing, 3, 5, 2)).shape, (3, 5, 2)) end @testset "getitem" begin - x = pyjl([1, 2, 3, 4, 5]) + x = pyjlarray([1, 2, 3, 4, 5]) @test pyeq(Bool, x[0], 1) @test pyeq(Bool, x[1], 2) @test pyeq(Bool, x[2], 3) @@ -267,7 +268,7 @@ end @test pyjlvalue(x[pyslice(nothing, nothing, 2)]) == [1, 3, 5] @test pyjlvalue(x[pyslice(1, nothing, 2)]) == [2, 4] @test pyjlvalue(x[pyslice(0, nothing, 3)]) == [1, 4] - x = pyjl([1 2; 3 4]) + x = pyjlarray([1 2; 3 4]) @test pyeq(Bool, x[0, 0], 1) @test pyeq(Bool, x[0, 1], 2) @test pyeq(Bool, x[1, 0], 3) @@ -277,7 +278,7 @@ end end @testset "setitem" begin x = [0 0; 0 0] - y = pyjl(x) + y = pyjlarray(x) y[0, 0] = 1 @test x == [1 0; 0 0] y[0, 1] = 2 @@ -295,7 +296,7 @@ end end @testset "delitem" begin x = [1, 2, 3, 4, 5, 6, 7, 8] - y = pyjl(x) + y = pyjlarray(x) pydelitem(y, 0) @test x == [2, 3, 4, 5, 6, 7, 8] pydelitem(y, 2) @@ -306,14 +307,14 @@ end @test x == [2, 5, 8] end @testset "reshape" begin - x = pyjl([1, 2, 3, 4, 5, 6, 7, 8]) + x = pyjlarray([1, 2, 3, 4, 5, 6, 7, 8]) @test pyeq(Bool, x.shape, (8,)) y = x.reshape((2, 4)) @test pyeq(Bool, y.shape, (2, 4)) @test pyjlvalue(y) == [1 3 5 7; 2 4 6 8] end @testset "copy" begin - x = pyjl([1 2; 3 4]) + x = pyjlarray([1 2; 3 4]) y = x.copy() @test pyis(pytype(y), PythonCall.pyjlarraytype) @test pyjlvalue(x) == pyjlvalue(y) @@ -324,7 +325,7 @@ end @test pyjlvalue(y) == [1 2; 3 4] end @testset "array_interface" begin - x = pyjl(Float32[1 2 3; 4 5 6]).__array_interface__ + x = pyjlarray(Float32[1 2 3; 4 5 6]).__array_interface__ @test pyisinstance(x, pybuiltins.dict) @test pyeq(Bool, x["shape"], (2, 3)) @test pyeq(Bool, x["typestr"], "<f4") @@ -338,7 +339,7 @@ end # x = pyjl(Float32[1 2 3; 4 5 6]).__array_struct__ end @testset "buffer" begin - m = pybuiltins.memoryview(pyjl(Float32[1 2 3; 4 5 6])) + m = pybuiltins.memoryview(pyjlarray(Float32[1 2 3; 4 5 6])) @test !pytruth(m.c_contiguous) @test pytruth(m.contiguous) @test pytruth(m.f_contiguous) From 1ff9df67abd0485edb69bde3cf1ef2f863b3c150 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Mon, 10 Jun 2024 17:56:38 +0100 Subject: [PATCH 22/39] pyjlvector tests --- test/JlWrap.jl | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/test/JlWrap.jl b/test/JlWrap.jl index 1fe928d8..82170311 100644 --- a/test/JlWrap.jl +++ b/test/JlWrap.jl @@ -408,15 +408,15 @@ end @testitem "vector" begin @testset "type" begin - @test pyis(pytype(pyjl([1, 2, 3, 4])), PythonCall.pyjlvectortype) + @test pyis(pytype(pyjlarray([1, 2, 3, 4])), PythonCall.pyjlvectortype) end @testset "bool" begin - @test !pytruth(pyjl([])) - @test pytruth(pyjl([1])) - @test pytruth(pyjl([1, 2])) + @test !pytruth(pyjlarray([])) + @test pytruth(pyjlarray([1])) + @test pytruth(pyjlarray([1, 2])) end @testset "resize" begin - x = pyjl([1, 2, 3, 4, 5]) + x = pyjlarray([1, 2, 3, 4, 5]) @test pyjlvalue(x) == [1, 2, 3, 4, 5] x.resize(5) @test pyjlvalue(x) == [1, 2, 3, 4, 5] @@ -430,38 +430,38 @@ end @test pyjlvalue(x) == [5, 6] end @testset "sort" begin - x = pyjl([4, 6, 2, 3, 7, 6, 1]) + x = pyjlarray([4, 6, 2, 3, 7, 6, 1]) x.sort() @test pyjlvalue(x) == [1, 2, 3, 4, 6, 6, 7] - x = pyjl([4, 6, 2, 3, 7, 6, 1]) + x = pyjlarray([4, 6, 2, 3, 7, 6, 1]) x.sort(reverse=true) @test pyjlvalue(x) == [7, 6, 6, 4, 3, 2, 1] - x = pyjl([4, -6, 2, -3, 7, -6, 1]) + x = pyjlarray([4, -6, 2, -3, 7, -6, 1]) x.sort(key=abs) @test pyjlvalue(x) == [1, 2, -3, 4, -6, -6, 7] - x = pyjl([4, -6, 2, -3, 7, -6, 1]) + x = pyjlarray([4, -6, 2, -3, 7, -6, 1]) x.sort(key=abs, reverse=true) @test pyjlvalue(x) == [7, -6, -6, 4, -3, 2, 1] end @testset "reverse" begin - x = pyjl([1, 2, 3, 4, 5]) + x = pyjlarray([1, 2, 3, 4, 5]) x.reverse() @test pyjlvalue(x) == [5, 4, 3, 2, 1] end @testset "clear" begin - x = pyjl([1, 2, 3, 4, 5]) + x = pyjlarray([1, 2, 3, 4, 5]) @test pyjlvalue(x) == [1, 2, 3, 4, 5] x.clear() @test pyjlvalue(x) == [] end @testset "reversed" begin - x = pyjl([1, 2, 3, 4, 5]) + x = pyjlarray([1, 2, 3, 4, 5]) y = pybuiltins.reversed(x) @test pyjlvalue(x) == [1, 2, 3, 4, 5] @test pyjlvalue(y) == [5, 4, 3, 2, 1] end @testset "insert" begin - x = pyjl([1, 2, 3]) + x = pyjlarray([1, 2, 3]) x.insert(0, 4) @test pyjlvalue(x) == [4, 1, 2, 3] x.insert(2, 5) @@ -473,7 +473,7 @@ end @test_throws PyException x.insert(10, 10) end @testset "append" begin - x = pyjl([1, 2, 3]) + x = pyjlarray([1, 2, 3]) x.append(4) @test pyjlvalue(x) == [1, 2, 3, 4] x.append(5.0) @@ -483,7 +483,7 @@ end @test_throws PyException x.append("2") end @testset "extend" begin - x = pyjl([1, 2, 3]) + x = pyjlarray([1, 2, 3]) x.extend(pylist()) @test pyjlvalue(x) == [1, 2, 3] x.extend(pylist([4, 5])) @@ -492,7 +492,7 @@ end @test pyjlvalue(x) == [1, 2, 3, 4, 5, 6] end @testset "pop" begin - x = pyjl([1, 2, 3, 4, 5]) + x = pyjlarray([1, 2, 3, 4, 5]) @test pyeq(Bool, x.pop(), 5) @test pyjlvalue(x) == [1, 2, 3, 4] @test pyeq(Bool, x.pop(0), 1) @@ -504,7 +504,7 @@ end @test_throws PyException x.pop(10) end @testset "remove" begin - x = pyjl([1, 3, 2, 4, 5, 3, 1]) + x = pyjlarray([1, 3, 2, 4, 5, 3, 1]) @test pyjlvalue(x) == [1, 3, 2, 4, 5, 3, 1] x.remove(3) @test pyjlvalue(x) == [1, 2, 4, 5, 3, 1] @@ -514,7 +514,7 @@ end @test pyjlvalue(x) == [1, 2, 4, 5, 3, 1] end @testset "index" begin - x = pyjl([1, 3, 2, 4, 5, 2, 1]) + x = pyjlarray([1, 3, 2, 4, 5, 2, 1]) @test pyeq(Bool, x.index(1), 0) @test pyeq(Bool, x.index(2), 2) @test pyeq(Bool, x.index(3), 1) @@ -527,7 +527,7 @@ end @test_throws PyException x.index("2") end @testset "count" begin - x = pyjl([1, 2, 3, 4, 5, 1, 2, 3, 1]) + x = pyjlarray([1, 2, 3, 4, 5, 1, 2, 3, 1]) @test pyeq(Bool, x.count(0), 0) @test pyeq(Bool, x.count(1), 3) @test pyeq(Bool, x.count(2), 2) From 034f4872b3e205e432f99947234e089252733595 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Tue, 11 Jun 2024 17:46:26 +0100 Subject: [PATCH 23/39] pyjl hash, == and iter --- src/JlWrap/any.jl | 24 +++++-- src/JlWrap/array.jl | 160 ++++++++++++++++++++++---------------------- src/JlWrap/dict.jl | 2 +- src/JlWrap/io.jl | 4 +- src/JlWrap/set.jl | 2 - test/JlWrap.jl | 23 +++++++ 6 files changed, 125 insertions(+), 90 deletions(-) diff --git a/src/JlWrap/any.jl b/src/JlWrap/any.jl index 3e21cde7..df42c124 100644 --- a/src/JlWrap/any.jl +++ b/src/JlWrap/any.jl @@ -332,6 +332,8 @@ end pyjlany_hash(self) = pyint(hash(self)) +pyjlmixin_eq_bool(self, other) = pybool((self == pyjlvalue(other))::Bool) + function init_any() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" @@ -345,22 +347,30 @@ function init_any() else: name = t.__name__ return name + ":" + self._jl_callmethod($(pyjl_methodnum(pyjlany_repr))) - class _JlHashMixin: + class JlIter(JlBase, _JlReprMixin): __slots__ = () - def __hash__(self): - return self._jl_callmethod($(pyjl_methodnum(pyint ∘ hash))) - class JlIter(JlBase, _JlReprMixin, _JlHashMixin): def __iter__(self): return self + def __hash__(self): + return self._jl_callmethod($(pyjl_methodnum(pyjlany_hash))) def __next__(self): return self._jl_callmethod($(pyjl_methodnum(pyjliter_next))) - class _JlContainerMixin(_JlReprMixin, _JlHashMixin): + class _JlContainerMixin(_JlReprMixin): __slots__ = () def __len__(self): return self._jl_callmethod($(pyjl_methodnum(pyint ∘ length))) def __bool__(self): return self._jl_callmethod($(pyjl_methodnum(pybool ∘ !isempty))) - class Jl(JlBase, _JlReprMixin, _JlHashMixin): + def __iter__(self): + return self._jl_callmethod($(pyjl_methodnum(pyjliter ∘ Iterator))) + def __hash__(self): + return self._jl_callmethod($(pyjl_methodnum(pyjlany_hash))) + def __eq__(self, other): + if isinstance(self, type(other)) or isinstance(other, type(self)): + return self._jl_callmethod($(pyjl_methodnum(pyjlmixin_eq_bool)), other) + else: + return NotImplemented + class Jl(JlBase, _JlReprMixin): __slots__ = () def __str__(self): return self._jl_callmethod($(pyjl_methodnum(pyjlany_str))) @@ -464,6 +474,8 @@ function init_any() return self._jl_callmethod($(pyjl_methodnum(pyjlany_rev_op(⊻))), other) def __ror__(self, other): return self._jl_callmethod($(pyjl_methodnum(pyjlany_rev_op(|))), other) + def __hash__(self): + return self._jl_callmethod($(pyjl_methodnum(pyjlany_hash))) def __eq__(self, other): return self._jl_callmethod($(pyjl_methodnum(pyjlany_op(==))), other) def __ne__(self, other): diff --git a/src/JlWrap/array.jl b/src/JlWrap/array.jl index 4af140c8..d74f892d 100644 --- a/src/JlWrap/array.jl +++ b/src/JlWrap/array.jl @@ -63,7 +63,7 @@ function pyjl_getarrayindices(x::AbstractArray{T,N}, ks::Py) where {T,N} if pyistuple(ks) if pylen(ks) == N return ntuple(N) do i - k = pytuple_getitem(ks, i-1) + k = pytuple_getitem(ks, i - 1) ans = pyjl_getaxisindex(axes(x, i), k) pydel!(k) return ans @@ -149,12 +149,12 @@ pyjlarray_isbufferabletype(::Type{NamedTuple{names,T}}) where {names,T} = function pyjlarray_buffer_info(x::AbstractArray{T,N}) where {T,N} if pyjlarray_isbufferabletype(T) Cjl.PyBufferInfo{N}( - ptr = Base.unsafe_convert(Ptr{T}, x), - readonly = !Utils.ismutablearray(x), - itemsize = sizeof(T), - format = pybufferformat(T), - shape = size(x), - strides = strides(x) .* Base.aligned_sizeof(T), + ptr=Base.unsafe_convert(Ptr{T}, x), + readonly=!Utils.ismutablearray(x), + itemsize=sizeof(T), + format=pybufferformat(T), + shape=size(x), + strides=strides(x) .* Base.aligned_sizeof(T), ) else error("element type is not bufferable") @@ -163,43 +163,44 @@ end const PYBUFFERFORMAT = IdDict{Type,String}() -pybufferformat(::Type{T}) where {T} = get!(PYBUFFERFORMAT, T) do - T == Cchar ? "b" : - T == Cuchar ? "B" : - T == Cshort ? "h" : - T == Cushort ? "H" : - T == Cint ? "i" : - T == Cuint ? "I" : - T == Clong ? "l" : - T == Culong ? "L" : - T == Clonglong ? "q" : - T == Culonglong ? "Q" : - T == Float16 ? "e" : - T == Cfloat ? "f" : - T == Cdouble ? "d" : - T == Complex{Float16} ? "Ze" : - T == Complex{Cfloat} ? "Zf" : - T == Complex{Cdouble} ? "Zd" : - T == Bool ? "?" : - T == Ptr{Cvoid} ? "P" : - if isstructtype(T) && isconcretetype(T) && allocatedinline(T) - n = fieldcount(T) - flds = [] - for i = 1:n - nm = fieldname(T, i) - tp = fieldtype(T, i) - push!(flds, string(pybufferformat(tp), nm isa Symbol ? ":$nm:" : "")) - d = - (i == n ? sizeof(T) : fieldoffset(T, i + 1)) - - (fieldoffset(T, i) + sizeof(tp)) - @assert d ≥ 0 - d > 0 && push!(flds, "$(d)x") +pybufferformat(::Type{T}) where {T} = + get!(PYBUFFERFORMAT, T) do + T == Cchar ? "b" : + T == Cuchar ? "B" : + T == Cshort ? "h" : + T == Cushort ? "H" : + T == Cint ? "i" : + T == Cuint ? "I" : + T == Clong ? "l" : + T == Culong ? "L" : + T == Clonglong ? "q" : + T == Culonglong ? "Q" : + T == Float16 ? "e" : + T == Cfloat ? "f" : + T == Cdouble ? "d" : + T == Complex{Float16} ? "Ze" : + T == Complex{Cfloat} ? "Zf" : + T == Complex{Cdouble} ? "Zd" : + T == Bool ? "?" : + T == Ptr{Cvoid} ? "P" : + if isstructtype(T) && isconcretetype(T) && allocatedinline(T) + n = fieldcount(T) + flds = [] + for i = 1:n + nm = fieldname(T, i) + tp = fieldtype(T, i) + push!(flds, string(pybufferformat(tp), nm isa Symbol ? ":$nm:" : "")) + d = + (i == n ? sizeof(T) : fieldoffset(T, i + 1)) - + (fieldoffset(T, i) + sizeof(tp)) + @assert d ≥ 0 + d > 0 && push!(flds, "$(d)x") + end + string("T{", join(flds, " "), "}") + else + "$(sizeof(T))x" end - string("T{", join(flds, " "), "}") - else - "$(sizeof(T))x" end -end pyjlarray_isarrayabletype(::Type{T}) where {T} = T in ( UInt8, @@ -227,44 +228,45 @@ pyjlarray_isarrayabletype(::Type{NamedTuple{names,types}}) where {names,types} = const PYTYPESTRDESCR = IdDict{Type,Tuple{String,Py}}() -pytypestrdescr(::Type{T}) where {T} = get!(PYTYPESTRDESCR, T) do - c = Utils.islittleendian() ? '<' : '>' - T == Bool ? ("$(c)b$(sizeof(Bool))", PyNULL) : - T == Int8 ? ("$(c)i1", PyNULL) : - T == UInt8 ? ("$(c)u1", PyNULL) : - T == Int16 ? ("$(c)i2", PyNULL) : - T == UInt16 ? ("$(c)u2", PyNULL) : - T == Int32 ? ("$(c)i4", PyNULL) : - T == UInt32 ? ("$(c)u4", PyNULL) : - T == Int64 ? ("$(c)i8", PyNULL) : - T == UInt64 ? ("$(c)u8", PyNULL) : - T == Float16 ? ("$(c)f2", PyNULL) : - T == Float32 ? ("$(c)f4", PyNULL) : - T == Float64 ? ("$(c)f8", PyNULL) : - T == Complex{Float16} ? ("$(c)c4", PyNULL) : - T == Complex{Float32} ? ("$(c)c8", PyNULL) : - T == Complex{Float64} ? ("$(c)c16", PyNULL) : - if isstructtype(T) && isconcretetype(T) && Base.allocatedinline(T) - n = fieldcount(T) - flds = [] - for i = 1:n - nm = fieldname(T, i) - tp = fieldtype(T, i) - ts, ds = pytypestrdescr(tp) - isempty(ts) && return ("", PyNULL) - push!( - flds, - (nm isa Integer ? "f$(nm-1)" : string(nm), pyisnull(ds) ? ts : ds), - ) - d = (i == n ? sizeof(T) : fieldoffset(T, i + 1)) - (fieldoffset(T, i) + sizeof(tp)) - @assert d ≥ 0 - d > 0 && push!(flds, ("", "|V$(d)")) +pytypestrdescr(::Type{T}) where {T} = + get!(PYTYPESTRDESCR, T) do + c = Utils.islittleendian() ? '<' : '>' + T == Bool ? ("$(c)b$(sizeof(Bool))", PyNULL) : + T == Int8 ? ("$(c)i1", PyNULL) : + T == UInt8 ? ("$(c)u1", PyNULL) : + T == Int16 ? ("$(c)i2", PyNULL) : + T == UInt16 ? ("$(c)u2", PyNULL) : + T == Int32 ? ("$(c)i4", PyNULL) : + T == UInt32 ? ("$(c)u4", PyNULL) : + T == Int64 ? ("$(c)i8", PyNULL) : + T == UInt64 ? ("$(c)u8", PyNULL) : + T == Float16 ? ("$(c)f2", PyNULL) : + T == Float32 ? ("$(c)f4", PyNULL) : + T == Float64 ? ("$(c)f8", PyNULL) : + T == Complex{Float16} ? ("$(c)c4", PyNULL) : + T == Complex{Float32} ? ("$(c)c8", PyNULL) : + T == Complex{Float64} ? ("$(c)c16", PyNULL) : + if isstructtype(T) && isconcretetype(T) && Base.allocatedinline(T) + n = fieldcount(T) + flds = [] + for i = 1:n + nm = fieldname(T, i) + tp = fieldtype(T, i) + ts, ds = pytypestrdescr(tp) + isempty(ts) && return ("", PyNULL) + push!( + flds, + (nm isa Integer ? "f$(nm-1)" : string(nm), pyisnull(ds) ? ts : ds), + ) + d = (i == n ? sizeof(T) : fieldoffset(T, i + 1)) - (fieldoffset(T, i) + sizeof(tp)) + @assert d ≥ 0 + d > 0 && push!(flds, ("", "|V$(d)")) + end + ("|$(sizeof(T))V", pylist(flds)) + else + ("", PyNULL) end - ("|$(sizeof(T))V", pylist(flds)) - else - ("", PyNULL) end -end pyjlarray_array__array(x::AbstractArray) = x isa Array ? Py(nothing) : pyjlarray(Array(x)) pyjlarray_array__pyobjectarray(x::AbstractArray) = pyjlarray(PyObjectArray(x)) @@ -316,8 +318,6 @@ function init_array() self._jl_callmethod($(pyjl_methodnum(pyjlarray_setitem)), k, v) def __delitem__(self, k): self._jl_callmethod($(pyjl_methodnum(pyjlarray_delitem)), k) - def __iter__(self): - return self._jl_callmethod($(pyjl_methodnum(pyjliter ∘ Iterator))) @property def __array_interface__(self): return self._jl_callmethod($(pyjl_methodnum(pyjlarray_array_interface))) diff --git a/src/JlWrap/dict.jl b/src/JlWrap/dict.jl index 66586ddf..bcedc9b4 100644 --- a/src/JlWrap/dict.jl +++ b/src/JlWrap/dict.jl @@ -21,7 +21,7 @@ pyjldict_delitem(x::AbstractDict, k::Py) = (delete!(x, pyconvert(keytype(x), k)) function pyjldict_update(x::AbstractDict, items_::Py) for item_ in items_ - (k, v) = pyconvert(Tuple{keytype(x), valtype(x)}, item_) + (k, v) = pyconvert(Tuple{keytype(x),valtype(x)}, item_) x[k] = v end Py(nothing) diff --git a/src/JlWrap/io.jl b/src/JlWrap/io.jl index a0d77afb..fb8cd9d8 100644 --- a/src/JlWrap/io.jl +++ b/src/JlWrap/io.jl @@ -205,10 +205,12 @@ function init_io() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class JlIOBase(JlBase, _JlReprMixin, _JlHashMixin): + class JlIOBase(JlBase, _JlReprMixin): __slots__ = () def __init__(self, value): JlBase.__init__(self, value, Base.IO) + def __hash__(self): + return self._jl_callmethod($(pyjl_methodnum(pyjlany_hash))) def close(self): return self._jl_callmethod($(pyjl_methodnum(pyjlio_close))) @property diff --git a/src/JlWrap/set.jl b/src/JlWrap/set.jl index a3e7b805..2a3a420c 100644 --- a/src/JlWrap/set.jl +++ b/src/JlWrap/set.jl @@ -81,8 +81,6 @@ function init_set() __slots__ = () def __init__(self, value=None): JlBase.__init__(self, value, Base.AbstractSet) - def __iter__(self): - return self._jl_callmethod($(pyjl_methodnum(pyjliter ∘ Iterator))) def add(self, value): return self._jl_callmethod($(pyjl_methodnum(pyjlset_add)), value) def discard(self, value): diff --git a/test/JlWrap.jl b/test/JlWrap.jl index 82170311..1b62df40 100644 --- a/test/JlWrap.jl +++ b/test/JlWrap.jl @@ -353,6 +353,29 @@ end @test pyeq(Bool, m.suboffsets, ()) @test pyeq(Bool, m.tolist(), pylist([pylist([1, 2, 3]), pylist([4, 5, 6])])) end + @testset "iter" begin + @test pyeq(Bool, pybuiltins.list(pyjlarray(fill(0))), pylist([0])) + @test pyeq(Bool, pybuiltins.list(pyjlarray([1, 2, 3])), pylist([1, 2, 3])) + @test pyeq(Bool, pybuiltins.list(pyjlarray([1 2; 3 4])), pylist([1, 3, 2, 4])) + end + @testset "eq" begin + @test pyeq(Bool, pyjlarray([1, 2, 3]), pyjlarray([1, 2, 3])) + @test pyeq(Bool, pyjlarray([1, 2, 3]), pyjlarray(1:3)) + @test !pyeq(Bool, pyjlarray([1, 2, 3]), pyjlarray([2, 3, 4])) + @test !pyeq(Bool, pyjlarray([1, 2, 3]), pylist([1, 2, 3])) + end + @testset "hash" begin + @test pyhash(pyjlarray([1, 2, 3])) == pyhash(pyjlarray([1, 2, 3])) + @test pyhash(pyjlarray([1, 2, 3])) == pyhash(pyjlarray(1:3)) + @test pyhash(pyjlarray([1, 2, 3])) != pyhash(pyjlarray([2, 3, 4])) + @test pyhash(pyjlarray([1, 2, 3])) != pyhash(pytuple([1, 2, 3])) + end + @testset "len" begin + @test pylen(pyjlarray([1, 2, 3])) == 3 + @test pylen(pyjlarray([])) == 0 + @test pylen(pyjlarray(fill(0))) == 1 + @test pylen(pyjlarray([1 2; 3 4])) == 4 + end end @testitem "base" begin From 201fbf778310037404993b49a4432fa6c997abab Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Tue, 11 Jun 2024 18:00:29 +0100 Subject: [PATCH 24/39] add JlContainer --- src/JlWrap/JlWrap.jl | 4 +++- src/JlWrap/any.jl | 15 --------------- src/JlWrap/array.jl | 2 +- src/JlWrap/collection.jl | 39 +++++++++++++++++++++++++++++++++++++++ src/JlWrap/dict.jl | 2 +- src/JlWrap/set.jl | 2 +- 6 files changed, 45 insertions(+), 19 deletions(-) create mode 100644 src/JlWrap/collection.jl diff --git a/src/JlWrap/JlWrap.jl b/src/JlWrap/JlWrap.jl index d79885ba..3f2e98b9 100644 --- a/src/JlWrap/JlWrap.jl +++ b/src/JlWrap/JlWrap.jl @@ -20,6 +20,7 @@ include("base.jl") include("any.jl") include("io.jl") include("objectarray.jl") +include("collection.jl") include("array.jl") include("vector.jl") include("dict.jl") @@ -27,10 +28,11 @@ include("set.jl") include("callback.jl") function __init__() - Cjl.C.with_gil() do + Cjl.C.with_gil() do init_base() init_any() init_io() + init_collection() init_array() init_vector() init_dict() diff --git a/src/JlWrap/any.jl b/src/JlWrap/any.jl index df42c124..0160f7c1 100644 --- a/src/JlWrap/any.jl +++ b/src/JlWrap/any.jl @@ -355,21 +355,6 @@ function init_any() return self._jl_callmethod($(pyjl_methodnum(pyjlany_hash))) def __next__(self): return self._jl_callmethod($(pyjl_methodnum(pyjliter_next))) - class _JlContainerMixin(_JlReprMixin): - __slots__ = () - def __len__(self): - return self._jl_callmethod($(pyjl_methodnum(pyint ∘ length))) - def __bool__(self): - return self._jl_callmethod($(pyjl_methodnum(pybool ∘ !isempty))) - def __iter__(self): - return self._jl_callmethod($(pyjl_methodnum(pyjliter ∘ Iterator))) - def __hash__(self): - return self._jl_callmethod($(pyjl_methodnum(pyjlany_hash))) - def __eq__(self, other): - if isinstance(self, type(other)) or isinstance(other, type(self)): - return self._jl_callmethod($(pyjl_methodnum(pyjlmixin_eq_bool)), other) - else: - return NotImplemented class Jl(JlBase, _JlReprMixin): __slots__ = () def __str__(self): diff --git a/src/JlWrap/array.jl b/src/JlWrap/array.jl index d74f892d..3f892d59 100644 --- a/src/JlWrap/array.jl +++ b/src/JlWrap/array.jl @@ -297,7 +297,7 @@ function init_array() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class JlArray(JlBase, _JlContainerMixin): + class JlArray(JlCollection): __slots__ = () _jl_buffer_info = $(pyjl_methodnum(pyjlarray_buffer_info)) def __init__(self, value): diff --git a/src/JlWrap/collection.jl b/src/JlWrap/collection.jl new file mode 100644 index 00000000..31220f2c --- /dev/null +++ b/src/JlWrap/collection.jl @@ -0,0 +1,39 @@ +const pyjlcollectiontype = pynew() + +function init_collection() + jl = pyjuliacallmodule + pybuiltins.exec(pybuiltins.compile(""" + $("\n"^(@__LINE__()-1)) + class JlCollection(JlBase, _JlReprMixin): + __slots__ = () + def __len__(self): + return self._jl_callmethod($(pyjl_methodnum(pyint ∘ length))) + def __bool__(self): + return self._jl_callmethod($(pyjl_methodnum(pybool ∘ !isempty))) + def __iter__(self): + return self._jl_callmethod($(pyjl_methodnum(pyjliter ∘ Iterator))) + def __hash__(self): + return self._jl_callmethod($(pyjl_methodnum(pyjlany_hash))) + def __eq__(self, other): + if isinstance(self, type(other)) or isinstance(other, type(self)): + return self._jl_callmethod($(pyjl_methodnum(pyjlmixin_eq_bool)), other) + else: + return NotImplemented + def copy(self): + return type(self)(Base.copy(self)) + import collections.abc + collections.abc.Collection.register(JlCollection) + del collections + """, @__FILE__(), "exec"), jl.__dict__) + pycopy!(pyjlcollectiontype, jl.JlCollection) +end + +""" + pyjlcollection(x::Union{AbstractArray,AbstractSet,AbstractDict}) + +Wrap `x` as a Python `collections.abc.Collection` object. +""" +pyjlcollection(x::Union{AbstractArray,AbstractSet,AbstractDict}) = pyjl(pyjlcollectiontype, x) +export pyjlcollection + +# Py(x::AbstractSet) = pyjlcollection(x) diff --git a/src/JlWrap/dict.jl b/src/JlWrap/dict.jl index bcedc9b4..1763c53e 100644 --- a/src/JlWrap/dict.jl +++ b/src/JlWrap/dict.jl @@ -33,7 +33,7 @@ function init_dict() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class JlDict(JlBase, _JlContainerMixin): + class JlDict(JlCollection): __slots__ = () _jl_undefined_ = object() def __init__(self, value=None): diff --git a/src/JlWrap/set.jl b/src/JlWrap/set.jl index 2a3a420c..bbb54ad9 100644 --- a/src/JlWrap/set.jl +++ b/src/JlWrap/set.jl @@ -77,7 +77,7 @@ function init_set() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class JlSet(JlBase, _JlContainerMixin): + class JlSet(JlCollection): __slots__ = () def __init__(self, value=None): JlBase.__init__(self, value, Base.AbstractSet) From 8391bd69b8464446d70cdc77019b156a56acdc20 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Tue, 11 Jun 2024 18:29:53 +0100 Subject: [PATCH 25/39] eliminate mixins and add contains and clear to JlCollection --- src/JlWrap/any.jl | 8 +++----- src/JlWrap/array.jl | 2 -- src/JlWrap/collection.jl | 30 ++++++++++++++++++++++++------ src/JlWrap/dict.jl | 6 ------ src/JlWrap/io.jl | 2 +- src/JlWrap/set.jl | 6 ------ src/JlWrap/vector.jl | 11 ++--------- 7 files changed, 30 insertions(+), 35 deletions(-) diff --git a/src/JlWrap/any.jl b/src/JlWrap/any.jl index 0160f7c1..476aee65 100644 --- a/src/JlWrap/any.jl +++ b/src/JlWrap/any.jl @@ -332,13 +332,11 @@ end pyjlany_hash(self) = pyint(hash(self)) -pyjlmixin_eq_bool(self, other) = pybool((self == pyjlvalue(other))::Bool) - function init_any() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class _JlReprMixin: + class JlBase2(JlBase): __slots__ = () def __repr__(self): t = type(self) @@ -347,7 +345,7 @@ function init_any() else: name = t.__name__ return name + ":" + self._jl_callmethod($(pyjl_methodnum(pyjlany_repr))) - class JlIter(JlBase, _JlReprMixin): + class JlIter(JlBase2): __slots__ = () def __iter__(self): return self @@ -355,7 +353,7 @@ function init_any() return self._jl_callmethod($(pyjl_methodnum(pyjlany_hash))) def __next__(self): return self._jl_callmethod($(pyjl_methodnum(pyjliter_next))) - class Jl(JlBase, _JlReprMixin): + class Jl(JlBase2): __slots__ = () def __str__(self): return self._jl_callmethod($(pyjl_methodnum(pyjlany_str))) diff --git a/src/JlWrap/array.jl b/src/JlWrap/array.jl index 3f892d59..fa52560b 100644 --- a/src/JlWrap/array.jl +++ b/src/JlWrap/array.jl @@ -308,8 +308,6 @@ function init_array() @property def shape(self): return self._jl_callmethod($(pyjl_methodnum(Py ∘ size))) - def copy(self): - return self._jl_callmethod($(pyjl_methodnum(Py ∘ copy))) def reshape(self, shape): return self._jl_callmethod($(pyjl_methodnum(pyjlarray_reshape)), shape) def __getitem__(self, k): diff --git a/src/JlWrap/collection.jl b/src/JlWrap/collection.jl index 31220f2c..d740597e 100644 --- a/src/JlWrap/collection.jl +++ b/src/JlWrap/collection.jl @@ -1,10 +1,16 @@ const pyjlcollectiontype = pynew() +pyjlcollection_clear(x) = (empty!(x); Py(nothing)) + +pyjlcollection_contains(x, v::Py) = pybool(in(@pyconvert(eltype(x), v, (return Py(false))), x)::Bool) + +pyjlcollection_eq(self, other) = pybool((self == pyjlvalue(other))::Bool) + function init_collection() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class JlCollection(JlBase, _JlReprMixin): + class JlCollection(JlBase2): __slots__ = () def __len__(self): return self._jl_callmethod($(pyjl_methodnum(pyint ∘ length))) @@ -16,11 +22,15 @@ function init_collection() return self._jl_callmethod($(pyjl_methodnum(pyjlany_hash))) def __eq__(self, other): if isinstance(self, type(other)) or isinstance(other, type(self)): - return self._jl_callmethod($(pyjl_methodnum(pyjlmixin_eq_bool)), other) + return self._jl_callmethod($(pyjl_methodnum(pyjlcollection_eq)), other) else: return NotImplemented + def __contains__(self, v): + return self._jl_callmethod($(pyjl_methodnum(pyjlcollection_contains)), v) def copy(self): - return type(self)(Base.copy(self)) + return self._jl_callmethod($(pyjl_methodnum(pyjlcollection ∘ copy))) + def clear(self): + return self._jl_callmethod($(pyjl_methodnum(pyjlcollection_clear))) import collections.abc collections.abc.Collection.register(JlCollection) del collections @@ -29,11 +39,19 @@ function init_collection() end """ - pyjlcollection(x::Union{AbstractArray,AbstractSet,AbstractDict}) + pyjlcollection(x) Wrap `x` as a Python `collections.abc.Collection` object. + +The argument should be a collection of values, in the sense of supporting `iterate`, +`hash`, `in` and `length`. This includes `AbstractArray`, `AbstractSet`, `AbstractDict`, +`Tuple`, `Base.RefValue` (`Ref(...)`) and `Base.ValueIterator` (`values(Dict(...))`). """ -pyjlcollection(x::Union{AbstractArray,AbstractSet,AbstractDict}) = pyjl(pyjlcollectiontype, x) +pyjlcollection(x) = pyjl(pyjlcollectiontype, x) +pyjlcollection(x::AbstractSet) = pyjlset(x) +pyjlcollection(x::AbstractArray) = pyjlarray(x) +pyjlcollection(x::AbstractDict) = pyjldict(x) export pyjlcollection -# Py(x::AbstractSet) = pyjlcollection(x) +Py(x::Base.ValueIterator) = pyjlcollection(x) +Py(x::Base.RefValue) = pyjlcollection(x) diff --git a/src/JlWrap/dict.jl b/src/JlWrap/dict.jl index 1763c53e..154764ef 100644 --- a/src/JlWrap/dict.jl +++ b/src/JlWrap/dict.jl @@ -11,8 +11,6 @@ Base.in(v::Tuple{Any,Any}, x::DictPairSet) = Pair(v[1], v[2]) in x.dict pyjldict_contains(x::AbstractDict, k::Py) = Py(haskey(x, @pyconvert(keytype(x), k, return Py(false)))) -pyjldict_clear(x::AbstractDict) = (empty!(x); Py(nothing)) - pyjldict_getitem(x::AbstractDict, k::Py) = Py(x[pyconvert(keytype(x), k)]) pyjldict_setitem(x::AbstractDict, k::Py, v::Py) = (x[pyconvertarg(keytype(x), k, "key")] = pyconvertarg(valtype(x), v, "value"); Py(nothing)) @@ -71,8 +69,6 @@ function init_dict() if key not in self: self[key] = default return self[key] - def clear(self): - return self._jl_callmethod($(pyjl_methodnum(pyjldict_clear))) def pop(self, key, default=_jl_undefined_): if key in self: ans = self[key] @@ -98,8 +94,6 @@ function init_dict() self._jl_callmethod($(pyjl_methodnum(pyjldict_update)), items) if kwargs: self.update(kwargs) - def copy(self): - return self._jl_callmethod($(pyjl_methodnum(Py ∘ copy))) import collections.abc collections.abc.MutableMapping.register(JlDict) del collections diff --git a/src/JlWrap/io.jl b/src/JlWrap/io.jl index fb8cd9d8..a1662f22 100644 --- a/src/JlWrap/io.jl +++ b/src/JlWrap/io.jl @@ -205,7 +205,7 @@ function init_io() jl = pyjuliacallmodule pybuiltins.exec(pybuiltins.compile(""" $("\n"^(@__LINE__()-1)) - class JlIOBase(JlBase, _JlReprMixin): + class JlIOBase(JlBase2): __slots__ = () def __init__(self, value): JlBase.__init__(self, value, Base.IO) diff --git a/src/JlWrap/set.jl b/src/JlWrap/set.jl index bbb54ad9..47caa9c7 100644 --- a/src/JlWrap/set.jl +++ b/src/JlWrap/set.jl @@ -8,8 +8,6 @@ function pyjlset_discard(x::AbstractSet, v_::Py) Py(nothing) end -pyjlset_clear(x::AbstractSet) = (empty!(x); Py(nothing)) - function pyjlset_pop(x::AbstractSet) if isempty(x) errset(pybuiltins.KeyError, "pop from an empty set") @@ -85,10 +83,6 @@ function init_set() return self._jl_callmethod($(pyjl_methodnum(pyjlset_add)), value) def discard(self, value): return self._jl_callmethod($(pyjl_methodnum(pyjlset_discard)), value) - def clear(self): - return self._jl_callmethod($(pyjl_methodnum(pyjlset_clear))) - def copy(self): - return self._jl_callmethod($(pyjl_methodnum(pyjlset ∘ copy))) def pop(self): return self._jl_callmethod($(pyjl_methodnum(pyjlset_pop))) def remove(self, value): diff --git a/src/JlWrap/vector.jl b/src/JlWrap/vector.jl index b78bd0e0..7b5074a7 100644 --- a/src/JlWrap/vector.jl +++ b/src/JlWrap/vector.jl @@ -25,11 +25,6 @@ function pyjlvector_reverse(x::AbstractVector) Py(nothing) end -function pyjlvector_clear(x::AbstractVector) - empty!(x) - Py(nothing) -end - function pyjlvector_reversed(x::AbstractVector) Py(reverse(x)) end @@ -39,12 +34,12 @@ function pyjlvector_insert(x::AbstractVector, k_::Py, v_::Py) pydel!(k_) a = axes(x, 1) k′ = k < 0 ? (last(a) + 1 + k) : (first(a) + k) - if checkbounds(Bool, x, k′) || k′ == last(a)+1 + if checkbounds(Bool, x, k′) || k′ == last(a) + 1 v = pyconvertarg(eltype(x), v_, "value") insert!(x, k′, v) return Py(nothing) else - errset(pybuiltins.IndexError, "array index out of bounds"); + errset(pybuiltins.IndexError, "array index out of bounds") return PyNULL end end @@ -133,8 +128,6 @@ function init_vector() return self._jl_callmethod($(pyjl_methodnum(pyjlvector_sort)), reverse, key) def reverse(self): return self._jl_callmethod($(pyjl_methodnum(pyjlvector_reverse))) - def clear(self): - return self._jl_callmethod($(pyjl_methodnum(pyjlvector_clear))) def __reversed__(self): return self._jl_callmethod($(pyjl_methodnum(pyjlvector_reversed))) def insert(self, index, value): From 192d7d5009bb00d73d3df7299221dbff7fcab879 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Tue, 11 Jun 2024 18:36:04 +0100 Subject: [PATCH 26/39] fix keys, values, items --- src/JlWrap/dict.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/JlWrap/dict.jl b/src/JlWrap/dict.jl index 154764ef..b6c7abbb 100644 --- a/src/JlWrap/dict.jl +++ b/src/JlWrap/dict.jl @@ -55,11 +55,11 @@ function init_dict() else: raise KeyError(key) def keys(self): - return self._jl_callmethod($(pyjl_methodnum(pyset ∘ keys))) + return self._jl_callmethod($(pyjl_methodnum(pyjlset ∘ keys))) def values(self): - return self._jl_callmethod($(pyjl_methodnum(Py ∘ values))) + return self._jl_callmethod($(pyjl_methodnum(pyjlcollection ∘ values))) def items(self): - return self._jl_callmethod($(pyjl_methodnum(pyset ∘ DictPairSet))) + return self._jl_callmethod($(pyjl_methodnum(pyjlset ∘ DictPairSet))) def get(self, key, default=None): if key in self: return self[key] From 6ee17e18d464855ab06d1549777ece2422bc1957 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Tue, 11 Jun 2024 19:06:47 +0100 Subject: [PATCH 27/39] fix tests --- test/PyMacro.jl | 48 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/test/PyMacro.jl b/test/PyMacro.jl index d3a1b1b0..cb8fc848 100644 --- a/test/PyMacro.jl +++ b/test/PyMacro.jl @@ -84,11 +84,11 @@ @test x isa Py @test pyis(pytype(x), pybuiltins.dict) @test pyeq(Bool, x, pydict()) - x = @py {x=1, y=2} + x = @py {x = 1, y = 2} @test x isa Py @test pyis(pytype(x), pybuiltins.dict) @test pyeq(Bool, x, pydict(x=1, y=2)) - x = @py {"x": 1, "y": 2} + x = @py {"x":1, "y":2} @test x isa Py @test pyis(pytype(x), pybuiltins.dict) @test pyeq(Bool, x, pydict(x=1, y=2)) @@ -197,35 +197,59 @@ @test pyis(_ver, sys.version_info) end @testset "short-circuit" begin - x = @py 3 && pylist([1,2]) + x = @py 3 && @jl(pylist([1, 2])) @test pyeq(Bool, x, pylist([1, 2])) x = @py None && True @test pyis(x, pybuiltins.None) - x = @py None || 0 || pyset() + x = @py None || 0 || @jl(pyset()) @test pyeq(Bool, x, pyset()) - x = @py pydict() || 8 || "" + x = @py @jl(pydict()) || 8 || "" @test pyeq(Bool, x, 8) end @testset "if" begin - x = @py if 1 == 2; "a"; end + x = @py if 1 == 2 + "a" + end @test x isa Py @test pyis(x, pybuiltins.None) - x = @py if 1 < 2; "a"; end + x = @py if 1 < 2 + "a" + end @test x isa Py @test pyeq(Bool, x, "a") - x = @py if 1 == 2; "a"; else; "b"; end + x = @py if 1 == 2 + "a" + else + "b" + end @test x isa Py @test pyeq(Bool, x, "b") - x = @py if 1 < 2; "a"; else; "b"; end + x = @py if 1 < 2 + "a" + else + "b" + end @test x isa Py @test pyeq(Bool, x, "a") - x = @py if 1 == 2; "a"; elseif 1 < 2; "b"; end + x = @py if 1 == 2 + "a" + elseif 1 < 2 + "b" + end @test x isa Py @test pyeq(Bool, x, "b") - x = @py if 1 < 2; "a"; elseif 2 < 3; "b"; end + x = @py if 1 < 2 + "a" + elseif 2 < 3 + "b" + end @test x isa Py @test pyeq(Bool, x, "a") - x = @py if 1 == 2; "a"; elseif 2 == 3; "b"; end + x = @py if 1 == 2 + "a" + elseif 2 == 3 + "b" + end @test x isa Py @test pyis(x, pybuiltins.None) end From a76e3d183424e0cc54196398ba6ff1247bdc3024 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Wed, 12 Jun 2024 10:14:12 +0100 Subject: [PATCH 28/39] tests for JlCollection --- test/JlWrap.jl | 128 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/test/JlWrap.jl b/test/JlWrap.jl index 1b62df40..da0bb661 100644 --- a/test/JlWrap.jl +++ b/test/JlWrap.jl @@ -225,6 +225,134 @@ end end +@testitem "collection" begin + cases = [ + ( + x=fill(nothing), + type=:array, + list=pylist([nothing]), + ), + ( + x=[1, 2, 3], + type=:vector, + list=pylist([1, 2, 3]), + ), + ( + x=[1 2; 3 4], + type=:array, + list=pylist([1, 3, 2, 4]), + ), + ( + x=Set([1, 2, 3]), + type=:set, + list=pylist(Set([1, 2, 3])), + ), + ( + x=Dict(1 => 2, 3 => 4), + type=:dict, + list=pylist(keys(Dict(1 => 2, 3 => 4))), + ), + ( + x=keys(Dict()), + type=:set, + list=pylist(), + ), + ( + x=values(Dict()), + type=:collection, + list=pylist(), + ), + ( + x=(1, 2, 3), + type=:collection, + list=pylist([1, 2, 3]), + ), + ( + x=(x=1, y=2), + type=:collection, + list=pylist([1, 2]), + ), + ( + x=Ref(nothing), + type=:collection, + list=pylist([nothing]), + ),] + @testset "type $(c.x)" for c in cases + y = pyjlcollection(c.x) + @test pyisinstance(y, PythonCall.JlWrap.pyjlcollectiontype) + @test pyis(pytype(y), getproperty(PythonCall.JlWrap, Symbol(:pyjl, c.type, :type))) + end + @testset "len $(c.x)" for c in cases + @test pylen(pyjlcollection(c.x)) == length(c.x) + end + @testset "bool $(c.x)" for c in cases + @test pytruth(pyjlcollection(c.x)) == !isempty(c.x) + end + @testset "iter $(c.x)" for c in cases + @test pyeq(Bool, pylist(pyjlcollection(c.x)), c.list) + end + @testset "hash $(c.x)" for c in cases + # not sure why but the bottom byte doesn't always match + @test mod(pyhash(pyjlcollection(c.x)), UInt32) >> 8 == mod(hash(c.x), UInt32) >> 8 + end + @testset "eq $(c1.x) $(c2.x)" for (c1, c2) in Iterators.product(cases, cases) + @test pyeq(Bool, pyjlcollection(c1.x), pyjlcollection(c2.x)) == (c1.x == c2.x) + end + @testset "contains $(c.x) $(v)" for (c, v) in Iterators.product(cases, [nothing, 0, 1, 2, 3, 4, 5, 0.0, 0.5, 1.0]) + if !isa(c.x, Dict) + @test pycontains(pyjlcollection(c.x), v) == (v in c.x) + end + end + @testset "copy $(c.x)" for c in cases + copyable = try + copy(c.x) + true + catch + false + end + y = pyjlcollection(c.x) + if copyable + z = y.copy() + @test pyis(pytype(y), pytype(z)) + yv = pyjlvalue(y) + zv = pyjlvalue(z) + @test yv === c.x + @test yv == zv + @test yv !== zv + else + @test_throws PyException y.copy() + end + end + @testset "clear $(c.x)" for c in cases + # make a copy or skip the test + x2 = try + copy(c.x) + catch + continue + end + len = length(x2) + # see if the collection can be emptied + clearable = try + empty!(copy(c.x)) + true + catch + false + end + # try clearing the collection + y = pyjlcollection(x2) + @test pylen(y) == len + if clearable + y.clear() + @test pylen(y) == 0 + @test length(x2) == 0 + else + @test_throws PyException y.clear() + @test pylen(y) == len + @test length(x2) == len + end + end +end + @testitem "array" begin @testset "type" begin @test pyis(pytype(pyjlarray(fill(nothing))), PythonCall.pyjlarraytype) From d9980313d1ac8753121863f25e2e827ef160167f Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Wed, 12 Jun 2024 10:43:51 +0100 Subject: [PATCH 29/39] update jlwrap docs --- docs/src/juliacall-reference.md | 47 ++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/docs/src/juliacall-reference.md b/docs/src/juliacall-reference.md index 916af967..52889e49 100644 --- a/docs/src/juliacall-reference.md +++ b/docs/src/juliacall-reference.md @@ -42,10 +42,11 @@ interface and behaves very similar to a list. - `JlBase` - [`Jl`](#juliacall.Jl) - - [`JlArray`](#juliacall.JlArray) - - [`JlVector`](#juliacall.JlVector) - - [`JlDict`](#juliacall.JlDict) - - [`JlSet`](#juliacall.JlSet) + - [`JlCollection`](#juliacall.JlCollection) + - [`JlArray`](#juliacall.JlArray) + - [`JlVector`](#juliacall.JlVector) + - [`JlDict`](#juliacall.JlDict) + - [`JlSet`](#juliacall.JlSet) - [`JlIOBase`](#juliacall.JlIOBase) - `JlBinaryIO` - `JlTextIO` @@ -58,26 +59,44 @@ Wraps any Julia object, giving it some basic Python semantics. Supports `repr(x)`, `str(x)`, attributes (`x.attr`), calling (`x(a,b)`), iteration, comparisons, `len(x)`, `a in x`, `dir(x)`. -Calling, indexing, attribute access, etc. will convert the result to a Python object -according to [this table](@ref jl2py). This is typically a builtin Python type (for -immutables) or a subtype of `Jl`. +Calling, indexing, attribute access, etc. will always return a `Jl`. To get the result +as an ordinary Python object, you can use the `.jl_to_py()` method. -Attribute access can be used to access Julia properties as well as normal class members. In -the case of a name clash, the class member will take precedence. For convenience with Julia -naming conventions, `_b` at the end of an attribute is replaced with `!` and `_bb` is -replaced with `!!`. +Attribute access (`x.attr`) can be used to access Julia properties except those starting +and ending with `__` (since these are Python special methods) or starting with `jl_` or +`_jl_` (which are reserved by `juliacall` for Julia-specific methods). ###### Members -- `jl_raw()`: Convert to a [`RawValue`](#juliacall.RawValue). (See also [`pyjlraw`](@ref).) +- `jl_callback(*args, **kwargs)`: Calls the Julia object with the given arguments. + Unlike ordinary calling syntax, the arguments are passed as `Py` objects instead of + being converted. - `jl_display()`: Display the object using Julia's display mechanism. +- `jl_eval(expr)`: If the object is a Julia `Module`, evaluates the given expression. - `jl_help()`: Display help for the object. +- `jl_to_py()`: Convert to a Python object using the [usual conversion rules](@ref jl2py). +````` + +`````@customdoc +juliacall.JlCollection - Class + +Wraps any Julia collection. It is a subclass of `collections.abc.Collection`. + +Julia collections are arrays, sets, dicts, tuples, named tuples, refs, and in general +anything which is a collection of values in the sense that it supports functions like +`iterate`, `in`, `length`, `hash`, `==`, `isempty`, `copy`, `empty!`. + +It supports `in`, `iter`, `len`, `hash`, `bool`, `==`. + +###### Members +- `clear()`: Empty the collection in-place. +- `copy()`: A copy of the collection. ````` `````@customdoc juliacall.JlArray - Class This wraps any Julia `AbstractArray` value. It is a subclass of -`collections.abc.Collection`. +`juliacall.JlCollection`. It supports zero-up indexing, and can be indexed with integers or slices. Slicing returns a view of the original array. @@ -95,7 +114,6 @@ copy of the original array. ###### Members - `ndim`: The number of dimensions. - `shape`: Tuple of lengths in each dimension. -- `copy()`: A copy of the array. - `reshape(shape)`: A reshaped view of the array. - `to_numpy(dtype=None, copy=True, order="K")`: Convert to a numpy array. ````` @@ -110,7 +128,6 @@ This wraps any Julia `AbstractVector` value. It is a subclass of `juliacall.JlAr - `resize(size)`: Change the length of the vector. - `sort(reverse=False, key=None)`: Sort the vector in-place. - `reverse()`: Reverse the vector. -- `clear()`: Empty the vector. - `insert(index, value)`: Insert the value at the given index. - `append(value)`: Append the value to the end of the vector. - `extend(values)`: Append the values to the end of the vector. From 128071a3ec9af25aa9ecb5c0b1069e1c805a29e2 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Wed, 19 Jun 2024 16:48:36 +0100 Subject: [PATCH 30/39] Jl tests --- test/JlWrap.jl | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/test/JlWrap.jl b/test/JlWrap.jl index da0bb661..053cff04 100644 --- a/test/JlWrap.jl +++ b/test/JlWrap.jl @@ -24,6 +24,7 @@ Base.delete!(x::Foo, idx...) = (x.value = -sum(idx); x) Base.in(v::Int, x::Foo) = x.value == v Base.nameof(x::Foo) = "nameof $(x.value)" + Base.iterate(x::Foo, st::Int=1) = st <= x.value ? (st, st + 1) : nothing @testset "type" begin @test pyis(pytype(pyjl(Foo(1))), PythonCall.pyjlanytype) @test pyis(pytype(pyjl(nothing)), PythonCall.pyjlanytype) @@ -64,6 +65,10 @@ z = pyjl(Foo(1))(4, 5; foo=true, bar=true) @test pyconvert(String, z) == "1((4, 5))2" end + @testset "callback" begin + z = pyjl(Foo(1)).jl_callback(4, 5) + @test pyconvert(String, z) == "1((<py 4>, <py 5>))0" + end @testset "getitem" begin z = pygetitem(pyjl(Foo(1)), 3) @test pyconvert(String, z) == "1[(3,)]" @@ -223,6 +228,72 @@ @test pyconvert(Any, m.jl_eval("1 + 1")) === 2 # Basic behavior @test pyconvert(Any, m.jl_eval("1 + 1\n ")) === 2 # Trailing whitespace end + @testset "to_py $x" for x in [1, 2.3, nothing, "foo"] + y = Py(x) + z = pyjl(x).jl_to_py() + @test pyeq(Bool, pytype(z), pytype(y)) + @test pyeq(Bool, z, y) + end + @testset "iter" begin + z = pylist(pyjl(Foo(3))) + @test pyeq(Bool, z, pylist([pyjl(1), pyjl(2), pyjl(3)])) + end + @testset "next" begin + z = pynext(pyjl(Foo(3))) + @test pyisjl(z) + @test pyeq(Bool, z, pyjl(1)) + end + @testset "reversed" begin + x = pyjl([1, 2, 3]) + y = x.__reversed__() + @test pyisjl(y) + @test pyjlvalue(y) == [3, 2, 1] + @test pyjlvalue(x) == [1, 2, 3] + end + @testset "int" begin + x = pyjl(34.0) + y = x.__int__() + @test pyisinstance(y, pybuiltins.int) + @test pyeq(Bool, y, 34) + end + @testset "float" begin + x = pyjl(12) + y = x.__float__() + @test pyisinstance(y, pybuiltins.float) + @test pyeq(Bool, y, 12.0) + end + @testset "complex" begin + x = pyjl(Complex(1, 2)) + y = x.__complex__() + @test pyisinstance(y, pybuiltins.complex) + @test pyeq(Bool, y, pycomplex(1, 2)) + end + @testset "index" begin + y = pyjl(12).__index__() + @test pyisinstance(y, pybuiltins.int) + @test pyeq(Bool, y, 12) + @test_throws PyException pyjl(12.0).__index__() + end + @testset "trunc $x" for (x, y) in [(1.1, 1), (8.9, 8), (-1.3, -1), (-7.8, -7)] + z = pyjl(x).__trunc__() + @test pyisinstance(z, pybuiltins.int) + @test pyeq(Bool, z, y) + end + @testset "floor $x" for (x, y) in [(1.1, 1), (8.9, 8), (-1.3, -2), (-7.8, -8)] + z = pyjl(x).__floor__() + @test pyisinstance(z, pybuiltins.int) + @test pyeq(Bool, z, y) + end + @testset "ceil $x" for (x, y) in [(1.1, 2), (8.9, 9), (-1.3, -1), (-7.8, -7)] + z = pyjl(x).__ceil__() + @test pyisinstance(z, pybuiltins.int) + @test pyeq(Bool, z, y) + end + @testset "round $x" for (x, y) in [(1.1, 1), (8.9, 9), (-1.3, -1), (-7.8, -8)] + z = pyjl(x).__round__() + @test pyisinstance(z, pybuiltins.int) + @test pyeq(Bool, z, y) + end end @testitem "collection" begin From 93b6ea80cb07fcca22e9548b8ba04c843be08695 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Wed, 19 Jun 2024 17:15:09 +0100 Subject: [PATCH 31/39] JlDict tests --- test/JlWrap.jl | 116 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/test/JlWrap.jl b/test/JlWrap.jl index 053cff04..4c5855ac 100644 --- a/test/JlWrap.jl +++ b/test/JlWrap.jl @@ -593,6 +593,122 @@ end @test !pytruth(pyjldict(Dict())) @test pytruth(pyjldict(Dict("one" => 1, "two" => 2))) end + @testset "iter" begin + @test pyeq(Bool, pyset(pyjldict(Dict())), pyset()) + @test pyeq(Bool, pyset(pyjldict(Dict(1 => 2, 3 => 4))), pyset([1, 3])) + end + @testset "contains" begin + x = pyjldict(Dict(1 => 2, 3 => 4)) + @test pycontains(x, 1) + @test !pycontains(x, 2) + @test pycontains(x, 3) + @test !pycontains(x, 4) + @test !pycontains(x, 1.2) + @test pycontains(x, 1.0) + @test !pycontains(x, nothing) + end + @testset "getitem" begin + x = pyjldict(Dict(1 => 2, 3 => 4)) + @test pyisinstance(x[1], pybuiltins.int) + @test pyeq(Bool, x[1], 2) + @test pyisinstance(x[3], pybuiltins.int) + @test pyeq(Bool, x[3], 4) + @test_throws PyException x[2] + end + @testset "setitem" begin + x = Dict(1 => 2, 3 => 4) + y = pyjldict(x) + y[1] = pyint(11) + @test x[1] === 11 + y[2] = pyfloat(22.0) + @test x[2] === 22 + end + @testset "delitem" begin + x = Dict(1 => 2, 3 => 4) + y = pyjldict(x) + pydelitem(y, 3) + @test x == Dict(1 => 2) + pydelitem(y, 1) + @test x == Dict() + end + @testset "keys" begin + x = pyjldict(Dict(1 => 2, 3 => 4)).keys() + @test all(pyisinstance(k, pybuiltins.int) for k in x) + @test pyeq(Bool, pyset(x), pyset([1, 3])) + end + @testset "values" begin + x = pyjldict(Dict(1 => 2, 3 => 4)).values() + @test all(pyisinstance(k, pybuiltins.int) for k in x) + @test pyeq(Bool, pyset(x), pyset([2, 4])) + end + @testset "items" begin + x = pyjldict(Dict(1 => 2, 3 => 4)).items() + @test all(pyisinstance(i, pybuiltins.tuple) for i in x) + @test all(pylen(i) == 2 for i in x) + @test all(pyisinstance(i[0], pybuiltins.int) for i in x) + @test all(pyisinstance(i[1], pybuiltins.int) for i in x) + @test pyeq(Bool, pyset(x), pyset([(1, 2), (3, 4)])) + end + @testset "get" begin + x = pyjldict(Dict(1 => 2, 3 => 4)) + y = x.get(1) + @test pyisinstance(y, pybuiltins.int) + @test pyeq(Bool, y, 2) + y = x.get(3) + @test pyisinstance(y, pybuiltins.int) + @test pyeq(Bool, y, 4) + y = x.get(5) + @test pyis(y, pybuiltins.None) + y = x.get(5, 0) + @test pyisinstance(y, pybuiltins.int) + @test pyeq(Bool, y, 0) + end + @testset "setdefault" begin + x = Dict(1 => 2, 3 => 4) + y = pyjldict(x) + z = y.setdefault(1, 0) + @test pyisinstance(z, pybuiltins.int) + @test pyeq(Bool, z, 2) + @test x == Dict(1 => 2, 3 => 4) + z = y.setdefault(2, 0) + @test pyisinstance(z, pybuiltins.int) + @test pyeq(Bool, z, 0) + @test x == Dict(1 => 2, 3 => 4, 2 => 0) + z = y.setdefault(2, 99) + @test pyisinstance(z, pybuiltins.int) + @test pyeq(Bool, z, 0) + @test x == Dict(1 => 2, 3 => 4, 2 => 0) + end + @testset "pop" begin + x = Dict(1 => 2, 3 => 4) + y = pyjldict(x) + z1 = y.pop(1) + @test pyisinstance(z1, pybuiltins.int) + @test pyeq(Bool, z1, 2) + @test x == Dict(3 => 4) + @test_throws PyException y.pop(2) + z2 = y.pop(3) + @test pyisinstance(z2, pybuiltins.int) + @test pyeq(Bool, z2, 4) + @test x == Dict() + end + @testset "popitem" begin + x = Dict(1 => 2, 3 => 4) + y = pyjldict(x) + z1 = y.popitem() + z2 = y.popitem() + @test all(pyisinstance(z, pybuiltins.tuple) for z in [z1, z2]) + @test all(pylen(z) == 2 for z in [z1, z2]) + @test all(pyisinstance(z[0], pybuiltins.int) for z in [z1, z2]) + @test all(pyisinstance(z[1], pybuiltins.int) for z in [z1, z2]) + @test pyeq(Bool, pyset([z1, z2]), pyset([(1, 2), (3, 4)])) + end + @testset "update" begin + x = Dict(1 => 2, 3 => 4) + y = pyjldict(x) + y.update(pydict([(3, 3.0), (2, 2.0)])) + @test x == Dict(1 => 2, 2 => 2, 3 => 3) + end end @testitem "io" begin From 09c760c306895c4453331c62a1e1716f803ae65a Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Wed, 19 Jun 2024 17:30:42 +0100 Subject: [PATCH 32/39] PySet tests --- test/JlWrap.jl | 108 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/test/JlWrap.jl b/test/JlWrap.jl index 4c5855ac..d4a97f3a 100644 --- a/test/JlWrap.jl +++ b/test/JlWrap.jl @@ -742,6 +742,114 @@ end @test !pytruth(pyjlset(Set())) @test pytruth(pyjlset(Set([1, 2, 3]))) end + @testset "add" begin + x = Set([1, 2, 3]) + y = pyjlset(x) + y.add(1) + @test x == Set([1, 2, 3]) + y.add(0) + @test x == Set([0, 1, 2, 3]) + @test_throws PyException y.add(nothing) + end + @testset "discard" begin + x = Set([1, 2, 3]) + y = pyjlset(x) + y.discard(1) + @test x == Set([2, 3]) + y.discard(1) + @test x == Set([2, 3]) + end + @testset "pop" begin + x = Set([1, 2, 3]) + y = pyjlset(x) + zs = Py[] + for i in 1:3 + push!(zs, y.pop()) + @test length(x) == 3 - i + end + @test_throws PyException y.pop() + @test all(pyisinstance(z, pybuiltins.int) for z in zs) + @test pyeq(Bool, pyset(zs), pyset([1, 2, 3])) + end + @testset "remove" begin + x = Set([1, 2, 3]) + y = pyjlset(x) + y.remove(1) + @test x == Set([2, 3]) + @test_throws PyException y.remove(1) + end + @testset "difference" begin + x = Set([1, 2, 3]) + y = pyjlset(x) + z = y.difference(pyset([1, 3, 5])) + @test pyeq(Bool, z, pyset([2])) + end + @testset "intersection" begin + x = Set([1, 2, 3]) + y = pyjlset(x) + z = y.intersection(pyset([1, 3, 5])) + @test pyeq(Bool, z, pyset([1, 3])) + end + @testset "symmetric_difference" begin + x = Set([1, 2, 3]) + y = pyjlset(x) + z = y.symmetric_difference(pyset([1, 3, 5])) + @test pyeq(Bool, z, pyset([2, 5])) + end + @testset "union" begin + x = Set([1, 2, 3]) + y = pyjlset(x) + z = y.union(pyset([1, 3, 5])) + @test pyeq(Bool, z, pyset([1, 2, 3, 5])) + end + @testset "isdisjoint" begin + x = Set([1, 2, 3]) + y = pyjlset(x) + z = y.isdisjoint(pyset([1, 3, 5])) + @test pyeq(Bool, z, false) + z = y.isdisjoint(pyset([0, 5])) + @test pyeq(Bool, z, true) + end + @testset "issubset" begin + x = Set([1, 2, 3]) + y = pyjlset(x) + z = y.issubset(pyset([1, 3, 5])) + @test pyeq(Bool, z, false) + z = y.issubset(pyset([1, 2, 3, 4, 5])) + @test pyeq(Bool, z, true) + end + @testset "issuperset" begin + x = Set([1, 2, 3]) + y = pyjlset(x) + z = y.issuperset(pyset([1, 3, 5])) + @test pyeq(Bool, z, false) + z = y.issuperset(pyset([1, 3])) + @test pyeq(Bool, z, true) + end + @testset "difference_update" begin + x = Set([1, 2, 3]) + y = pyjlset(x) + y.difference_update(pyset([1, 3, 5])) + @test x == Set([2]) + end + @testset "intersection_update" begin + x = Set([1, 2, 3]) + y = pyjlset(x) + y.intersection_update(pyset([1, 3, 5])) + @test x == Set([1, 3]) + end + @testset "symmetric_difference_update" begin + x = Set([1, 2, 3]) + y = pyjlset(x) + y.symmetric_difference_update(pyset([1, 3, 5])) + @test x == Set([2, 5]) + end + @testset "update" begin + x = Set([1, 2, 3]) + y = pyjlset(x) + y.update(pyset([1, 3, 5])) + @test x == Set([1, 2, 3, 5]) + end end @testitem "vector" begin From 674ab2c0cc589f218ba141918a9cd71ec0f0226b Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Wed, 19 Jun 2024 18:08:58 +0100 Subject: [PATCH 33/39] JlIO tests --- test/JlWrap.jl | 258 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 256 insertions(+), 2 deletions(-) diff --git a/test/JlWrap.jl b/test/JlWrap.jl index d4a97f3a..cbd95e1e 100644 --- a/test/JlWrap.jl +++ b/test/JlWrap.jl @@ -711,15 +711,269 @@ end end end -@testitem "io" begin +@testitem "io/base" begin + @testset "close" begin + x = IOBuffer() + print(x, "hello\n") + seekstart(x) + y = pytextio(x) + @test isopen(x) + y.close() + @test !isopen(x) + end + @testset "closed" begin + x = IOBuffer() + print(x, "hello\n") + seekstart(x) + y = pytextio(x) + z = y.closed + @test pyisinstance(z, pybuiltins.bool) + @test pyeq(Bool, z, false) + close(x) + z = y.closed + @test pyisinstance(z, pybuiltins.bool) + @test pyeq(Bool, z, true) + end + @testset "fileno" begin + x = IOBuffer() + print(x, "hello\n") + seekstart(x) + y = pytextio(x) + # TODO + end + @testset "flush" begin + x = IOBuffer() + print(x, "hello\n") + seekstart(x) + y = pytextio(x) + # TODO + end + @testset "isatty $(typeof(x))" for (x, y) in [(IOBuffer(), false), (devnull, false), (stdout, false)] + # TODO: how to get a TTY in a test environment?? + z = pytextio(x).isatty() + @test pyisinstance(z, pybuiltins.bool) + @test pyeq(Bool, z, y) + end + @testset "readable $(typeof(x))" for (x, y) in [(IOBuffer(), true), (devnull, false), (stdin, true)] + z = pytextio(x).readable() + @test pyisinstance(z, pybuiltins.bool) + @test pyeq(Bool, z, y) + end + @testset "readlines" begin + x = IOBuffer() + print(x, "hello\n") + seekstart(x) + y = pytextio(x) + # TODO + end + @testset "seek" begin + x = IOBuffer() + print(x, "hello\n") + seekstart(x) + y = pytextio(x) + # TODO + end + @testset "seekable $(typeof(x))" for (x, y) in [(IOBuffer(), true), (devnull, true), (stdin, true), (stdout, true)] + # TODO: currently always returns true, can this be improved?? + z = pytextio(x).seekable() + @test pyisinstance(z, pybuiltins.bool) + @test pyeq(Bool, z, y) + end + @testset "tell" begin + x = IOBuffer() + print(x, "hello\n") + seekstart(x) + y = pytextio(x) + # TODO + end + @testset "truncate" begin + x = IOBuffer() + print(x, "hello\n") + seekstart(x) + y = pytextio(x) + # TODO + end + @testset "writable" begin + x = IOBuffer() + print(x, "hello\n") + seekstart(x) + y = pytextio(x) + # TODO + end + @testset "writelines" begin + x = IOBuffer() + print(x, "hello\n") + seekstart(x) + y = pytextio(x) + # TODO + end + @testset "enter/exit" begin + x = IOBuffer() + print(x, "hello\n") + seekstart(x) + y = pytextio(x) + # TODO + end + @testset "iter" begin + x = IOBuffer() + print(x, "hello\n") + seekstart(x) + y = pytextio(x) + # TODO + end + @testset "next" begin + x = IOBuffer() + print(x, "hello\n") + seekstart(x) + y = pytextio(x) + # TODO + end +end + +@testitem "io/binary" begin @testset "type" begin @test pyis(pytype(pybinaryio(devnull)), PythonCall.pyjlbinaryiotype) - @test pyis(pytype(pytextio(devnull)), PythonCall.pyjltextiotype) end @testset "bool" begin @test pytruth(pybinaryio(devnull)) + end + @testset "detach" begin + x = IOBuffer() + print(x, "hello\n") + seekstart(x) + y = pybinaryio(x) + @test_throws PyException y.detach() + end + @testset "read" begin + x = IOBuffer() + print(x, "hello\n") + print(x, "world\n") + seekstart(x) + y = pybinaryio(x) + z = y.read() + @test pyisinstance(z, pybuiltins.bytes) + @test pyeq(Bool, z, pybytes(b"hello\nworld\n")) + z = y.read() + @test pyisinstance(z, pybuiltins.bytes) + @test pyeq(Bool, z, pybytes(b"")) + end + @testset "read1" begin + x = IOBuffer() + print(x, "hello\n") + print(x, "world\n") + seekstart(x) + y = pybinaryio(x) + z = y.read1() + @test pyisinstance(z, pybuiltins.bytes) + @test pyeq(Bool, z, pybytes(b"hello\nworld\n")) + z = y.read1() + @test pyisinstance(z, pybuiltins.bytes) + @test pyeq(Bool, z, pybytes(b"")) + end + @testset "readline" begin + x = IOBuffer() + print(x, "hello\n") + seekstart(x) + y = pybinaryio(x) + z = y.readline() + @test pyisinstance(z, pybuiltins.bytes) + @test pyeq(Bool, z, pybytes(b"hello\n")) + z = y.readline() + @test pyisinstance(z, pybuiltins.bytes) + @test pyeq(Bool, z, pybytes(b"")) + end + @testset "readinto" begin + x = IOBuffer() + print(x, "hello\n") + seekstart(x) + y = pybinaryio(x) + z = pybuiltins.bytearray(pybytes(b"xxxxxxxxxx")) + n = y.readinto(z) + @test pyisinstance(n, pybuiltins.int) + @test pyeq(Bool, n, 6) + @test pyeq(Bool, pybytes(z), pybytes(b"hello\nxxxx")) + end + @testset "readinto1" begin + x = IOBuffer() + print(x, "hello\n") + seekstart(x) + y = pybinaryio(x) + z = pybuiltins.bytearray(pybytes(b"xxxxxxxxxx")) + n = y.readinto(z) + @test pyisinstance(n, pybuiltins.int) + @test pyeq(Bool, n, 6) + @test pyeq(Bool, pybytes(z), pybytes(b"hello\nxxxx")) + end + @testset "write" begin + x = IOBuffer() + print(x, "hello\n") + y = pybinaryio(x) + y.write(pybytes(b"world\n")) + @test String(take!(x)) == "hello\nworld\n" + end +end + +@testitem "io/text" begin + @testset "type" begin + @test pyis(pytype(pytextio(devnull)), PythonCall.pyjltextiotype) + end + @testset "bool" begin @test pytruth(pytextio(devnull)) end + @testset "encoding" begin + x = IOBuffer() + print(x, "hello\n") + y = pytextio(x) + z = y.encoding + @test pyisinstance(z, pybuiltins.str) + @test pyeq(Bool, z, "UTF-8") + end + @testset "errors" begin + x = IOBuffer() + print(x, "hello\n") + y = pytextio(x) + z = y.errors + @test pyisinstance(z, pybuiltins.str) + @test pyeq(Bool, z, "strict") + end + @testset "detach" begin + x = IOBuffer() + print(x, "hello\n") + y = pytextio(x) + @test_throws PyException y.detach() + end + @testset "read" begin + x = IOBuffer() + print(x, "hello\n") + print(x, "world\n") + seekstart(x) + y = pytextio(x) + z = y.read() + @test pyisinstance(z, pybuiltins.str) + @test pyeq(Bool, z, "hello\nworld\n") + z = y.read() + @test pyisinstance(z, pybuiltins.str) + @test pyeq(Bool, z, "") + end + @testset "readline" begin + x = IOBuffer() + print(x, "hello\n") + seekstart(x) + y = pytextio(x) + z = y.readline() + @test pyisinstance(z, pybuiltins.str) + @test pyeq(Bool, z, "hello\n") + z = y.readline() + @test pyisinstance(z, pybuiltins.str) + @test pyeq(Bool, z, "") + end + @testset "write" begin + x = IOBuffer() + print(x, "hello\n") + y = pytextio(x) + y.write("world!") + @test String(take!(x)) == "hello\nworld!" + end end @testitem "iter" begin From 77b5ee1b2707ac0717c209cc4a754d9c9b59219c Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Thu, 20 Jun 2024 21:42:46 +0100 Subject: [PATCH 34/39] more JlIO tests --- test/JlWrap.jl | 100 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 20 deletions(-) diff --git a/test/JlWrap.jl b/test/JlWrap.jl index cbd95e1e..fb5ebc05 100644 --- a/test/JlWrap.jl +++ b/test/JlWrap.jl @@ -735,18 +735,27 @@ end @test pyeq(Bool, z, true) end @testset "fileno" begin + # IOBuffer has no fileno x = IOBuffer() print(x, "hello\n") seekstart(x) y = pytextio(x) - # TODO + @test_throws PyException y.fileno() + # check some file that has a fileno + mktemp() do name, x + y = pytextio(x) + z = y.fileno() + @test pyisinstance(z, pybuiltins.int) + @test pyeq(Bool, z, fd(x)) + end end @testset "flush" begin x = IOBuffer() print(x, "hello\n") seekstart(x) y = pytextio(x) - # TODO + y.flush() + # TODO: check it actually flushed something end @testset "isatty $(typeof(x))" for (x, y) in [(IOBuffer(), false), (devnull, false), (stdout, false)] # TODO: how to get a TTY in a test environment?? @@ -762,19 +771,34 @@ end @testset "readlines" begin x = IOBuffer() print(x, "hello\n") + print(x, "world\n") seekstart(x) y = pytextio(x) - # TODO + z = y.readlines() + @test pyisinstance(z, pybuiltins.list) + @test pylen(z) == 2 + @test all(pyisinstance(line, pybuiltins.str) for line in z) + @test pyeq(Bool, z, pylist(["hello\n", "world\n"])) end @testset "seek" begin x = IOBuffer() print(x, "hello\n") seekstart(x) y = pytextio(x) - # TODO + @test position(x) == 0 + zs = Py[] + push!(zs, y.seek(1)) + @test position(x) == 1 + push!(zs, y.seek(2, 0)) + @test position(x) == 2 + push!(zs, y.seek(1, 1)) + @test position(x) == 3 + push!(zs, y.seek(-2, 2)) + @test position(x) == 4 + @test all(pyisinstance(z, pybuiltins.int) for z in zs) + @test pyeq(Bool, pylist(zs), pylist([1, 2, 3, 4])) end @testset "seekable $(typeof(x))" for (x, y) in [(IOBuffer(), true), (devnull, true), (stdin, true), (stdout, true)] - # TODO: currently always returns true, can this be improved?? z = pytextio(x).seekable() @test pyisinstance(z, pybuiltins.bool) @test pyeq(Bool, z, y) @@ -784,49 +808,85 @@ end print(x, "hello\n") seekstart(x) y = pytextio(x) - # TODO + zs = Py[] + @test position(x) == 0 + push!(zs, y.tell()) + seek(x, 5) + push!(zs, y.tell()) + @test all(pyisinstance(z, pybuiltins.int) for z in zs) + @test pyeq(Bool, pylist(zs), pylist([0, 5])) end @testset "truncate" begin x = IOBuffer() print(x, "hello\n") seekstart(x) y = pytextio(x) - # TODO - end - @testset "writable" begin - x = IOBuffer() - print(x, "hello\n") + y.truncate(5) + seekend(x) + @test position(x) == 5 + seek(x, 3) + y.truncate() seekstart(x) - y = pytextio(x) - # TODO + seekend(x) + @test position(x) == 3 + end + @testset "writable $(typeof(x))" for (x, y) in [(IOBuffer(), true), (IOBuffer(""), false), (devnull, true), (stdout, true)] + z = pytextio(x).writable() + @test pyisinstance(z, pybuiltins.bool) + @test pyeq(Bool, z, y) end @testset "writelines" begin x = IOBuffer() - print(x, "hello\n") - seekstart(x) y = pytextio(x) - # TODO + y.writelines(pylist(["test\n", "message\n"])) + seekstart(x) + @test readline(x) == "test" + @test readline(x) == "message" + @test readline(x) == "" end @testset "enter/exit" begin x = IOBuffer() - print(x, "hello\n") seekstart(x) y = pytextio(x) - # TODO + @test isopen(x) + r = pywith(y) do z + @test pyis(z, y) + 12 + end + @test r === 12 + @test !isopen(x) + # same again by cause an error + x = IOBuffer() + seekstart(x) + y = pytextio(x) + @test isopen(x) + @test_throws PyException pywith(y) do z + z.invalid_attr + end + @test !isopen(x) # should still get closed end @testset "iter" begin x = IOBuffer() print(x, "hello\n") + print(x, "world\n") seekstart(x) y = pytextio(x) - # TODO + zs = pylist(y) + @test all(pyisinstance(z, pybuiltins.str) for z in zs) + @test pyeq(Bool, zs, pylist(["hello\n", "world\n"])) end @testset "next" begin x = IOBuffer() print(x, "hello\n") + print(x, "world\n") seekstart(x) y = pytextio(x) - # TODO + zs = Py[] + push!(zs, y.__next__()) + push!(zs, y.__next__()) + @test_throws PyException y.__next__() + @test all(pyisinstance(z, pybuiltins.str) for z in zs) + @test pyeq(Bool, pylist(zs), pylist(["hello\n", "world\n"])) end end From e88de1e3c5f87146067861ffa55b1d8ae8f1e7a8 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Thu, 20 Jun 2024 21:47:13 +0100 Subject: [PATCH 35/39] rename julia wrapper classes --- docs/src/conversion-to-python.md | 17 +++++++---------- docs/src/juliacall-reference.md | 2 +- pytest/test_all.py | 2 +- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/docs/src/conversion-to-python.md b/docs/src/conversion-to-python.md index 8333d900..37c25d92 100644 --- a/docs/src/conversion-to-python.md +++ b/docs/src/conversion-to-python.md @@ -23,16 +23,13 @@ From Python, this occurs when converting the return value of a Julia function. | Standard integer range (`AbstractRange{T}`, `T` a standard integer) | `range` | | `Date`, `Time`, `DateTime` (from `Dates`) | `date`, `time`, `datetime` (from `datetime`) | | `Second`, `Millisecond`, `Microsecond`, `Nanosecond` (from `Dates`) | `timedelta` (from `datetime`) | -| `Number` | `juliacall.NumberValue`, `juliacall.ComplexValue`, etc. | -| `AbstractArray` | `juliacall.JlArray`, `juliacall.JlVector` | -| `AbstractDict` | `juliacall.JlDict` | -| `AbstractSet` | `juliacall.JlSet` | -| `IO` | `juliacall.BufferedIOValue` | -| `Module` | `juliacall.ModuleValue` | -| `Type` | `juliacall.TypeValue` | -| Anything else | `juliacall.Jl` | - -See [here](@ref julia-wrappers) for an explanation of the `juliacall.*Value` wrapper types. +| `AbstractArray` | `juliacall.JlArray`, `juliacall.JlVector` | +| `AbstractDict` | `juliacall.JlDict` | +| `AbstractSet` | `juliacall.JlSet` | +| `IO` | `juliacall.JlBinaryIO` | +| Anything else | `juliacall.Jl` | + +See [here](@ref julia-wrappers) for an explanation of the `juliacall.Jl*` wrapper types. ## [Custom rules](@id jl2py-conversion-custom) diff --git a/docs/src/juliacall-reference.md b/docs/src/juliacall-reference.md index 52889e49..d646b421 100644 --- a/docs/src/juliacall-reference.md +++ b/docs/src/juliacall-reference.md @@ -5,7 +5,7 @@ `````@customdoc juliacall.Main - Constant -The Julia `Main` module, as a [`ModuleValue`](#juliacall.ModuleValue). +The Julia `Main` module, as a [`Jl`](#juliacall.Jl). In interactive scripts, you can use this as the main entry-point to JuliaCall: ```python diff --git a/pytest/test_all.py b/pytest/test_all.py index e531e321..2226df4c 100644 --- a/pytest/test_all.py +++ b/pytest/test_all.py @@ -5,7 +5,7 @@ def test_newmodule(): import juliacall jl = juliacall.Main m = juliacall.newmodule("TestModule") - assert isinstance(m, juliacall.ModuleValue) + assert isinstance(m, juliacall.Jl) assert jl.isa(m, jl.Module) assert str(jl.nameof(m)) == "TestModule" From 4edab030c655d9b8efa110cbef56190b95ff8bc6 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Thu, 20 Jun 2024 22:04:20 +0100 Subject: [PATCH 36/39] enable CI on v1 branch --- .github/workflows/docs.yml | 1 + .github/workflows/tests.yml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 8d57d8fa..69f30498 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - v1 tags: - '*' pull_request: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a3462b48..44f9e137 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,9 +4,11 @@ on: pull_request: branches: - main + - v1 push: branches: - main + - v1 tags: - '*' From 8057efbff345e5edf39dd5eff4a4176a25984cf8 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Thu, 20 Jun 2024 22:40:42 +0100 Subject: [PATCH 37/39] Jl.jl_eval fix return type --- src/JlWrap/any.jl | 2 +- test/JlWrap.jl | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/JlWrap/any.jl b/src/JlWrap/any.jl index 476aee65..6a521bae 100644 --- a/src/JlWrap/any.jl +++ b/src/JlWrap/any.jl @@ -235,7 +235,7 @@ function pyjlany_mimebundle(self, include::Py, exclude::Py) return ans end -pyjlany_eval(self::Module, expr::Py) = Py(Base.eval(self, Meta.parseall(strip(pyconvert(String, expr))))) +pyjlany_eval(self::Module, expr::Py) = pyjl(Base.eval(self, Meta.parseall(strip(pyconvert(String, expr))))) pyjl_handle_error_type(::typeof(pyjlany_eval), self, exc::MethodError) = pybuiltins.TypeError pyjlany_int(self) = pyint(convert(Integer, self)) diff --git a/test/JlWrap.jl b/test/JlWrap.jl index fb5ebc05..70e00e28 100644 --- a/test/JlWrap.jl +++ b/test/JlWrap.jl @@ -225,8 +225,14 @@ end @testset "eval" begin m = pyjl(Main) - @test pyconvert(Any, m.jl_eval("1 + 1")) === 2 # Basic behavior - @test pyconvert(Any, m.jl_eval("1 + 1\n ")) === 2 # Trailing whitespace + # Basic behavior + z = m.jl_eval("1 + 1") + @test pyisjl(z) + @test pyconvert(Any, z) === 2 + # Trailing whitespace + z = m.jl_eval("1 + 2\n ") + @test pyisjl(z) + @test pyconvert(Any, z) === 3 end @testset "to_py $x" for x in [1, 2.3, nothing, "foo"] y = Py(x) @@ -236,6 +242,7 @@ end @testset "iter" begin z = pylist(pyjl(Foo(3))) + @test all(pyisjl, z) @test pyeq(Bool, z, pylist([pyjl(1), pyjl(2), pyjl(3)])) end @testset "next" begin From e5661bb9227435fb8978f08d64f3d2ab80c870bd Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Thu, 20 Jun 2024 22:41:17 +0100 Subject: [PATCH 38/39] adapt python tests to latest version --- pysrc/juliacall/__init__.py | 2 +- pytest/test_all.py | 26 +++++++++----------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/pysrc/juliacall/__init__.py b/pysrc/juliacall/__init__.py index 286ce5b9..9b7133d1 100644 --- a/pysrc/juliacall/__init__.py +++ b/pysrc/juliacall/__init__.py @@ -9,7 +9,7 @@ def newmodule(name): "A new module with the given name." global _newmodule if _newmodule is None: - _newmodule = Main.seval("name -> (n1=Symbol(name); n2=gensym(n1); Main.@eval(module $n2; module $n1; end; end); Main.@eval $n2.$n1)") + _newmodule = Main.jl_eval("name -> (n1=Symbol(name); n2=gensym(n1); Main.@eval(module $n2; module $n1; end; end); Main.@eval $n2.$n1)") return _newmodule(name) def interactive(enable=True): diff --git a/pytest/test_all.py b/pytest/test_all.py index 2226df4c..00303587 100644 --- a/pytest/test_all.py +++ b/pytest/test_all.py @@ -9,14 +9,6 @@ def test_newmodule(): assert jl.isa(m, jl.Module) assert str(jl.nameof(m)) == "TestModule" -def test_convert(): - import juliacall - jl = juliacall.Main - for (x, t) in [(None, jl.Nothing), (True, jl.Bool), ([1,2,3], jl.Vector)]: - y = juliacall.convert(t, x) - assert isinstance(y, juliacall.Jl) - assert jl.isa(y, t) - def test_interactive(): import juliacall juliacall.interactive(True) @@ -47,26 +39,26 @@ def test_issue_394(): f = lambda x: x+1 y = 5 jl.x = x - assert jl.x is x + assert jl.x.jl_to_py() is x jl.f = f - assert jl.f is f + assert jl.f.jl_to_py() is f jl.y = y - assert jl.y is y - assert jl.x is x - assert jl.f is f - assert jl.y is y - assert jl.seval("f(x)") == 4 + assert jl.y.jl_to_py() is y + assert jl.x.jl_to_py() is x + assert jl.f.jl_to_py() is f + assert jl.y.jl_to_py() is y + assert jl.jl_eval("f(x)").jl_to_py() == 4 def test_issue_433(): "https://github.com/JuliaPy/PythonCall.jl/issues/433" from juliacall import Main as jl # Smoke test - jl.seval("x=1\nx=1") + jl.jl_eval("x=1\nx=1") assert jl.x == 1 # Do multiple things - out = jl.seval( + out = jl.jl_eval( """ function _issue_433_g(x) return x^2 From dba0f37e8fa269f18c4a5c665337f613dda03632 Mon Sep 17 00:00:00 2001 From: Christopher Doris <github.com/cjdoris> Date: Thu, 20 Jun 2024 22:57:10 +0100 Subject: [PATCH 39/39] propagate seval -> jl_eval --- docs/src/juliacall.md | 4 ++-- examples/flux.ipynb | 4 ++-- pysrc/juliacall/importer.py | 2 +- pysrc/juliacall/ipython.py | 16 ++++++++-------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/src/juliacall.md b/docs/src/juliacall.md index d111a0c9..678a747d 100644 --- a/docs/src/juliacall.md +++ b/docs/src/juliacall.md @@ -50,11 +50,11 @@ import juliacall jl = juliacall.newmodule("SomeName") ``` -Julia modules have a special method `seval` which will evaluate a given piece of code given +Julia modules have a special method `jl_eval` which will evaluate a given piece of code given as a string in the module. This is most frequently used to import modules: ```python from array import array -jl.seval("using Statistics") +jl.jl_eval("using Statistics") x = array('i', [1, 2, 3]) jl.mean(x) # 2.0 diff --git a/examples/flux.ipynb b/examples/flux.ipynb index c15d8d17..181b3090 100644 --- a/examples/flux.ipynb +++ b/examples/flux.ipynb @@ -31,14 +31,14 @@ "metadata": {}, "outputs": [], "source": [ - "jl.seval(\"using Flux\")\n", + "jl.jl_eval(\"using Flux\")\n", "model = jl.Chain(\n", " jl.Dense(1, 10, jl.relu),\n", " jl.Dense(10, 10, jl.relu),\n", " jl.Dense(10, 10, jl.relu),\n", " jl.Dense(10, 1),\n", ")\n", - "loss = jl.seval(\"m -> (x, y) -> Flux.Losses.mse(m(x), y)\")(model)" + "loss = jl.jl_eval(\"m -> (x, y) -> Flux.Losses.mse(m(x), y)\")(model)" ] }, { diff --git a/pysrc/juliacall/importer.py b/pysrc/juliacall/importer.py index 6aea2750..c1bd7a92 100644 --- a/pysrc/juliacall/importer.py +++ b/pysrc/juliacall/importer.py @@ -83,7 +83,7 @@ def gen_file(jl, py): def exec_module(name, code): pymod = sys.modules[name] jlmod = newmodule(name) - jlmod.seval('begin\n' + code + '\nend') + jlmod.jl_eval('begin\n' + code + '\nend') delattr(pymod, 'juliacall') setattr(pymod, '__jl_code__', code) setattr(pymod, '__jl_module__', jlmod) diff --git a/pysrc/juliacall/ipython.py b/pysrc/juliacall/ipython.py index 7edc43a4..cc774d68 100644 --- a/pysrc/juliacall/ipython.py +++ b/pysrc/juliacall/ipython.py @@ -16,9 +16,9 @@ from . import Main, PythonCall import __main__ -_set_var = Main.seval("(k, v) -> @eval $(Symbol(k)) = $v") -_get_var = Main.seval("k -> hasproperty(Main, Symbol(k)) ? PythonCall.pyjlraw(getproperty(Main, Symbol(k))) : nothing") -_egal = Main.seval("===") +_set_var = Main.jl_eval("(k, v) -> @eval $(Symbol(k)) = $v") +_get_var = Main.jl_eval("k -> hasproperty(Main, Symbol(k)) ? PythonCall.pyjlraw(getproperty(Main, Symbol(k))) : nothing") +_egal = Main.jl_eval("===") @magics_class class JuliaMagics(Magics): @@ -49,7 +49,7 @@ def julia(self, line, cell=None): if k in syncvars: cachevars[k] = _get_var(k) # run the code - ans = Main.seval('begin\n' + code + '\nend') + ans = Main.jl_eval('begin\n' + code + '\nend') # flush stderr/stdout PythonCall._ipython._flush_stdio() # copy variables back to Python @@ -61,7 +61,7 @@ def julia(self, line, cell=None): __main__.__dict__[k] = v1._jl_any() # return the value unless suppressed with trailing ";" if not code.strip().endswith(';'): - return ans + return ans.jl_to_py() def load_ipython_extension(ip): # register magics @@ -69,7 +69,7 @@ def load_ipython_extension(ip): # redirect stdout/stderr if ip.__class__.__name__ == 'TerminalInteractiveShell': # no redirection in the terminal - PythonCall.seval("""module _ipython + PythonCall.jl_eval("""module _ipython function _flush_stdio() end end""") @@ -77,7 +77,7 @@ def load_ipython_extension(ip): # In Julia 1.7+ redirect_stdout() returns a Pipe object. Earlier versions of Julia # just return a tuple of the two pipe ends. This is why we have [1] and [2] below. # They can be dropped on earlier versions. - PythonCall.seval("""module _ipython + PythonCall.jl_eval("""module _ipython using ..PythonCall const _redirected_stdout = redirect_stdout() const _redirected_stderr = redirect_stderr() @@ -98,7 +98,7 @@ def load_ipython_extension(ip): end""") ip.events.register('post_execute', PythonCall._ipython._flush_stdio) # push displays - PythonCall.seval("""begin + PythonCall.jl_eval("""begin pushdisplay(Compat.PythonDisplay()) pushdisplay(Compat.IPythonDisplay()) nothing