Python'daki switch deyiminin değiştirilmesi?


1718

Bir girdi dizininin değerine göre farklı sabit değerler döndüren bir işlev Python yazmak istiyorum.

Diğer dillerde bir switchveya casedeyimi kullanırım, ancak Python'un bir switchifadesi yok gibi görünüyor . Bu senaryoda önerilen Python çözümleri nelerdir?


77
İlgili PEP, Guido'nun kendisi tarafından yazılmıştır: PEP 3103
chb

28
@chb Bu PEP'te Guido, / elif zincirlerinin de klasik bir hata kaynağı olduğundan bahsetmez. Çok kırılgan bir yapı.
itsbruce

15
Buradaki tüm çözümlerden eksik, yinelenen vaka değerlerinin algılanmasıdır . Başarısız bir prensip olarak, bu performanstan veya geçiş özelliğinden daha önemli bir kayıp olabilir.
Bob Stein

6
switchaslında bir girdi dizininin değerine göre farklı sabit değerler döndüren bir şeyden daha "çok yönlü" dür. Farklı kod parçalarının yürütülmesine izin verir. Aslında bir değer döndürmesi bile gerekmez. Buradaki cevapların bir kısmının genel bir switchifade için mi yoksa sadece genel kod parçalarını yürütme olasılığı olmayan değerlerin döndürülmesi için mi iyi olduğunu merak ediyorum .
sancho.s ReinstateMonicaCellio

3
@ MalikA.Rumi Kırılgan yapı, bir süre için olduğu gibi ... in ... yaptığı şeyi yapmak için kullanmayı denerseniz kırılgan bir yapıdır. Programcıları döngüler için kullandıkları için zayıf olarak mı arayacaksınız? Her ne kadar döngüler gerçekten ihtiyaçları olsa da Ancak döngüler net bir niyet gösterir, anlamsız kazan plakasını kaydedin ve güçlü soyutlamalar yaratma fırsatı verin.
itsbruce

Yanıtlar:


1486

Bir sözlük kullanabilirsiniz:

def f(x):
    return {
        'a': 1,
        'b': 2,
    }[x]

100
X bulunmazsa ne olur?
Nick

46
@nick: defaultdict kullanabilirsiniz
Eli Bendersky

385
Performans bir sorunsa, dikteyi fonksiyonun dışına koymanızı tavsiye ederim, bu yüzden her fonksiyon çağrısında dikteyi yeniden oluşturmaz
Claudiu

56
@EliBendersky, getyöntemi kullanmak muhtemelen collections.defaultdictbu durumda bir kullanmaktan daha normal olurdu .
Mike Graham

27
@Nick, Bir istisna atılır - }.get(x, default)bunun yerine bir varsayılan olması gerekir. (Not: Bu, varsayılanı bir anahtar ifadesinden çıkarırsanız ne olduğundan çok daha güzel!)
Mike Graham

1375

Varsayılanları isterseniz sözlük get(key[, default])yöntemini kullanabilirsiniz :

def f(x):
    return {
        'a': 1,
        'b': 2
    }.get(x, 9)    # 9 is default if x not found

11
'A' ve 'b' 1 ile 'c' ve 'd' 2 eşleşirse ne olur?
John Mee

13
@JM: Açıkçası sözlük aramaları düşüşü desteklemiyor. Çift sözlük araması yapabilirsiniz. Yani ikinci bir sözlükte yer alan 'a' ve 'b' noktası1'e ve 'c' ve 'd' noktası2'ye.
Nick

3
varsayılan değeri geçmek daha iyidir
HaTiMSuM

Bu yaklaşımla ilgili bir problem var, önce f'yi her aradığınızda, daha karmaşık bir değere sahipseniz, ikinci bir istisna oluşturacaksınız. x bir demetse ve bunun gibi bir şey yapmak istiyorsak x = ('a') def f (x): dönüş {'a': x [0], 'b': x [1]} .get ( x [0], 9) Bu, IndexError
Idan Haim

2
@Idan: Soru, anahtarı çoğaltmaktı. Garip değerler koyarak denedim ben de bu kodu kırmak eminim. Evet, yeniden oluşturulacak, ancak düzeltilmesi basit.
Nick

394

Bunu hep böyle yapmayı sevdim

result = {
  'a': lambda x: x * 5,
  'b': lambda x: x + 7,
  'c': lambda x: x - 2
}[value](x)

Buradan


16
varsayılan işlemek için get () ile birlikte harika bir yöntem de benim en iyi seçimdir
drAlberT

27
bu durumda lambda kullanmak iyi bir fikir olmayabilir çünkü lambda aslında her sözlük oluşturulduğunda çağrılır.
Asher

13
Ne yazık ki bu insanlar en yakın olacak. .get()(Şu anki en yüksek cevaplar gibi) kullanan yöntemlerin , sevkıyattan önce tüm olasılıkları hevesle değerlendirmesi gerekecektir ve bu nedenle sadece (sadece çok değil) son derece verimsiz değildir ve aynı zamanda yan etkileri de olamaz; bu cevap bu sorunu çözer, ancak daha ayrıntılıdır. Ben sadece / elif / else komutunu kullanırdım, ve bunların 'vaka' olarak yazılması bile uzun sürer.
ninjagecko

13
sonuçlardan sadece birini döndürse bile, bu her durumda tüm fonksiyonları / lambdaları değerlendirmez mi?
slf

23
@slf Hayır, kontrol akışı bu kod parçasına ulaştığında, 3 işlev (3 lambdanın kullanımı yoluyla) oluşturacak ve daha sonra bu 3 işlevle değerler olarak bir sözlük oluşturacak, ancak çağrılmamış olarak kalacak ( değerlendirme biraz belirsiz bu bağlamda). Daha sonra sözlük endekslenir [value], bu da 3 fonksiyondan sadece birini döndürür ( value3 tuştan biri olduğu varsayılarak ). İşlev henüz bu noktada çağrılmamıştır. Sonra argüman olarak (x)yeni döndürülen işlevi çağırır x(ve sonuç gider result). Diğer 2 işlev çağrılmaz.
blubberdiblub

