while 替代Python中的switch語句?




python2 switch case (24)

我想在Python中編寫一個函數,它根據輸入索引的值返回不同的固定值。

在其他語言中,我會使用switchcase語句,但Python似乎沒有switch語句。 在這種情況下推薦的Python解決方案是什麼?


# 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

我沒有找到我在谷歌搜索的任何地方尋找的簡單答案。 但無論如何我都明白了。 這真的很簡單。 決定發布它,也許可以防止在他人頭上少一些划痕。 關鍵是簡單的“進”和元組。 這是轉換語句的行為,包括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.

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

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

我認為最好的方法是使用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濫用,並保持所有的清潔和可測試。

假設您必須處理來自網絡或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代碼中學到了一種模式。

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上進行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' )。 這被標記為['HELO', 'foo.bar.com'] 。 實際的方法查找名稱取自parts[0]

(原始方法也稱為state_COMMAND ,因為它使用相同的模式來實現狀態機,即getattr(self, 'state_' + self.mode)


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

我為此做了一個(相對)靈活和可重用的解決方案。 可以在GitHub上找到它作為這個要點 。 如果開關函數的結果是可調用的,它會自動調用。


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.

如果你有一個複雜的case塊,你可以考慮使用函數字典查找表...

如果你還沒有完成這個任務,那麼先進入你的調試器並仔細查看字典是如何查找每個函數的。

注意:不要在case / dictionary查找中使用“()”,或者在字典/大小寫塊被創建時調用每個函數。 請記住這一點,因為您只想使用哈希樣式查找來調用每個函數。

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

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

我發現一個普通的開關結構:

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版本不是一個聲明,而是一個表達式,它的值是一個值。


Just mapping some a key to some code is not really and issue as most people have shown using the dict. The real trick is trying to emulate the whole drop through and break thing. I don't think I've ever written a case statement where I used that "feature". Here's a go at drop through.

def case(list): reduce(lambda b, f: (b | f[0], {False:(lambda:None),True:f[1]}[b | f[0]]())[0], list, False)

case([
    (False, lambda:print(5)),
    (True, lambda:print(4))
])

I was really imagining it as a single statement. I hope you'll pardon the silly formatting.

reduce(
    initializer=False,
    function=(lambda b, f:
        ( b | f[0]
        , { False: (lambda:None)
          , True : f[1]
          }[b | f[0]]()
        )[0]
    ),
    iterable=[
        (False, lambda:print(5)),
        (True, lambda:print(4))
    ]
)

I hope that's valid python. It should give you drop through. of course the boolean checks could be expressions and if you wanted them to be evaluated lazily you could wrap them all in a lambda. I wouldn't be to hard to make it accept after executing some of the items in the list either. Just make the tuple (bool, bool, function) where the second bool indicates whether or not to break or drop through.


I was quite confused after reading the answer, but this cleared it all up:

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

This code is analogous to:

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

Check the Source for more about dictionary mapping to functions.


我只是在這裡放下我的兩分錢。 在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中的原則,包括“只有一個正確的方法去做某件事”


你可以使用字典:

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

假設你不想只返回一個值,但想要使用改變對像上的某些東西的方法。 使用這裡陳述的方法將是:

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)

所以你得到一個包含一個函數及其參數的列表。 這樣,只有函數指針和參數列表被返回, 沒有被評估。 '結果'然後評估返回的函數調用。


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

簡潔易讀,具有默認值,並支持條件和返回值中的表達式。

但是,它比字典解決方案效率低。 例如,Python必須在返回默認值之前掃描所有條件。


定義:

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()在查找值之前合併字典。 它仍然比我想要的更醜,但它具有在表達式上使用散列查找的基本效率,而不是遍歷所有鍵的循環。


I would just use if/elif/else statements. I think that it's good enough to replace the switch statement.


我喜歡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 switch ; case else


擴大“字典作為切換”的想法。 如果您想為交換機使用默認值:

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


除了字典方法(我真的很喜歡,BTW)之外,還可以使用if-elif-else獲取開關/大小寫/默認功能:

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更好,儘管功能上更接近它。


If you don't worry losing syntax highlight inside the case suites, you can do the following:

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

Where value is the value. In C, this would be:

switch (value) {
    case 1:
        printf("one");
        break;
    case 2:
        printf("two");
        break;
    case 3:
        printf("three");
        break;
    default:
        printf("None");
        break;
}

We can also create a helper function to do this:

def switch(value, cases, default):
    exec cases.get(value, default)

So we can use it like this for the example with one, two and three:

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






python