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

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

评论

相关的PEP,由Guido亲自撰写:PEP 3103

@chb在该PEP中,Guido没有提到if / elif链也是经典的错误来源。这是一个非常脆弱的结构。

这里所有解决方案都缺少检测重复的案例值。作为快速失败的原则,这可能是比性能或失败功能更重要的损失。

实际上,switch比基于输入索引的值返回不同固定值的东西更“通用”。它允许执行不同的代码段。实际上,它甚至不需要返回任何值。我不知道这里的某些答案是对常规switch语句的良好替代,还是仅用于无法执行常规代码的返回值的情况。

@ MalikA.Rumi脆弱的构造,就像while循环是一个脆弱的构造,如果您尝试使用它来执行...在...中的作用。您是否会因为使用for循环而使程序员感到软弱?虽然循环实际上是他们所需要的。但是for循环显示出清晰的意图,节省了毫无意义的样板,并为创建强大的抽象提供了机会。

#1 楼

您可以使用字典:

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


评论


如果找不到x会怎样?

–尼克
08年9月19日在15:46

@nick:您可以使用defaultdict

– Eli Bendersky
08年9月19日在17:38

如果性能存在问题,我建议将dict放到函数之外,这样就不会在每个函数调用时都重新构建dict

– Claudiu
08-10-23在16:22

@EliBendersky,在这种情况下,使用get方法可能比使用collections.defaultdict更普通。

–迈克·格雷厄姆(Mike Graham)
2012年2月23日在16:38

@Nick,如果应该使用默认值,则抛出一个异常-do} .get(x,default)。 (注意:这比将默认值保留为switch语句时要好得多!)

–迈克·格雷厄姆(Mike Graham)
2012年2月23日在16:39

#2 楼

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

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


评论


如果“ a”和“ b”匹配1,而“ c”和“ d”匹配2怎么办?

–John Mee
2010-4-9的7:57

@JM:好吧,显然字典查找不支持掉线。您可以进行双重字典查找。即“ a”和“ b”指向答案1,“ c”和“ d”指向答案2,它们包含在第二个词典中。

–尼克
2010-4-9 9:54

最好传递一个默认值

–HaTiMSuM
16-10-13在11:01

这种方法存在一个问题,首先,每次调用f时,您都会再次创建字典,其次,如果您具有更复杂的值,则可以获取例外。如果x是一个元组,我们想做这样的事情x =('a')def f(x):return {'a':x [0],'b':x [1]} .get( x [0],9)这将引发IndexError

–伊丹·海姆·沙洛姆(Idan Haim Shalom)
17年8月30日在8:01



@Idan:问题是复制开关。我敢肯定,如果我尝试输入奇数值,我也会破坏这段代码。是的,它将重新创建,但修复起来很简单。

–尼克
17年9月19日在21:59

#3 楼

我一直很喜欢用这种方式

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


从这里

评论


很棒的方法,结合get()处理默认值也是我的最佳选择

–drAlberT
09年9月2日于16:11

在这种情况下使用lambda可能不是一个好主意,因为实际上每次构建字典时都会调用lambda。

–碎石机
2012年4月22日在21:48

不幸的是,这是最接近的人。使用.get()的方法(如当前的最高答案)将需要在分发之前急切地评估所有可能性,因此,不仅效率非常低而且不仅没有副作用,而且还没有副作用。这个答案绕过了这个问题,但更为冗长。我只会使用if / elif / else,甚至那些只要写'case'就花一样的时间。

– ninjagecko
2014年3月17日在13:48



即使只返回结果之一,这也不会在所有情况下都每次都评估所有函数/ lambda吗?

–slf
14年8月6日在19:04

@slf否,当控制流到达那段代码时,它将构建3个函数(通过使用3个lambda),然后构建将这3个函数用作值的字典,但它们仍然未被调用(在首先)。然后,字典通过[value]进行索引,该索引将仅返回3个函数之一(假设value是3个键之一)。该函数当时尚未被调用。然后(x)以x作为参数调用刚返回的函数(结果进入结果)。其他2个函数将不会被调用。

–blubberdiblub
2015年9月18日在6:11



#4 楼

