문제

사용을 선호하는 이유가 있나요? map() 목록 이해를 넘어서거나 그 반대입니까?둘 중 하나가 일반적으로 다른 것보다 더 효율적이거나 일반적으로 더 파이썬적인 것으로 간주됩니까?

도움이 되었습니까?

해결책

map 어떤 경우에는 현미경으로 더 빠를 수 있습니다 (목적을 위해 람다를 만들지 않고 MAP 및 ListComp에서 동일한 함수를 사용하는 경우). 다른 경우에는 목록 이해력이 더 빠를 수 있으며 대부분의 (전부는 아님) Pythonistas는 더 직접적이고 명확하게 생각합니다.

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

$ 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

다른 팁

사례

  • 일반적인 경우:거의 항상 목록 이해 기능을 사용하고 싶을 것입니다. 파이썬 왜냐하면 당신의 코드를 읽는 초보 프로그래머에게 당신이 무엇을 하고 있는지 더 분명하게 알 수 있기 때문입니다.(이것은 다른 관용구가 적용될 수 있는 다른 언어에는 적용되지 않습니다.) 목록 이해는 반복을 위한 Python의 사실상 표준이기 때문에 Python 프로그래머에게 무엇을 하고 있는지 훨씬 더 분명해질 것입니다.그들은 예상되는.
  • 덜 일반적인 경우:그러나 만약 당신이 이미 정의된 함수가 있습니다., 종종 사용하는 것이 합리적입니다. map, '비파이썬적'으로 간주되지만.예를 들어, 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에서는 다음과 같은 펑터 인터페이스가 있습니다. fmap 모든 데이터 구조에 대한 매핑을 일반화합니다.Python 문법으로 인해 반복에 대해 이야기하기 위해 생성기 스타일을 사용해야 하기 때문에 Python에서는 매우 흔하지 않습니다.쉽게 일반화할 수는 없습니다.(이것은 좋을 때도 있고 나쁠 때도 있습니다.) 아마도 희귀한 Python 예제를 생각해 낼 수 있을 것입니다. map(f, *lists) 합리적인 일입니다.내가 생각해 낼 수있는 가장 가까운 예는 다음과 같습니다. sumEach = partial(map,sum), 이는 대략 다음과 거의 동일한 한 줄짜리 코드입니다.

def sumEach(myLists):
    return [sum(_) for _ in myLists]
  • 그냥 for-고리:물론 for-loop를 사용할 수도 있습니다.기능적 프로그래밍 관점에서 볼 때 그다지 우아하지는 않지만, 때때로 비지역 변수는 Python과 같은 명령형 프로그래밍 언어에서 코드를 더 명확하게 만듭니다. 왜냐하면 사람들은 그런 식으로 코드를 읽는 데 매우 익숙하기 때문입니다.For 루프는 또한 일반적으로 목록 이해 및 맵과 같은 목록을 작성하지 않는 복잡한 작업을 수행할 때 가장 효율적입니다(예:합산, 트리 만들기 등) - 적어도 메모리 측면에서는 효율적입니다(드문 병리학적 가비지 수집 딸꾹질을 제외하고 최악의 경우 상수 요소를 기대하는 시간 측면에서 반드시 필요한 것은 아닙니다).

"파이톤주의"

나는 "pythonic"이라는 단어를 싫어합니다. 왜냐하면 내 눈에는 pythonic이 항상 우아하다고 생각되지 않기 때문입니다.그럼에도 불구하고, map 그리고 filter 그리고 유사한 기능(매우 유용한 itertools 모듈)은 아마도 스타일 측면에서 파이썬이 아닌 것으로 간주될 것입니다.

게으름

대부분의 함수형 프로그래밍 구성과 마찬가지로 효율성 측면에서 지도가 느릴 수 있음, 실제로 Python에서는 게으르다.이는 당신이 이것을 할 수 있다는 것을 의미합니다 ( 파이썬3) 그러면 컴퓨터의 메모리가 부족해지지 않고 저장하지 않은 데이터가 모두 손실되지 않습니다.

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

따라서 모든 데이터를 사용하지 않거나 필요한 데이터의 양을 미리 알 수 없는 경우, map python3(및 python2 또는 python3의 생성기 표현식)에서는 필요한 마지막 순간까지 값을 계산하지 않습니다.일반적으로 이는 일반적으로 사용으로 인한 오버헤드보다 큽니다. map.단점은 대부분의 기능적 언어와 달리 Python에서는 이것이 매우 제한적이라는 것입니다.Python 생성기 표현식은 순서대로만 평가할 수 있으므로 왼쪽에서 오른쪽으로 "순서대로" 데이터에 액세스하는 경우에만 이 이점을 얻을 수 있습니다. 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년경 Intel 워크스테이션에서 수행되었으며 B와 C는 Python 3.2.1이 설치된 2013년경 AMD 워크스테이션에서 수행되었습니다. 매우 다른 하드웨어를 사용합니다.그 결과 맵과 리스트 컴프리헨션은 성능 면에서 비슷한 것으로 보이며, 이는 다른 무작위 요인에 의해 가장 큰 영향을 받습니다.우리가 말할 수 있는 유일한 것은 이상하게도 목록 이해를 기대하는 동안 [...] 생성기 표현식보다 더 나은 성능을 발휘하려면 (...), map 또한 생성기 표현식보다 더 효율적입니다(다시 모든 값이 평가/사용된다고 가정).

