Danh sách hiểu vs bản đồ


732

Có một lý do để thích sử dụng map()hơn hiểu danh sách hoặc ngược lại? Là một trong số họ nói chung hiệu quả hơn hoặc được coi là nói chung nhiều pythonic hơn những người khác?


8
Lưu ý rằng PyLint cảnh báo nếu bạn sử dụng bản đồ thay vì hiểu danh sách, hãy xem tin nhắn W0141 .
thắt lưng

2
@lumbric, tôi không chắc nhưng chỉ khi lambda được sử dụng trên bản đồ.
0xc0de

Câu trả lời:


661

mapcó thể nhanh hơn kính hiển vi trong một số trường hợp (khi bạn KHÔNG tạo lambda cho mục đích, nhưng sử dụng cùng chức năng trong bản đồ và listcomp). Việc hiểu danh sách có thể nhanh hơn trong các trường hợp khác và hầu hết (không phải tất cả) pythonistas coi chúng trực tiếp và rõ ràng hơn.

Một ví dụ về lợi thế tốc độ nhỏ của bản đồ khi sử dụng chính xác cùng một chức năng:

$ 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

Một ví dụ về cách so sánh hiệu suất bị đảo ngược hoàn toàn khi bản đồ cần lambda:

$ 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

39
Đúng, hướng dẫn phong cách Python nội bộ của chúng tôi tại nơi làm việc rõ ràng áp dụng các danh sách đối với bản đồ và bộ lọc (thậm chí không đề cập đến bản đồ cải thiện hiệu suất nhỏ nhưng có thể đo lường được trong một số trường hợp ;-).
Alex Martelli

46
Không kibash trên các điểm phong cách vô hạn của Alex, nhưng đôi khi bản đồ có vẻ dễ đọc hơn đối với tôi: data = map (str, some_list_of_objects). Một số cái khác ... oper.attrgetter, oper.itemgetter, v.v.
Gregg Lind

57
map(operator.attrgetter('foo'), objs)dễ đọc hơn [o.foo for o in objs]?!
Alex Martelli

52
@Alex: Tôi không muốn giới thiệu những cái tên không cần thiết, như oở đây, và ví dụ của bạn cho thấy lý do tại sao.
Reid Barton

29
Tuy nhiên, tôi nghĩ rằng @GreggLind có một điểm, với str()ví dụ của anh ấy .
Eric O Lebigot

474

Các trường hợp

  • Trường hợp phổ biến : Hầu như luôn luôn, bạn sẽ muốn sử dụng một sự hiểu biết danh sách trong python bởi vì nó sẽ rõ ràng hơn những gì bạn đang làm cho những lập trình viên mới đọc mã của bạn. (Điều này không áp dụng cho các ngôn ngữ khác, nơi các thành ngữ khác có thể áp dụng.) Thậm chí sẽ rõ ràng hơn những gì bạn đang làm với các lập trình viên python, vì việc hiểu danh sách là tiêu chuẩn thực tế trong python cho phép lặp; họ đang mong đợi .
  • Trường hợp ít phổ biến hơn : Tuy nhiên nếu bạn đã có một chức năng được xác định , nó thường hợp lý để sử dụng map, mặc dù nó được coi là 'unpythonic'. Ví dụ, map(sum, myLists)là thanh lịch / ngắn gọn hơn [sum(x) for x in myLists]. Bạn có được sự tao nhã khi không phải tạo ra một biến giả (ví dụ sum(x) for x...hoặc sum(_) for _...hoặc sum(readableName) for readableName...) mà bạn phải gõ hai lần, chỉ để lặp lại. Lập luận tương tự giữ filterreducevà bất cứ điều gì từ itertoolsmô-đun: nếu bạn đã có sẵn một chức năng, bạn có thể tiếp tục và thực hiện một số lập trình chức năng. Điều này đạt được khả năng đọc trong một số tình huống và mất nó trong các tình huống khác (ví dụ: lập trình viên mới làm quen, nhiều đối số) ... nhưng khả năng đọc mã của bạn phụ thuộc rất nhiều vào nhận xét của bạn.
  • Hầu như không bao giờ : Bạn có thể muốn sử dụng mapchức năng như một chức năng trừu tượng thuần túy trong khi thực hiện lập trình chức năng, trong đó bạn đang lập bản đồ map, hoặc curry map, hoặc nói cách khác là có lợi khi nói về mapchức năng. Ví dụ, trong Haskell, giao diện functor được gọi là fmaptổng quát hóa ánh xạ trên bất kỳ cấu trúc dữ liệu nào. Điều này rất không phổ biến trong python vì ngữ pháp python buộc bạn phải sử dụng kiểu trình tạo để nói về phép lặp; bạn không thể khái quát nó dễ dàng. (Điều này đôi khi tốt và đôi khi xấu.) Bạn có thể đến với các ví dụ trăn quý hiếm, nơi đó map(f, *lists)là một điều hợp lý để làm. Ví dụ gần nhất tôi có thể đưa ra là sumEach = partial(map,sum), đó là một lớp lót tương đương với:

