Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commiteaa4833

Browse files
committed
Add support for the mapping protocol to Python interop
https://docs.python.org/3/c-api/typeobj.html#mapping-object-structures
1 parentb4554f6 commiteaa4833

File tree

5 files changed

+645
-2
lines changed

5 files changed

+645
-2
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
load("@rules_mojo//mojo:mojo_shared_library.bzl","mojo_shared_library")
2+
load("//bazel:api.bzl","modular_py_test")
3+
4+
mojo_shared_library(
5+
name="mojo_module",
6+
testonly=True,
7+
srcs= ["mojo_module.mojo"],
8+
shared_lib_name="mojo_module.so",
9+
target_compatible_with=select({
10+
"//:asan": ["@platforms//:incompatible"],
11+
"//:tsan": ["@platforms//:incompatible"],
12+
"//:ubsan": ["@platforms//:incompatible"],
13+
"//conditions:default": [],
14+
}),
15+
deps= [
16+
"@mojo//:stdlib",
17+
],
18+
)
19+
20+
modular_py_test(
21+
name="main",
22+
srcs= ["test_module.py"],
23+
tags= ["no-mypy"],# Fails to find mojo_module.so
24+
deps= [
25+
":mojo_module",
26+
],
27+
)
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
# ===----------------------------------------------------------------------=== #
2+
# Copyright (c) 2025, Modular Inc. All rights reserved.
3+
#
4+
# Licensed under the Apache License v2.0 with LLVM Exceptions:
5+
# https://llvm.org/LICENSE.txt
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
# ===----------------------------------------------------------------------=== #
13+
14+
from osimport abort
15+
16+
from memoryimport UnsafePointer, alloc
17+
from utilsimport IndexList
18+
from pythonimport Python, PythonObject
19+
from python._cpythonimport PyObjectPtr, Py_LT, Py_GT
20+
from python.bindingsimport (
21+
PythonModuleBuilder,
22+
PyTypeObjectSlot,
23+
)
24+
25+
comptime Coord1DColumn= List[Float64]
26+
27+
28+
fn_extent(pos: Coord1DColumn) -> Tuple[Float64, Float64]:
29+
"""Return the min and max value in the buffer."""
30+
v_min= Float64.MAX
31+
v_max= Float64.MIN
32+
for vin pos:
33+
v_min=min(v_min, v)
34+
v_max=max(v_max, v)
35+
return (v_min, v_max)
36+
37+
38+
fn_compute_bounding_box_area(
39+
pos_x: Coord1DColumn,
40+
pos_y: Coord1DColumn,
41+
) -> Float64:
42+
iflen(pos_x)==0:
43+
return0.0
44+
ext_x= _extent(pos_x)
45+
ext_y= _extent(pos_y)
46+
return (ext_x[1]- ext_x[0])* (ext_y[1]- ext_y[0])
47+
48+
49+
structDataFrame(Defaultable,Movable,Representable):
50+
"""A simple columnar data structure.
51+
52+
This struct contains points with the x,y coordinates stored in columns. Some algorithms
53+
are a lot more efficient in this representation.
54+
"""
55+
56+
varpos_x: Coord1DColumn
57+
varpos_y: Coord1DColumn
58+
59+
# The bounding box area that contains all points.
60+
var_bounding_box_area: Float64
61+
62+
fn__init__(outself):
63+
"""Default initializer."""
64+
self.pos_x= []
65+
self.pos_y= []
66+
self._bounding_box_area=0
67+
68+
fn__init__(
69+
outself,
70+
varx: Coord1DColumn,
71+
vary: Coord1DColumn,
72+
):
73+
self._bounding_box_area= _compute_bounding_box_area(x, y)
74+
self.pos_x= x^
75+
self.pos_y= y^
76+
77+
@staticmethod
78+
fn_get_self_ptr(
79+
py_self: PythonObject,
80+
) -> UnsafePointer[Self, MutAnyOrigin]:
81+
try:
82+
return py_self.downcast_value_ptr[Self]()
83+
except e:
84+
abort(
85+
String(
86+
(
87+
"Python method receiver object did not have the"
88+
" expected type:"
89+
),
90+
e,
91+
)
92+
)
93+
94+
@staticmethod
95+
fnwith_columns(
96+
pos_x: PythonObject,pos_y: PythonObject
97+
)raises -> PythonObject:
98+
varlen_x= Int(pos_x.__len__())
99+
varlen_y= Int(pos_y.__len__())
100+
if len_x!= len_y:
101+
raise Error("The length of the two columns does not match.")
102+
103+
# Allocate memory for the buffers
104+
varptr_x= Coord1DColumn(capacity=len_x)
105+
varptr_y= Coord1DColumn(capacity=len_y)
106+
107+
# Copy values from Python objects
108+
for valuein pos_x:
109+
ptr_x.append(Float64(value))
110+
for valuein pos_y:
111+
ptr_y.append(Float64(value))
112+
113+
# Create NDBuffers with dynamic shape
114+
# var m_x = NDBuffer[DType.float64, 1, MutAnyOrigin](
115+
# ptr_x, IndexList[1](len_x)
116+
# )
117+
# var m_y = NDBuffer[DType.float64, 1, MutAnyOrigin](
118+
# ptr_y, IndexList[1](len_x)
119+
# )
120+
#
121+
return PythonObject(alloc=DataFrame(ptr_x^, ptr_y^))
122+
123+
@staticmethod
124+
fnpy__len__(py_self: PythonObject)raises -> Int:
125+
"""For __len__ we return the number of rows in the DataFrame."""
126+
varself_ptr= Self._get_self_ptr(py_self)
127+
returnlen(self_ptr[].pos_x)
128+
129+
@staticmethod
130+
fnpy__getitem__(
131+
py_self: PythonObject,index: PythonObject
132+
)raises -> PythonObject:
133+
"""For __getitem__ we will return a row of the DataFrame."""
134+
varself_ptr= Self._get_self_ptr(py_self)
135+
varindex_mojo= Int(index)
136+
return Python().tuple(
137+
self_ptr[].pos_x[index_mojo], self_ptr[].pos_y[index_mojo]
138+
)
139+
140+
@staticmethod
141+
fnpy__setitem__(
142+
py_self: PythonObject,index: PythonObject,value: PythonObject
143+
)raises ->None:
144+
"""For __setitem__ we set the x and y values at the given index."""
145+
varself_ptr= Self._get_self_ptr(py_self)
146+
varindex_mojo= Int(index)
147+
# Expect value to be a tuple of (x, y)
148+
self_ptr[].pos_x[index_mojo]= Float64(value[0])
149+
self_ptr[].pos_y[index_mojo]= Float64(value[1])
150+
151+
fn__repr__(self) -> String:
152+
return String("DataFrame( length=",len(self.pos_x),")")
153+
154+
@staticmethod
155+
fnrich_compare(
156+
self_ptr: PythonObject,other: PythonObject,op: Int
157+
)raises -> Bool:
158+
"""Implement the rich compare functionality.
159+
160+
We use the bounding box area to order the DataFrame objects.
161+
"""
162+
varself_df= Self._get_self_ptr(self_ptr)
163+
varother_df= Self._get_self_ptr(other)
164+
if op== Py_LT:
165+
return self_df[]._bounding_box_area< other_df[]._bounding_box_area
166+
elif op== Py_GT:
167+
return self_df[]._bounding_box_area> other_df[]._bounding_box_area
168+
# For other comparisons, return False
169+
returnFalse
170+
171+
172+
@export
173+
fnPyInit_mojo_module() -> PythonObject:
174+
"""Create a Python module with a type binding for `DataFrame`."""
175+
176+
try:
177+
varb= PythonModuleBuilder("mojo_module")
178+
_= (
179+
b.add_type[DataFrame]("DataFrame")
180+
.def_init_defaultable[DataFrame]()
181+
.def_staticmethod[DataFrame.with_columns]("with_columns")
182+
# Mapping protocol.
183+
.def_method[DataFrame.py__len__, PyTypeObjectSlot.mp_length]()
184+
.def_method[DataFrame.py__getitem__, PyTypeObjectSlot.mp_getitem]()
185+
.def_method[DataFrame.py__setitem__, PyTypeObjectSlot.mp_setitem]()
186+
.def_method[
187+
DataFrame.rich_compare, PyTypeObjectSlot.tp_richcompare
188+
]()
189+
)
190+
return b.finalize()
191+
except e:
192+
abort(String("failed to create Python module:", e))
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# ===----------------------------------------------------------------------=== #
2+
# Copyright (c) 2025, Modular Inc. All rights reserved.
3+
#
4+
# Licensed under the Apache License v2.0 with LLVM Exceptions:
5+
# https://llvm.org/LICENSE.txt
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
# ===----------------------------------------------------------------------=== #
13+
14+
# Imports from 'mojo_module.so'
15+
importmojo_module
16+
17+
18+
deftest_mojo_columnar()->None:
19+
print("Hello from Basic Columnar Example!")
20+
21+
try:
22+
_=mojo_module.DataFrame.with_columns([1.0,2.0,3.0], [0.1,0.2])
23+
raiseException("ValueError expected due to unbalanced columns.")
24+
exceptExceptionasex:
25+
assert"not match"instr(ex)
26+
pass
27+
28+
df=mojo_module.DataFrame.with_columns([1.0,2.0,3.0], [0.1,0.2,0.3])
29+
assert"DataFrame"instr(df)
30+
31+
# Test __len__ (mapping protocol mp_length)
32+
assertlen(df)==3
33+
34+
# Access rows in the DataFrame
35+
assertdf[0]== (1.0,0.1)
36+
assertdf[1]== (2.0,0.2)
37+
38+
# Test __setitem__ (mapping protocol mp_ass_subscript)
39+
df[1]= (5.0,6.0)
40+
assertdf[1]== (5.0,6.0)
41+
# Verify other values unchanged
42+
assertdf[0]== (1.0,0.1)
43+
assertdf[2]== (3.0,0.3)
44+
45+
big_df=mojo_module.DataFrame.with_columns(
46+
[1.0,2.0,30000.0], [0.1,0.2,1.0]
47+
)
48+
assertbig_df>df
49+
50+
print("🎉🎉🎉 Mission Success! 🎉🎉🎉")

