Skip to content

Commit 09f1a43

Browse files
committed
BUG: Show allocatable arrays in dir() for F2PY modules
Add a __dir__ method to PyFortranObject that includes variable names from the defs array (which contains allocatable arrays) alongside the regular dict keys. Previously dir() only showed dict contents, hiding allocatable variables that are handled dynamically via getattr. Closes numpy#27696
1 parent 63c2ad5 commit 09f1a43

3 files changed

Lines changed: 53 additions & 0 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
``f2py`` modules now show allocatable arrays in ``dir()``
2+
---------------------------------------------------------
3+
Allocatable module variables wrapped by ``f2py`` now appear in ``dir()``
4+
output, matching their accessibility by name.

numpy/f2py/src/fortranobject.c

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,13 +576,55 @@ fortran_repr(PyFortranObject *fp)
576576
return repr;
577577
}
578578

579+
static PyObject *
580+
fortran_dir(PyFortranObject *fp, PyObject *Py_UNUSED(args))
581+
{
582+
int i;
583+
PyObject *dir_list = PyDict_Keys(fp->dict);
584+
if (dir_list == NULL) {
585+
return NULL;
586+
}
587+
for (i = 0; i < fp->len; i++) {
588+
PyObject *name = PyUnicode_FromString(fp->defs[i].name);
589+
if (name == NULL) {
590+
Py_DECREF(dir_list);
591+
return NULL;
592+
}
593+
int contains = PySequence_Contains(dir_list, name);
594+
if (contains == -1) {
595+
Py_DECREF(name);
596+
Py_DECREF(dir_list);
597+
return NULL;
598+
}
599+
if (!contains) {
600+
if (PyList_Append(dir_list, name) < 0) {
601+
Py_DECREF(name);
602+
Py_DECREF(dir_list);
603+
return NULL;
604+
}
605+
}
606+
Py_DECREF(name);
607+
}
608+
if (PyList_Sort(dir_list) < 0) {
609+
Py_DECREF(dir_list);
610+
return NULL;
611+
}
612+
return dir_list;
613+
}
614+
615+
static PyMethodDef fortran_methods[] = {
616+
{"__dir__", (PyCFunction)fortran_dir, METH_NOARGS, NULL},
617+
{NULL, NULL, 0, NULL}
618+
};
619+
579620
PyTypeObject PyFortran_Type = {
580621
PyVarObject_HEAD_INIT(NULL, 0).tp_name = "fortran",
581622
.tp_basicsize = sizeof(PyFortranObject),
582623
.tp_dealloc = (destructor)fortran_dealloc,
583624
.tp_getattr = (getattrfunc)fortran_getattr,
584625
.tp_setattr = (setattrfunc)fortran_setattr,
585626
.tp_repr = (reprfunc)fortran_repr,
627+
.tp_methods = fortran_methods,
586628
.tp_call = (ternaryfunc)fortran_call,
587629
};
588630

numpy/f2py/tests/test_modules.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,19 @@ class TestModuleAndSubroutine(util.F2PyTest):
5858
sources = [
5959
util.getpath("tests", "src", "modules", "gh25337", "data.f90"),
6060
util.getpath("tests", "src", "modules", "gh25337", "use_data.f90"),
61+
util.getpath("tests", "src", "regression", "datonly.f90"),
6162
]
6263

6364
def test_gh25337(self):
6465
self.module.data.set_shift(3)
6566
assert "data" in dir(self.module)
6667

68+
def test_allocatable_in_dir(self):
69+
# gh-27696: allocatable arrays should appear in dir()
70+
names = dir(self.module.datonly)
71+
assert "data_array" in names
72+
assert "max_value" in names
73+
6774

6875
@pytest.mark.slow
6976
class TestUsedModule(util.F2PyTest):

0 commit comments

Comments
 (0)