def sumEach(myLists):
    return [sum(_) for _ in myLists]
  • Chỉ cần sử dụng một for-loop : Tất nhiên bạn cũng có thể chỉ sử dụng một vòng lặp for. Mặc dù không thanh lịch theo quan điểm lập trình chức năng, đôi khi các biến không cục bộ làm cho mã rõ ràng hơn trong các ngôn ngữ lập trình bắt buộc như python, bởi vì mọi người rất quen đọc mã theo cách đó. Nhìn chung, các vòng lặp cũng hiệu quả nhất khi bạn chỉ thực hiện bất kỳ thao tác phức tạp nào không xây dựng danh sách như hiểu danh sách và bản đồ được tối ưu hóa cho (ví dụ: tổng hợp hoặc tạo cây, v.v.) - ít nhất hiệu quả về mặt bộ nhớ (không nhất thiết là về thời gian, trong đó tôi mong đợi điều tồi tệ nhất là yếu tố bất biến, loại bỏ một số trục trặc thu gom rác bệnh lý hiếm gặp).

"Chủ nghĩa trăn"

Tôi không thích từ "pythonic" bởi vì tôi không thấy rằng pythonic luôn thanh lịch trong mắt tôi. Tuy nhiên, mapfiltercác chức năng tương tự (như itertoolsmô-đun rất hữu ích ) có thể được coi là unpythonic về kiểu dáng.

Lười biếng

Về hiệu quả, giống như hầu hết các cấu trúc lập trình chức năng, MAP CAN BE LAZY , và trên thực tế là lười biếng trong python. Điều đó có nghĩa là bạn có thể làm điều này (trong python3 ) và máy tính của bạn sẽ không hết bộ nhớ và mất tất cả dữ liệu chưa được lưu của bạn:

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

Hãy thử làm điều đó với một sự hiểu biết danh sách:

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

Xin lưu ý rằng việc hiểu danh sách vốn dĩ rất lười biếng, nhưng python đã chọn thực hiện chúng là không lười biếng . Tuy nhiên, python không hỗ trợ việc hiểu danh sách lười biếng dưới dạng biểu thức trình tạo, như sau:

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

Về cơ bản, bạn có thể nghĩ về [...]cú pháp như chuyển một biểu thức trình tạo đến hàm tạo danh sách, như thế nào list(x for x in range(5)).

Ví dụ ngắn gọn

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