354

(Gerçekten BTW gibi), ayrıca kullanabilirsiniz Sözlük yöntemlere ek olarak if- elif- elseelde etmek switch/ case/ defaultişlevsellik:

if x == 'a':
    # Do the thing
elif x == 'b':
    # Do the other thing
if x in 'bc':
    # Fall-through by not using elif, but now the default case includes case 'a'!
elif x in 'xyz':
    # Do yet another thing
else:
    # Do the default

Bu elbette geçiş / durumla aynı değildir - breakifadeyi bırakmak kadar kolay düşemezsiniz, ancak daha karmaşık bir test yapabilirsiniz. ifİşlevsel olarak daha yakın olmasına rağmen, biçimlendirmesi bir dizi iç içe s'den daha iyidir .


51
bunu gerçekten tercih ederdim, standart bir dil yapısı kullanır ve eşleşen bir durum bulunmazsa bir
KeyError atmaz

7
Sözlük / getyol hakkında düşündüm , ancak standart yol daha kolay okunabilir.
Martin Thoma

2
@someuser ama onlar "üst üste gelebilir" gerçeği bir özelliktir. Sıralamanın, eşleşmelerin gerçekleşmesi gereken öncelik olduğundan emin olmanız yeterlidir. Tekrarlanan x için: sadece bir tane yapın x = the.other.thing. Tipik olarak, anlaşılması daha kolay olduğu için tek bir if, birden fazla elif ve başka bir tane olacaktır.
Matthew Schinckel

7
Güzel, "Güz elif kullanarak değil" biraz kafa karıştırıcı olsa. Buna ne dersiniz: "düşmeyi" unutun ve ikisini kabul edin if/elif/else?
Alois Mahdal

7
Kullanarak işler ister Ayrıca bahsetmemiz, x in 'bc'akılda tutulması "" in "bc"olduğunu True.
Lohmar ASHAR

185

Switch / case için en sevdiğim Python tarifi:

choices = {'a': 1, 'b': 2}
result = choices.get(key, 'default')

Kısa ve basit senaryolar için basit.

11+ C kodu satırıyla karşılaştırın:

// C Language version of a simple 'switch/case'.
switch( key ) 
{
    case 'a' :
        result = 1;
        break;
    case 'b' :
        result = 2;
        break;
    default :
        result = -1;
}

Tüpleri kullanarak birden çok değişken bile atayabilirsiniz:

choices = {'a': (1, 2, 3), 'b': (4, 5, 6)}
(result1, result2, result3) = choices.get(key, ('default1', 'default2', 'default3'))

16
Bunu kabul edilenden daha sağlam bir cevap olarak görüyorum.
Cerd

3
@some user: C, dönüş değerinin tüm durumlar için aynı tür olmasını gerektirir. Python yapmaz. Python'un bu esnekliğini, birinin böyle bir kullanımı gerektiren bir durum olması durumunda vurgulamak istedim.
ChaimG

3
@some user: Şahsen ben {} .get (,) okunabilir buluyorum. Python yeni başlayanlar için ekstra okunabilirlik için kullanmak isteyebilirsiniz default = -1; result = choices.get(key, default).
ChaimG

4
c ++ ile 1 satır karşılaştırresult=key=='a'?1:key==b?2:-1
Jasen

4
@Jasen biri siz de Python biri doğrultusunda yapabiliriz iddia edebilirsiniz: result = 1 if key == 'a' else (2 if key == 'b' else 'default'). ama bir astar okunabilir mi?
ChaimG

101
class switch(object):
    value = None
    def __new__(class_, value):
        class_.value = value
        return True

def case(*args):
    return any((arg == switch.value for arg in args))

Kullanımı:

while switch(n):
    if case(0):
        print "You typed zero."
        break
    if case(1, 4, 9):
        print "n is a perfect square."
        break
    if case(2):
        print "n is an even number."
    if case(2, 3, 5, 7):
        print "n is a prime number."
        break
    if case(6, 8):
        print "n is an even number."
        break
    print "Only single-digit numbers are allowed."
    break

Testler:

n = 2
#Result:
#n is an even number.
#n is a prime number.
n = 11
#Result:
#Only single-digit numbers are allowed.

64
Bu tehdit için güvenli değildir. Aynı anda birden fazla anahtara basarsanız, tüm anahtarlar son anahtarın değerini alır.
francescortiz

48
@Francescortiz muhtemelen iş parçacığı güvenli anlamına gelirken, aynı zamanda tehdit güvenli değildir. Değişkenlerin değerlerini tehdit ediyor!
Zizouz212

7
İş parçacığı güvenliği sorunu, yerel iş parçacığı deposu kullanılarak çözülebilir . Veya bir örneği döndürerek ve bu örneği vaka karşılaştırmaları için kullanarak tamamen önlenebilir.
blubberdiblub

6
@blubberdiblub Ancak standart bir ififade kullanmak daha verimli değil mi?
wizzwizz4

9
Bu, birden fazla işlevde kullanıldığında da güvenli değildir. Verilen örnekte, case(2)blok switch () kullanan başka bir fonksiyon case(2, 3, 5, 7)çağırdıysa, yürütülecek bir sonraki vakayı aramak için etc yaparken , geçerli switch deyimi tarafından ayarlanan değil, diğer fonksiyon tarafından ayarlanan switch değerini kullanacaktır. .
user9876

52

En sevdiğim, gerçekten güzel bir tarif . Gerçekten hoşuna gidecek. Özellikle özelliklerde, gerçek anahtar durum ifadelerine en yakın gördüğüm.

class switch(object):
    def __init__(self, value):
        self.value = value
        self.fall = False

    def __iter__(self):
        """Return the match method once, then stop"""
        yield self.match
        raise StopIteration

    def match(self, *args):
        """Indicate whether or not to enter a case suite"""
        if self.fall or not args:
            return True
        elif self.value in args: # changed for v1.5, see below
            self.fall = True
            return True
        else:
            return False

