Skip to content

Commit 4ebceed

Browse files
author
Christopher Doris
committed
pyclass dev
1 parent add1c8f commit 4ebceed

File tree

8 files changed

+141
-24
lines changed

8 files changed

+141
-24
lines changed

docs/src/pythoncall.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,7 @@ pyfrozenset
110110
pydict
111111
pyslice
112112
pyrange
113-
pymethod
114113
pytype
115-
pyclass
116114
```
117115

118116
### Builtins
@@ -244,6 +242,19 @@ pyge
244242
pygt
245243
```
246244

245+
## Create classes
246+
247+
These functions can be used to create new Python classes where the functions are implemented
248+
in Julia. You can instead use [`@pyeval`](@ref) etc. to create pure-Python classes.
249+
250+
```@docs
251+
pyclass
252+
pyfunc
253+
pyclassmethod
254+
pystaticmethod
255+
pyproperty
256+
```
257+
247258
## [Installing Python packages](@id python-deps)
248259

249260
PythonCall uses [CondaPkg.jl](https://github.com/cjdoris/CondaPkg.jl) to manage its

docs/src/releasenotes.md

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

33
## Unreleased
4+
* **Breaking:** Removes `pymethod`.
5+
* **Breaking:** Any member passed to `pyclass` which is a `Function` is automatically
6+
wrapped with `pyfunc`, making it an instance method.
7+
* Adds `pyfunc`, `pyclassmethod`, `pystaticmethod` and `pyproperty`.
48
* `pyconvert_add_rule` is now documented. Its semantics have changed, including the
5-
separator of the first argument from `/` to `:` (not breaking because it was previously
6-
undocumented).
9+
separator of the first argument from `/` to `:`.
710
* Bug fixes.
811

912
## v0.6.1 (2022-02-21)

src/PythonCall.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ include("concrete/range.jl")
3737
include("concrete/none.jl")
3838
include("concrete/type.jl")
3939
include("concrete/fraction.jl")
40-
include("concrete/method.jl")
4140
include("concrete/datetime.jl")
4241
include("concrete/code.jl")
4342
include("concrete/ctypes.jl")

src/concrete/method.jl

Lines changed: 0 additions & 9 deletions
This file was deleted.

src/concrete/type.jl

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,75 @@
44
The Python `type` of `x`.
55
"""
66
pytype(x) = pynew(errcheck(@autopy x C.PyObject_Type(getptr(x_))))
7-
pytype(name, bases, dict) = pybuiltins.type(name, ispy(bases) && pyistype(bases) ? pytuple((bases,)) : pytuple(bases), pydict(dict))
87
export pytype
98

9+
"""
10+
pytype(name, bases, dict)
11+
12+
Create a new type. Equivalent to `type(name, bases, dict)` in Python.
13+
14+
See [`pyclass`](@ref) for a more convenient syntax.
15+
"""
16+
pytype(name, bases, dict) = pybuiltins.type(name, ispy(bases) ? bases : pytuple(bases), ispy(dict) ? dict : pydict(dict))
17+
1018
"""
1119
pyclass(name, bases=(); members...)
1220
1321
Construct a new Python type with the given name, bases and members.
1422
15-
Equivalent to `pytype(name, bases, members)`.
23+
The `bases` may be a Python type or a tuple of Python types.
24+
25+
Any `members` which are Julia functions are interpreted as instance methods (equivalent to
26+
wrapping the function in [`pyfunc`](@ref)). To create class methods, static methods or
27+
properties, wrap the function in [`pyclassmethod`](@ref), [`pystaticmethod`](@ref) or
28+
[`pyproperty`](@ref).
29+
30+
Note that the arguments to any method or property are passed as `Py`, i.e. they are not
31+
converted first.
32+
33+
# Example
34+
35+
```
36+
Foo = pyclass("Foo",
37+
__init__ = function (self, x, y = nothing)
38+
self.x = x
39+
self.y = y
40+
nothing
41+
end,
42+
43+
__repr__ = function (self)
44+
"Foo(\$(self.x), \$(self.y))"
45+
end,
46+
47+
frompair = function (cls, xy)
48+
cls(xy...)
49+
end
50+
|> pyclassmethod,
51+
52+
hello = function (name)
53+
println("Hello, \$name")
54+
end
55+
|> pystaticmethod,
56+
57+
xy = pyproperty(
58+
get = function (self)
59+
(self.x, self.y)
60+
end,
61+
set = function (self, xy)
62+
(x, y) = xy
63+
self.x = x
64+
self.y = y
65+
nothing
66+
end,
67+
),
68+
)
69+
```
1670
"""
17-
pyclass(name, bases=(); dict...) = pytype(name, bases, pystrdict_fromiter(dict))
71+
function pyclass(name, bases=(); members...)
72+
bases2 = ispy(bases) && pyistype(bases) ? pytuple((bases,)) : pytuple(bases)
73+
members2 = pydict(pystr(k) => v isa Function ? pyfunc(v) : Py(v) for (k, v) in members)
74+
pytype(name, bases2, members2)
75+
end
1876
export pyclass
1977

2078
pyistype(x) = pytypecheckfast(x, C.Py_TPFLAGS_TYPE_SUBCLASS)

src/jlwrap/callback.jl

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const pyjlcallbacktype = pynew()
33

44
pyjlcallback_repr(self) = Py("<jl $(repr(self))>")
55

6-
pyjlcallback_str(self) = Py(string(self))
6+
pyjlcallback_str(self) = Py(sprint(print, self))
77

88
function pyjlcallback_call(self, args_::Py, kwargs_::Py)
99
if pylen(kwargs_) > 0
@@ -44,18 +44,73 @@ function init_jlwrap_callback()
4444
end
4545

4646
pyjlcallback(f) = pyjl(pyjlcallbacktype, f)
47-
export pyjlcallback
4847

49-
function pycallback(f; name=nothing, doc=nothing)
50-
f2 = pyjlcallback(f)
48+
"""
49+
pyfunc(f; [name], [doc])
50+
51+
Wrap the callable `f` as an ordinary Python function.
52+
53+
Its name and docstring can be given with `name` and `doc`.
54+
55+
Unlike `Py(f)` (or `pyjl(f)`), the arguments passed to `f` are always of type `Py`, i.e.
56+
they are never converted.
57+
"""
58+
function pyfunc(f; name=nothing, doc=nothing)
59+
f2 = ispy(f) ? f : pyjlcallback(f)
5160
f3 = pywrapcallback(f2)
5261
pydel!(f2)
5362
if name !== nothing
5463
f3.__name__ = f3.__qualname__ = name
64+
else
65+
f3.__name__ = f3.__qualname__ = "<lambda>"
5566
end
5667
if doc !== nothing
5768
f3.__doc__ = doc
5869
end
5970
return f3
6071
end
61-
export pycallback
72+
export pyfunc
73+
74+
"""
75+
pyclassmethod(f)
76+
77+
Convert callable `f` to a Python class method.
78+
79+
If `f` is not a Python object (e.g. if `f` is a `Function`) then it is converted to one with
80+
[`pyfunc`](@ref). In particular this means the arguments passed to `f` are always of type
81+
`Py`.
82+
"""
83+
pyclassmethod(f) = pybuiltins.classmethod(ispy(f) ? f : pyfunc(f))
84+
export pyclassmethod
85+
86+
"""
87+
pystaticmethod(f)
88+
89+
Convert callable `f` to a Python static method.
90+
91+
If `f` is not a Python object (e.g. if `f` is a `Function`) then it is converted to one with
92+
[`pyfunc`](@ref). In particular this means the arguments passed to `f` are always of type
93+
`Py`.
94+
"""
95+
pystaticmethod(f) = pybuiltins.staticmethod(ispy(f) ? f : pyfunc(f))
96+
export pystaticmethod
97+
98+
"""
99+
pyproperty(; get=nothing, set=nothing, del=nothing, doc=nothing)
100+
pyproperty(get)
101+
102+
Create a Python `property` with the given getter, setter and deleter.
103+
104+
If `get`, `set` or `del` is not a Python object (e.g. if it is a `Function`) then it is
105+
converted to one with [`pyfunc`](@ref). In particular this means the arguments passed to it
106+
are always of type `Py`.
107+
"""
108+
pyproperty(; get=nothing, set=nothing, del=nothing, doc=nothing) =
109+
pybuiltins.property(
110+
fget = ispy(get) || get === nothing ? get : pyfunc(get),
111+
fset = ispy(set) || set === nothing ? set : pyfunc(set),
112+
fdel = ispy(del) || del === nothing ? del : pyfunc(del),
113+
doc = doc,
114+
)
115+
pyproperty(get) = pyproperty(get=get)
116+
export pyproperty

src/jlwrap/raw.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const pyjlrawtype = pynew()
22

33
pyjlraw_repr(self) = Py("<jl $(repr(self))>")
44

5-
pyjlraw_str(self) = Py(string(self))
5+
pyjlraw_str(self) = Py(sprint(print, self))
66

77
pyjl_attr_py2jl(k::String) = replace(k, r"_[b]+$" => (x -> "!"^(length(x) - 1)))
88

src/py_macro.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# TODO:
2-
# - function definition (wrap the function like a pycallback)
2+
# - function definition (wrap the function like a pyfunc)
33
# - class definition (e.g. `struct User <: BaseModel; id::int=0; name::str=""; end`)
44
# - property syntax (e.g. `classmethod |> function foo(cls); cls(); end`)
55
# - with syntax (`@with`)

0 commit comments

Comments
 (0)