[python] 목록 이해 vs지도

map()오버리스트 이해 를 선호 하거나 그 반대 의 이유가 있습니까? 그들 중 하나가 다른 것보다 일반적으로 더 효율적이거나 일반적으로 더 많은 파이썬으로 간주됩니까?



답변

map경우에 따라 현미경으로 더 빠를 수 있습니다 (목적을 위해 람다를 만들지 않지만 map과 listcomp에서 동일한 기능을 사용하는 경우). 다른 경우에는 목록 이해가 더 빠를 수 있으며 대부분의 pythonista는 더 직접적이고 명확하게 생각합니다.

정확히 동일한 기능을 사용할 때 맵의 작은 속도 이점의 예 :

$ python -mtimeit -s'xs=range(10)' 'map(hex, xs)'
100000 loops, best of 3: 4.86 usec per loop
$ python -mtimeit -s'xs=range(10)' '[hex(x) for x in xs]'
100000 loops, best of 3: 5.58 usec per loop

지도에 람다가 필요할 때 성능 비교가 완전히 역전되는 예 :

$ python -mtimeit -s'xs=range(10)' 'map(lambda x: x+2, xs)'
100000 loops, best of 3: 4.24 usec per loop
$ python -mtimeit -s'xs=range(10)' '[x+2 for x in xs]'
100000 loops, best of 3: 2.32 usec per loop


답변

사례

  • 일반적인 경우 : 거의 항상, 당신은 목록 이해를 사용하고자 할 것입니다 파이썬 것입니다. 코드를 읽는 초보자 프로그래머에게 무엇을하고 있는지 분명하기 때문입니다. (이것은 다른 관용구가 적용될 수있는 다른 언어에는 적용되지 않습니다.) 목록 이해가 파이썬에서 사실상 반복되는 표준이기 때문에 파이썬 프로그래머에게하고있는 일이 더 분명 할 것입니다. 그들은 기대된다 .
  • 덜 일반적인 경우 : 그러나 이미 정의 된 함수 가있는 경우 map‘unpythonic’으로 간주되지만 사용하는 것이 합리적 입니다. 예를 들어, map(sum, myLists)보다 우아하고 간결합니다 [sum(x) for x in myLists]. 반복하기 위해 두 번 입력 해야하는 더미 변수 (예 : sum(x) for x...또는 sum(_) for _...또는 sum(readableName) for readableName...)를 구성하지 않아도되는 우아함을 얻습니다 . 같은 주장이filter 하고 reduce과에서 아무것도 itertools모듈 : 이미 함수가 편리한 경우, 당신이 가서 어떤 기능을 프로그래밍 할 수 있습니다. 이것은 어떤 상황에서는 가독성을 얻고 다른 상황에서는 읽지 못합니다 (예 : 초보 프로그래머, 여러 주장) …하지만 코드의 가독성은 어쨌든 주석에 크게 의존합니다.
  • 거의 절대 : map함수 프로그래밍을 할 때, 매핑 map또는 카레 를하는 동안 함수로서 순수한 추상 함수로 함수 를 사용하고 싶거나 함수로서 map이야기하는 map것으로 부터 이점을 얻을 수 있습니다. 예를 들어 Haskell에서 functor 인터페이스는 fmap일반 데이터 구조에 대한 매핑을 일반화합니다. 파이썬 문법은 생성자 스타일을 사용하여 반복에 대해 이야기하도록 강요하기 때문에 파이썬에서는 매우 드물다. 쉽게 일반화 할 수 없습니다. (이것은 때때로 좋고 나쁘다.) 당신은 아마 map(f, *lists)합리적인 일인 희귀 한 파이썬 예제 를 생각 해낼 수있다. 내가 올릴 수있는 가장 가까운 예 sumEach = partial(map,sum)는 다음과 매우 비슷한 하나의 라이너입니다.
def sumEach(myLists):
    return [sum(_) for _ in myLists]
  • 그냥 for -loop 사용 : 물론 for-loop를 사용할 수도 있습니다. 함수형 프로그래밍 관점에서 볼 때 우아하지는 않지만 때로는 로컬이 아닌 변수는 파이썬과 같은 명령형 프로그래밍 언어에서 코드를 더 명확하게 만듭니다. 사람들은 그런 방식으로 코드를 읽는 데 매우 익숙하기 때문입니다. For 루프는 일반적으로 목록 이해와 같은 목록을 작성하지 않고 복잡한 작업을 수행하고 맵이 (예 : 합산 또는 트리 만들기 등) 최적화되는 경우에만 가장 효율적입니다. 기억력 측면에서 효율적입니다 (필요한 병리학 적 가비지 수집 딸꾹질을 제외하고 최악의 일정한 요인을 예상 할 수있는 시간 측면에서는 반드시 그런 것은 아닙니다).

