Pregunta

Quiero hacer una coincidencia de patrones en las listas en Python. Por ejemplo, en Haskell, puedo hacer algo como lo siguiente:

fun (head : rest) = ...

Entonces, cuando paso en una lista, head será el primer elemento, y rest serán los elementos finales.

Del mismo modo, en Python, puedo descomprimir automáticamente las tuplas:

(var1, var2) = func_that_returns_a_tuple()

Quiero hacer algo similar con listas en Python. En este momento, tengo una función que devuelve una lista y un fragmento de código que hace lo siguiente:

ls = my_func()
(head, rest) = (ls[0], ls[1:])

Me pregunté si podría hacerlo de alguna manera en una línea en Python, en lugar de dos.

¿Fue útil?

Solución

Por lo que sé, no hay forma de convertirlo en una sola línea en Python actual sin introducir otra función, por ejemplo:

split_list = lambda lst: (lst[0], lst[1:])
head, rest = split_list(my_func())

Sin embargo, en Python 3.0, la sintaxis especializada utilizada para las firmas de argumentos variables y el desempaquetado de los argumentos también estará disponible para este tipo de desempaquetado de secuencias generales, por lo que en 3.0 podrá escribir:

head, *rest = my_func()

Consulte PEP 3132 para obtener más información.

Otros consejos

En primer lugar, tenga en cuenta que la " coincidencia de patrones " Los lenguajes funcionales y la asignación de tuplas que mencionas no son realmente tan similares. En los lenguajes funcionales, los patrones se utilizan para dar definiciones parciales de una función. Así que f (x: s) = e no significa tomar la cabeza y la cola del argumento de f y devolver e usándolos, pero significa que si el argumento de f tiene el formato x: s (para algunos x y s ), luego f (x: s) es igual a e .

La asignación de python es más como una asignación múltiple (sospecho que fue su intención original). Así que escribe, por ejemplo, x, y = y, x para intercambiar los valores en x y y sin necesidad de una variable temporal (como lo haría con una simple declaración de asignación). Esto tiene poco que ver con la coincidencia de patrones, ya que es básicamente una abreviatura para el " simultáneo " ejecución de x = y y y = x . Aunque Python permite secuencias arbitrarias en lugar de listas separadas por comas, no sugeriría llamar a este patrón de coincidencia. Con la coincidencia de patrones usted verifica si algo coincide o no con un patrón; en la asignación de python, debe asegurarse de que las secuencias en ambos lados sean las mismas.

Para hacer lo que parece querer, usualmente (también en lenguajes funcionales) usará una función auxiliar (como lo mencionaron otros) o algo similar a let o donde constructos (que puede considerar como usar funciones anónimas). Por ejemplo:

(head, tail) = (x[0], x[1:]) where x = my_func()

O, en python real:

(head, tail) = (lambda x: (x[0], x[1:]))(my_func())

Tenga en cuenta que esto es esencialmente lo mismo que las soluciones dadas por otros con una función auxiliar, excepto que esta es la línea que usted quería. Sin embargo, no es necesariamente mejor que una función separada.

(Lo siento si mi respuesta es un poco exagerada. Creo que es importante dejar clara la distinción)

Este es un enfoque "puramente funcional" y, como tal, es un lenguaje sensible en Haskell, pero probablemente no sea tan apropiado para Python. Python solo tiene un concepto muy limitado de patrones de esta manera, y sospecho que podría necesito un sistema de tipo algo más rígido para implementar ese tipo de construcción ( erlang buffs invitados a estar en desacuerdo aquí).

Lo que tienes es probablemente lo más cercano a ese idioma, pero probablemente estés mejor usando una comprensión de lista o un enfoque imperativo en lugar de llamar recursivamente a una función con la cola de la lista.

Como se ha indicado indicado en algunas ocasiones antes , Python no es realmente un lenguaje funcional. Solo toma prestadas ideas del mundo de FP. No es intrínsecamente Tail Recursive de la forma que usted esperaría ver incrustado en la arquitectura de un lenguaje funcional, por lo que tendría dificultades para realizar este tipo de operación recursiva en un conjunto de datos de gran tamaño sin utilizar mucho espacio de pila.

el desempaquetado extendido se introdujo en 3.0 http://www.python.org/dev/peps/pep-3132/

A diferencia de Haskell o ML, Python no tiene estructuras de emparejamiento de patrones incorporadas. La forma más pitónica de hacer una comparación de patrones es con un bloque try-except:

def recursive_sum(x):
    try:
        head, tail = x[0], x[1:]
        return head + recursive-sum(tail)
    except IndexError:  # empty list: [][0] raises IndexError
        return 0

Tenga en cuenta que esto solo funciona con objetos con indexación de segmentos. Además, si la función se complica, algo en el cuerpo después de la línea head, tail podría generar un IndexError, lo que dará lugar a errores sutiles. Sin embargo, esto le permite hacer cosas como:

for frob in eggs.frob_list:
    try:
        frob.spam += 1
    except AttributeError:
        eggs.no_spam_count += 1

