Python idiom untuk mengembalikan item pertama atau Tidak Ada


274

Saya yakin ada cara sederhana untuk melakukan ini yang tidak terjadi pada saya.

Saya memanggil banyak metode yang mengembalikan daftar. Daftarnya mungkin kosong. Jika daftar ini tidak kosong, saya ingin mengembalikan item pertama; jika tidak, saya ingin mengembalikan None. Kode ini berfungsi:

my_list = get_list()
if len(my_list) > 0: return my_list[0]
return None

Tampaknya bagi saya harus ada idiom satu garis sederhana untuk melakukan ini, tetapi untuk kehidupan saya, saya tidak dapat memikirkannya. Disana?

Edit:

Alasan saya mencari ekspresi satu baris di sini bukan karena saya suka kode yang sangat singkat, tetapi karena saya harus menulis banyak kode seperti ini:

x = get_first_list()
if x:
    # do something with x[0]
    # inevitably forget the [0] part, and have a bug to fix
y = get_second_list()
if y:
    # do something with y[0]
    # inevitably forget the [0] part AGAIN, and have another bug to fix

Apa yang ingin saya lakukan tentu saja dapat dicapai dengan suatu fungsi (dan mungkin akan):

def first_item(list_or_none):
    if list_or_none: return list_or_none[0]

x = first_item(get_first_list())
if x:
    # do something with x
y = first_item(get_second_list())
if y:
    # do something with y

Saya memposting pertanyaan karena saya sering terkejut dengan apa yang bisa dilakukan ekspresi sederhana dengan Python, dan saya pikir menulis fungsi adalah hal yang konyol untuk dilakukan jika ada ekspresi sederhana yang bisa melakukan trik. Tetapi melihat jawaban ini, sepertinya fungsi adalah solusi sederhana.


28
btw, pada Python 2.6+ Anda bisa menggunakan next(iter(your_list), None)alih-alih first_item(your_list)dengan asumsi your_listtidak None( get_first_list()dan get_second_list()harus selalu mengembalikan iterable).
jfs

1
Saya pikir maksud Anda next(iter(your_list)), karena jika Anda memberikan argumen kedua iter, Anda mengatakan bahwa argumen pertama dapat dipanggil.
Robert Rossney

7
Tidak, Noneini parameter kedua untuk next(), bukan iter(). next()mengembalikan parameter kedua jika diberikan alih-alih menaikkan StopIterationjika your_listkosong.
jfs

Solusi yang disarankan oleh @JFSebastian ada dengan pertanyaan rangkap: stackoverflow.com/a/18533669/144408
shahjapan

Jawaban:


205

Python 2.6+

next(iter(your_list), None)

Jika your_listbisa None:

next(iter(your_list or []), None)

Python 2.4

def get_first(iterable, default=None):
    if iterable:
        for item in iterable:
            return item
    return default

Contoh:

x = get_first(get_first_list())
if x:
    ...
y = get_first(get_second_list())
if y:
    ...

Pilihan lain adalah dengan menggarisbawahi fungsi di atas:

for x in get_first_list() or []:
    # process x
    break # process at most one item
for y in get_second_list() or []:
    # process y
    break

Untuk menghindari breakAnda bisa menulis:

for x in yield_first(get_first_list()):
    x # process x
for y in yield_first(get_second_list()):
    y # process y

Dimana:

def yield_first(iterable):
    for item in iterable or []:
        yield item
        return

1
Opsi terakhir itu hampir persis seperti yang saya cari: sudah jelas, itu berfungsi, dan tidak mengharuskan saya untuk mendefinisikan fungsi baru. Saya akan mengatakan "persis" jika istirahat itu entah bagaimana tidak diperlukan, karena risiko menghilangkannya tidak signifikan. Tetapi pendekatan ini memiliki cincin kebenaran.
Robert Rossney

Oh, aku tidak suka ini sama sekali. Jika ada item dalam daftar yang mengevaluasi False, nilai itu dibuang dan diganti. Jika Anda memiliki string kosong ""dalam daftar, itu dibuang dan diganti dengan daftar kosong []. Jika Anda memiliki 0, juga diganti dengan []. Jika sudah Falseada di sana, ganti juga. Dll. Anda mungkin lolos dengan ini dalam kasus tertentu, tetapi ini adalah kebiasaan buruk untuk dikembangkan.
steveha

