목록 이해 vs. 람다 + 필터


857

기본 필터링이 필요한 것을 발견했습니다. 목록이 있고 항목의 속성으로 필터링해야합니다.

내 코드는 다음과 같습니다.

my_list = [x for x in my_list if x.attribute == value]

그러나 나는 이것을 이렇게 쓰는 것이 낫지 않을 것이라고 생각했다.

my_list = filter(lambda x: x.attribute == value, my_list)

더 읽기 쉽고 성능을 위해 필요한 경우 람다를 꺼내어 무언가를 얻을 수 있습니다.

질문은 : 두 번째 방법을 사용할 때주의 사항이 있습니까? 성능 차이가 있습니까? Pythonic Way ™가 완전히 누락되어 다른 방법으로 (예 : 람다 대신 itemgetter 사용)해야합니까?


19
더 좋은 예는 술어로 사용할 멋진 이름의 함수가 이미있는 경우입니다. 이 경우 더 많은 사람들 filter이 더 읽기 쉽다 는 데 동의 할 것 입니다. listcomp에서 그대로 사용할 수 있지만 전달할 람다 (또는 이와 유사하게 구성 partial되거나 operator함수 등으로)로 묶어야하는 간단한 표현이있는 filter경우 listcomps가 이기게됩니다.
abarnert

3
Python3에서 적어도 리턴은 filter목록이 아닌 필터 생성기 객체 라고 말해야 합니다.
Matteo Ferla

답변:


588

사람들마다 아름다움이 얼마나 달라지는지는 이상합니다. 목록 이해력이 filter+ 보다 훨씬 명확 lambda하지만 더 쉬운 것을 사용하십시오.

사용 속도가 느려질 수있는 두 가지가 있습니다 filter.

첫 번째는 함수 호출 오버 헤드입니다. 파이썬 함수 ( def또는에 의해 생성됨)를 사용하자마자 lambda필터가 목록 이해보다 느려질 수 있습니다. 거의 확실하게 충분하지 않으며 코드 시간을 정하고 병목 현상이 생길 때까지 성능에 대해 많이 생각해서는 안되지만 차이점이 있습니다.

적용될 수있는 다른 오버 헤드는 람다가 범위 변수 ( value) 에 액세스해야한다는 것 입니다. 그것은 로컬 변수에 액세스하는 것보다 느리고 Python 2.x에서는 목록 이해가 로컬 변수에만 액세스합니다. Python 3.x를 사용하는 경우 목록 이해는 별도의 함수에서 실행되므로 value클로저를 통해 액세스 하므로이 차이는 적용되지 않습니다.

고려해야 할 다른 옵션은 목록 이해 대신 생성기를 사용하는 것입니다.

def filterbyvalue(seq, value):
   for el in seq:
       if el.attribute==value: yield el

그런 다음 메인 코드 (가독성이 중요한 곳)에서 목록 이해와 필터를 희망있는 의미있는 함수 이름으로 대체했습니다.


68
발전기의 경우 +1 집에서 멋진 발전기가 얼마나 멋진 지 보여주는 프레젠테이션 링크가 있습니다. 당신은 또한 단지 변경하여 발전기 식 지능형리스트를 대체 할 수 있습니다 [](). 또한 목록 광고가 더 아름답다는 데 동의합니다.
Wayne Werner

1
실제로, 아니오-필터가 더 빠릅니다. stackoverflow.com/questions/5998245/…
skqr

2
@skqr은 벤치 마크에 timeit을 사용하는 것이 더 좋지만 filterPython 콜백 함수를 사용하여 더 빠를 수있는 예제를 제공하십시오 .
던컨

8
@ tnq177 David Beasley의 발전기에 대한 프레젠테이션 -dabeaz.com/generators
Wayne Werner

2
@ VictorSchröder 네, 아마도 불분명했을 것입니다. 내가 말하려고했던 것은 메인 코드에서 더 큰 그림을 볼 수 있어야한다는 것입니다. 작은 도우미 기능에서는 해당 기능 하나만 신경 쓰면됩니다. 외부에서 진행중인 다른 작업은 무시할 수 있습니다.
던컨

237

이것은 파이썬에서 다소 종교적인 문제입니다. 비록 귀도는 제거 간주 map, filter그리고 reduce파이썬 3에서 , 결국 것만 반발의 충분했습니다 reduce에 내장 된 기능에서 이동 된 functools.reduce .

