python haskell functional-programming let

¿Hay un equivalente en Python del ''let'' de Haskell?



range python 3 (8)

¿Existe un equivalente en Python de la expresión ''let'' de Haskell que me permita escribir algo como:

list2 = [let (name,size)=lookup(productId) in (barcode(productId),metric(size)) for productId in list]

Si no, ¿cuál sería la alternativa más legible ?

Agregado para aclarar la sintaxis de let:

x = let (name,size)=lookup(productId) in (barcode(productId),metric(size))

es equivalente a

(name,size) = lookup(productId) x = (barcode(productId),metric(size))

Sin embargo, la segunda versión no funciona tan bien con listas de comprensión.


Aunque puedes simplemente escribir esto como:

list2 = [(barcode(pid), metric(lookup(pid)[1])) for pid in list]

Podrías definir LET para obtener:

list2 = [LET((''size'', lookup(pid)[1]), lambda o: (barcode(pid), metric(o.size))) for pid in list]

o incluso:

list2 = map(lambda pid: LET((''name_size'', lookup(pid), ''size'', lambda o: o.name_size[1]), lambda o: (barcode(pid), metric(o.size))), list)

como sigue:

import types def _obj(): return lambda: None def LET(bindings, body, env=None): ''''''Introduce local bindings. ex: LET((''a'', 1, ''b'', 2), lambda o: [o.a, o.b]) gives: [1, 2] Bindings down the chain can depend on the ones above them through a lambda. ex: LET((''a'', 1, ''b'', lambda o: o.a + 1), lambda o: o.b) gives: 2 '''''' if len(bindings) == 0: return body(env) env = env or _obj() k, v = bindings[:2] if isinstance(v, types.FunctionType): v = v(env) setattr(env, k, v) return LET(bindings[2:], body, env)


Como solicitó la mejor legibilidad, podría considerar la opción lambda pero con un pequeño giro: inicialice los argumentos. Aquí hay varias opciones que uso, empezando con la primera que probé y terminando con la que más uso ahora.

Supongamos que tenemos una función (no mostrada) que obtiene data_structure como argumento, y usted necesita obtener x de ella repetidamente.

Primer intento (según la respuesta de 2012 de huon):

(lambda x: x * x + 42 * x) (data_structure[''a''][''b''])

Con varios símbolos, esto se vuelve menos legible, así que a continuación intenté:

(lambda x, y: x * x + 42 * x + y) (x = data_structure[''a''][''b''], y = 16)

Eso todavía no es muy legible ya que repite los nombres simbólicos. Entonces intenté:

(lambda x = data_structure[''a''][''b''], y = 16: x * x + 42 * x + y)()

Esto casi se lee como una expresión ''let''. El posicionamiento y el formato de las tareas es suyo, por supuesto.

Este idioma se reconoce fácilmente por el inicio ''('' y el final ''()''.

En expresiones funcionales (también en Python), muchos paréntesis tienden a acumularse al final. El extraño "(" es fácil de detectar.


El múltiplo para las cláusulas en la respuesta de b0fh es el estilo que he estado usando personalmente por un tiempo, ya que creo que proporciona más claridad y no llena el espacio de nombres con funciones temporales. Sin embargo, si la velocidad es un problema, es importante recordar que la construcción temporal de una lista de elementos toma notablemente más tiempo que la construcción de una tupla única.

Comparando la velocidad de las diversas soluciones en este hilo, encontré que el truco feo lambda es más lento, seguido por los generadores anidados y luego la solución por b0fh. Sin embargo, todos estos fueron superados por el ganador de una tupla:

list2 = [ barcode(productID), metric(size) for productID in list for (_, size) in (lookup(productID),) ]

Esto puede no ser tan relevante para la pregunta del OP, pero hay otros casos en los que se puede mejorar la claridad y aumentar la velocidad en los casos en que se podría desear usar una comprensión de lista, usando tuplas en lugar de listas para iteradores ficticios.


Las versiones recientes de python permiten múltiples cláusulas para una expresión generadora, por lo que ahora puedes hacer algo como:

list2 = [ barcode(productID), metric(size) for productID in list for (name,size) in (lookup(productID),) ]

que es similar a lo que Haskell proporciona también:

list2 = [ (barcode productID, metric size) | productID <- list , let (name,size) = lookup productID ]

y denotacionalmente equivalente a

list2 = [ (barcode productID, metric size) | productID <- list , (name,size) <- [lookup productID] ]


No existe tal cosa. Podrías emularlo de la misma forma en let está desugared al cálculo lambda ( let x = foo in bar <=> (/x -> bar) (foo) ).

La alternativa más legible depende de las circunstancias. Para su ejemplo específico, elegiría algo como [barcode(productId), metric(size) for productId, (_, size) in zip(productIds, map(lookup, productIds))] (realmente feo en el segundo pensamiento, es más fácil si no necesita productId también, entonces podría usar map ) o un bucle explícito (en un generador):

def barcodes_and_metrics(productIds): for productId in productIds: _, size = lookup(productId) yield barcode(productId), metric(size)


Para obtener algo vagamente comparable, tendrá que hacer dos comprensiones o mapas, o definir una nueva función. Un enfoque que aún no se ha sugerido es dividirlo en dos líneas como tal. Creo que esto es algo legible; aunque probablemente la definición de su propia función sea la forma correcta de hacerlo:

pids_names_sizes = (pid, lookup(pid) for pid in list1) list2 = [(barcode(pid), metric(size)) for pid, (name, size) in pids_names_sizes]


Podrías usar una lista temporal de comprensión.

[(barcode(productId), metric(size)) for name, size in [lookup(productId)]][0]

o, equivalentemente, una expresión generadora.

next((barcode(productId), metric(size)) for name, size in [lookup(productId)])

Pero ambos son bastante horribles.

Otro método (horrible) es a través de un lambda temporal, al que llamas inmediatamente.

(lambda (name, size): (barcode(productId), metric(size)))(lookup(productId))

Creo que la forma recomendada de "Pythonic" sería definir una función, como

def barcode_metric(productId): name, size = lookup(productId) return barcode(productId), metric(size) list2 = [barcode_metric(productId) for productId in list]


Solo adivinando qué hace Haskell, aquí está la alternativa. Utiliza lo que se conoce en Python como "comprensión de lista".

[barcode(productId), metric(size) for (productId, (name, size)) in [ (productId, lookup(productId)) for productId in list_] ]

Podría incluir el uso de lambda: como otros han sugerido.