İşte bir örnek:

# The following example is pretty much the exact use-case of a dictionary,
# but is included for its simplicity. Note that you can include statements
# in each suite.
v = 'ten'
for case in switch(v):
    if case('one'):
        print 1
        break
    if case('two'):
        print 2
        break
    if case('ten'):
        print 10
        break
    if case('eleven'):
        print 11
        break
    if case(): # default, could also just omit condition or 'if True'
        print "something else!"
        # No need to break here, it'll stop anyway

# break is used here to look as much like the real thing as possible, but
# elif is generally just as good and more concise.

# Empty suites are considered syntax errors, so intentional fall-throughs
# should contain 'pass'
c = 'z'
for case in switch(c):
    if case('a'): pass # only necessary if the rest of the suite is empty
    if case('b'): pass
    # ...
    if case('y'): pass
    if case('z'):
        print "c is lowercase!"
        break
    if case('A'): pass
    # ...
    if case('Z'):
        print "c is uppercase!"
        break
    if case(): # default
        print "I dunno what c was!"

# As suggested by Pierre Quentel, you can even expand upon the
# functionality of the classic 'case' statement by matching multiple
# cases in a single shot. This greatly benefits operations such as the
# uppercase/lowercase example above:
import string
c = 'A'
for case in switch(c):
    if case(*string.lowercase): # note the * for unpacking as arguments
        print "c is lowercase!"
        break
    if case(*string.uppercase):
        print "c is uppercase!"
        break
    if case('!', '?', '.'): # normal argument passing style also applies
        print "c is a sentence terminator!"
        break
    if case(): # default
        print "I dunno what c was!"

3
Ben yerine olurdu for case in switch()ile with switch() as case, daha mantıklı yalnızca bir kez çalıştırmak için s ihtiyacım beri.
Kayak

4
@Skirmantas: Buna withizin vermediğini unutmayın break, bu nedenle geçiş seçeneği kaldırılır.
Jonas Schäfer

5
Bunu kendim belirlemek için daha fazla çaba göstermediğim için özür dilerim: yukarıdaki benzer bir cevap iş parçacığı güvenli değildir. Bu?
David Winiecki

1
@DavidWiniecki Yukarıdakilerden eksik olan kod bileşenleri (ve muhtemelen activestate tarafından telif hakkı) iş parçacığı açısından güvenli görünmektedir.
Jasen

bunun başka bir versiyonu nasıl olurdu if c in set(range(0,9)): print "digit" elif c in set(map(chr, range(ord('a'), ord('z')))): print "lowercase"?
mpag

51
class Switch:
    def __init__(self, value):
        self.value = value

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        return False # Allows a traceback to occur

    def __call__(self, *values):
        return self.value in values


from datetime import datetime

with Switch(datetime.today().weekday()) as case:
    if case(0):
        # Basic usage of switch
        print("I hate mondays so much.")
        # Note there is no break needed here
    elif case(1,2):
        # This switch also supports multiple conditions (in one line)
        print("When is the weekend going to be here?")
    elif case(3,4):
        print("The weekend is near.")
    else:
        # Default would occur here
        print("Let's go have fun!") # Didn't use case for example purposes

9
Bağlam yöneticilerini kullanmak iyi bir yaratıcı çözümdür. Ben açıklama biraz ekleyerek öneriyoruz ve belki Bağlam Yöneticileri için bazı bilgilere bir bağlantı Bu postayı bazı, iyi, içerik sağlamak için;)
Will

2
/ Elif zincirlerini çok sevmiyorum ama bu Python'un mevcut sözdizimini kullanarak gördüğüm tüm çözümlerin hem en yaratıcı hem de en pratik olanı.
itsbruce

2
Bu gerçekten güzel. Önerilen bir iyileştirme, ifade valuesınıfına başvurabilmeniz için Switch sınıfına (public) özelliği eklemektir case.value.
Peter

48

Twisted Python kodundan öğrendiğim bir model var.

class SMTP:
    def lookupMethod(self, command):
        return getattr(self, 'do_' + command.upper(), None)
    def do_HELO(self, rest):
        return 'Howdy ' + rest
    def do_QUIT(self, rest):
        return 'Bye'

SMTP().lookupMethod('HELO')('foo.bar.com') # => 'Howdy foo.bar.com'
SMTP().lookupMethod('QUIT')('') # => 'Bye'

Bir jetona göndermeniz ve genişletilmiş bir kod parçası yürütmeniz gerektiğinde bunu kullanabilirsiniz. Durum makinesinde state_yöntemleriniz olur ve gönderilir self.state. Bu anahtar, temel sınıftan devralınarak ve kendi do_yöntemlerinizi tanımlayarak temiz bir şekilde genişletilebilir . Çoğu zaman do_temel sınıfta yönteminiz bile olmaz .

Düzenleme: tam olarak nasıl kullanılır

SMTP durumunda HELOtelden alacaksınız . İlgili kod ( twisted/mail/smtp.pybizim durumumuz için değiştirildi) şu şekilde görünüyor

class SMTP:
    # ...

    def do_UNKNOWN(self, rest):
        raise NotImplementedError, 'received unknown command'

    def state_COMMAND(self, line):
        line = line.strip()
        parts = line.split(None, 1)
        if parts:
            method = self.lookupMethod(parts[0]) or self.do_UNKNOWN
            if len(parts) == 2:
                return method(parts[1])
            else:
                return method('')
        else:
            raise SyntaxError, 'bad syntax'

SMTP().state_COMMAND('   HELO   foo.bar.com  ') # => Howdy foo.bar.com

Sen alacaksınız ' HELO foo.bar.com '(veya alabilirsiniz 'QUIT'ya 'RCPT TO: foo'). Bu içine simgeleþtirilmiþ edilir partsolarak ['HELO', 'foo.bar.com']. Gerçek yöntem arama adı alınır parts[0].

(Orijinal yöntem de denir state_COMMAND, çünkü bir durum makinesini uygulamak için aynı modeli kullanır, yani getattr(self, 'state_' + self.mode))


