Có phải Pythonic sử dụng cách hiểu danh sách cho các tác dụng phụ không?


108

Hãy nghĩ về một chức năng mà tôi đang gọi vì các tác dụng phụ của nó chứ không phải trả về giá trị (như in ra màn hình, cập nhật GUI, in ra tệp, v.v.).

def fun_with_side_effects(x):
    ...side effects...
    return y

Bây giờ, liệu Pythonic có sử dụng cách hiểu danh sách để gọi hàm này không:

[fun_with_side_effects(x) for x in y if (...conditions...)]

Lưu ý rằng tôi không lưu danh sách ở bất kỳ đâu

Hay tôi nên gọi đây là func như thế này:

for x in y:
    if (...conditions...):
        fun_with_side_effects(x)

Đó là tốt hơn và tại sao?


6
đây là ranh giới, nhưng có thể bạn sẽ bị phản đối nhiều hơn là ủng hộ. Tôi sẽ ngồi cái này ra: ^)
jcomeau_ictx

6
Đây là một sự lựa chọn dễ dàng. Tính khả năng đọc - hãy làm theo cách thứ hai. Nếu bạn không thể phù hợp với 2 dòng phụ trên màn hình của bạn có được một màn hình lớn hơn :)
John La Rooy

1
Khả năng hiểu danh sách là không chính xác vì nó vi phạm "rõ ràng tốt hơn ẩn" - bạn đang ẩn một vòng lặp trong một cấu trúc khác.
Fred Foo

3
@larsmans: giá như GvR nhận ra điều đó khi anh ấy giới thiệu khả năng hiểu danh sách ngay từ đầu!
Steve Jessop

2
@larsmans, Steve Jessop, tôi nghĩ rằng việc hiểu danh sách là một vòng lặp là không chính xác. Nó có thể được triển khai dưới dạng một vòng lặp, nhưng điểm của các cấu trúc như thế này là hoạt động trên dữ liệu tổng hợp theo cách song song về mặt chức năng và (về mặt khái niệm). Nếu có vấn đề với cú pháp, nó for ... inđược sử dụng trong cả hai trường hợp - dẫn đến những câu hỏi như thế này!
gửi

Câu trả lời:


84

Làm như vậy là rất chống lại Pythonic, và bất kỳ Pythonista dày dạn nào cũng sẽ khiến bạn phải chịu đựng nó. Danh sách trung gian sẽ bị loại bỏ sau khi nó được tạo và nó có thể rất rất lớn và do đó tốn kém để tạo.


5
Vì vậy, những gì sẽ là một cách trăn trở hơn?
Joachim Sauer

6
Một trong những không giữ danh sách xung quanh; tức là một số biến thể của cách thứ hai (tôi đã được biết là sử dụng genx fortrước đây, để loại bỏ if).
Ignacio Vazquez-Abrams

6
@Joachim Sauer: Ví dụ 2 ở trên. Một vòng lặp thích hợp, rõ ràng, không thuộc danh sách-hiểu. Rõ ràng. Thông thoáng. Rõ ràng.
S.Lott

31

Bạn không nên sử dụng khả năng hiểu danh sách , vì như mọi người đã nói, điều đó sẽ tạo ra một danh sách tạm thời lớn mà bạn không cần. Hai phương pháp sau là tương đương:

consume(side_effects(x) for x in xs)

for x in xs:
    side_effects(x)

với định nghĩa của consumefrom the itertoolsman page:

def consume(iterator, n=None):
    "Advance the iterator n-steps ahead. If n is none, consume entirely."
    # Use functions that consume iterators at C speed.
    if n is None:
        # feed the entire iterator into a zero-length deque
        collections.deque(iterator, maxlen=0)
    else:
        # advance to the empty slice starting at position n
        next(islice(iterator, n, n), None)

Tất nhiên, phần sau rõ ràng và dễ hiểu hơn.


@Paul: Tôi nghĩ nó nên như vậy. Và thực sự thì bạn có thể, mặc dù mapcó thể không trực quan bằng nếu người ta chưa thực hiện lập trình chức năng trước đây.
Katriel

4
Không chắc điều này là đặc biệt thành ngữ. Không có lợi thế khi sử dụng vòng lặp rõ ràng.
Marcin

1
Giải pháp làconsume = collections.deque(maxlen=0).extend
PaulMcG

24

Danh sách dễ hiểu là để tạo danh sách. Và trừ khi bạn thực sự đang tạo một danh sách, bạn không nên sử dụng cách hiểu danh sách.