개인적으로 나는 목록 이해가 더 읽기 쉽다는 것을 안다. [i for i in list if i.attribute == value]모든 동작이 필터 함수가 아닌 표면에 있기 때문에 식에서 발생하는 일이 더 분명 합니다.

두 접근 방식의 성능 차이가 미미하기 때문에 두 가지 접근 방식의 성능 차이에 대해 너무 걱정하지 않아도됩니다. 응용 프로그램의 병목 현상이 아닌 것으로 판명되면 실제로 이것을 최적화합니다.

또한 BDFLfilter언어에서 벗어나기를 원했기 때문에 목록 이해를 자동으로 파이썬으로 만듭니다. ;-)


1
귀도의 의견에 대한 링크에 감사드립니다. 저에게 아무것도 없다면 더 이상 사용하지 않으려 고 노력할 것이므로 습관을 얻지 못하고 종교를지지하지 않을 것입니다 :)
dashesy

1
그러나 간단한 도구로는 축소가 가장 복잡합니다! 지도와 필터는 이해하기 쉽지 않습니다!
njzk2

8
파이썬 3에서 감소가 강등되었다는 것을 몰랐습니다. 통찰력에 감사드립니다! reduce ()는 PySpark와 같은 분산 컴퓨팅에 여전히 유용합니다. 그건 실수라고 생각합니다.
Tagar

1
@Tagar는 여전히 functools에서 가져와야하는 축소를 사용할 수 있습니다
icc97

69

속도 차이는 아주 작기 때문에 필터 사용 여부 또는 목록 이해 여부는 취향에 달려 있습니다. 일반적으로 이해력을 사용하는 경향이 있지만 (여기서는 대부분의 다른 답변과 동의하는 것 같습니다) 선호하는 경우가 filter있습니다.

매우 빈번한 유스 케이스는 술어 P (x)에 따라 반복 가능한 X 값을 가져 오는 것입니다.

[x for x in X if P(x)]

그러나 때로는 일부 기능을 먼저 값에 적용하려고합니다.

[f(x) for x in X if P(f(x))]


구체적인 예로서,

primes_cubed = [x*x*x for x in range(1000) if prime(x)]

나는 이것을 사용하는 것보다 약간 더 좋아 보인다고 생각합니다 filter. 그러나 지금 고려

prime_cubes = [x*x*x for x in range(1000) if prime(x*x*x)]

이 경우 우리 filter는 사후 계산 값에 반대하고 싶습니다 . 큐브를 두 번 계산하는 문제 (더 비싼 계산을 상상) 외에도 식을 두 번 작성하여 DRY 미학을 위반하는 문제가 있습니다. 이 경우 나는 사용하기 쉽다

prime_cubes = filter(prime, [x*x*x for x in range(1000)])

7
다른 목록 이해를 통한 소수 사용을 고려하지 않겠습니까? 같은[prime(i) for i in [x**3 for x in range(1000)]]
viki.omega9

20
x*x*x그것은이 같은 소수가 될 수 없습니다 x^2x요소로, 예 정말 수학적인 방법으로 이해가되지 않습니다,하지만 어쩌면 그것은 여전히 helpul입니다. (아마도 더 나은 것을 찾을 수 있을까?)
Zelphir Kaltstahl

3
메모리를 사용하지 않으려면 마지막 예제 대신 생성기 표현식을 사용할 수 있습니다.prime_cubes = filter(prime, (x*x*x for x in range(1000)))
Mateen Ulhaq

4
@MateenUlhaq prime_cubes = [1]메모리와 CPU 사이클을 모두 절약 하도록 최적화 할 수 있습니다 ;-)
Dennis Krupenik

7
@DennisKrupenik 또는 오히려[]
Mateen Ulhaq

29

filter"빠른 방법"일 수도 있지만 "Pythonic 방법"은 성능이 절대적으로 중요하지 않은 경우 (이 경우 Python을 사용하지 않을 것입니다!) 그런 것들에 신경 쓰지 않을 것입니다.


9
흔히 볼 수있는 주장에 대한 최근의 언급 : 때로는 10 대신 5 시간 안에 분석을 실행하는 데 차이가 있으며, 1 시간 동안 파이썬 코드를 최적화하여 달성 할 수 있다면, 특히 가치가있는 경우 파이썬에 익숙하고 더 빠른 언어에는 익숙하지 않습니다).
bli

