Skip to content

Commit 19d6e4b

Browse files
committed
Initial stab at an object using sequence methods.
1 parent 373d231 commit 19d6e4b

File tree

2 files changed

+223
-6
lines changed

2 files changed

+223
-6
lines changed

doc/sphinx/source/new_types.rst

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,35 @@ This can be tested thus, in ``tests/unit/test_c_object.py``:
393393
obj.some_attr
394394
assert err.value.args[0] == "'cObject.ObjectWithAttributes' object has no attribute 'some_attr'"
395395
396+
397+
.. _sq_length: https://docs.python.org/3/c-api/typeobj.html#c.PySequenceMethods.sq_length
398+
.. _lenfunc: https://docs.python.org/3/c-api/typeobj.html#c.lenfunc
399+
400+
---------------
401+
Sequence Types
402+
---------------
403+
404+
.. todo::
405+
406+
"Creating New Types": Add a section on making an object act like a sequence using
407+
`tp_as_sequence <https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_as_sequence>`_.
408+
See also `Sequence Object Structures <https://docs.python.org/3/c-api/typeobj.html#sequence-structs>`_
409+
410+
411+
.. list-table:: Sequence Methods
412+
:widths: 20 70 10
413+
:header-rows: 1
414+
415+
* - Member
416+
- Function Type
417+
- Function Signatures
418+
- Description
419+
* - `sq_length`_
420+
- `lenfunc`_
421+
- ``typedef Py_ssize_t (*lenfunc)(PyObject*)``
422+
- Returns the length of the sequence.
423+
424+
396425
---------------
397426
TODOs:
398427
---------------
@@ -403,12 +432,6 @@ TODOs:
403432
`tp_as_number <https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_as_number>`_.
404433
See also `Number Object Structures <https://docs.python.org/3/c-api/typeobj.html#number-structs>`_
405434

406-
.. todo::
407-
408-
"Creating New Types": Add a section on making an object act like a sequence using
409-
`tp_as_sequence <https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_as_sequence>`_.
410-
See also `Sequence Object Structures <https://docs.python.org/3/c-api/typeobj.html#sequence-structs>`_
411-
412435
.. todo::
413436

414437
"Creating New Types": Add a section on making an object act like a mapping object (like a ``dict``) using

