lambdas - lambda python
Asignación dentro de la expresión lambda en Python (12)
La asignación normal ( =
) no es posible dentro de una expresión lambda
, aunque es posible realizar varios trucos con setattr
y friends.
Sin embargo, resolver tu problema es bastante simple:
input = [Object(name=""), Object(name="fake_name"), Object(name="")]
output = filter(
lambda o, _seen=set():
not (not o and o in _seen or _seen.add(o)),
input
)
que te dará
[Object(Object(name=''''), name=''fake_name'')]
Como puede ver, conserva la primera instancia en blanco en lugar de la última. Si necesita el último en su lugar, invierta la lista para filter
, e invierta la lista que sale del filter
:
output = filter(
lambda o, _seen=set():
not (not o and o in _seen or _seen.add(o)),
input[::-1]
)[::-1]
que te dará
[Object(name=''fake_name''), Object(name='''')]
Una cosa a tener en cuenta: para que esto funcione con objetos arbitrarios, esos objetos deben implementar correctamente __eq__
y __hash__
como se explica here .
Tengo una lista de objetos y quiero eliminar todos los objetos que están vacíos, excepto uno, usando filter
y una expresión lambda
.
Por ejemplo, si la entrada es:
[Object(name=""), Object(name="fake_name"), Object(name="")]
... entonces la salida debería ser:
[Object(name=""), Object(name="fake_name")]
¿Hay alguna forma de agregar una tarea a una expresión lambda
? Por ejemplo:
flag = True
input = [Object(name=""), Object(name="fake_name"), Object(name="")]
output = filter(
(lambda o: [flag or bool(o.name), flag = flag and bool(o.name)][0]),
input
)
La forma pitónica de seguir el estado durante la iteración es con generadores. El camino itertools es bastante difícil de entender en mi humilde opinión y tratar de hackear lambdas para hacer esto es simple tontería. Lo intentaría:
def keep_last_empty(input):
last = None
for item in iter(input):
if item.name: yield item
else: last = item
if last is not None: yield last
output = list(keep_last_empty(input))
En general, la legibilidad supera a la compacidad cada vez.
No es necesario utilizar un lambda, cuando puede eliminar todos los nulos, y poner uno de nuevo si el tamaño de la entrada cambia:
input = [Object(name=""), Object(name="fake_name"), Object(name="")]
output = [x for x in input if x.name]
if(len(input) != len(output)):
output.append(Object(name=""))
No, no puedes poner una tarea dentro de una lambda debido a su propia definición. Si trabaja usando programación funcional, entonces debe asumir que sus valores no son mutables.
Una solución sería el siguiente código:
output = lambda l, name: [] if l==[] /
else [ l[ 0 ] ] + output( l[1:], name ) if l[ 0 ].name == name /
else output( l[1:], name ) if l[ 0 ].name == "" /
else [ l[ 0 ] ] + output( l[1:], name )
Puede realizar asignaciones locales como efecto secundario de las listas de comprensión en Python 2.
import sys
say_hello = lambda: [
[None for message in ["Hello world"]],
sys.stdout.write(message + "/n")
][-1]
say_hello()
Sin embargo, no es posible usar esto en su ejemplo porque su flag
variable está en un ámbito externo, no en el alcance de la lambda
. Esto no tiene que ver con lambda
, es el comportamiento general en Python 2. Python 3 te permite nonlocal
con la palabra clave nonlocal
dentro de def
s, pero nonlocal
no se puede usar dentro de lambda
y Python 3 elimina este lado efecto de las listas de comprensión, por lo que esto no es posible en Python 3.
Hay una solución (ver a continuación), pero mientras estamos en el tema ...
En algunos casos, puedes usar esto para hacer todo dentro de una lambda
:
(lambda: [
[''def''
for sys in [__import__(''sys'')]
for math in [__import__(''math'')]
for sub in [lambda *vals: None]
for fun in [lambda *vals: vals[-1]]
for echo in [lambda *vals: sub(
sys.stdout.write(u" ".join(map(unicode, vals)) + u"/n"))]
for Cylinder in [type(''Cylinder'', (object,), dict(
__init__ = lambda self, radius, height: sub(
setattr(self, ''radius'', radius),
setattr(self, ''height'', height)),
volume = property(lambda self: fun(
[''def'' for top_area in [math.pi * self.radius ** 2]],
self.height * top_area))))]
for main in [lambda: sub(
[''loop'' for factor in [1, 2, 3] if sub(
[''def''
for my_radius, my_height in [[10 * factor, 20 * factor]]
for my_cylinder in [Cylinder(my_radius, my_height)]],
echo(u"A cylinder with a radius of %.1fcm and a height "
u"of %.1fcm has a volume of %.1fcm³."
% (my_radius, my_height, my_cylinder.volume)))])]],
main()])()
Un cilindro con un radio de 10.0cm y una altura de 20.0cm tiene un volumen de 6283.2cm³.
Un cilindro con un radio de 20.0cm y una altura de 40.0cm tiene un volumen de 50265.5cm³.
Un cilindro con un radio de 30.0cm y una altura de 60.0cm tiene un volumen de 169646.0cm³.
Por favor no.
... volviendo al ejemplo original: aunque no puede realizar asignaciones a la variable de marcador en el ámbito externo, puede usar funciones para modificar el valor previamente asignado.
Por ejemplo, flag
podría ser un objeto cuyo .value
establezcamos utilizando setattr
:
flag = Object(value=True)
input = [Object(name=''''), Object(name=''fake_name''), Object(name='''')]
output = filter(lambda o: [
flag.value or bool(o.name),
setattr(flag, ''value'', flag.value and bool(o.name))
][0], input)
[Object(name=''''), Object(name=''fake_name'')]
Si quisiéramos adaptar el tema anterior, podríamos usar una lista de comprensión en lugar de setattr
:
[None for flag.value in [bool(o.name)]]
Pero realmente, en un código serio siempre debes usar una definición de función normal en lugar de una lambda
si vas a hacer una tarea.
flag = Object(value=True)
def not_empty_except_first(o):
result = flag.value or bool(o.name)
flag.value = flag.value and bool(o.name)
return result
input = [Object(name=""), Object(name="fake_name"), Object(name="")]
output = filter(not_empty_except_first, input)
Puede usar una función de vinculación para usar un pseudo multi-statement lambda. Luego puede usar una clase contenedora para un marcador para habilitar la asignación.
bind = lambda x, f=(lambda y: y): f(x)
class Flag(object):
def __init__(self, value):
self.value = value
def set(self, value):
self.value = value
return value
input = [Object(name=""), Object(name="fake_name"), Object(name="")]
flag = Flag(True)
output = filter(
lambda o: (
bind(flag.value, lambda orig_flag_value:
bind(flag.set(flag.value and bool(o.name)), lambda _:
bind(orig_flag_value or bool(o.name))))),
input)
Realmente no se puede mantener el estado en una expresión de filter
/ lambda
(a menos que se abuse del espacio de nombres global). Sin embargo, puede lograr algo similar usando el resultado acumulado que se transmite en una expresión de reduce()
:
>>> f = lambda a, b: (a.append(b) or a) if (b not in a) else a
>>> input = ["foo", u"", "bar", "", "", "x"]
>>> reduce(f, input, [])
[''foo'', u'''', ''bar'', ''x'']
>>>
Por supuesto, puedes modificar la condición un poco. En este caso, filtra los duplicados, pero también puede usar a.count("")
, por ejemplo, para restringir solo las cadenas vacías.
No hace falta decir que puedes hacer esto pero no deberías hacerlo. :)
Por último, puedes hacer cualquier cosa en Python lambda
pura: http://vanderwijk.info/blog/pure-lambda-calculus-python/
Si en lugar de flag = True
podemos hacer una importación en su lugar, entonces creo que cumple los criterios:
>>> from itertools import count
>>> a = [''hello'', '''', ''world'', '''', '''', '''', ''bob'']
>>> filter(lambda L, j=count(): L or not next(j), a)
[''hello'', '''', ''world'', ''bob'']
O tal vez el filtro está mejor escrito como:
>>> filter(lambda L, blank_count=count(1): L or next(blank_count) == 1, a)
O bien, solo por un booleano simple, sin ninguna importación:
filter(lambda L, use_blank=iter([True]): L or next(use_blank, False), a)
Si necesita un lambda para recordar el estado entre llamadas, recomendaría una función declarada en el espacio de nombres local o una clase con una __call__
sobrecargada. Ahora que todas mis advertencias en contra de lo que intentas hacer están fuera del camino, podemos obtener una respuesta real a tu consulta.
Si realmente necesita tener su lambda para tener algo de memoria entre llamadas, puede definirlo así:
f = lambda o, ns = {"flag":True}: [ns["flag"] or o.name, ns.__setitem__("flag", ns["flag"] and o.name)][0]
Entonces solo necesitas pasar f
para filter()
. Si realmente lo necesita, puede recuperar el valor de la flag
con lo siguiente:
f.__defaults__[0]["flag"]
Alternativamente, puede modificar el espacio de nombres global modificando el resultado de globals()
. Desafortunadamente, no se puede modificar el espacio de nombres local de la misma manera que la modificación del resultado de los locals()
no afecta el espacio de nombres local.
primero, no necesita usar una asignación local para su trabajo, simplemente marque la respuesta anterior
segundo, es fácil de usar locales () y globales () para obtener la tabla de variables y luego cambiar el valor
revisa este código de muestra:
print [locals().__setitem__(''x'', ''Hillo :]''), x][-1]
si necesita cambiar el agregar una variable global a su entorno, intente reemplazar locals () con globales ()
La compilación de la lista de Python es genial, pero la mayoría del proyecto tridimensional no acepta esto (como el matraz: [)
espero que pueda ayudar
ACTUALIZAR :
[o for d in [{}] for o in lst if o.name != "" or d.setdefault("", o) == o]
o usando filter
y lambda
:
flag = {}
filter(lambda o: bool(o.name) or flag.setdefault("", o) == o, lst)
Respuesta anterior
OK, ¿estás atrapado en el uso de filtro y lambda?
Parece que esto estaría mejor servido con una comprensión de diccionario,
{o.name : o for o in input}.values()
Creo que la razón por la que Python no permite la asignación en un lambda es similar a por qué no permite la asignación en una comprensión y eso tiene algo que ver con el hecho de que estas cosas se evalúan en el lado C
y así nos pueden dar un aumento en la velocidad. Al menos esa es mi impresión después de leer uno de los ensayos de Guido .
Creo que esto también iría en contra de la filosofía de tener una forma correcta de hacer cualquier cosa en Python.
TL; DR: cuando se utilizan modismos funcionales, es mejor escribir código funcional
Como muchas personas han señalado, en Python la asignación de lambdas no está permitida. En general, al usar modismos funcionales, es mejor que pienses de una manera funcional, lo que significa que, donde sea posible, no hay efectos secundarios ni asignaciones.
Aquí hay una solución funcional que usa una lambda. Le asigné el lambda a fn
para mayor claridad (y porque se hizo un poco largo).
from operator import add
from itertools import ifilter, ifilterfalse
fn = lambda l, pred: add(list(ifilter(pred, iter(l))), [ifilterfalse(pred, iter(l)).next()])
objs = [Object(name=""), Object(name="fake_name"), Object(name="")]
fn(objs, lambda o: o.name != '''')
También puede hacer que esto funcione con iteradores en lugar de listas cambiando las cosas un poco. También tiene algunas importaciones diferentes.
from itertools import chain, islice, ifilter, ifilterfalse
fn = lambda l, pred: chain(ifilter(pred, iter(l)), islice(ifilterfalse(pred, iter(l)), 1))
Siempre puede volver a marcar el código para reducir la duración de las declaraciones.