Skip to content

Commit 9301a58

Browse files
committed
WIP on sequence methods.
1 parent 7edfb51 commit 9301a58

File tree

3 files changed

+329
-34
lines changed

3 files changed

+329
-34
lines changed

doc/sphinx/source/new_types.rst

Lines changed: 235 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,26 @@
77
.. index::
88
single: New Types; Creating
99

10-
====================================
10+
************************************
1111
Creating New Types
12-
====================================
12+
************************************
1313

1414
The creation of new extension types (AKA 'classes') is pretty well described in the Python documentation
1515
`tutorial <https://docs.python.org/extending/newtypes_tutorial.html>`_ and
1616
`reference <https://docs.python.org/extending/newtypes.html>`_.
1717
This section is a cookbook of tricks and examples.
1818

19-
------------------------------------
19+
====================================
2020
Properties
21-
------------------------------------
21+
====================================
2222

2323
.. index::
2424
single: New Types; Existing Python Properties
2525
single: New Types; Existing C Properties
2626

27-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
27+
------------------------------------
2828
Referencing Existing Properties
29-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
29+
------------------------------------
3030

3131
If the property is part of the extension type then it is fairly easy to make it directly accessible as
3232
`described here <https://docs.python.org/extending/newtypes.html#adding-data-and-methods-to-the-basic-example>`_
@@ -75,9 +75,9 @@ And the type struct must reference this array of ``PyMemberDef`` thus:
7575
single: New Types; Dynamic Python Properties
7676
single: New Types; Created Python Properties
7777

78-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
78+
--------------------------
7979
Created Properties
80-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
80+
--------------------------
8181

8282
If the properties are not directly accessible, for example they might need to be created, then an array of ``PyGetSetDef`` structures is used in the `PyTypeObject.tp_getset <https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_getset>`_ slot.
8383

@@ -113,19 +113,19 @@ And the type struct must reference this array of ``PyMemberDef`` thus:
113113
114114
`Reference to PyGetSetDef. <https://docs.python.org/3/c-api/structures.html#c.PyGetSetDef>`_
115115

116-
---------------
116+
====================================
117117
Subclassing
118-
---------------
118+
====================================
119119

120120
This large subject gets it own chapter: :ref:`chapter_subclassing_and_using_super`.
121121

122122

123123
.. index::
124124
single: New Types; Examples
125125

126-
---------------
126+
====================================
127127
Examples
128-
---------------
128+
====================================
129129

130130
See ``src/cpy/cObject.c`` for some examples, the tests for these are in ``tests/unit/test_c_object.py``:
131131

@@ -138,9 +138,9 @@ See ``src/cpy/cObject.c`` for some examples, the tests for these are in ``tests/
138138
single: New Types; Get Attributes Dynamically
139139
single: New Types; Delete Attributes Dynamically
140140

141-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
141+
----------------------------------------------------
142142
Setting, Getting and Deleting Attributes Dynamically
143-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
143+
----------------------------------------------------
144144

145145
In ``src/cpy/cObject.c`` there is an example, ``ObjectWithAttributes``, which is a class that can set, get, delete
146146
attributes dynamically.
@@ -421,15 +421,105 @@ This can be tested thus, in ``tests/unit/test_c_object.py``:
421421
.. _ssizeobjargproc: https://docs.python.org/3/c-api/typeobj.html#c.ssizeobjargproc
422422
.. _objobjproc: https://docs.python.org/3/c-api/typeobj.html#c.objobjproc
423423

424-
---------------
424+
=========================
425425
Sequence Types
426-
---------------
426+
=========================
427427

428-
.. todo::
428+
This section describes how to make an object act like a sequence using
429+
`tp_as_sequence <https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_as_sequence>`_.
430+
See also `Sequence Object Structures <https://docs.python.org/3/c-api/typeobj.html#sequence-structs>`_
431+
432+
As an example here is an extension that can represent a sequence of longs in C with a CPython sequence interface.
433+
The code is in ``src/cpy/Object/cSeqObject.c``.
434+
435+
First the class declaration:
436+
437+
.. code-block:: c
438+
439+
#define PY_SSIZE_T_CLEAN
440+
#include <Python.h>
441+
#include "structmember.h"
442+
443+
typedef struct {
444+
PyObject_HEAD
445+
long *array_long;
446+
ssize_t size;
447+
} SequenceLongObject;
448+
449+
450+
Then the equivalent of ``__new__``:
451+
452+
.. code-block:: c
453+
454+
static PyObject *
455+
SequenceLongObject_new(PyTypeObject *type, PyObject *Py_UNUSED(args), PyObject *Py_UNUSED(kwds)) {
456+
SequenceLongObject *self;
457+
self = (SequenceLongObject *) type->tp_alloc(type, 0);
458+
if (self != NULL) {
459+
assert(!PyErr_Occurred());
460+
self->size = 0;
461+
self->array_long = NULL;
462+
}
463+
return (PyObject *) self;
464+
}
465+
466+
And the equivalent of ``__init__`` that takes a Python sequence of ints:
467+
468+
.. code-block:: c
469+
470+
static int
471+
SequenceLongObject_init(SequenceLongObject *self, PyObject *args, PyObject *kwds) {
472+
static char *kwlist[] = {"sequence", NULL};
473+
PyObject *sequence = NULL;
474+
475+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &sequence)) {
476+
return -1;
477+
}
478+
if (!PySequence_Check(sequence)) {
479+
return -2;
480+
}
481+
self->size = PySequence_Length(sequence);
482+
self->array_long = malloc(self->size * sizeof(long));
483+
if (!self->array_long) {
484+
return -3;
485+
}
486+
for (Py_ssize_t i = 0; i < self->size; ++i) {
487+
// New reference.
488+
PyObject *py_value = PySequence_GetItem(sequence, i);
489+
if (PyLong_Check(py_value)) {
490+
self->array_long[i] = PyLong_AsLong(py_value);
491+
Py_DECREF(py_value);
492+
} else {
493+
PyErr_Format(
494+
PyExc_TypeError,
495+
"Argument [%zd] must be a int, not type %s",
496+
i,
497+
Py_TYPE(sequence)->tp_name
498+
);
499+
// Clean up on error.
500+
free(self->array_long);
501+
self->array_long = NULL;
502+
Py_DECREF(py_value);
503+
return -4;
504+
}
505+
}
506+
return 0;
507+
}
508+
509+
And de-allocation:
510+
511+
.. code-block:: c
512+
513+
static void
514+
SequenceLongObject_dealloc(SequenceLongObject *self) {
515+
free(self->array_long);
516+
Py_TYPE(self)->tp_free((PyObject *) self);
517+
}
518+
519+
---------------------------
520+
The Sequence Function Table
521+
---------------------------
429522