除了字典方法(我非常喜欢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

>这当然与switch / case不同-您不能像放弃break语句那样容易地遇到故障,但是您可以进行更复杂的测试。它的格式比一系列嵌套的if更好,即使在功能上也更接近。

评论


我真的很喜欢这个,它使用标准的语言构造,并且如果找不到匹配的大小写,则不会抛出KeyError

–martyglaubitz
13年5月18日在10:30

我考虑过字典/获取方式,但是标准方式更具可读性。

–马丁·托马
2015年6月25日在6:33



@someuser,但事实是它们可以“重叠”是一个功能。您只需要确保顺序是匹配发生的优先顺序即可。至于重复的x:只需做一个x = the.other.thing。通常,您会有一个if,多个elif和一个else,因为这更容易理解。

–马修·辛克尔(Matthew Schinckel)
16-3-3在6:55



不错,但是“不使用elif直通”会有些混乱。那怎么办:忘掉“失败”,而只接受两个if / elif / else。

–阿洛瓦·马哈德
16年5月30日在11:40

同样值得一提的是,在“ bc”中使用x时,请记住“ bc”中的“”为True。

– Lohmar ASHAR
18年8月28日在11:46

#5 楼

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


评论


我发现这是一个比公认的更可靠的答案。

–cerd
15年8月18日在23:16

@some用户:C要求在所有情况下返回值都是相同的类型。 Python没有。我想强调Python的这种灵活性,以防万一有人需要保证这种用法。

– ChaimG
16 Mar 1 '16 at 0:17



@some用户:我个人认为{} .get(,)可读。为了使Python初学者更加易读,您可能需要使用default = -1;结果= choices.get(键,默认)。

– ChaimG
16 Mar 1 '16 at 0:19

与1行C ++结果进行比较result = key =='a'?1:key == b?2:-1

–詹森
16年8月23日在22:01

@Jasen可能会争辩说,您也可以在Python的一行中完成此操作:如果key =='a'else,则result = 1;如果key =='b'else'default',则result = 1。但是那只班轮可读吗?

– ChaimG
16年8月24日在2:57

#6 楼

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.


评论


这不是威胁安全的。如果同时按下多个开关,则所有开关均取最后一个开关的值。

–francescortiz
2013年6月26日16:35



虽然@francescortiz可能意味着线程安全,但它也不是威胁安全的。它威胁到变量的值!

– ZiZuzuz212
15年6月16日在16:29

线程安全问题可能可以通过使用线程本地存储来解决。或者可以通过返回实例并将该实例用于案例比较而完全避免这种情况。

–blubberdiblub
2015年9月18日在6:24

@blubberdiblub但是,使用标准的if语句不仅效率更高吗?

– wizzwizz4
16年5月23日在17:32

如果用于多种功能,这也不安全。在给定的示例中,如果case(2)块调用了另一个使用switch()的函数,则在执行case(2、3、5、7)等以查找下一个要执行的情况时,它将使用开关值由另一个函数设置,而不是由当前switch语句设置的函数。

–user9876
17年8月17日在8:58



#7 楼

我最喜欢的是一个非常好的食谱。这是我所见过的最接近实际开关案例的陈述,尤其是在功能方面。
 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!"
 

一些评论指出,使用with foo as case而不是for case in foo的上下文管理器解决方案可能更简洁,对于大型switch语句,线性而不是二次方的行为可能是一个不错的选择。这个带有for循环的答案的部分价值是具有突破和失败的能力,如果我们愿意稍微选择一些关键词,我们也可以在上下文管理器中得到它:
class Switch:
    def __init__(self, value):
        self.value = value
        self._entered = False
        self._broken = False
        self._prev = None

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        return False # Allows a traceback to occur

    def __call__(self, *values):
        if self._broken:
            return False
        
        if not self._entered:
            if values and self.value not in values:
                return False
            self._entered, self._prev = True, values
            return True
        
        if self._prev is None:
            self._prev = values
            return True
        
        if self._prev != values:
            self._broken = True
            return False
        
        if self._prev == values:
            self._prev = None
            return False
    
    @property
    def default(self):
        return self()

这里是一个例子:
# Prints 'bar' then 'baz'.
with Switch(2) as case:
    while case(0):
        print('foo')
    while case(1, 2, 3):
        print('bar')
    while case(4, 5):
        print('baz')
        break
    while case.default:
        print('default')
        break


评论


我会用switch()来代替switch()中的case,这更有意义,因为它只需要运行一次即可。

–滑雪
2013年12月12日下午16:24

@Skirmantas:请注意,由于不允许中断,因此删除选项被删除。

–乔纳斯·谢弗(JonasSchäfer)
2014年5月8日在16:53



抱歉不要自己下决心:上面的类似回答也不是线程安全的。这是?

– David Winiecki
2014年9月12日15:47

@DavidWiniecki上面缺少的代码组件(可能还有activestate的版权)似乎是线程安全的。

–詹森
16年8月23日在22:09

另一个版本是否会类似于set(range(0,9))中的c:在set(map(chr,range(ord('a'),ord('z')))中打印“数字”省略号c )):打印“小写”?

