Mengapa tidak ada fungsi xrange di Python3?

273

Baru-baru ini saya mulai menggunakan Python3 dan kurangnya xrange sakit.

Contoh sederhana:

1) Python2:

from time import time as t
def count():
  st = t()
  [x for x in xrange(10000000) if x%4 == 0]
  et = t()
  print et-st
count()

2) Python3:

from time import time as t

def xrange(x):

    return iter(range(x))

def count():
    st = t()
    [x for x in xrange(10000000) if x%4 == 0]
    et = t()
    print (et-st)
count()

Hasilnya, masing-masing:

1) 1.53888392448 2) 3.215819835662842

Mengapa demikian? Maksudku, mengapa xrange dihapus? Ini alat yang luar biasa untuk dipelajari. Untuk pemula, sama seperti saya, seperti kita semua ada di beberapa titik. Mengapa menghapusnya? Adakah yang bisa mengarahkan saya ke PEP yang tepat, saya tidak dapat menemukannya.

Bersulang.

catalesia
sumber
231
rangedalam Python 3.x berasal xrangedari Python 2.x. Sebenarnya Python 2.x rangeyang dihapus.
Anorov
27
PS, Anda tidak boleh membuang waktu time. Selain menjadi lebih mudah digunakan dan lebih sulit untuk salah, dan mengulangi tes untuk Anda, timeitmengurus semua hal yang Anda tidak akan ingat, atau bahkan tahu bagaimana, untuk mengurus (seperti menonaktifkan GC), dan dapat menggunakan Jam dengan resolusi ribuan kali lebih baik.
abarnert
7
Juga, mengapa kamu mencobai waktu untuk menyaring rangepada x%4 == 0? Mengapa tidak hanya menguji list(xrange())vs list(range()), jadi ada pekerjaan luar yang sesedikit mungkin? (Misalnya, bagaimana Anda tahu 3.x tidak melakukan x%4lebih lambat?) Untuk itu, mengapa Anda membangun besar list, yang melibatkan banyak alokasi memori (yang, selain lambat, juga sangat bervariasi) ?
abarnert
5
Lihat docs.python.org/3.0/whatsnew/3.0.html , bagian "Tampilan Dan Iterator Alih-alih Daftar": "range () sekarang berperilaku seperti xrange () yang digunakan untuk berperilaku, kecuali ia bekerja dengan nilai ukuran sewenang-wenang. tidak ada lagi. " Jadi, rentang sekarang mengembalikan iterator. iter(range)berlebihan.
ToolmakerSteve
9
Maaf, menyadari mengutip dokumen perubahan tidak membuatnya begitu jelas. Untuk siapa pun yang bingung, dan tidak ingin membaca jawaban yang telah lama diterima, dan semua komentarnya: Di mana pun Anda menggunakan xrange di python 2, gunakan rentang di python 3. Itu melakukan apa yang biasa dilakukan xrange, yaitu kembalikan iterator. Jika Anda membutuhkan hasil dalam daftar, lakukan list(range(..)). Itu setara dengan rentang python 2. Atau mengatakannya dengan cara lain: xrange telah diubah jangkauannya, karena ini adalah default yang lebih baik; tidak perlu memiliki keduanya, lakukan list(range)jika Anda benar-benar membutuhkan daftar. .
ToolmakerSteve

Jawaban:

175

Beberapa pengukuran kinerja, menggunakan timeitalih-alih mencoba melakukannya secara manual time.

Pertama, Apple 2.7.2 64-bit:

In [37]: %timeit collections.deque((x for x in xrange(10000000) if x%4 == 0), maxlen=0)
1 loops, best of 3: 1.05 s per loop

Sekarang, python.org 3.3.0 64-bit:

In [83]: %timeit collections.deque((x for x in range(10000000) if x%4 == 0), maxlen=0)
1 loops, best of 3: 1.32 s per loop

In [84]: %timeit collections.deque((x for x in xrange(10000000) if x%4 == 0), maxlen=0)
1 loops, best of 3: 1.31 s per loop

In [85]: %timeit collections.deque((x for x in iter(range(10000000)) if x%4 == 0), maxlen=0) 
1 loops, best of 3: 1.33 s per loop

Rupanya, 3.x rangebenar-benar sedikit lebih lambat dari 2.x xrange. Dan fungsi OP xrangetidak ada hubungannya dengan itu. (Tidak mengherankan, karena panggilan satu kali ke __iter__slot tidak akan terlihat di antara 10.000.000 panggilan untuk apa pun yang terjadi dalam loop, tetapi seseorang mengemukakannya sebagai suatu kemungkinan.)

