Search code examples
pythonpython-3.xcpythonpython-c-api

Python C-API: Using `PySequence_Length` with dictionaries


I'm trying to use PySequence_Length to get the length of a Python dictionary in C. I realize I can use PyDict_Size, but I'm interested in using a more generic function in certain contexts.

PyObject* d = PyDict_New();
Py_ssize_t res = PySequence_Length(d);

printf("Result : %ld\n", res);
if (res == -1) PyErr_Print();

This fails, and prints the error:

TypeError: object of type 'dict' has no len()

My question is: why does this fail? Although Python dictionary objects don't support the Sequence protocol, the documentation for PySequence_Length says:

Py_ssize_t PySequence_Length(PyObject *o)

Returns the number of objects in sequence o on success, and -1 on failure. For objects that do not provide sequence protocol, this is equivalent to the Python expression len(o).

Since a dictionary type does have a __len__ attribute, and since the expression len(d) (where d is a dictionary) properly returns the length in Python, I don't understand why PySequence_Length should fail in C.

Any explanation? Is the documentation incorrect here?


Solution

  • The documentation is misleading, yes. A dict is not a sequence, even though it does implement some parts of the sequence protocol (for containment tests, which are part of the sequence protocol.) This particular distinction in the Python/C types API is unfortunate, but it's an artifact of a design choice made decades ago. The documentation reflects that distinction, albeit in an equally awkward way. What it tries to say is that for Python classes it's the same thing as len(o), regardless of what the Python class pretends to be. For C types, if the type does not implement the sequence version of the sizefunc, PySequence_Length() will raise an exception without even considering whether the type has the mapping version of the sizefunc.

    If you are not entirely sure whether you have a sequence or not, you should use PyObject_Size() instead. In fact, there's very little reason to call PySequence_Length(); normally you either know the type (because you just created it, and you can call a type-specific length function or macro like PyList_GET_SIZE()) or you don't even know if it'll be a sequence.