在Python中替换switch语句?

问题:在Python中替换switch语句?

我想用Python编写一个函数,该函数根据输入索引的值返回不同的固定值。

在其他语言中,我将使用switchor case语句,但Python似乎没有switch语句。在这种情况下,推荐的Python解决方案是什么?

I want to write a function in Python that returns different fixed values based on the value of an input index.

In other languages I would use a switch or case statement, but Python does not appear to have a switch statement. What are the recommended Python solutions in this scenario?


回答 0

您可以使用字典:

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

You could use a dictionary:

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

回答 1

如果您想要默认值,可以使用字典 get(key[, default])方法:

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

If you’d like defaults you could use the dictionary get(key[, default]) method:

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

回答 2

我一直喜欢这样

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

从这里

I’ve always liked doing it this way

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

From here


回答 3

除了字典方法(我真的很喜欢,BTW),你也可以使用ifelifelse获取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语句那样容易地遇到故障,但是您可以进行更复杂的测试。它的格式比一系列nested更好if,即使从功能上来说,它也更接近。

In addition to the dictionary methods (which I really like, BTW), you can also use ifelifelse to obtain the switch/case/default functionality:

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

This of course is not identical to switch/case – you cannot have fall-through as easily as leaving off the break statement, but you can have a more complicated test. Its formatting is nicer than a series of nested ifs, even though functionally that’s what it is closer to.


回答 4

我最喜欢的用于switch / case的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'))

My favorite Python recipe for switch/case is:

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

Short and simple for simple scenarios.

Compare to 11+ lines of C code:

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

You can even assign multiple variables by using tuples:

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

回答 5

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

Usage:

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

Tests:

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

回答 6

我最喜欢的是一个非常好的食谱。您会非常喜欢它。这是我所见过的最接近实际开关案例的陈述,尤其是在功能方面。

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!"

My favorite one is a really nice recipe. You’ll really like it. It’s the closest one I’ve seen to actual switch case statements, especially in features.

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

Here’s an example:

# 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!"

回答 7

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

回答 8

我从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')。这被标记为partsas ['HELO', 'foo.bar.com']。实际的方法查找名称取自parts[0]