이러한 테스트는 매우 간단한 기능(신원 기능)을 가정한다는 점을 인식하는 것이 중요합니다.그러나 함수가 복잡하다면 프로그램의 다른 요소에 비해 성능 오버헤드가 미미할 것이기 때문에 괜찮습니다.(다음과 같은 다른 간단한 것으로 테스트하는 것은 여전히 ​​흥미로울 수 있습니다. f=lambda x:x+x)

Python 어셈블리를 읽는 데 능숙하다면 다음을 사용할 수 있습니다. 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 클래스는 분해하기가 약간 불투명하지만 속도 테스트를 통해 결과를 얻을 수 있습니다.

Python 2 : 사용해야합니다 map 그리고 filter 목록 이해 대신.

an 목적 "Pythonic"이 아니더라도 선호 해야하는 이유는 다음과 같습니다.
그들은 인수로 기능/람다를 요구합니다 새로운 범위를 소개합니다.

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

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)

그러면 모든 것이 괜찮 았을 것입니다.

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

나는 아니었다. 코드는 원래 두 가지였습니다 xS는 같은 범위가 아니었다.
나는 나 이후였다 움직이는 문제가 발생한 코드의 다른 섹션에 대한 내부 블록 (읽기 : 개발이 아닌 유지 보수 중 문제).

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

결론:

사용 map 그리고 filter. 그들은 진단하기 어려운 범위 관련 버그를 방지합니다.

사이드 참고 :

사용을 고려하는 것을 잊지 마십시오 imap 그리고 ifilter (안에 itertools) 그들이 당신의 상황에 적합하다면!

실제로, map 그리고 목록 이해는 Python 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 그것은 세 가지 요소의 시퀀스로 행동하는 것처럼 보이지만 두 번째는 비어있는 것입니다.

Python 2 언어로 map 목록 이해가 두 언어로하는 것처럼 평범한 오래된 목록을 반환합니다. 요점은 반환 값입니다 map Python 3에서 (및 imap Python 2)에서 목록이 아닙니다. 반복자입니다!

목록을 반복 할 때와 달리 반복자를 반복 할 때 요소가 소비됩니다. 이는 이유 squares 마지막으로 비어 있습니다 print(list(squares)) 선.

요약:

  • 반복자를 다룰 때는 그들이 진정한 상태이며 당신이 그들을 가로 질러 돌연변이한다는 것을 기억해야합니다.
  • 목록은 명시 적으로 돌연변이 할 때만 변경되기 때문에 더 예측 가능합니다. 그들은 컨테이너.
  • 그리고 보너스 : 숫자, 줄 및 튜플은 전혀 바꿀 수 없기 때문에 훨씬 더 예측 가능합니다. 그들은 가치.

나는 목록 이해가 일반적으로 내가하려는 것보다 더 표현한다는 것을 알았습니다. map - 둘 다 끝내지 만, 전자는 복잡한 것이 무엇인지 이해하려고 노력하는 정신 부하를 구합니다. lambda 표현.

Guido가 목록에있는 곳에서 인터뷰도 있습니다 (나는 그것을 찾을 수 없습니다) lambdaS와 기능적 기능은 파이썬을 받아들이는 것에 대해 가장 후회하는 기능으로서, 당신은 그것에 의해 그것들에 의해 그것들이 비판적이라는 주장을 할 수 있습니다.

가능한 한 가지 경우가 있습니다.

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

대:

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

Zip ()가 맵 대신 목록 이해를 사용한다고 주장하면 불행하고 불필요한 오버 헤드라고 생각합니다. 누군가가 긍정적으로 또는 부정적으로 이것을 명확히한다면 좋을 것입니다.

비동기식, 병렬 또는 분산 코드를 작성할 계획이라면 아마도 선호 할 것입니다. map 목록 이해력을 통해 - 대부분의 비동기식, 병렬 또는 분산 패키지가 map Python을 과부하시키는 기능 map. 그런 다음 적절한 것을 통과시킵니다 map 코드의 나머지 부분에 기능하면 원래 직렬 코드를 병렬로 실행하도록 원래 직렬 코드를 수정할 필요가 없습니다.

