Nice programing

목록 이해와지도

nicepro 2020. 9. 30. 11:28
반응형

목록 이해와지도


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

사례

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

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

"파이토 니즘"

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 #

list comprehensions도 본질적으로 게으르지 만 파이썬은이를 non-lazy로 구현하도록 선택했습니다 . 그럼에도 불구하고 파이썬은 다음과 같이 생성기 표현식의 형태로 게으른 목록 이해를 지원합니다.

>>> (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 년경 Intel 워크 스테이션에서 수행되었으며 B와 C는 python 3.2.1이있는 circa-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)

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

동일한 범위에서 동일한 변수 이름을 사용하는 것이 어리 석다고 말할 수 있습니다.

나는 아니었다. 코드는 원래 괜찮 았습니다. 두 개의 xs가 동일한 범위에 있지 않았습니다. 내부 블록을 코드의 다른 섹션으로 옮긴
후에야 문제가 발생했으며 (읽기 : 개발이 아닌 유지 관리 중 문제) 예상하지 못했습니다.

예, 이 실수를적이 없다면 목록 이해가 더 우아합니다.
그러나 개인적인 경험 (그리고 다른 사람들이 똑같은 실수를하는 것을 보아서)에서 이러한 버그가 코드에 침투 할 때 겪어야 할 고통을 감당할 가치가 없다고 생각할만큼 충분히 발생하는 것을 보았습니다.

결론:

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

참고 :

상황에 적합한 경우 imapifilter(에서 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목록 이해가 두 언어에서 수행하는 것처럼 일반 이전 목록을 반환합니다. 핵심은 mapPython 3 (및 imapPython 2) 의 반환 값이 목록이 아니라 반복 자라는 것입니다!

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

요약:

  • 이터레이터를 다룰 때, 그것들은 상태 저장이고 그것들을 순회 할 때 변이한다는 것을 기억해야합니다.
  • Lists are more predictable since they only change when you explicitly mutate them; they are containers.
  • And a bonus: numbers, strings, and tuples are even more predictable since they cannot change at all; they are values.

I find list comprehensions are generally more expressive of what I'm trying to do than map - they both get it done, but the former saves the mental load of trying to understand what could be a complex lambda expression.

There's also an interview out there somewhere (I can't find it offhand) where Guido lists lambdas and the functional functions as the thing he most regrets about accepting into Python, so you could make the argument that they're un-Pythonic by virtue of that.


If you plan on writing any asynchronous, parallel, or distributed code, you will probably prefer map over a list comprehension -- as most asynchronous, parallel, or distributed packages provide a map function to overload python's map. Then by passing the appropriate map function to the rest of your code, you may not have to modify your original serial code to have it run in parallel (etc).


Here is one possible case:

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

versus:

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

I am guessing the zip() is an unfortunate and unnecessary overhead you need to indulge in if you insist on using list comprehensions instead of the map. Would be great if someone clarifies this whether affirmatively or negatively.


So since Python 3, map() is an iterator, you need to keep in mind what do you need: an iterator or list object.

As @AlexMartelli already mentioned, map() is faster than list comprehension only if you don't use lambda function.

I will present you some time comparisons.

Python 3.5.2 and CPython
I've used Jupiter notebook and especially %timeit built-in magic command
Measurements: s == 1000 ms == 1000 * 1000 µs = 1000 * 1000 * 1000 ns

Setup:

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

Built-in function:

%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 function:

%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

There is also such thing as generator expression, see PEP-0289. So i thought it would be useful to add it to comparison

%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

You need list object:

Use list comprehension if it's custom function, use list(map()) if there is builtin function

You don't need list object, you just need iterable one:

Always use map()!


I consider that the most Pythonic way is to use a list comprehension instead of map and filter. The reason is that list comprehensions are clearer than map and 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

As you an see, a comprehension does not require extra lambda expressions as map needs. Furthermore, a comprehension also allows filtering easily, while map requires filter to allow filtering.


I ran a quick test comparing three methods for invoking the method of an object. The time difference, in this case, is negligible and is a matter of the function in question (see @Alex Martelli's response). Here, I looked at the following methods:

# 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]

I looked at lists (stored in the variable vals) of both integers (Python int) and floating point numbers (Python float) for increasing list sizes. The following dummy class DummyNum is considered:

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

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

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

Specifically, the add method. The __slots__ attribute is a simple optimization in Python to define the total memory needed by the class (attributes), reducing memory size. Here are the resulting plots.

Performance of mapping Python object methods

As stated previously, the technique used makes a minimal difference and you should code in a way that is most readable to you, or in the particular circumstance. In this case, the list comprehension (map_comprehension technique) is fastest for both types of additions in an object, especially with shorter lists.

Visit this pastebin for the source used to generate the plot and data.

참고URL : https://stackoverflow.com/questions/1247486/list-comprehension-vs-map

반응형