Cara Pythonic untuk menghindari pernyataan “if x: return x”


218

Saya memiliki metode yang memanggil 4 metode lain secara berurutan untuk memeriksa kondisi tertentu, dan segera kembali (tidak memeriksa yang berikut) setiap kali seseorang mengembalikan sesuatu yang Sejati.

def check_all_conditions():
    x = check_size()
    if x:
        return x

    x = check_color()
    if x:
        return x

    x = check_tone()
    if x:
        return x

    x = check_flavor()
    if x:
        return x
    return None

Ini sepertinya banyak kode bagasi. Alih-alih setiap pernyataan 2-baris jika, saya lebih suka melakukan sesuatu seperti:

x and return x

Tapi itu Python tidak valid. Apakah saya melewatkan solusi sederhana dan elegan di sini? Kebetulan, dalam situasi ini, keempat metode pemeriksaan mungkin mahal, jadi saya tidak ingin memanggil mereka beberapa kali.


7
Apa ini x? Apakah hanya Benar / Salah, atau apakah struktur data berisi beberapa informasi, dengan Tidak ada atau serupa digunakan sebagai kasus khusus untuk menunjukkan tidak adanya data? Jika yang terakhir, Anda hampir pasti menggunakan pengecualian.
Nathaniel

13
@gerrit Kode yang disajikan di atas adalah kode hipotetis / semu yang di luar topik pada Tinjauan Kode. Jika penulis posting ingin agar kode kerja mereka yang sebenarnya dan aktual ditinjau, maka ya mereka boleh memposting di Tinjauan Kode.
Phrancis

4
Mengapa menurut Anda x and return xlebih baik daripada if x: return x? Yang terakhir jauh lebih mudah dibaca dan dengan demikian dapat dipertahankan. Anda tidak perlu terlalu khawatir tentang jumlah karakter atau garis; jumlah keterbacaan. Mereka adalah jumlah karakter non-spasi yang sama persis, dan jika Anda benar-benar harus, if x: return xakan bekerja dengan baik hanya pada satu baris.
marcelm

3
Harap jelaskan apakah Anda peduli dengan nilai aktual atau Anda benar-benar hanya perlu mengembalikan boolean. Ini membuat perbedaan opsi apa yang tersedia dan juga mana yang lebih jelas mengomunikasikan maksud. Penamaannya menyarankan Anda hanya perlu boolean. Itu juga membuat perbedaan apakah menghindari beberapa panggilan ke fungsi-fungsi ini penting. Bisa juga masalah jika fungsinya mengambil set parameter yang berbeda. Tanpa klarifikasi ini, saya pikir pertanyaan ini jatuh ke salah satu Berbasis Tidak Jelas, Terlalu Luas, atau Berbasis Opini.
jpmc26

7
@ jpmc26 OP secara eksplisit berbicara tentang nilai pengembalian yang benar, dan kemudian kodenya kembali x(berlawanan dengan bool(x)) sehingga saya kira aman untuk menganggap bahwa fungsi OP dapat mengembalikan apa pun, dan dia menginginkan apa pun yang benar yang benar.
timgeb

Jawaban:


278

Anda bisa menggunakan loop:

conditions = (check_size, check_color, check_tone, check_flavor)
for condition in conditions:
    result = condition()
    if result:
        return result

Ini memiliki keuntungan tambahan yang sekarang Anda dapat membuat jumlah kondisi menjadi variabel.

Anda bisa menggunakan map()+ filter()(versi Python 3, menggunakan future_builtinsversi dalam Python 2) untuk mendapatkan nilai pencocokan pertama seperti:

try:
    # Python 2
    from future_builtins import map, filter
except ImportError:
    # Python 3
    pass

conditions = (check_size, check_color, check_tone, check_flavor)
return next(filter(None, map(lambda f: f(), conditions)), None)

tetapi jika ini lebih mudah dibaca bisa diperdebatkan.

Pilihan lain adalah menggunakan ekspresi generator:

conditions = (check_size, check_color, check_tone, check_flavor)
checks = (condition() for condition in conditions)
return next((check for check in checks if check), None)

