Назначение внутри выражения лямбда в Python
У меня есть список объектов, и я хочу удалить все объекты, которые пусты, за исключением одного, используя выражения filter
и lambda
.
Например, если вход:
[Object(name=""), Object(name="fake_name"), Object(name="")]
... тогда выход должен быть:
[Object(name=""), Object(name="fake_name")]
Есть ли способ добавить назначение в выражение lambda
? Например:
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
)
Ответы
Ответ 1
Оператор выражения присваивания :=
добавлен в Python 3.8 поддерживает присваивание внутри лямбда-выражений. Этот оператор может появляться только в скобках (...)
, в скобках [...]
или в скобках {...}
по синтаксическим причинам. Например, мы сможем написать следующее:
import sys
say_hello = lambda: (
message := "Hello world",
sys.stdout.write(message + "\n")
)[-1]
say_hello()
В Python 2 можно было выполнять локальные назначения как побочный эффект от понимания списка.
import sys
say_hello = lambda: (
[None for message in ["Hello world"]],
sys.stdout.write(message + "\n")
)[-1]
say_hello()
Тем не менее, невозможно использовать ни один из них в вашем примере, потому что ваш flag
переменной находится во внешней области видимости, а не в области lambda
выражения. Это не имеет отношения к lambda
, это общее поведение в Python 2. Python 3 позволяет обойти это с помощью nonlocal
ключевого слова внутри def
s, но nonlocal
нельзя использовать внутри lambda
s.
Есть обходной путь (см. Ниже), но пока мы находимся на теме...
В некоторых случаях вы можете использовать это, чтобы сделать все внутри 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()])()
Цилиндр с радиусом 10,0 см и высотой 20,0 см имеет объем 6283,2 см³.
Цилиндр с радиусом 20,0 см и высотой 40,0 см имеет объем 50265,5 см³.
Цилиндр с радиусом 30,0 см и высотой 60,0 см имеет объем 169646,0 см³.
Пожалуйста, не надо.
... вернемся к исходному примеру: хотя вы не можете выполнять присваивания переменной flag
во внешней области видимости, вы можете использовать функции для изменения ранее назначенного значения.
Например, flag
может быть объектом которого .value
мы установили с помощью 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')]
Если мы хотим соответствовать вышеупомянутой теме, мы могли бы использовать понимание списка вместо setattr
:
[None for flag.value in [bool(o.name)]]
Но на самом деле, в серьезном коде вы всегда должны использовать обычное определение функции вместо lambda
если вы собираетесь выполнять внешнее присваивание.
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)
Ответ 2
Вы не можете поддерживать состояние в выражении filter
/lambda
(если не злоупотреблять глобальным пространством имен). Однако вы можете добиться чего-то подобного, используя накопленный результат, передаваемый в выражении 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']
>>>
Вы можете, конечно, немного настроить состояние. В этом случае он отфильтровывает дубликаты, но вы также можете использовать a.count("")
, например, только для ограничения пустых строк.
Излишне говорить, что вы можете это сделать, но вы действительно этого не должны.:)
Наконец, вы можете сделать что-нибудь в чистом Python lambda
: http://vanderwijk.info/blog/pure-lambda-calculus-python/
Ответ 3
Нет необходимости использовать лямбда, когда вы можете удалить все нулевые, и поместите обратно, если размер ввода изменится:
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=""))
Ответ 4
Обычное назначение (=
) невозможно в выражении lambda
, хотя можно выполнить различные трюки с помощью setattr
и друзей.
Решение вашей проблемы, однако, на самом деле довольно просто:
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
)
который даст вам
[Object(Object(name=''), name='fake_name')]
Как вы можете видеть, он сохраняет первый пустой экземпляр вместо последнего. Если вам нужно последнее вместо этого, отмените список, идущий на filter
, и отмените список, выходящий из filter
:
output = filter(
lambda o, _seen=set():
not (not o and o in _seen or _seen.add(o)),
input[::-1]
)[::-1]
который даст вам
[Object(name='fake_name'), Object(name='')]
Одна вещь, о которой нужно знать: для того, чтобы это работало с произвольными объектами, эти объекты должны правильно реализовывать __eq__
и __hash__
, как описано здесь.
Ответ 5
UPDATE
[o for d in [{}] for o in lst if o.name != "" or d.setdefault("", o) == o]
или используя filter
и lambda
:
flag = {}
filter(lambda o: bool(o.name) or flag.setdefault("", o) == o, lst)
Предыдущий ответ
ОК, вы застряли на использовании фильтра и лямбда?
Кажется, что это лучше было бы использовать с пониманием словаря,
{o.name : o for o in input}.values()
Я думаю, что причина, по которой Python не позволяет назначать в лямбда, похожа на то, почему она не позволяет назначать в понимании и что имеет какое-то отношение к тому факту, что эти вещи оцениваются на стороне C
и, таким образом, может дать нам увеличение скорости. По крайней мере, мое впечатление после прочтения одного из эссе Гвидо.
Мое предположение заключается в том, что это также противоречит философии того, чтобы иметь один правильный способ сделать что-то в Python.
Ответ 6
TL; DR: При использовании функциональных идиом лучше писать функциональный код
Как отмечают многие люди, назначение Python lambdas не допускается. В общем, при использовании функциональных идиом вам лучше думать в функциональной манере, что означает, когда это возможно, никаких побочных эффектов и никаких назначений.
Вот функциональное решение, которое использует лямбда. Я назначил лямбда fn
для ясности (и потому, что она немного длинновато).
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 != '')
Вы также можете сделать эту сделку с итераторами, а не списками, немного изменив ситуацию. У вас также есть несколько разных импортов.
from itertools import chain, islice, ifilter, ifilterfalse
fn = lambda l, pred: chain(ifilter(pred, iter(l)), islice(ifilterfalse(pred, iter(l)), 1))
Вы всегда можете переориентировать код, чтобы уменьшить длину операторов.
Ответ 7
Если вместо flag = True
мы можем сделать импорт вместо этого, то я думаю, что это соответствует критериям:
>>> from itertools import count
>>> a = ['hello', '', 'world', '', '', '', 'bob']
>>> filter(lambda L, j=count(): L or not next(j), a)
['hello', '', 'world', 'bob']
Или, может быть, фильтр лучше писать как:
>>> filter(lambda L, blank_count=count(1): L or next(blank_count) == 1, a)
Или просто для простого логического, без импорта:
filter(lambda L, use_blank=iter([True]): L or next(use_blank, False), a)
Ответ 8
Питонический способ отслеживания состояния во время итерации - это генераторы. Путь itertools довольно трудно понять ИМХО и пытаться взломать лямбда, чтобы сделать это, просто глупо. Я бы попробовал:
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))
В целом, коэффициент удобочитаемости коллирует каждый раз подряд.
Ответ 9
Нет, вы не можете назначить задание внутри лямбды из-за своего собственного определения. Если вы работаете с использованием функционального программирования, вы должны предположить, что ваши значения не изменяемы.
Одним из решений может быть следующий код:
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 )
Ответ 10
Если вам нужна лямбда для запоминания состояния между вызовами, я бы рекомендовал либо функцию, объявленную в локальном пространстве имен, либо класс с перегруженным __call__
. Теперь, когда все мои предостережения против того, что вы пытаетесь сделать, в стороне, мы можем получить реальный ответ на ваш запрос.
Если вам действительно нужно, чтобы ваша лямбда имела некоторую память между вызовами, вы можете определить ее как:
f = lambda o, ns = {"flag":True}: [ns["flag"] or o.name, ns.__setitem__("flag", ns["flag"] and o.name)][0]
Тогда вам просто нужно передать f
в filter()
. Если вам действительно нужно, вы можете вернуть значение flag
со следующим:
f.__defaults__[0]["flag"]
В качестве альтернативы вы можете изменить глобальное пространство имен, изменив результат globals()
. К сожалению, вы не можете изменить локальное пространство имен так же, как изменение результата locals()
не влияет на локальное пространство имен.
Ответ 11
Вы можете использовать функцию привязки для использования псевдо-многозадачной лямбда. Затем вы можете использовать класс-оболочку для флага для включения назначения.
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)
Ответ 12
во-первых, вам не нужно использовать локальную настройку для своей работы, просто проверьте приведенный выше ответ
second, его простые в использовании locals() и globals(), чтобы получить таблицу переменных, а затем изменить значение
проверьте этот пример кода:
print [locals().__setitem__('x', 'Hillo :]'), x][-1]
если вам нужно изменить добавление глобальной переменной в среду, попробуйте заменить locals() на globals()
python list comp классно, но большинство из трех проектов не принимают это (например, колбу: [)
надеюсь, что это поможет