(原始方法也称为state_COMMAND,因为它使用相同的模式来实现状态机,即getattr(self, 'state_' + self.mode)

There’s a pattern that I learned from Twisted Python code.

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'

You can use it any time you need to dispatch on a token and execute extended piece of code. In a state machine you would have state_ methods, and dispatch on self.state. This switch can be cleanly extended by inheriting from base class and defining your own do_ methods. Often times you won’t even have do_ methods in the base class.

Edit: how exactly is that used

In case of SMTP you will receive HELO from the wire. The relevant code (from twisted/mail/smtp.py, modified for our case) looks like this

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

You’ll receive ' HELO foo.bar.com ' (or you might get 'QUIT' or 'RCPT TO: foo'). This is tokenized into parts as ['HELO', 'foo.bar.com']. The actual method lookup name is taken from parts[0].

(The original method is also called state_COMMAND, because it uses the same pattern to implement a state machine, i.e. getattr(self, 'state_' + self.mode))


回答 9

假设您不想只返回一个值,而是想使用更改对象上某些内容的方法。使用此处说明的方法将是:

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

这里发生的是python评估字典中的所有方法。因此,即使您的值为’a’,该对象也会被递增 x减少x。

解:

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

result = func(*args)

因此,您将获得一个包含函数及其参数的列表。这样,仅返回函数指针和参数列表,而不对其求值。然后,“结果”评估返回的函数调用。

Let’s say you don’t want to just return a value, but want to use methods that change something on an object. Using the approach stated here would be:

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

What happens here is that python evaluates all methods in the dictionary. So even if your value is ‘a’, the object will get incremented and decremented by x.

Solution:

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

result = func(*args)

So you get a list containing a function and its arguments. This way, only the function pointer and the argument list get returned, not evaluated. ‘result’ then evaluates the returned function call.


回答 10

我只想在这里放两分钱。Python中没有case / switch语句的原因是因为Python遵循“只有一种正确的方法来做某事”的原则。因此,显然您可以想出各种重新创建开关/案例功能的方法,但是实现此功能的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的美丽之处之一就是它的简洁和优雅。这很大程度上源于PEP 8中规定的原则,包括“做某事的唯一正确方法”

I’m just going to drop my two cents in here. The reason there isn’t a case/switch statement in Python is because Python follows the principle of ‘Theres only one right way to do something’. So obviously you could come up with various ways of recreating switch/case functionality, but the Pythonic way of accomplishing this is the if/elif construct. ie

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

I just felt PEP 8 deserved a nod here. One of the beautiful things about Python is its simplicity and elegance. That is largely derived from principles laid our in PEP 8, including “There’s only one right way to do something”


回答 11

扩展“ dict as switch”的想法。如果要为开关使用默认值:

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

expanding on the “dict as switch” idea. if you want to use a default value for your switch:

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

回答 12

如果您有一个复杂的case块,可以考虑使用函数字典查找表…

如果您尚未执行此操作,那么最好进入调试器并准确查看字典如何查找每个函数。

注意:千万不能使用“()”的情况下/字典查找内部或它会调用每个函数被创建字典/ case块。请记住这一点,因为您只想使用哈希样式查找一次调用每个函数。

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

If you have a complicated case block you can consider using a function dictionary lookup table…

If you haven’t done this before its a good idea to step into your debugger and view exactly how the dictionary looks up each function.

NOTE: Do not use “()” inside the case/dictionary lookup or it will call each of your functions as the dictionary / case block is created. Remember this because you only want to call each function once using a hash style lookup.

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

回答 13

如果要搜索额外的语句(例如“ switch”),则构建了扩展Python的python模块。叫做ESPY称为“ Python的增强结构”,并且可用于Python 2.x和Python3.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

If you’re searching extra-statement, as “switch”, I built a python module that extends Python. It’s called ESPY as “Enhanced Structure for Python” and it’s available for both Python 2.x and Python 3.x.

For example, in this case, a switch statement could be performed by the following code:

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

that can be used like this:

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

so espy translate it in Python as:

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

回答 14

我发现了一个常见的开关结构:

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版本不是表达式,而是表达式,其计算结果为值。

I found that a common switch structure:

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

can be expressed in Python as follows:

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

or formatted in a clearer way:

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

Instead of being a statement, the python version is an expression, which evaluates to a value.


回答 15

这里的大多数答案都是很旧的,尤其是已被接受的答案,因此似乎值得更新。

首先,正式的Python FAQ涵盖了这一点,并elif为简单案例和dict较大或更复杂的案例推荐了链。它还visit_针对某些情况建议了一组方法(许多服务器框架使用的一种样式):

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

常见问题解答中还提到了PEP 275,它是为了获得添加C样式转换语句的一劳永逸的正式决定而编写的。但是该PEP实际上推迟到了Python 3,并且只是作为单独的提案PEP 3103被正式拒绝。答案当然不是。但是,如果您对原因或历史记录感兴趣,则两个PEP都有指向其他信息的链接。


多次出现的一件事(即使在PEP 275中作为实际建议被删除,也可以在PEP 275中看到)是,如果您真的不愿意拥有8行代码来处理4种情况,而6条代码使用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被拒绝以来的十多年里,C风格的案例声明,甚至Go中功能更强大的版本,都被认为已经死了。每当有人提出python-ideas或-dev时,都会参考旧的决定。

但是,完全ML样式模式匹配的想法每隔几年就会出现,特别是因为Swift和Rust这样的语言已经采用了它。问题在于,如果没有代数数据类型,很难从模式匹配中获得更多使用。尽管Guido对这个想法很同情,但是没有人提出一个非常适合Python的提议。(您可以阅读我的2014年草民的示例。)这可能会dataclass在3.7版本中发生变化,并偶尔会提出一些更强大的建议enum以处理总和类型,或者针对各种类型的语句局部绑定(例如PEP 3150)提出各种建议或目前正在-ideas上讨论的一组建议)。但是到目前为止,还没有。

偶尔也有关于Perl 6样式匹配的建议,这基本上是从elif正则表达式到单分派类型切换的所有内容的混搭。

Most of the answers here are pretty old, and especially the accepted ones, so it seems worth updating.

