Pythonのswitchステートメントの置き換えですか?


1718

入力インデックスの値に基づいてさまざまな固定値を返す関数をPythonで記述したいと思います。

他の言語ではswitchor caseステートメントを使用しますが、Pythonにはステートメントがないようswitchです。このシナリオで推奨されるPythonソリューションは何ですか?


77
Guido自身が作成した関連PEP:PEP 3103
chb

28
@chbそのPEPで、Guidoはif / elifチェーンも古典的なエラーの原因であることを述べていません。これは非常に壊れやすい構造です。
itsbruce 2014年

15
ここですべてのソリューションに欠けているのは、ケース値重複の検出です。フェイルファストの原則として、これはパフォーマンスやフォールスルー機能よりも重要な損失になる可能性があります。
Bob Stein

6
switch実際には、入力インデックスの値に基づいて異なる固定値を返すものより「汎用性」があります。これにより、さまざまなコードを実行できます。実際には値を返す必要さえありません。ここでの回答のいくつかは、一般的なswitchステートメントの良い代替品か、一般的なコードの一部を実行する可能性のない値を返す場合のみかと思います。
sancho.s ReinstateMonicaCellio 2017

3
@ MalikA.Rumi壊れやすい構成体。whileループがfor ... in ...で何をするために使用しようとしても壊れやすい構成体であるのと同じように。forループを使うのが苦手なプログラマを呼ぶつもりですか?ループは実際に必要なすべてですが。しかし、forループは明確な意図を示し、無意味な定型文を保存して、強力な抽象化を作成する機会を与えます。
itsbruce 2017年

回答:


1486

あなたは辞書を使うことができます:

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

100
xが見つからない場合はどうなりますか?
Nick

46
@nick:defaultdictを使用できます
Eli Bendersky

385
パフォーマンスに問題がある場合は、dictを関数の外に置くことをお勧めします。これにより、すべての関数呼び出しでdictが再構築されません
Claudiu

56
@EliBendersky、getメソッドを使用する方がcollections.defaultdict、この場合はを使用するよりもおそらく通常でしょう。
マイクグラハム

27
@Nick、例外がスローされます— }.get(x, default)デフォルトがある場合は、代わりに実行してください。(注:これは、switchステートメントのデフォルトをオフのままにした場合に何が起こるかよりもはるかに優れています。)
Mike Graham

1375

デフォルトが必要な場合は、辞書get(key[, default])メソッドを使用できます。

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

11
「a」と「b」が1に一致し、「c」と「d」が2に一致する場合はどうなりますか?
John Mee

13
@JM:まあ、明らかに辞書の検索はフォールスルーをサポートしていません。あなたは二重の辞書検索を行うことができます。つまり、「a」と「b」は回答1を指し、「c」と「d」は回答2を指します。これらは2番目の辞書に含まれています。
Nick

3
これはデフォルト値を渡す方が良い
HaTiMSuM

このアプローチには問題があります。最初にfを呼び出すたびに、次に2番目の辞書を作成します。より複雑な値がある場合は、exを使用して例外を取得できます。xがタプルで、このようなことをしたい場合x =( 'a')def f(x):return {'a':x [0]、 'b':x [1]} .get( x [0]、9)これによりIndexErrorが発生します
Idan Haim Shalom

2
@アイダン:問題はスイッチを複製することでした。奇数の値を入れてみたら、このコードも破れると確信しています。はい、再作成されますが、修正は簡単です。
Nick

394

私はいつもこのようにするのが好きでした

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

ここから


16
デフォルトを処理するためのget()と組み合わせた優れたメソッドも私の最良の選択です
drAlberT

27
ラムダは実際には辞書が作成されるたびに呼び出されるため、この場合はラムダを使用することはお勧めできません。
Asher

13
悲しいことに、これは人々が手に入れる最も近いものです。.get()(現在の最高の回答のように)を使用するメソッドは、ディスパッチする前にすべての可能性を熱心に評価する必要があるため、(非常にだけでなく)非常に非効率であるだけでなく、副作用もありません。この答えはその問題を回避しますが、より冗長です。私はif / elif / elseを使用するだけで、それらも「ケース」と書くのと同じくらい時間がかかります。
ninjagecko 14年

