Reemplazo de Python 3 para la compilación obsoleta. Aplanar la función

¿Cuál es la forma recomendada para aplanar las listas anidadas desde ladesaprobación del paquete compilador?

>>> from compiler.ast import flatten
>>> flatten(["junk",["nested stuff"],[],[[]]])
['junk', 'nested stuff']

Sé que hay algunas respuestas de desbordamiento de pila para el aplanamiento de listas, pero estoy esperando que el paquete estándar y "pythonic" sea una forma obvia y obvia de hacerlo.

Respuestas a la pregunta(6)

itertools.chain es la mejor solución para aplanar cualquier nivel anidado iterable, es altamente eficiente en comparación con cualquier solución de python puro.

Dicho esto, funcionará entodos iterables, por lo que es necesario realizar algunas comprobaciones si desea evitar el aplanamiento de las cadenas, por ejemplo.

Del mismo modo, no se aplanará mágicamente a una profundidad arbitraria. Dicho esto, en general, no se requiere una solución tan genérica; en su lugar, es mejor mantener sus datos estructurados para que no requieran un aplanamiento de esa manera.

Edit: Yo diría que si uno tuviera que hacer un aplanamiento arbitrario, esta es la mejor manera:

import collections

def flatten(iterable):
    for el in iterable:
        if isinstance(el, collections.Iterable) and not isinstance(el, str): 
            yield from flatten(el)
        else:
            yield el

Recuerda usarbasestring en 2.x másstryfor subel in flatten(el): yield el en lugar deyield from flatten(el) pre-3.3.

Como se señaló en los comentarios, yo diría que esta es la opción nuclear, y es probable que cause más problemas de los que resuelve. En su lugar, la mejor idea es hacer que su salida sea más regular (por ejemplo, la salida que contiene un elemento aún le da como una tupla de un elemento), y realizar un aplanamiento regular en un nivel en el que se introduce, en lugar de hacerlo al final.

Esto producirá más código lógico, legible y más fácil de trabajar. Naturalmente, hay casos en los quenecesitar para hacer este tipo de aplanamiento (si los datos provienen de un lugar con el que no puede meterse, así que no tiene más remedio que tomarlos en un formato mal estructurado), en cuyo caso, este tipo de solución podría ser necesaria, pero en general, es probablemente una mala idea.

 Gareth Latty23 abr. 2013 20:58
@Mittenchops, expliqué en la respuesta que aplanaría las cuerdas (ya que también son iterables) y no funcionaría a una profundidad arbitraria. En general, aplanar listas anidadas arbitrariamente es la opción nuclear, y es probable que cause más problemas de los que resuelve. En su lugar, la mejor idea es hacer que su salida sea más regular (por ejemplo, la salida que contiene un elemento aún le da como una tupla de un elemento), y realizar un aplanamiento regular en un nivel en el que se introduce, en lugar de hacerlo al final.
 Gareth Latty23 abr. 2013 20:48
@Mittenchops Sí, ha eliminado una capa de anidamiento: la lista externa (que solo contiene un elemento).
 Gareth Latty23 abr. 2013 20:43
uniterado? No estoy realmente seguro de lo que significa significar.list(iterable) Es la mejor manera de obtener una lista de un iterable arbitrario.
 Mittenchops23 abr. 2013 20:42
Gracias. Esto puede ser una aclaración tonta, pero ¿tiene alguna recomendación sobre cómo anularla?>>> itertools.chain([["junk",["nested stuff"],[],[[]]]]) <itertools.chain object at 0x227dc90>  Usualmente utilizo la lista (iterable) para anularla, pero si hago eso en este caso, ¡acabo con una lista no plana!
 Gareth Latty23 abr. 2013 20:46
chain() toma un iterable como cada argumento, si quiere pasar un iterable, usechain.from_iterable(). Tenga en cuenta que (como se indica en la respuesta) solo eliminará una capa de anidamiento. Más allá de eso, realmente no vale la pena hacer una herramienta genérica, ya que diferentes situaciones tratarán diferentes iterables como la necesidad de ser aplanados (por ejemplo, cadenas).
 Mittenchops23 abr. 2013 20:54