First, the official Python FAQ covers this, and recommends the elif chain for simple cases and the dict for larger or more complex cases. It also suggests a set of visit_ methods (a style used by many server frameworks) for some cases:

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

The FAQ also mentions PEP 275, which was written to get an official once-and-for-all decision on adding C-style switch statements. But that PEP was actually deferred to Python 3, and it was only officially rejected as a separate proposal, PEP 3103. The answer was, of course, no—but the two PEPs have links to additional information if you’re interested in the reasons or the history.


One thing that came up multiple times (and can be seen in PEP 275, even though it was cut out as an actual recommendation) is that if you’re really bothered by having 8 lines of code to handle 4 cases, vs. the 6 lines you’d have in C or Bash, you can always write this:

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

This isn’t exactly encouraged by PEP 8, but it’s readable and not too unidiomatic.


Over the more than a decade since PEP 3103 was rejected, the issue of C-style case statements, or even the slightly more powerful version in Go, has been considered dead; whenever anyone brings it up on python-ideas or -dev, they’re referred to the old decision.

However, the idea of full ML-style pattern matching arises every few years, especially since languages like Swift and Rust have adopted it. The problem is that it’s hard to get much use out of pattern matching without algebraic data types. While Guido has been sympathetic to the idea, nobody’s come up with a proposal that fits into Python very well. (You can read my 2014 strawman for an example.) This could change with dataclass in 3.7 and some sporadic proposals for a more powerful enum to handle sum types, or with various proposals for different kinds of statement-local bindings (like PEP 3150, or the set of proposals currently being discussed on -ideas). But so far, it hasn’t.

There are also occasionally proposals for Perl 6-style matching, which is basically a mishmash of everything from elif to regex to single-dispatch type-switching.


回答 16

运行功能的解决方案:

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

其中foo1(),foo2(),foo3()和default()是函数

Solution to run functions:

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

where foo1(), foo2(), foo3() and default() are functions


回答 17

在Google搜索的任何地方都找不到我想要的简单答案。但是我还是想通了。这真的很简单。决定将其张贴,并可能防止在其他人的头部上刮一些划痕。关键只是“输入”和元组。这是带有穿透的switch语句行为,包括RANDOM穿透。

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.

I didn’t find the simple answer I was looking for anywhere on Google search. But I figured it out anyway. It’s really quite simple. Decided to post it, and maybe prevent a few less scratches on someone else’s head. The key is simply “in” and tuples. Here is the switch statement behavior with fall-through, including RANDOM fall-through.

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)

Provides:

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.

回答 18

我使用的解决方案:

此处发布的两种解决方案的组合,相对易于阅读并支持默认设置。

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

The solutions I use:

A combination of 2 of the solutions posted here, which is relatively easy to read and supports defaults.

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

where

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

looks up "lambda x: x - 2" in the dict and uses it with x=23

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

doesn’t find it in the dict and uses the default "lambda x: x - 22" with x=44.


回答 19

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

回答 20

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

回答 21

我喜欢Mark Bies的回答

由于x变量必须使用两次,因此我将lambda函数修改为无参数。

我必须与 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'

编辑:我注意到我可以将Nonetype与字典配合使用。所以这会效仿switch ; case else

I liked Mark Bies’s answer

Since the x variable must used twice, I modified the lambda functions to parameterless.

I have to run with 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'

Edit: I noticed that I can use None type with with dictionaries. So this would emulate switch ; case else


回答 22

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

简短易读,具有默认值,并支持条件和返回值中的表达式。

但是,它的效率不如字典解决方案。例如,Python必须先扫描所有条件,然后再返回默认值。

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

Short and easy to read, has a default value and supports expressions in both conditions and return values.

However, it is less efficient than the solution with a dictionary. For example, Python has to scan through all the conditions before returning the default value.


回答 23

您可以使用调度的字典:

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

you can use a dispatched 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()

Output:

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

回答 24

简单,未经测试;每种条件都是独立评估的:没有穿透,但是所有情况都进行评估(尽管打开的表达式仅评估一次),除非有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计算结果为别的东西。

Simple, not tested; each condition is evaluated independently: there is no fall-through, but all cases are evaluated (although the expression to switch on is only evaluated once), unless there is a break statement. For example,

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

