Yes, it has to do with the fact that built-ins are generally implemented in C. Really often C code will introduce new types instead of plain functions, as in the case of enumerate.
Writing them in C provide finer control over them and often some performance improvements,
and since there is no real downside it's a natural choice.
Take into account that to write the equivalent of:
def enumerate(sequence, start=0):
n = start
for elem in sequence:
yield n, elem
n += 1
in C, i.e. a new instance of a generator, you should create a code object that contains the actual bytecode. This is not impossible, but is not so easier than writing a new type which simply implements __iter__ and __next__ calling the Python C-API, plus the other advantages of having a different type.
So, in the case of enumerate and reversed it's simply because it provides better performance, and it's more maintainable.
Other advantages include:
- You can add methods to the type(e.g.
chain.from_iterable). This could be done even with functions, but you'd have to first define them and then manually set the attributes, which doesn't look so clean.
- You can us
isinstance on the iterables. This could allow some optimizations(e.g if you know that isinstance(iterable, itertools.repeat), then you may be able to optimize the code since you know which values will be yielded.
Edit: Just to clarify what I mean by:
in C, i.e. a new instance of a generator, you should create a code
object that contains the actual bytecode.
Looking at Objects/genobject.c the only function to create a PyGen_Type instance is PyGen_New whose signature is:
PyObject *
PyGen_New(PyFrameObject *f)
Now, looking at Objects/frameobject.c we can see that to create a PyFrameObject you must call PyFrame_New, which has this signature:
PyFrameObject *
PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals,
PyObject *locals)
As you can see it requires a PyCodeObject instance. PyCodeObjects are how the python interpreter represents bytecode internally(e.g. a PyCodeObject can represent the bytecode of a function), so: yes, to create a PyGen_Type instance from C you must manually create the bytecode, and it's not so easy to create PyCodeObjects since PyCode_New has this signature:
PyCodeObject *
PyCode_New(int argcount, int kwonlyargcount,
int nlocals, int stacksize, int flags,
PyObject *code, PyObject *consts, PyObject *names,
PyObject *varnames, PyObject *freevars, PyObject *cellvars,
PyObject *filename, PyObject *name, int firstlineno,
PyObject *lnotab)
Note how it contains arguments such as firstlineno, filename which are obviously meant to be obtained by python source and not from other C code. Obviously you can create it in C, but I'm not at all sure that it would require less characters than writing a simple new type.