그래서 Python 3 이후 map() 반복자이므로 필요한 것을 명심해야합니다. 반복자 또는 list 물체.

이미 @alexmartelli처럼 말하는, map() 사용하지 않는 경우에만 목록 이해보다 빠릅니다. lambda 기능.

시간 비교를 제시하겠습니다.

파이썬 3.5.2 및 Cpython
나는 사용했다 목성 노트북 특히 %timeit 내장 마술 명령
측정: s == 1000 ms == 1000 * 1000 µs = 1000 * 1000 * 1000 ns

설정:

x_list = [(i, i+1, i+2, i*2, i-9) for i in range(1000)]
i_list = list(range(1000))

내장 기능 :

%timeit map(sum, x_list)  # creating iterator object
# Output: The slowest run took 9.91 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 277 ns per loop

%timeit list(map(sum, x_list))  # creating list with map
# Output: 1000 loops, best of 3: 214 µs per loop

%timeit [sum(x) for x in x_list]  # creating list with list comprehension
# Output: 1000 loops, best of 3: 290 µs per loop

lambda 기능:

%timeit map(lambda i: i+1, i_list)
# Output: The slowest run took 8.64 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 325 ns per loop

%timeit list(map(lambda i: i+1, i_list))
# Output: 1000 loops, best of 3: 183 µs per loop

%timeit [i+1 for i in i_list]
# Output: 10000 loops, best of 3: 84.2 µs per loop

발전기 표현과 같은 것이 있습니다. PEP-0289. 그래서 비교에 추가하는 것이 유용 할 것이라고 생각했습니다.

%timeit (sum(i) for i in x_list)
# Output: The slowest run took 6.66 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 495 ns per loop

%timeit list((sum(x) for x in x_list))
# Output: 1000 loops, best of 3: 319 µs per loop

%timeit (i+1 for i in i_list)
# Output: The slowest run took 6.83 times longer than the fastest. 
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 506 ns per loop

%timeit list((i+1 for i in i_list))
# Output: 10000 loops, best of 3: 125 µs per loop

당신은 필요합니다 list 물체:

사용자 정의 함수 인 경우 목록 이해력을 사용하십시오. list(map()) 내장 기능이있는 경우

당신은 필요하지 않습니다 list 객체, 당신은 반복 가능한 것만 필요합니다.

항상 사용하십시오 map()!

나는 가장 피스닉적인 방법은 대신 목록 이해력을 사용하는 것이라고 생각합니다. map 그리고 filter. 그 이유는 목록 이해가 명확하기 때문입니다 map 그리고 filter.

In [1]: odd_cubes = [x ** 3 for x in range(10) if x % 2 == 1] # using a list comprehension

In [2]: odd_cubes_alt = list(map(lambda x: x ** 3, filter(lambda x: x % 2 == 1, range(10)))) # using map and filter

In [3]: odd_cubes == odd_cubes_alt
Out[3]: True

보시다시피, 이해력은 추가로 필요하지 않습니다. lambda 표현 map 필요합니다. 또한 이해력은 쉽게 필터링을 허용하는 반면 map 필요합니다 filter 필터링을 허용합니다.

객체의 방법을 호출하기위한 세 가지 방법을 비교하는 빠른 테스트를 실행했습니다. 이 경우 시차는 무시할 수 있으며 문제의 기능의 문제입니다 (@alex Martelli의 참조. 응답). 여기에서 다음 방법을 살펴 보았습니다.

# map_lambda
list(map(lambda x: x.add(), vals))

# map_operator
from operator import methodcaller
list(map(methodcaller("add"), vals))

# map_comprehension
[x.add() for x in vals]

목록을 보았습니다 (변수에 저장되었습니다 vals두 정수 (파이썬 int) 및 부동 소수점 번호 (파이썬 float) 목록 크기를 늘리기 위해. 다음 더미 수업 DummyNum 고려:

class DummyNum(object):
    """Dummy class"""
    __slots__ = 'n',

    def __init__(self, n):
        self.n = n

    def add(self):
        self.n += 5

구체적으로, add 방법. 그만큼 __slots__ 속성은 Python의 간단한 최적화로 클래스 (속성)에 필요한 총 메모리를 정의하여 메모리 크기를 줄입니다. 결과 음모는 다음과 같습니다.

Performance of mapping Python object methods

앞에서 언급 한 바와 같이, 사용 된 기술은 최소한의 차이를 만들고, 당신에게 가장 읽을 수있는 방식으로 또는 특정 상황에서 코딩해야합니다. 이 경우 목록 이해력 (map_comprehension 기술)은 객체의 두 가지 유형, 특히 더 짧은 목록이있는 두 가지 유형 모두에 가장 빠릅니다.

방문 이 페이스트 빈 플롯과 데이터를 생성하는 데 사용되는 소스.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top