Mengapa daftar tidak memiliki metode “get” yang aman seperti kamus?

264

Mengapa daftar tidak memiliki metode "get" aman seperti kamus?

>>> d = {'a':'b'}
>>> d['a']
'b'
>>> d['c']
KeyError: 'c'
>>> d.get('c', 'fail')
'fail'

>>> l = [1]
>>> l[10]
IndexError: list index out of range
CSZ
sumber
1
Daftar digunakan untuk tujuan yang berbeda dari kamus. Get () tidak diperlukan untuk kasus penggunaan daftar. Namun, untuk kamus get () cukup sering berguna.
mgronber
42
Anda selalu bisa mendapatkan sublist kosong dari daftar tanpa menaikkan IndexError jika Anda meminta slice sebagai gantinya: l[10:11]alih-alih l[10], misalnya. () Sublist Th akan memiliki elemen yang diinginkan jika ada)
jsbueno
56
Bertentangan dengan beberapa di sini, saya mendukung gagasan tentang brankas .get. Itu akan menjadi setara dengan l[i] if i < len(l) else default, tetapi lebih mudah dibaca, lebih ringkas, dan memungkinkan untuk imenjadi ekspresi tanpa harus menghitung ulang
Paul Draper
6
Hari ini aku berharap ini ada. Saya menggunakan fungsi mahal yang mengembalikan daftar, tetapi saya hanya menginginkan item pertama, atau Nonejika tidak ada. Akan lebih baik untuk mengatakannya x = expensive().get(0, None)sehingga saya tidak perlu memasukkan pengembalian mahal yang tidak berguna ke dalam variabel sementara.
Ryan Hiebert
2
@Ryan jawaban saya dapat membantu Anda stackoverflow.com/a/23003811/246265
Jake

Jawaban:

112

Pada akhirnya itu mungkin tidak memiliki .getmetode yang aman karena a dictadalah kumpulan asosiatif (nilai dikaitkan dengan nama) di mana tidak efisien untuk memeriksa apakah kunci ada (dan mengembalikan nilainya) tanpa melemparkan pengecualian, sementara itu super sepele untuk menghindari pengecualian mengakses elemen daftar (karena lenmetode ini sangat cepat). The .getMetode memungkinkan Anda untuk query nilai yang terkait dengan nama, tidak langsung mengakses item ke-37 dalam kamus (yang akan lebih seperti apa yang Anda minta dari daftar Anda).

Tentu saja, Anda dapat dengan mudah menerapkan ini sendiri:

def safe_list_get (l, idx, default):
  try:
    return l[idx]
  except IndexError:
    return default

Anda bahkan dapat melakukan monkeypatch ke __builtins__.listkonstruktor di __main__, tetapi itu akan menjadi perubahan yang tidak terlalu luas karena sebagian besar kode tidak menggunakannya. Jika Anda hanya ingin menggunakan ini dengan daftar yang dibuat oleh kode Anda sendiri, Anda dapat dengan mudah membuat subkelas listdan menambahkan getmetode.

Nick Bastin
sumber
24
Python tidak mengizinkan monkeypatching tipe builtin sepertilist
Imran
7
@CSZ: .getmemecahkan masalah yang tidak dimiliki daftar - cara efisien untuk menghindari pengecualian saat mendapatkan data yang mungkin tidak ada. Sangat sepele dan sangat efisien untuk mengetahui apa itu indeks daftar yang valid, tetapi tidak ada cara yang sangat baik untuk melakukan ini untuk nilai-nilai kunci dalam kamus.
Nick Bastin
10
Saya tidak berpikir ini tentang efisiensi sama sekali - memeriksa apakah kunci ada dalam kamus dan / atau mengembalikan item O(1). Ini tidak akan cukup cepat dalam hal mentah seperti memeriksa len, tetapi dari sudut pandang kompleksitas mereka semua O(1). Jawaban yang benar adalah penggunaan / semantik yang khas ...
Mark Longair
3
@ Mark: Tidak semua O (1) dibuat sama. Juga, dicthanya yang terbaik O (1), tidak semua kasus.
Nick Bastin
4
Saya pikir orang kehilangan poin di sini. Diskusi tidak boleh tentang efisiensi. Harap hentikan pengoptimalan prematur. Jika program Anda terlalu lambat, Anda salah menggunakan .get()atau Anda memiliki masalah di tempat lain dalam kode Anda (atau lingkungan). Titik menggunakan metode seperti itu adalah keterbacaan kode. Teknik "vanilla" membutuhkan empat baris kode di setiap tempat yang perlu dilakukan. The .get()teknik hanya membutuhkan satu dan dapat dengan mudah dirantai dengan metode panggilan berikutnya (misalnya my_list.get(2, '').uppercase()).
Tyler Crompton
67

