Erlang and Ruby both come with functions for flattening arrays. It seems like such a simple and useful tool to add to a language. One could do this:
>>> mess = [[1, [2]], 3, [[[4, 5]], 6]]
>>> mess.flatten()
[1, 2, 3, 4, 5, 6]
Or even:
>>> import itertools
>>> mess = [[1, [2]], 3, [[[4, 5]], 6]]
>>> list(itertools.flatten(mess))
[1, 2, 3, 4, 5, 6]
Instead, in Python, one has to go through the trouble of writing a function for flattening arrays from scratch. This seems silly to me, flattening arrays is such a common thing to do. It’s like having to write a custom function for concatenating two arrays.
I have Googled this fruitlessly, so I’m asking here; is there a particular reason why a mature language like Python 3, which comes with a hundred thousand various batteries included, doesn’t provide a simple method of flattening arrays? Has the idea of including such a function been discussed and rejected at some point?
13
Proposals for a flatten
function to be added to the standard library appear from time to time on python-dev and python-ideas mailing lists. Python developers usually respond with the following points:
-
A one-level flatten (turning an iterable of iterables into a single iterable) is a trivial one-line expression
(x for y in z for x in y)
and in any case is already in the standard library under the nameitertools.chain.from_iterable
. -
What are the use cases for a general-purpose multi-level flatten? Are these really compelling enough for the function to be added to the standard library?
-
How would a general-purpose multi-level flatten decide when to flatten and when to leave alone? You might think that a rule like “flatten anything that supports the iterable interface” would work, but that would lead to an infinite loop for
flatten('a')
.
See for example Raymond Hettinger:
It has been discussed ad nauseam on comp.lang.python. People seem to
enjoy writing their own versions of flatten more than finding legitimate
use cases that don’t already have trivial solutions.A general purpose flattener needs some way to be told what is atomic and
what can be further subdivided. Also, it is not obvious how the algorithm
should be extended to cover inputs with tree-like data structures with
data at nodes as well as the leaves (preorder, postorder, inorder
traversal, etc.)
7
It does come with such a method but it doesn’t call it flatten. It’s called “chain”. It returns an iterator which you’d then need to use the list() function on to turn it back into a list. If you don’t want to use a *, you can use the second “from_iterator” version. It works the same in Python 3. It will fail if the list input is not a list of lists.
[[1], [2, 3], [3, 4, 5]] #yes
[1, 2, [5, 6]] #no
There was at one time a flatten method in the compiler.ast module but this was deprecated in 2.6 and then removed in 3.0. Arbitrary depth recursion, necessary for arbitrarily nested lists does not function well with Python’s conservative maximum recursion depth. The reasoning for compiler’s removal were largely due to it being a mess. Compiler was turned into ast but flatten was left behind.
Arbitrary depth can be achieved with numpy’s arrays and that library’s flatten.
5
… maybe because it’s not that difficult to write one yourself
def flatten(l): return flatten(l[0]) + (flatten(l[1:]) if len(l) > 1 else []) if type(l) is list else [l]
… and then flatten all you want 🙂
>>> flatten([1,[2,3],4])
[1, 2, 3, 4]
>>> flatten([1, [2, 3], 4, [5, [6, {'name': 'some_name', 'age':30}, 7]], [8, 9, [10, [11, [12, [13, {'some', 'set'}, 14, [15, 'some_string'], 16], 17, 18], 19], 20], 21, 22, [23, 24], 25], 26, 27, 28, 29, 30])
[1, 2, 3, 4, 5, 6, {'age': 30, 'name': 'some_name'}, 7, 8, 9, 10, 11, 12, 13, set(['set', 'some']), 14, 15, 'some_string', 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
>>>
3