Diğer en çok yapıldığında if-elif-elif-else ifadesini oluşturmanın en etkili yolu?


102

% 99 oranında, else ifadesinin çalıştırıldığı bir if-elif-elif-else ifadesine sahibim:

if something == 'this':
    doThis()
elif something == 'that':
    doThat()
elif something == 'there':
    doThere()
else:
    doThisMostOfTheTime()

Bu yapı yapılır çok ama bu çok verimli değildir his var başka vurur önce her şartta gider beri, tek başına Pythonic edelim. Öte yandan, bu koşullardan herhangi birinin karşılanıp karşılanmadığını bilmesi gerekir, bu yüzden yine de test etmesi gerekir.

Bunun daha verimli bir şekilde yapılıp yapılmayacağını ve nasıl yapılacağını bilen var mı, yoksa bunu yapmanın en iyi yolu bu mu?


Koşullardan sortbirinin eşleşeceği tüm unsurların bir ucunda, geri kalanın diğer ucunda olacağı şekilde eğer / değilse ... zincirinizi çalıştırdığınız şeyler olabilir mi ? Eğer öyleyse, bunun daha hızlı / daha zarif olup olmadığını görebilirsiniz. Ancak, performans sorunu yoksa optimizasyon konusunda endişelenmek için henüz çok erken olduğunu unutmayın.
Patashu


4
Üç özel durumun ortak bir yanı var mı? Örneğin if not something.startswith("th"): doThisMostOfTheTime(), elsecümlede başka bir karşılaştırma yapabilir ve yapabilirsiniz .
Tim Pietzcker

3
@ kramer65 Eğer bu kadar uzun bir if / elif zinciriyse ... yavaş olabilir, ancak kodunuzun profilini gerçekten belirlediğinizden ve çoğu zaman alan parçayı optimize ederek işe başladığınızdan emin olun .
jorgeca

1
Bu karşılaştırmalar değeri başına yalnızca bir kez mi somethingyoksa benzer karşılaştırmalar aynı değer üzerinde birden çok kez mi yapılır?
Chris Pitman

Yanıtlar:


101

Kod ...

options.get(something, doThisMostOfTheTime)()

... daha hızlı olması gerekiyor gibi görünüyor, ama aslında if... elif... elseyapısından daha yavaştır , çünkü bir işlevi çağırmak zorundadır, bu da sıkı bir döngüde önemli bir performans yükü olabilir.

Şu örnekleri düşünün ...

1.py

something = 'something'

for i in xrange(1000000):
    if something == 'this':
        the_thing = 1
    elif something == 'that':
        the_thing = 2
    elif something == 'there':
        the_thing = 3
    else:
        the_thing = 4

2.py

something = 'something'
options = {'this': 1, 'that': 2, 'there': 3}

for i in xrange(1000000):
    the_thing = options.get(something, 4)

3.py

something = 'something'
options = {'this': 1, 'that': 2, 'there': 3}

for i in xrange(1000000):
    if something in options:
        the_thing = options[something]
    else:
        the_thing = 4

4.py

from collections import defaultdict

something = 'something'
options = defaultdict(lambda: 4, {'this': 1, 'that': 2, 'there': 3})

for i in xrange(1000000):
    the_thing = options[something]

... ve kullandıkları CPU süresini not edin ...

1.py: 160ms
2.py: 170ms
3.py: 110ms
4.py: 100ms

... kullanıcı zamanını kullanarak time(1).

Seçenek # 4, her farklı anahtar kaçırma için yeni bir öğe eklemek için ek bellek ek yüküne sahiptir, bu nedenle, sınırsız sayıda farklı anahtar kaçırma bekliyorsanız, seçenek # 3 ile devam ediyorum, bu hala önemli bir gelişme orijinal yapı.


2
python'un switch deyimi var mı?
nathan hayfield

öf ... iyi şimdiye kadar ben bir şey olması kaçınılmazdır edildi tahmin için ... umrumda değil o piton duydum tek şey bu
Nathan samanli

2
-1 a kullanmanın dictdaha yavaş olduğunu söylüyorsunuz , ancak zamanlamanız aslında bunun ikinci en hızlı seçenek olduğunu gösteriyor.
Marcin

11
@Marcin Bunun dict.get()daha yavaş olduğunu söylüyorum , ki bu 2.py- hepsinin en yavaş olanı.
Aya

Kayıt için, üç ve dört aynı zamanda bir dene / hariç yapısındaki anahtar hatayı yakalamadan önemli ölçüde daha hızlıdır.
Jeff

80

Bir sözlük oluşturardım:

options = {'this': doThis,'that' :doThat, 'there':doThere}

Şimdi sadece şunu kullanın:

options.get(something, doThisMostOfTheTime)()

Eğer diktede somethingbulunmazsa , varsayılan değeri döndürüroptionsdict.getdoThisMostOfTheTime

Bazı zamanlama karşılaştırmaları:

Senaryo:

from random import shuffle
def doThis():pass
def doThat():pass
def doThere():pass
def doSomethingElse():pass
options = {'this':doThis, 'that':doThat, 'there':doThere}
lis = range(10**4) + options.keys()*100
shuffle(lis)