src/cpy/Object/cSeqObject.c

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
//
2+
// Created by Paul Ross on 08/07/2021.
3+
//
4+
// Example of an object implementing the sequence methods with PySequenceMethods.
5+
//
6+
// See also src/cpy/Iterators/cIterator.c
7+
#define PY_SSIZE_T_CLEAN
8+
9+
#include <Python.h>
10+
#include "structmember.h"
11+
12+
typedef struct {
13+
PyObject_HEAD
14+
long *array_long;
15+
ssize_t size;
16+
} SequenceOfLong;
17+
18+
// Forward reference
19+
static int is_sequence_of_long_type(PyObject *op);
20+
21+
static PyObject *
22+
SequenceOfLong_new(PyTypeObject *type, PyObject *Py_UNUSED(args), PyObject *Py_UNUSED(kwds)) {
23+
SequenceOfLong *self;
24+
self = (SequenceOfLong *) type->tp_alloc(type, 0);
25+
if (self != NULL) {
26+
assert(!PyErr_Occurred());
27+
self->size = 0;
28+
self->array_long = NULL;
29+
}
30+
return (PyObject *) self;
31+
}
32+
33+
static int
34+
SequenceOfLong_init(SequenceOfLong *self, PyObject *args, PyObject *kwds) {
35+
static char *kwlist[] = {"sequence", NULL};
36+
PyObject *sequence = NULL;
37+
38+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &sequence)) {
39+
return -1;
40+
}
41+
if (!PySequence_Check(sequence)) {
42+
return -2;
43+
}
44+
self->size = PySequence_Length(sequence);
45+
self->array_long = malloc(self->size * sizeof(long));
46+
if (!self->array_long) {
47+
return -3;
48+
}
49+
for (Py_ssize_t i = 0; i < PySequence_Length(sequence); ++i) {
50+
// New reference.
51+
PyObject *py_value = PySequence_GetItem(sequence, i);
52+
if (PyLong_Check(py_value)) {
53+
self->array_long[i] = PyLong_AsLong(py_value);
54+
Py_DECREF(py_value);
55+
} else {
56+
PyErr_Format(
57+
PyExc_TypeError,
58+
"Argument [%zd] must be a int, not type %s",
59+
i,
60+
Py_TYPE(sequence)->tp_name
61+
);
62+
// Clean up on error.
63+
free(self->array_long);
64+
self->array_long = NULL;
65+
Py_DECREF(py_value);
66+
return -4;
67+
}
68+
}
69+
return 0;
70+
}
71+
72+
static void
73+
SequenceOfLong_dealloc(SequenceOfLong *self) {
74+
free(self->array_long);
75+
Py_TYPE(self)->tp_free((PyObject *) self);
76+
}
77+
78+
//static PyObject *
79+
//SequenceOfLong_size(SequenceOfLong *self, PyObject *Py_UNUSED(ignored)) {
80+
// return Py_BuildValue("n", self->size);
81+
//}
82+
83+
static PyMethodDef SequenceOfLong_methods[] = {
84+
// {
85+
// "size",
86+
// (PyCFunction) SequenceOfLong_size,
87+
// METH_NOARGS,
88+
// "Return the size of the sequence."
89+
// },
90+
{NULL, NULL, 0, NULL} /* Sentinel */
91+
};
92+
93+
/* Sequence methods. */
94+
static Py_ssize_t
95+
SequenceOfLong_len(PyObject *self) {
96+
return ((SequenceOfLong *)self)->size;
97+
}
98+
99+
static PyObject *
100+
SequenceOfLong_getitem(PyObject *self, Py_ssize_t index) {
101+
Py_ssize_t my_index = index;
102+
if (my_index < 0) {
103+
my_index += SequenceOfLong_len(self);
104+
}
105+
if (my_index > SequenceOfLong_len(self)) {
106+
PyErr_Format(
107+
PyExc_IndexError,
108+
"Index %ld is out of range for length %ld",
109+
index,
110+
SequenceOfLong_len(self)
111+
);
112+
return NULL;
113+
}
114+
return PyLong_FromLong(((SequenceOfLong *)self)->array_long[my_index]);
115+
}
116+
117+
PySequenceMethods SequenceOfLong_sequence_methods = {
118+
.sq_length = &SequenceOfLong_len,
119+
.sq_concat = NULL,
120+
.sq_repeat = NULL,
121+
.sq_item = &SequenceOfLong_getitem,
122+
.sq_ass_item = NULL,
123+
.sq_contains = NULL,
124+
.sq_inplace_concat = NULL,
125+
.sq_inplace_repeat = NULL,
126+
};
127+
128+
static PyObject *
129+
SequenceOfLong___str__(SequenceOfLong *self, PyObject *Py_UNUSED(ignored)) {
130+
assert(!PyErr_Occurred());
131+
return PyUnicode_FromFormat("<SequenceOfLong sequence size: %ld>", self->size);
132+
}
133+
134+
static PyTypeObject SequenceOfLongType = {
135+
PyVarObject_HEAD_INIT(NULL, 0)
136+
.tp_name = "SequenceOfLong",
137+
.tp_basicsize = sizeof(SequenceOfLong),
138+
.tp_itemsize = 0,
139+
.tp_dealloc = (destructor) SequenceOfLong_dealloc,
140+
.tp_as_sequence = &SequenceOfLong_sequence_methods,
141+
.tp_str = (reprfunc) SequenceOfLong___str__,
142+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
143+
.tp_doc = "Sequence of long integers.",
144+
.tp_methods = SequenceOfLong_methods,
145+
.tp_init = (initproc) SequenceOfLong_init,
146+
.tp_new = SequenceOfLong_new,
147+
};
148+
149+
static int
150+
is_sequence_of_long_type(PyObject *op) {
151+
return Py_TYPE(op) == &SequenceOfLongType;
152+
}
153+
154+
155+
static PyMethodDef cIterator_methods[] = {
156+
// {"iterate_and_print", (PyCFunction) iterate_and_print, METH_VARARGS,
157+
// "Iteratee through the argument printing the values."},
158+
{NULL, NULL, 0, NULL} /* Sentinel */
159+
};
160+
161+
static PyModuleDef sequence_object_cmodule = {
162+
PyModuleDef_HEAD_INIT,
163+
.m_name = "cSeqObject",
164+
.m_doc = (
165+
"Example module that creates an extension type with sequence methods"
166+
),
167+
.m_size = -1,
168+
.m_methods = cIterator_methods,
169+
};
170+
171+
PyMODINIT_FUNC
172+
PyInit_cSeqObject(void) {
173+
PyObject *m;
174+
m = PyModule_Create(&sequence_object_cmodule);
175+
if (m == NULL) {
176+
return NULL;
177+
}
178+
179+
if (PyType_Ready(&SequenceOfLongType) < 0) {
180+
Py_DECREF(m);
181+
return NULL;
182+
}
183+
Py_INCREF(&SequenceOfLongType);
184+
if (PyModule_AddObject(
185+
m,
186+
"SequenceOfLong",
187+
(PyObject *) &SequenceOfLongType) < 0
188+
) {
189+
Py_DECREF(&SequenceOfLongType);
190+
Py_DECREF(m);
191+
return NULL;
192+
}
193+
return m;
194+
}

0 commit comments

Comments
 (0)