Cara terbaik untuk menangani list.index (mungkin-tidak-ada) dengan python?

113

Saya memiliki kode yang terlihat seperti ini:

thing_index = thing_list.index(thing)
otherfunction(thing_list, thing_index)

ok jadi itu disederhanakan tetapi Anda mendapatkan idenya. Sekarang thingmungkin tidak benar-benar ada dalam daftar, dalam hal ini saya ingin meneruskan -1 sebagai thing_index. Dalam bahasa lain, inilah yang Anda harapkan index()untuk dikembalikan jika tidak dapat menemukan elemennya. Sebenarnya itu melempar ValueError.

Saya bisa melakukan ini:

try:
    thing_index = thing_list.index(thing)
except ValueError:
    thing_index = -1
otherfunction(thing_list, thing_index)

Tapi ini terasa kotor, ditambah lagi saya tidak tahu apakah ValueErrorbisa dibesarkan karena alasan lain. Saya datang dengan solusi berikut berdasarkan fungsi generator, tetapi tampaknya sedikit rumit:

thing_index = ( [(i for i in xrange(len(thing_list)) if thing_list[i]==thing)] or [-1] )[0]

Adakah cara yang lebih bersih untuk mencapai hal yang sama? Mari kita asumsikan bahwa daftar tersebut tidak diurutkan.

Draemon
sumber
4
"... dalam hal ini saya ingin memberikan -1 sebagai thing_index." - Ini jelas tidak Pythonic. Meneruskan nilai token (tidak berarti) jika operasi tidak berhasil tidak disukai - pengecualian benar-benar cara yang tepat di sini. Terutama karena thing_list[-1]merupakan ekspresi yang valid, yang berarti entri terakhir dalam daftar.
Tim Pietzcker
@jellybean: facepalm ... spot the java coder: P
Draemon
4
@ Tim: ada str.findmetode yang melakukan persis seperti itu: mengembalikan -1ketika jarum tidak ditemukan dalam subjek.
SilentGhost
@Tim Tidak ada yang lebih baik dari itu ... dan ini akan menjadi analogi dengan dict [key] vs dict.get [key]
Draemon
@SilentGhost: Hm, menarik. Saya mungkin harus melihat ini lebih detail. str.index()tidak melempar pengecualian jika string pencarian tidak ditemukan.
Tim Pietzcker

Jawaban:

66

Tidak ada yang "kotor" tentang penggunaan klausa coba-kecuali. Ini adalah cara pythonic. ValueErrorakan dimunculkan oleh .indexmetode saja, karena itu satu-satunya kode yang Anda miliki di sana!

Untuk menjawab komentar:
Dengan Python, filosofi lebih mudah untuk meminta maaf daripada mendapatkan izin sudah mapan, dan tidak tidak index akan meningkatkan jenis kesalahan ini untuk masalah lainnya. Bukannya aku bisa memikirkan apapun.

SilentGhost
sumber
29
Tentunya pengecualian untuk kasus luar biasa, dan ini bukan itu. Saya tidak akan mengalami masalah seperti itu jika pengecualiannya lebih spesifik daripada ValueError.
Draemon
1
Saya tahu itu hanya dapat dilempar dari metode itu tetapi apakah dijamin hanya akan dibuang karena alasan itu ? Bukannya saya bisa memikirkan alasan lain mengapa indeks akan gagal..tetapi bukankah pengecualian untuk hal-hal yang mungkin tidak Anda pikirkan?
Draemon
4
Bukankah {}.get(index, '')lebih pythonic? Belum lagi lebih pendek lebih mudah dibaca.
Esteban Küber
1
Saya menggunakan dict [key] ketika saya mengharapkan kunci itu ada dan dict.get (key) ketika saya tidak yakin, dan saya sedang mencari sesuatu yang setara di sini. Mengembalikan Nonebukan -1 akan baik-baik saja, tetapi saat Anda berkomentar sendiri, str.find () mengembalikan -1 jadi mengapa tidak ada list.find () yang melakukan hal yang sama? Saya tidak membeli argumen "pythonic"
Draemon
3
Tetapi intinya adalah bahwa solusi paling pythonic adalah dengan hanya menggunakan try / kecuali dan bukan -1 nilai sentinel sama sekali. IE Anda harus menulis ulang otherfunction. Di sisi lain, jika tidak rusak, ...
Andrew Jaffe
53
thing_index = thing_list.index(elem) if elem in thing_list else -1