27
jika kondisi benar-benar hanya kondisi, yaitu boolean maka dalam proposal pertama Anda, Anda juga bisa menggunakan builtin anybukan loop. return any(condition() for condition in conditions)

4
@ Leonhard: anymemiliki implementasi yang hampir sama di dalam. Tapi kelihatannya jauh lebih baik, silakan posting sebagai jawaban)
Nick Volynkin

13
Keterbacaan mengalahkan hampir semua pertimbangan lainnya. Anda mengatakan, peta / filter 'dapat diperdebatkan', saya memberikan suara saya untuk jelek sekali. Terima kasih, tentu saja, tetapi jika ada orang di tim saya memasukkan peta / filter untuk kode ini, saya akan mentransfernya ke tim lain atau menugaskan mereka untuk tugas pispot.
Kevin J. Rice

15
Apakah blok kode yang tidak dapat dibaca ini benar-benar "pythonian"? Dan terutama gagasan zip conditionsdan arguments? Ini IMHO jauh lebih buruk daripada kode asli, yang membutuhkan waktu sekitar 10 detik untuk diurai oleh brainparser saya.
yo '

34
"Lebih suka Python", kata mereka. "Perl tidak dapat dibaca", kata mereka. Dan kemudian ini terjadi: return next((check for check in checks if check), None).
jja

393

Atau sebagai jawaban baik Martijn, Anda bisa berantai or. Ini akan mengembalikan nilai kebenaran pertama, atau Nonejika tidak ada nilai kebenaran:

def check_all_conditions():
    return check_size() or check_color() or check_tone() or check_flavor() or None

Demo:

>>> x = [] or 0 or {} or -1 or None
>>> x
-1
>>> x = [] or 0 or {} or '' or None
>>> x is None
True

9
Tentu, tetapi ini akan membosankan untuk membaca cepat jika ada lebih dari beberapa opsi. Plus pendekatan saya memungkinkan Anda menggunakan sejumlah kondisi.
Martijn Pieters

14
@ MartijnPieters dapat Anda gunakan \untuk menaruh setiap cek pada barisnya sendiri.
Caridorc

12
@ MartijnPieters Saya tidak pernah menyiratkan jawaban saya lebih baik daripada jawaban Anda, saya juga menyukai jawaban Anda :)
timgeb

38
@Caridorc: Saya sangat tidak suka menggunakan \untuk memperpanjang garis logis. Sebaliknya, gunakan tanda kurung; jadi return (....)dengan baris baru dimasukkan sesuai kebutuhan. Namun, itu akan menjadi satu baris logis yang panjang.
Martijn Pieters

47
Saya pikir ini adalah solusi yang lebih baik. Argumen "itu akan menjadi membosankan [..] jika ada lebih dari beberapa opsi" sedang diperdebatkan, karena fungsi tunggal tidak boleh membuat jumlah cek terlalu tinggi. Jika itu diperlukan, pemeriksaan harus dibagi menjadi beberapa fungsi.
BlueRaja - Danny Pflughoeft

88

Jangan ubah itu

Ada cara lain untuk melakukan ini sebagaimana ditunjukkan oleh berbagai jawaban lainnya. Tidak ada yang sejelas kode asli Anda.


39
Saya menentang hal itu, tetapi saran Anda adalah yang sah untuk disuarakan. Secara pribadi, saya menemukan mata saya tegang mencoba membaca OP sementara, misalnya, solusi timgeb mengklik langsung.
Reti43

3
Ini benar-benar masalah pendapat. Saya pribadi, saya akan menghapus baris baru setelah :, karena saya anggap if x: return xcukup baik, dan itu membuat fungsi terlihat lebih kompak. Tapi itu mungkin hanya aku.
yo '

2
Bukan hanya kamu. Menggunakan orsebagai timgeb lakukan adalah ungkapan yang tepat dan dipahami dengan baik. Banyak bahasa memiliki ini; mungkin ketika disebut orelseitu bahkan lebih jelas, tetapi bahkan tua or(atau ||dalam bahasa lain) yang biasa dimaksudkan untuk dipahami sebagai alternatif untuk mencoba jika yang pertama "tidak berhasil."
Ray Toal