13
これは、結果の1つしか返さない場合でも、すべての場合にすべての関数/ラムダを毎回評価しませんか?
slf 2014

23
@slfいいえ、制御フローがそのコードに到達すると、3つの関数を構築し(3つのラムダを使用)、これらの3つの関数を値として使用して辞書を構築しますが、呼び出さません(評価は少しあいまいです)そのコンテキスト)最初に。次に、ディクショナリはを介してインデックス付けされます[value]。これにより、3つの関数の1つだけが返されます(value3つのキーの1つであると想定)。その時点では、この関数はまだ呼び出されていません。次に、引数としてが(x)返されたばかりの関数を呼び出しxます(結果はに進みますresult)。他の2つの関数は呼び出されません。
blubberdiblub 2015

354

(私は本当にところで、のような)、あなたも使用できる辞書方法に加えてif- elif- else取得するswitch/ case/ default機能を:

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

もちろん、これはswitch / caseと同じではありません。breakステートメントを省略するほど簡単にフォールスルーすることはできませんが、より複雑なテストを行うことができます。if機能的にはこれに近いものの、そのフォーマットは一連のネストされたs よりも優れています。


51
私はこれが本当に好きです、それは標準の言語構成を使用し、一致するケースが見つからない場合にKeyErrorをスローしません
martyglaubitz

7
辞書/ get方法について考えましたが、標準的な方法の方が読みやすくなっています。
Martin Thoma

2
@someuserですが、「オーバーラップ」できるのが特徴です。順序が一致が発生する優先順位であることを確認するだけです。繰り返しxに関しては、x = the.other.thing前に行うだけです。理解しやすいように、通常、1つのif、複数のelif、および1つのelseがあります。
Matthew Schinckel 2016

7
ただし、「elifを使用しないことによるフォールスルー」は少し混乱します。これについてはどうですか:「フォールスルー」を忘れて、それを2つとして受け入れif/elif/elseますか?
2016年

7
また、のようなものを使用して言及、価値がx in 'bc'心に留めておく、"" in "bc"ですTrue
Lohmar ASHAR

185

スイッチ/ケースの私のお気に入りのPythonレシピは次のとおりです。

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

単純なシナリオでは短くて単純です。

11行以上のCコードと比較してください。

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

タプルを使用して複数の変数を割り当てることもできます。

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

16
これは、承認されたものよりも強力な答えであると思います。
cerd

3
@一部のユーザー:Cでは、すべてのケースで戻り値が同じ型である必要があります。Pythonではサポートしていません。私は誰かがそのような使用を正当化する状況にあった場合に備えて、Pythonのこの柔軟性を強調したいと思いました。
ChaimG 2016年

3
@一部のユーザー:個人的には、{}。get(、)は読み取り可能です。Python初心者のために読みやすくするには、を使用することをお勧めしますdefault = -1; result = choices.get(key, default)
ChaimG 2016年

4
c ++の1行と比較result=key=='a'?1:key==b?2:-1
Jasen

4
@Jasenは、Pythonの1行でも実行できると主張できますresult = 1 if key == 'a' else (2 if key == 'b' else 'default')。しかし、1つのライナーは読み取り可能ですか?
ChaimG 2016

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))

使用法:

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

テスト:

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

64
これは脅威に対して安全ではありません。複数のスイッチが同時にヒットした場合、すべてのスイッチが最後のスイッチの値を取ります。
francescortiz 2013年

48
@francescortizはスレッドセーフを意味する可能性がありますが、脅威に対しても安全ではありません。変数の値を脅かしています!
Zizouz212 2015年

7
スレッドセーフの問題は、スレッドローカルストレージを使用することで回避できる可能性があります。または、インスタンスを返し、そのインスタンスをケース比較に使用することで、完全に回避することもできます。
blubberdiblub 2015

6
@blubberdiblubしかし、標準のifステートメントを使用する方が効率的ではないですか?
wizzwizz4 2016年