4
@steveha: bool(lst) memberi tahu kami apakah len(lst) > 0item tersebut tidak memberi tahu kami tentang item apa yang lstberisi mis., bool([False]) == True;oleh karena itu ekspresi [False] or []kembali [False], hal yang sama juga terjadi untuk [0] or [] mengembalikannya [0].
jfs

@RobertRossney: Saya menambahkan yield_first()untuk menghindari breakpernyataan.
jfs

1
@ Thomas: keduanya benar. Saya telah memperbarui jawaban untuk memberikan solusi untuk kasus yang tidak ada.
jfs

218

Cara terbaik adalah ini:

a = get_list()
return a[0] if a else None

Anda juga bisa melakukannya dalam satu baris, tetapi jauh lebih sulit bagi programmer untuk membaca:

return (get_list()[:1] or [None])[0]

Ups. Seharusnya disebutkan ini adalah aplikasi Python 2.4.
Robert Rossney

15
@ Adam: masa depan dapat digunakannext(iter(your_list), None)
jfs

@ JSFSebastian sebenarnya next(iter(your_list))sudah cukup
Brad Johnson

13
@BradleagheJohnson: salah. Itu meningkatkan StopIteration bukannya kembali Noneseperti yang diminta OP. Cobalah: next(iter([])).
jfs

72
(get_list() or [None])[0]

Itu seharusnya bekerja.

BTW Saya tidak menggunakan variabel list, karena itu menimpa list()fungsi builtin .

Sunting: Saya memiliki versi yang sedikit lebih sederhana, tetapi salah di sini sebelumnya.


4
Ini cerdas dan berfungsi, tetapi saya pikir "return foo [0] jika foo else None" seperti yang disarankan oleh efotinis sedikit lebih mudah diikuti untuk pemeliharaan.
Jay

3
Pertanyaannya adalah meminta solusi satu baris, jadi saya mengesampingkannya.
Rekursif

Tak satu pun dari ekspresi itu yang berfungsi jika get_list () mengembalikan Tidak ada; Anda mendapatkan "TypeError: objek 'NoneType' tidak dapat disubkripsikan." atau "TypeError: tipe operan yang tidak didukung untuk +: 'NoneType' dan 'list'."
Robert Rossney

4
Dalam pertanyaan tersebut, dikatakan bahwa metode mengembalikan daftar, yang tidak satupun tidak satu. Jadi mengingat persyaratannya, saya masih percaya keduanya bekerja.
Rekursif

Harus berfungsi sekarang jika get_list () mengembalikan Tidak ada karena tidak mendapatkan subskrip atau ditambahkan lagi.
Robert

32

Cara idiom paling python adalah dengan menggunakan next () pada iterator karena daftar iterable . seperti apa yang dimasukkan @JFSebastian dalam komentar pada 13 Des 2011.

next(iter(the_list), None)Ini mengembalikan None jika the_listkosong. lihat selanjutnya () Python 2.6+

atau jika Anda tahu pasti the_listtidak kosong:

iter(the_list).next()lihat iterator.next () Python 2.2+


1
Ini memiliki keuntungan bahwa Anda tidak harus menetapkan daftar Anda ke variabel seperti return l[0] if l else Nonejika daftar tersebut adalah libcomp. Ketika daftar sebenarnya adalah genexpr, ini bahkan lebih baik karena Anda tidak harus membangun seluruh daftar seperti dinext(iter(x for x in range(10) if x % 2 == 0), None)
RubenLaguna

Jika Anda tahu pasti the_listtidak kosong, Anda akan menulis the_list[0].
kaya3

12

Jika Anda mencoba untuk memetik hal pertama (atau Tidak Ada) dari pemahaman daftar, Anda dapat beralih ke generator untuk melakukannya seperti:

next((x for x in blah if cond), None)

Pro: berfungsi jika bla tidak dapat diindeks Con: itu sintaksis yang tidak dikenal. Ini berguna saat meretas dan memfilter hal-hal di ipython.


11

Solusi OP hampir tiba, hanya ada beberapa hal untuk membuatnya lebih Pythonic.