Satu baris. Sederhana. Tidak ada pengecualian.

Emil Ivanov
sumber
35
Sederhana ya, tetapi itu akan melakukan dua pencarian linier dan sementara kinerja bukanlah masalah per-se, itu tampaknya berlebihan.
Draemon
4
@Draemon: Setuju - itu akan melakukan 2 lintasan - tetapi tidak mungkin bahwa dari basis kode seribu baris ini akan menjadi penghambat. :) Seseorang selalu dapat memilih untuk solusi penting dengan for.
Emil Ivanov
dengan lambdindexOf = lambda item,list_ : list_.index(item) if item in list_ else -1 # OR None
Alaa Akiel
17

The dictjenis memiliki getfungsi , di mana jika kunci tidak ada dalam kamus, argumen 2 untuk getadalah nilai yang harus kembali. Demikian pula ada setdefault, yang mengembalikan nilai dalam dictjika kunci ada, jika tidak, menetapkan nilai sesuai dengan parameter default Anda dan kemudian mengembalikan parameter default Anda.

Anda bisa memperluas listtipe untuk memiliki getindexdefaultmetode.

class SuperDuperList(list):
    def getindexdefault(self, elem, default):
        try:
            thing_index = self.index(elem)
            return thing_index
        except ValueError:
            return default

Yang kemudian bisa digunakan seperti:

mylist = SuperDuperList([0,1,2])
index = mylist.getindexdefault( 'asdf', -1 )
Ross Rogers
sumber
6

Tidak ada yang salah dengan kode yang Anda gunakan ValueError. Berikut satu baris lain jika Anda ingin menghindari pengecualian:

thing_index = next((i for i, x in enumerate(thing_list) if x == thing), -1)
jfs
sumber
Apa itu python 2.6? Saya tahu saya tidak menyebutkannya, tetapi saya menggunakan 2.5. Ini mungkin yang akan saya lakukan di 2.6
Draemon
1
@Draemon: Ya, next()fungsi ada di Python 2.6+. Tetapi mudah diimplementasikan untuk 2.5, lihat implementasi fungsi next () untuk Python 2.5
jfs
4

Masalah ini adalah salah satu filosofi bahasa. Di Jawa misalnya, selalu ada tradisi bahwa pengecualian seharusnya hanya digunakan dalam "keadaan luar biasa" yaitu ketika kesalahan terjadi, bukan untuk kontrol aliran. . Pada awalnya ini untuk alasan kinerja karena pengecualian Java lambat tetapi sekarang ini telah menjadi gaya yang diterima.

Sebaliknya Python selalu menggunakan pengecualian untuk menunjukkan aliran program normal, seperti menaikkan a ValueErrorseperti yang kita bahas di sini. Tidak ada yang "kotor" tentang ini dalam gaya Python dan masih banyak lagi dari mana asalnya. Contoh yang lebih umum adalah StopIterationpengecualian yang dimunculkan oleh metode iterator untuk next()memberi sinyal bahwa tidak ada nilai lebih lanjut.

Tendayi Mawushe
sumber
Sebenarnya, JDK melempar cara terlalu banyak pengecualian diperiksa, jadi saya tidak yakin bahwa filsafat sebenarnya diterapkan ke Jawa. Saya tidak memiliki masalah sendiri StopIterationkarena sudah jelas apa arti pengecualiannya. ValueErrorsedikit terlalu umum.
Draemon
Saya mengacu pada gagasan bahwa pengecualian tidak boleh digunakan untuk kontrol aliran: c2.com/cgi/wiki?DontUseExceptionsForFlowControl , bukan jumlah pengecualian yang dicentang yang dimiliki Java yang merupakan diskusi lain: mindview.net/Etc/Discussions / CheckedExceptions
Tendayi Mawushe
4

Jika Anda sering melakukan ini, maka lebih baik untuk membakarnya di dalam fungsi pembantu:

def index_of(val, in_list):
    try:
        return in_list.index(val)
    except ValueError:
        return -1 
Veneet Reddy
sumber
4

Bagaimana dengan ini 😃:

li = [1,2,3,4,5] # create list 