9
複数の関数で使用する場合も安全ではありません。上記の例では、case(2)ブロックがswitch()を使用する別の関数を呼び出した場合、case(2, 3, 5, 7)etcを実行して次のケースを探して実行すると、現在のswitchステートメントによって設定されたものではなく、他の関数によって設定されたスイッチ値が使用されます。
user9876 2017

52

私のお気に入りは本当に素敵なレシピです。あなたは本当にそれを好きになるでしょう。これは、特に機能の点で、実際のスイッチケースステートメントに最も近いものです。

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

次に例を示します。

# 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
私が代わりしまうfor case in switch()with switch() as case、それは一度だけ実行するのに必要があるため、より理にかなって、。
スキー

4
@Skirmantas:ただし、これはwith許可されbreakないため、フォールスルーオプションは使用できません。
JonasSchäfer、2014年

5
これを自分で決定するためにこれ以上の労力を費やさなかったことに対する謝罪:上記の同様の答えはスレッドセーフではありません。これは?
David Winiecki 14

1
@DavidWiniecki上記から欠落しているコードコンポーネント(および場合によってはactivestateによる著作権)はスレッドセーフであるように見えます。
Jasen

これの別のバージョンは次のようなもの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
コンテキストマネージャを使用することは、優れたクリエイティブソリューションです。私はこの記事にいくつかの、よく、コンテキストを与えるために、説明のビットを追加することをお勧めしますと、おそらくコンテキストマネージャにいくつかの情報へのリンクと思います;)
ウィル

2
if / elifチェーンはあまり好きではありませんが、これは、Pythonの既存の構文を使用して見たすべてのソリューションの中で、最もクリエイティブで最も実用的なものです。
itsbruce 2017年

2
これは本当にいいです。推奨される改善点の1つは、(パブリック)valueプロパティをSwitchクラスに追加してcase.value、ステートメント内でを参照できるようにすることです。
Peter

48

Twisted Pythonコードから学んだパターンがあります。

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'

トークンでディスパッチして拡張コードを実行する必要があるときにいつでも使用できます。ステートマシンでは、state_メソッドがあり、でディスパッチされself.stateます。このスイッチは、基本クラスから継承して独自のdo_メソッドを定義することにより、きれいに拡張できます。多くの場合do_、基本クラスにメソッドさえありません。

編集:それはどのように使用されていますか

SMTPの場合、ネットワークHELOから受信します。関連するコード(からtwisted/mail/smtp.py、私たちのケース用に変更)は次のようになります

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

あなたは受け取ります' HELO foo.bar.com '(またはあなたが得る可能性があります'QUIT''RCPT TO: foo')。これはとしてトークン化さparts['HELO', 'foo.bar.com']ます。実際のメソッド検索名はから取得されparts[0]ます。

