Skip to content

Commit

Permalink
Python 3.13.1 broke [s for s in sl] where sl is a SectionList. (#3276)
Browse files Browse the repository at this point in the history
* Also accept len(sl)
  • Loading branch information
nrnhines authored Dec 9, 2024
1 parent 47b0359 commit 00ba007
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 3 deletions.
4 changes: 4 additions & 0 deletions docs/python/modelspec/programmatic/topology/seclist.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ SectionList
for sec in python_iterable_of_sections:
sl.append(sec)
``len(sl)`` returns the number of sections in the SectionList.

``list(sl)`` and ``[s for s in sl]`` generate equivalent lists.

.. seealso::
:class:`SectionBrowser`, :class:`Shape`, :meth:`RangeVarPlot.list`

Expand Down
20 changes: 17 additions & 3 deletions src/nrnpython/nrnpy_hoc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1646,6 +1646,16 @@ static int araychk(Arrayinfo* a, PyHocObject* po, int ix) {
return 0;
}

static Py_ssize_t seclist_count(Object* ho) {
assert(ho->ctemplate == hoc_sectionlist_template_);
hoc_List* sl = (hoc_List*) (ho->u.this_pointer);
Py_ssize_t n = 0;
for (hoc_Item* q1 = sl->next; q1 != sl; q1 = q1->next) {
n++;
}
return n;
}

static Py_ssize_t hocobj_len(PyObject* self) {
PyHocObject* po = (PyHocObject*) self;
if (po->type_ == PyHoc::HocObject) {
Expand All @@ -1654,8 +1664,7 @@ static Py_ssize_t hocobj_len(PyObject* self) {
} else if (po->ho_->ctemplate == hoc_list_template_) {
return ivoc_list_count(po->ho_);
} else if (po->ho_->ctemplate == hoc_sectionlist_template_) {
PyErr_SetString(PyExc_TypeError, "hoc.SectionList has no len()");
return -1;
return seclist_count(po->ho_);
}
} else if (po->type_ == PyHoc::HocArray) {
Arrayinfo* a = hocobj_aray(po->sym_, po->ho_);
Expand All @@ -1682,6 +1691,8 @@ static int hocobj_nonzero(PyObject* self) {
b = vector_capacity((Vect*) po->ho_->u.this_pointer) > 0;
} else if (po->ho_->ctemplate == hoc_list_template_) {
b = ivoc_list_count(po->ho_) > 0;
} else if (po->ho_->ctemplate == hoc_sectionlist_template_) {
b = seclist_count(po->ho_) > 0;
}
} else if (po->type_ == PyHoc::HocArray) {
Arrayinfo* a = hocobj_aray(po->sym_, po->ho_);
Expand Down Expand Up @@ -1715,13 +1726,16 @@ static PyObject* hocobj_iter(PyObject* raw_self) {

nb::object self = nb::borrow(raw_self);
PyHocObject* po = (PyHocObject*) self.ptr();
if (po->type_ == PyHoc::HocObject) {
if (po->type_ == PyHoc::HocObject || po->type_ == PyHoc::HocSectionListIterator) {
if (po->ho_->ctemplate == hoc_vec_template_) {
return PySeqIter_New(self.ptr());
} else if (po->ho_->ctemplate == hoc_list_template_) {
return PySeqIter_New(self.ptr());
} else if (po->ho_->ctemplate == hoc_sectionlist_template_) {
// need a clone of self so nested loops do not share iteritem_
// The HocSectionListIter arm of the outer 'if' became necessary
// at Python-3.13.1 upon which the following body is executed
// twice. See https://github.com/python/cpython/issues/127682
auto po2 = nb::steal(nrnpy_ho2po(po->ho_));
PyHocObject* pho2 = (PyHocObject*) po2.ptr();
pho2->type_ = PyHoc::HocSectionListIterator;
Expand Down
18 changes: 18 additions & 0 deletions test/hoctests/tests/test_seclist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from neuron import h

secs = [h.Section() for _ in range(10)]


def test():
sl = h.SectionList()
sl.allroots()
assert len(sl) == len(list(sl))
b = [(s1, s2) for s1 in sl for s2 in sl]
n = len(sl)
for i in range(n):
for j in range(n):
assert b[i * n + j][0] == b[i + j * n][1]
return sl


sl = test()

0 comments on commit 00ba007

Please sign in to comment.