Python에서 람다 식 내부 할당


105

개체 목록이 있고 하나를 제외하고 비어있는 모든 개체, using filterlambda식 을 제거하고 싶습니다 .

예를 들어 입력이 다음과 같은 경우 :

[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
아니요.하지만 이건 필요 없어요. 실제로 작동하더라도 이것을 달성하는 꽤 모호한 방법이라고 생각합니다.

8
일반 오래된 함수를 필터에 전달하지 않는 이유는 무엇입니까?
DFB

5
저는 람다를 사용하여 정말 컴팩트 한 솔루션이되기를 원했습니다. OCaml에서 return 표현식 앞에 print 문을 연결할 수 있었음을 기억합니다. 이것이 Python에서 복제 될 수 있다고 생각했습니다
Cat

연결된 pipeilne을 개발하는 흐름에있는 동안 "오, 흐름을보다 명확하게 만들기 위해 임시 변수를 만들고 싶습니다"또는 "이 중간 단계를 기록하고 싶습니다"라는 것을 깨닫는 것은 매우 고통 스럽습니다. 그리고 나서 점프해야합니다. 이를 수행 할 함수를 생성하기 위해 다른 곳에서 : 해당 함수의 이름지정 하고 추적 할 수 있습니다. 비록 한 곳에서만 사용 되더라도 말입니다.
javadba

답변:


215

:=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. 이것은와 관련이 없습니다. 이것은 lambdaPython 2의 일반적인 동작입니다. Python 3를 사용하면 s nonlocal내부의 키워드 로이 문제를 해결할 수 def있지만 s 내부에서는 nonlocal사용할 수 없습니다 lambda.

해결 방법이 있지만 (아래 참조) 주제를 다루는 동안 ...


어떤 경우에는 이것을 사용하여 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.0cm이고 높이가 20.0cm 인 원통의 부피는 6283.2cm³입니다.
반지름이 20.0cm이고 높이가 40.0cm 인 실린더의 부피는 50265.5cm³입니다.
반지름이 30.0cm이고 높이가 60.0cm 인 실린더의 부피는 169646.0cm³입니다.

하지 마십시오.


... 원래 예제로 돌아가서 : flag 외부 범위 변수에 함수를 사용하여 이전에 할당 된 값을 수정할 수 있습니다.

예를 들어 다음을 사용하여 설정 flag한 객체 일 수 있습니다 ..valuesetattr

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)

이 답변의 마지막 예제는 예제와 동일한 출력을 생성하지 않지만 예제 출력이 잘못된 것처럼 보입니다.
제레미

요컨대, 이것은 다음과 같이 요약됩니다 : 어쨌든 함수 코드에 부작용을 해킹하기 위해 사용 .setattr()및 유사 ( 사전 도 마찬가지로해야 함), @JeremyBanks의 멋진 코드가 표시되었습니다 :)
jno

에 메모에 대한 Thx assignment operator!
javadba

37

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/


17

null을 모두 제거 하고 입력 크기가 변경되면 다시 넣을 수있는 경우 람다를 사용할 필요가 없습니다 .

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=""))

1
코드에 작은 실수가 있다고 생각합니다. 두 번째 줄은 output = [x for x in input if x.name].
halex

요소의 순서가 중요 할 수 있습니다.
MAnyKey

15

및 친구들과 다양한 트릭을 수행 할 수는 있지만 표현식 =내에서는 일반 할당 ( )이 불가능합니다 . lambdasetattr

그러나 문제를 해결하는 것은 실제로 매우 간단합니다.

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__같은 설명 여기 .


7

업데이트 :

[o for d in [{}] for o in lst if o.name != "" or d.setdefault("", o) == o]

또는 사용 filterlambda:

flag = {}
filter(lambda o: bool(o.name) or flag.setdefault("", o) == o, lst)

이전 답변

좋습니다. 필터와 람다를 계속 사용하고 있습니까?

이것은 사전 이해력과 함께 제공되는 것이 더 나을 것 같습니다.

{o.name : o for o in input}.values()

저는 파이썬이 람다에서 할당을 허용하지 않는 이유가 이해에서 할당을 허용하지 않는 이유와 유사하다고 생각합니다. 그리고 그것은 이러한 것들이 C측면에서 평가 되고 따라서 우리에게 줄 수 있다는 사실 과 관련이 있습니다. 속도 증가. 적어도 귀도의 에세이 중 하나를 읽은 후의 인상입니다. 입니다.