Khả năng hiểu danh sách là không lười biếng, vì vậy có thể cần nhiều bộ nhớ hơn (trừ khi bạn sử dụng khả năng hiểu trình tạo). Dấu ngoặc vuông [...]thường làm cho mọi thứ rõ ràng, đặc biệt là khi trong một mớ dấu ngoặc đơn. Mặt khác, đôi khi bạn trở nên dài dòng như gõ [x for x in.... Miễn là bạn giữ các biến lặp của bạn ngắn, việc hiểu danh sách thường rõ ràng hơn nếu bạn không thụt mã. Nhưng bạn luôn có thể thụt lề mã của bạn.

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

hoặc phá vỡ mọi thứ:

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

So sánh hiệu quả cho python3

map bây giờ lười biếng:

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

Do đó, nếu bạn sẽ không sử dụng tất cả dữ liệu của mình hoặc không biết trước mình cần bao nhiêu dữ liệu, maptrong python3 (và biểu thức trình tạo trong python2 hoặc python3) sẽ tránh tính toán giá trị của chúng cho đến giây phút cuối cùng cần thiết. Thông thường, điều này thường sẽ vượt trội hơn bất kỳ chi phí sử dụng map. Nhược điểm là điều này rất hạn chế đối với python so với hầu hết các ngôn ngữ chức năng: bạn chỉ nhận được lợi ích này nếu bạn truy cập dữ liệu từ trái sang phải "theo thứ tự", bởi vì các biểu thức của trình tạo python chỉ có thể được đánh giá theo thứ tự x[0], x[1], x[2], ....

Tuy nhiên, hãy nói rằng chúng tôi có một chức năng được tạo sẵn fmà chúng tôi muốn mapvà chúng tôi bỏ qua sự lười biếng mapbằng cách ngay lập tức buộc phải đánh giá list(...). Chúng tôi nhận được một số kết quả rất thú vị:

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

Kết quả là ở dạng AAA / BBB / CCC, trong đó A được thực hiện trên máy trạm Intel vào khoảng năm 2010 với python 3.?., Và B và C đã được thực hiện với máy trạm AMD vào khoảng năm 2013 với python 3.2.1, với phần cứng cực kỳ khác nhau. Kết quả có vẻ là sự hiểu biết về bản đồ và danh sách có thể so sánh về hiệu suất, bị ảnh hưởng mạnh mẽ nhất bởi các yếu tố ngẫu nhiên khác. Điều duy nhất chúng ta có thể nói dường như là, thật kỳ lạ, trong khi chúng ta mong đợi việc hiểu danh sách [...]sẽ hoạt động tốt hơn các biểu thức của trình tạo (...), mapC ALNG hiệu quả hơn các biểu thức của trình tạo (một lần nữa giả định rằng tất cả các giá trị được ước tính / sử dụng).

Điều quan trọng là phải nhận ra rằng các thử nghiệm này đảm nhận một chức năng rất đơn giản (chức năng nhận dạng); tuy nhiên điều này là tốt vì nếu chức năng phức tạp, thì chi phí hoạt động sẽ không đáng kể so với các yếu tố khác trong chương trình. (Có thể vẫn thú vị khi thử nghiệm với những thứ đơn giản khác như f=lambda x:x+x)

Nếu bạn thành thạo đọc lắp ráp python, bạn có thể sử dụng dismô-đun để xem đó có thực sự là những gì đang diễn ra sau hậu trường không:

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

Có vẻ như tốt hơn để sử dụng [...]cú pháp hơn list(...). Đáng buồn là maplớp học hơi mờ để tháo gỡ, nhưng chúng ta có thể thực hiện nhờ kiểm tra tốc độ của chúng tôi.


5
"Mô-đun itertools rất hữu ích [có thể] được coi là unpythonic về kiểu dáng". Hừm. Tôi cũng không thích thuật ngữ "Pythonic", vì vậy trong một số trường hợp, tôi không quan tâm đến ý nghĩa của nó, nhưng tôi không nghĩ nó công bằng với những người sử dụng nó, để nói rằng theo các nội dung của "Pythonicness" mapfiltercùng với thư viện tiêu chuẩn itertoolsvốn đã có phong cách xấu. Trừ khi GvR thực sự nói rằng họ là một sai lầm khủng khiếp hoặc chỉ vì hiệu suất, kết luận tự nhiên duy nhất nếu đó là điều "Pythonicness" nói là hãy quên nó đi là ngu ngốc ;-)
Steve Jessop

4
@SteveJessop: Trên thực tế, Guido nghĩ rằng việc bỏ map/ filterlà một ý tưởng tuyệt vời cho Python 3 và chỉ có một cuộc nổi loạn của các Pythonistas khác giữ chúng trong không gian tên tích hợp (trong khi reduceđược chuyển sang functools). Cá nhân tôi không đồng ý ( mapfiltervẫn ổn với các chức năng được xác định trước, đặc biệt là tích hợp sẵn, chỉ không bao giờ sử dụng chúng nếu lambdacần thiết), nhưng về cơ bản GvR đã gọi chúng không phải là Pythonic trong nhiều năm.
ShadowRanger