Untuk satu, tidak perlu mendapatkan panjang daftar. Daftar kosong di Python mengevaluasi ke False dalam tanda centang if. Cukup katakan

if list:

Selain itu, itu adalah ide yang sangat buruk untuk menetapkan variabel yang tumpang tindih dengan kata-kata yang dipesan. "daftar" adalah kata yang disediakan dalam Python.

Jadi mari kita ubah ke

some_list = get_list()
if some_list:

Poin yang sangat penting yang terlewatkan oleh banyak solusi di sini adalah bahwa semua fungsi / metode Python mengembalikan None secara default . Coba yang berikut di bawah ini.

def does_nothing():
    pass

foo = does_nothing()
print foo

Jika Anda tidak perlu mengembalikan None untuk menghentikan fungsi lebih awal, Anda tidak perlu mengembalikannya secara eksplisit. Cukup ringkas, kembalikan saja entri pertama, jika ada.

some_list = get_list()
if some_list:
    return list[0]

Dan akhirnya, mungkin ini tersirat, tetapi hanya untuk menjadi eksplisit (karena eksplisit lebih baik daripada implisit ), Anda seharusnya tidak memiliki fungsi Anda mendapatkan daftar dari fungsi lain; cukup kirimkan sebagai parameter. Jadi, hasil akhirnya adalah

def get_first_item(some_list): 
    if some_list:
        return list[0]

my_list = get_list()
first_item = get_first_item(my_list)

Seperti yang saya katakan, OP sudah hampir sampai, dan hanya beberapa sentuhan memberikan rasa Python yang Anda cari.


1
Mengapa 'daftar' kata yang disediakan dalam python? Atau, lebih tepatnya, di mana ia mengatakan bahwa 'daftar' adalah kata reserver dengan python? Atau, lebih tepatnya, bagaimana Anda memverifikasi bahwa 'daftar' adalah kata yang dilindungi undang-undang dengan python? Menimbang bahwa saya dapat mendeklarasikan variabel bernama 'daftar', itu jelas bukan kata yang dipesan.
Lasse V. Karlsen

2
Poin yang diambil. Kata-kata yang benar-benar dicadangkan dalam Python dapat ditemukan di sini tinyurl.com/424663 Namun, itu ide yang baik untuk mempertimbangkan fungsi dan tipe builtin sebagai dicadangkan. list () sebenarnya menghasilkan daftar. Hal yang sama berlaku untuk dict, range, dll.
Gotgenes

Saya akan menggabungkan dua baris terakhir, menghilangkan variabel sementara (kecuali Anda perlu my_list lebih lanjut). Itu meningkatkan keterbacaan.
Svante

Ini adalah jawaban yang saya sukai.
Robert Rossney

1
get_first_itemharus menerima defaultparameter (seperti dict.get) yang secara default menjadi Tidak ada. Dengan demikian antarmuka fungsi berkomunikasi secara eksplisit apa yang terjadi jika my_listkosong.
jfs

3
for item in get_list():
    return item

Ini singkat dan indah.
Gotgenes

Tragisnya, itu tidak berhasil; itu melempar "TypeError: objek 'NoneType' tidak iterable" pengecualian jika get_list () mengembalikan None.
Robert Rossney

Untuk memperbaiki: "untuk item di get_list () atau []:"
itsadok

5
Pertanyaannya dengan jelas mengira get_list mengembalikan daftar. len dan getitem dipanggil pada hasilnya.
A. Coady

2

Terus terang, saya tidak berpikir ada idiom yang lebih baik: Anda jelas dan singkat - tidak perlu untuk sesuatu yang "lebih baik". Mungkin, tetapi ini benar-benar masalah selera, Anda dapat mengubahnya if len(list) > 0:dengan if list:- daftar kosong akan selalu mengevaluasi ke False.

Pada catatan terkait, Python bukan Perl (tidak ada permainan kata-kata!), Anda tidak perlu mendapatkan kode paling keren.
Sebenarnya, kode terburuk yang saya lihat di Python, juga sangat keren :-) dan benar-benar tidak dapat dipelihara.

