Pergunta

Quero escrever uma função em Python que retorne diferentes valores fixos com base no valor de um índice de entrada.

Em outras línguas eu usaria um switch ou case declaração, mas Python não parece ter um switch declaração.Quais são as soluções Python recomendadas neste cenário?

Foi útil?

Solução

Você poderia usar um dicionário:

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

Outras dicas

Se você quiser padrões, você pode usar o dicionário get(key[, default]) método:

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

Sempre gostei de fazer assim

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

Daqui

Além dos métodos de dicionário (que eu realmente gosto, aliás), você também pode usar if-elif-else para obter a funcionalidade 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

É claro que isso não é idêntico ao switch/case - você não pode falhar tão facilmente quanto deixar o intervalo;declaração, mas você pode ter um teste mais complicado.Sua formatação é melhor do que uma série de ifs aninhados, embora funcionalmente seja disso que está mais próximo.

Minha receita Python favorita para switch/case é:

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

Curto e simples para cenários simples.

Compare com mais de 11 linhas de código C:

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

Você pode até atribuir múltiplas variáveis ​​usando tuplas:

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

Uso:

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

Testes:

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

Há um padrão que aprendi com o código 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'

Você pode usá-lo sempre que precisar despachar um token e executar um trecho de código estendido.Em uma máquina de estado você teria state_ métodos e envio em self.state.Essa opção pode ser estendida de forma limpa herdando da classe base e definindo sua própria do_ métodos.Muitas vezes você nem terá do_ métodos na classe base.

Editar:como exatamente isso é usado

No caso de SMTP você receberá HELO do fio.O código relevante (de twisted/mail/smtp.py, modificado para o nosso caso) fica assim

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

Você receberá ' HELO foo.bar.com ' (ou você pode obter 'QUIT' ou 'RCPT TO: foo').Isso é tokenizado em parts como ['HELO', 'foo.bar.com'].O nome real da pesquisa do método é obtido de parts[0].

(O método original também é chamado state_COMMAND, porque usa o mesmo padrão para implementar uma máquina de estados, ou seja, getattr(self, 'state_' + self.mode))

O meu favorito é muito legal receita.Você realmente vai gostar.É o mais próximo que já vi das instruções reais de switch case, especialmente em recursos.

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

Aqui está um exemplo:

# 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

Digamos que você não queira apenas retornar um valor, mas sim usar métodos que alterem algo em um objeto.Usar a abordagem declarada aqui seria:

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

O que acontece aqui é que python avalia todos os métodos do dicionário.Portanto, mesmo que o seu valor seja 'a', o objeto será incrementado e decrementado por x.

Solução:

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

result = func(*args)

Então você obtém uma lista contendo uma função e seus argumentos.Desta forma, apenas o ponteiro da função e a lista de argumentos são retornados, não avaliado.'resultado' então avalia a chamada de função retornada.

Vou apenas deixar meus dois centavos aqui.A razão pela qual não existe uma instrução case/switch em Python é porque Python segue o princípio de 'Só existe uma maneira certa de fazer algo'.Então, obviamente, você poderia criar várias maneiras de recriar a funcionalidade switch/case, mas a maneira Python de fazer isso é a construção if/elif.ou seja

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

Achei que o PEP 8 merecia um aceno aqui.Uma das coisas bonitas do Python é sua simplicidade e elegância.Isso é em grande parte derivado dos princípios estabelecidos no PEP 8, incluindo "Só existe uma maneira certa de fazer algo"

expandindo a ideia de "dict as switch".se você quiser usar um valor padrão para o seu switch:

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

Se você tiver um bloco de caso complicado, considere usar uma tabela de pesquisa de dicionário de funções...

Se você ainda não fez isso, é uma boa ideia entrar no seu depurador e ver exatamente como o dicionário procura cada função.

OBSERVAÇÃO:Fazer não use "()" dentro da pesquisa de caso/dicionário ou ele chamará cada uma de suas funções conforme o bloco de dicionário/caso for criado.Lembre-se disso porque você deseja chamar cada função apenas uma vez usando uma pesquisa de estilo hash.

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

Se você estiver pesquisando uma instrução extra, como "switch", criei um módulo python que estende o Python.É chamado ESPY como "Estrutura aprimorada para Python" e está disponível para Python 2.x e Python 3.x.

Por exemplo, neste caso, uma instrução switch poderia ser executada pelo seguinte código:

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

que pode ser usado assim:

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

então espie traduza-o em Python como:

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

Não encontrei a resposta simples que procurava em nenhum lugar da pesquisa do Google.Mas eu descobri de qualquer maneira.É realmente muito simples.Decidi postar, e quem sabe evitar alguns arranhões a menos na cabeça de outra pessoa.A chave é simplesmente “in” e tuplas.Aqui está o comportamento da instrução switch com fall-through, incluindo fall-through 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)

Fornece:

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.

Descobri que uma estrutura de switch comum:

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

pode ser expresso em Python da seguinte forma:

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

ou formatado de forma mais clara:

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

Em vez de ser uma instrução, a versão python é uma expressão avaliada como um valor.

As soluções que uso:

Uma combinação de 2 das soluções postadas aqui, que é relativamente fácil de ler e suporta padrões.

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

onde

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

olha para cima "lambda x: x - 2" no dict e usa-o com x=23

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

não encontra no dict e usa o padrão "lambda x: x - 22" com 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

A maioria das respostas aqui são bem antigas, principalmente as aceitas, por isso parece que vale a pena atualizar.

Primeiro, o oficial Perguntas frequentes sobre Python cobre isso e recomenda o elif cadeia para casos simples e o dict para casos maiores ou mais complexos.Também sugere um conjunto de visit_ métodos (um estilo usado por muitas estruturas de servidor) para alguns casos:

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