@ShadowRanger: đúng, nhưng GvR đã bao giờ lên kế hoạch xóa itertoolschưa? Phần tôi trích dẫn từ câu trả lời này là yêu cầu chính làm tôi bối rối. Tôi không biết liệu trong thế giới lý tưởng của anh ấy, mapfiltersẽ chuyển đến itertools(hoặc functools) hoặc đi hoàn toàn, nhưng bất kể là trường hợp nào, một khi người ta nói rằng đó itertoolslà unPythonic hoàn toàn, thì tôi không thực sự biết "Pythonic" là gì có nghĩa là nhưng tôi không nghĩ nó có thể giống với "những gì GvR khuyên mọi người nên sử dụng".
Steve Jessop

2
@SteveJessop: Tôi chỉ giải quyết map/ filter, không phải itertools. Lập trình chức năng là hoàn toàn Pythonic ( itertools, functoolsoperatortất cả đều được thiết kế đặc biệt với chức năng lập trình trong tâm trí, và tôi sử dụng thành ngữ chức năng trong Python tất cả các thời gian), và itertoolscung cấp các tính năng đó sẽ là một nỗi đau để thực hiện chính mình, Nó đặc biệt mapfilterlà dư thừa với các biểu thức máy phát điện Điều đó khiến Guido ghét họ. itertoolsluôn luôn tốt
ShadowRanger

1
Tôi có thể thích câu trả lời này nếu có một cách. Giải thích tốt.
NelsonGon

95

Python 2: Bạn nên sử dụng mapfilterthay vì hiểu danh sách.

Một lý do khách quan tại sao bạn nên thích chúng hơn mặc dù chúng không phải là "Pythonic" là:
Chúng yêu cầu các hàm / lambdas làm đối số, giới thiệu một phạm vi mới .

Tôi đã bị cắn bởi điều này hơn một lần:

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

nhưng nếu thay vào đó tôi đã nói:

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

sau đó mọi thứ sẽ ổn

Bạn có thể nói rằng tôi đã ngớ ngẩn vì sử dụng cùng một tên biến trong cùng một phạm vi.

Tôi đã không. Mã ban đầu vẫn ổn - hai xs không có cùng phạm vi.
Chỉ sau khi tôi chuyển khối bên trong sang một phần khác của mã thì vấn đề mới xuất hiện (đọc: vấn đề trong quá trình bảo trì, không phát triển) và tôi không mong đợi điều đó.

Có, nếu bạn không bao giờ phạm sai lầm này thì danh sách hiểu được thanh lịch hơn.
Nhưng từ kinh nghiệm cá nhân (và từ việc nhìn thấy người khác mắc lỗi tương tự) Tôi đã thấy điều đó xảy ra đủ lần mà tôi nghĩ rằng nó không đáng để bạn phải trải qua khi những con bọ này chui vào mã của bạn.

Phần kết luận:

Sử dụng mapfilter. Chúng ngăn ngừa các lỗi liên quan đến phạm vi khó chẩn đoán.

Lưu ý bên:

Đừng quên cân nhắc sử dụng imapifilter(trong itertools) nếu chúng phù hợp với hoàn cảnh của bạn!


7
Cảm ơn đã chỉ ra điều này. Nó đã không rõ ràng xảy ra với tôi rằng việc hiểu danh sách là trong cùng một phạm vi và có thể là một vấn đề. Như đã nói, tôi nghĩ rằng một số câu trả lời khác cho thấy rõ rằng việc hiểu danh sách nên là cách tiếp cận mặc định hầu hết thời gian nhưng đây là điều cần nhớ. Đây cũng là một lời nhắc chung chung để giữ cho các chức năng (và do đó phạm vi) nhỏ và có các bài kiểm tra đơn vị kỹ lưỡng và sử dụng các câu lệnh khẳng định.
TimothyAWiseman

13
@wim: Đây chỉ là về Python 2, mặc dù nó áp dụng cho Python 3 nếu bạn muốn tương thích ngược. Tôi đã biết về nó và tôi đã sử dụng Python được một thời gian rồi (vâng, chỉ hơn một vài tháng), nhưng nó đã xảy ra với tôi. Tôi đã thấy những người khác thông minh hơn tôi rơi vào cùng một cái bẫy. Nếu bạn quá sáng dạ và / hoặc có kinh nghiệm rằng đây không phải là vấn đề với bạn thì tôi mừng cho bạn, tôi không nghĩ rằng hầu hết mọi người đều giống bạn. Nếu đúng như vậy, sẽ không có sự thôi thúc khắc phục nó trong Python 3.
user541686 17/12/13

