Apakah mungkin untuk mengimplementasikan Python untuk rentang loop tanpa variabel iterator?

187

Apakah mungkin untuk mengikuti tanpa i?

for i in range(some_number):
    # do something

Jika Anda hanya ingin melakukan sesuatu sebanyak N kali dan tidak perlu iterator.

James McMahon
sumber
21
Ini pertanyaan yang bagus! PyDev bahkan menandai 'i' sebagai peringatan untuk 'variabel yang tidak digunakan'. Solusi di bawah ini menghilangkan peringatan ini.
Ashwin Nanjappa
@Ashwin Anda dapat menggunakan \ @UnusedVariable untuk menghapus peringatan itu. Perhatikan bahwa saya perlu melarikan diri dari simbol 'at' untuk mendapatkan komentar ini.
Raffi Khatchadourian
Saya mengajukan pertanyaan yang sama kepada Anda. Ini mengganggu dengan peringatan pylint. Tentu saja Anda dapat menonaktifkan peringatan dengan penekanan tambahan seperti yang diusulkan @Raffi Khatchadourian. Akan lebih baik untuk menghindari peringatan pylint dan komentar penindasan.
tangoal

Jawaban:

110

Dari atas kepalaku, tidak.

Saya pikir yang terbaik yang dapat Anda lakukan adalah sesuatu seperti ini:

def loop(f,n):
    for i in xrange(n): f()

loop(lambda: <insert expression here>, 5)

Tapi saya pikir Anda bisa hidup dengan ivariabel tambahan .

Berikut adalah opsi untuk menggunakan _variabel, yang pada kenyataannya, hanyalah variabel lain.

for _ in range(n):
    do_something()

Catatan yang _diberikan hasil terakhir yang dikembalikan dalam sesi python interaktif:

>>> 1+2
3
>>> _
3

Untuk alasan ini, saya tidak akan menggunakannya dengan cara ini. Saya tidak mengetahui adanya idiom seperti yang disebutkan oleh Ryan. Ini dapat mengacaukan penerjemah Anda.

>>> for _ in xrange(10): pass
...
>>> _
9
>>> 1+2
3
>>> _
9

Dan menurut tata bahasa Python , itu adalah nama variabel yang dapat diterima:

identifier ::= (letter|"_") (letter | digit | "_")*
Tidak Dikenal
sumber
4
"Tapi aku pikir kamu bisa hidup dengan tambahan" i "" Ya itu hanya poin akademik.
James McMahon
1
@nemo, Anda dapat mencoba melakukan _ dalam rentang (n): jika Anda tidak ingin menggunakan nama alfanumerik.
Tidak diketahui
Apakah _ variabel dalam kasus itu? Atau itu sesuatu yang lain dengan Python?
James McMahon
1
@nemo Ya itu hanya nama variabel yang dapat diterima. Pada penerjemah, secara otomatis ditetapkan ekspresi terakhir yang Anda buat.
Tidak diketahui
3
@kurczak Ada benarnya. Menggunakan _membuatnya jelas bahwa itu harus diabaikan. Mengatakan tidak ada gunanya melakukan ini seperti mengatakan tidak ada gunanya mengomentari kode Anda - karena bagaimanapun juga akan melakukan hal yang sama.
Lambda Fairy
69

Anda mungkin sedang mencari

for _ in itertools.repeat(None, times): ...

ini adalah cara tercepat untuk mengulang timeskali menggunakan Python.

Alex Martelli
sumber
2
Saya tidak peduli dengan kinerja, saya hanya ingin tahu apakah ada cara terser untuk menulis pernyataan. Sementara saya telah menggunakan Python secara sporadis selama sekitar 2 tahun sekarang saya masih merasa ada banyak yang saya lewatkan. Itertools adalah salah satunya, terima kasih atas informasinya.
James McMahon
5
Itu menarik, saya tidak menyadarinya. Saya baru saja melihat dokumen itertools; tapi saya ingin tahu mengapa ini lebih cepat daripada hanya menggunakan range atau xrange?
si28719e
5
@blackkettle: lebih cepat karena tidak perlu mengembalikan indeks iterasi saat ini, yang merupakan bagian terukur dari biaya xrange (dan rentang Python 3, yang memberikan iterator, bukan daftar). @nemo, range sama optimalnya dengan itu, tetapi perlu membangun dan mengembalikan daftar adalah pekerjaan yang lebih berat daripada iterator (di Py3, range memang mengembalikan iterator, seperti xrange Py2; kompatibilitas mundur tidak memungkinkan perubahan seperti itu) di Py2), terutama yang tidak perlu mengembalikan nilai yang bervariasi.
Alex Martelli
4
@ Kristen, ya, jelas mempersiapkan dan mengembalikan int Python setiap waktu, inc. pekerjaan gc, memang memiliki biaya yang terukur - menggunakan counter secara internal tidak masalah.
Alex Martelli
4
Saya mengerti sekarang. Perbedaannya berasal dari overhead GC, bukan dari "algoritma". Ngomong-ngomong, saya menjalankan benchmark timeit cepat dan speedupnya ~ 1.42x.
Cristian Ciupitu
59