Hmm, pero si intento anular eso al aplanarlo de nuevo, obtengo esto:>>> list(itertools.chain.from_iterable(itertools.chain.from_iterable([["junk",["nested stuff"],[],[[]]]]))) ['j', 'u', 'n', 'k', 'nested stuff', []] que todavía no es plano y también muy difícil de leer. = /
 Mittenchops23 abr. 2013 20:48
Lo siento, tal vez todavía no lo entiendo:>>> list(itertools.chain.from_iterable([["junk",["nested stuff"],[],[[]]]])) me da['junk', ['nested stuff'], [], [[]]]
 Mittenchops23 abr. 2013 20:44
Usando la lista de lo anterior, obtengo:>>> list(itertools.chain([["junk",["nested stuff"],[],[[]]]])) [['junk', ['nested stuff'], [], [[]]]] versusflatten() lo que da['junk', 'nested stuff']

Construido para la recursión: Python 3.6

def flatten(lst):
    """Flattens a list of lists"""
    return [subelem for elem in lst 
                    for subelem in elem]

Defina sus tipos en una lista y use los elementos incorporados para verificar

Mi feowhile-chain solución, sólo por diversión:

from collections import Iterable
from itertools import chain

def flatten3(seq, exclude=(str,)):
    sub = iter(seq)
    try:
        while sub:
            while True:
                j = next(sub)
                if not isinstance(j, Iterable) or isinstance(j, exclude):
                    yield j
                else:
                    sub = chain(j, sub)
                    break
    except StopIteration:
        return
Solución de preguntas

ista.

Para aplanar una lista anidada arbitrariamente en una nueva lista, esto funciona en Python 3 como espera:

import collections
def flatten(x):
    result = []
    for el in x:
        if isinstance(x, collections.Iterable) and not isinstance(el, str):
            result.extend(flatten(el))
        else:
            result.append(el)
    return result

print(flatten(["junk",["nested stuff"],[],[[]]]))  

Huellas dactilares:

['junk', 'nested stuff']

Si quieres un generador que haga lo mismo:

def flat_gen(x):
    def iselement(e):
        return not(isinstance(e, collections.Iterable) and not isinstance(e, str))
    for el in x:
        if iselement(el):
            yield el
        else:
            for sub in flat_gen(el): yield sub

print(list(flat_gen(["junk",["nested stuff"],[],[[[],['deep']]]]))) 
# ['junk', 'nested stuff', 'deep']

Para Python 3.3 y versiones posteriores, userendimiento de en lugar del bucle:

def flat_gen(x):
    def iselement(e):
        return not(isinstance(e, collections.Iterable) and not isinstance(e, str))
    for el in x:
        if iselement(el):
            yield el
        else:
            yield from flat_gen(el)   
 Gareth Latty23 abr. 2013 20:50
Crear una lista como esta es una solución realmente mala, un generador sería mucho más adecuado.
 Gareth Latty23 abr. 2013 21:38
Bien puede ser más lento crear un generador en una pequeña lista, pero cuando es pequeño, la diferencia de tiempo no va a importar. Cuando es grande, sin embargo, bien puede hacerlo. Lo mejor es usar el generador, y es más legible. Más allá de eso,hasattr(e, "__iter__") es generalmente un cheque mejor hecho usando elIterable ABC - es más legible de esa manera.
 dawg23 abr. 2013 21:33
Yo diría que 'realmente mala solución' es un poco fuerte. Si la lista anidada es relativamente pequeña, construir un generador podría ser más lento. Depende de la necesidad. He publicado ambos.

Puedes usaraplanar función desdefuncy biblioteca:

from funcy import flatten, isa
flat_list = flatten(your_list)

También puede especificar explícitamente qué valores seguir:

# Follow only sets
flat_list = flatten(your_list, follow=isa(set))

Echar un vistazo asu implementación Si quieres un algoritmo.

 Mittenchops30 nov. 2014 03:24
He llegado a gustarme mucho. Gracias por el consejo.

pero algo como esto ...

def flatten(l):
    for i in l:
        if isinstance(i, (list, tuple)):
            for ii in flatten(i):
                yield ii
        else:
            yield i

>>> l = ["junk",["nested stuff"],[],[[]]]
>>> list(flatten(l))
['junk', 'nested stuff']