“파이썬주의”

나는 pythonic이 항상 내 눈에 우아하다는 것을 알지 못하기 때문에 “pythonic”이라는 단어를 싫어합니다. 그럼에도 불구 map하고 filter매우 유용 등과 유사한 기능 (itertools 모듈) 아마 스타일의 측면에서 unpythonic 간주됩니다.

게으름

대부분의 함수형 프로그래밍 구문과 같이 효율성 측면에서 MAP CAN BE LAZY 는 실제로 파이썬에서는 게으르다. 즉, python3 에서이 작업을 수행 할 수 있으며 컴퓨터에 메모리가 부족하지 않고 저장하지 않은 모든 데이터가 손실됩니다.

>>> map(str, range(10**100))
<map object at 0x2201d50>

목록 이해력으로 시도해보십시오.

>>> [str(n) for n in range(10**100)]
# DO NOT TRY THIS AT HOME OR YOU WILL BE SAD #

리스트 이해도 본질적으로 게으르지 만, 파이썬은 그것들을 비 게으른 것으로 구현하기로 선택했습니다 . 그럼에도 불구하고, 파이썬은 다음과 같이 생성기 표현식의 형태로 게으른 목록 이해를 지원합니다.

>>> (str(n) for n in range(10**100))
<generator object <genexpr> at 0xacbdef>

기본적으로 [...]구문을 생성자와 같은 목록 생성자에 전달하는 것으로 생각할 수 있습니다 list(x for x in range(5)).

간단한 예

from operator import neg
print({x:x**2 for x in map(neg,range(5))})

print({x:x**2 for x in [-y for y in range(5)]})

print({x:x**2 for x in (-y for y in range(5))})

목록 이해는 지연되지 않으므로 더 많은 메모리가 필요할 수 있습니다 (생성자 이해를 사용하지 않는 한). 대괄호 [...]는 특히 괄호가 엉망 일 때 특히 명확합니다. 반면에 때로는 타이핑하는 것처럼 장황하게 보일 수 [x for x in...있습니다. 반복자 변수를 짧게 유지하는 한 일반적으로 코드를 들여 쓰지 않으면 목록 이해가 더 명확 해집니다. 그러나 항상 코드를 들여 쓸 수 있습니다.

print(
    {x:x**2 for x in (-y for y in range(5))}
)

또는 헤어지다 :

rangeNeg5 = (-y for y in range(5))
print(
    {x:x**2 for x in rangeNeg5}
)

Python3의 효율성 비교

map 이제 게으르다 :

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=map(f,xs)'
1000000 loops, best of 3: 0.336 usec per loop            ^^^^^^^^^

따라서 모든 데이터를 사용하지 않거나 필요한 데이터 양을 미리 알지 못하면 mappython3 (및 python2 또는 python3의 생성자 표현식)에서 필요한 마지막 순간까지 값을 계산하지 않아도됩니다. 일반적으로 이것은을 사용하는 것보다 오버 헤드보다 중요합니다 map. 단점은 대부분의 기능적 언어와 달리 파이썬에서 매우 제한적이라는 것입니다. 파이썬 생성기 표현식은 순서 만 평가할 수 있기 때문에 왼쪽에서 오른쪽으로 “순서대로”데이터에 액세스하는 경우에만이 이점을 얻을 수 있습니다 x[0], x[1], x[2], ....

그러나 이제 우리는 미리 만들어진 기능이 있다고 가정 해 봅시다 f우리가 원하는을 map, 우리가의 게으름을 무시하고 map즉시로 평가를 강제로 list(...). 우리는 매우 흥미로운 결과를 얻습니다 :

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=list(map(f,xs))'
10000 loops, best of 3: 165/124/135 usec per loop        ^^^^^^^^^^^^^^^
                    for list(<map object>)

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=[f(x) for x in xs]'
10000 loops, best of 3: 181/118/123 usec per loop        ^^^^^^^^^^^^^^^^^^
                    for list(<generator>), probably optimized

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=list(f(x) for x in xs)'
1000 loops, best of 3: 215/150/150 usec per loop         ^^^^^^^^^^^^^^^^^^^^^^
                    for list(<generator>)

결과는 AAA / BBB / CCC 형식이며 여기서 A는 python 3.?.?가 포함 된 2010 년 1 월 Intel 워크 스테이션에서 수행되고 B와 C는 python 3.2.1이 포함 된 2013 년경 AMD 워크 스테이션에서 수행됩니다. 매우 다른 하드웨어로. 결과적으로지도 및 목록 이해력은 성능면에서 비슷하며 다른 임의의 요인에 의해 가장 큰 영향을받습니다. 우리는 지능형리스트 기대하면서 우리가 말할 수있는 유일한 것은, 이상한, 그 것으로 보인다 [...]발전기 표현보다 더 잘 수행하기 위해 (...), map발전기 표현 (다시 모든 값이 / 평가 사용된다고 가정) 것이 더 효율적입니다.

이러한 테스트는 매우 간단한 기능 (식별 기능)을 가정한다는 것을 인식하는 것이 중요합니다. 그러나 기능이 복잡하면 프로그램의 다른 요소와 비교하여 성능 오버 헤드가 무시할 수 있기 때문에 이것은 좋습니다. (와 같은 다른 간단한 것들로 테스트하는 것은 여전히 ​​흥미로울 수 있습니다 f=lambda x:x+x)

파이썬 어셈블리를 읽는 데 능숙하다면, dis모듈을 사용하여 실제로 그 장면이 어떻게 진행되고 있는지 확인할 수 있습니다.

>>> listComp = compile('[f(x) for x in xs]', 'listComp', 'eval')
>>> dis.dis(listComp)
  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x2511a48, file "listComp", line 1>)
              3 MAKE_FUNCTION            0
              6 LOAD_NAME                0 (xs)
              9 GET_ITER
             10 CALL_FUNCTION            1
             13 RETURN_VALUE