Ungkapan umum untuk menetapkan nilai yang tidak digunakan adalah memberi nama _.

for _ in range(times):
    do_stuff()
Ryan
sumber
18

Apa yang semua orang menyarankan Anda untuk menggunakan _ tidak mengatakan adalah _ sering digunakan sebagai jalan pintas ke salah satu fungsi gettext , jadi jika Anda ingin perangkat lunak Anda tersedia dalam lebih dari satu bahasa maka Anda sebaiknya menghindari menggunakannya untuk keperluan lain.

import gettext
gettext.bindtextdomain('myapplication', '/path/to/my/language/directory')
gettext.textdomain('myapplication')
_ = gettext.gettext
# ...
print _('This is a translatable string.')
Ignacio Vazquez-Abrams
sumber
Bagi saya, penggunaan ini _sepertinya ide yang buruk, saya tidak keberatan berkonflik dengan itu.
KeithWM
9

Inilah gagasan acak yang memanfaatkan (menyalahgunakan?) Model data ( tautan Py3 ).

class Counter(object):
    def __init__(self, val):
        self.val = val

    def __nonzero__(self):
        self.val -= 1
        return self.val >= 0
    __bool__ = __nonzero__  # Alias to Py3 name to make code work unchanged on Py2 and Py3

x = Counter(5)
while x:
    # Do something
    pass

Saya ingin tahu apakah ada sesuatu seperti ini di perpustakaan standar?

saffsd
sumber
10
Saya pikir memiliki metode seperti __nonzero__dengan efek samping adalah ide yang mengerikan.
ThiefMaster
2
Saya akan menggunakan __call__sebagai gantinya. while x():tidak terlalu sulit untuk ditulis.
Jasmijn
1
Ada juga argumen untuk menghindari nama Counter; tentu saja, itu tidak dicadangkan atau dalam lingkup bawaan, tetapi collections.Countermerupakan suatu hal , dan membuat kelas dengan nama yang sama berisiko membingungkan pengelola (bukan berarti ini sudah tidak mempertaruhkan yang sudah ada).
ShadowRanger
7

Anda dapat menggunakan _11 (atau nomor apa pun atau pengidentifikasi tidak valid lainnya) untuk mencegah tabrakan nama dengan gettext. Setiap kali Anda menggunakan underscore + pengidentifikasi tidak valid Anda mendapatkan nama dummy yang dapat digunakan untuk loop.

JirkaV
sumber
Bagus! PyDev setuju dengan Anda: ini menghilangkan peringatan kuning "Variabel yang tidak digunakan".
mike rodent
2

Mungkin jawabannya tergantung pada masalah apa yang Anda miliki dengan menggunakan iterator? dapat digunakan

i = 100
while i:
    print i
    i-=1

atau

def loop(N, doSomething):
    if not N:
        return
    print doSomething(N)
    loop(N-1, doSomething)

loop(100, lambda a:a)

tapi terus terang saya tidak melihat gunanya menggunakan pendekatan seperti itu

Anurag Uniyal
sumber
1
Catatan: Python (pasti bukan penerjemah referensi CPython setidaknya, mungkin tidak sebagian besar yang lain) tidak mengoptimalkan rekursi ekor, jadi N akan terbatas pada sesuatu di lingkungan nilai sys.getrecursionlimit()(yang default ke suatu tempat di empat rendah rentang digit pada CPython); menggunakan sys.setrecursionlimitakan menaikkan batas, tetapi pada akhirnya Anda akan mencapai batas C stack dan penerjemah akan mati dengan stack overflow (tidak hanya meningkatkan nice RuntimeError/ RecursionError).
ShadowRanger
1
t=0    
for _ in range(10):
    print t
    t = t+1

KELUARAN:

0
1 
2 
3 
4 
5 
6 
7
8
9
Imran Farid
sumber
1

Alih-alih penghitung yang tidak dibutuhkan, sekarang Anda memiliki daftar yang tidak dibutuhkan. Solusi terbaik adalah dengan menggunakan variabel yang dimulai dengan "_", yang memberi tahu checker sintaks bahwa Anda sadar Anda tidak menggunakan variabel.

x = range(5)
while x:
  x.pop()
  print "Work!"
Robert Jacobs
sumber
0

Saya biasanya setuju dengan solusi yang diberikan di atas. Yaitu dengan:

  1. Menggunakan garis bawah di for-loop (2 dan lebih banyak baris)
  2. Menentukan whilepenghitung normal (3 dan lebih banyak baris)
  3. Mendeklarasikan kelas khusus dengan __nonzero__implementasi (banyak lagi baris)

Jika seseorang ingin mendefinisikan objek seperti pada # 3 saya akan merekomendasikan menerapkan protokol untuk dengan kata kunci atau menerapkan contextlib .

Selanjutnya saya mengusulkan solusi lain. Ini adalah 3 liner dan bukan dari keanggunan tertinggi, tetapi menggunakan paket itertools dan dengan demikian mungkin menarik.

from itertools import (chain, repeat)