(元のメソッドはstate_COMMAND、同じパターンを使用して状態マシンを実装するため、とも呼ばれますgetattr(self, 'state_' + self.mode)


4
メソッドを直接呼び出すだけのこのパターンの利点はわかりません:SMTP()。do_HELO( 'foo.bar.com')OK、lookupMethodに共通のコードがある可能性がありますが、それによって次のように上書きすることもできます:サブクラス私はあなたが間接から得るものを見ません。
シャーク氏

1
事前にどのメソッドを呼び出すかはわかりません。つまり、「HELO」は変数から取得されます。使用例を元の投稿に追加しました

簡単に提案しましょう:eval( 'SMTP()。do_' + command)( 'foo.bar.com')
jforberg

8
評価?真剣に?また、呼び出しごとに1つのメソッドをインスタンス化する代わりに、内部状態がない場合は、一度インスタンス化してすべての呼び出しで使用することもできます。
Mahesh 2013年

1
ここでのIMOの実際のキーは、getattrを使用して実行する関数を指定するディスパッチです。メソッドがモジュール内にある場合は、getattr(locals()、func_name)を実行して取得できます。「do_」の部分はセキュリティ/エラーに適しているため、プレフィックスの付いたfuncのみを呼び出すことができます。SMTP自体がlookupMethodを呼び出します。理想的には、外部はこれについて何も知りません。SMTP()。lookupMethod(name)(data)を行うことは実際には意味がありません。コマンドとデータは1つの文字列にあり、SMTPはそれを解析するので、それはより理にかなっています。最後に、SMTPにはおそらくクラスであることを正当化する他の共有状態があります。
ShawnFumo 2013

27

値を返すだけでなく、オブジェクトの何かを変更するメソッドを使用したいとします。ここで述べたアプローチを使用すると、次のようになります。

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

ここで何が起こるかは、Pythonが辞書内のすべてのメソッドを評価することです。したがって、値が「a」の場合でも、オブジェクトはインクリメントされ xデクリメントされます。

解決:

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

result = func(*args)

したがって、関数とその引数を含むリストを取得します。この方法では、関数ポインタと引数リストのみが返され、評価されません。次に、 'result'は返された関数呼び出しを評価します。


23

ここに2セントを落とします。Pythonにcase / switchステートメントがない理由は、Pythonが「何かを行うには正しい方法が1つしかない」という原則に従うためです。ですから、switch / case機能を再現するさまざまな方法を考え出すことができるのは明らかですが、これを実現するPythonの方法はif / elif構文です。すなわち

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

私はPEP 8がここでうなずくに値すると感じました。Pythonの美しい点の1つは、そのシンプルさと優雅さです。これは主に、PEP 8で定められた「何かを行うための正しい方法は1つだけある」などの原則に由来しています。


6
では、なぜPythonにはforループとwhileループがあるのでしょうか。forループでできることはすべて、whileループで実装できます。
itsbruce 2017年

1
本当です。スイッチ/ケースは、初心者プログラマーによって頻繁に悪用されます。彼らが本当に望んでいるのは戦略パターンです。
user228395 2017年

PythonがClojureであることを望んでいるように聞こえる
TWR Cole

1
@TWRCole私はそうは思わない、Pythonが最初にそれをしていた。Pythonは2007年から1990年のClojure以来の周りされている
テイラー

何かを行う正しい方法は1つだけです。Python 2.7またはPython 3?笑。
TWR Cole

17

「dict as switch」の考えを拡張します。スイッチのデフォルト値を使用する場合:

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

14
デフォルトを指定してdictで.get()を使用する方が明確だと思います。私は例外的な状況のために例外を残すことを好みます、そしてそれは曖昧になることなくコードの3行とインデントのレベルをカットします。
クリスB.

10
これ例外的な状況です。これは、有用性に応じてまれな状況になる場合とそうでない場合がありますが'default'、ルールからの例外(にフォールバック)です(この辞書から何かを取得します)。設計上、Pythonプログラムはすぐに例外を使用します。そうは言っても、使用getすることでコードが少し良くなる可能性があります。
マイクグラハム

16

複雑なケースブロックがある場合は、関数辞書ルックアップテーブルの使用を検討できます...

以前にこれを行っていない場合は、デバッガーにステップインして、ディクショナリが各関数をどのように検索するかを正確に表示することをお勧めします。

注:ケース/辞書のルックアップ内で「()」を使用しないでください。使用すると、辞書/ケースブロックが作成されるときに各関数が呼び出されます。ハッシュスタイルのルックアップを使用して各関数を一度だけ呼び出したいので、これを覚えておいてください。

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()

私はあなたの解決策が好きです。しかし、いくつかの変数またはオブジェクトを渡す必要がある場合はどうなりますか?
Tedo Vrbanec、

メソッドがパラメーターを必要とする場合、これは機能しません。
クラサンガー

16

エクストラステートメントを「スイッチ」として検索する場合は、Pythonを拡張するPythonモジュールを作成しました。それはESPYと呼ばれています「Enhanced Structure for Python」としてれ、Python 2.xとPython 3.xの両方で使用できます。

たとえば、この場合、switchステートメントは次のコードで実行できます。

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

これは次のように使用できます。

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

だからespyはPythonでそれを次のように翻訳します:

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

とてもクールですwhile True:が、生成されたPythonコードの一番上にあるのポイントは何ですか?break生成されたPythonコードの下部にあるが必然的にヒットするため、while True:およびの両方をbreak削除できるように思えます。さらに、ESPYはcont、ユーザーが自分のコードで同じ名前を使用する場合、その名前を変更するのに十分スマートですか?いずれにせよ、私はバニラパイソンを使いたいのでこれは使いませんが、それでもクールです。純粋なクールさの+1。
ArtOfWarfare 14年

@ArtOfWarfare while True:and breaks の理由は、フォールスルーを許可するが必須ではないことです。
ソロモンウッコ

このモジュールはまだ利用できますか?
ソロモンUcko

15

私は一般的なスイッチ構造を見つけました:

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

Pythonでは次のように表現できます。

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

またはより明確な方法でフォーマット:

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

ステートメントではなく、Pythonバージョンは、値に評価される式です。


また、... parameter ...とp1(x)の代わりに、parameterそしてp1==parameter
Bob Stein

@ BobStein-VisiBoneこんにちは、私のpythonセッションで実行される例ですf = lambda x: 'a' if x==0 else 'b' if x==1 else 'c'。私が後で電話したときf(2)、私は得ました'c'f(1)'b'; そしてf(0)'a'。p1(x)に関しては、述語を示します。Trueor が返される限りFalse、関数呼び出しや式であっても問題ありません。
leo

@ BobStein-VisiBoneはい、そうです!ありがとう:)複数行の式が機能するためには、提案のように、または私の変更した例のように、括弧を配置する必要があります。
レオ