1
@RayToal: Mengimpor idiom dari bahasa lain adalah cara yang bagus untuk mengaburkan kode.
Jack Aidley

1
Terkadang ya, pasti! Juga bisa menjadi cara untuk membuka pikiran dan menuntun orang menemukan pola dan paradigma baru yang lebih baik yang belum pernah dicoba sebelumnya. Gaya berevolusi dengan orang-orang meminjam dan berbagi dan mencoba hal-hal baru. Bekerja dua arah. Lagi pula, saya belum pernah mendengar penggunaan orberlabel un-Pythonic atau dengan cara apa pun dikaburkan, tapi itu masalah pendapat pula --- sebagaimana mestinya.
Ray Toal

83

Secara efektif jawaban yang sama dengan timgeb, tetapi Anda bisa menggunakan tanda kurung untuk pemformatan yang lebih baik:

def check_all_the_things():
    return (
        one()
        or two()
        or five()
        or three()
        or None
    )

8
semuanya tolong bantu menaikkan jawaban ini ke tempat pertama. lakukan bagianmu!
Gyom

74

Menurut hukum Curly , Anda dapat membuat kode ini lebih mudah dibaca dengan membagi dua masalah:

  • Apa yang harus saya periksa?
  • Apakah satu hal kembali benar?

menjadi dua fungsi:

def all_conditions():
    yield check_size()
    yield check_color()
    yield check_tone()
    yield check_flavor()

def check_all_conditions():
    for condition in all_conditions():
        if condition:
            return condition
    return None

Ini menghindari:

  • struktur logis yang rumit
  • garis yang sangat panjang
  • pengulangan

... sembari mempertahankan alur yang linier dan mudah dibaca.

Anda mungkin juga dapat membuat nama fungsi yang lebih baik, sesuai dengan keadaan khusus Anda, yang membuatnya lebih mudah dibaca.


Saya suka yang ini, meskipun Benar / Salah harus diubah ke kondisi / Tidak ada yang cocok dengan pertanyaan.
Malcolm

2
Ini adalah favorit saya! Itu mengatasi dengan cek dan argumen yang berbeda juga. Sangat mungkin dirancang untuk contoh khusus ini tetapi alat yang sangat berguna untuk masalah di masa depan!
rjh

4
Catatan yang return Nonetidak perlu, karena fungsi kembali Nonesecara default. Namun, tidak ada yang salah dengan kembali Nonesecara eksplisit, dan saya suka Anda memilih untuk melakukannya.
timgeb

1
Saya pikir pendekatan ini akan lebih baik diimplementasikan dengan definisi fungsi lokal.
Jack Aidley

1
@timgeb "Eksplisit lebih baik daripada implisit," Zen dari Python .
jpmc26

42

Ini adalah varian dari Martijns, contoh pertama. Ini juga menggunakan "koleksi callable" -style untuk memungkinkan hubungan arus pendek.

Alih-alih loop Anda bisa menggunakan builtin any.

conditions = (check_size, check_color, check_tone, check_flavor)
return any(condition() for condition in conditions) 

Perhatikan bahwa anymengembalikan boolean, jadi jika Anda membutuhkan nilai pengembalian yang tepat dari cek, solusi ini tidak akan berfungsi. anytidak akan membedakan antara 14, 'red', 'sharp', 'spicy'sebagai nilai-nilai kembali, mereka semua akan dikembalikan sebagai True.


Anda bisa melakukannya next(itertools.ifilter(None, (c() for c in conditions)))untuk mendapatkan nilai aktual tanpa membuangnya ke boolean.
kojiro

1
Apakah anysebenarnya hubungan arus pendek?
zwol

1
@zwol Ya coba dengan beberapa fungsi sampel atau lihat docs.python.org/3/library/functions.html

1
Ini kurang mudah dibaca daripada merantai 4 fungsi dengan 'atau' dan hanya terbayar jika jumlah kondisinya besar atau dinamis.
rjh

1
@ rjh Sangat mudah dibaca; itu hanya daftar literal dan pemahaman. Saya lebih suka karena mata saya berkaca-kaca setelah sekitar yang ketigax = bar(); if x: return x;
Blacklight Shining

