cách pythonic để làm một cái gì đó N lần mà không có một biến chỉ số?


161

Càng ngày tôi càng yêu trăn.

Hôm nay, tôi đã viết một số mã như:

for i in xrange(N):
    do_something()

Tôi đã phải làm một cái gì đó N lần. Nhưng mỗi lần không phụ thuộc vào giá trị của i(biến chỉ số). Tôi nhận ra rằng tôi đang tạo một biến mà tôi chưa bao giờ sử dụng ( i) và tôi nghĩ rằng "Chắc chắn có một cách thức sâu sắc hơn để làm điều này mà không cần đến biến chỉ số vô dụng đó."

Vì vậy, ... câu hỏi là: bạn có biết làm thế nào để thực hiện nhiệm vụ đơn giản này theo cách đẹp hơn (pythonic) không?


7
Tôi mới tìm hiểu về biến _, nhưng nếu không tôi sẽ xem xét cách bạn đang thực hiện nó Pythonic. Tôi không nghĩ rằng tôi đã từng thấy một vòng lặp đơn giản được thực hiện theo bất kỳ cách nào khác, ít nhất là trong python. Mặc dù tôi chắc chắn có những trường hợp sử dụng cụ thể khi bạn nhìn vào nó và nói "Đợi đã, trông rất tệ" - nhưng nói chung, xrange là cách ưa thích (theo như tôi đã thấy).
Wayne Werner


5
LƯU Ý: xrange không tồn tại trong Python3. Sử dụng rangethay thế.
John Henckel

Câu trả lời:


110

Một cách tiếp cận nhanh hơn một chút so với lặp xrange(N) là:

import itertools

for _ in itertools.repeat(None, N):
    do_something()

3
Nhanh hơn bao nhiêu? Vẫn còn một sự khác biệt trong Python 3.1?
Hamish Grubijan

15
@ Hamish: Bài kiểm tra của tôi với 2.6 cho biết nhanh hơn 32% (23,2 chúng tôi so với 17,6 chúng tôi cho N = 1000). Nhưng đó là một thời gian thực sự dù sao thời gian. Tôi sẽ mặc định mã của OP vì nó dễ đọc hơn (đối với tôi).
Mike Boers

3
Đó là điều tốt để biết về tốc độ. Tôi chắc chắn lặp lại cảm nghĩ của Mike về mã của OP dễ đọc hơn.
Wayne Werner

@Wayne, tôi đoán thói quen thực sự rất mạnh mẽ - ngoại trừ thực tế là bạn đã quen với nó, tại sao người khác lại "đếm từ 0 đến N-1 [[và hoàn toàn bỏ qua số đếm]] mỗi lần thực hiện số đếm này Hoạt động phụ thuộc "về bản chất có rõ ràng hơn" lặp lại N lần hoạt động sau "...?
Alex Martelli

4
Bạn có chắc là tốc độ thực sự phù hợp? Không phải vì vậy mà nếu bạn làm bất cứ điều gì có ý nghĩa trong vòng lặp đó, rất có thể sẽ mất hàng trăm hoặc hàng ngàn thời gian như kiểu lặp mà bạn đã chọn?
Henning

55

Sử dụng biến _, như tôi đã học khi tôi hỏi câu hỏi này , ví dụ:

# A long way to do integer exponentiation
num = 2
power = 3
product = 1
for _ in xrange(power):
    product *= num
print product

6
Không phải là downvoter nhưng có thể là do bạn đang đề cập đến một bài đăng khác thay vì thêm chi tiết hơn trong câu trả lời
Downgoat

5
@Downgoat: Cảm ơn bạn đã phản hồi. Điều đó nói rằng, không có nhiều điều để nói về thành ngữ này. Quan điểm của tôi khi đề cập đến một bài viết khác là chỉ ra rằng một tìm kiếm có thể đã tạo ra câu trả lời. Tôi thấy thật mỉa mai khi câu hỏi này có số lần upvote nhiều lần như câu hỏi khác.
GreenMatt

39

Tôi chỉ sử dụng for _ in range(n), nó đi thẳng vào vấn đề. Nó sẽ tạo ra toàn bộ danh sách cho số lượng lớn trong Python 2, nhưng nếu bạn đang sử dụng Python 3 thì đó không phải là vấn đề.


10

vì chức năng là công dân hạng nhất, bạn có thể viết trình bao bọc nhỏ (từ câu trả lời của Alex)

def repeat(f, N):
    for _ in itertools.repeat(None, N): f()

sau đó bạn có thể truyền hàm dưới dạng đối số.


@ Hamish: Hầu như không có gì. (17,8 chúng tôi trên mỗi vòng lặp trong cùng điều kiện với thời gian trả lời của Alex, cho chênh lệch 0,2 chúng tôi).
Mike Boers

9