times = chain(repeat(True, 2), repeat(False))
while next(times):
    print 'do stuff!'

Dalam contoh ini 2 adalah berapa kali untuk mengulang loop. rantai membungkus dua iterator berulang , yang pertama terbatas tetapi yang kedua tak terbatas. Ingatlah bahwa ini adalah objek iterator sejati, karenanya mereka tidak memerlukan memori tak terbatas. Jelas ini jauh lebih lambat maka solusi # 1 . Kecuali ditulis sebagai bagian dari suatu fungsi mungkin diperlukan pembersihan untuk variabel kali .

Yauhen Yakimovich
sumber
2
chaintidak perlu, times = repeat(True, 2); while next(times, False):melakukan hal yang sama.
AChampion
0

Kami bersenang-senang dengan yang berikut ini, menarik untuk dibagikan:

class RepeatFunction:
    def __init__(self,n=1): self.n = n
    def __call__(self,Func):
        for i in xrange(self.n):
            Func()
        return Func


#----usage
k = 0

@RepeatFunction(7)                       #decorator for repeating function
def Job():
    global k
    print k
    k += 1

print '---------'
Job()

Hasil:

0
1
2
3
4
5
6
---------
7
Pengembang
sumber
0

Jika do_somethingfungsi sederhana atau dapat dibungkus menjadi satu, map()kaleng sederhana do_something range(some_number)kali:

# Py2 version - map is eager, so it can be used alone
map(do_something, xrange(some_number))

# Py3 version - map is lazy, so it must be consumed to do the work at all;
# wrapping in list() would be equivalent to Py2, but if you don't use the return
# value, it's wastefully creating a temporary, possibly huge, list of junk.
# collections.deque with maxlen 0 can efficiently run a generator to exhaustion without
# storing any of the results; the itertools consume recipe uses it for that purpose.
from collections import deque

deque(map(do_something, range(some_number)), 0)

Jika Anda ingin meneruskan argumen do_something, Anda juga mungkin menemukan bahwa resep itertoolsrepeatfunc terbaca dengan baik:

Untuk memberikan argumen yang sama:

from collections import deque
from itertools import repeat, starmap

args = (..., my args here, ...)

# Same as Py3 map above, you must consume starmap (it's a lazy generator, even on Py2)
deque(starmap(do_something, repeat(args, some_number)), 0)

Untuk memberikan argumen yang berbeda:

argses = [(1, 2), (3, 4), ...]

deque(starmap(do_something, argses), 0)
mtd
sumber
-1

Jika Anda benar - benar ingin menghindari meletakkan sesuatu dengan nama (baik variabel iterasi seperti dalam OP, atau daftar yang tidak diinginkan atau generator yang tidak diinginkan mengembalikan true jumlah waktu yang diinginkan) Anda dapat melakukannya jika Anda benar-benar ingin:

for type('', (), {}).x in range(somenumber):
    dosomething()

Trik yang digunakan adalah membuat kelas anonim type('', (), {})yang menghasilkan kelas dengan nama kosong, tetapi NB yang tidak dimasukkan dalam ruang nama lokal atau global (bahkan jika nama yang kosong disediakan). Kemudian Anda menggunakan anggota kelas itu sebagai variabel iterasi yang tidak dapat dijangkau karena kelas yang menjadi anggotanya tidak dapat dijangkau.

meroket
sumber
Jelas ini sengaja patologis, jadi mengkritik itu tidak penting, tapi saya akan mencatat jebakan tambahan di sini. Pada CPython, interpreter referensi, definisi kelas adalah siklik alami (membuat kelas tidak dapat dihindari menciptakan siklus referensi yang mencegah pembersihan deterministik kelas berdasarkan penghitungan referensi). Itu berarti Anda menunggu siklik GC untuk menendang dan membersihkan kelas. Biasanya akan dikumpulkan sebagai bagian dari generasi muda, yang secara default sering dikumpulkan, tetapi meskipun demikian, setiap loop berarti ~ 1,5 KB sampah dengan masa hidup non-deterministik.
ShadowRanger
Pada dasarnya, untuk menghindari variabel bernama yang akan (biasanya) secara deterministik dibersihkan pada setiap loop (ketika rebound, dan nilai lama dibersihkan), Anda membuat variabel besar tanpa nama yang dibersihkan secara non-deterministik, dan dapat dengan mudah bertahan lebih lama.
ShadowRanger
-7

Bagaimana dengan:

while range(some_number):
    #do something
Adam Scott
sumber
3
Itu loop tak terbatas karena kondisinya range(some_number)selalu benar!
Mematikan
@ Mati: Yah, jika some_numberkurang dari atau sama dengan 0, itu tidak terbatas, tidak pernah berjalan. :-) Dan itu agak tidak efisien untuk loop tak terbatas (terutama pada Py2), karena itu menciptakan segar list(Py2) atau rangeobjek (Py3) untuk setiap tes (itu bukan konstan dari sudut pandang penerjemah, ia harus memuat rangedan some_numbersetiap loop, panggil range, lalu uji hasilnya).
ShadowRanger