Ngomong-ngomong, sebagian besar solusi yang saya lihat di sini tidak mempertimbangkan ketika daftar [0] dievaluasi menjadi False (mis. String kosong, atau nol) - dalam hal ini, mereka semua mengembalikan Tidak ada dan bukan elemen yang benar.


Dalam Python, dianggap gaya yang baik untuk menulis if lst:daripada if len(lst) > 0:. Juga, jangan gunakan kata kunci Python sebagai nama variabel; itu berakhir dengan air mata. Biasa digunakan Latau lstsebagai nama variabel untuk beberapa daftar. Saya yakin dalam hal ini Anda tidak bermaksud menyarankan untuk menggunakan listsebagai nama variabel, Anda hanya bermaksud "daftar". Dan, +1 untuk "saat pertama [0] dievaluasi menjadi Salah".
steveha

Ya, saya hanya mempertahankan nama OP yang sama, untuk kejelasan yang lebih baik. Tapi Anda pasti benar: harus selalu berhati-hati untuk tidak menimpa kata kunci Python.
merampok

2

Ungkapan python untuk mengembalikan item pertama atau Tidak Ada?

Pendekatan paling Pythonic adalah apa yang ditunjukkan oleh jawaban yang paling banyak dipilih, dan itu adalah hal pertama yang muncul di benak saya ketika saya membaca pertanyaan itu. Berikut cara menggunakannya, pertama-tama jika daftar yang kosong dilewatkan ke fungsi:

def get_first(l): 
    return l[0] if l else None

Dan jika daftar dikembalikan dari suatu get_listfungsi:

l = get_list()
return l[0] if l else None

Cara lain ditunjukkan untuk melakukan ini di sini, dengan penjelasan

for

Ketika saya mulai mencoba memikirkan cara cerdas untuk melakukan ini, ini adalah hal kedua yang saya pikirkan:

for item in get_list():
    return item

Ini menganggap fungsi berakhir di sini, secara implisit mengembalikan Nonejika get_listmengembalikan daftar kosong. Kode eksplisit di bawah ini persis sama:

for item in get_list():
    return item
return None

if some_list

Berikut ini juga diusulkan (saya memperbaiki nama variabel yang salah) yang juga menggunakan implisit None. Ini lebih disukai daripada yang di atas, karena menggunakan pemeriksaan logis alih-alih iterasi yang mungkin tidak terjadi. Ini harus lebih mudah untuk segera memahami apa yang terjadi. Tetapi jika kita menulis untuk keterbacaan dan pemeliharaan, kita juga harus menambahkan eksplisit return Nonedi akhir:

some_list = get_list()
if some_list:
    return some_list[0]

iris or [None]dan pilih indeks nol

Jawaban ini juga dalam jawaban yang paling banyak dipilih:

return (get_list()[:1] or [None])[0]

Iris tidak perlu, dan membuat daftar satu item tambahan dalam memori. Yang berikut harus lebih performan. Untuk menjelaskan, ormengembalikan elemen kedua jika yang pertama Falsedalam konteks boolean, jadi jika get_listmengembalikan daftar kosong, ekspresi yang terkandung dalam tanda kurung akan mengembalikan daftar dengan 'Tidak Ada', yang kemudian akan diakses oleh 0indeks:

return (get_list() or [None])[0]

Yang berikutnya menggunakan fakta itu dan mengembalikan item kedua jika yang pertama Truedalam konteks boolean, dan karena mereferensikan my_list dua kali, itu tidak lebih baik daripada ekspresi ternary (dan secara teknis bukan satu-liner):

my_list = get_list() 
return (my_list and my_list[0]) or None

next

Kemudian kita memiliki penggunaan pintar dari builtin nextdaniter

return next(iter(get_list()), None)

Untuk menjelaskan, iterkembalikan iterator dengan .nextmetode. ( .__next__dalam Python 3.) Kemudian builtin nextmemanggil .nextmetode itu, dan jika iterator habis, mengembalikan default yang kita berikan None,.

ekspresi ternary berlebihan ( a if b else c) dan berputar-putar kembali

Di bawah ini diusulkan, tetapi kebalikannya akan lebih disukai, karena logika biasanya lebih baik dipahami dalam hal positif daripada negatif. Karena get_listdipanggil dua kali, kecuali jika hasilnya memoise dalam beberapa cara, ini akan berkinerja buruk:

return None if not get_list() else get_list()[0]

Pembalikan yang lebih baik:

return get_list()[0] if get_list() else None

Lebih baik lagi, gunakan variabel lokal sehingga get_listhanya disebut satu kali, dan Anda memiliki solusi Pythonic yang direkomendasikan pertama kali dibahas:

l = get_list()
return l[0] if l else None

2

Mengenai idiom, ada resep itertools yang disebut nth.

Dari resep itertools:

def nth(iterable, n, default=None):
    "Returns the nth item or a default value"
    return next(islice(iterable, n, None), default)

Jika Anda ingin one-liners, pertimbangkan untuk menginstal perpustakaan yang mengimplementasikan resep ini untuk Anda, misalnya more_itertools:

import more_itertools as mit

mit.nth([3, 2, 1], 0)
# 3

mit.nth([], 0)                                             # default is `None`
# None

Alat lain tersedia yang hanya mengembalikan item pertama, yang disebut more_itertools.first.

mit.first([3, 2, 1])
# 3

mit.first([], default=None)
# None

Itertools ini secara umum skala untuk setiap iterable, tidak hanya untuk daftar.



1

Karena penasaran, saya menjalankan timing pada dua solusi. Solusi yang menggunakan pernyataan kembali untuk mengakhiri sebelum untuk loop sedikit lebih mahal pada mesin saya dengan Python 2.5.1, saya menduga ini ada hubungannya dengan pengaturan iterable.

import random
import timeit

def index_first_item(some_list):
    if some_list:
        return some_list[0]


def return_first_item(some_list):
    for item in some_list:
        return item


empty_lists = []
for i in range(10000):
    empty_lists.append([])

assert empty_lists[0] is not empty_lists[1]

full_lists = []
for i in range(10000):
    full_lists.append(list([random.random() for i in range(10)]))

mixed_lists = empty_lists[:50000] + full_lists[:50000]
random.shuffle(mixed_lists)

if __name__ == '__main__':
    ENV = 'import firstitem'
    test_data = ('empty_lists', 'full_lists', 'mixed_lists')
    funcs = ('index_first_item', 'return_first_item')
    for data in test_data:
        print "%s:" % data
        for func in funcs:
            t = timeit.Timer('firstitem.%s(firstitem.%s)' % (
                func, data), ENV)
            times = t.repeat()
            avg_time = sum(times) / len(times)
            print "  %s:" % func
            for time in times:
                print "    %f seconds" % time
            print "    %f seconds avg." % avg_time

Ini adalah timing yang saya dapat:

empty_lists:
  index_first_item:
    0,748353 detik
    0,741086 detik
    0,741191 detik
    Rata-rata 0,743543 detik.
  return_first_item:
    0,785511 detik
    0,822178 detik
    0,782846 detik
    Rata-rata 0,796845 detik.
daftar lengkap:
  index_first_item:
    0,762618 detik
    0,788040 detik
    0,786849 detik
    Rata-rata 0,779169 detik.
  return_first_item:
    0,802735 detik
    0,878706 detik
    0,808781 detik
    Rata-rata 0,830074 detik.
daftar campuran:
  index_first_item:
    0,791129 detik
    0,743526 detik
    0,744441 detik
    Rata-rata 0,759699 detik.
  return_first_item:
    0,784801 detik
    0,785146 detik
    0,840193 detik
    Rata-rata 0,803380 detik.

0
try:
    return a[0]
except IndexError:
    return None

1
Pengecualian umumnya tidak digunakan untuk kontrol aliran.
Matt Green

Saya tidak berpikir membuat kode ini lebih lama dan menambahkan penanganan pengecualian untuk itu adalah ungkapan yang saya cari.
Robert Rossney

3
Tapi ini idiom Python yang umum! Untuk alasan kinerja, Anda mungkin tidak menggunakannya; Anda akan mendapatkan kinerja yang lebih baik if len(a) > 0:tetapi ini dianggap gaya yang baik. Perhatikan seberapa baik ini mengekspresikan masalah: "cobalah untuk mengekstrak kepala daftar, dan jika itu tidak berhasil, kembali None". Pencarian Google untuk "EAFP" atau lihat di sini: python.net/~goodger/projects/pycon/2007/idiomatic/…
steveha