li = dict(zip(li,range(len(li)))) # convert List To Dict 
print( li ) # {1: 0, 2: 1, 3: 2, 4:3 , 5: 4}
li.get(20) # None 
li.get(1)  # 0 
Alaa Akiel
sumber
1

Bagaimana dengan ini:

otherfunction(thing_collection, thing)

Daripada mengekspos sesuatu yang sangat tergantung pada implementasi seperti indeks daftar di antarmuka fungsi, teruskan koleksi dan hal tersebut dan biarkan fungsi lain menangani masalah "uji keanggotaan". Jika fungsi lain ditulis menjadi collection-type-agnostic, maka mungkin akan dimulai dengan:

if thing in thing_collection:
    ... proceed with operation on thing

yang akan berfungsi jika thing_collection adalah list, tuple, set, atau dict.

Ini mungkin lebih jelas dari:

if thing_index != MAGIC_VALUE_INDICATING_NOT_A_MEMBER:

yang merupakan kode yang sudah Anda miliki di fungsi lain.

PaulMcG
sumber
1

Bagaimana dengan seperti ini:

temp_inx = (L + [x]).index(x) 
inx = temp_inx if temp_inx < len(L) else -1
Jie Xiong
sumber
0

Saya memiliki masalah yang sama dengan metode ".index ()" di daftar. Saya tidak memiliki masalah dengan fakta bahwa ini melontarkan pengecualian tetapi saya sangat tidak setuju dengan fakta bahwa ini adalah ValueError yang tidak deskriptif. Aku bisa mengerti jika itu adalah IndexError.

Saya dapat melihat mengapa mengembalikan "-1" akan menjadi masalah juga karena ini adalah indeks yang valid dalam Python. Tapi secara realistis, saya tidak pernah mengharapkan metode ".index ()" untuk mengembalikan angka negatif.

Ini dia satu baris (ok, ini adalah baris yang agak panjang ...), melewati daftar tepat sekali dan mengembalikan "Tidak ada" jika item tidak ditemukan. Akan mudah untuk menulis ulang untuk mengembalikan -1, jika Anda menginginkannya.

indexOf = lambda list, thing: \
            reduce(lambda acc, (idx, elem): \
                   idx if (acc is None) and elem == thing else acc, list, None)

Cara Penggunaan:

>>> indexOf([1,2,3], 4)
>>>
>>> indexOf([1,2,3], 1)
0
>>>
haavee
sumber
-2

Saya tidak tahu mengapa Anda harus berpikir itu kotor ... karena pengecualian? jika Anda ingin oneliner, ini dia:

thing_index = thing_list.index(elem) if thing_list.count(elem) else -1

tetapi saya akan menyarankan agar tidak menggunakannya; Menurut saya solusi Ross Rogers adalah yang terbaik, gunakan objek untuk merangkum perilaku Anda yang diinginkan, jangan mencoba memaksakan bahasa hingga batasnya dengan mengorbankan keterbacaan.

Alan Franzoni
sumber
1
Ya, karena pengecualian. Kode Anda akan melakukan dua pencarian linier, bukan? Bukan berarti performa sangat penting di sini. Solusi SuperDuperList bagus, tetapi tampaknya berlebihan dalam situasi khusus ini. Saya pikir saya hanya akan mendapatkan pengecualian, tetapi saya ingin melihat apakah ada cara yang lebih bersih (untuk estetika saya).
Draemon
@Draemon: baik Anda akan merangkum kode yang Anda miliki ke dalam find()fungsi dan semuanya akan bersih;)
SilentGhost
1
Sangat mengherankan bahwa jawaban saya memiliki dua suara negatif, sementara jawaban Emil Ivanov, meski identik secara semantik, adalah yang paling disukai. Kemungkinan besar ini terjadi karena milik saya lebih lambat, karena saya menggunakan count () daripada operator "dalam" ... setidaknya ada komentar yang mengatakan itu akan sangat bagus, meskipun :-)
Alan Franzoni
-2

Saya sarankan:

if thing in thing_list:
  list_index = -1
else:
  list_index = thing_list.index(thing)
Jonas
sumber
2
Masalah dengan solusi ini adalah bahwa "-1" adalah indeks yang valid dalam daftar (indeks terakhir; indeks pertama dari akhir). Cara yang lebih baik untuk menangani ini adalah mengembalikan False di cabang pertama kondisi Anda.
FanaticD