430-
"Creating New Types": Add a section on making an object act like a sequence using
431-
`tp_as_sequence <https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_as_sequence>`_.
432-
See also `Sequence Object Structures <https://docs.python.org/3/c-api/typeobj.html#sequence-structs>`_
433523

434524

435525
.. list-table:: Sequence Methods
@@ -485,12 +575,135 @@ Sequence Types
485575
Returns the existing sequence repeated n times.
486576
If this slot is ``NULL`` then `PySequence_Repeat()`_ will be used returning a new object.
487577

488-
TOOD:
578+
---------------
579+
``sq_length``
580+
---------------
581+
582+
.. list-table:: Sequence Methods: ``sq_length``
583+
:widths: 20 80
584+
:header-rows: 0
585+
586+
* - Member
587+
- `sq_length`_
588+
* - Function type
589+
- `lenfunc`_
590+
* - Function signature
591+
- ``Py_ssize_t (*lenfunc)(PyObject*)``
592+
* - Description
593+
- Returns the length of the sequence.
594+
595+
Implementation
596+
--------------
597+
598+
In ``src/cpy/Object/cSeqObject.c``:
599+
600+
.. code-block:: c
601+
602+
static Py_ssize_t
603+
SequenceLongObject_sq_length(PyObject *self) {
604+
return ((SequenceLongObject *) self)->size;
605+
}
606+
607+
Tests
608+
--------------
609+
610+
Tests are in ``tests/unit/test_c_seqobject.py``:
611+
612+
.. code-block:: python
613+
614+
from cPyExtPatt import cSeqObject
615+
616+
def test_SequenceLongObject_len():
617+
obj = cSeqObject.SequenceLongObject([7, 4, 1, ])
618+
assert len(obj) == 3
619+
620+
489621
490622
---------------
491-
TODOs:
623+
``sq_concat``
492624
---------------
493625

626+
.. list-table:: Sequence Methods: ``sq_concat``
627+
:widths: 20 80
628+
:header-rows: 0
629+
630+
* - Member
631+
- `sq_concat`_
632+
* - Function type
633+
- `binaryfunc`_
634+
* - Function signature
635+
- ``PyObject *(*binaryfunc)(PyObject*, PyObject*)``
636+
* - Description
637+
- Takes two sequences and returns a new third one with the first and second concatenated.
638+
This is used by the ``+`` operator.
639+
640+
Implementation
641+
--------------
642+
643+
Note the use of forward references for the type checker as we need to check of the second argument is of the
644+
correct type.
645+
In ``src/cpy/Object/cSeqObject.c``:
646+
647+
.. code-block:: c
648+
649+
// Forward references
650+
static PyTypeObject SequenceLongObjectType;
651+
static int is_sequence_of_long_type(PyObject *op);
652+
653+
/**
654+
* Returns a new SequenceLongObject composed of self + other.
655+
* @param self
656+
* @param other
657+
* @return
658+
*/
659+
static PyObject *
660+
SequenceLongObject_sq_concat(PyObject *self, PyObject *other) {
661+
if (!is_sequence_of_long_type(other)) {
662+
PyErr_Format(
663+
PyExc_TypeError,
664+
"%s(): argument 1 must have type \"SequenceLongObject\" not %s",
665+
Py_TYPE(other)->tp_name
666+
);
667+
return NULL;
668+
}
669+
PyObject *ret = SequenceLongObject_new(&SequenceLongObjectType, NULL, NULL);
670+
/* For convenience. */
671+
SequenceLongObject *sol = (SequenceLongObject *) ret;
672+
sol->size = ((SequenceLongObject *) self)->size + ((SequenceLongObject *) other)->size;
673+
sol->array_long = malloc(sol->size * sizeof(long));
674+
if (!sol->array_long) {
675+
PyErr_Format(PyExc_MemoryError, "%s(): Can not create new object.", __FUNCTION__);
676+
}
677+
678+
ssize_t i = 0;
679+
ssize_t ub = ((SequenceLongObject *) self)->size;
680+
while (i < ub) {
681+
sol->array_long[i] = ((SequenceLongObject *) self)->array_long[i];
682+
i++;
683+
}
684+
ub += ((SequenceLongObject *) other)->size;
685+
while (i < ub) {
686+
sol->array_long[i] = ((SequenceLongObject *) other)->array_long[i];
687+
i++;
688+
}
689+
return ret;
690+
}
691+
692+
693+
Tests
694+
--------------
695+
696+
Tests are in ``tests/unit/test_c_seqobject.py``:
697+
698+
.. code-block:: python
699+
700+
701+
TOOD:
702+
703+
====================================
704+
TODOs:
705+
====================================
706+
494707
.. todo::
495708

496709
"Creating New Types": Add a section on making an object act like a number using

0 commit comments

Comments
 (0)