優秀な。次に、括弧に関するすべてのコメントを削除します。
Bob Stein

15

ここでの回答のほとんどはかなり古く、特に受け入れられているものなので、更新する価値があるようです。

まず、公式のPython FAQがこれをカバーしており、elif単純なケースのチェーンと、dictより大きなまたはより複雑なケースのチェーンを推奨しています。またvisit_、いくつかの場合に一連のメソッド(多くのサーバーフレームワークで使用されるスタイル)を提案します。

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

FAQには、PEP 275についても記載されています。これは、Cスタイルのswitchステートメントの追加に関する正式な決定をすべて取得するために作成されました。しかし、そのPEPは実際にはPython 3に委ねられており、別の提案として正式に拒否されたのはPEP 3103だけでした。もちろん、答えは「いいえ」でした。ただし、理由や履歴に興味がある場合は、2つのPEPに追加情報へのリンクがあります。


複数回発生したことの1つは(実際の推奨事項として切り取られたものの、PEP 275で確認できます)、4つのケースを処理するために8行のコードを処理するのではなく、あなたがCまたはBashで持っている行、あなたはいつでもこれを書くことができます:

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

これはPEP 8で厳密に推奨されているわけではありませんが、読みやすく、慣用的ではありません。


PEP 3103が拒否されてから10年以上にわたって、Cスタイルのcaseステートメントの問題、またはGoのわずかに強力なバージョンでさえ、死んだと見なされてきました。誰かがpython-ideasまたは-devでそれを取り上げるときはいつでも、彼らは古い決定に言及されます。

ただし、完全なMLスタイルのパターンマッチングのアイデアは、特にSwiftやRustなどの言語で採用されているため、数年ごとに発生します。問題は、代数的データ型なしでパターンマッチングを活用するのが難しいことです。Guidoはこの考えに共感していますが、Pythonにうまく適合する提案は誰も思いつきません。(例として、2014年のストローマンを読むことができます。)これは、dataclassenum、合計タイプを処理するためのより強力な3.7およびいくつかの散発的な提案、またはさまざまな種類のステートメントローカルバインディング(PEP 3150などのさまざまな提案)または-アイデアで現在議論されている一連の提案)。しかし、今のところそうではありません。

また、Perl 6スタイルのマッチングの提案も時々あります。これは、基本的に、elif正規表現から単一ディスパッチタイプの切り替えまで、すべての寄せ集めです。


15

関数を実行するためのソリューション:

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

ここで、foo1()、foo2()、foo3()およびdefault()は関数です


1
はい、たとえば、変数option == "case2"の場合、result = foo2()
Alejandro Quintanar

などなど。
Alejandro Quintanar

はい、目的を理解しました。しかし、私の懸念は、あなただけしたい場合ということですfoo2()foo1()foo3()、およびdefault()機能は、すべてのも、長い時間がかかることがあります事を意味し、実行しようとしている
ブライアン・アンダーウッドを