12
Tôi xin lỗi nhưng bạn đã viết nó vào cuối năm 2012, ngay sau khi python 3 xuất hiện và câu trả lời có vẻ như bạn đang đề xuất một kiểu mã trăn không phổ biến khác chỉ vì bạn bị bọ cắn trong khi cắt và- dán mã. Tôi chưa bao giờ tuyên bố là sáng sủa hoặc có kinh nghiệm, tôi chỉ không đồng ý rằng yêu cầu táo bạo được chứng minh bằng lý do của bạn.
wim

8
@wim: Hả? Python 2 vẫn được sử dụng ở nhiều nơi, thực tế là Python 3 tồn tại không thay đổi điều đó. Và khi bạn nói "đó không hẳn là một lỗi tinh vi đối với bất kỳ ai đã sử dụng Python hơn một vài tháng", câu đó có nghĩa đen là "điều này chỉ liên quan đến các nhà phát triển thiếu kinh nghiệm" (rõ ràng không phải bạn). Và đối với hồ sơ, bạn rõ ràng đã không đọc câu trả lời vì tôi đã in đậm rằng tôi đang di chuyển , không sao chép, mã. Lỗi sao chép-dán khá đồng đều trên các ngôn ngữ. Loại lỗi này là duy nhất đối với Python vì phạm vi của nó; nó tinh tế hơn và dễ quên hơn và bỏ lỡ.
dùng541686

3
Nó vẫn không phải là một lý do hợp lý để chuyển sang mapvà / hoặc filter. Nếu bất cứ điều gì, bản dịch trực tiếp và hợp lý nhất để tránh vấn đề của bạn không phải là map(lambda x: x ** 2, numbers)mà là một biểu thức trình list(x ** 2 for x in numbers)tạo không bị rò rỉ, như JeromeJ đã chỉ ra. Hãy nhìn Mehrdad, đừng coi thường cá nhân, tôi hoàn toàn không đồng ý với lý luận của bạn ở đây.
wim

46

Trên thực tế, mapvà việc hiểu danh sách hoạt động hoàn toàn khác nhau trong ngôn ngữ Python 3. Hãy xem chương trình Python 3 sau:

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

Bạn có thể mong đợi nó in dòng "[1, 4, 9]" hai lần, nhưng thay vào đó, nó sẽ in "[1, 4, 9]" theo sau là "[]". Lần đầu tiên bạn nhìn vào squaresnó dường như hành xử như một chuỗi ba yếu tố, nhưng lần thứ hai là một yếu tố trống rỗng.

Trong ngôn ngữ Python 2 maptrả về một danh sách cũ đơn giản, giống như việc hiểu danh sách thực hiện ở cả hai ngôn ngữ. Điểm mấu chốt là giá trị trả về của mapPython 3 (và imaptrong Python 2) không phải là một danh sách - đó là một trình vòng lặp!

Các phần tử được tiêu thụ khi bạn lặp qua một trình vòng lặp không giống như khi bạn lặp qua một danh sách. Đây là lý do tại sao squarestrông trống rỗng trong print(list(squares))dòng cuối cùng .

Để tóm tắt:

  • Khi làm việc với các trình vòng lặp, bạn phải nhớ rằng chúng có trạng thái và chúng biến đổi khi bạn duyệt chúng.
  • Danh sách dễ dự đoán hơn vì chúng chỉ thay đổi khi bạn đột biến chúng; họ là container .
  • Và một phần thưởng: số, chuỗi và bộ dữ liệu thậm chí còn dễ dự đoán hơn vì chúng không thể thay đổi; họ là giá trị .

đây có lẽ là lý lẽ tốt nhất để hiểu danh sách bản đồ pythons không phải là bản đồ chức năng mà là con ghẻ đầu đỏ què quặt của một thực hiện chức năng. Rất buồn, vì tôi thực sự không thích hiểu.
semiomant