Ini berfungsi jika Anda ingin elemen pertama, seperti my_list.get(0)

>>> my_list = [1,2,3]
>>> next(iter(my_list), 'fail')
1
>>> my_list = []
>>> next(iter(my_list), 'fail')
'fail'

Saya tahu itu bukan apa yang Anda minta, tetapi mungkin bisa membantu orang lain.

Jake
sumber
7
kurang pythonic daripada fungsional pemrograman-esque
Eric
next(iter(my_list[index:index+1]), 'fail')Memungkinkan untuk indeks apapun, bukan hanya 0 Atau kurang FP tapi bisa dibilang lebih Pythonic, dan hampir pasti lebih mudah dibaca: my_list[index] if index < len(my_list) else 'fail'.
alphabetasoup
47

Mungkin karena itu tidak masuk akal untuk daftar semantik. Namun, Anda dapat dengan mudah membuatnya sendiri dengan mensubclassing.

class safelist(list):
    def get(self, index, default=None):
        try:
            return self.__getitem__(index)
        except IndexError:
            return default

def _test():
    l = safelist(range(10))
    print l.get(20, "oops")

if __name__ == "__main__":
    _test()
Keith
sumber
5
Sejauh ini, inilah yang paling pythonic menjawab OP. Perhatikan bahwa Anda juga bisa mengekstrak sublist, yang merupakan operasi aman di Python. Diberi daftar saya = [1, 2, 3], Anda dapat mencoba mengekstrak elemen ke-9 dengan daftar saya [8: 9] tanpa memicu pengecualian. Anda kemudian dapat menguji apakah daftar itu kosong dan, jika itu tidak kosong, ekstrak elemen tunggal dari daftar yang dikembalikan.
jose.angel.jimenez
1
Ini harus menjadi jawaban yang diterima, bukan peretas satu-baris non-pythonic lainnya, terutama karena itu menghemat simetri dengan kamus.
Eric
1
Tidak ada yang pythonic tentang subclass daftar Anda sendiri hanya karena Anda memerlukan getmetode yang bagus . Jumlah keterbacaan diperhitungkan. Dan keterbacaan menderita dengan setiap kelas tambahan yang tidak perlu. Cukup gunakan try / exceptpendekatan tanpa membuat subclass.
Jeyekomon
@Jeyekomon Sangat sempurna untuk mengurangi boilerplate dengan subclassing.
Keith
42

Alih-alih menggunakan .get, menggunakan seperti ini seharusnya ok untuk daftar. Hanya perbedaan penggunaan.

>>> l = [1]
>>> l[10] if 10 < len(l) else 'fail'
'fail'
KAMU
sumber
15
Ini gagal jika kita mencoba untuk mendapatkan elemen terbaru dengan -1.
pretobomba
Perhatikan bahwa ini tidak berfungsi untuk objek daftar yang ditautkan secara melingkar. Selain itu, sintaksisnya menyebabkan apa yang saya suka sebut sebagai "blok pemindaian". Saat memindai kode untuk melihat apa yang dilakukannya, ini adalah baris yang akan memperlambat saya sejenak.
Tyler Crompton
sebaris jika / tidak bekerja dengan python yang lebih tua seperti 2.6 (atau 2,5?)
Eric
3
@TylerCrompton: Tidak ada daftar tertaut melingkar di python. Jika Anda menulis sendiri, Anda bebas menerapkan .getmetode (kecuali saya tidak yakin bagaimana Anda akan menjelaskan apa arti indeks dalam kasus ini, atau mengapa itu akan gagal).
Nick Bastin
Alternatif yang menangani indeks negatif di luar batas adalahlst[i] if -len(lst) <= i < len(l) else 'fail'
mic
17

Coba ini:

>>> i = 3
>>> a = [1, 2, 3, 4]
>>> next(iter(a[i:]), 'fail')
4
>>> next(iter(a[i + 1:]), 'fail')
'fail'
Vsevolod Kulaga
sumber
1
Saya suka yang ini, meskipun itu memerlukan pembuatan sub-daftar baru terlebih dahulu.
Rick mendukung Monica
15