Tapi ini hanya 30% lebih lambat. Bagaimana OP mendapat 2x lebih lambat? Nah, jika saya mengulangi tes yang sama dengan Python 32-bit, saya mendapatkan 1,58 vs 3,12. Jadi tebakan saya adalah bahwa ini adalah kasus lain di mana 3.x telah dioptimalkan untuk kinerja 64-bit dengan cara yang menyakitkan 32-bit.

Tetapi apakah itu benar-benar penting? Lihat ini, dengan 3.3.0 64-bit lagi:

In [86]: %timeit [x for x in range(10000000) if x%4 == 0]
1 loops, best of 3: 3.65 s per loop

Jadi, membangun listmembutuhkan lebih dari dua kali lebih lama dari seluruh iterasi.

Dan untuk "menghabiskan lebih banyak sumber daya daripada Python 2.6+", dari pengujian saya, sepertinya 3.x rangepersis ukuran yang sama dengan 2.x xrange—dan, bahkan jika itu 10x lebih besar, membangun daftar yang tidak perlu masih sekitar 10.000.000x lebih dari masalah daripada apa pun rentang iterasi mungkin bisa dilakukan.

Dan bagaimana dengan forloop eksplisit, bukan loop C di dalam deque?

In [87]: def consume(x):
   ....:     for i in x:
   ....:         pass
In [88]: %timeit consume(x for x in range(10000000) if x%4 == 0)
1 loops, best of 3: 1.85 s per loop

Jadi, hampir sebanyak waktu yang terbuang dalam forpernyataan seperti pada pekerjaan aktual iterasi range.

Jika Anda khawatir tentang mengoptimalkan iterasi objek rentang, Anda mungkin mencari di tempat yang salah.


Sementara itu, Anda terus bertanya mengapa xrangedihapus, tidak peduli berapa kali orang mengatakan hal yang sama kepada Anda, tetapi saya akan mengulanginya lagi: Tidak dihapus: diubah namanya menjadi range, dan 2.x rangeadalah yang dihapus.

Berikut beberapa bukti bahwa rangeobjek 3.3 adalah turunan langsung dari xrangeobjek 2.x (dan bukan dari rangefungsi 2.x ): sumber ke 3.3range dan 2.7xrange . Anda bahkan dapat melihat riwayat perubahan (terkait dengan, saya percaya, perubahan yang menggantikan instance terakhir dari string "xrange" di mana saja dalam file).

Jadi, mengapa lebih lambat?

Pertama, mereka telah menambahkan banyak fitur baru. Untuk yang lain, mereka telah melakukan semua jenis perubahan di semua tempat (terutama di dalam iterasi) yang memiliki efek samping kecil. Dan ada banyak pekerjaan untuk secara dramatis mengoptimalkan berbagai kasus penting, bahkan jika kadang-kadang sedikit pesimis kasus yang kurang penting. Tambahkan semua ini, dan saya tidak terkejut bahwa iterasi rangesecepat mungkin sekarang sedikit lebih lambat. Ini adalah salah satu dari kasus-kasus yang kurang penting yang tidak akan pernah dipedulikan oleh siapa pun. Tidak ada yang mungkin memiliki kasus penggunaan nyata di mana perbedaan kinerja ini adalah hotspot dalam kode mereka.