>>> listComp.co_consts
(<code object <listcomp> at 0x2511a48, file "listComp", line 1>,)
>>> dis.dis(listComp.co_consts[0])
  1           0 BUILD_LIST               0
              3 LOAD_FAST                0 (.0)
        >>    6 FOR_ITER                18 (to 27)
              9 STORE_FAST               1 (x)
             12 LOAD_GLOBAL              0 (f)
             15 LOAD_FAST                1 (x)
             18 CALL_FUNCTION            1
             21 LIST_APPEND              2
             24 JUMP_ABSOLUTE            6
        >>   27 RETURN_VALUE

 

>>> listComp2 = compile('list(f(x) for x in xs)', 'listComp2', 'eval')
>>> dis.dis(listComp2)
  1           0 LOAD_NAME                0 (list)
              3 LOAD_CONST               0 (<code object <genexpr> at 0x255bc68, file "listComp2", line 1>)
              6 MAKE_FUNCTION            0
              9 LOAD_NAME                1 (xs)
             12 GET_ITER
             13 CALL_FUNCTION            1
             16 CALL_FUNCTION            1
             19 RETURN_VALUE
>>> listComp2.co_consts
(<code object <genexpr> at 0x255bc68, file "listComp2", line 1>,)
>>> dis.dis(listComp2.co_consts[0])
  1           0 LOAD_FAST                0 (.0)
        >>    3 FOR_ITER                17 (to 23)
              6 STORE_FAST               1 (x)
              9 LOAD_GLOBAL              0 (f)
             12 LOAD_FAST                1 (x)
             15 CALL_FUNCTION            1
             18 YIELD_VALUE
             19 POP_TOP
             20 JUMP_ABSOLUTE            3
        >>   23 LOAD_CONST               0 (None)
             26 RETURN_VALUE

 

>>> evalledMap = compile('list(map(f,xs))', 'evalledMap', 'eval')
>>> dis.dis(evalledMap)
  1           0 LOAD_NAME                0 (list)
              3 LOAD_NAME                1 (map)
              6 LOAD_NAME                2 (f)
              9 LOAD_NAME                3 (xs)
             12 CALL_FUNCTION            2
             15 CALL_FUNCTION            1
             18 RETURN_VALUE 

[...]보다 구문 을 사용하는 것이 좋습니다 list(...). 슬프게도 map클래스는 분해하기에 약간 불투명하지만 속도 테스트로 인해 만들 수 있습니다.


답변

파이썬 2 : 당신은 리스트 이해력 대신에 map그리고 filter이해력을 사용해야합니다 .

목적 당신은 그들이하지 않은 경우에도 그들을 좋아해야하는 이유 이유는 “파이썬은”이것이다 :
그들은 인수 등의 기능 / 람다 요구하는 새로운 범위를 소개를 .

