Khi nào không phải là thời điểm thích hợp để sử dụng trình tạo python?


83

Điều này thay vì ngược lại Bạn có thể sử dụng các hàm của trình tạo Python để làm gì? : trình tạo python, biểu thức trình tạo và itertoolsmô-đun là một số tính năng yêu thích của tôi về python ngày nay. Chúng đặc biệt hữu ích khi thiết lập chuỗi hoạt động để thực hiện trên một đống dữ liệu lớn - tôi thường sử dụng chúng khi xử lý tệp DSV.

Vì vậy, khi nào không phải là thời điểm tốt để sử dụng máy phát điện, hoặc biểu thức trình tạo, hoặc một itertoolshàm?

  • Khi nào tôi nên thích zip()hơn itertools.izip(), hoặc
  • range()hết xrange(), hoặc
  • [x for x in foo]hết (x for x in foo)?

Rõ ràng, cuối cùng chúng ta cần "giải quyết" một trình tạo thành dữ liệu thực tế, thường bằng cách tạo một danh sách hoặc lặp lại nó với một vòng lặp không phải trình tạo. Đôi khi chúng ta chỉ cần biết chiều dài. Đây không phải là điều tôi đang hỏi.

Chúng tôi sử dụng trình tạo để không gán danh sách mới vào bộ nhớ cho dữ liệu tạm thời. Điều này đặc biệt có ý nghĩa đối với các tập dữ liệu lớn. Nó cũng có ý nghĩa đối với các tập dữ liệu nhỏ? Có sự đánh đổi bộ nhớ / cpu đáng chú ý không?

Tôi đặc biệt quan tâm nếu ai đó đã thực hiện một số hồ sơ về điều này, dựa trên cuộc thảo luận mở rộng về hiệu suất hiểu danh sách so với map () và filter () . ( liên kết thay thế )


2
Tôi đã đặt ra một câu hỏi tương tự ở đây và thực hiện một số phân tích để thấy rằng trong các danh sách <5ví dụ cụ thể của tôi nhanh hơn đối với các đoạn có độ dài lặp lại .
Alexander McFarlane

Điều này có trả lời câu hỏi của bạn không? Biểu thức của Trình tạo so với Hiểu danh sách
ggorlen

Câu trả lời:


57

Sử dụng danh sách thay vì trình tạo khi:

1) Bạn cần truy cập dữ liệu nhiều lần (tức là vào bộ nhớ cache kết quả thay vì tính toán lại chúng):

for i in outer:           # used once, okay to be a generator or return a list
    for j in inner:       # used multiple times, reusing a list is better
         ...

2) Bạn cần quyền truy cập ngẫu nhiên (hoặc bất kỳ quyền truy cập nào ngoài thứ tự tuần tự chuyển tiếp):

for i in reversed(data): ...     # generators aren't reversible

s[i], s[j] = s[j], s[i]          # generators aren't indexable

3) Bạn cần nối các chuỗi (yêu cầu hai lần chuyển dữ liệu):

s = ''.join(data)                # lists are faster than generators in this use case

4) Bạn đang sử dụng PyPy mà đôi khi không thể tối ưu hóa mã trình tạo càng nhiều càng tốt với các lệnh gọi hàm thông thường và thao tác danh sách.


Đối với # 3, bạn không thể tránh được hai lần vượt qua bằng cách sử dụng ireduceđể sao chép tham gia?
Platinum Azure

Cảm ơn! Tôi không biết về hành vi nối chuỗi. Bạn có thể cung cấp hoặc liên kết đến giải thích tại sao nó yêu cầu hai lần vượt qua không?
David Eyk

5
@DavidEyk str.join thực hiện một lần vượt qua để cộng độ dài của tất cả các đoạn chuỗi để nó biết nhiều bộ nhớ để phân bổ cho kết quả cuối cùng được kết hợp. Truyền thứ hai sao chép các đoạn chuỗi vào trong bộ đệm mới để tạo một chuỗi mới. Xem hg.python.org/cpython/file/82fd95c2851b/Objects/stringlib/…
Raymond Hettinger

1
Thật thú vị, tôi rất thường xuyên sử dụng máy phát điện để nối các chuỗi. Nhưng, tôi tự hỏi, làm thế nào nó hoạt động nếu nó cần hai lần? chẳng hạn''.join('%s' % i for i in xrange(10))
bgusach

4
@ ikaros45 Nếu đầu vào để tham gia không phải là danh sách, nó phải thực hiện thêm công việc để tạo danh sách tạm thời cho hai lần vượt qua. Đại khái this `` data = data if isinstance (dữ liệu, danh sách) else list (dữ liệu); n = sum (map (len, data)); đệm = bytearray (n); ... <sao chép các đoạn vào bộ đệm> `` '.
Raymond Hettinger

40

Nói chung, không sử dụng trình tạo khi bạn cần các hoạt động danh sách, như len (), đảo ngược (), v.v.

