我想用 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 更好,尽管在功能上它更接近。

我最喜欢的 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'))
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”评估返回的函数调用。

我只想在这里投入两分钱。Python 中没有 case/switch 语句的原因是 Python 遵循“做某事只有一种正确的方法”的原则。显然,您可以想出各种重新创建 switch/case 功能的方法,但实现此目的的 Pythonic 方法是 if/elif 构造。IE

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

我只是觉得 PEP 8 值得在这里得到认可。Python 的优点之一是它的简单和优雅。这很大程度上源自 PEP 8 中规定的原则,包括“做某事只有一种正确的方法”

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

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

如果您有一个复杂的 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()

如果您正在搜索额外的语句,如“switch”,我构建了一个扩展 Python 的 python 模块。它被称为 ESPY 作为“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

我在谷歌搜索中没有找到我正在寻找的简单答案。但我还是想通了。这真的很简单。决定将其发布,也许可以防止别人的头上少一些划痕。关键就在于“in”和元组。下面是带有失败的 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结构:

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" 在字典中并将其与 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()

常见问题解答还提到 公众号 275, ,这是为了获得关于添加 C 风格 switch 语句的正式决定而编写的。但该 PEP 实际上被推迟到 Python 3,并且只是作为单独的提案被正式拒绝, 公众号 3103. 。答案当然是否定的,但是如果您对原因或历史感兴趣,这两个政治公众人物还提供了其他信息的链接。


多次出现的一件事(可以在 PEP 275 中看到,尽管它被作为实际建议删除)是,如果您真的对用 8 行代码来处理 4 个案例感到困扰,那么您是否真的很介意用 8 行代码来处理 4 个案例,而不是用 8 行代码来处理 4 个案例。你在 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 被拒绝以来的十多年里,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 变量必须使用两次,我将 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

运行函数的解决方案:

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”的。这样你就可以避免 is实例滥用 并保持一切干净且可测试。

假设您必须处理来自网络或 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语句。例如,

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:”的方式重新定义 switch,但放弃了。调整定义:

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)

所以我们可以像这样使用它作为一、二和三的例子:

switch(value, {
    1: """
print ('one')
    """, 
    2: """
print ('two')
    """, 
    3: """
print ('three')
    """,
}, """
print ('None')
""")
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top