4
Sadece bu yöntemleri doğrudan yöntemleri çağırarak yarar görmüyorum: SMTP (). Do_HELO ('foo.bar.com') Tamam, lookupMethod'da ortak kod olabilir, ancak bunun üzerine de yazılabilir dolaylı derslerden ne kazandığınızı görmüyorum.
Bay Shark

1
Önceden hangi yöntemi arayacağınızı bilemezsiniz, yani 'HELO' bir değişkenden gelir. Orijinal mesaja kullanım örneği ekledim

Basitçe önerebilir miyim: eval ('SMTP (). Do_' + command) ('foo.bar.com')
jforberg

8
eval? ciddi anlamda? ve çağrı başına bir yöntem başlatmak yerine, bir kez çok iyi örnekleyebilir ve dahili durumu olmadığı sürece tüm çağrılarda kullanabiliriz.
Mahesh

1
IMO burada gerçek anahtar çalıştırmak için bir işlev belirtmek için getattr kullanarak gönderme olduğunu. Yöntemler bir modüldeyse, almak için getattr (locals (), func_name) yapabilirsiniz. 'Do_' bölümü güvenlik / hatalar için iyidir, bu nedenle yalnızca önekli işlevler çağrılabilir. SMTP'nin kendisi lookupMethod'u çağırır. İdeal olarak dışarısı bunların hiçbirini bilmez. SMTP () yapmak gerçekten mantıklı değil. LookupMethod (name) (data). Komut ve veriler bir dizede bulunduğundan ve SMTP onu ayrıştırdığından, bu daha mantıklıdır. Son olarak, SMTP muhtemelen bir sınıf olmasını haklı kılan başka bir paylaşılan duruma sahiptir.
ShawnFumo

27

Diyelim ki sadece bir değer döndürmek istemiyorsunuz, aynı zamanda bir nesnede bir şeyi değiştiren yöntemleri kullanmak istiyorsunuz. Burada belirtilen yaklaşımı kullanmak:

result = {
  'a': obj.increment(x),
  'b': obj.decrement(x)
}.get(value, obj.default(x))

Burada olan şey, python'un sözlükteki tüm yöntemleri değerlendirmesidir. Bu nedenle, değeriniz 'a' olsa bile, nesne x ile artırılır ve azaltılır.

Çözüm:

func, args = {
  'a' : (obj.increment, (x,)),
  'b' : (obj.decrement, (x,)),
}.get(value, (obj.default, (x,)))

result = func(*args)

Böylece bir işlev ve argümanlarını içeren bir liste alırsınız. Bu şekilde, yalnızca işlev işaretçisi ve argüman listesi geri verilmesinde, değil değerlendirdi. 'sonuç' sonra döndürülen işlev çağrısını değerlendirir.


23

İki sentimi buraya bırakacağım. Python'da bir vaka / anahtar ifadesinin olmamasının nedeni, Python'un 'Bir şey yapmanın tek bir doğru yolu' ilkesini izlemesidir. Açıkçası, anahtar / vaka işlevselliğini yeniden yaratmanın çeşitli yollarını bulabilirdiniz, ancak bunu gerçekleştirmenin Pythonic yolu if / elif yapısıdır. yani

if something:
    return "first thing"
elif somethingelse:
    return "second thing"
elif yetanotherthing:
    return "third thing"
else:
    return "default thing"

PEP 8'in burada başını hak ettiğini hissettim. Python ile ilgili güzel şeylerden biri sadeliği ve zarafeti. Bu büyük ölçüde PEP 8'de "Bir şeyi yapmanın tek bir doğru yolu var"


6
Peki neden Python'un döngüler ve döngüler için var? For döngüsü ile yapabileceğiniz her şey while döngüsü ile uygulayabilirsiniz.
itsbruce

1
Doğru. Anahtar / kasa, programcıları başlatırken sıklıkla istismar edilir. Gerçekten istedikleri şey strateji modelidir .
user228395

Python, Clojure
TWR Cole

1
@TWRCole Sanmıyorum, Python ilk önce bunu yapıyordu. Python 1990'dan beri, Clojure 2007'den beri var.
Taylor

Bir şey yapmanın tek bir doğru yolu var. Python 2.7 veya Python 3? Lol.
TWR Cole

17

"anahtar olarak dikte" fikrini genişletiyor. anahtarınız için varsayılan bir değer kullanmak istiyorsanız:

def f(x):
    try:
        return {
            'a': 1,
            'b': 2,
        }[x]
    except KeyError:
        return 'default'

14
Dict üzerinde belirtilen varsayılan ile .get () kullanmak daha net olduğunu düşünüyorum. İstisnai durumlar için İstisnalar bırakmayı tercih ederim ve üç satır kod ve belirsiz bir girinti seviyesi keser.
Chris

10
Bu ise istisnai bir durum. Yararlıya bağlı olarak nadir bir durum olabilir veya olmayabilir , ancak kesinlikle 'default'kuraldan bir istisna (geri çekilme) (bu diksiyondan bir şey alın). Tasarım gereği, Python programları şapka kapağında istisnalar kullanır. Bununla birlikte, kullanmak getkodu biraz daha hoş hale getirebilir.
Mike Graham

16

Karmaşık bir vaka bloğunuz varsa, bir işlev sözlüğü arama tablosu kullanmayı düşünebilirsiniz ...

Bunu daha önce yapmadıysanız, hata ayıklayıcınıza adım atmak ve sözlüğün her işlevi nasıl aradığını görmek iyi bir fikirdir.

NOT: Do not "()" veya vaka / sözlük arama içeride o Sözlük / kutu bloğu oluşturulur olarak fonksiyonların her arayacak kullanın. Bunu unutmayın, çünkü her işlevi yalnızca bir kez karma stil araması kullanarak çağırmak istersiniz.

def first_case():
    print "first"

def second_case():
    print "second"

def third_case():
    print "third"