–mpag
16-10-31在19:08



#8 楼

 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
 


评论


使用上下文管理器是一个很好的创意解决方案。我建议添加一些说明,或者可能链接到Context Managers上的一些信息,以便为本文提供一些上下文信息;)

–将
2015年5月3日,9:13

我不太喜欢if / elif链,但这是我使用Python的现有语法看到的所有解决方案中最有创意和最实用的方法。

–itsbruce
17年10月2日在8:05

真的很好建议的一项改进是在Switch类中添加(公共)value属性,以便您可以在语句中引用case.value。

–彼得
19年1月24日,11:03



这个答案非常简单,却提供了大多数类似开关的功能。使用dict的问题在于您只能检索数据,而不能运行函数/方法。

– moshevi
10月5日15:55



#9 楼

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

评论


与直接调用方法相比,我没有看到这种模式的好处:SMTP()。do_HELO('foo.bar.com')确定,lookupMethod中可以包含通用代码,但是由于该代码也可以被覆盖子类,我看不到您从间接获得的收益。

–鲨鱼先生
08-09-13在11:35

您将不知道提前调用什么方法,也就是说“ HELO”来自变量。我已经在原始帖子中添加了用法示例

–user6205
08年9月13日在17:45

我可以简单地建议一下:eva​​l('SMTP()。do_'+ command)('foo.bar.com')

– jforberg
2011年6月21日17:32



评估?认真吗?而不是为每个调用实例化一个方法,我们可以很好地实例化一次并在没有内部状态的情况下在所有调用中使用它。

–马赫什
13年3月19日在18:10

IMO真正的关键是使用getattr进行调度以指定要运行的功能。如果方法在模块中,则可以执行getattr(locals(),func_name)来获取它。 “ do_”部分对于安全性/错误很有用,因此只能调用带有前缀的函数。 SMTP本身调用lookupMethod。理想情况下,外界对此一无所知。进行SMTP()。lookupMethod(name)(data)并没有任何意义。由于命令和数据在一个字符串中,并且SMTP对其进行解析,因此这更有意义。最后,SMTP可能具有其他共享状态,这证明它是一类。

– ShawnFumo
13年8月15日在22:17



#10 楼

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

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)


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

#11 楼

我只想在这里放两分钱。 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中的原则,包括“做某事的唯一正确方法”

评论


那么,为什么Python有for循环和while循环呢? for循环可以执行的所有操作,while循环可以实现。

–itsbruce
17年10月1日在10:01

真正。开关/案例经常被新手程序员滥用。他们真正想要的是策略模式。

–user228395
17-10-6在12:34

听起来像Python希望它是Clojure

– T.W.R.油菜
18年2月16日在5:17

@ T.W.R.Cole我不这么认为,Python首先这样做。 Python自1990年以来一直存在,而Clojure自2007年以来一直存在。

–泰勒
18年5月9日在16:52

做某事只有一种正确的方法。 Python 2.7还是Python 3?大声笑。

– T.W.R.油菜
18年8月23日在19:09

#12 楼

运行函数的解决方案:

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


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

评论


是的,例如,如果您的变量option ==“ case2”,而您的结果= foo2()

–亚历杭德罗·昆塔纳尔(Alejandro Quintanar)
18年2月20日在21:27

等等。

–亚历杭德罗·昆塔纳尔(Alejandro Quintanar)
18-2-20在21:41

是的,我明白目的。但我担心的是,如果只需要foo2(),foo1(),foo3()和default()函数也都将运行,这意味着可能需要很长时间

–布莱恩·安德伍德(Brian Underwood)
18年2月21日在17:57

省略字典中的()。使用get(option)()。问题解决了。

–timgeb
18年4月10日在6:00

出色的使用()是炉排解决方案,我做了一个要点来对其进行测试。github.com/ aquintanar / 01e9920d8341c5c6252d507669758fe5