나는 이것에 두 번 이상 물렸다.

for x, y in somePoints:
    # (several lines of code here)
    squared = [x ** 2 for x in numbers]
    # Oops, x was silently overwritten!

그러나 대신에 나는 말했다 :

for x, y in somePoints:
    # (several lines of code here)
    squared = map(lambda x: x ** 2, numbers)

그러면 모든 것이 잘되었을 것입니다.

같은 범위에서 동일한 변수 이름을 사용하는 것에 대해 바보라고 말할 수 있습니다.

나는 아니었다. 코드는 원래 괜찮 았습니다. 두 코드는 x같은 범위에 없었습니다. 내부 블록을 코드의 다른 섹션으로 옮긴
후에야 문제가 발생했습니다 (읽기 : 유지 관리 중 문제, 개발 아님).

예, 이 실수하지 않으면 목록 이해력이 더 우아합니다.
그러나 개인적인 경험 (그리고 다른 사람들이 같은 실수를하는 것을 보았을 때)에서 나는이 버그가 코드에 들어갔을 때 겪어야 할 고통이 가치가 없다고 생각합니다.

결론:

map및을 사용하십시오 filter. 미묘한 진단하기 어려운 범위 관련 버그를 방지합니다.

사이드 노트 :

사용을 고려하는 것을 잊지 마십시오 imapifilter(에 itertools그들은 상황에 맞는 적절한 경우)!


답변

실제로, map리스트 이해는 파이썬 3 언어에서 상당히 다르게 행동합니다. 다음 Python 3 프로그램을 살펴보십시오.

def square(x):
    return x*x
squares = map(square, [1, 2, 3])
print(list(squares))
print(list(squares))

“[1, 4, 9]”줄을 두 번 인쇄해야하지만 대신 “[1, 4, 9]”뒤에 “[]”가 인쇄됩니다. 처음 보았을 때 squares세 가지 요소의 시퀀스로 작동하지만 두 번째는 빈 요소로 동작합니다.

파이썬 2에서 언어 map는 목록 이해가 두 언어에서와 같이 평범한 오래된 목록을 반환합니다. 요점은 mapPython 3 (및 imapPython 2) 의 반환 값이 목록이 아니라 반복 자라는 것입니다!

목록을 반복 할 때와 달리 반복자를 반복 할 때 요소가 소비됩니다. 이것이 squares마지막 print(list(squares))줄 에서 비어있는 이유 입니다.

요약:

  • 이터레이터를 다룰 때, 그것들은 스테이트 풀 (stateful)이고 그것들을 순회 할 때 돌연변이한다는 것을 기억해야합니다.
  • 목록은 명시 적으로 변경했을 때만 변경되므로 더 예측 가능합니다. 그들은 컨테이너입니다 입니다.
  • 그리고 보너스 : 숫자, 문자열 및 튜플은 전혀 변경 될 수 없으므로 훨씬 더 예측 가능합니다. 그것들은 가치 입니다.

답변

나는 목록 이해가 일반적으로 내가하려는 일보다 더 표현력이 뛰어나다는 것을 알았 map습니다. 둘 다 완료하지만 전자는 복잡한 것이 무엇인지 이해하려고 노력하는 정신적 부담을 덜어줍니다.lambda 표현이 .

Guido lambda가 파이썬으로 받아들이는 것에 대해 가장 후회하는 것으로 s와 기능적 기능을 나열하는 곳에서 (내가 손으로 찾을 수없는) 인터뷰가 있습니다 . 그래서 그들은 미덕에 의해 비 파이썬 적이라는 주장을 할 수 있습니다 그것의.


답변

가능한 사례는 다음과 같습니다.

map(lambda op1,op2: op1*op2, list1, list2)

대:

[op1*op2 for op1,op2 in zip(list1,list2)]

zip ()은 불행히도 불필요한 오버 헤드라고 생각합니다.지도 대신 목록 이해를 고집하는 경우 탐닉해야합니다. 누군가 긍정적 으로든 부정적 으로든 이것을 명확히하면 좋을 것입니다.


답변

비동기식, 병렬 식 또는 분산 형 코드를 작성하려는 경우 map대부분의 비동기식, 병렬 식 또는 분산 형 패키지가 mappython에 과부하를 가하는 기능을 제공하므로 목록 이해를 선호 할 것입니다 map. 그런 다음 map나머지 코드에 적절한 기능을 전달하면 원래 직렬 코드를 수정하여 병렬로 실행하지 않아도 될 수 있습니다.