Pregunta Asignación dentro de la expresión lambda en Python


Tengo una lista de objetos y quiero eliminar todos los objetos que están vacíos excepto uno, usando filter y un lambda expresión.

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 manera de agregar una tarea a un lambda ¿expresión? 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
)

73
2018-06-08 16:23


origen


Respuestas:


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 variable flag está en un ámbito externo, no el lambdaalcance de Esto no tiene que ver con lambda, es el comportamiento general en Python 2. Python 3 te permite solucionar esto con el nonlocal palabra clave dentro de defs, pero nonlocal no se puede usar dentro lambdasy Python 3 elimina este efecto secundario 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 puede usar esto para hacer todo dentro de un 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 al flag variable en el ámbito externo, puede usar funciones para modificar el valor previamente asignado.

Por ejemplo, flag podría ser un objeto cuya .value establecemos usando 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 encajar en 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 estar haciendo 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)

166
2018-01-31 01:58



Realmente no se puede mantener el estado en una filter/lambda expresión (a menos que abuse del espacio de nombres global). Sin embargo, puede lograr algo similar utilizando el resultado acumulado que se transmite en una reduce() expresión:

>>> 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 cadenas vacías.

No hace falta decir que puedes hacer esto pero no deberías hacerlo. :)

Por último, tú poder hacer algo en Python puro lambda: http://vanderwijk.info/blog/pure-lambda-calculus-python/


29
2018-01-31 11:09



No es necesario usar una lambda, cuando puedes eliminar todas los nulos, y poner uno de nuevo si el tamaño de 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=""))

16
2018-06-08 16:29



Asignación normal (=) no es posible dentro de una lambda expresión, aunque es posible realizar varios trucos con setattr y amigos.

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 que va a filter, y revertir la lista que sale de 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 implementarse correctamente __eq__ y __hash__ como se explica aquí.


7
2018-02-04 17:32



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 cual 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 C lado y por lo tanto puede darnos 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 uno forma correcta de hacer cualquier cosa en Python.


6
2018-01-31 06:03



Si en lugar de flag = True podemos hacer una importación en su lugar, entonces creo que esto 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)

5
2018-02-06 00:24



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.


5
2017-08-31 11:03



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.


4
2018-02-05 09:46



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 sobrecarga __call__. 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 a filter(). Si realmente lo necesita, puede recuperar el valor de flag con lo siguiente:

f.__defaults__[0]["flag"]

Alternativamente, puede modificar el espacio de nombres global modificando el resultado de globals(). Lamentablemente, no puede modificar el espacio de nombres local de la misma manera que modificando el resultado de locals() no afecta el espacio de nombres local.


3
2018-01-31 19:37