–亚历杭德罗·昆塔纳尔(Alejandro Quintanar)
18年4月17日在5:13

#13 楼

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

如果您尚未执行此操作,那么最好进入调试器并确切查看字典的方式查找每个功能。

注意:不要在case / dictionary查找中使用“()”,否则在创建字典/ 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()


评论


我喜欢你的解决方案。但是,如果我只需要传递一些变量或对象怎么办?

– Tedo Vrbanec
18/12/19在20:27

如果该方法需要参数,则将无法使用。

–库拉桑加尔
1月29日6:35

这是Python常见问题中正式推荐的方法

– est
10月18日19:43

#14 楼

如果您要搜索额外的语句(例如“ 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


评论


非常酷,但是True的意义何在:在生成的Python代码顶部?它不可避免地会在生成的Python代码的底部出现中断,因此在我看来,虽然True:和break都可以删除。此外,如果用户在自己的代码中使用相同的名称,ESPY是否足够聪明以更改cont的名称?无论如何,我都想使用香草Python,所以我不会使用它,但它仍然很酷。 +1绝对酷。

–ArtOfWarfare
2014年6月29日12:56



@ArtOfWarfare之所以使用True:和中断的原因是允许但不要求失败。

–所罗门·乌科(Solomon Ucko)
19 Mar 23 '19 at 14:10

该模块仍然可用吗?

–所罗门·乌科(Solomon Ucko)
19 Mar 24 '19 at 0:57

#15 楼

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

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


评论


我认为在指定默认值的字典上使用.get()会更清楚。我更喜欢在特殊情况下保留Exceptions,它可以减少三行代码,并在不模糊的情况下减少缩进程度。

–克里斯·B。
2009年6月5日15:14

这是一种特殊情况。根据有用情况,它可能是也可能不是罕见的情况,但这绝对是规则(从此dict中得到一些东西)的例外(回退为“默认”)。按照设计,Python程序一经使用就会使用异常。话虽这么说,使用get可能会使代码更好一些。

–迈克·格雷厄姆(Mike Graham)
2010-3-26在16:49

#16 楼



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

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


FAQ中还提到了PEP 275,其编写目的是为了获得添加C样式switch语句的正式决定。但是该PEP实际上是推迟到Python 3了,它只是作为一个单独的建议PEP 3103而被正式拒绝。答案当然不是。但是,如果您对原因感兴趣,那么两个PEP可以链接到其他信息。或历史。


多次出现的一件事(即使是实际建议,也可以在PEP 275中看到)是,如果您真的用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风格的案例陈述,甚至是Go中功能稍强的版本,都被认为已经死了;每当有人提出python-ideas或-dev时,都会参考旧的决定。

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

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

#17 楼

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

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

评论


另外代替参数...和p1(x)参数和p1 ==参数如何

–鲍勃·斯坦(Bob Stein)
15年3月11日在15:28

@ BobStein-VisiBone嗨,这是一个在我的python会话中运行的示例:f = lambda x:如果x == 0则为'a'否则为'b'如果x == 1否则为'c'。后来我打电话给f(2)时,我得到了“ c”。 f(1),'b';和f(0),“ a”。至于p1(x),它表示一个谓词;只要它返回True或False,无论是函数调用还是表达式,都可以。

–leo
2015年3月13日在16:16



@ BobStein-VisiBone是的,您是对的!谢谢:)为了使多行表达式起作用,应在您的建议中或在我的修改示例中添加括号。

–leo
2015年3月14日5:16



优秀的。现在,我将删除所有有关括号的评论。

–鲍勃·斯坦(Bob Stein)
15年3月14日在20:48

#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

#19 楼

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


评论


这里到底是哪里?

–乔纳斯·谢弗(JonasSchäfer)
14年5月8日在16:56

糟糕!有很多困难,但是我不再对堆栈溢出有所贡献。根本不喜欢他们。我喜欢其他人的贡献,但不喜欢Stackoverflow。如果您要使用FALLTIONALITY作为FUNCTIONALITY,那么您希望在开关中的所有条件下捕获所有条件中的某些条件(全部捕获),直到在开关中达到break语句为止。

– JD Graham
2014年7月30日4:58在

此处,“ Dog”和“ Cat”这两个值均会通过SAME功能处理,即将它们定义为具有“四只脚”。这是一个抽象含义,等同于发生断裂并由SAME case语句处理发生中断的不同值。