... trabajará para listas y tuplas. Si desea admitir cualquier objeto que pueda utilizar en unafor item in object Expresión, entonces probablemente sea mejor usar tipografía de pato como esta ...

def flatten(l):
    for i in l:
        if isinstance(i, (str, bytes)):
            yield i
        else:
            try:
                for ii in flatten(i):
                    yield ii
            except TypeError:
                yield i

>>> l = ["junk",["nested stuff"],[],[[]]]
>>> list(flatten(l))
['junk', 'nested stuff']

... que es un poco más robusto que el chequeoisinstance(el, Iterable), ya que no va a hacer frente a algunos casos, como este ...

class Range10:
    def __getitem__(self, index):
        if index >= 10:
            raise IndexError
        return index

>>> import collections
>>> r10 = Range10()
>>> isinstance(r10, collections.Iterable)
False
>>> list(Range10())
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 Aya23 abr. 2013 21:13
@Lattyware Bueno, he notado un inconveniente de su opción, pero es una especie de caso de borde. Aún así, parece un poco duro bajar el voto por la razón que dio, particularmente porque ya no se aplica.
 Aya23 abr. 2013 21:00
@Lattyware ¿Algún comentario sobre la segunda opción?
 Aya23 abr. 2013 21:26
@Lattyware Recuerdo haber tenido una discusión similar con Martijn Pieters recientemente, quien sugirió usarisinstance(el, Sequence) para manejar el___getitem__() caso, aunque nunca estuvo claro si eso implicaba queSequence era un superconjunto estricto deIterable o si se requería probar ambas posibilidades.
 Gareth Latty23 abr. 2013 21:01
Lo siento, estaba respondiendo en mi propia pregunta. Mi problema es que Python es un lenguaje flexible y la comprobación de tipos es simplemente una mala solución. El solicitante puede pensar solo en listas, pero en algún momento use alguna estructura de datos personalizada que funcione como una lista, pero que no funcione con su código. Python está diseñado para la escritura de pato, por lo que las funciones que solo funcionan con ciertos tipos, a pesar de que otros tipos son capaces, son muy molestas de trabajar y, en general, son una mala idea.
 Gareth Latty23 abr. 2013 21:07
En cuanto al segundo método, no es terrible (es muy similar a la opción que di, excepto que toma eltry/except de antemano sobre el cheque - son muy similares, si la lista para ser aplanada tiene una sola proporción alta de elementos o una alta proporción de iterables anidados, uno podría ser preferible, pero es poco probable que importe. Como afirmo en mi respuesta, siento que la mejor solución esEvitar la necesidad de tal función en primer lugar..
 Gareth Latty23 abr. 2013 21:19
Verifique su recuento de votos, eliminé esa votación tan pronto como agregó la alternativa. En cuanto al caso de borde, sí, hay un ligero potencial, pero al mismo tiempo, el ABC para los resultados es claro que deben implementar__iter__(), así que diría que es más un caso de clase de diseño deficiente si se pretende que sea iterable. He visto casos en los que__getitem__() se ha definido donde la iteración sobre los valores no tendría sentido. Es un caso raro, pero diría que es difícil decir cuál debe ser el comportamiento en un objeto como ese.
 Aya23 abr. 2013 21:49
@Lattyware Parece que tienes razón. Creo que el punto que trato de señalar es la distinción entre el término "iterable" y "cualquier objeto que pueda usarse en unafor item in object expresión ". Así que si, como usted dice," Python está diseñado para la escritura de pato ", entonces ninguno de loscollections Las clases van a ayudar, y la única manera real de estar seguro es probandofor bucle, y atrapar la excepción si falla.
 Aya23 abr. 2013 20:51
@Lattyware La pregunta fue: "¿Cuál es la forma recomendada de aplanar las listas anidadas ..."
 Gareth Latty23 abr. 2013 20:50
-1 - La verificación de tipos contra listas y tuplas lo hace extremadamente inflexible.
 Gareth Latty23 abr. 2013 21:29
Estoy bastante seguroSequence requiere__len__() para ser definido, y probablemente prueba esIterable también.

Su respuesta a la pregunta