그러나 소스 코드를 읽고 이해하는 속도를 늦추는 것이 훨씬 중요합니다!
thoni56

20

필자는 파이썬 3에서 filter ()가 실제로 반복자 객체라고 추가한다고 생각했기 때문에 필터링 된 목록을 작성하려면 list ()에 필터 메서드 호출을 전달해야합니다. 파이썬 2에서 :

lst_a = range(25) #arbitrary list
lst_b = [num for num in lst_a if num % 2 == 0]
lst_c = filter(lambda num: num % 2 == 0, lst_a)

리스트 b와 c는 같은 값을 가지며, filter ()가 [z의 경우 x의 y에 대해 x]와 같은 시간과 거의 같은 시간에 완료되었습니다. 그러나 3에서 동일한 코드는 필터링 된 목록이 아닌 필터 객체를 포함하는 목록 c를 남깁니다. 3에서 동일한 값을 생성하려면

lst_a = range(25) #arbitrary list
lst_b = [num for num in lst_a if num % 2 == 0]
lst_c = list(filter(lambda num: num %2 == 0, lst_a))

문제는 list ()가 인수로 iterable을 가져 와서 해당 인수에서 새 목록을 작성한다는 것입니다. 결과적으로 파이썬 3에서 이런 식으로 필터를 사용하면 원래 목록뿐만 아니라 filter ()의 출력을 반복해야하기 때문에 [x for x in y if z] 방법보다 최대 두 배가 걸립니다.


13

중요한 차이점은 목록 이해가 list필터를 반환 하는 동안 을 반환한다는 것입니다 . 필터는을 반환 filter할 수 없습니다 list(예 :의 호출 len로 작동하지 않는 호출 filter).

내 자신의 학습으로 비슷한 문제가 발생했습니다.

즉, .NET에서 할 때와 같은 결과 list를 얻을 수있는 방법이 있다면 알고 싶습니다.filterlst.Where(i => i.something()).ToList()

편집 : 이것은 2가 아닌 Python 3의 경우입니다 (주석의 토론 참조).


4
filter는리스트를 반환하고 len을 사용할 수 있습니다. 적어도 내 파이썬 2.7.6에서는.
thiruvenkadam

7
파이썬 3에서는 그렇지 않습니다. a = [1, 2, 3, 4, 5, 6, 7, 8] f = filter(lambda x: x % 2 == 0, a) lc = [i for i in a if i % 2 == 0] >>> type(f) <class 'filter'> >>> type(lc) <class 'list'>
Adeynack

3
"결과 목록을 얻는 방법이 있다면 ... 나는 그것을 알고 궁금하다". list()결과를 전화 하십시오 : list(filter(my_func, my_iterable)). 물론 당신은 대체 할 수 list와 함께 set, 또는 tuple, 또는 반복 가능한 소요 아무것도. 그러나 함수형 프로그래머가 아닌 다른 사람에게는을 filter명시 적으로 변환하는 것보다 목록 이해력을 사용하는 것이 더 강력 합니다 list.
Steve Jessop

10

두 번째 방법이 더 읽기 쉽습니다. 의도를 정확히 알려줍니다. 목록을 필터링하십시오.
추신 : 변수 이름으로 'list'를 사용하지 마십시오


7

일반적으로 filter내장 함수를 사용하는 경우 으로 약간 빠릅니다.

귀하의 경우 목록 이해가 약간 더 빠를 것으로 기대합니다


python -m timeit 'filter (lambda x : x in [1,2,3,4,5], range (10000000))'10 루프, 루프 당 3 : 3 초 최고 python -m timeit '[x for x 범위 (10000000)에서 x가 [1,2,3,4,5]] '10 개 루프의 경우 루프 당 3 개 중 최고 : 3 : 860msec 실제로 그렇지 않습니까?!
giaosudau

@ sepdau, 람다 함수는 내장되어 있지 않습니다. 목록 이해력은 지난 4 년 동안 개선되었습니다. 이제는 내장 기능을 사용하더라도 차이는 무시할
만합니다.

7

