Tại sao bản đồ trả về một đối tượng bản đồ thay vì một danh sách trong Python 3?


82

Tôi đang quan tâm trong việc tìm hiểu ngôn ngữ thiết kế mới của Python 3.x .

Tôi thực sự thích, trong Python 2.7, hàm map:

Python 2.7.12
In[2]: map(lambda x: x+1, [1,2,3])
Out[2]: [2, 3, 4]

Tuy nhiên, trong Python 3.x mọi thứ đã thay đổi:

Python 3.5.1
In[2]: map(lambda x: x+1, [1,2,3])
Out[2]: <map at 0x4218390>

Tôi hiểu cách làm, nhưng tôi không thể tìm thấy tham chiếu đến lý do tại sao. Tại sao các nhà thiết kế ngôn ngữ lại đưa ra lựa chọn này, theo tôi, điều này gây ra rất nhiều đau đớn. Điều này có phải là để các nhà phát triển vật lộn trong việc bám sát vào việc hiểu danh sách không?

IMO, danh sách có thể được coi là Functors ; và bằng cách nào đó tôi đã được cho là nghĩ theo cách này:

fmap :: (a -> b) -> f a -> f b

2
Cơ sở lý luận cũng giống như lý do tại sao chúng ta sử dụng trình tạo thay vì hiểu danh sách. Bằng cách sử dụng đánh giá lười biếng, chúng ta không cần phải lưu giữ những thứ khổng lồ trong bộ nhớ. Kiểm tra câu trả lời được chấp nhận tại đây: stackoverflow.com/questions/1303347/…
Moberg

8
Bạn có thể giải thích lý do tại sao điều này mang lại cho bạn "rất nhiều đau đớn"?
RemcoGerlich

3
Tôi nghĩ đó là do nhiều năm sử dụng cho thấy rằng hầu hết các cách sử dụng phổ biến của mapchỉ đơn giản được lặp lại trên kết quả. Việc xây dựng một danh sách khi bạn không cần sẽ không hiệu quả nên các nhà phát triển quyết định làm mapbiếng. Có rất nhiều thứ để đạt được ở đây cho hiệu suất và không có nhiều thứ để mất (Nếu bạn cần một danh sách, chỉ cần yêu cầu một ... list(map(...))).
mgilson

3
Được rồi, tôi thấy thú vị là thay vì giữ nguyên mô hình Functor và cung cấp một phiên bản Danh sách lười biếng, bằng cách nào đó, họ đã đưa ra quyết định buộc đánh giá lười biếng đối với một danh sách bất cứ khi nào nó được ánh xạ. Tôi muốn có quyền đưa ra lựa chọn của riêng mình, hay còn gọi là Trình tạo -> bản đồ -> Trình tạo hoặc Danh sách -> bản đồ -> Danh sách (do tôi quyết định)
NoIdeaHowToFixThis 13/10/16

4
@NoIdeaHowToFixThis, thực sự là tùy thuộc vào bạn, nếu bạn cần toàn bộ danh sách, chỉ cần chuyển đổi nó vào một danh sách, dễ dàng như địa ngục
Netwave

Câu trả lời:


37

Tôi nghĩ rằng lý do tại sao bản đồ vẫn tồn tại khi các biểu thức trình tạo cũng tồn tại, là nó có thể nhận nhiều đối số của trình lặp được lặp lại và chuyển vào hàm:

>>> list(map(min, [1,2,3,4], [0,10,0,10]))
[0,2,0,4]

Điều đó dễ dàng hơn một chút so với sử dụng zip:

>>> list(min(x, y) for x, y in zip([1,2,3,4], [0,10,0,10]))

Nếu không, nó chỉ đơn giản là không thêm bất cứ thứ gì qua các biểu thức của trình tạo.


1
Tôi nghĩ rằng nếu chúng ta thêm mong muốn nhấn mạnh rằng khả năng hiểu danh sách là khó hiểu hơn và các nhà thiết kế ngôn ngữ muốn nhấn mạnh rằng, đây là câu trả lời đúng chỗ nhất, tôi nghĩ. @vishes_shell bằng cách nào đó không tập trung đủ vào thiết kế ngôn ngữ.
NoIdeaHowToFixThis 13/10/16

2
Tạo ra các kết quả khác nhau trong Python 2 và 3 nếu hai danh sách không có độ dài bằng nhau . Hãy thử c = list(map(max, [1,2,3,4], [0,10,0,10, 99]))trong python 2 và trong python 3.
cdarke

1
Đây là tài liệu tham khảo cho kế hoạch ban đầu để xóa hoàn toàn bản đồ khỏi python3: Artima.com/weblogs/viewpost.jsp?thread=98196
Bernhard

Hmm thật kỳ lạ khi tôi đặt bản đồ trong danh sách, tôi nhận được một danh sách gồm 1 danh sách phần tử.
awiebe,

24

Bởi vì nó trả về một trình lặp, nó bỏ qua việc lưu trữ danh sách kích thước đầy đủ trong bộ nhớ. Vì vậy, bạn có thể dễ dàng lặp lại nó trong tương lai mà không gây đau đớn cho bộ nhớ. Có thể bạn thậm chí không cần một danh sách đầy đủ, nhưng một phần của nó, cho đến khi đạt được điều kiện của bạn.

Bạn có thể thấy tài liệu này hữu ích, trình vòng lặp thật tuyệt vời.