@semiomant Tôi sẽ nói bản đồ lười biếng (như trong python3) là 'chức năng' hơn bản đồ háo hức (như trong python2). Ví dụ: bản đồ trong Haskell là lười biếng (tốt, mọi thứ trong Haskell đều lười biếng ...). Dù sao, bản đồ lười biếng sẽ tốt hơn cho việc xâu chuỗi các bản đồ - nếu bạn có một bản đồ được áp dụng cho bản đồ được áp dụng cho bản đồ, bạn có một danh sách cho mỗi cuộc gọi bản đồ trung gian trong python2, trong khi trong python3 bạn chỉ có một danh sách kết quả, do đó, bộ nhớ sẽ hiệu quả hơn .
MnZrK

Tôi đoán những gì tôi muốn là mapđể tạo ra một cấu trúc dữ liệu, không phải là một trình vòng lặp. Nhưng có lẽ các trình vòng lặp lười biếng dễ hơn các cấu trúc dữ liệu lười biếng. Thức ăn cho suy nghĩ. Cảm ơn @MnZrK
bán kết

Bạn muốn nói map trả về một iterable chứ không phải iterator.
dùng541686

16

Tôi thấy việc hiểu danh sách nói chung thể hiện rõ hơn những gì tôi đang cố gắng thực hiện hơn map- cả hai đều hoàn thành nó, nhưng trước đây giúp tiết kiệm tinh thần của việc cố gắng hiểu những gì có thể là một lambdabiểu thức phức tạp .

Ngoài ra còn có một cuộc phỏng vấn ở đâu đó (tôi không thể tìm thấy nó một cách trực tiếp) nơi Guido liệt kê lambdacác chức năng và chức năng là điều anh ấy hối hận nhất khi chấp nhận vào Python, vì vậy bạn có thể đưa ra lập luận rằng họ không phải là Pythonic bởi đức hạnh điều đó


9
Vâng, thở dài, nhưng ý định ban đầu của Guido là loại bỏ lambda hoàn toàn trong Python 3 đã ngăn cản hành lang chống lại nó, vì vậy anh ta đã quay trở lại mặc dù có sự hỗ trợ mạnh mẽ của tôi - à, đoán xem lambda chỉ quá tiện dụng trong nhiều trường hợp ĐƠN GIẢN , chỉ vấn đề là khi nó vượt quá giới hạn của SIMPLE hoặc được gán cho một tên (trong trường hợp sau đó, đó là một bản sao sai lầm ngớ ngẩn của def! -).
Alex Martelli

1
Cuộc phỏng vấn mà bạn đang nghĩ đến là cuộc phỏng vấn này: amk.ca/python/wr/gvr-interview , nơi Guido nói "Đôi khi tôi đã quá nhanh chóng trong việc chấp nhận những đóng góp, và sau đó nhận ra rằng đó là một sai lầm. Một ví dụ sẽ là Một số tính năng lập trình chức năng, chẳng hạn như các hàm lambda. lambda là một từ khóa cho phép bạn tạo một hàm ẩn danh nhỏ, các hàm tích hợp như ánh xạ, bộ lọc và giảm chạy một hàm theo loại trình tự, chẳng hạn như danh sách. "
J. Taylor

3
@Alex, tôi không có nhiều năm kinh nghiệm của bạn, nhưng tôi đã thấy những hiểu biết về danh sách quá phức tạp hơn nhiều so với lambdas. Tất nhiên, lạm dụng các tính năng ngôn ngữ luôn là một cám dỗ khó cưỡng. Thật thú vị khi hiểu danh sách (theo kinh nghiệm) có vẻ dễ bị lạm dụng hơn lambdas, mặc dù tôi không chắc tại sao lại như vậy. Tôi cũng sẽ chỉ ra rằng "lúng túng" không phải lúc nào cũng là điều xấu. Việc giảm phạm vi của "những điều mà dòng này có thể đang làm" đôi khi có thể giúp người đọc dễ dàng hơn. Ví dụ, consttừ khóa trong C ++ là một chiến thắng tuyệt vời dọc theo những dòng này.
Stuart Berg