Vì vậy, tôi sẽ có tùy chọn thứ hai, chỉ cần lặp lại danh sách và sau đó gọi hàm khi các điều kiện áp dụng.


6
Tôi thậm chí còn đi xa hơn và tuyên bố rằng các tác dụng phụ bên trong việc hiểu danh sách là bất thường, không mong đợi và do đó xấu xa, ngay cả khi bạn đang sử dụng danh sách kết quả khi hoàn thành.
Mark Ransom

11

Thứ hai là tốt hơn.

Hãy nghĩ về người cần hiểu mã của bạn. Bạn có thể nhận được nghiệp xấu dễ dàng với lần đầu tiên :)

Bạn có thể đi giữa cả hai bằng cách sử dụng filter (). Hãy xem xét ví dụ:

y=[1,2,3,4,5,6]
def func(x):
    print "call with %r"%x

for x in filter(lambda x: x>3, y):
    func(x)

10
Lambda của bạn được viết tốt hơn nhiều lambda x : x > 3.
PaulMcG

Bạn thậm chí không cần bộ lọc. Chỉ cần đặt một biểu thức máy phát điện trong dấu ngoặc ở đây: for el in (x for x in y if x > 3):. elxcó thể trùng tên, nhưng điều đó có thể khiến mọi người nhầm lẫn.
Omnifarious

3

Phụ thuộc vào mục tiêu của bạn.

Nếu bạn đang cố gắng thực hiện một số thao tác trên từng đối tượng trong danh sách, thì nên áp dụng cách tiếp cận thứ hai.

Nếu bạn đang cố gắng tạo một danh sách từ một danh sách khác, bạn có thể sử dụng tính năng hiểu danh sách.

Rõ ràng là tốt hơn ngầm. Đơn giản là tốt hơn phức tạp. (Python Zen)


0

Bạn có thể làm

for z in (fun_with_side_effects(x) for x in y if (...conditions...)): pass

nhưng nó không đẹp lắm.


-1

Sử dụng một danh sách để hiểu các tác dụng phụ của nó là xấu xí, không phải Pythonic, không hiệu quả và tôi sẽ không làm điều đó. forThay vào đó, tôi sẽ sử dụng một vòng lặp, vì một forvòng lặp báo hiệu một kiểu thủ tục trong đó các tác dụng phụ là quan trọng.

Tuy nhiên, nếu bạn hoàn toàn khăng khăng sử dụng khả năng hiểu danh sách cho các tác dụng phụ của nó, bạn nên tránh sự kém hiệu quả bằng cách sử dụng biểu thức trình tạo thay thế. Nếu bạn hoàn toàn kiên định với phong cách này, hãy thực hiện một trong hai cách sau:

any(fun_with_side_effects(x) and False for x in y if (...conditions...))

hoặc là:

all(fun_with_side_effects(x) or True for x in y if (...conditions...))

Đây là các biểu thức của trình tạo và chúng không tạo ra một danh sách ngẫu nhiên bị loại bỏ. Tôi nghĩ allhình thức có lẽ rõ ràng hơn một chút, mặc dù tôi nghĩ cả hai đều khó hiểu và không nên sử dụng.

Tôi nghĩ rằng điều này là xấu và tôi sẽ không thực sự làm điều đó trong mã. Nhưng nếu bạn kiên quyết thực hiện các vòng lặp của mình theo kiểu này, thì đó là cách tôi sẽ làm.

Tôi có xu hướng cảm thấy rằng khả năng hiểu danh sách và ilk của chúng nên báo hiệu nỗ lực sử dụng thứ gì đó ít nhất là giống một phong cách chức năng. Đưa những thứ có tác dụng phụ phá vỡ giả định đó sẽ khiến mọi người phải đọc mã của bạn cẩn thận hơn, và tôi nghĩ đó là một điều tồi tệ.


Nếu fun_with_side_effectstrả về True thì sao?
Katriel

7
Tôi nghĩ cách chữa này còn tệ hơn căn bệnh - itertools.consume sạch hơn nhiều.
PaulMcG

@PaulMcG - itertools.consumekhông còn tồn tại nữa, có thể là do việc sử dụng hiểu biết với các tác dụng phụ là xấu xí.
Omnifarious

1
Hóa ra là tôi đã nhầm, và nó chưa bao giờ tồn tại như một phương thức trong stdlib. Đó một công thức trong tài liệu itertools: docs.python.org/3/library/...
PaulMcG
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.