그저 필터 입니다. 목록의 요소를 필터링합니다. 정의에 대한 언급이 동일하다는 것을 알 수 있습니다 (앞서 언급 한 공식 문서 링크에서). 반면, 목록 이해는 이전 목록의 작업을 수행 한 후 새 목록을 생성하는 입니다 (필터 및 목록 이해는 모두 새 목록을 생성하고 이전 목록 대신 작업을 수행하지 않습니다. 여기에있는 새로운 목록은 다음과 같은 목록입니다). 예를 들어, 정수를 문자열 등으로 변환하는 것과 같이 완전히 새로운 데이터 유형)

귀하의 예에서는 정의에 따라 목록 이해보다 필터를 사용하는 것이 좋습니다. 그러나 원하는 경우 목록 요소에서 other_attribute를 말하면 예에서 새 목록으로 검색되는 경우 목록 이해를 사용할 수 있습니다.

return [item.other_attribute for item in my_list if item.attribute==value]

이것이 실제로 필터 및 목록 이해에 대해 기억하는 방법입니다. 목록에서 몇 가지를 제거하고 다른 요소는 그대로 유지하고 필터를 사용하십시오. 요소에서 직접 논리를 사용하고 목적에 맞는 워터 다운 목록을 작성하십시오. 목록 이해를 사용하십시오.


2
앞으로 투표를 중단해야 할 이유를 알고 기꺼이 다시는 앞으로도 계속 반복하지 않을 것입니다.
thiruvenkadam

필터 및 목록 이해의 정의는 그 의미가 논의되지 않았기 때문에 필요하지 않았습니다. 리스트 이해는“새로운”리스트에만 사용되어야한다는 주장이 있지만 제시된 것은 아닙니다.
Agos

필자는 필터를 사용하여 사례에 해당하는 동일한 요소로 목록을 제공하지만 목록 이해를 통해 int를 str로 변환하는 것과 같이 요소 자체를 수정할 수 있다고 말합니다. 그러나 포인트 찍은 :-)
thiruvenkadam

4

다음 은 목록 이해 무언가를 필터링해야 할 때 사용하는 짧은 부분 입니다. 필터, 람다 및 목록의 조합 (고양이의 충성도 및 개의 청결성이라고도 함).

이 경우 파일을 읽고 빈 줄을 제거하고 줄을 주석 처리 한 다음 줄에 주석 다음에 나오는 내용을 읽습니다.

# Throw out blank lines and comments
with open('file.txt', 'r') as lines:        
    # From the inside out:
    #    [s.partition('#')[0].strip() for s in lines]... Throws out comments
    #   filter(lambda x: x!= '', [s.part... Filters out blank lines
    #  y for y in filter... Converts filter object to list
    file_contents = [y for y in filter(lambda x: x != '', [s.partition('#')[0].strip() for s in lines])]

이것은 실제로 아주 작은 코드로 많은 것을 달성합니다. 한 줄에 너무 많은 논리가있어 쉽게 이해하고 가독성이 중요하다고 생각합니다.
Zelphir Kaltstahl

당신은 이것을 다음과 같이 쓸 수 있습니다file_contents = list(filter(None, (s.partition('#')[0].strip() for s in lines)))
Steve Jessop

4

수락 된 답변 외에도 목록 이해 대신 필터를 사용해야하는 경우가 있습니다. 목록을 해싱 할 수없는 경우 목록을 이해하여 직접 처리 할 수 ​​없습니다. 실제 예는 pyodbc데이터베이스에서 결과를 읽는 데 사용 하는 경우 입니다. 의 fetchAll()결과 cursor는 해시 할 수없는 목록입니다. 이 상황에서 반환 된 결과를 직접 조작하려면 필터를 사용해야합니다.

cursor.execute("SELECT * FROM TABLE1;")
data_from_db = cursor.fetchall()
processed_data = filter(lambda s: 'abc' in s.field1 or s.StartTime >= start_date_time, data_from_db) 

여기에서 목록 이해를 사용하면 오류가 발생합니다.

TypeError : 해싱 할 수없는 유형 : 'list'


1
모든 목록은 >>> hash(list()) # TypeError: unhashable type: 'list'두 번째로 해싱 할 수 없습니다. 이것은 잘 작동합니다.processed_data = [s for s in data_from_db if 'abc' in s.field1 or s.StartTime >= start_date_time]
Thomas Grainger