27

Sudahkah Anda mempertimbangkan untuk menulis if x: return xsemuanya dalam satu baris?

def check_all_conditions():
    x = check_size()
    if x: return x

    x = check_color()
    if x: return x

    x = check_tone()
    if x: return x

    x = check_flavor()
    if x: return x

    return None

Ini tidak kurang berulang dari apa yang Anda miliki, tetapi IMNSHO bunyinya sedikit lebih lancar.


24

Saya cukup terkejut tidak ada yang menyebutkan built-in anyyang dibuat untuk tujuan ini:

def check_all_conditions():
    return any([
        check_size(),
        check_color(),
        check_tone(),
        check_flavor()
    ])

Perhatikan bahwa meskipun implementasi ini mungkin yang paling jelas, ia mengevaluasi semua pemeriksaan bahkan jika yang pertama adalah True.


Jika Anda benar-benar harus berhenti di cek gagal pertama, pertimbangkan untuk menggunakan reduceyang dibuat untuk mengkonversi daftar ke nilai sederhana:

def check_all_conditions():
    checks = [check_size, check_color, check_tone, check_flavor]
    return reduce(lambda a, f: a or f(), checks, False)

reduce(function, iterable[, initializer]): Menerapkan fungsi dua argumen secara kumulatif ke item yang dapat diubah, dari kiri ke kanan, sehingga mengurangi iterable ke nilai tunggal. Argumen kiri, x, adalah nilai akumulasi dan argumen kanan, y, adalah nilai pembaruan dari iterable. Jika inisialisasi opsional hadir, ia ditempatkan sebelum item iterable dalam perhitungan

Dalam kasus Anda:

  • lambda a, f: a or f()adalah fungsi yang memeriksa apakah akumulator aatau cek saat f()ini True. Perhatikan bahwa jika aadalah True,f() tidak akan dievaluasi.
  • checks berisi fungsi pemeriksaan (the f item dari lambda)
  • False adalah nilai awal, jika tidak cek tidak akan terjadi dan hasilnya akan selalu True

anydan reducemerupakan alat dasar untuk pemrograman fungsional. Saya sangat mendorong Anda untuk melatih ini dan mapyang juga luar biasa!


9
anyhanya berfungsi jika cek benar-benar mengembalikan nilai boolean, secara harfiah Trueatau False, tetapi pertanyaannya tidak menentukan itu. Anda harus menggunakan reduceuntuk mengembalikan nilai aktual yang dikembalikan oleh cek. Juga, cukup mudah untuk menghindari mengevaluasi semua cek dengan anymenggunakan generator, mis any(c() for c in (check_size, check_color, check_tone, check_flavor)). Seperti dalam jawaban Leonhard
David Z

Saya suka penjelasan dan penggunaan Anda dari reduce. Seperti @ DavidZ, saya percaya solusi Anda anyharus menggunakan generator dan perlu ditunjukkan bahwa itu terbatas untuk mengembalikan Trueatau False.
timgeb

1
@ DavidZ benar-benar anybekerja dengan nilai-nilai yang benar: any([1, "abc", False]) == Truedanany(["", 0]) == False
ngasull

3
@ blint maaf, saya tidak jelas. Tujuan dari pertanyaan ini adalah untuk mengembalikan hasil cek (dan bukan hanya untuk menunjukkan apakah cek berhasil atau gagal). Saya menunjukkan bahwa anyhanya bekerja untuk itu dimaksudkan jika nilai-nilai boolean yang sebenarnya dikembalikan dari fungsi cek.
David Z

19

Jika Anda menginginkan struktur kode yang sama, Anda bisa menggunakan pernyataan ternary!

def check_all_conditions():
    x = check_size()
    x = x if x else check_color()
    x = x if x else check_tone()
    x = x if x else check_flavor()

    return x if x else None

Saya pikir ini terlihat bagus dan jelas jika Anda melihatnya.

Demo:

Tangkapan layar itu berjalan


7
Whats dengan ikan ASCII kecil di atas terminal Anda?