> guido. Đó là một bằng chứng khác cho thấy Guido đã mất trí. Tất nhiên lambdađã được thực hiện rất khập khiễng (không có tuyên bố ..) rằng chúng khó sử dụng và hạn chế dù sao đi nữa.
javadba

16

Đây là một trường hợp có thể:

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

đấu với:

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

Tôi đoán rằng zip () là một chi phí không may và không cần thiết mà bạn cần phải thưởng thức nếu bạn khăng khăng sử dụng danh sách hiểu thay vì bản đồ. Sẽ là tuyệt vời nếu ai đó làm rõ điều này cho dù khẳng định hay phủ định.


"[op1 * op2 từ op1, op2 trong zip (list1, list2)]" | s / form / for / Và một danh sách tương đương với zip ngoài: (ít đọc hơn) [list1 [i] * list2 [i] cho i trong phạm vi (len (list1))]
yếu

2
Nên là "cho" chứ không phải "từ" trong trích dẫn mã thứ hai của bạn, @andz và trong bình luận của @ yếu. Tôi nghĩ rằng tôi đã phát hiện ra một cách tiếp cận cú pháp mới để liệt kê những hiểu biết ... Darn.
vật lý học

4
để thêm một bình luận rất muộn, bạn có thể làm cho ziplười biếng bằng cách sử dụngitertools.izip
tacaswell

5
Tôi nghĩ rằng tôi vẫn thích map(operator.mul, list1, list2). Chính ở những biểu hiện bên trái rất đơn giản này mà sự hiểu biết trở nên vụng về.
Yann Vernier

1
Tôi đã nhận ra rằng bản đồ có thể lấy một số lần lặp làm đầu vào cho chức năng của nó và do đó có thể tránh được một zip.
bli

16

Nếu bạn có kế hoạch viết bất kỳ mã không đồng bộ, song song hoặc phân tán nào, có lẽ bạn sẽ thích maphơn việc hiểu danh sách - vì hầu hết các gói không đồng bộ, song song hoặc phân tán đều cung cấp một maphàm để quá tải python map. Sau đó, bằng cách chuyển maphàm thích hợp cho phần còn lại của mã của bạn, bạn có thể không phải sửa đổi mã nối tiếp ban đầu của mình để nó chạy song song (v.v.).



1
Mô-đun đa xử lý của Python thực hiện điều này: docs.python.org/2/l Library /
Robert L.

9

Vì vậy, vì Python 3, map()là một trình vòng lặp, bạn cần ghi nhớ những gì bạn cần: một trình vòng lặp hoặc listđối tượng.

Như @AlexMartelli đã đề cập , map()chỉ nhanh hơn việc hiểu danh sách nếu bạn không sử dụng lambdachức năng.

Tôi sẽ trình bày cho bạn một số so sánh thời gian.

Python 3.5.2 và CPython
Tôi đã sử dụng máy tính xách tay Jupiter và đặc biệt là %timeitlệnh ma thuật tích hợp Các phép
đo : s == 1000 ms == 1000 * 1000 trận đấu = 1000 * 1000 * 1000 ns

Thiết lập:

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

Chức năng tích hợp:

%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 chức năng:

%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

Ngoài ra còn có biểu thức như trình tạo, xem PEP-0289 . Vì vậy, tôi nghĩ rằng nó sẽ hữu ích để thêm nó để so sánh

%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

Bạn cần listđối tượng:

Sử dụng chức năng hiểu danh sách nếu đó là chức năng tùy chỉnh, sử dụng list(map())nếu có chức năng dựng sẵn

Bạn không cần listđối tượng, bạn chỉ cần lặp lại:

Luôn luôn sử dụng map()!


1

Tôi đã chạy thử nghiệm nhanh so sánh ba phương thức để gọi phương thức của một đối tượng. Sự khác biệt về thời gian, trong trường hợp này là không đáng kể và là vấn đề của chức năng được đề cập (xem phản hồi của @Alex Martelli ). Ở đây, tôi đã xem xét các phương pháp sau:

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

Tôi đã xem các danh sách (được lưu trữ trong biến vals) của cả số nguyên (Python int) và số dấu phẩy động (Python float) để tăng kích thước danh sách. Lớp giả sau đây DummyNumđược xem xét:

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

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

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