abarnert
sumber
Tapi ini hanya 30% lebih lambat. Masih lebih lambat, tetapi pasangan respons yang baik, sesuatu untuk dipikirkan. Itu tidak menjawab pertanyaan saya: mengapa xrange dihapus ?? Pikirkan seperti ini - jika Anda memiliki aplikasi yang bergantung pada kinerja berdasarkan multiprocessing mengetahui berapa banyak antrian yang perlu Anda konsumsi satu kali, apakah 30% akan membuat perbedaan atau tidak? Anda tahu, Anda mengatakan itu tidak masalah, tetapi setiap kali saya menggunakan jangkauan saya mendengar bahwa suara kipas yang sangat besar yang berarti cpu adalah yang terburuk, sementara xrange tidak melakukannya. Coba pikirkan;)
catalesia
9
@catalesia: Sekali lagi, itu tidak dihapus, baru saja diganti namanya range. The rangeobjek dalam 3.3 adalah keturunan langsung dari xrangeobjek di 2.7, bukan dari rangefungsi di 2,7. Ini seperti meminta sementara itertools.imapdihapus untuk mendukung map. Tidak ada jawaban, karena tidak ada yang terjadi.
abarnert
1
@catalesia: Perubahan kinerja kecil mungkin bukan hasil dari keputusan desain langsung untuk membuat rentang lebih lambat, tetapi efek samping dari 4 tahun perubahan di seluruh Python yang telah membuat banyak hal lebih cepat, beberapa hal sedikit lebih lambat (dan beberapa hal) lebih cepat pada x86_64 tetapi lebih lambat pada x86, atau lebih cepat dalam beberapa kasus penggunaan tetapi lebih lambat pada yang lain, dll.). Tidak ada yang mungkin khawatir tentang perbedaan 30% baik dalam berapa lama untuk iterate rangesementara tidak melakukan hal lain.
abarnert
1
"Tidak ada yang mungkin khawatir tentang perbedaan 30% baik dalam berapa lama waktu yang dibutuhkan untuk beralih rentang sementara tidak melakukan hal lain. "
catalesia
18
@catalesia: Ya, persis. Tetapi Anda tampaknya berpikir itu berarti kebalikan dari apa yang dikatakannya. Ini bukan kasus penggunaan yang akan dipedulikan oleh siapa pun, jadi tidak ada yang menyadari bahwa ini menjadi 30% lebih lambat. Terus? Jika Anda dapat menemukan program kehidupan nyata yang berjalan lebih lambat di Python 3.3 daripada di 2.7 (atau 2.6) karena ini, orang akan peduli. Jika Anda tidak bisa, mereka tidak akan, dan Anda seharusnya tidak.
abarnert
141

Berbagai Python3 ini adalah xrange python2 ini. Tidak perlu membungkus iter di sekitarnya. Untuk mendapatkan daftar aktual di Python3, Anda harus menggunakanlist(range(...))

Jika Anda menginginkan sesuatu yang berfungsi dengan Python2 dan Python3, coba ini

try:
    xrange
except NameError:
    xrange = range
John La Rooy
sumber
1
Terkadang Anda membutuhkan kode yang berfungsi di Python 2 dan 3. Ini adalah solusi yang baik.
Greg Glockner
3
Masalahnya adalah bahwa dengan ini, kode yang menggunakan keduanya rangedan xrangeakan berperilaku berbeda. Tidak cukup untuk melakukan ini, kita juga harus memastikan untuk tidak menganggap bahwa rangemengembalikan daftar (seperti dalam python 2).
LangeHaare
Anda dapat menggunakan xrange dari proyek ini. Ada futurizealat untuk secara otomatis mengonversi kode sumber Anda: python-future.org/…
guettli
17

rangeJenis Python 3 berfungsi seperti halnya Python 2 xrange. Saya tidak yakin mengapa Anda melihat perlambatan, karena iterator yang dikembalikan oleh xrangefungsi Anda adalah persis apa yang akan Anda dapatkan jika Anda mengulanginya rangesecara langsung.

Saya tidak dapat mereproduksi perlambatan pada sistem saya. Inilah cara saya menguji:

Python 2, dengan xrange:

Python 2.7.3 (default, Apr 10 2012, 23:24:47) [MSC v.1500 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> import timeit
>>> timeit.timeit("[x for x in xrange(1000000) if x%4]",number=100)
18.631936646865853

Python 3, dengan rangesedikit lebih cepat:

Python 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:57:17) [MSC v.1600 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> import timeit
>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=100)
17.31399508687869

Baru-baru ini saya mengetahui bahwa rangetipe Python 3 memiliki beberapa fitur rapi lainnya, seperti dukungan untuk slicing: range(10,100,2)[5:25:5]is range(20, 60, 10)!