1
辞書内の()を省略します。使用しますget(option)()。問題が解決しました。
timgeb

1
()の優れた使用が火格子ソリューションですが、私はそれをテストするための要旨作らgist.github.com/aquintanar/01e9920d8341c5c6252d507669758fe5
アレハンドロQuintanar

13

Google検索で探していた簡単な答えが見つかりませんでした。しかし、とにかくそれを理解しました。とても簡単です。それを投稿することに決めました、そしておそらく他の誰かの頭の少し少ない傷を防ぐでしょう。キーは単純に「in」とタプルです。以下は、RANDOMフォールスルーを含む、フォールスルーを使用したswitchステートメントの動作です。

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)

提供するもの:

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.

ここでフォールスルーは正確にはどこですか?
JonasSchäfer、2014年

おっとっと!そこには落ち込みがありますが、私はスタックオーバーフローにもう貢献していません。それらをまったく好きではありません。私は他の人の貢献が好きですが、Stackoverflowは好きではありません。FUNCTIONALITYにフォールスルーを使用している場合は、スイッチのbreakステートメントに到達するまで、スイッチの1つのcaseステートメントですべての条件をキャッチします(catch all)。
JDグラハム

2
ここでは、「Dog」と「Cat」の両方の値がFALL THROUGHであり、同じ機能によって処理されます。つまり、「4本の脚」を持つと定義されています。これは、フォールスルーと同等のABSTRACTであり、ブレークが発生するSAMEケースステートメントで処理されるさまざまな値です。
JDグラハム

@JDGrahamジョナスはフォールスルーの別の側面を意味していたと思います。これは、プログラマーbreakがのコードの最後に書き込むのを忘れることがあるときに起こりcaseます。しかし、私はそのような「フォールスルー」は必要ないと思います:)
ミハイル・バッサー

12

私が使用するソリューション:

ここに掲載されている2つのソリューションの組み合わせ。比較的読みやすく、デフォルトをサポートしています。

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

どこ

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

"lambda x: x - 2"辞書を調べて、それをx=23

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

辞書でそれを見つけることができず、でデフォルト"lambda x: x - 22"が使用されます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

コードの短い説明と、投稿された質問を解決する方法を含めることを検討してください
Henry Woody

では、コメントを追加しました。
Vikhyat Agarwal

8

好きだった Mark Biesの答え

以来 x二回使用する変数必須、私はパラメータなしにラムダ関数を変更しました。

私は一緒に走らなければなりません 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'

編集:None辞書でタイプを使用できることに気づきました。これはエミュレートしますswitch ; case else


Noneケースは単純にエミュレートしませんresult[None]()か?
Bob Stein

はい、正確に。私が意味するresult = {'a': 100, None:5000}; result[None]
guneysus

4
誰もがそのNone:ように振る舞うと考えていないことを確認するだけdefault:です。
Bob Stein

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

短くて読みやすく、デフォルト値があり、条件と戻り値の両方で式をサポートします。

ただし、辞書を使用するソリューションほど効率的ではありません。たとえば、Pythonはデフォルト値を返す前にすべての条件をスキャンする必要があります。


7

あなたはディスパッチされたdictを使うことができます:

#!/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()

出力:

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

6

シンプルで、テストされていません。各条件は独立して評価されます。フォールスルーはありませんが、breakステートメントがない限り、すべてのケースが評価されます(ただし、スイッチをオンにする式は一度しか評価されません)。例えば、

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. ')

版画Was 1. Was 1 or 2. Was something. (くそ!なぜ私は、インラインコードブロック内の末尾の空白をしていることができない?)であればexpression評価さに1Was 2.場合expressionに評価され2、あるいはWas something.もしexpression何か他のものと評価されます。


1
まあ、フォールスルーは機能しますが、do_defaultに行くだけです。
syockit

5

定義:

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

ケースをマップにバンドルして、かなり単純な構文を使用できます。

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

私は "lambda:"を取り除くことができるようにスイッチを再定義しようとし続けましたが、あきらめました。定義を微調整する:

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