mycase = {
'first': first_case, #do not use ()
'second': second_case, #do not use ()
'third': third_case #do not use ()
}
myfunc = mycase['first']
myfunc()

Çözümünü beğendim. Ancak, bazı değişkenleri veya nesneleri iletmem gerekirse ne olur?
Tedo Vrbanec

Yöntem parametreler bekliyorsa bu çalışmaz.
Kulasangar

16

Ekstra deyim arıyorsanız "switch" olarak Python'u genişleten bir python modülü oluşturdum. ESPY'ye "Python için Gelişmiş Yapı" denir ve hem Python 2.x hem de Python 3.x için kullanılabilir.

Örneğin, bu durumda, aşağıdaki kodla bir switch ifadesi gerçekleştirilebilir:

macro switch(arg1):
    while True:
        cont=False
        val=%arg1%
        socket case(arg2):
            if val==%arg2% or cont:
                cont=True
                socket
        socket else:
            socket
        break

şu şekilde kullanılabilir:

a=3
switch(a):
    case(0):
        print("Zero")
    case(1):
        print("Smaller than 2"):
        break
    else:
        print ("greater than 1")

bu yüzden espy Python'da şöyle tercüme eder:

a=3
while True:
    cont=False
    if a==0 or cont:
        cont=True
        print ("Zero")
    if a==1 or cont:
        cont=True
        print ("Smaller than 2")
        break
    print ("greater than 1")
    break

Çok güzel, ama while True:oluşturulan Python kodunun üstündeki nokta nedir? Bu kaçınılmaz vurmak edeceğiz breakhem geliyor bana bu yüzden, oluşturulan Python kodunun en altına while True:ve breakkaldırılmış olabilir. Ayrıca, ESPY cont, kullanıcı aynı kodu kendi kodunda kullanıyorsa adını değiştirmek için yeterince akıllı mıdır? Her durumda, vanilya Python kullanmak istiyorum, bu yüzden bunu kullanmayacağım, ama hiç de havasız. Saf serinlik için +1.
ArtOfWarfare

@ArtOfWarfare while True:ve breaks'nin nedeni düşmesine izin vermek, ancak buna izin vermemek.
Solomon Ucko

Bu modül hala mevcut mu?
Solomon Ucko

15

Ortak bir anahtar yapısı buldum:

switch ...parameter...
case p1: v1; break;
case p2: v2; break;
default: v3;

Python ile şu şekilde ifade edilebilir:

(lambda x: v1 if p1(x) else v2 if p2(x) else v3)

veya daha net bir şekilde biçimlendirilmiş:

(lambda x:
     v1 if p1(x) else
     v2 if p2(x) else
     v3)

Bir deyim olmak yerine, python sürümü bir değeri değerlendiren bir ifadedir.


Ayrıca ... parametre ... ve p1 (x) yerine parametervep1==parameter
Bob Stein

BobStein-VisiBone @ hi, burada bir örnek olduğunu benim piton oturumunda çalışır: f = lambda x: 'a' if x==0 else 'b' if x==1 else 'c'. Daha sonra f(2)aradığımda 'c'; f(1), 'b'; ve f(0), 'a'. P1 (x) 'e gelince, bir yüklemi belirtir; geri döndüğü müddetçe Trueya Falseda bir işlev çağrısı ya da bir ifade olursa olsun sorun yok.
leo

@ BobStein-VisiBone Evet, haklısın! Teşekkürler :) Çok satırlı ifadenin çalışması için, önerinizdeki gibi veya değiştirilmiş örneğimdeki gibi parantezler yerleştirilmelidir.
leo

Mükemmel. Şimdi parens hakkındaki tüm yorumlarımı sileceğim .
Bob Stein

15

Buradaki cevapların çoğu oldukça eski ve özellikle kabul edilenler, bu yüzden güncellenmeye değer görünüyor.

İlk olarak, resmi Python SSS bu konuyu kapsar ve elifbasit vakalar için ve dictdaha büyük veya daha karmaşık vakalar için zinciri önerir . Ayrıca visit_, bazı durumlarda bir dizi yöntem (birçok sunucu çerçevesi tarafından kullanılan bir stil) önerir :

def dispatch(self, value):
    method_name = 'visit_' + str(value)
    method = getattr(self, method_name)
    method()

SSS ayrıca C-tarzı anahtar ifadeleri ekleme konusunda resmi bir kez ve hep birlikte karar almak için yazılan PEP 275'ten de bahsetmektedir . Ancak bu PEP aslında Python 3'e ertelendi ve sadece resmi olarak ayrı bir teklif olarak reddedildi, PEP 3103 . Yanıt elbette hayırdı, ancak nedenlerle veya geçmişle ilgileniyorsanız iki PEP'in ek bilgilere bağlantıları vardır.


Birden fazla kez ortaya çıkan bir şey (ve gerçek bir öneri olarak kesilmiş olsa da PEP 275'te görülebilir), 4 vakayı işlemek için 8 satır kodla gerçekten rahatsız olursanız, C veya Bash'taki satırlara her zaman şunu yazabilirsiniz:

if x == 1: print('first')
elif x == 2: print('second')
elif x == 3: print('third')
else: print('did not place')

Bu PEP 8 tarafından tam olarak teşvik edilmez, ancak okunabilir ve çok tekdüze değil.


PEP 3103'ün reddedilmesinden bu yana geçen on yıldan fazla bir süredir, C-tarzı vaka ifadeleri, hatta Go'daki biraz daha güçlü bir sürümün ölü olduğu düşünülüyordu; ne zaman birisi python-fikirleri ya da -dev'i ortaya koyarsa, eski karara yönlendirilirler.

Bununla birlikte, tam ML tarzı desen eşleştirme fikri, özellikle Swift ve Rust gibi diller onu benimsediğinden, birkaç yılda bir ortaya çıkar. Sorun, cebirsel veri türleri olmadan desen eşleşmesinden çok fazla faydalanmanın zor olmasıdır. Guido bu fikre sempati duysa da, hiç kimse Python'a çok iyi uyan bir teklif bulamadı. ( Bir örnek için 2014 strawman'ımı okuyabilirsiniz .) Bu, 3.7'dedataclass ve enumtoplam türlerini işlemek için daha güçlü bir şekilde bazı düzensiz tekliflerle veya farklı türde yerel bildirimler için ( PEP 3150 veya şu anda tartışılan teklifler kümesi -ideler). Ama şimdiye kadar olmadı.

