2008-10-26 14 views
34

Quiero hacer algunas coincidencias de patrones en listas en Python. Por ejemplo, en Haskell, puedo hacer algo como lo siguiente:Coincidencia de patrones de listas en Python

fun (head : rest) = ... 

Así que cuando paso en una lista, head será el primer elemento, y rest serán los elementos que se arrastran.

Del mismo modo, en Python, puedo descomprimir automáticamente 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 trozo de código que hace lo siguiente:

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

Me preguntaba si de alguna manera podría hacer eso en una sola línea en Python, en lugar de dos.

Respuesta

55

Por lo que yo sé no hay manera para que sea una sola línea en la corriente de Python 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 especializado que se utiliza para la firma de argumentos variadic y argumento desembalaje quedará disponible para este tipo de secuencia general de desempaquetar, así, por lo que en 3.0 podrás escribir:

head, *rest = my_func() 

Ver PEP 3132 para más detalles.

+0

Por supuesto se puede poner ese lambda en la misma línea con todo lo demás: cabeza, resto = (lambda lst: (lst [0], lst [1:])) (my_func()) –

+11

Sí, pero eso comienza a rayar en la ofuscación. –

+1

+1 para la nueva característica de Python 3 y vinculación al PEP. – fossilet

4

Eso en gran medida un enfoque de 'pura funcional' y como tal es una expresión idiomática es una sensata en Haskell, pero probablemente no es tan apropiado para Python. Python sólo tiene un concepto muy limitado de patterns de esta manera - y sospecho que es posible que tenga un sistema de tipo algo más rígida para implementar ese tipo de construcción (erlang aficionados a invitados a estar en desacuerdo aquí).

Lo que tiene es probablemente lo más cercano que pueda llegar a ese modismo, pero probablemente sea mejor usar una lista de comprensión o un enfoque imperativo en lugar de llamar recursivamente a una función con el final de la lista.

Como ha sido statedon a few occasionsbefore, Python no es en realidad un lenguaje funcional. Simplemente toma prestada ideas del mundo de FP. No es intrínsecamente Tail Recursive de la manera que esperaría ver incrustado en la arquitectura de un lenguaje funcional, por lo que tendría alguna dificultad para realizar este tipo de operación recursiva en un gran conjunto de datos sin utilizar mucho espacio de pila.

2

Bueno, por qué lo quiere en 1 línea en el primer lugar?

Si realmente quiere, siempre se puede hacer un truco como esto:

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 :) 
+2

Principalmente porque, suponiendo que haya una sintaxis "agradable", sería más fácil de leer. – mipadi

+0

También porque 'cabeza' y 'cola' son listas estándar idioms. Se usan en todas las estructuras de datos y en el libro de algoritmos por una razón. Python no tiene una función incorporada 'head (my_list)' es el problema real. –

32

En primer lugar, tenga en cuenta que la "coincidencia de patrones" de idiomas funcionales y la asignación a las tuplas que mencionas no son realmente similares. En los lenguajes funcionales, los patrones se utilizan para dar definiciones parciales de una función.Así f (x : s) = e no significa tomar la cabeza y la cola del argumento de f y volver e usarlos, pero significa que si el argumento de f es de la forma x : s (por alguna x y s), continuaciónf (x : s) es igual a e.

La asignación de python es más como una asignación múltiple (sospecho que era su intención original). Por lo tanto, escriba, 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 declaración de asignación simple). Esto tiene poco que ver con la coincidencia de patrones, ya que es básicamente una abreviatura de la ejecución "simultánea" de x = y y y = x. Aunque Python permite secuencias arbitrarias en lugar de listas separadas por comas, no recomendaría llamar a este patrón de coincidencia. Con la coincidencia de patrones puede verificar 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 desear, normalmente (también en lenguajes funcionales) utilizaría una función auxiliar (como lo mencionan otros) o algo similar a las construcciones let o where (que puede considerar que usan funciones anónimas). Por ejemplo:

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

O, en pitón real:

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

Tenga en cuenta que este es esencialmente el mismo que las soluciones dadas por otros con una función auxiliar, excepto que este es el de una sola línea que quería . Sin embargo, no es necesariamente mejor que una función separada.

(Lo siento si mi respuesta es un poco por encima. Sólo creo que es importante hacer la distinción clara.)

1

había un reciepe en el libro de cocina de pitón para hacer esto. i parece que no puede encontrar ahora, pero aquí está el código (he modificado 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, se advierte lo que sólo funciona cuando se utiliza una asignación de desempaquetado debido a la forma en que inespects la trama previa ... todavía es bastante útil.

2

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. Este es O (n) el 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 pueden ser los enfoques basados ​​en iterador, donde la lista no se devuelve realmente, pero sí un iterador. Sin embargo, esto puede no ser aplicable en todos los lugares donde se desea una lista (por ejemplo, iterar varias veces).

Por ejemplo, el enfoque Cipher's, si se modifica para devolver el iterador en lugar de convertirlo en una tupla tendrá este comportamiento.Alternativamente, un simple 2-elemento único método no confía en el código de bytes 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, sin embargo todavía tiene que envolver en una función de utilidad más que haber azúcar sintáctico agradable para él.

2

A diferencia de Haskell o ML, Python no tiene una coincidencia de patrones incorporada de las estructuras. La forma más Pythonic de hacer de patrones es con un bloque try-excepto:

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 sólo funciona con objetos con la indexación rebanada. Además, si la función se complica, algo en el cuerpo después de la línea head, tail puede provocar un error Indexer, lo que dará lugar a errores sutiles. Sin embargo, esto le permite hacer cosas como:

En Python, la recursión de cola es generalmente mejor implementado 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 

Ésta es la más obvia, a la derecha forma de hacerlo el 99% del tiempo. No solo es más fácil de leer, es más rápido y funcionará en otras cosas que no sean listas (conjuntos, por ejemplo). Si hay una excepción esperando que ocurra allí, la función fracasará felizmente y la entregará en la cadena.

2

Estoy trabajando en pyfpm, una biblioteca para la coincidencia de patrones en Python con una sintaxis tipo Scala. Se puede utilizar para descomprimir los objetos de esta manera:

from pyfpm import Unpacker 

unpacker = Unpacker() 

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

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

O en arglist 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)) 
+0

muy, muy antiponético :) –

Cuestiones relacionadas