Danh sách hiểu không có [] trong Python


85

Tham gia một danh sách:

>>> ''.join([ str(_) for _ in xrange(10) ])
'0123456789'

join phải có một lần lặp lại.

Rõ ràng, joinlập luận của là [ str(_) for _ in xrange(10) ], và đó là một danh sách dễ hiểu .

Nhìn vào cái này:

>>>''.join( str(_) for _ in xrange(10) )
'0123456789'

Bây giờ, joinlập luận của chỉ là str(_) for _ in xrange(10), không [], nhưng kết quả là như nhau.

Tại sao? Có str(_) for _ in xrange(10)cũng sản xuất một danh sách hoặc một iterable?


1
Tôi sẽ tưởng tượng rằng joinnó rất có thể được viết bằng C và do đó chạy nhanh hơn nhiều so với việc hiểu danh sách ... Thời gian kiểm tra!
Joel Cornett

Rõ ràng, tôi đọc câu hỏi của bạn hoàn toàn sai. Nó dường như được trở về một máy phát điện cho tôi ...
Joel Cornett

18
Chỉ cần lưu ý: _không có ý nghĩa đặc biệt, đó là một tên biến thông thường. Nó thường được sử dụng làm tên bỏ đi nhưng không phải vậy (bạn đang sử dụng biến). Tôi sẽ tránh sử dụng nó trong mã (ít nhất là theo cách này).
rplnt

Câu trả lời:


67
>>>''.join( str(_) for _ in xrange(10) )

Đây được gọi là biểu thức trình tạo và được giải thích trong PEP 289 .

Sự khác biệt chính giữa biểu thức trình tạo và khả năng hiểu danh sách là trước đây không tạo danh sách trong bộ nhớ.

Lưu ý rằng có một cách thứ ba để viết biểu thức:

''.join(map(str, xrange(10)))

1
Như tôi biết, một máy phát điện có thể được tạo ra thông qua một biểu thức giống như tuple, như ( str(_) for _ in xrange(10) ). Nhưng tôi đã nhầm lẫn rằng, tại sao ()có thể được giới hạn trong join, có nghĩa là, mã phải giống như `` '' .join ((str (_) for _ in xrange (10))), phải không?
Alcott

1
@Alcott Hiểu biết của tôi về các bộ giá trị là chúng thực sự được xác định bởi danh sách các biểu thức được phân tách bằng dấu phẩy chứ không phải dấu ngoặc đơn; dấu ngoặc chỉ ở đó để nhóm trực quan các giá trị trong một phép gán hoặc thực sự nhóm các giá trị nếu bộ giá trị đi vào một số danh sách được phân tách bằng dấu phẩy khác, như một lệnh gọi hàm. Điều này thường được chứng minh bằng cách chạy mã như tup = 1, 2, 3; print(tup). Với ý nghĩ đó, việc sử dụng fornhư một phần của biểu thức sẽ tạo ra trình tạo và dấu ngoặc đơn ở đó để phân biệt nó với một vòng lặp được viết sai.
Eric Ed Lohmar

132

Những người trả lời khác đã đúng khi trả lời rằng bạn đã phát hiện ra một biểu thức trình tạo (có ký hiệu tương tự như khả năng hiểu danh sách nhưng không có dấu ngoặc vuông xung quanh).

Nói chung, genxps (như chúng được biết đến một cách trìu mến) là bộ nhớ hiệu quả hơn và nhanh hơn so với việc hiểu danh sách.

TUY NHIÊN, trong trường hợp đó ''.join(), việc hiểu danh sách vừa nhanh hơn vừa hiệu quả hơn. Lý do là phép nối cần thực hiện hai lần chuyển dữ liệu, vì vậy nó thực sự cần một danh sách thực. Nếu bạn cho nó một cái, nó có thể bắt đầu công việc của nó ngay lập tức. Nếu bạn cung cấp cho nó một genxp thay thế, nó không thể bắt đầu hoạt động cho đến khi nó tạo một danh sách mới trong bộ nhớ bằng cách chạy genxp đến cạn kiệt:

~ $ python -m timeit '"".join(str(n) for n in xrange(1000))'
1000 loops, best of 3: 335 usec per loop
~ $ python -m timeit '"".join([str(n) for n in xrange(1000)])'
1000 loops, best of 3: 288 usec per loop

Kết quả tương tự khi so sánh itertools.imap với bản đồ :

~ $ python -m timeit -s'from itertools import imap' '"".join(imap(str, xrange(1000)))'
1000 loops, best of 3: 220 usec per loop
~ $ python -m timeit '"".join(map(str, xrange(1000)))'
1000 loops, best of 3: 212 usec per loop

4
@lazyr Thời gian thứ hai của bạn đang làm quá nhiều việc. Không quấn genexp quanh listcomp - chỉ cần sử dụng genexp trực tiếp. Không có gì ngạc nhiên khi bạn có thời gian kỳ lạ.
Raymond Hettinger

11
Bạn có thể giải thích tại sao ''.join()cần 2 lần vượt qua trình vòng lặp để xây dựng một chuỗi không?
ovgolovin

27
@ovgolovin Tôi đoán lần vượt qua đầu tiên là tính tổng độ dài của các chuỗi để có thể phân bổ lượng bộ nhớ chính xác cho chuỗi được nối, trong khi lần chuyển thứ hai là sao chép các chuỗi riêng lẻ vào không gian được cấp phát.
Lauritz V. Thaulow

20
@lazyr Dự đoán đó đúng. Đó là chính xác những gì str.join không :-)
Raymond hettinger

4
Đôi khi tôi thực sự bỏ lỡ khả năng "yêu thích" một câu trả lời cụ thể trên SO.
Air

5