Ayrıca, temelde elifnormal ifadeden tek gönderim tür anahtarlamaya kadar her şeyin bir karması olan Perl 6 tarzı eşleştirme için öneriler de vardır .


15

Fonksiyonları çalıştırmak için çözüm:

result = {
    'case1':     foo1, 
    'case2':     foo2,
    'case3':     foo3,
    'default':   default,
}.get(option)()

burada foo1 (), foo2 (), foo3 () ve default () işlevlerdir


1
Evet, örneğin değişken seçeneğiniz == "case2" sonucunuz = foo2 ()
Alejandro Quintanar

ve benzerleri.
Alejandro Quintanar

Evet, amacı anlıyorum. Ama benim endişem sadece istiyorsanız olmasıdır foo2(), foo1(), foo3(), ve default()işlevleri tüm aynı zamanda uzun zaman alabilir şeyler yani çalıştırmak için gidiyoruz
Brian Underwood

1
sözlüğün içindeki () işaretini atlayın. kullanın get(option)(). sorun çözüldü.
timgeb

1
Mükemmel () kullanımı bir ızgaralı çözümdür, test etmek için bir gist
Alejandro Quintanar

13

Google aramada hiçbir yerde aradığım basit cevabı bulamadım. Ama yine de anladım. Gerçekten çok basit. Göndermeye karar verdim ve belki de başkasının kafasında birkaç çizik olmasını önleyin. Anahtar basitçe "içeri" ve tuples. RANDOM düşmesi de dahil olmak üzere düşme ile switch deyimi davranışı.

l = ['Dog', 'Cat', 'Bird', 'Bigfoot',
     'Dragonfly', 'Snake', 'Bat', 'Loch Ness Monster']

for x in l:
    if x in ('Dog', 'Cat'):
        x += " has four legs"
    elif x in ('Bat', 'Bird', 'Dragonfly'):
        x += " has wings."
    elif x in ('Snake',):
        x += " has a forked tongue."
    else:
        x += " is a big mystery by default."
    print(x)

print()

for x in range(10):
    if x in (0, 1):
        x = "Values 0 and 1 caught here."
    elif x in (2,):
        x = "Value 2 caught here."
    elif x in (3, 7, 8):
        x = "Values 3, 7, 8 caught here."
    elif x in (4, 6):
        x = "Values 4 and 6 caught here"
    else:
        x = "Values 5 and 9 caught in default."
    print(x)

sağlar:

Dog has four legs
Cat has four legs
Bird has wings.
Bigfoot is a big mystery by default.
Dragonfly has wings.
Snake has a forked tongue.
Bat has wings.
Loch Ness Monster is a big mystery by default.

Values 0 and 1 caught here.
Values 0 and 1 caught here.
Value 2 caught here.
Values 3, 7, 8 caught here.
Values 4 and 6 caught here
Values 5 and 9 caught in default.
Values 4 and 6 caught here
Values 3, 7, 8 caught here.
Values 3, 7, 8 caught here.
Values 5 and 9 caught in default.

Burada tam olarak nerede?
Jonas Schäfer

Hata! Orada düşüş var, ama artık Stack Overflow'a katkıda bulunmuyorum. ONLARI hiç sevmiyorum. Başkalarının katkılarını seviyorum, ama Stackoverflow değil. FUNCTIONALITY için fall through kullanıyorsanız, bir anahtardaki bir break deyimine ulaşıncaya kadar, bir anahtardaki (bir catch all) bir durum ifadesinde belirli koşulları CATCH yapmak istersiniz.
JD Graham

2
Burada hem "Köpek" hem de "Kedi" değerleri DÜŞÜYOR ve AYNI işlevsellik tarafından işleniyor, yani "dört ayak" olarak tanımlanıyorlar. Bir ABSTRACT eşdeğeri ve bir kesme oluştuğunda SAME vaka ifadesi tarafından işlenen farklı değerler.
JD Graham

@JDGraham Bence Jonas, programcı zaman zaman a'nın breakkodunun sonuna yazmayı unuttuğunda gerçekleşen, düşüşün başka bir yönü anlamına geliyordu case. Ama bence böyle bir "çöküş" lazım :)
Mikhail Batcer

12

Kullandığım çözümler:

Burada yayınlanan ve okunması nispeten kolay olan ve varsayılanları destekleyen 2 çözümün bir kombinasyonu.

result = {
  'a': lambda x: x * 5,
  'b': lambda x: x + 7,
  'c': lambda x: x - 2
}.get(whatToUse, lambda x: x - 22)(value)

nerede

.get('c', lambda x: x - 22)(23)

"lambda x: x - 2"dikte bakar ve kullanırx=23

.get('xxx', lambda x: x - 22)(44)

bunu dikte bulamaz ve varsayılanı "lambda x: x - 22"ile kullanır x=44.


10
# simple case alternative

some_value = 5.0

# this while loop block simulates a case block

# case
while True:

    # case 1
    if some_value > 5:
        print ('Greater than five')
        break

    # case 2
    if some_value == 5:
        print ('Equal to five')
        break

    # else case 3
    print ( 'Must be less than 5')
    break

10
def f(x):
    dictionary = {'a':1, 'b':2, 'c':3}
    return dictionary.get(x,'Not Found') 
##Returns the value for the letter x;returns 'Not Found' if x isn't a key in the dictionary

Kodunuzun kısa bir açıklamasını ve gönderilen soruyu nasıl çözdüğünü düşünün
Henry Woody

Tamam, şimdi bunun için bir yorum ekledim.
Vikhyat Agarwal

8

Mark Bies'in cevabını beğendim