36
@LegoStormtroopr Saya menggunakan cangkang ikan, jadi saya menghiasnya dengan tangki ikan ascii untuk membuat saya bahagia. :)
Phinet

3
Terima kasih untuk ikan yang bagus (dan warnanya, editor apa itu?)
mathreadler

4
Anda bisa mendapatkan ikan di fishshell.com , dan file konfigurasi untuk ascii di sini pastebin.com/yYVYvVeK , juga editornya adalah teks yang agung.
Phinet

9
x if x else <something>hanya dapat direduksi menjadix or <something>

5

Bagi saya, jawaban terbaik adalah dari @ phil-frost, diikuti oleh @ wayne-werner's.

Apa yang saya temukan menarik adalah bahwa tidak ada yang mengatakan apa-apa tentang fakta bahwa suatu fungsi akan mengembalikan banyak tipe data yang berbeda, yang akan membuat wajib untuk melakukan pemeriksaan pada tipe x itu sendiri untuk melakukan pekerjaan lebih lanjut.

Jadi saya akan mencampur respon @ PhilFrost dengan ide menjaga satu tipe:

def all_conditions(x):
    yield check_size(x)
    yield check_color(x)
    yield check_tone(x)
    yield check_flavor(x)

def assessed_x(x,func=all_conditions):
    for condition in func(x):
        if condition:
            return x
    return None

Perhatikan bahwa xdilewatkan sebagai argumen, tetapi juga all_conditionsdigunakan sebagai generator lulus dari fungsi pemeriksaan di mana semuanya mendapatkan xdiperiksa, dan kembali Trueatau False. Dengan menggunakan funcdengan all_conditionssebagai nilai default, Anda dapat menggunakan assessed_x(x), atau Anda dapat melewati generator pribadi lebih lanjut melalui func.

Dengan begitu, Anda mendapatkan xbegitu satu cek berlalu, tetapi itu akan selalu menjadi tipe yang sama.


4

Idealnya, saya akan menulis ulang check_ fungsi untuk mengembalikan Trueatau Falsedaripada nilai. Cek Anda kemudian menjadi

if check_size(x):
    return x
#etc

Dengan asumsi Anda xtidak dapat diubah, fungsi Anda masih dapat memodifikasinya (meskipun mereka tidak dapat menugaskannya kembali) - tetapi fungsi yang dipanggil checktidak boleh benar-benar memodifikasinya.


3

Sedikit variasi pada Martijns contoh pertama di atas, yang menghindari if di dalam loop:

Status = None
for c in [check_size, check_color, check_tone, check_flavor]:
  Status = Status or c();
return Status

Melakukannya? Anda masih melakukan perbandingan. Dalam versi Anda, Anda juga akan memeriksa semua kondisi terlepas dan tidak kembali pada contoh pertama dari nilai kebenaran, Bergantung pada seberapa mahal fungsi-fungsi itu, yang mungkin tidak diinginkan.
Reti43

4
@ Reti43: Status or c()akan melewati / melakukan hubungan pendek mengevaluasi panggilan ke c()jika Statusbenar, sehingga kode dalam jawaban ini tampaknya tidak memanggil lebih banyak fungsi daripada kode OP. stackoverflow.com/questions/2580136/…
Neil Slater

2
@NeilSlater Benar. Satu-satunya downside yang saya lihat adalah bahwa kasus terbaik sekarang di O (n) karena listiterator harus menghasilkan n kali, ketika O (1) sebelumnya jika fungsi pertama mengembalikan sesuatu yang benar dalam O (1).
timgeb

1
Ya poin bagus. Saya hanya harus berharap bahwa c () membutuhkan waktu lebih lama untuk mengevaluasi daripada mengulangi lingkaran yang hampir kosong. Memeriksa rasa bisa memakan waktu sepanjang malam setidaknya jika itu bagus.
mathreadler

3

Saya suka @ timgeb. Sementara itu saya ingin menambahkan bahwa mengekspresikan Nonedalam returnpernyataan tidak diperlukan karena kumpulan orpernyataan terpisah dievaluasi dan yang pertama tidak-nol, tidak-kosong, tidak ada-Tidak ada yang dikembalikan dan jika tidak ada maka Nonedikembalikan apakah adaNone atau tidak!