O FAQ também menciona PEP 275, que foi escrito para obter uma decisão oficial definitiva sobre a adição de instruções switch no estilo C.Mas esse PEP foi na verdade adiado para Python 3 e só foi oficialmente rejeitado como uma proposta separada, PEP 3103.A resposta foi, claro, não – mas os dois PEPs têm links para informações adicionais se você estiver interessado nos motivos ou na história.


Uma coisa que surgiu várias vezes (e pode ser vista no PEP 275, mesmo que tenha sido cortado como uma recomendação real) é que se você estiver realmente incomodado por ter 8 linhas de código para lidar com 4 casos, vs.as 6 linhas que você teria em C ou Bash, você sempre pode escrever isto:

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

Isto não é exatamente incentivado pelo PEP 8, mas é legível e não muito pouco idiomático.


Ao longo de mais de uma década desde que o PEP 3103 foi rejeitado, a questão das declarações de caso no estilo C, ou mesmo a versão um pouco mais poderosa em Go, foi considerada morta;sempre que alguém menciona ideias de python ou -dev, eles são encaminhados para a decisão antiga.

No entanto, a ideia de correspondência completa de padrões no estilo ML surge a cada poucos anos, especialmente desde que linguagens como Swift e Rust a adotaram.O problema é que é difícil aproveitar muito a correspondência de padrões sem tipos de dados algébricos.Embora Guido tenha simpatizado com a ideia, ninguém apresentou uma proposta que se encaixe muito bem no Python.(Você pode ler meu espantalho de 2014 por exemplo.) Isso pode mudar com dataclass em 3.7 e algumas propostas esporádicas para um sistema mais poderoso enum para lidar com tipos de soma, ou com várias propostas para diferentes tipos de ligações locais de instrução (como PEP 3150, ou o conjunto de propostas atualmente em discussão sobre -ideias).Mas até agora isso não aconteceu.

Ocasionalmente, também há propostas para correspondência no estilo Perl 6, que é basicamente uma mistura de tudo, desde elif para regex para troca de tipo de despacho único.

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

eu gostei A resposta de Mark Bies

Desde o x variável deve ser usada duas vezes, modifiquei as funções lambda para sem parâmetros.

Eu tenho que correr com 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'

Editar: notei que posso usar None digite com dicionários.Então isso iria emular switch ; case else

Solução para executar funções:

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

onde foo1(), foo2(), foo3() e default() são funções

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

Curto e fácil de ler, possui um valor padrão e suporta expressões tanto em condições quanto em valores de retorno.

No entanto, é menos eficiente que a solução com dicionário.Por exemplo, Python precisa examinar todas as condições antes de retornar o valor padrão.

Acho que a melhor maneira é use os idiomas da linguagem python para manter seu código testável.Como mostrado nas respostas anteriores, eu uso dicionários para aproveite as estruturas e a linguagem python e mantenha o código "case" isolado em métodos diferentes.Abaixo tem uma classe, mas você pode usar diretamente um módulo, globais e funções.A classe possui métodos que pode ser testado com isolamento.Dependendo de suas necessidades, você também pode brincar com métodos e atributos estáticos.

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)

É possível aproveite este método usando também classes como chaves de "__choice_table".Desta forma você pode evitar abuso de isinstance e mantenha tudo limpo e testável.

Supondo que você precise processar muitas mensagens ou pacotes da rede ou do seu MQ.Cada pacote possui sua própria estrutura e seu código de gerenciamento (de forma genérica).Com o código acima é possível fazer algo assim:

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)

Então a complexidade não é espalhada no fluxo do código, mas é renderizada na estrutura do código.

Expandindo Resposta de Greg Hewgill - Podemos encapsular a solução do dicionário usando um decorador:

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

Isso pode então ser usado com o @case-decorador

@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

A boa notícia é que isso já foi feito em NeoPySwitch-módulo.Basta instalar usando pip:

pip install NeoPySwitch

Uma solução que costumo usar e que também faz uso de dicionários é:

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

Isso tem a vantagem de não tentar avaliar as funções todas as vezes, e você apenas precisa garantir que a função externa obtenha todas as informações de que as funções internas precisam.

você pode usar um ditado despachado:

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

Saída:

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

Simples, não testado;cada condição é avaliada independentemente:não há falhas, mas todos os casos são avaliados (embora a expressão a ser ativada seja avaliada apenas uma vez), a menos que haja uma instrução break.Por exemplo,

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

estampas Was 1. Was 1 or 2. Was something. (Droga!Por que não posso ter espaços em branco nos blocos de código embutidos?) se expression avalia para 1, Was 2. se expression avalia para 2, ou Was something. se expression avalia para outra coisa.

Definindo:

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

permite que você use uma sintaxe bastante simples, com os casos agrupados em um mapa:

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

Continuei tentando redefinir o switch de uma forma que me permitisse me livrar do "lambda:", mas desisti.Ajustando a definição:

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

Permitiu-me mapear vários casos para o mesmo código e fornecer uma opção padrão:

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

Cada caso replicado deverá estar em seu próprio dicionário;switch() consolida os dicionários antes de procurar o valor.Ainda é mais feio do que eu gostaria, mas tem a eficiência básica de usar uma pesquisa com hash na expressão, em vez de um loop por todas as chaves.

Se você não se preocupa em perder o realce de sintaxe dentro dos conjuntos de casos, você pode fazer o seguinte:

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

Onde value é o valor.Em C, isso seria:

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

Também podemos criar uma função auxiliar para fazer isso:

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

Então podemos usar assim no exemplo com um, dois e três:

switch(value, {
    1: """
print ('one')
    """, 
    2: """
print ('two')
    """, 
    3: """
print ('three')
    """,
}, """
print ('None')
""")
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top