"목록을 해싱 할 수 없으면 목록을 이해하여 직접 처리 할 수 ​​없습니다." 이것은 사실 이 아니며 어쨌든 모든 목록을 해시 할 수 없습니다.
juanpa.arrivillaga

3

그것은 나에게 익숙하려면 시간이 좀 걸렸다 higher order functions filtermap. 그래서 나는 그들에게 filter익숙해졌고 나는 그것이 진실한 것을 유지함으로써 걸러내는 것이 명백하다는 것을 실제로 좋아 했고 나는 어떤 functional programming용어를 알고 있다는 것을 시원하게 느꼈습니다 .

그런 다음이 구절 (Fluent Python Book)을 읽습니다.

맵 및 필터 함수는 여전히 Python 3에 내장되어 있지만 목록 이해 및 생성기 표현이 도입되었으므로 중요하지 않습니다. listcomp 또는 genexp는 map과 filter를 결합하여 수행하지만 더 읽기 쉽습니다.

그리고 이제 나는 목록 이해와 같은 이미 널리 퍼진 관용구로 그것을 달성 할 수 있다면 filter/ 개념을 귀찮게 생각 map합니다. 더욱이 mapsfilters유형의 함수이다. 이 경우에는 사용을 선호합니다Anonymous functions 람다를 좋습니다.

마지막으로 테스트를하기 위해 두 가지 방법 ( maplistComp)의 시간을 정했으며 그것에 대한 논쟁을 정당화하는 관련 속도 차이를 보지 못했습니다.

from timeit import Timer

timeMap = Timer(lambda: list(map(lambda x: x*x, range(10**7))))
print(timeMap.timeit(number=100))

timeListComp = Timer(lambda:[(lambda x: x*x) for x in range(10**7)])
print(timeListComp.timeit(number=100))

#Map:                 166.95695265199174
#List Comprehension   177.97208347299602

0

흥미롭게도 파이썬 3에서는 목록 이해보다 필터 성능이 더 빠릅니다.

나는 항상 목록 이해력이 더 성능이 좋을 것이라고 생각했습니다. [name이 None이 아닌 경우 brand_names_db에서 name의 이름] 생성 된 바이트 코드가 조금 더 좋습니다.

>>> def f1(seq):
...     return list(filter(None, seq))
>>> def f2(seq):
...     return [i for i in seq if i is not None]
>>> disassemble(f1.__code__)
2         0 LOAD_GLOBAL              0 (list)
          2 LOAD_GLOBAL              1 (filter)
          4 LOAD_CONST               0 (None)
          6 LOAD_FAST                0 (seq)
          8 CALL_FUNCTION            2
         10 CALL_FUNCTION            1
         12 RETURN_VALUE
>>> disassemble(f2.__code__)
2           0 LOAD_CONST               1 (<code object <listcomp> at 0x10cfcaa50, file "<stdin>", line 2>)
          2 LOAD_CONST               2 ('f2.<locals>.<listcomp>')
          4 MAKE_FUNCTION            0
          6 LOAD_FAST                0 (seq)
          8 GET_ITER
         10 CALL_FUNCTION            1
         12 RETURN_VALUE

그러나 실제로는 느립니다.

   >>> timeit(stmt="f1(range(1000))", setup="from __main__ import f1,f2")
   21.177661532000116
   >>> timeit(stmt="f2(range(1000))", setup="from __main__ import f1,f2")
   42.233950221000214

8
잘못된 비교 입니다. 먼저 람다 함수를 필터 버전으로 전달하지 않으므로 ID 함수가 기본값으로 설정됩니다. 정의 할 때 if not None목록의 이해 당신이 하는 람다 함수를 정의합니다 (주의 MAKE_FUNCTION문). 둘째, 목록 이해 버전은 None값만 제거 하지만 필터 버전은 모든 "거짓"값을 제거 하므로 결과가 다릅니다 . 마이크로 벤치마킹의 전체 목적은 쓸모가 없습니다. 그것들은 백만 번의 반복이며, 1k 항목을 곱한 것입니다! 그 차이는 무시할 만하다 .
Victor Schröder

-7

내 테이크

def filter_list(list, key, value, limit=None):
    return [i for i in list if i[key] == value][:limit]

3
i이라고 말한 적이 dict없으며, 필요가 없습니다 limit. 그 외에는 이것이 OP가 제안한 것과 어떻게 다릅니 까? 그리고 질문에 어떻게 대답합니까?
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.