Jadi check_all_conditions()fungsi saya terlihat seperti ini:

def check_all_conditions():
    return check_size() or check_color() or check_tone() or check_flavor()

Menggunakan timeitdengan number=10**7saya melihat waktu berjalan sejumlah saran. Demi perbandingan, saya hanya menggunakan random.random()fungsi untuk mengembalikan string atau Noneberdasarkan angka acak. Ini seluruh kodenya:

import random
import timeit

def check_size():
    if random.random() < 0.25: return "BIG"

def check_color():
    if random.random() < 0.25: return "RED"

def check_tone():
    if random.random() < 0.25: return "SOFT"

def check_flavor():
    if random.random() < 0.25: return "SWEET"

def check_all_conditions_Bernard():
    x = check_size()
    if x:
        return x

    x = check_color()
    if x:
        return x

    x = check_tone()
    if x:
        return x

    x = check_flavor()
    if x:
        return x
    return None

def check_all_Martijn_Pieters():
    conditions = (check_size, check_color, check_tone, check_flavor)
    for condition in conditions:
        result = condition()
        if result:
            return result

def check_all_conditions_timgeb():
    return check_size() or check_color() or check_tone() or check_flavor() or None

def check_all_conditions_Reza():
    return check_size() or check_color() or check_tone() or check_flavor()

def check_all_conditions_Phinet():
    x = check_size()
    x = x if x else check_color()
    x = x if x else check_tone()
    x = x if x else check_flavor()

    return x if x else None

def all_conditions():
    yield check_size()
    yield check_color()
    yield check_tone()
    yield check_flavor()

def check_all_conditions_Phil_Frost():
    for condition in all_conditions():
        if condition:
            return condition

def main():
    num = 10000000
    random.seed(20)
    print("Bernard:", timeit.timeit('check_all_conditions_Bernard()', 'from __main__ import check_all_conditions_Bernard', number=num))
    random.seed(20)
    print("Martijn Pieters:", timeit.timeit('check_all_Martijn_Pieters()', 'from __main__ import check_all_Martijn_Pieters', number=num))
    random.seed(20)
    print("timgeb:", timeit.timeit('check_all_conditions_timgeb()', 'from __main__ import check_all_conditions_timgeb', number=num))
    random.seed(20)
    print("Reza:", timeit.timeit('check_all_conditions_Reza()', 'from __main__ import check_all_conditions_Reza', number=num))
    random.seed(20)
    print("Phinet:", timeit.timeit('check_all_conditions_Phinet()', 'from __main__ import check_all_conditions_Phinet', number=num))
    random.seed(20)
    print("Phil Frost:", timeit.timeit('check_all_conditions_Phil_Frost()', 'from __main__ import check_all_conditions_Phil_Frost', number=num))

if __name__ == '__main__':
    main()

Dan inilah hasilnya:

Bernard: 7.398444877040768
Martijn Pieters: 8.506569201346597
timgeb: 7.244275416364456
Reza: 6.982133448743038
Phinet: 7.925932800076634
Phil Frost: 11.924794811353031

2

Cara ini sedikit di luar kotak, tapi saya pikir hasil akhirnya sederhana, mudah dibaca, dan terlihat bagus.

Ide dasarnya adalah raisepengecualian ketika salah satu fungsi mengevaluasi sebagai kebenaran, dan mengembalikan hasilnya. Begini tampilannya:

def check_conditions():
    try:
        assertFalsey(
            check_size,
            check_color,
            check_tone,
            check_flavor)
    except TruthyException as e:
        return e.trigger
    else:
        return None

Anda akan membutuhkan assertFalseyfungsi yang menimbulkan pengecualian ketika salah satu dari argumen fungsi yang dipanggil mengevaluasi sebagai benar:

def assertFalsey(*funcs):
    for f in funcs:
        o = f()
        if o:
            raise TruthyException(o)

Di atas dapat dimodifikasi sehingga juga memberikan argumen untuk fungsi yang akan dievaluasi.