Cũng có thể có những lúc bạn không muốn đánh giá lười biếng (ví dụ: thực hiện tất cả các phép tính trước để bạn có thể giải phóng tài nguyên). Trong trường hợp đó, biểu thức danh sách có thể tốt hơn.


25
Ngoài ra, việc thực hiện tất cả các phép tính trước đảm bảo rằng nếu việc tính toán các phần tử trong danh sách ném ra một ngoại lệ, nó sẽ được ném vào điểm mà danh sách được tạo , không phải trong vòng lặp sau đó lặp lại nó. Nếu bạn cần đảm bảo xử lý toàn bộ danh sách không có lỗi trước khi tiếp tục, thì trình tạo không tốt.
Ryan C. Thompson

4
Đó là một điểm hay. Thật khó chịu khi đang xử lý một máy phát điện được nửa chừng thì mọi thứ phát nổ. Nó có thể nguy hiểm.
David Eyk

26

Hồ sơ, Hồ sơ, Hồ sơ.

Lập hồ sơ mã của bạn là cách duy nhất để biết liệu những gì bạn đang làm có ảnh hưởng gì không.

Hầu hết cách sử dụng xrange, máy phát điện, v.v. đều vượt quá kích thước tĩnh, tập dữ liệu nhỏ. Chỉ khi bạn truy cập vào bộ dữ liệu lớn thì nó mới thực sự tạo ra sự khác biệt. range () so với xrange () chủ yếu chỉ là vấn đề làm cho mã trông xấu xí hơn một chút, và không mất gì cả và có thể đạt được thứ gì đó.

Hồ sơ, Hồ sơ, Hồ sơ.


1
Hồ sơ, thực sự. Một trong những ngày này, tôi sẽ thử và làm một phép so sánh thực nghiệm. Cho đến lúc đó, tôi chỉ hy vọng người khác đã có. :)
David Eyk 29/10/08

Hồ sơ, Hồ sơ, Hồ sơ. Tôi hoàn toàn đồng ý. Hồ sơ, Hồ sơ, Hồ sơ.
Jeppe

17

Bạn không bao giờ nên ưu tiên ziphơn izip, rangehơn xrange, hoặc liệt kê các phần hiểu hơn các phần hiểu của trình tạo. Trong Python 3.0 rangexrangengữ nghĩa giống và zipizipngữ nghĩa giống.

Việc hiểu danh sách thực sự rõ ràng hơn giống như list(frob(x) for x in foo)những lúc bạn cần một danh sách thực tế.


3
@Steven Tôi không đồng ý, nhưng tôi tự hỏi lý do đằng sau câu trả lời của bạn là gì. Tại sao các hiểu biết về zip, phạm vi và danh sách không bao giờ được ưa chuộng hơn phiên bản "lười biếng" tương ứng ??
mhawke

bởi vì, như ông đã nói, hành vi cũ của zip và phạm vi sẽ sớm biến mất.

@Steven: Điểm tốt. Tôi đã quên về những thay đổi này trong 3.0, điều đó có thể có nghĩa là ai đó ở trên đó bị thuyết phục về tính ưu việt chung của họ. Re: Liệt kê các điều dễ hiểu, chúng thường rõ ràng hơn (và nhanh hơn forcác vòng lặp mở rộng !), Nhưng người ta có thể dễ dàng viết các danh sách khó hiểu.
David Eyk 29/10/08

9
Tôi hiểu ý của bạn, nhưng tôi thấy []biểu mẫu đủ mô tả (và ngắn gọn hơn, nói chung là ít lộn xộn hơn). Nhưng đây chỉ là vấn đề thị hiếu.
David Eyk 30/10/08

4
Các thao tác với danh sách nhanh hơn đối với kích thước dữ liệu nhỏ, nhưng mọi thứ đều nhanh khi kích thước dữ liệu nhỏ, vì vậy bạn nên luôn ưu tiên trình tạo trừ khi bạn có lý do cụ thể để sử dụng danh sách (vì những lý do đó, hãy xem câu trả lời của Ryan Ginstrom).
Ryan C. Thompson

7

Khi bạn đề cập, "Điều này đặc biệt có ý nghĩa đối với các bộ dữ liệu lớn", tôi nghĩ điều này trả lời câu hỏi của bạn.

Nếu bạn không gặp bất kỳ bức tường nào, về mặt hiệu suất, bạn vẫn có thể bám vào danh sách và các chức năng tiêu chuẩn. Sau đó, khi bạn gặp vấn đề với hiệu suất, hãy chuyển đổi.

Tuy nhiên, như đã đề cập bởi @ u0b34a0f6ae trong phần nhận xét, việc sử dụng trình tạo khi bắt đầu có thể giúp bạn dễ dàng mở rộng quy mô thành tập dữ liệu lớn hơn.


5
Trình tạo +1 giúp mã của bạn sẵn sàng hơn cho các bộ dữ liệu lớn mà bạn không cần phải đoán trước.
u0b34a0f6ae

