dictmemiliki versi get yang nyaman:

get(key[, default])

Kembalikan nilai untuk kunci jika kunci ada dalam kamus, jika tidak default. Jika default tidak diberikan, defaultnya adalah None, sehingga metode ini tidak pernah memunculkan KeyError.

Penekanan saya yang berani, karena saya mencari dan tidak dapat menemukan versi yang setara untuk daftar. Jadi saya menerapkan sendiri:

In [148]: class myList(list):
     ...:     def pop(self, idx=-1, default=None):
     ...:         try:
     ...:             return super().pop(idx)
     ...:         except IndexError:
     ...:             return default

Ini berfungsi seperti yang diharapkan:

In [149]: l = myList([1, 2, 3, 4, 5])

In [150]: l.pop()
Out[150]: 5

In [151]: l.pop(12345, 'default')
Out[151]: 'default'

Karena ini berfungsi, ini juga dapat diperluas ke set. Bagaimanapun, saya punya beberapa pertanyaan:

  1. Apakah ada pihak yang lebih mudah/inbuilt/3 yang setara dengan apa yang telah saya lakukan yang tidak mengharuskan saya untuk memperluas kelas list?

  2. Jika tidak ada, apakah ada alasan khusus mengapa? Saya percaya ini akan menjadi perilaku yang berguna untuk dimiliki.

Beberapa kasus penggunaan tertentu yang dapat saya pikirkan akan memanggil pop pada daftar yang ukurannya tidak Anda ketahui, di tempat yang tidak Anda inginkan, atau tidak dapat menangkap kesalahan, seperti daftar comp.


Pertanyaan ini membahas topik yang sama tetapi jawabannya tidak sesuai dengan kebutuhan saya, seperti yang telah saya jelaskan di atas.

2
cs95 8 Agustus 2017, 10:23

2 jawaban

Jawaban Terbaik

Salah satu alasan mengapa ini hanya diterapkan untuk kamus adalah bahwa pemeriksaan if key in some_dict tidak selalu merupakan operasi O(1) dalam kasus terburuk bisa jadi O(n). Tetapi bahkan jika pencariannya O(1) sebenarnya ini adalah operasi yang mahal karena Anda perlu hash kunci, maka jika ada kecocokan Anda perlu membandingkan kunci dengan kunci yang disimpan untuk kesetaraan. Itu membuat LBYL sangat mahal untuk kamus.

Untuk daftar di sisi lain memeriksa bahwa indeks berada dalam batas adalah operasi yang cukup murah. Maksud saya, Anda dapat memeriksa apakah itu dalam batas dengan sederhana:

valid_index = -len(some_list) <= index < len(some_list)

Tapi itu hanya untuk pendekatan LBYL. Seseorang selalu dapat menggunakan EAFP dan menangkap Pengecualian.

Itu harus membebankan overhead yang sama ke daftar dan dikte. Jadi mengapa dict metode get untuk menghindari penanganan pengecualian? Alasannya sebenarnya cukup sederhana: dict adalah blok bangunan fundamental dari hampir semua hal. Sebagian besar kelas dan semua modul pada dasarnya hanyalah kamus dan banyak kode pengguna yang sebenarnya menggunakan kamus. Itu (dan masih) layak untuk memiliki metode yang dapat melakukan pengembalian yang dijamin tanpa pengecualian penanganan overhead.

Untuk daftar itu bisa berguna juga (saya tidak begitu yakin tentang hal itu) tapi saya pikir dalam banyak kasus ketika itu melempar IndexError itu terjadi secara tidak sengaja, bukan dengan sengaja. Jadi itu akan menyembunyikan "bug" nyata jika itu akan mengembalikan Nones.

Itu hanya alasan saya tentang ini. Saya bisa jadi pengembang Python memiliki alasan yang sama sekali berbeda untuk perilaku saat ini.


Mengenai pertanyaan pertama Anda:

Apakah ada pihak yang lebih mudah/inbuilt/3 yang setara dengan apa yang telah saya lakukan yang tidak mengharuskan saya untuk memperluas kelas daftar?

Saya juga tidak tahu perpustakaan standar atau modul pihak ke-3 untuk operasi ini dengan default untuk daftar atau set. Beberapa perpustakaan menerapkan "daftar jarang" (daftar yang diisi dengan nilai default) tetapi yang saya lihat tidak menangani kasus "daftar kosong" dan mereka menggunakan satu default untuk daftar lengkap .

Namun mungkin ada beberapa opsi yang mungkin menarik jika Anda tidak ingin melakukan "penanganan pengecualian" sendiri:

Cara lain untuk mengatasi masalah umum di sini adalah dengan membuat fungsi yang memanggil fungsi lain dan dalam kasus pengecualian tertentu akan mengembalikan nilai default jika tidak, hasil dari pemanggilan fungsi:

def call_with_default(func, default=None, *exceptions):
    def inner(*args, **kwargs):
        try:
            res = func(*args, **kwargs)
        except exceptions:
            res = default
        return res
    return inner

>>> a = [1,2,3,4]
>>> a_pop_with_default = call_with_default(a.pop, 'abc', IndexError)
>>> [a_pop_with_default(2) for _ in range(10)]
[3, 4, 'abc', 'abc', 'abc', 'abc', 'abc', 'abc', 'abc', 'abc']
3
MSeifert 8 Agustus 2017, 08:46

Mengapa Anda tidak bisa menggunakan sesuatu yang sederhana seperti ini?

my_list.pop(n) if n < len(my_list) else "default"

EDIT

Komentar yang bagus. Apa pendapat Anda tentang hal seperti ini?

def list_popper(my_list, idx=-1, default='default'):
    try:
        default = my_list.pop(idx)
    except IndexError:
        return default

Fungsionalitasnya sama dengan metode pewarisan kelas OP. Namun, demi keterbacaan dan pemeliharaan di masa mendatang, saya biasanya lebih suka fungsi sederhana daripada pembuatan kelas biasa.

1
Alexander 8 Agustus 2017, 08:14