prints Was 1. Was 1 or 2. Was something. (Dammit! Why can’t I have trailing whitespace in inline code blocks?) if expression evaluates to 1, Was 2. if expression evaluates to 2, or Was something. if expression evaluates to something else.


回答 25

定义:

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()合并字典。它仍然比我想要的还要难看,但是它具有在表达式上使用哈希查找的基本效率,而不是循环遍历所有键。

Defining:

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

allows you to use a fairly straightforward syntax, with the cases bundled into a map:

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

I kept trying to redefine switch in a way that would let me get rid of the “lambda:”, but gave up. Tweaking the definition:

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

Allowed me to map multiple cases to the same code, and to supply a default option:

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

Each replicated case has to be in its own dictionary; switch() consolidates the dictionaries before looking up the value. It’s still uglier than I’d like, but it has the basic efficiency of using a hashed lookup on the expression, rather than a loop through all the keys.


回答 26

我认为最好的方法是使用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)

因此,复杂性不会在代码流中扩散,而是以代码结构呈现

I think the best way is to use the python language idioms to keep your code testable. As showed in previous answers, I use dictionaries to take advantage of python structures and language and keep the “case” code isolated in different methods. Below there is a class, but you can use directly a module, globals and functions. The class has methods that can be tested with isolation. Dependending to your needs, you can play with static methods and attributes too.

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)

It is possible to take advantage of this method using also classes as keys of “__choice_table”. In this way you can avoid isinstance abuse and keep all clean and testable.

Supposing you have to process a lot of messages or packets from the net or your MQ. Every packet has its own structure and its management code (in a generic way). With the above code it is possible to do something like this:

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)

So complexity is not spread in the code flow but it is rendered in code structure.


回答 27

扩展Greg Hewgill的答案 -我们可以使用装饰器封装字典解决方案:

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 -module中完成。只需使用pip安装:

pip install NeoPySwitch

Expanding on Greg Hewgill’s answer – We can encapsulate the dictionary-solution using a decorator:

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

This can then be used with the @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

The good news are that this has already been done in NeoPySwitch-module. Simply install using pip:

pip install NeoPySwitch

回答 28

我倾向于使用也使用字典的解决方案是:

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

这样做的好处是,它不必每次都尝试评估函数,而只需确保外部函数获取内部函数所需的所有信息即可。

A solution I tend to use which also makes use of dictionaries is :

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

This has the advantage that it doesn’t try to evaluate the functions every time, and you just have to ensure that the outer function gets all the information that the inner functions need.


回答 29

到目前为止,已经有很多答案说:“我们没有在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语句的绝大多数“用例”将属于这两种情况之一;如果您已经彻底考虑了问题,则几乎没有理由使用它。

因此,与其问“如何切换Python?”,不如问“为什么要切换Python”?因为这通常是更有趣的问题,并且经常会暴露出您要构建的产品的设计缺陷。

现在,这并不是说也不应该使用任何开关。状态机,词法分析器,解析器和自动机在某种程度上都使用它们,通常,当您从对称输入开始并转到非对称输出时,它们可能会有用。您只需要确保不要将开关用作锤子,因为在代码中会看到很多钉子。

There have been a lot of answers so far that have said, “we don’t have a switch in Python, do it this way”. However, I would like to point out that the switch statement itself is an easily-abused construct that can and should be avoided in most cases because they promote lazy programming. Case in point:

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

Now, you could do this with a switch-statement (if Python offered one) but you’d be wasting your time because there are methods that do this just fine. Or maybe, you have something less obvious:

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

However, this sort of operation can and should be handled with a dictionary because it will be faster, less complex, less prone to error and more compact.

And the vast majority of “use cases” for switch statements will fall into one of these two cases; there’s just very little reason to use one if you’ve thought about your problem thoroughly.

So, rather than asking “how do I switch in Python?”, perhaps we should ask, “why do I want to switch in Python?” because that’s often the more interesting question and will often expose flaws in the design of whatever you’re building.

Now, that isn’t to say that switches should never be used either. State machines, lexers, parsers and automata all use them to some degree and, in general, when you start from a symmetrical input and go to an asymmetrical output they can be useful; you just need to make sure that you don’t use the switch as a hammer because you see a bunch of nails in your code.