En Python, la recursión de la cola generalmente se implementa mejor como un bucle con un acumulador, es decir:

def iterative_sum(x):
    ret_val = 0
    for i in x:
        ret_val += i
    return ret_val

Esta es la única manera obvia y correcta de hacerlo el 99% del tiempo. No solo es más claro de leer, sino que es más rápido y funcionará en otras cosas que no sean listas (conjuntos, por ejemplo). Si hay una excepción esperando a que ocurra allí, la función fallará y lo entregará en la cadena.

Estoy trabajando en pyfpm , una biblioteca para la comparación de patrones en Python con una sintaxis similar a Scala . Puede usarlo para desempaquetar objetos como este:

from pyfpm import Unpacker

unpacker = Unpacker()

unpacker('head :: tail') << (1, 2, 3)

unpacker.head # 1
unpacker.tail # (2, 3)

O en el arglista de una función:

from pyfpm import match_args

@match_args('head :: tail')
def f(head, tail):
    return (head, tail)

f(1)          # (1, ())
f(1, 2, 3, 4) # (1, (2, 3, 4))

Bueno, ¿por qué lo quieres en 1-línea en primer lugar?

Si realmente quieres, siempre puedes hacer un truco como este:

def x(func):
  y = func()
  return y[0], y[1:]

# then, instead of calling my_func() call x(my_func)
(head, rest) = x(my_func) # that's one line :)

Además de las otras respuestas, tenga en cuenta que la operación de cabeza / cola equivalente en Python, incluida la extensión de la sintaxis * de python3, generalmente será menos eficiente que la coincidencia de patrones de Haskell.

Las listas de Python se implementan como vectores, por lo que la obtención de la cola tendrá que tomar una copia de la lista. Esto es O (n) wrt del tamaño de la lista, mientras que una implementación que usa listas enlazadas como Haskell puede simplemente usar el puntero de cola, una operación O (1).

La única excepción puede ser los enfoques basados ??en iteradores, donde la lista no se devuelve realmente, pero sí un iterador. Sin embargo, esto puede no ser aplicable a todos los lugares donde se desea una lista (por ejemplo, iterando varias veces).

Por ejemplo, Enfoque de cifrado , si se modifica para devolver el iterador en lugar de convertirlo en una tupla tendrá este comportamiento. Alternativamente, un método más simple de solo 2 elementos que no se basa en el bytecode sería:

def head_tail(lst):
    it = iter(list)
    yield it.next()
    yield it

>>> a, tail = head_tail([1,2,3,4,5])
>>> b, tail = head_tail(tail)
>>> a,b,tail
(1, 2, <listiterator object at 0x2b1c810>)
>>> list(tail)
[3, 4]

Obviamente, aunque todavía tienes que envolver una función de utilidad en lugar de tener un buen azúcar sintáctica para ella.

hubo un comentario en el libro de cocina de python para hacer esto. Parece que no puedo encontrarlo ahora, pero aquí está el código (lo modifiqué ligeramente)


def peel(iterable,result=tuple):
    '''Removes the requested items from the iterable and stores the remaining in a tuple
    >>> x,y,z=peel('test')
    >>> print repr(x),repr(y),z
    't' 'e' ('s', 't')
    '''
    def how_many_unpacked():
        import inspect,opcode
        f = inspect.currentframe().f_back.f_back
        if ord(f.f_code.co_code[f.f_lasti])==opcode.opmap['UNPACK_SEQUENCE']:
            return ord(f.f_code.co_code[f.f_lasti+1])
        raise ValueError("Must be a generator on RHS of a multiple assignment!!")
    iterator=iter(iterable)
    hasItems=True
    amountToUnpack=how_many_unpacked()-1
    next=None
    for num in xrange(amountToUnpack):
        if hasItems:        
            try:
                next = iterator.next()
            except StopIteration:
                next = None
                hasItems = False
        yield next
    if hasItems:
        yield result(iterator)
    else:
        yield None

Sin embargo, debes tener en cuenta que eso solo funciona cuando se usa un desempaquetado de asignación debido a la forma en que se inspecciona el marco anterior ... sigue siendo bastante útil.

Para su caso de uso específico: emulación de fun (head: rest) = ... de Haskell, seguro. Las definiciones de funciones han permitido desempaquetar parámetros durante bastante tiempo:

def my_method(head, *rest):
    # ...

A partir de Python 3.0, como @bpowah mencionado , Python también admite el desempaquetado en la asignación:

my_list = ['alpha', 'bravo', 'charlie', 'delta', 'echo']
head, *rest = my_list
assert head == 'alpha'
assert rest == ['bravo', 'charlie', 'delta', 'echo']

Tenga en cuenta que el asterisco (la "splat") significa "el resto de lo iterable", no "hasta el final". Lo siguiente funciona bien:

first, *middle, last = my_list
assert first == 'alpha'
assert last == 'echo'
assert middle == ['bravo', 'charlie', 'delta']

first, *middle, last = ['alpha', 'bravo']
assert first == 'alpha'
assert last == 'bravo'
assert middle == []
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top