Dan tentu saja Anda akan membutuhkannya TruthyExceptionsendiri. Pengecualian ini memberikan objectyang memicu pengecualian:

class TruthyException(Exception):
    def __init__(self, obj, *args):
        super().__init__(*args)
        self.trigger = obj

Anda dapat mengubah fungsi asli menjadi sesuatu yang lebih umum, tentu saja:

def get_truthy_condition(*conditions):
    try:
        assertFalsey(*conditions)
    except TruthyException as e:
        return e.trigger
    else:
        return None

result = get_truthy_condition(check_size, check_color, check_tone, check_flavor)

Ini mungkin sedikit lebih lambat karena Anda menggunakan ifpernyataan dan penanganan pengecualian. Namun, pengecualian hanya ditangani maksimal satu kali, sehingga hit ke kinerja harus kecil kecuali jika Anda berharap untuk menjalankan pemeriksaan dan mendapatkan Truenilai ribuan kali.


// , Imut! Apakah dianggap "Pythonic" menggunakan penanganan pengecualian untuk hal semacam ini?
Nathan Basanese

@NathanBasanese Sure- pengecualian digunakan untuk aliran kontrol setiap saat. StopIterationadalah contoh yang cukup bagus: sebuah pengecualian dimunculkan setiap kali Anda menghabiskan iterable. Hal yang ingin Anda hindari adalah terus meningkatkan pengecualian, yang akan menjadi mahal. Tetapi melakukannya sekali saja tidak.
Rick mendukung Monica

//, Ah, kurasa kau merujuk ke sesuatu seperti programmers.stackexchange.com/questions/112463/… . Saya telah memilih untuk pertanyaan itu dan untuk jawaban ini. Python 3 docs untuk ini ada di sini: docs.python.org/3/library/stdtypes.html#iterator-types , saya kira.
Nathan Basanese

1
Anda ingin mendefinisikan fungsi tujuan umum dan pengecualian, hanya untuk melakukan beberapa pemeriksaan di beberapa fungsi lain di suatu tempat? Saya pikir itu agak berlebihan.
Blacklight Shining

@ BacklightShining, saya setuju. Saya tidak pernah benar-benar melakukan ini sendiri. OP meminta cara untuk menghindari kode berulang, tapi saya pikir apa yang dia mulai dengan baik-baik saja.
Rick mendukung Monica

2

Cara pythonic adalah dengan menggunakan perkecil (seperti yang telah seseorang sebutkan) atau itertools (seperti yang ditunjukkan di bawah), tetapi bagi saya tampaknya hanya dengan menggunakan korsleting dari oroperator menghasilkan kode yang lebih jelas.

from itertools import imap, dropwhile

def check_all_conditions():
    conditions = (check_size,\
        check_color,\
        check_tone,\
        check_flavor)
    results_gen = dropwhile(lambda x:not x, imap(lambda check:check(), conditions))
    try:
        return results_gen.next()
    except StopIteration:
        return None

0

Saya akan melompat ke sini dan tidak pernah menulis satu baris pun Python, tapi saya berasumsi if x = check_something(): return x valid?

jika begitu:

def check_all_conditions():

    if (x := check_size()): return x
    if (x := check_color()): return x
    if (x := check_tone()): return x
    if (x := check_flavor()): return x

    return None

1
Itu tidak sah Python, tidak. Python tidak membiarkan Anda menggunakan operator penugasan seperti itu. Namun, ekspresi tugas khusus baru telah ditambahkan baru-baru ini, jadi Anda sekarang dapat menulis if ( x := check_size() ) :untuk efek yang sama.
Jack Aidley

0

Atau gunakan max:

def check_all_conditions():
    return max(check_size(), check_color(), check_tone(), check_flavor()) or None

-2

Saya telah melihat beberapa implementasi yang menarik dari pernyataan switch / case dengan dikt di masa lalu yang mengarahkan saya ke jawaban ini. Dengan menggunakan contoh yang Anda berikan Anda akan mendapatkan yang berikut. (Ini kegilaan using_complete_sentences_for_function_names, jadi check_all_conditionsberganti nama menjadi status. Lihat (1))