‎mojo/stdlib/std/python/_cpython.mojo‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,28 @@ comptime Py_TPFLAGS_DICT_SUBCLASS = c_ulong(1 << 29)
6767
comptime Py_TPFLAGS_BASE_EXC_SUBCLASS= c_ulong(1<<30)
6868
comptime Py_TPFLAGS_TYPE_SUBCLASS= c_ulong(1<<31)
6969

70+
# Python constants, for example some operations may want to return Not Implemented.
71+
# ref: https://github.com/python/cpython/blob/main/Include/object.h#L659
72+
# ref: https://docs.python.org/3/c-api/object.html#c.Py_CONSTANT_NONE
73+
comptime Py_CONSTANT_NONE=0
74+
comptime Py_CONSTANT_FALSE=1
75+
comptime Py_CONSTANT_TRUE=2
76+
comptime Py_CONSTANT_ELLIPSIS=3
77+
comptime Py_CONSTANT_NOT_IMPLEMENTED=4
78+
comptime Py_CONSTANT_ZERO=5
79+
comptime Py_CONSTANT_ONE=6
80+
comptime Py_CONSTANT_EMPTY_STR=7
81+
comptime Py_CONSTANT_EMPTY_BYTES=8
82+
comptime Py_CONSTANT_EMPTY_TUPLE=9
83+
84+
# These flags are used by the richcompare function.
85+
# ref: https://github.com/python/cpython/blob/main/Include/object.h#L721
86+
comptime Py_LT=0
87+
comptime Py_LE=1
88+
comptime Py_EQ=2
89+
comptime Py_NE=3
90+
comptime Py_GT=4
91+
comptime Py_GE=5
7092

7193
#TODO(MOCO-1138):
7294
# This should be a C ABI function pointer, not a Mojo ABI function.
@@ -1568,7 +1590,7 @@ struct CPython(Defaultable, Movable):
15681590
# PyObject *Py_GetConstantBorrowed(unsigned int constant_id)
15691591
self._Py_None=self.lib.call[
15701592
"Py_GetConstantBorrowed", PyObjectPtr
1571-
](0)
1593+
](Py_CONSTANT_NONE)
15721594
else:
15731595
# PyObject *Py_None
15741596
self._Py_None= PyObjectPtr(

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp