Biểu thức máy phát điện so với danh sách hiểu


411

Khi nào bạn nên sử dụng biểu thức trình tạo và khi nào bạn nên sử dụng tính năng hiểu danh sách trong Python?

# Generator expression
(x*2 for x in range(256))

# List comprehension
[x*2 for x in range(256)]

27
[exp for x in iter]chỉ có thể là đường cho list((exp for x in iter))? hoặc có một sự khác biệt thực hiện?
b0fh

1
nó nghĩ rằng tôi đã có một câu hỏi có liên quan, vì vậy khi sử dụng năng suất, chúng ta có thể chỉ sử dụng biểu thức trình tạo từ một hàm hay chúng ta phải sử dụng năng suất cho một hàm để trả về đối tượng trình tạo?

28
@ b0fh Câu trả lời rất muộn cho nhận xét của bạn: trong Python2 có một sự khác biệt rất nhỏ, biến vòng lặp sẽ bị rò rỉ ra khỏi sự hiểu biết danh sách, trong khi biểu thức trình tạo sẽ không bị rò rỉ. So sánh X = [x**2 for x in range(5)]; print xvới Y = list(y**2 for y in range(5)); print y, thứ hai sẽ cho một lỗi. Trong Python3, sự hiểu biết danh sách thực sự là đường cú pháp cho một biểu thức trình tạo được cung cấp list()như bạn mong đợi, vì vậy biến vòng lặp sẽ không còn bị rò rỉ nữa .
Bas Swinckels

12
Tôi khuyên bạn nên đọc PEP 0289 . Tóm tắt bởi "PEP này giới thiệu các biểu thức của trình tạo như là một hiệu suất cao, khái quát hóa hiệu quả bộ nhớ của việc hiểu và liệt kê danh sách" . Nó cũng có các ví dụ hữu ích về thời điểm sử dụng chúng.
icc97

5
@ icc97 Tôi cũng dự tiệc muộn tám năm và liên kết PEP rất hoàn hảo. Cảm ơn đã làm cho nó dễ dàng để tìm thấy!
eenblam

Câu trả lời:


283

Câu trả lời của John là tốt (danh sách đó hiểu tốt hơn khi bạn muốn lặp đi lặp lại nhiều thứ). Tuy nhiên, cũng đáng lưu ý rằng bạn nên sử dụng một danh sách nếu bạn muốn sử dụng bất kỳ phương pháp danh sách nào. Ví dụ: đoạn mã sau sẽ không hoạt động:

def gen():
    return (something for something in get_some_stuff())

print gen()[:2]     # generators don't support indexing or slicing
print [5,6] + gen() # generators can't be added to lists

Về cơ bản, sử dụng biểu thức trình tạo nếu tất cả những gì bạn đang làm là lặp lại một lần. Nếu bạn muốn lưu trữ và sử dụng các kết quả đã tạo, thì có lẽ bạn nên tìm hiểu danh sách.

Vì hiệu suất là lý do phổ biến nhất để chọn cái này hơn cái kia, lời khuyên của tôi là đừng lo lắng về nó và chỉ chọn một cái; nếu bạn thấy rằng chương trình của bạn chạy quá chậm, thì và sau đó bạn nên quay lại và lo lắng về việc điều chỉnh mã của mình.


70
Đôi khi bạn phải sử dụng máy phát điện - ví dụ: nếu bạn đang viết coroutines với lịch trình hợp tác sử dụng năng suất. Nhưng nếu bạn đang làm điều đó, có lẽ bạn đang không hỏi câu hỏi này;)
ephemient

12
Tôi biết điều này đã cũ, nhưng tôi nghĩ rằng đáng chú ý rằng các máy phát điện (và bất kỳ lần lặp nào) có thể được thêm vào danh sách với phần mở rộng: a = [1, 2, 3] b = [4, 5, 6] a.extend(b)- bây giờ sẽ là [1, 2, 3, 4, 5, 6]. (Bạn có thể thêm dòng mới trong ý kiến ​​không?)
jarvisteve

12
@jarvisteve ví dụ của bạn tin vào những lời bạn đang nói. Cũng có một điểm tốt ở đây. Danh sách có thể được mở rộng bằng máy phát điện, nhưng sau đó không có lý do gì để biến nó thành máy phát điện. Máy phát điện không thể được mở rộng với danh sách và máy phát điện không hoàn toàn lặp lại được. a = (x for x in range(0,10)), b = [1,2,3]ví dụ. a.extend(b)ném một ngoại lệ. b.extend(a)sẽ đánh giá tất cả a, trong trường hợp đó không có điểm nào để biến nó thành máy phát điện ngay từ đầu.
Slater Victoroff

4
@SlaterTyranus bạn chính xác 100% và tôi đánh giá cao bạn về tính chính xác. tuy nhiên, tôi nghĩ rằng nhận xét của anh ấy là một câu trả lời không hữu ích cho câu hỏi của OP bởi vì nó sẽ giúp những người tìm thấy chính họ ở đây vì họ đã gõ một cái gì đó như 'kết hợp trình tạo với hiểu danh sách' vào công cụ tìm kiếm.
rbp

1
Không phải lý do sử dụng trình tạo để lặp lại một lần (ví dụ: mối quan tâm của tôi về việc thiếu bộ nhớ sẽ ghi đè lên mối quan tâm của tôi về việc "tìm nạp" từng giá trị một lần ) có thể vẫn được áp dụng khi lặp lại nhiều lần? Tôi muốn nói rằng nó có thể làm cho một danh sách hữu ích hơn, nhưng liệu điều đó có đủ để vượt qua các mối quan tâm về bộ nhớ hay không là điều khác.
Rob Grant

181

Lặp lại biểu thức trình tạo hoặc hiểu danh sách sẽ làm điều tương tự. Tuy nhiên, việc hiểu danh sách sẽ tạo toàn bộ danh sách trong bộ nhớ trước trong khi biểu thức trình tạo sẽ tạo các mục một cách nhanh chóng, do đó bạn có thể sử dụng nó cho các chuỗi rất lớn (và cả vô hạn!).


39
+1 cho vô hạn. Bạn không thể làm điều đó với một danh sách, bất kể bạn quan tâm đến hiệu suất như thế nào.
Paul Draper

Bạn có thể tạo máy phát vô hạn bằng phương pháp hiểu không?
AnnanFay

5
@Annan Chỉ khi bạn đã có quyền truy cập vào một trình tạo vô hạn khác. Ví dụ, itertools.count(n)là một chuỗi số nguyên vô hạn, bắt đầu từ n, do đó (2 ** item for item in itertools.count(n))sẽ là một chuỗi vô hạn các quyền hạn 2bắt đầu từ 2 ** n.
Kevin

2
Một trình tạo xóa các mục khỏi bộ nhớ sau khi lặp đi lặp lại. Vì vậy, nó nhanh nếu bạn có dữ liệu lớn, bạn chỉ muốn hiển thị nó, ví dụ. Nó không phải là một con heo nhớ. với các mục máy phát điện được xử lý "khi cần thiết". nếu bạn muốn bám vào danh sách hoặc lặp đi lặp lại nó (vì vậy hãy lưu trữ các mục) sau đó sử dụng danh sách hiểu.
j2emanue

102

Sử dụng hiểu danh sách khi kết quả cần phải được lặp đi lặp lại nhiều lần hoặc trong đó tốc độ là tối quan trọng. Sử dụng các biểu thức trình tạo trong đó phạm vi lớn hoặc vô hạn.

Xem biểu thức máy phát điện và danh sách hiểu để biết thêm.


2
Đây có lẽ sẽ là một chủ đề nhỏ, nhưng thật không may là "không thể hiểu được" ... "tối quan trọng" nghĩa là gì trong bối cảnh này? Tôi không phải là người nói tiếng Anh bản địa ... :)
Guillermo Ares

6
@GuillermoAres đây là kết quả trực tiếp của "googling" với ý nghĩa tối quan trọng: quan trọng hơn bất cứ điều gì khác; tối cao.
Sнаđошƒаӽ

1
Vì vậy, listscó nhanh hơn generatorbiểu thức? Từ việc đọc câu trả lời của dF, người ta nhận ra rằng đó là cách khác.
Hassan Baig

1
Có lẽ tốt hơn để nói rằng việc hiểu danh sách sẽ nhanh hơn khi phạm vi nhỏ, nhưng khi thang đo tăng lên, việc tính toán các giá trị một cách nhanh chóng - đúng lúc để sử dụng chúng trở nên có giá trị hơn. Đó là những gì một biểu thức máy phát điện làm.
Kyle

59

Điểm quan trọng là việc hiểu danh sách tạo ra một danh sách mới. Trình tạo tạo một đối tượng có thể lặp lại sẽ "lọc" vật liệu nguồn đang hoạt động khi bạn tiêu thụ các bit.

Hãy tưởng tượng bạn có một tệp nhật ký 2TB có tên là "hugefile.txt" và bạn muốn nội dung và độ dài cho tất cả các dòng bắt đầu bằng từ "ENTRY".

Vì vậy, bạn hãy thử bắt đầu bằng cách viết một danh sách hiểu:

logfile = open("hugefile.txt","r")
entry_lines = [(line,len(line)) for line in logfile if line.startswith("ENTRY")]

Điều này làm mờ toàn bộ tệp, xử lý từng dòng và lưu trữ các dòng khớp trong mảng của bạn. Do đó, mảng này có thể chứa tới 2TB nội dung. Đó là rất nhiều RAM, và có lẽ không thực tế cho mục đích của bạn.

Vì vậy, thay vào đó chúng ta có thể sử dụng trình tạo để áp dụng "bộ lọc" cho nội dung của mình. Không có dữ liệu thực sự được đọc cho đến khi chúng tôi bắt đầu lặp lại kết quả.

logfile = open("hugefile.txt","r")
entry_lines = ((line,len(line)) for line in logfile if line.startswith("ENTRY"))

Thậm chí không có một dòng nào được đọc từ tệp của chúng tôi. Trong thực tế, giả sử chúng tôi muốn lọc kết quả của chúng tôi hơn nữa:

long_entries = ((line,length) for (line,length) in entry_lines if length > 80)

Vẫn chưa có gì được đọc, nhưng chúng tôi đã chỉ định hai trình tạo sẽ hoạt động trên dữ liệu của chúng tôi như chúng tôi muốn.

Hãy viết ra các dòng được lọc của chúng tôi vào một tệp khác:

outfile = open("filtered.txt","a")
for entry,length in long_entries:
    outfile.write(entry)

Bây giờ chúng tôi đọc các tập tin đầu vào. Khi forvòng lặp của chúng tôi tiếp tục yêu cầu các dòng bổ sung, trình long_entriestạo yêu cầu các dòng từ trình entry_linestạo, chỉ trả về những dòng có độ dài lớn hơn 80 ký tự. Và lần lượt, trình entry_linestạo yêu cầu các dòng (được lọc như được chỉ định) từ logfileiterator, lần lượt đọc tệp.

Vì vậy, thay vì "đẩy" dữ liệu đến chức năng đầu ra của bạn dưới dạng danh sách được điền đầy đủ, bạn sẽ cung cấp cho chức năng đầu ra một cách để "kéo" dữ liệu khi cần. Đây là trường hợp của chúng tôi hiệu quả hơn nhiều, nhưng không hoàn toàn linh hoạt. Máy phát điện là một chiều, một lượt; dữ liệu từ tệp nhật ký chúng tôi đã đọc sẽ bị loại bỏ ngay lập tức, vì vậy chúng tôi không thể quay lại dòng trước đó. Mặt khác, chúng tôi không phải lo lắng về việc giữ dữ liệu xung quanh một khi chúng tôi đã hoàn thành việc đó.


46

Lợi ích của biểu thức trình tạo là nó sử dụng ít bộ nhớ hơn vì nó không tạo toàn bộ danh sách cùng một lúc. Các biểu thức của trình tạo được sử dụng tốt nhất khi danh sách là một trung gian, chẳng hạn như tổng hợp các kết quả hoặc tạo ra một lệnh ra khỏi kết quả.

Ví dụ:

sum(x*2 for x in xrange(256))

dict( (k, some_func(k)) for k in some_list_of_keys )

Ưu điểm ở đây là danh sách không được tạo hoàn toàn và do đó ít bộ nhớ được sử dụng (và cũng nên nhanh hơn)

Tuy nhiên, bạn nên sử dụng danh sách hiểu khi sản phẩm cuối cùng mong muốn là một danh sách. Bạn sẽ không lưu bất kỳ memeory nào bằng các biểu thức của trình tạo, vì bạn muốn danh sách được tạo. Bạn cũng nhận được lợi ích của việc có thể sử dụng bất kỳ chức năng nào trong danh sách như được sắp xếp hoặc đảo ngược.

Ví dụ:

reversed( [x*2 for x in xrange(256)] )

9
Có một gợi ý đặt ra cho bạn ngay trong ngôn ngữ rằng các biểu thức của trình tạo được sử dụng theo cách đó. Mất dấu ngoặc! sum(x*2 for x in xrange(256))
u0b34a0f6ae

8
sortedreversedhoạt động tốt trên bất kỳ biểu thức lặp, trình tạo bao gồm.
marr75

1
Nếu bạn có thể sử dụng 2.7 trở lên, ví dụ dict () đó sẽ trông tốt hơn khi hiểu chính tả (PEP cho cái đó cũ hơn so với trình tạo PEP, nhưng mất nhiều thời gian hơn để hạ cánh)
Jürgen A. Erhard

14

Khi tạo trình tạo từ một đối tượng có thể thay đổi (như danh sách), hãy lưu ý rằng trình tạo sẽ được đánh giá theo trạng thái của danh sách tại thời điểm sử dụng trình tạo, chứ không phải tại thời điểm tạo trình tạo:

>>> mylist = ["a", "b", "c"]
>>> gen = (elem + "1" for elem in mylist)
>>> mylist.clear()
>>> for x in gen: print (x)
# nothing

Nếu có bất kỳ cơ hội nào danh sách của bạn bị sửa đổi (hoặc một đối tượng có thể thay đổi trong danh sách đó) nhưng bạn cần trạng thái khi tạo trình tạo, bạn cần sử dụng cách hiểu danh sách thay thế.


1
Và đây nên là câu trả lời được chấp nhận. Nếu dữ liệu của bạn lớn hơn bộ nhớ khả dụng, bạn nên luôn sử dụng trình tạo mặc dù việc lặp qua danh sách trong bộ nhớ có thể nhanh hơn (nhưng bạn không có đủ bộ nhớ để làm như vậy).
Marek Marczak

4

Đôi khi bạn có thể thoát khỏi chức năng tee từ itertools , nó trả về nhiều trình lặp cho cùng một trình tạo có thể được sử dụng độc lập.


4

Tôi đang sử dụng mô-đun Hadoop Mincemeat . Tôi nghĩ rằng đây là một ví dụ tuyệt vời để ghi chú về:

import mincemeat

def mapfn(k,v):
    for w in v:
        yield 'sum',w
        #yield 'count',1


def reducefn(k,v): 
    r1=sum(v)
    r2=len(v)
    print r2
    m=r1/r2
    std=0
    for i in range(r2):
       std+=pow(abs(v[i]-m),2)  
    res=pow((std/r2),0.5)
    return r1,r2,res

Tại đây, trình tạo sẽ lấy các số trong tệp văn bản (lớn nhất là 15 GB) và áp dụng phép toán đơn giản cho các số đó bằng cách sử dụng bản đồ giảm của Hadoop. Nếu tôi không sử dụng hàm sản lượng, nhưng thay vào đó là việc hiểu danh sách, thì sẽ mất nhiều thời gian hơn để tính tổng và trung bình (không đề cập đến độ phức tạp của không gian).

Hadoop là một ví dụ tuyệt vời cho việc sử dụng tất cả các lợi thế của Máy phát điện.

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.