diff --git a/docs/python/modelspec/programmatic/topology/seclist.rst b/docs/python/modelspec/programmatic/topology/seclist.rst index 3ff618c188..810d145d62 100755 --- a/docs/python/modelspec/programmatic/topology/seclist.rst +++ b/docs/python/modelspec/programmatic/topology/seclist.rst @@ -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` diff --git a/src/nrnpython/nrnpy_hoc.cpp b/src/nrnpython/nrnpy_hoc.cpp index 1e68124c8c..0ee57d8289 100644 --- a/src/nrnpython/nrnpy_hoc.cpp +++ b/src/nrnpython/nrnpy_hoc.cpp @@ -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) { @@ -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_); @@ -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_); @@ -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; diff --git a/test/hoctests/tests/test_seclist.py b/test/hoctests/tests/test_seclist.py new file mode 100644 index 0000000000..0f3c86696a --- /dev/null +++ b/test/hoctests/tests/test_seclist.py @@ -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()