提问



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


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

最佳参考


你可以使用字典:


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

其它参考1


如果您喜欢默认设置,可以使用字典get(key[, default])方法:[87]


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)


从这里[88]

其它参考3


除了字典方法(我非常喜欢BTW)之外,您还可以使用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


这当然与开关/箱子不一样 - 你不能像离开休息那样容易穿透;声明,但你可以进行更复杂的测试。它的格式比一系列嵌套ifs更好,即使在功能上它更接近于它。

其它参考4


我最喜欢的用于开关/案例的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'))

其它参考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.

其它参考6


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

其它参考7


我最喜欢的是一个非常好的食谱。你会非常喜欢它。它是我在实际转换案例陈述中看到的最接近的一个,特别是在特征中。[89]


这是一个例子:


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

其它参考8


class Switch:
    def __init__(self, value): self._val = value
    def __enter__(self): return self
    def __exit__(self, type, value, traceback): return False # Allows traceback to occur
    def __call__(self, *mconds): return self._val in mconds

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


让我们说你不想只返回一个值,但想要使用改变对象上某些东西的方法。使用此处说明的方法将是:


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计算返回的函数调用。

其它参考10


扩大dict as switch的想法。如果要使用交换机的默认值:


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

其它参考11


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


如果你没有这样做,那么在进入你的调试器并确切地查看字典如何查找每个函数之前是一个好主意。


注意:不在案例/字典查找中使用(),或者在创建字典/案例块时调用每个函数。请记住这一点,因为您只想使用哈希样式查找调用每个函数一次。


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

其它参考12


我只是要把我的两分钱放在这里。在Python中没有case/switch语句的原因是因为Python遵循Theres只有一种正确的方法来做某事的原则。很明显,你可以想出各种方法来重新创建开关/案例功能,但Pythonic实现这一目标的方法是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中提出的原则,包括只有一种正确的做法

其它参考13


如果您正在搜索extra语句,作为switch,我构建了一个扩展Python的python模块。它将ESPY称为Python的增强结构,它可用于Python 2.x和Python 3。 [[X]]。[90]


例如,在这种情况下,可以通过以下代码执行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

其它参考14


我在谷歌搜索的任何地方都没有找到我想要的简单答案。但无论如何我都想出来了。这真的很简单。决定发布它,也许可以防止在别人的头上少一些划痕。关键只是in和元组。这是切换声明行为与直通,包括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.

其它参考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版本不是一个语句,而是一个表达式,它的计算结果为一个值。

其它参考16


我使用的解决方案:


此处发布的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)


没有在dict中找到它并使用默认"lambda x: x - 22"x=44

其它参考17


# 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

其它参考18


我为此制定了(相对)灵活且可重复使用的解决方案。它可以在GitHub上找到这个要点。如果switch函数的结果是可调用的,则会自动调用它。[91]

其它参考19


我喜欢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'


 我注意到我可以使用None类型和词典。所以这会模仿switch ; case else

其它参考20


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


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


但是,它的效率低于带字典的解决方案。例如,Python必须在返回默认值之前扫描所有条件。

其它参考21


定义:


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()在查找值之前合并字典。它仍然比我更喜欢,但它具有在表达式上使用散列查找的基本效率,而不是通过所有键的循环。

其它参考22


我认为最好的方法是使用python语言习语来保持代码可测试。如前面的答案所示,我使用词典来利用python结构和语言,并将case代码保存在不同的方法中。下面有一个类,但您可以直接使用模块,全局和函数。该类具有可以使用隔离进行测试的方法。
根据您的需要,您也可以使用静态方法和属性。


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的键。通过这种方式,您可以避免 isinstance abuse 并保持所有清除和可测试。


假设您必须处理来自网络或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)


因此,复杂性不会在代码流中传播,而是在代码结构中呈现

其它参考23


这里的大多数答案都很古老,特别是被接受的答案,所以看起来值得更新。


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


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


FAQ还提到了PEP 275,它是为了获得关于添加C风格切换语句的官方一劳永逸的决定而编写的。但是,PEP实际上被推迟到Python 3,并且它仅被正式拒绝作为单独的提案,PEP 3103.当然,答案是否定的,但如果您对原因感兴趣,那么两个PEP可以链接到其他信息。或历史。[94] [95]





多次出现的一件事(并且可以在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年稻草人的例子。)这可能会随着3.7中的dataclass和一些更强大的enum来处理和类型的零星提议或者对不同类型的陈述的各种提议而改变 - 本地绑定(如PEP 3150,或目前正在讨论的提议集)。但到目前为止,它还没有。[96] [97]


对于Perl 6风格的匹配,偶尔也会提出建议,这基本上是从elif到正则表达式到单一调度类型切换的所有内容的混搭。

其它参考24


我做了这个小而干净的解决方案


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


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

其它参考25


如果您不担心在案例套件中丢失语法高亮,您可以执行以下操作:


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)


所以我们可以像这样使用它来做一个,两个和三个:


switch(value, {
    1: """
print ('one')
    """, 
    2: """
print ('two')
    """, 
    3: """
print ('three')
    """,
}, """
print ('None')
""")

其它参考26


看完答案后我感到很困惑,但是这一切都清除了:


def numbers_to_strings(argument):
    switcher = {
        0: "zero",
        1: "one",
        2: "two",
    }
    return switcher.get(argument, "nothing")


此代码类似于:


function(argument){
    switch(argument) {
        case 0:
            return "zero";
        case 1:
            return "one";
        case 2:
            return "two";
        default:
            return "nothing";
    }
}


检查源以获取有关字典映射到函数的更多信息。[98]

其它参考27


受到这个令人敬畏的答案的启发。不需要其他代码。未经测试。意识到通过不正常工作。


for case in [expression]:
    if case == 1:
        do_stuff()
        # Fall through

    if case in range(2, 5):
        do_other_stuff()
        break

    do_default()

其它参考28


扩展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 - 装饰器一起使用


@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安装:[101]


pip install NeoPySwitch

其它参考29


我只想使用if/elif/else语句。我认为替换switch语句已经足够了。