Ví dụ thứ hai của bạn sử dụng một biểu thức trình tạo hơn là một danh sách hiểu. Sự khác biệt là với việc hiểu danh sách, một danh sách hoàn toàn được xây dựng và chuyển đến .join(). Với biểu thức trình tạo, các mục được tạo từng cái một và được tiêu thụ bởi .join(). Loại thứ hai sử dụng ít bộ nhớ hơn và thường nhanh hơn.

Khi nó xảy ra, hàm tạo danh sách sẽ vui vẻ sử dụng bất kỳ tệp nào có thể lặp lại, bao gồm cả biểu thức trình tạo. Vì thế:

[str(n) for n in xrange(10)]

chỉ là "đường cú pháp" cho:

list(str(n) for n in xrange(10))

Nói cách khác, khả năng hiểu danh sách chỉ là một biểu thức tạo được chuyển thành danh sách.


2
Bạn có chắc chúng tương đương nhau dưới mui xe không? Thời gian cho biết:: [str(x) for x in xrange(1000)]262 usec ,: list(str(x) for x in xrange(1000))304 usec.
Lauritz V. Thaulow

2
@lazyr Bạn nói đúng. Việc hiểu danh sách nhanh hơn. Và đây là lý do tại sao danh sách dễ hiểu bị rò rỉ trong Python 2.x. Đây là những gì GVR đã viết: "" Đây là một tạo tác của việc triển khai ban đầu của việc hiểu danh sách; nó là một trong những "bí mật nhỏ bẩn thỉu" của Python trong nhiều năm. Nó bắt đầu như một sự thỏa hiệp có chủ đích để làm cho việc hiểu danh sách nhanh chóng đến chóng mặt và mặc dù nó không phải là một cạm bẫy phổ biến cho người mới bắt đầu, nhưng nó chắc chắn đôi khi khiến mọi người đau đớn. " Python-history.blogspot.com/2010/06/…
ovgolovin

3
@ovgolovin Lý do listcomp nhanh hơn là vì phép nối phải tạo một danh sách trước khi nó có thể bắt đầu hoạt động. "Rò rỉ" mà bạn đề cập đến không phải là vấn đề tốc độ - nó chỉ có nghĩa là biến cảm ứng vòng lặp được hiển thị bên ngoài listcomp.
Raymond Hettinger

1
@RaymondHettinger Vậy thì những từ này có nghĩa là gì "Nó bắt đầu như một sự thỏa hiệp có chủ ý để làm cho việc hiểu danh sách nhanh chóng đến chóng mặt "? Như tôi đã hiểu, có mối liên hệ giữa sự rò rỉ của chúng với các vấn đề về tốc độ. GVR cũng viết: "Đối với biểu thức trình tạo, chúng tôi không thể thực hiện điều này. Biểu thức trình tạo được thực hiện bằng cách sử dụng trình tạo, mà việc thực thi đòi hỏi một khung thực thi riêng biệt. Do đó, biểu thức trình tạo (đặc biệt nếu chúng lặp qua một chuỗi ngắn) kém hiệu quả hơn so với khả năng hiểu danh sách . "
ovgolovin

4
@ovgolovin Bạn đã thực hiện một bước nhảy vọt không chính xác từ chi tiết triển khai listcomp về lý do tại sao str.join thực hiện theo cách nó thực hiện. Một trong những dòng đầu tiên của mã str.join là seq = PySequence_Fast(orig, "");và đó là lý do duy nhất khiến trình vòng lặp chạy chậm hơn danh sách hoặc bộ giá trị khi gọi str.join (). Bạn có thể bắt đầu cuộc trò chuyện nếu bạn muốn thảo luận thêm về nó (Tôi là tác giả của PEP 289, người tạo ra opcode LIST_APPEND và là người đã tối ưu hóa hàm tạo list (), vì vậy tôi có một số quen thuộc với vấn đề).
Raymond Hettinger


4

Nếu nó ở dạng parens, nhưng không phải dấu ngoặc, về mặt kỹ thuật thì nó là một biểu thức máy phát. Biểu thức trình tạo lần đầu tiên được giới thiệu trong Python 2.4.

http://wiki.python.org/moin/Generators

Phần sau phép nối ( str(_) for _ in xrange(10) )tự nó là một biểu thức trình tạo. Bạn có thể làm điều gì đó như:

mylist = (str(_) for _ in xrange(10))
''.join(mylist)

và nó có nghĩa chính xác như những gì bạn đã viết trong trường hợp thứ hai ở trên.

Các trình tạo có một số thuộc tính rất thú vị, đặc biệt là chúng không kết thúc việc phân bổ toàn bộ danh sách khi bạn không cần. Thay vào đó, một chức năng như tham gia "bơm" các mục ra khỏi biểu thức trình tạo tại một thời điểm, thực hiện công việc của nó trên các bộ phận trung gian nhỏ.

Trong các ví dụ cụ thể của bạn, danh sách và trình tạo có thể không hoạt động quá khác biệt, nhưng nói chung, tôi thích sử dụng các biểu thức của trình tạo (và thậm chí cả các hàm của trình tạo) bất cứ khi nào tôi có thể, chủ yếu là vì rất hiếm khi trình tạo chậm hơn danh sách đầy đủ vật chất hóa.


1

Đó là một trình tạo, chứ không phải là một danh sách hiểu. Trình tạo cũng có thể lặp lại, nhưng thay vì tạo toàn bộ danh sách trước rồi chuyển nó để tham gia, nó chuyển từng giá trị trong xrange một, có thể hiệu quả hơn nhiều.


0

Đối số cho joincuộc gọi thứ hai của bạn là biểu thức trình tạo. Nó tạo ra một tệp có thể lặp lại.

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.