Penghargaan untuk jose.angel.jimenez


Untuk penggemar "oneliner" ...


Jika Anda ingin elemen pertama daftar atau jika Anda ingin nilai default jika daftar kosong coba:

liste = ['a', 'b', 'c']
value = (liste[0:1] or ('default',))[0]
print(value)

kembali a

dan

liste = []
value = (liste[0:1] or ('default',))[0]
print(value)

kembali default


Contoh untuk elemen lain ...

liste = ['a', 'b', 'c']
print(liste[0:1])  # returns ['a']
print(liste[1:2])  # returns ['b']
print(liste[2:3])  # returns ['c']

Dengan fallback default ...

liste = ['a', 'b', 'c']
print((liste[0:1] or ('default',))[0])  # returns a
print((liste[1:2] or ('default',))[0])  # returns b
print((liste[2:3] or ('default',))[0])  # returns c

Diuji dengan Python 3.6.0 (v3.6.0:41df79263a11, Dec 22 2016, 17:23:13)

qräbnö
sumber
1
Alternatif pendek: value, = liste[:1] or ('default',). Sepertinya Anda membutuhkan tanda kurung.
qräbnö
13

Hal terbaik yang dapat Anda lakukan adalah mengubah daftar menjadi dict dan kemudian mengaksesnya dengan metode get:

>>> my_list = ['a', 'b', 'c', 'd', 'e']
>>> my_dict = dict(enumerate(my_list))
>>> print my_dict
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e'}
>>> my_dict.get(2)
'c'
>>> my_dict.get(10, 'N/A')
hebat
sumber
20
Solusi yang masuk akal, tetapi bukan "hal terbaik yang dapat Anda lakukan".
tripleee
3
Sangat tidak efisien. Catatan: Alih-alih zip range lenhal itu, orang hanya bisa menggunakandict(enumerate(my_list))
Marian
3
Ini bukan hal terbaik, itu hal terburuk yang bisa Anda lakukan.
erikbwork
3
Ini hal terburuk jika Anda mempertimbangkan kinerja ... jika Anda peduli kinerja Anda tidak kode dalam bahasa yang ditafsirkan seperti python. Saya menemukan solusi ini menggunakan kamus yang agak elegan, kuat dan pythonic. Optimalisasi awal itu jahat, jadi mari kita buat dikt dan lihat nanti itu adalah hambatan.
Eric
7

Jadi saya melakukan penelitian lebih lanjut tentang ini dan ternyata tidak ada yang spesifik untuk ini. Saya merasa senang ketika saya menemukan list.index (nilai), itu mengembalikan indeks dari item yang ditentukan, tetapi tidak ada apa pun untuk mendapatkan nilai pada indeks tertentu. Jadi jika Anda tidak ingin menggunakan solusi safe_list_get yang menurut saya cukup bagus. Berikut adalah beberapa kalimat jika pernyataan yang dapat menyelesaikan pekerjaan untuk Anda tergantung pada skenario:

>>> x = [1, 2, 3]
>>> el = x[4] if len(x) > 4 else 'No'
>>> el
'No'

Anda juga dapat menggunakan Tidak Ada alih-alih 'Tidak', yang lebih masuk akal .:

>>> x = [1, 2, 3]
>>> i = 2
>>> el_i = x[i] if len(x) == i+1 else None

Juga jika Anda ingin mendapatkan item pertama atau terakhir dalam daftar, ini berfungsi

end_el = x[-1] if x else None

Anda juga dapat menjadikan ini fungsi tetapi saya masih menyukai solusi pengecualian IndexError. Saya bereksperimen dengan versi safe_list_getsolusi bodoh dan membuatnya sedikit lebih sederhana (tidak ada default):

def list_get(l, i):
    try:
        return l[i]
    except IndexError:
        return None

Belum diperbandingkan untuk melihat apa yang tercepat.