Một đối tượng đại diện cho một luồng dữ liệu. Các lệnh gọi lặp đi lặp lại đến __next__()phương thức của trình lặp (hoặc chuyển nó tới hàm tích hợp next()) trả về các mục kế tiếp trong luồng. StopIterationThay vào đó, khi không còn dữ liệu nào nữa, một ngoại lệ sẽ được đưa ra. Tại thời điểm này, đối tượng vòng lặp đã hết và bất kỳ lệnh gọi nào khác đến __next__()phương thức của nó chỉ cần nâng lên StopIterationmột lần nữa. Các trình lặp bắt buộc phải có một __iter__()phương thức trả về chính đối tượng trình vòng lặp để mọi trình lặp cũng có thể lặp lại và có thể được sử dụng ở hầu hết những nơi chấp nhận các trình lặp khác. Một ngoại lệ đáng chú ý là mã cố gắng vượt qua nhiều lần lặp lại. Một đối tượng vùng chứa (chẳng hạn như a list) tạo ra một trình vòng lặp mới mỗi khi bạn chuyển nó đếniter()hoặc sử dụng nó trong vòng lặp for. Cố gắng điều này với một trình vòng lặp sẽ chỉ trả về cùng một đối tượng trình vòng lặp đã cạn kiệt được sử dụng trong lần vượt qua vòng lặp trước đó, làm cho nó xuất hiện giống như một vùng chứa trống.


14

Guido trả lời câu hỏi này ở đây : " vì việc tạo một danh sách sẽ rất lãng phí ".

Ông cũng nói rằng phép biến đổi chính xác là sử dụng một forvòng lặp thông thường .

Việc chuyển đổi map()từ 2 thành 3 có thể không chỉ là một trường hợp đơn giản khi gắn bó với list( )nó. Guido cũng nói:

"Nếu các chuỗi đầu vào không có độ dài bằng nhau, map()sẽ dừng lại ở phần cuối của chuỗi ngắn nhất. Để tương thích hoàn toàn với map()từ Python 2.x, hãy quấn các chuỗi vào itertools.zip_longest(), ví dụ:

map(func, *sequences)

trở thành

list(map(func, itertools.zip_longest(*sequences)))

"


3
Nhận xét của Guido là về map()các tác dụng phụ của chức năng , không phải về việc sử dụng nó như một trò chơi.
abukaj

4
Phép biến hình với zip_longestlà sai. bạn phải sử dụng itertools.starmapcho nó là tương đương: list(starmap(func, zip_longest(*sequences))). Đó là bởi vì zip_longesttạo ra các bộ giá trị, vì vậy hàm funcsẽ nhận một nđối số đơn lẻ thay vì các nđối số riêng biệt như trường hợp gọi map(func, *sequences).
Bakuriu

12

Trong Python 3 nhiều chức năng (không chỉ là mapnhưng zip, rangevà những người khác) trả về một iterator chứ không phải là danh sách đầy đủ. Bạn có thể muốn một trình lặp (ví dụ để tránh giữ toàn bộ danh sách trong bộ nhớ) hoặc bạn có thể muốn một danh sách (ví dụ: có thể lập chỉ mục).

Tuy nhiên, tôi nghĩ lý do chính cho sự thay đổi trong Python 3 là mặc dù việc chuyển đổi một trình lặp thành một danh sách bằng cách sử dụng list(some_iterator)tương đương ngược lại là điều tầm thường iter(some_list)nhưng lại không đạt được kết quả mong muốn vì danh sách đầy đủ đã được tạo và lưu giữ trong bộ nhớ.

Ví dụ, trong Python 3 list(range(n))hoạt động tốt vì có rất ít chi phí để xây dựng rangeđối tượng và sau đó chuyển đổi nó thành một danh sách. Tuy nhiên, trong Python 2 iter(range(n))không lưu bất kỳ bộ nhớ nào vì danh sách đầy đủ được xây dựng range()trước khi trình lặp được tạo.

Do đó, trong Python 2, các hàm riêng biệt được yêu cầu để tạo một trình lặp thay vì một danh sách, chẳng hạn như imapfor map(mặc dù chúng không hoàn toàn tương đương ), xrangefor range, izipfor zip. Ngược lại, Python 3 chỉ yêu cầu một hàm duy nhất khi một list()cuộc gọi tạo ra danh sách đầy đủ nếu được yêu cầu.


AFAIK trong Python 2.7 cũng có các hàm từ các itertoolstrình vòng lặp trả về. Ngoài ra, tôi sẽ không thấy trình vòng lặp là danh sách lười biếng, vì danh sách có thể được lặp lại nhiều lần và được truy cập ngẫu nhiên.
abukaj

@abukaj ok cảm ơn, tôi đã chỉnh sửa câu trả lời của tôi để cố gắng được rõ ràng hơn
Chris_Rands

@IgorRivin ý bạn là gì? Các mapđối tượng Python 3 có một next()phương thức. Python 3 rangeđối tượng phạm vi không đúng lặp Tôi biết
Chris_Rands

@Chris_Rands trong python phân phối Anaconda 3.6.2 của tôi, foo = map(lambda x: x, [1, 2, 3])trả về một đối tượng bản đồ foo. làm foo.next()trở lại với một lỗi:'map' object has no attribute 'next'
Igor Rivin

1
@IgorRivin: Các phương thức bắt đầu và kết thúc bằng __được dành riêng cho Python; nếu không có bảo lưu đó, bạn sẽ gặp vấn đề khi phân biệt những thứ nextchỉ là một phương thức (chúng không thực sự là trình vòng lặp) và những thứ là trình vòng lặp. Trong thực tế, bạn nên bỏ qua các phương pháp và chỉ sử dụng next()hàm (ví dụ next(foo)), hàm này hoạt động bình thường trên mọi phiên bản Python từ 2.6 trở đi. Nó giống như cách bạn sử dụng len(foo)mặc dù foo.__len__()sẽ hoạt động tốt; các phương thức dunder thường không được gọi trực tiếp, nhưng ngầm hiểu là một phần của một số hoạt động khác.
ShadowRanger
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.