Yana xdeğişken zorunluluk iki kez kullanılan, ben parametresiz lambda fonksiyonları modifiye.

Koşmak zorundayım results[value](value)

In [2]: result = {
    ...:   'a': lambda x: 'A',
    ...:   'b': lambda x: 'B',
    ...:   'c': lambda x: 'C'
    ...: }
    ...: result['a']('a')
    ...: 
Out[2]: 'A'

In [3]: result = {
    ...:   'a': lambda : 'A',
    ...:   'b': lambda : 'B',
    ...:   'c': lambda : 'C',
    ...:   None: lambda : 'Nothing else matters'

    ...: }
    ...: result['a']()
    ...: 
Out[3]: 'A'

Düzenleme: Ben Nonesözlük ile birlikte türü kullanabileceğini fark ettim . Bu taklit olurduswitch ; case else


None durumu basitçe taklit edilmiyor mu result[None]()?
Bob Stein

Evet kesinlikle. Yaniresult = {'a': 100, None:5000}; result[None]
guneysus

4
Sadece kimse düşünüyor olmadığını kontrol None:gibi davranır default:.
Bob Stein

7
def f(x):
     return 1 if x == 'a' else\
            2 if x in 'bcd' else\
            0 #default

Kısa ve okunması kolay, varsayılan bir değere sahiptir ve hem koşullarda hem de dönüş değerlerinde ifadeleri destekler.

Ancak, sözlüğü olan çözümden daha az verimlidir. Örneğin, Python varsayılan değeri döndürmeden önce tüm koşulları taramak zorundadır.


7

gönderilen bir sözlük kullanabilirsiniz:

#!/usr/bin/env python


def case1():
    print("This is case 1")

def case2():
    print("This is case 2")

def case3():
    print("This is case 3")


token_dict = {
    "case1" : case1,
    "case2" : case2,
    "case3" : case3,
}


def main():
    cases = ("case1", "case3", "case2", "case1")
    for case in cases:
        token_dict[case]()


if __name__ == '__main__':
    main()

Çıktı:

This is case 1
This is case 3
This is case 2
This is case 1

6

Basit, test edilmedi; her koşul bağımsız olarak değerlendirilir: düşüş olmaz, ancak bir kesme ifadesi olmadığı sürece tüm durumlar değerlendirilir (açılma ifadesi yalnızca bir kez değerlendirilir). Örneğin,

for case in [expression]:
    if case == 1:
        print(end='Was 1. ')

    if case == 2:
        print(end='Was 2. ')
        break

    if case in (1, 2):
        print(end='Was 1 or 2. ')

    print(end='Was something. ')

baskılar Was 1. Was 1 or 2. Was something. (Dammit! Neden? inline kod bloklar halinde sonlarındaki boşluk olamaz) eğer expressionüzere değerlendirir 1, Was 2.eğer expressiondeğerlendirir için 2veya Was something.eğer expressiondeğerlendirir başka bir şeye.


1
Eh, sonbaharda çalışır, ancak sadece do_default'a gitmek.
syockit

5

tanımlanması:

def switch1(value, options):
  if value in options:
    options[value]()

durumları bir harita halinde paketlenmiş olarak oldukça basit bir sözdizimi kullanmanıza olanak tanır:

def sample1(x):
  local = 'betty'
  switch1(x, {
    'a': lambda: print("hello"),
    'b': lambda: (
      print("goodbye," + local),
      print("!")),
    })

Anahtarı "lambda:" 'dan kurtulmama izin verecek şekilde yeniden tanımlamaya çalıştım ama vazgeçtim. Tanımı değiştirmek:

def switch(value, *maps):
  options = {}
  for m in maps:
    options.update(m)
  if value in options:
    options[value]()
  elif None in options:
    options[None]()

Birden fazla örneği aynı koda eşlememe ve varsayılan bir seçenek sunmama izin verdi:

def sample(x):
  switch(x, {
    _: lambda: print("other") 
    for _ in 'cdef'
    }, {
    'a': lambda: print("hello"),
    'b': lambda: (
      print("goodbye,"),
      print("!")),
    None: lambda: print("I dunno")
    })

Her çoğaltılmış durumun kendi sözlüğünde olması gerekir; switch (), değeri aramadan önce sözlükleri birleştirir. Hala istediğimden daha çirkin, ama tüm anahtarlar arasında bir döngü yerine ifadede karma bir arama kullanmanın temel verimliliğine sahip.


5

En iyi yolu kod test edilebilir tutmak için python dil deyimlerini kullanmak olduğunu düşünüyorum . Önceki yanıtlarda gösterildiği gibi , python yapılarından ve dilinden yararlanmak ve "case" kodunu farklı yöntemlerle izole tutmak için sözlükler kullanıyorum . Aşağıda bir sınıf var, ancak doğrudan bir modül, globaller ve fonksiyonlar kullanabilirsiniz. Sınıfta izolasyon ile test edilebilecek yöntemler vardır . İhtiyaçlarınıza bağlı olarak, statik yöntemler ve niteliklerle de oynayabilirsiniz.

class ChoiceManager:

    def __init__(self):
        self.__choice_table = \
        {
            "CHOICE1" : self.my_func1,
            "CHOICE2" : self.my_func2,
        }

    def my_func1(self, data):
        pass

    def my_func2(self, data):
        pass

    def process(self, case, data):
        return self.__choice_table[case](data)

ChoiceManager().process("CHOICE1", my_data)

"__Choice_table" anahtarları olarak sınıflar kullanarak da bu yöntemden yararlanmak mümkündür . Bu şekilde isinstance kötüye kullanımını önleyebilir ve tüm temiz ve test edilebilir tutabilirsiniz.

İnternetten veya MQ'nuzdan çok fazla mesaj veya paket işlemeniz gerektiğini varsayalım. Her paketin kendi yapısı ve yönetim kodu vardır (genel bir şekilde). Yukarıdaki kod ile böyle bir şey yapmak mümkündür:

class PacketManager:

    def __init__(self):
        self.__choice_table = \
        {
            ControlMessage : self.my_func1,
            DiagnosticMessage : self.my_func2,
        }

    def my_func1(self, data):
        # process the control message here
        pass

    def my_func2(self, data):
        # process the diagnostic message here
        pass

    def process(self, pkt):
        return self.__choice_table[pkt.__class__](pkt)

pkt = GetMyPacketFromNet()
PacketManager().process(pkt)


# isolated test or isolated usage example
def test_control_packet():
    p = ControlMessage()
    PacketManager().my_func1(p)

Dolayısıyla karmaşıklık kod akışında yayılmaz ancak kod yapısında oluşturulur .


Gerçekten çirkin ... anahtar kutusu okurken çok temiz. Python'a neden uygulanmadığını anlayamıyorum.
jmcollin92

@AndyClifton: Üzgünüm ... bir örnek? Her seferinde birden fazla karar dallanma koduna sahip olmanız gerektiğini düşünün ve bu yöntemi uygulayabilirsiniz.
J_Zar

@ jmcollin92: switch ifadesi konforlu, katılıyorum. Ancak programcı, tekrar kullanılamayan çok uzun ifadeler ve kod yazma eğilimindedir. Anlattığım yol test etmek için daha temiz ve daha çok kullanılabilir, IMHO.
J_Zar

@J_Zar: yeniden. Bir örnek için isteğim: evet, anladım, ama bunu daha büyük bir kodun bağlamına koymak için uğraşıyorum. Bunu gerçek dünyada nasıl kullanabileceğimi gösterebilir misin?
Andy Clifton

1
@AndyClifton: Üzgünüm, geç kaldım ama bazı örnek olay gönderdim.
J_Zar

5

Üzerine Genişleyen Greg Hewgill cevabı - Bir dekoratör kullanarak Sözlük-çözüm sarabiliriz:

def case(callable):
    """switch-case decorator"""
    class case_class(object):
        def __init__(self, *args, **kwargs):
            self.args = args
            self.kwargs = kwargs

        def do_call(self):
            return callable(*self.args, **self.kwargs)

return case_class

def switch(key, cases, default=None):
    """switch-statement"""
    ret = None
    try:
        ret = case[key].do_call()
    except KeyError:
        if default:
            ret = default.do_call()
    finally:
        return ret

Bu daha sonra @case-decorator ile kullanılabilir

@case
def case_1(arg1):
    print 'case_1: ', arg1

@case
def case_2(arg1, arg2):
    print 'case_2'
    return arg1, arg2

@case
def default_case(arg1, arg2, arg3):
    print 'default_case: ', arg1, arg2, arg3

ret = switch(somearg, {
    1: case_1('somestring'),
    2: case_2(13, 42)
}, default_case(123, 'astring', 3.14))

print ret

İyi haber şu ki, bu zaten NeoPySwitch modülünde yapılmış . Sadece pip kullanarak kurun:

pip install NeoPySwitch

5

Sözlükleri de kullanan, kullanma eğiliminde olduğum bir çözüm:

def decision_time( key, *args, **kwargs):
    def action1()
        """This function is a closure - and has access to all the arguments"""
        pass
    def action2()
        """This function is a closure - and has access to all the arguments"""
        pass
    def action3()
        """This function is a closure - and has access to all the arguments"""
        pass

   return {1:action1, 2:action2, 3:action3}.get(key,default)()

Bunun avantajı, her seferinde fonksiyonları değerlendirmeye çalışmadığıdır ve sadece dış fonksiyonun iç fonksiyonların ihtiyaç duyduğu tüm bilgileri almasını sağlamanız gerekir.


5

Şimdiye kadar, "Python'da bir anahtarımız yok, bu şekilde yap" diyen çok sayıda cevap vardı. Ancak, switch ifadesinin kendisinin, tembel programlamayı teşvik ettikleri için çoğu durumda kaçınılması gereken ve kaçınılması gereken kolayca kötüye kullanılan bir yapı olduğunu belirtmek isterim. Konuşma konusu olan mesele:

def ToUpper(lcChar):
    if (lcChar == 'a' or lcChar == 'A'):
        return 'A'
    elif (lcChar == 'b' or lcChar == 'B'):
        return 'B'
    ...
    elif (lcChar == 'z' or lcChar == 'Z'):
        return 'Z'
    else:
        return None        # or something

Şimdi, olabilir bir switch deyimi ile bunu (Python bir teklif varsa) ama bu sadece para cezası yapmak yöntem vardır çünkü vaktini boşa olurdu. Ya da belki daha az belirgin bir şeyiniz vardır:

def ConvertToReason(code):
    if (code == 200):
        return 'Okay'
    elif (code == 400):
        return 'Bad Request'
    elif (code == 404):
        return 'Not Found'
    else:
        return None

Bununla birlikte, bu tür bir işlem daha hızlı, daha az karmaşık, hataya daha az eğilimli ve daha kompakt olacağı için bir sözlükle ele alınabilir ve ele alınmalıdır.

Ve anahtar ifadeleri için "kullanım senaryolarının" büyük çoğunluğu bu iki durumdan birine girecektir; sorununuzu iyice düşündüyseniz, bunu kullanmanız için çok az neden vardır.

Yani, "Python'a nasıl geçebilirim?" Sorusunu sormak yerine, belki de "Python'a neden geçmek istiyorum?" Diye sormalıyız. çünkü bu genellikle daha ilginç bir sorudur ve inşa ettiğiniz şeyin tasarımındaki kusurları ortaya çıkarır.

Şimdi, bu anahtarların asla kullanılmaması gerektiği anlamına gelmez. Durum makineleri, sözlükler, ayrıştırıcılar ve otomatalar bunları bir dereceye kadar kullanırlar ve genel olarak simetrik bir girdiden başlayıp asimetrik bir çıktıya gittiğinizde yararlı olabilirler; kodunuzda bir sürü çivi gördüğünüz için anahtarı çekiç olarak kullanmadığınızdan emin olmanız gerekir.

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.