def get():
    for x in lis:
        options.get(x, doSomethingElse)()

def key_in_dic():
    for x in lis:
        if x in options:
            options[x]()
        else:
            doSomethingElse()

def if_else():
    for x in lis:
        if x == 'this':
            doThis()
        elif x == 'that':
            doThat()
        elif x == 'there':
            doThere()
        else:
            doSomethingElse()

Sonuçlar:

>>> from so import *
>>> %timeit get()
100 loops, best of 3: 5.06 ms per loop
>>> %timeit key_in_dic()
100 loops, best of 3: 3.55 ms per loop
>>> %timeit if_else()
100 loops, best of 3: 6.42 ms per loop

Var 10**5olmayan anahtarlar ve 100 geçerli anahtar için:

>>> %timeit get()
10 loops, best of 3: 84.4 ms per loop
>>> %timeit key_in_dic()
10 loops, best of 3: 50.4 ms per loop
>>> %timeit if_else()
10 loops, best of 3: 104 ms per loop

Bu nedenle, normal bir sözlük için anahtarı kontrol etmek key in optionsburada en etkili yoldur:

if key in options:
   options[key]()
else:
   doSomethingElse()

options = collections.defaultdict(lambda: doThisMostOfTheTime, {'this': doThis,'that' :doThat, 'there':doThere}); options[something]()marjinal olarak daha etkilidir.
Aya

Harika fikir, ama okunabilir değil. Ayrıca muhtemelen optionsyeniden inşa etmekten kaçınmak için dikteyi ayırmak , böylece mantığın bir kısmını (ama tamamını değil) kullanım noktasından uzağa taşımak isteyeceksiniz . Yine de güzel numara!
Anders Johansson

7
Eğer do biliyorum bu daha verimli olup olmadığı? Tahminim, basit bir koşullu kontrol veya üç yerine bir hash araması yaptığı için daha yavaş. Soru, kodun kompaktlığından çok verimlilikle ilgilidir.
Bryan Oakley

2
@BryanOakley Bazı zamanlama karşılaştırmaları ekledim.
Ashwini Chaudhary

2
aslında yapmak daha verimli olmalı try: options[key]() except KeyError: doSomeThingElse()(çünkü if key in options: options[key]()sözlüğü iki kez arıyorsunkey
hardmooth

9

Pypy kullanabilir misin?

Orijinal kodunuzu saklamak ancak onu pypy'de çalıştırmak bana 50 kat hız kazandırıyor.

CPython:

matt$ python
Python 2.6.8 (unknown, Nov 26 2012, 10:25:03)
[GCC 4.2.1 Compatible Apple Clang 3.0 (tags/Apple/clang-211.12)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> from timeit import timeit
>>> timeit("""
... if something == 'this': pass
... elif something == 'that': pass
... elif something == 'there': pass
... else: pass
... """, "something='foo'", number=10000000)
1.728302001953125

Pypy:

matt$ pypy
Python 2.7.3 (daf4a1b651e0, Dec 07 2012, 23:00:16)
[PyPy 2.0.0-beta1 with GCC 4.2.1] on darwin
Type "help", "copyright", "credits" or "license" for more information.
And now for something completely different: ``a 10th of forever is 1h45''
>>>>
>>>> from timeit import timeit
>>>> timeit("""
.... if something == 'this': pass
.... elif something == 'that': pass
.... elif something == 'there': pass
.... else: pass
.... """, "something='foo'", number=10000000)
0.03306388854980469

Merhaba Foz. Bahşiş için teşekkürler. Aslında zaten pypy kullanıyorum (seviyorum), ancak yine de hız iyileştirmelerine ihtiyacım var .. :)
kramer65

Oh iyi! Bundan önce, 'bu', 'bu' ve 'orada' için bir karma ön hesaplamayı denedim - ve sonra dizeler yerine karma kodları karşılaştırmayı denedim. Bunun orijinalden iki kat daha yavaş olduğu ortaya çıktı, bu nedenle dize karşılaştırmaları zaten dahili olarak oldukça iyi optimize edilmiş görünüyor.
foz

4

Burada, sözlüğe çevrilmiş dinamik koşullara sahip bir if örneği.

selector = {lambda d: datetime(2014, 12, 31) >= d : 'before2015',
            lambda d: datetime(2015, 1, 1) <= d < datetime(2016, 1, 1): 'year2015',
            lambda d: datetime(2016, 1, 1) <= d < datetime(2016, 12, 31): 'year2016'}

def select_by_date(date, selector=selector):
    selected = [selector[x] for x in selector if x(date)] or ['after2016']
    return selected[0]

Bu bir yoldur, ancak bunu yapmanın en pitonik yolu olmayabilir çünkü Python'da akıcı olmayanlar için daha az okunabilir.


0

İnsanlar execgüvenlik nedeniyle uyarıyor ama bu onun için ideal bir durum.
Kolay durum makinesi.

Codes = {}
Codes [0] = compile('blah blah 0; nextcode = 1')
Codes [1] = compile('blah blah 1; nextcode = 2')
Codes [2] = compile('blah blah 2; nextcode = 0')

nextcode = 0
While True:
    exec(Codes[nextcode])
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.