Blckknght
sumber
Mungkin perlambatan datang dari pencarian yang baru xrangeberkali-kali, atau apakah itu dilakukan hanya sekali?
askewchan
Apakah iterator sebenarnya meningkatkan kecepatan? Saya pikir itu hanya menghemat memori.
askewchan
3
@catalesia Saya pikir titik di sini adalah bahwa xrangeitu tidak dihapus, hanya berganti nama .
askewchan
1
@ Blckknght: Ceria, tetapi masih menyebalkan memiliki penjelasan seperti: "Tetapkan literal dan pemahaman [19] [20] [selesai] {x} berarti set ([x]); {x, y} berarti set ([ x, y]). {F (x) untuk x dalam S jika P (x)} berarti set (F (x) untuk x dalam S jika P (x)). NB. {range (x)} berarti set ( [range (x)]), BUKAN set (range (x)). Tidak ada literal untuk set kosong; gunakan set () (atau {1} & {2} :-). Tidak ada frozenset literal; mereka juga terlalu jarang dibutuhkan. "
catalesia
3
Kemenangan terbesar dalam 3.x range, sejauh yang saya ketahui, adalah waktu yang konstan __contains__. Pemula biasa menulis 300000 in xrange(1000000)dan menyebabkannya mengulangi keseluruhan xrange(atau setidaknya 30% pertama dari itu), jadi kami harus menjelaskan mengapa itu adalah ide yang buruk, meskipun itu terlihat sangat pythonic. Sekarang, itu adalah pythonic.
abarnert
1

Salah satu cara untuk memperbaiki kode python2 Anda adalah:

import sys

if sys.version_info >= (3, 0):
    def xrange(*args, **kwargs):
        return iter(range(*args, **kwargs))
andrew pate
sumber
1
Intinya adalah di python3 xrange tidak didefinisikan, jadi kode warisan yang digunakan istirahat xrange.
andrew pate
tidak, cukup definisikan range = xrangeseperti yang di komentar oleh @John La Roy
mimi.vx
@ mimi.vx Tidak yakin range = xrange akan berfungsi di Python3 karena xrange tidak didefinisikan. Komentar saya merujuk pada kasus di mana Anda memiliki kode lawas lama yang berisi panggilan xrange DAN upaya Anda untuk menjalankannya di bawah python3.
andrew pate
1
Ah, xrange = range
salahku
range ADALAH iiterator, dan lagi pula ini akan menjadi ide yang buruk bahkan jika tidak, karena harus membongkar seluruh rentang terlebih dahulu dan kehilangan keuntungan menggunakan iterator untuk hal semacam ini. Jadi jawaban yang benar bukanlah "range = xrange" "xrange = range"
Shayne
0

xrange dari Python 2 adalah generator dan mengimplementasikan iterator sementara range hanyalah sebuah fungsi. Dalam Python3 saya tidak tahu mengapa dijatuhkan xrange.

Michel Fernandes
sumber
Tidak, rentang bukan interator. Anda tidak dapat melakukan next () dengan struktur ini. Untuk info lebih lanjut, Anda dapat memeriksa di sini treyhunner.com/2018/02/python-range-is-not-an-iterator
Michel Fernandes
Terima kasih banyak atas klarifikasi. Tapi saya akan menyatakan kembali maksud dari komentar asli, dan itu adalah bahwa PY3 range()adalah setara dengan PY2 xrange(). Dan dengan demikian di PY3 xrange()berlebihan.
Stephen Rauch
-2

comp: ~ $ python Python 2.7.6 (default, 22 Jun 2015, 17:58:13) [GCC 4.8.2] di linux2

>>> import timeit
>>> timeit.timeit("[x for x in xrange(1000000) if x%4]",number=100)

5.656799077987671

>>> timeit.timeit("[x for x in xrange(1000000) if x%4]",number=100)

5.579368829727173

>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=100)

21.54827117919922

>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=100)

22.014557123184204

Dengan angka timeit = 1 param:

>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=1)

0.2245171070098877

>>> timeit.timeit("[x for x in xrange(1000000) if x%4]",number=1)

0.10750913619995117

comp: ~ $ python3 Python 3.4.3 (default, 14 Okt 2015, 20:28:29) [GCC 4.8.4] di linux

>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=100)

9.113872020003328

>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=100)

9.07014398300089

Dengan angka timeit = 1,2,3,4 param bekerja cepat dan dengan cara linear:

>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=1)

0,09329321900440846

>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=2)

0.18501482300052885

>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=3)

0,2703447980020428

>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=4)

0.36209142999723554

Jadi sepertinya jika kita mengukur 1 siklus putaran berjalan seperti timeit.timeit ("[x untuk x dalam kisaran (1000000) jika x% 4]", angka = 1) (seperti yang kita gunakan dalam kode nyata) python3 bekerja cukup cepat, tetapi dalam loop berulang python 2 xrange () menang dalam kecepatan terhadap range () dari python 3.

dmitriy
sumber
tapi ini dengan bahasa itu sendiri ... tidak ada hubungannya dengan xrange / range.
mimi.vx