radtek
sumber
1
Tidak terlalu pythonic.
Eric
@Eric cuplikan mana? Saya pikir coba, kecuali paling masuk akal dengan melihatnya lagi.
radtek
Fungsi mandiri bukanlah pythonic. Pengecualian memang sedikit lebih pythonic tetapi tidak sebanyak pola umum dalam bahasa pemrograman. Terlebih lagi pythonic adalah objek baru yang memperluas tipe builtin listdengan mensubklasifikasikannya. Dengan cara itu konstruktor dapat mengambil listatau apa pun yang berperilaku seperti daftar, dan contoh baru berperilaku seperti list. Lihat jawaban Keith di bawah ini yang seharusnya menjadi yang diterima IMHO.
Eric
1
@ Eric Saya parsing pertanyaan bukan sebagai OOP-spesifik tetapi sebagai "mengapa daftar tidak memiliki analogi untuk dict.get()mengembalikan nilai default dari referensi indeks daftar daripada harus menangkap IndexError? Jadi ini benar-benar tentang fitur bahasa / perpustakaan (dan bukan OOP vs. konteks FP) .Selain itu, seseorang mungkin harus memenuhi syarat penggunaan 'pythonic' Anda sebagai WWGD (karena penghinaannya terhadap FP Python sudah dikenal) dan tidak harus memuaskan PEP8 / 20 saja.
cowbert
1
el = x[4] if len(x) == 4 else 'No'- maksud Anda len(x) > 4? x[4]di luar batas jika len(x) == 4.
mic
4

Kamus untuk mencari. Masuk akal untuk bertanya apakah ada entri atau tidak. Daftar biasanya diulang. Tidak umum untuk bertanya apakah L [10] ada tetapi jika panjang L adalah 11.

Antrian Magang
sumber
Ya, setuju dengan Anda. Tapi saya hanya menguraikan url relatif halaman "/ group / Page_name". Membaginya dengan '/' dan ingin memeriksa apakah PageName sama dengan halaman tertentu. Akan lebih nyaman untuk menulis sesuatu seperti [url.split ('/'). Get_from_index (2, None) == "lalala"] daripada melakukan pemeriksaan ekstra untuk panjang atau menangkap pengecualian atau menulis fungsi sendiri. Mungkin Anda benar itu hanya dianggap tidak biasa. Pokoknya saya masih tidak setuju dengan ini =)
CSZ
@Nick Bastin: Tidak ada yang salah. Ini semua tentang kesederhanaan dan kecepatan pengkodean.
CSZ
Ini juga akan berguna jika Anda ingin menggunakan daftar sebagai kamus yang lebih hemat ruang jika kuncinya adalah int berturut-turut. Tentu saja keberadaan pengindeksan negatif sudah menghentikan itu.
Antimony
-1

Usecase Anda pada dasarnya hanya relevan ketika melakukan array dan matriks dengan panjang tetap, sehingga Anda tahu berapa lama mereka berada di depan. Dalam hal ini Anda biasanya juga membuat mereka sebelum mengisi mereka dengan None atau 0, sehingga sebenarnya setiap indeks yang akan Anda gunakan sudah ada.

Anda bisa mengatakan ini: Saya perlu .get () kamus cukup sering. Setelah sepuluh tahun sebagai programmer penuh waktu, saya rasa saya tidak pernah membutuhkannya dalam daftar. :)

Lennart Regebro
sumber
Bagaimana dengan contoh saya di komentar? Apa yang lebih sederhana dan mudah dibaca? (url.split ('/'). getFromIndex (2) == "lalala") ATAU (result = url.split (); len (hasil)> 2 dan hasil [2] == "lalala"). Dan ya, saya tahu saya bisa menulis fungsi seperti itu sendiri =) tetapi saya terkejut fungsi tersebut tidak dibangun.
CSZ
1
Id 'katakan dalam kasus Anda Anda melakukannya salah. Penanganan URL harus dilakukan dengan rute (pencocokan pola) atau traversal objek. Tapi, untuk menjawab kasus tertentu Anda: 'lalala' in url.split('/')[2:]. Tetapi masalah dengan solusi Anda di sini adalah bahwa Anda hanya melihat elemen kedua. Bagaimana jika URLnya adalah '/ monkeybonkey / lalala'? Anda akan mendapatkan Truemeskipun URL tidak valid.
Lennart Regebro
Saya hanya mengambil elemen kedua karena saya hanya membutuhkan elemen kedua. Tapi ya, irisan tampaknya merupakan alternatif kerja yang baik
CSZ
@ CSZ: Tapi kemudian elemen pertama diabaikan, dan dalam hal ini Anda bisa melewatkannya. :) Lihat apa yang saya maksud, contohnya tidak bekerja dengan baik di kehidupan nyata.
Lennart Regebro