6

Về hiệu suất: nếu sử dụng psyco, danh sách có thể nhanh hơn một chút so với máy phát điện. Trong ví dụ dưới đây, danh sách nhanh hơn gần như 50% khi sử dụng psyco.full ()

import psyco
import time
import cStringIO

def time_func(func):
    """The amount of time it requires func to run"""
    start = time.clock()
    func()
    return time.clock() - start

def fizzbuzz(num):
    """That algorithm we all know and love"""
    if not num % 3 and not num % 5:
        return "%d fizz buzz" % num
    elif not num % 3:
        return "%d fizz" % num
    elif not num % 5:
        return "%d buzz" % num
    return None

def with_list(num):
    """Try getting fizzbuzz with a list comprehension and range"""
    out = cStringIO.StringIO()
    for fibby in [fizzbuzz(x) for x in range(1, num) if fizzbuzz(x)]:
        print >> out, fibby
    return out.getvalue()

def with_genx(num):
    """Try getting fizzbuzz with generator expression and xrange"""
    out = cStringIO.StringIO()
    for fibby in (fizzbuzz(x) for x in xrange(1, num) if fizzbuzz(x)):
        print >> out, fibby
    return out.getvalue()

def main():
    """
    Test speed of generator expressions versus list comprehensions,
    with and without psyco.
    """

    #our variables
    nums = [10000, 100000]
    funcs = [with_list, with_genx]

    #  try without psyco 1st
    print "without psyco"
    for num in nums:
        print "  number:", num
        for func in funcs:
            print func.__name__, time_func(lambda : func(num)), "seconds"
        print

    #  now with psyco
    print "with psyco"
    psyco.full()
    for num in nums:
        print "  number:", num
        for func in funcs:
            print func.__name__, time_func(lambda : func(num)), "seconds"
        print

if __name__ == "__main__":
    main()

Các kết quả:

without psyco
  number: 10000
with_list 0.0519102208309 seconds
with_genx 0.0535933367509 seconds

  number: 100000
with_list 0.542204280744 seconds
with_genx 0.557837353115 seconds

with psyco
  number: 10000
with_list 0.0286369007033 seconds
with_genx 0.0513424889137 seconds

  number: 100000
with_list 0.335414877839 seconds
with_genx 0.580363490491 seconds

1
Đó là bởi vì psyco hoàn toàn không tăng tốc máy phát điện, nên đó là một thiếu sót của psyco hơn là của máy phát điện. Câu trả lời tốt, mặc dù.
Steven Huwig

4
Ngoài ra, psyco hiện nay khá nhiều. Tất cả các nhà phát triển đang dành thời gian cho PyPy's JIT, công cụ tối ưu hóa máy phát điện theo hiểu biết tốt nhất của tôi.
Noufal Ibrahim,

3

Về hiệu suất có liên quan, tôi không thể nghĩ đến bất kỳ lúc nào bạn muốn sử dụng danh sách trên trình tạo.


all(True for _ in range(10 ** 8))chậm hơn all([True for _ in range(10 ** 8)])trong Python 3.8. Tôi thích một danh sách hơn một máy phát điện ở đây
ggorlen

3

Tôi chưa bao giờ tìm thấy tình huống mà máy phát điện sẽ cản trở những gì bạn đang cố gắng làm. Tuy nhiên, có rất nhiều trường hợp sử dụng máy phát điện sẽ không giúp ích gì cho bạn hơn là không sử dụng chúng.

Ví dụ:

sorted(xrange(5))

Không cung cấp bất kỳ cải tiến nào so với:

sorted(range(5))

4
Cả hai đều không cung cấp bất kỳ cải tiến nào range(5), vì danh sách kết quả đã được sắp xếp.
dan04

3

Bạn nên thích danh sách dễ hiểu hơn nếu bạn cần giữ các giá trị xung quanh cho một thứ khác sau này và kích thước tập hợp của bạn không quá lớn.

Ví dụ: bạn đang tạo một danh sách mà bạn sẽ lặp lại nhiều lần sau đó trong chương trình của mình.

Ở một mức độ nào đó, bạn có thể nghĩ về trình tạo như một sự thay thế cho lặp (vòng lặp) so với hiểu danh sách như một kiểu khởi tạo cấu trúc dữ liệu. Nếu bạn muốn giữ nguyên cấu trúc dữ liệu thì hãy sử dụng cách hiểu danh sách.


Nếu bạn chỉ cần hạn chế nhìn trước / nhìn sau trên luồng, thì có itertools.tee()thể giúp bạn. Nhưng nói chung, nếu bạn muốn có nhiều hơn một lần truy cập hoặc truy cập ngẫu nhiên vào một số dữ liệu trung gian, hãy tạo một danh sách / tập hợp / mệnh lệnh của nó.
Beni Cherniavsky-Paskin
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.