複数のケースを同じコードにマップし、デフォルトのオプションを指定できるようにしました。

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")
    })

複製された各ケースは、独自の辞書に存在する必要があります。switch()は、値を検索する前に辞書を統合します。それは私が望むよりも醜いですが、すべてのキーをループするのではなく、式にハッシュされたルックアップを使用するという基本的な効率があります。


5

最善の方法は、Python言語のイディオムを使用してコードをテスト可能に保つことだと思います。以前の回答で示したように、私は辞書を使用してpythonの構造と言語を利用し、「ケース」コードをさまざまな方法で分離しています。以下はクラスですが、モジュール、グローバル、関数を直接使用できます。クラスには、分離でテストできるメソッドがあります。必要に応じて、静的なメソッドや属性を操作することもできます。

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」のキーとしてクラスも使用して、このメソッドを利用することができます。このようにして、インスタンスの乱用を回避し、すべてをクリーンでテスト可能な状態に保つことができます。

ネットまたはMQからの大量のメッセージまたはパケットを処理する必要があるとします。すべてのパケットには独自の構造とその管理コードがあります(一般的な方法で)。上記のコードを使用すると、次のようなことが可能になります。

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)

そう 複雑さはコードフローでは分散されませんが、コード構造でレンダリングされます


本当に醜い...スイッチケースは読んでいるときとてもきれいです。Pythonに実装されていない理由を理解できません。
jmcollin92

@AndyClifton:ごめんなさい...例?複数の決定分岐コードが必要で、この方法を適用できるたびに考えてください。
J_Zar

@ jmcollin92:switchステートメントは快適です。ただし、プログラマーは非常に長いステートメントや再利用できないコードを書く傾向があります。私が説明した方法は、テストがよりクリーンで、より再利用可能なIMHOです。
J_Zar

@J_Zar:re。例に対する私の要求:はい、わかりましたが、これをより大きなコードのコンテキストに組み込むのに苦労しています。これを実際の状況でどのように使用するかを示していただけますか?
アンディクリフトン

1
@AndyClifton:すみません、遅れましたが、いくつかの事例を投稿しました。
J_Zar

5

グレッグ・ヒューギルの答えを拡張する-デコレーターを使用して、辞書ソリューションをカプセル化できます。

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

これは@case-decorator と一緒に使用できます

@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

良い知らせは、これはすでにNeoPySwitchモジュールで行われているということです。単にpipを使用してインストールします。

pip install NeoPySwitch

5

辞書を利用する私がよく使用する解決策は次のとおりです。

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)()

これには、毎回関数を評価しようとしないという利点があります。外側の関数が内側の関数が必要とするすべての情報を取得するようにする必要があるだけです。


5

これまでのところ、「Pythonにはスイッチがないので、このようにしてください」との回答が多数あります。ただし、switchステートメント自体は簡単に悪用される構成であり、遅延プログラミングを促進するため、ほとんどの場合は回避できることを指摘しておきます。適例:

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

さて、あなたは可能性がスイッチ文でこれを行う(Pythonがものを提供している場合)が、これだけで罰金を行う方法があるので、あなたはあなたの時間を無駄にすることだろう。または、多分、あまり明白でないものがあります。

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

ただし、この種の操作は、より高速で、複雑でなく、エラーが発生しにくく、コンパクトになるため、ディクショナリで処理できます。

また、switchステートメントの「ユースケース」の大部分は、これら2つのケースのいずれかに該当します。あなたが問題について徹底的に考えたなら、それを使う理由はほとんどありません。

したがって、「Pythonに切り替えるにはどうすればよいですか」と尋ねるのではなく、「なぜPythonに切り替えるのですか?」なぜなら、それは多くの場合、より興味深い質問であり、多くの場合、構築しているものの設計に欠陥があることが明らかになるからです。

さて、それはスイッチが決して使用されるべきではないということではありません。ステートマシン、レクサー、パーサー、オートマトンはすべてそれらをある程度使用します。一般に、対称入力から開始して非対称出力に移動する場合、これらは有用です。コードに多数の釘が表示されるため、スイッチをハンマーとして使用しないようにする必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.