@steveha Tergantung pada data Anda. Jika len (a) biasanya> 0 dan len (a) <1 adalah keadaan langka, menangkap pengecualian sebenarnya akan lebih berkinerja.
limscoder

@limscoder, saya setuju dengan Anda. Saya hanya tidak masuk ke detail ketika Anda mungkin tidak menggunakannya karena alasan kinerja; Saya tidak mengatakan Anda tidak akan pernah menggunakan ini.
steveha

0
def head(iterable):
    try:
        return iter(iterable).next()
    except StopIteration:
        return None

print head(xrange(42, 1000)  # 42
print head([])               # None

BTW: Saya akan mengerjakan ulang aliran program umum Anda menjadi sesuatu seperti ini:

lists = [
    ["first", "list"],
    ["second", "list"],
    ["third", "list"]
]

def do_something(element):
    if not element:
        return
    else:
        # do something
        pass

for li in lists:
    do_something(head(li))

(Menghindari pengulangan bila memungkinkan)


0

Bagaimana dengan ini:

(my_list and my_list[0]) or None

Catatan: Ini harus berfungsi dengan baik untuk daftar objek tetapi mungkin mengembalikan jawaban yang salah jika jumlah atau daftar string per komentar di bawah ini.


Bagaimana dengan ([0,1] and 0) or None!
Kenly

Itu poin yang bagus ... Tidak aman untuk digunakan dengan koleksi nomor dalam bentuk saat ini :(
VitalyB

Sama halnya dengan (['','A'] and '') or None.
Kenly

Jika my_list[0]dievaluasi sebagai False, hasilnya harus None.
Kenly

2
my_list[0] if len(my_list) else None
Nicholas Hamilton

0

Tidak yakin bagaimana pythonic ini tetapi sampai ada fungsi pertama di perpustakaan saya memasukkan ini dalam sumber:

first = lambda l, default=None: next(iter(l or []), default)

Ini hanya satu baris (sesuai dengan hitam) dan menghindari ketergantungan.



-1

Mungkin bukan solusi tercepat, tetapi tidak ada yang menyebutkan opsi ini:

dict(enumerate(get_list())).get(0)

jika get_list()dapat kembali, NoneAnda dapat menggunakan:

dict(enumerate(get_list() or [])).get(0)

Keuntungan:

-satu baris

-Anda hanya menelepon get_list()sekali

-mudah dimengerti


-1

Kasus penggunaan saya hanya untuk mengatur nilai variabel lokal.

Secara pribadi saya menemukan mencoba dan kecuali pembersih gaya untuk membaca

items = [10, 20]
try: first_item = items[0]
except IndexError: first_item = None
print first_item

daripada mengiris daftar.

items = [10, 20]
first_item = (items[:1] or [None, ])[0]
print first_item

-1

Beberapa orang menyarankan melakukan sesuatu seperti ini:

list = get_list()
return list and list[0] or None

Itu berfungsi dalam banyak kasus, tetapi hanya akan berfungsi jika daftar [0] tidak sama dengan 0, False, atau string kosong. Jika daftar [0] adalah 0, Salah, atau string kosong, metode ini tidak akan mengembalikan satupun.

Saya telah membuat bug ini terlalu banyak dalam kode saya sendiri!


3
Ini harus menjadi komentar di bawah jawaban yang salah daripada jawaban yang berdiri sendiri.
IJ Kennedy

-2

Anda dapat menggunakan Metode Ekstrak . Dengan kata lain, ekstrak kode itu ke metode yang akan Anda panggil.

Saya tidak akan mencoba untuk mengompresnya lebih banyak, satu baris sepertinya lebih sulit dibaca daripada versi verbose. Dan jika Anda menggunakan Metode Ekstrak, itu adalah satu liner;)



-3

bukan python idiomatik yang setara dengan operator ternary C-style

cond and true_expr or false_expr

yaitu.

list = get_list()
return list and list[0] or None

Jika [0] adalah angka 0 atau string kosong (atau apa pun yang bernilai false), ini tidak akan mengembalikan Tidak ada alih-alih apa [0] sebenarnya.
Adam Rosenfield
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.