def status(k = 'a', s = {'a':'b','b':'c','c':'d','d':None}) :
  select = lambda next, test : test if test else next
  d = {'a': lambda : select(s['a'], check_size()  ),
       'b': lambda : select(s['b'], check_color() ),
       'c': lambda : select(s['c'], check_tone()  ),
       'd': lambda : select(s['d'], check_flavor())}
  while k in d : k = d[k]()
  return k

Fungsi pilih menghilangkan kebutuhan untuk memanggil masing-masing check_FUNCTIONdua kali yaitu Anda menghindaricheck_FUNCTION() if check_FUNCTION() else next dengan menambahkan lapisan fungsi lain. Ini berguna untuk fungsi yang berjalan lama. Lambda dalam eksekusi penundaan nilai-nilai itu sampai loop sementara.

Sebagai bonus, Anda dapat memodifikasi urutan eksekusi dan bahkan melewatkan beberapa tes dengan mengubah kdan smisalnyak='c',s={'c':'b','b':None} mengurangi jumlah tes dan membalik urutan pemrosesan asli.

Itu timeit orang mungkin tawar-menawar tentang biaya menambahkan satu atau dua lapisan tambahan ke tumpukan dan biaya untuk mendikte mencari tetapi Anda tampaknya lebih peduli dengan kecantikan kode.

Atau implementasi yang lebih sederhana mungkin sebagai berikut:

def status(k=check_size) :
  select = lambda next, test : test if test else next
  d = {check_size  : lambda : select(check_color,  check_size()  ),
       check_color : lambda : select(check_tone,   check_color() ),
       check_tone  : lambda : select(check_flavor, check_tone()  ),
       check_flavor: lambda : select(None,         check_flavor())}
  while k in d : k = d[k]()
  return k
  1. Maksud saya ini bukan dalam hal pep8 tetapi dalam hal menggunakan satu kata deskriptif ringkas sebagai pengganti kalimat. Memang OP mungkin mengikuti beberapa konvensi pengkodean, bekerja satu basis kode yang ada atau tidak peduli dengan istilah singkat dalam basis kode mereka.

1
Kadang-kadang orang menjadi sangat gila dengan penamaan mereka ketika satu kata akan dilakukan. Menggunakan kode OP sebagai contoh, tidak mungkin ia memiliki fungsi yang dipanggil check_no/some/even/prime/every_third/fancy_conditionstetapi hanya fungsi yang satu ini jadi mengapa tidak menyebutnya statusatau jika seseorang bersikeras check_status. Menggunakan _all_itu berlebihan, dia tidak memastikan integritas alam semesta. Penamaan tentu harus menggunakan set kata kunci yang konsisten yang meningkatkan spasi nama bila memungkinkan. Kalimat yang panjang paling baik dijadikan sebagai dokumen. Seseorang jarang membutuhkan lebih dari 8-10 karakter untuk menggambarkan sesuatu secara ringkas.
Carel

1
Saya penggemar nama fungsi yang panjang, karena saya ingin fungsi tingkat tinggi didokumentasikan sendiri. Tapi check_all_conditionsitu nama yang buruk, karena itu tidak memeriksa semua kondisi jika ada yang benar. Saya akan menggunakan sesuatu seperti matches_any_condition.
John Hazen

Itu adalah taktik yang menarik untuk dilakukan. Saya mencoba untuk meminimalkan jumlah huruf yang akan saya buat salah ketik nanti :) Sepertinya saya telah memasukkan banyak pendapat ke dalam solusi saya, ketika saya benar-benar mencoba memberikan petunjuk yang bermanfaat. Haruskah ini diedit?
Carel

2
Ini sepertinya terlalu gila, terutama mengingat solusi lain untuk pertanyaan ini. Apa yang OP coba lakukan tidak rumit sama sekali; solusinya harus cukup sederhana untuk dipahami setengah tertidur. Dan saya tidak tahu apa yang terjadi di sini.
Blacklight Shining

Saya mengincar fleksibilitas. Jawaban yang dimodifikasi untuk menyertakan varian yang kurang 'kocak'
Carel
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.