Many iterator “functions” in the __builtin__ module are actually implemented as types, even although the documentation talks about them as being “functions”. Take for instance enumerate. The documentation says that it is equivalent to:
def enumerate(sequence, start=0):
n = start
for elem in sequence:
yield n, elem
n += 1
Which is exactly as I would have implemented it, of course. However, I ran the following test with the previous definition, and got this:
>>> x = enumerate(range(10))
>>> x
<generator object enumerate at 0x01ED9F08>
Which is what I expect. However, when using the __builtin__ version, I get this:
>>> x = enumerate(range(10))
>>> x
<enumerate object at 0x01EE9EE0>
From this I infer that it is defined as
class enumerate:
def __init__(self, sequence, start=0):
# ....
def __iter__(self):
# ...
Rather than in the standard form the documentation shows. Now I can understand how this works, and how it is equivalent to the standard form, what I want to know is what is the reason to do it this way. Is it more efficient this way? Does it has something to do with these functions being implemented in C (I don’t know if they are, but I suspect so)?
I’m using Python 2.7.2, just in case the difference is important.
Thanks in advance.
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:
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
enumerateandreversedit’s simply because it provides better performance, and it’s more maintainable.Other advantages include:
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.isinstanceon the iterables. This could allow some optimizations(e.g if you know thatisinstance(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:
Looking at
Objects/genobject.cthe only function to create aPyGen_Typeinstance isPyGen_Newwhose signature is:Now, looking at
Objects/frameobject.cwe can see that to create aPyFrameObjectyou must callPyFrame_New, which has this signature:As you can see it requires a
PyCodeObjectinstance.PyCodeObjects are how the python interpreter represents bytecode internally(e.g. aPyCodeObjectcan represent the bytecode of a function), so: yes, to create aPyGen_Typeinstance from C you must manually create the bytecode, and it’s not so easy to createPyCodeObjects sincePyCode_Newhas this signature:Note how it contains arguments such as
firstlineno,filenamewhich 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.