Comments 21
Принцип единственной ответственности решает эту проблему и вряд ли стоит тащить код подобный как в статье в продакшн.
Вот вполне себе production код, чтобы сделать "плоским" irregular list произвольной вложенности:
def flatten(l):
for el in l:
if isinstance(el, Iterable):
for sub in flatten(el):
yield sub
else:
yield el
И он не работает со строками если не добавить условие проверки на строку:
if isinstance(el, Iterable) and not isinstance(el, (str, bytes)):
...
Так работает.
произвольной вложенности
Это не так. Попробуйте сделать со своим кодом следующее (в CPython):
nested = reduce(lambda acc, _: [acc], range(sys.getrecursionlimit()), 0)
tuple(flatten(nested))
Получите RuntimeError: maximum recursion depth
.
Если действительно нужно обрабатывать коллекции произвольной вложенности, можно использовать нерекурсивный подход (использовать свой стек).
А ещё в Python 3 можно использовать yield from
:
def flatten(l):
for el in l:
if isinstance(el, Iterable):
yield from flatten(el)
else:
yield el
Очевидно, что любая рекурсивная функция когда-то упрётся в recursion limit. Произвольная вложенность, конечно, в разумных пределах.
Про yield from понятно, но у меня подобный код используется в AsyncGenerator, где нет поддержки yield from.
Например алгоритм Евклида для поиска наибольшего общего делителя хорошо работает и в рекурсивном виде для обычных чисел. При этом именно такая реализация является самой наивной, лёгкой для восприятия.
Да, я неверно написал. Конечно, не любая, а которая достигает терминального состояния глубже чем заданный предел рекурсии. Но утверждать, что рекурсивная функция некорректна только из-за того, что она может достичь какого-то заданного предела глубины рекурсии, не перейдя в терминальную ветвь, тоже неверно.
__iter__
и итерируется по «символам», но возвращает он всётаки строки.Совсем другое дело с типом bytes, для его элементов в питоне есть специальный тип — byte. И для него всё как бы нормально — итератор возвращает «числа», а не bytes с длиной в один байт.
В ruby 1.8.х у строки был метод each
(практически аналог __iter__
в руби) https://ruby-doc.org/core-1.8.7/String.html#method-i-each, который итерировался по "линиям" (и был алиасом к each_line
). Это лучше, чем по символам, но всё равно мешал.
В 1.9.1 его выкинули. Оставили each_line
, each_byte
и each_char
.
Что можно сделать?
В подобном коде нам остаётся только добавлять условие для проверки строк:
if isinstance(foo, Iterable) and not isinstance(foo, (str, bytes)):
...
Реальный случай: клиент в JSON вместо списка строк передал строку. И код работал, итерируясь по символам строки.
Должны ли строки в Python быть итерируемы?