내 생각 엔 이것은 또한 가지고있는 철학에 갈 것입니다 하나의 파이썬에서 어떤 한 가지 일을 올바른 방법.


그래서 이것은 완전히 옳지 않습니다. 순서를 유지하지 않으며 비어 있지 않은 문자열 개체의 중복도 유지하지 않습니다.
JPvdMerwe

7

요약 : 기능적 관용구를 사용할 때는 기능적 코드를 작성하는 것이 좋습니다.

많은 사람들이 지적했듯이 Python에서는 람다 할당이 허용되지 않습니다. 일반적으로 기능적 관용구를 사용할 때 기능적 방식으로 생각하는 것이 좋습니다. 즉, 가능한 한 부작용이나 할당이 없습니다.

다음은 람다를 사용하는 기능적 솔루션입니다. 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))

언제든지 코드를 재구성하여 문의 길이를 줄일 수 있습니다.


6

대신 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)

6

반복하는 동안 상태를 추적하는 비단뱀적인 방법은 생성기를 사용하는 것입니다. itertools 방식은 IMHO를 이해하기가 매우 어렵고이를 위해 람다를 해킹하는 것은 어리석은 일입니다. 나는 시도 할 것이다 :

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))

전반적으로 가독성이 항상 간결함을 능가합니다.


4

아니요, 자체 정의로 인해 람다 안에 할당을 넣을 수 없습니다. 함수형 프로그래밍을 사용하여 작업하는 경우 값이 변경 불가능하다고 가정해야합니다.

한 가지 해결책은 다음 코드입니다.

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 )

4

호출 사이의 상태를 기억하기 위해 람다가 필요한 경우 로컬 네임 스페이스에 선언 된 함수 또는 오버로드 된 __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()해도 로컬 네임 스페이스에 영향 을 주지 않는 것과 같은 방식으로 로컬 네임 스페이스를 수정할 수 없습니다 .


또는 원래 Lisp : (let ((var 42)) (lambda () (setf var 43))).
Kaz

4

bind 함수를 사용하여 의사 다중 문 람다를 사용할 수 있습니다. 그런 다음 플래그에 래퍼 클래스를 사용하여 할당을 활성화 할 수 있습니다.

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)

0

일종의 지저분한 해결 방법이지만 람다에서의 할당은 어쨌든 불법이므로 실제로 중요하지 않습니다. exec()다음 예제와 같이 내장 함수를 사용하여 람다 내부에서 할당을 실행할 수 있습니다 .

>>> val
Traceback (most recent call last):
  File "<pyshell#31>", line 1, in <module>
    val
NameError: name 'val' is not defined
>>> d = lambda: exec('val=True', globals())
>>> d()
>>> val
True

-2

먼저, 당신은 당신의 직업을 위해 지역 할당을 사용할 필요가 없습니다, 위의 대답을 확인하십시오

둘째, locals () 및 globals ()를 사용하여 변수 테이블을 얻은 다음 값을 변경하는 것이 간단합니다.

이 샘플 코드를 확인하십시오.

print [locals().__setitem__('x', 'Hillo :]'), x][-1]

환경에 전역 변수 추가를 변경해야하는 경우 locals () globals ()

파이썬의 목록 comp는 멋지지만 대부분의 triditional 프로젝트는 이것을 받아들이지 않습니다 (예 : flask : [)

도움이 되길 바래


2
을 사용할 수 없으며 locals()문서에 명시 적으로 변경하면 실제로 로컬 범위가 변경되지 않습니다 (또는 적어도 항상 그런 것은 아님). globals()반면에 예상대로 작동합니다.
JPvdMerwe

@JPvdMerwe 그냥 시도하고 맹목적으로 문서를 따르지 마십시오. 람다의 할당은 차단 규칙을 이미
jyf1987은

3
안타깝게도 전역 네임 스페이스에서만 작동하며,이 경우 실제로 globals(). pastebin.com/5Bjz1mR4(2.6 및 3.2에서 테스트 됨) 가이를 증명합니다.
JPvdMerwe 2013
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.