_ Là điều tương tự như x. Tuy nhiên, đó là một thành ngữ python được sử dụng để chỉ một định danh mà bạn không có ý định sử dụng. Trong python, các định danh này không ghi nhớ hoặc phân bổ không gian như các biến làm trong các ngôn ngữ khác. Thật dễ dàng để quên điều đó. Chúng chỉ là các tên trỏ đến các đối tượng, trong trường hợp này là một số nguyên trên mỗi lần lặp.


8

Tôi đã tìm thấy những câu trả lời khác nhau thực sự tao nhã (đặc biệt là của Alex Martelli) nhưng tôi muốn định lượng hiệu suất đầu tiên, vì vậy tôi đã nấu kịch bản sau:

from itertools import repeat
N = 10000000

def payload(a):
    pass

def standard(N):
    for x in range(N):
        payload(None)

def underscore(N):
    for _ in range(N):
        payload(None)

def loopiter(N):
    for _ in repeat(None, N):
        payload(None)

def loopiter2(N):
    for _ in map(payload, repeat(None, N)):
        pass

if __name__ == '__main__':
    import timeit
    print("standard: ",timeit.timeit("standard({})".format(N),
        setup="from __main__ import standard", number=1))
    print("underscore: ",timeit.timeit("underscore({})".format(N),
        setup="from __main__ import underscore", number=1))
    print("loopiter: ",timeit.timeit("loopiter({})".format(N),
        setup="from __main__ import loopiter", number=1))
    print("loopiter2: ",timeit.timeit("loopiter2({})".format(N),
        setup="from __main__ import loopiter2", number=1))

Tôi cũng đã đưa ra một giải pháp thay thế dựa trên giải pháp của Martelli và sử dụng map()để gọi hàm tải trọng. OK, tôi đã lừa dối một chút rằng tôi có quyền tự do làm cho trọng tải chấp nhận một tham số bị loại bỏ: Tôi không biết liệu có cách nào khác không. Tuy nhiên, đây là kết quả:

standard:  0.8398549720004667
underscore:  0.8413165839992871
loopiter:  0.7110594899968419
loopiter2:  0.5891903560004721

do đó, sử dụng bản đồ mang lại sự cải thiện khoảng 30% so với tiêu chuẩn cho vòng lặp và thêm 19% so với Martelli.


4

Giả sử rằng bạn đã xác định do_s Something là một hàm và bạn muốn thực hiện nó N lần. Có lẽ bạn có thể thử như sau:

todos = [do_something] * N  
for doit in todos:  
    doit()

45
Chắc chắn rồi. Chúng ta không chỉ gọi hàm một triệu lần, hãy phân bổ danh sách một triệu mục. Nếu CPU đang hoạt động, bộ nhớ có bị căng thẳng một chút không? Câu trả lời không thể được mô tả là chắc chắn là không có ích gì (nó hiển thị một cách tiếp cận chức năng khác) vì vậy tôi không thể đánh giá thấp, nhưng tôi không đồng ý và tôi hoàn toàn phản đối nó.
tzot

1
Không phải nó chỉ là một danh sách N tham chiếu đến cùng một giá trị hàm sao?
Nick McCurdy

tốt hơn là làm fn() for fn in itertools.repeat(do_something, N)và lưu trước khi tạo mảng ... đây là thành ngữ ưa thích của tôi.
F1Rumors

1
@tzot Tại sao âm điệu hạ thấp? Người này đã nỗ lực viết một câu trả lời và bây giờ có thể không được khuyến khích đóng góp trong tương lai. Ngay cả khi nó có ý nghĩa về hiệu năng, thì đó là một tùy chọn hoạt động và đặc biệt nếu N nhỏ, thì hàm ý hiệu năng / bộ nhớ không đáng kể.
davidscolgan

Tôi luôn ngạc nhiên về hiệu suất của các nhà phát triển Python bị ám ảnh :) Mặc dù tôi đồng ý rằng nó không phải là thành ngữ và ai đó mới đọc Python có thể không hiểu điều gì đang diễn ra rõ ràng như khi chỉ sử dụng một trình vòng lặp
Asfand Qazi

1

Một vòng lặp while đơn giản thì sao?

while times > 0:
    do_something()
    times -= 1

Bạn đã có biến; Tại sao không sử dụng nó?


1
Suy nghĩ duy nhất của tôi là 3 dòng mã so với một (?)
AJP

2
@AJP - Giống như 4 dòng so với 2 dòng
ArtOfWarfare

thêm phép so sánh (lần> 0) và mức giảm (lần - = 1) vào tổng phí ... quá chậm so với vòng lặp for ...
F1Rumors

@ F1Rumors Chưa đo được, nhưng tôi sẽ ngạc nhiên nếu các trình biên dịch JIT như PyPy sẽ tạo mã chậm hơn cho một vòng lặp while đơn giản như vậy.
Philipp Claßen
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.