Pythonのswitchステートメントの置き換え?
-
09-06-2019 - |
質問
入力インデックスの値に基づいてさまざまな固定値を返す関数を Python で作成したいと考えています。
他の言語では、 switch
または case
ステートメントがありますが、Python には switch
声明。このシナリオで推奨される Python ソリューションは何ですか?
解決
辞書を使用することもできます。
def f(x):
return {
'a': 1,
'b': 2,
}[x]
他のヒント
デフォルトが必要な場合は、辞書を使用できます get(key[, default])
方法:
def f(x):
return {
'a': 1,
'b': 2
}.get(x, 9) # 9 is default if x not found
私はいつもこの方法でやるのが好きでした
result = {
'a': lambda x: x * 5,
'b': lambda x: x + 7,
'c': lambda x: x - 2
}[value](x)
辞書メソッド (ところで、私はこれがとても気に入っています) に加えて、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 と同じではありません。ブレークを離れるほど簡単にフォールスルーを起こすことはできません。ステートメントですが、より複雑なテストを行うこともできます。機能的にはそれに近いものですが、その書式設定は一連のネストされた if よりも優れています。
私のお気に入りのスイッチ/ケースの 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'))
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.
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)
)
私のお気に入りは本当に素敵なものです レシピ. 。きっと気に入っていただけるでしょう。これは、特に機能において、私がこれまでに見た実際の switch case ステートメントに最も近いものです。
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!"
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
値を返すだけではなく、オブジェクトの何かを変更するメソッドを使用したいとします。ここで説明されているアプローチを使用すると、次のようになります。
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' は返された関数呼び出しを評価します。
ここに 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 つは、そのシンプルさと優雅さです。これは主に、「何かを行うための正しい方法は 1 つだけである」など、PEP 8 で定められた原則に由来しています。
「スイッチとしての辞書」のアイデアを拡張します。スイッチにデフォルト値を使用する場合:
def f(x):
try:
return {
'a': 1,
'b': 2,
}[x]
except KeyError:
return 'default'
複雑な case ブロックがある場合は、関数辞書ルックアップ テーブルの使用を検討できます。
これをこれまでに行ったことがない場合は、デバッガにステップインして、ディクショナリが各関数をどのように検索するかを正確に確認することをお勧めします。
注記:する ない case/dictionary ルックアップ内で「()」を使用しないと、辞書/case ブロックの作成時に各関数が呼び出されます。ハッシュ スタイル ルックアップを使用して各関数を呼び出すのは 1 回だけなので、これを覚えておいてください。
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()
「switch」として余分なステートメントを検索している場合は、Python を拡張する Python モジュールを構築しました。それは呼ばれています スパイ 「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")
したがって、それを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
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.
一般的なスイッチ構造は次のとおりであることがわかりました。
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 バージョンはステートメントではなく、値として評価される式です。
私が使用するソリューション:
ここに掲載されている解決策のうち 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"
dict 内にあり、それを使用します x=23
.get('xxx', lambda x: x - 22)(44)
辞書内で見つからず、デフォルトを使用します "lambda x: x - 22"
と x=44
.
# 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
ここでの回答のほとんどはかなり古いものであり、特に受け入れられているものは更新する価値があるようです。
まず、公式 Python よくある質問 はこれをカバーし、次のことを推奨します。 elif
単純な場合のチェーンと、 dict
大規模またはより複雑なケースの場合。のセットもご提案します visit_
場合によってはメソッド (多くのサーバー フレームワークで使用されるスタイル):
def dispatch(self, value):
method_name = 'visit_' + str(value)
method = getattr(self, method_name)
method()
FAQにも言及されています PEP275, 、これは、C スタイルの switch ステートメントの追加に関する正式な決定を得るために書かれました。しかし、その PEP は実際には Python 3 に延期され、別の提案として正式に拒否されただけでした。 PEP 3103. 。もちろん、答えはノーでした。しかし、理由や歴史に興味がある場合は、2 つの PEP に追加情報へのリンクがあります。
何度も出てきたことの 1 つは (実際の推奨事項として切り取られたにもかかわらず、PEP 275 で確認できます)、4 つのケースを処理するのに 8 行のコードが必要になるのが本当に面倒だという場合に、C または Bash の場合は 6 行ですが、いつでも次のように記述できます。
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年のストローマン 例として)これは次のように変わる可能性があります。 dataclass
3.7 と、より強力な機能を実現するためのいくつかの散発的な提案 enum
合計タイプを処理するため、またはさまざまな種類のステートメントローカルバインディングのさまざまな提案 (たとえば、 PEP 3150, 、または現在 -ideas で議論されている一連の提案)。しかし、これまでのところ、そうではありません。
また、Perl 6 スタイルのマッチングの提案も時々ありますが、これは基本的に、以下のものすべての寄せ集めです。 elif
正規表現から単一ディスパッチのタイプ切り替えまで。
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
好きだった マーク・ビーズの答え
以来、 x
変数を 2 回使用する必要があるため、ラムダ関数をパラメータなしに変更しました。
一緒に走らなければなりません 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
関数を実行するための解決策:
result = {
'case1': foo1,
'case2': foo2,
'case3': foo3,
'default': default,
}.get(option)()
foo1()、foo2()、foo3()、default() は関数です。
def f(x):
return 1 if x == 'a' else\
2 if x in 'bcd' else\
0 #default
短くて読みやすく、デフォルト値があり、条件と戻り値の両方で式をサポートしています。
ただし、辞書を使用した解決策よりも効率的ではありません。たとえば、Python はデフォルト値を返す前に、すべての条件をスキャンする必要があります。
最善の方法は次のとおりだと思います 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)
それで 複雑さはコード フロー内に分散されませんが、コード構造内でレンダリングされます。.
拡張する グレッグ・ヒューギルの答え - デコレーターを使用して辞書ソリューションをカプセル化できます。
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
-デコレータ
@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
私がよく使用する、辞書も利用する解決策は次のとおりです。
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)()
これには、関数を毎回評価しようとする必要がなく、内部関数が必要とするすべての情報を外部関数が確実に取得するだけで済むという利点があります。
ディスパッチされた辞書を使用できます。
#!/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
シンプルですがテストされていません。各条件は独立して評価されます。フォールスルーはありませんが、break ステートメントがない限り、すべてのケースが評価されます (ただし、スイッチをオンにする式は 1 回だけ評価されます)。例えば、
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
と評価される 1
, Was 2.
もし expression
と評価される 2
, 、 または Was something.
もし expression
何か別のものに評価されます。
定義:
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() は、値を検索する前に辞書を統合します。これはまだ私が望んでいるよりも醜いですが、すべてのキーをループするのではなく、式のハッシュ検索を使用するという基本的な効率を備えています。
ケーススイート内の構文のハイライトが失われることを心配しない場合は、次の操作を行うことができます。
exec {
1: """
print ('one')
""",
2: """
print ('two')
""",
3: """
print ('three')
""",
}.get(value, """
print ('None')
""")
どこ value
値です。C では、これは次のようになります。
switch (value) {
case 1:
printf("one");
break;
case 2:
printf("two");
break;
case 3:
printf("three");
break;
default:
printf("None");
break;
}
これを行うためのヘルパー関数を作成することもできます。
def switch(value, cases, default):
exec cases.get(value, default)
したがって、1、2、3 の例では次のように使用できます。
switch(value, {
1: """
print ('one')
""",
2: """
print ('two')
""",
3: """
print ('three')
""",
}, """
print ('None')
""")