Cụ thể, addphương pháp. Các __slots__thuộc tính là một tối ưu hóa đơn giản bằng Python để xác định tổng số bộ nhớ cần thiết bởi lớp (thuộc tính), giảm kích thước bộ nhớ. Dưới đây là các ô kết quả.

Hiệu năng của ánh xạ phương thức đối tượng Python

Như đã nêu trước đây, kỹ thuật được sử dụng tạo ra sự khác biệt tối thiểu và bạn nên viết mã theo cách dễ đọc nhất đối với bạn hoặc trong trường hợp cụ thể. Trong trường hợp này, việc hiểu danh sách ( map_comprehensionkỹ thuật) là nhanh nhất cho cả hai loại bổ sung trong một đối tượng, đặc biệt là với các danh sách ngắn hơn.

Truy cập pastebin này để biết nguồn được sử dụng để tạo cốt truyện và dữ liệu.


1
Như đã giải thích trong các câu trả lời khác, mapchỉ nhanh hơn nếu hàm được gọi theo cùng một cách chính xác (nghĩa là [*map(f, vals)]so với [f(x) for x in vals]). Như vậy list(map(methodcaller("add"), vals))là nhanh hơn [methodcaller("add")(x) for x in vals]. mapcó thể không nhanh hơn khi đối tác lặp sử dụng một phương thức gọi khác có thể tránh một số chi phí (ví dụ: x.add()tránh methodcallerchi phí biểu thức hoặc lambda). Đối với trường hợp thử nghiệm cụ thể này, [*map(DummyNum.add, vals)]sẽ nhanh hơn (vì về cơ bản DummyNum.add(x)x.add()có cùng hiệu suất).
GZ0

1
Nhân tiện, list()các cuộc gọi rõ ràng là hơi chậm hơn so với hiểu danh sách. Để so sánh công bằng bạn cần viết [*map(...)].
GZ0

@ GZ0 cảm ơn vì những phản hồi tuyệt vời! Tất cả đều có ý nghĩa, và tôi đã không biết rằng list()các cuộc gọi tăng chi phí. Nên dành nhiều thời gian hơn để đọc qua các câu trả lời. Tôi sẽ chạy lại các thử nghiệm này để so sánh công bằng, tuy nhiên không đáng kể sự khác biệt có thể có.
craymichael

0

Tôi nghĩ rằng cách Pythonic nhất là sử dụng một sự hiểu biết danh sách thay vì mapfilter. Lý do là sự hiểu biết danh sách rõ ràng hơn mapfilter.

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

Như bạn thấy, một sự hiểu biết không yêu cầu lambdabiểu hiện thêm như mapnhu cầu. Hơn nữa, một sự hiểu biết cũng cho phép lọc dễ dàng, trong khi mapyêu cầu filtercho phép lọc.


0

Tôi đã thử mã bởi @ alex-martelli nhưng thấy một số khác biệt

python -mtimeit -s "xs=range(123456)" "map(hex, xs)"
1000000 loops, best of 5: 218 nsec per loop
python -mtimeit -s "xs=range(123456)" "[hex(x) for x in xs]"
10 loops, best of 5: 19.4 msec per loop

bản đồ chiếm cùng một lượng thời gian ngay cả đối với các phạm vi rất lớn trong khi sử dụng khả năng hiểu danh sách mất rất nhiều thời gian như hiển nhiên từ mã của tôi. Vì vậy, ngoài việc được coi là "unpythonic", tôi không gặp phải bất kỳ vấn đề hiệu suất nào liên quan đến việc sử dụng bản đồ.


3
Đây là một câu hỏi rất cũ và câu trả lời mà bạn đang đề cập rất có thể được viết trong tham chiếu đến Python 2, nơi maptrả về một danh sách. Trong Python 3, mapđược đánh giá một cách lười biếng, do đó, việc gọi đơn giản là mapkhông tính toán bất kỳ thành phần danh sách mới nào, do đó bạn có được thời gian ngắn như vậy.
kaya3

Tôi nghĩ rằng bạn đang sử dụng Python 3.x Khi tôi hỏi câu hỏi này Python 3 chỉ mới được phát hành gần đây và Python 2.x là rất nhiều tiêu chuẩn.
TimothyAWiseman
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.