– JD Graham
2014年7月30日在5:16



@JDGraham我认为Jonas意味着失败的另一个方面,这种情况发生在程序员偶尔忘记为某种情况在代码末尾写断点的时候。但是我认为我们不需要这种“失败” :)

– Mikhail Batcer
2015年8月12日9:00



#20 楼

# 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


#21 楼

def f(x):
    dictionary = {'a':1, 'b':2, 'c':3}
    return dictionary.get(x,'Not Found') 
##Returns the value for the letter x;returns 'Not Found' if x isn't a key in the dictionary


评论


考虑包括您的代码的简短描述以及它如何解决发布的问题

–亨利·伍迪(Henry Woody)
18-09-28在19:28

好的,我现在对此发表评论。

– Vikhyat Agarwal
18-10-30在17:22

#22 楼

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

评论


None案例不是仅模拟result [None]()吗?

–鲍勃·斯坦(Bob Stein)
2015年3月11日15:19

对,就是这样。我的意思是结果= {'a':100,None:5000};结果[无]

– Guneysus
2015年3月11日15:52



只需检查是否没有人认为None:就像default:一样。

–鲍勃·斯坦(Bob Stein)
15年3月11日在16:54

#23 楼

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


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

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

#24 楼

您可以使用调度的字典:

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


#25 楼

简单,未经测试;每个条件都是独立评估的:没有穿透,但是所有情况都被评估(尽管要打开的表达式仅评估一次),除非有break语句。例如,如果Was 1. Was 1 or 2. Was something.评估为expression1如果Was 2.评估为expression,则

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


打印2(该死!为什么不能在行内代码块中尾随空格?)。 Was something.expression(如果q4312079q评估为其他值)。

评论


好吧,这是行得通的,但只能转到do_default。

–syockit
19年1月28日在11:17

#26 楼

定义:

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

#27 楼

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

class ChoiceManager:

    def __init__(self):
        self.__choice_table = \
        {
            "CHOICE1" : self.my_func1,
            "CHOICE2" : self.my_func2,
        }

    def my_func1(self, data):
        pass

    def my_func2(self, data):
        pass

    def process(self, case, data):
        return self.__choice_table[case](data)

ChoiceManager().process("CHOICE1", my_data)


有可能要利用此方法,还可以将类用作“ __choice_table”的键。这样,您就可以避免实例滥用,并保持所有清理和可测试性。

假设您必须处理来自网络或MQ的大量消息或数据包。每个数据包都有自己的结构和管理代码(以通用方式)。
使用上述代码,可以执行以下操作:

class PacketManager:

    def __init__(self):
        self.__choice_table = \
        {
            ControlMessage : self.my_func1,
            DiagnosticMessage : self.my_func2,
        }

    def my_func1(self, data):
        # process the control message here
        pass

    def my_func2(self, data):
        # process the diagnostic message here
        pass

    def process(self, pkt):
        return self.__choice_table[pkt.__class__](pkt)

pkt = GetMyPacketFromNet()
PacketManager().process(pkt)


# isolated test or isolated usage example
def test_control_packet():
    p = ControlMessage()
    PacketManager().my_func1(p)


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

评论


真的很丑...阅读时开关盒很干净。无法理解为什么未在Python中实现。

– jmcollin92
16年4月8日在13:25

@AndyClifton:对不起...一个例子吗?考虑每一次您需要多个决策分支代码,便可以应用此方法。

– J_Zar
16-4-11的6:51

@ jmcollin92:我同意switch语句很舒服。但是,程序员倾向于编写非常长的语句和代码,这些语句和代码不可重用。我描述的方法更干净,可以测试,并且更可重用,恕我直言。

– J_Zar
16-4-11的6:53

@J_Zar:重新。我要求提供示例:是的,我明白了,但是我正在努力将其放入更大的代码环境中。您能否显示在现实世界中如何使用此功能?

–安迪·克里夫顿(Andy Clifton)
16-4-11在9:14



@AndyClifton:对不起,我来晚了,但我举了一些例子。

– J_Zar
16年4月20日在14:56

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


#29 楼

阅读答案后,我感到非常困惑,但是这一切都清除了:

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


检查源代码以获取有关字典到函数的更多信息。

#30 楼

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

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


它的优点是它不会每次都尝试评估函数,只需确保外部函数获取内部函数所需的所有信息即可。