Maneira pitônica de evitar declarações “if x: return x”

218

Eu tenho um método que chama 4 outros métodos em sequência para verificar condições específicas e retorna imediatamente (não verificando os seguintes) sempre que alguém retorna algo Truthy.

def check_all_conditions():
    x = check_size()
    if x:
        return x

    x = check_color()
    if x:
        return x

    x = check_tone()
    if x:
        return x

    x = check_flavor()
    if x:
        return x
    return None

Parece muito código de bagagem. Em vez de cada declaração if de 2 linhas, prefiro fazer algo como:

x and return x

Mas isso é Python inválido. Estou perdendo uma solução simples e elegante aqui? Aliás, nessa situação, esses quatro métodos de verificação podem ser caros, então não quero chamá-los várias vezes.

Bernard
fonte
7
O que são esses x's? Eles são apenas Verdadeiro / Falso ou são estruturas de dados que contêm algumas informações, com Nenhum ou similar sendo usado como um caso especial para indicar a ausência de dados? Se for o último, você certamente deve estar usando exceções.
Nathaniel
13
@gerrit O código como apresentado acima é hipotético / pseudo-código, fora do tópico da Revisão de Código. Se o autor da postagem desejar revisar seu código de trabalho real e real , sim, será bem-vindo a publicar na Revisão de Código.
Phrancis 21/03
4
Por que você acha que x and return xé melhor do que if x: return x? O último é muito mais legível e, portanto, sustentável. Você não deve se preocupar muito com o número de caracteres ou linhas; legibilidade conta. Eles são exatamente o mesmo número de caracteres que não são espaços em branco e, se você realmente precisar, if x: return xfuncionará bem em apenas uma linha.
marcelm
3
Esclareça se você se preocupa com os valores reais ou se realmente precisa apenas retornar um valor booleano. Isso faz diferença que opções estão disponíveis e também quais comunicam mais claramente a intenção. A nomeação sugere que você só precisa de um valor booleano. Também faz diferença se é importante evitar várias chamadas para essas funções. Também pode importar se as funções aceitam um ou diferentes conjuntos de parâmetros. Sem esses esclarecimentos, acho que essa pergunta se enquadra em uma das questões pouco claras, muito amplas ou baseadas em opiniões.
Jpmc26 21/03
7
@ jpmc26 O OP fala explicitamente dos valores de retorno verdadeiros, e então seu código retorna x(ao contrário de bool(x)) assim como está, acho que é seguro assumir que as funções do OP podem retornar qualquer coisa, e ele quer o primeiro qualquer coisa que seja verdadeira.
timgeb

Respostas:

278

Você pode usar um loop:

conditions = (check_size, check_color, check_tone, check_flavor)
for condition in conditions:
    result = condition()
    if result:
        return result

Isso tem a vantagem adicional de que agora você pode tornar o número de condições variáveis.

Você pode usar map()+ filter()(as versões do Python 3, use as future_builtinsversões do Python 2) para obter o primeiro valor correspondente:

try:
    # Python 2
    from future_builtins import map, filter
except ImportError:
    # Python 3
    pass

conditions = (check_size, check_color, check_tone, check_flavor)
return next(filter(None, map(lambda f: f(), conditions)), None)

mas se for mais legível, é discutível.

Outra opção é usar uma expressão geradora:

conditions = (check_size, check_color, check_tone, check_flavor)
checks = (condition() for condition in conditions)
return next((check for check in checks if check), None)
Martijn Pieters
fonte
27
se as condições são realmente apenas condições, ou seja, booleanos, em sua primeira proposta, você também pode usar o builtin em anyvez do loop. return any(condition() for condition in conditions)
4
@ Leonhard: anytem quase a mesma implementação dentro. Mas parece muito melhor, por favor, postá-lo como uma resposta)
Nick Volynkin
13
A legibilidade supera quase todas as outras considerações. Você diz que o mapa / filtro é 'discutível', ponho meu voto em indiscutivelmente feio. Obrigado, certamente, mas se alguém da minha equipe colocasse um mapa / filtro para esse código, eu os transferiria para outra equipe ou os atribuiria ao dever de dormir.
Kevin J. Rice,
15
Esse bloco de código ilegível é realmente "pythoniano"? E, especialmente, a idéia de fechar conditionse arguments? Isso é muito pior do que o código original, que leva cerca de 10 segundos para analisar pelo meu analisador cerebral.
yo
34
"Preferem Python", eles disseram. "Perl é ilegível", disseram eles. E, em seguida, isso aconteceu: return next((check for check in checks if check), None).
JJA
393

Como alternativa à boa resposta de Martijn, você pode encadear or. Isso retornará o primeiro valor de verdade, ou Nonese não houver valor de verdade:

def check_all_conditions():
    return check_size() or check_color() or check_tone() or check_flavor() or None

Demo:

>>> x = [] or 0 or {} or -1 or None
>>> x
-1
>>> x = [] or 0 or {} or '' or None
>>> x is None
True
timgeb
fonte
9
Claro, mas será tedioso ler rapidamente se houver mais do que algumas opções. Além disso, minha abordagem permite que você use um número variável de condições.
Martijn Pieters
14
@MartijnPieters que você pode usar \para colocar cada verificação em sua própria linha.
Caridorc 20/03/16
12
@MartijnPieters eu nunca implícita a minha resposta é melhor que o seu, eu como a sua resposta, também :)
timgeb
38
@Caridorc: Não gosto muito de usar \para estender a linha lógica. Use parênteses sempre que possível; assim return (....)com as novas linhas inseridas conforme necessário. Ainda assim, essa será uma longa linha lógica.
Martijn Pieters
47
Eu acho que essa é a melhor solução. O argumento "será tedioso [..] se houver mais do que algumas opções" é discutível, porque uma única função não deveria estar fazendo um número exorbitante de verificações. Se isso for necessário, as verificações devem ser divididas em várias funções.
precisa saber é o seguinte
88

Não mude

Existem outras maneiras de fazer isso, como mostram as várias outras respostas. Nenhum é tão claro quanto o seu código original.

Jack Aidley
fonte
39
Eu argumentaria contra isso, mas sua sugestão é legítima para ser expressa. Pessoalmente, vejo meus olhos cansados ​​tentando ler o OP enquanto, por exemplo, a solução do timgeb clica instantaneamente.
precisa saber é o seguinte
3
É realmente uma questão de opinião. Pessoalmente, eu removeria as novas linhas depois :, porque considero if x: return xmuito bem, e isso torna a função mais compacta. Mas isso pode ser apenas eu.
yo
2
Não é só você. Usar orcomo timgeb fez é um idioma adequado e bem compreendido. Muitas línguas têm isso; talvez, quando chamado orelse, seja ainda mais claro, mas até o mais antigo or(ou ||em outras línguas) deve ser entendido como a alternativa para tentar se o primeiro "não funciona".
Ray Toal
1
@ RayToal: Importar idiomas de outros idiomas é uma ótima maneira de ofuscar código.
Jack Aidley
1
Às vezes sim, com certeza! Também pode ser uma maneira de abrir a mente e levá-la a descobrir novos e melhores padrões e paradigmas que ninguém pode ter tentado antes. O estilo evolui quando as pessoas tomam empréstimos, compartilham e tentam coisas novas. Funciona nos dois sentidos. De qualquer forma, nunca ouvi falar do uso de ornão-pitonico ou de alguma forma ofuscado, mas isso é uma questão de opinião de qualquer maneira - como deveria ser.
precisa
83

Efetivamente, a mesma resposta que timgeb, mas você pode usar parênteses para obter uma formatação melhor:

def check_all_the_things():
    return (
        one()
        or two()
        or five()
        or three()
        or None
    )
Wayne Werner
fonte
8
todos ajudem a elevar esta resposta até o 1º lugar. faça sua parte !
Gyom 23/03/16
74

De acordo com a lei de Curly , você pode tornar esse código mais legível dividindo duas preocupações:

  • Que coisas eu verifico?
  • Uma coisa voltou verdadeira?

em duas funções:

def all_conditions():
    yield check_size()
    yield check_color()
    yield check_tone()
    yield check_flavor()

def check_all_conditions():
    for condition in all_conditions():
        if condition:
            return condition
    return None

Isso evita:

  • estruturas lógicas complicadas
  • linhas muito longas
  • repetição

... preservando um fluxo linear e fácil de ler.

Você provavelmente também pode criar nomes de funções ainda melhores, de acordo com sua circunstância específica, o que o torna ainda mais legível.

Phil Frost
fonte
Eu gosto deste, embora True / False deva ser alterado para condição / None para corresponder à pergunta.
22316 Malcolm
2
Este é meu favorito! Ele lida com diferentes verificações e argumentos também. Possivelmente super projetada para este exemplo em particular, mas uma ferramenta realmente útil para problemas futuros!
Rjh 23/03
4
Observe que isso return Nonenão é necessário, porque as funções retornam Nonepor padrão. No entanto, não há nada errado em retornar Noneexplicitamente, e eu gosto que você tenha escolhido fazê-lo.
timgeb
1
Eu acho que essa abordagem seria melhor implementada com uma definição de função local.
Jack Aidley
1
@timgeb "Explícito é melhor que implícito", Zen do Python .
Jpmc26 29/03
42

Esta é uma variante do primeiro exemplo de Martijns. Ele também usa o estilo "coleção de callables" para permitir um curto-circuito.

Em vez de um loop, você pode usar o builtin any.

conditions = (check_size, check_color, check_tone, check_flavor)
return any(condition() for condition in conditions) 

Observe que anyretorna um booleano; portanto, se você precisar do valor exato de retorno da verificação, esta solução não funcionará. anynão fará distinção entre 14, 'red', 'sharp', 'spicy'como valores de retorno, todos eles serão devolvidos como True.


fonte
Você poderia fazer next(itertools.ifilter(None, (c() for c in conditions)))para obter o valor real sem convertê-lo em um booleano.
Kojiro # 22/16
1
Será que anyrealmente curto-circuito?
Zwol 22/03/16
1
@zwol Sim experimentá-lo com algumas funções de exemplo ou ver docs.python.org/3/library/functions.html
1
Isso é menos legível do que encadear as 4 funções com 'ou' e só compensa se o número de condições for grande ou dinâmico.
Rjh 23/03
1
@rjh É perfeitamente legível; é apenas uma lista literal e uma compreensão. Eu preferiria porque meus olhos vidram depois do terceirox = bar(); if x: return x;
Blacklight Shining
27

Você já pensou em escrever if x: return xtudo em uma linha?

def check_all_conditions():
    x = check_size()
    if x: return x

    x = check_color()
    if x: return x

    x = check_tone()
    if x: return x

    x = check_flavor()
    if x: return x

    return None

Isso não é menos repetitivo do que você tinha, mas IMNSHO parece um pouco mais suave.

zwol
fonte
24

Estou surpreso que ninguém tenha mencionado o built-in anyque é feito para esse fim:

def check_all_conditions():
    return any([
        check_size(),
        check_color(),
        check_tone(),
        check_flavor()
    ])

Observe que embora essa implementação seja provavelmente a mais clara, ela avalia todas as verificações, mesmo que a primeira seja True.


Se você realmente precisar parar na primeira falha na verificação, considere usar o reduceque é feito para converter uma lista em um valor simples:

def check_all_conditions():
    checks = [check_size, check_color, check_tone, check_flavor]
    return reduce(lambda a, f: a or f(), checks, False)

reduce(function, iterable[, initializer]): Aplique a função de dois argumentos cumulativamente aos itens de iterável, da esquerda para a direita, para reduzir o iterável a um único valor. O argumento da esquerda, x, é o valor acumulado e o argumento da direita, y, é o valor de atualização do iterável. Se o inicializador opcional estiver presente, ele será colocado antes dos itens do iterável no cálculo

No seu caso:

  • lambda a, f: a or f()é a função que verifica se o acumulador aou a verificação atual f()é True. Observe que se afor True,f() não será avaliado.
  • checks contém funções de verificação (o f item do lambda)
  • False é o valor inicial, caso contrário, nenhuma verificação aconteceria e o resultado seria sempre True

anye reducesão ferramentas básicas para programação funcional. Convido você a treiná-los e também o mapque é incrível!

ngasull
fonte
9
anysó funciona se as verificações realmente retornarem um valor booleano, literalmente Trueou False, mas a pergunta não especificar isso. Você precisaria usar reducepara retornar o valor real retornado pela verificação. Além disso, é fácil evitar avaliar todas as verificações anyusando um gerador, por exemplo any(c() for c in (check_size, check_color, check_tone, check_flavor)). Como na resposta de Leonhard
David Z
Eu gosto da sua explicação e uso de reduce. Como @DavidZ, acredito que sua solução anydeve usar um gerador e é preciso salientar que se limita a retornar Trueou False.
timgeb
1
@DavidZ realmente anytrabalha com valores de verdade : any([1, "abc", False]) == Trueeany(["", 0]) == False
ngasull 22/03
3
@ Blint desculpe, eu não estava claro. O objetivo da pergunta é retornar o resultado da verificação (e não apenas indicar se a verificação foi bem-sucedida ou falhou). Eu estava apontando que anysó funciona para esse propósito se valores booleanos reais forem retornados das funções de verificação.
David Z
19

Se você quiser a mesma estrutura de código, poderá usar declarações ternárias!

def check_all_conditions():
    x = check_size()
    x = x if x else check_color()
    x = x if x else check_tone()
    x = x if x else check_flavor()

    return x if x else None

Eu acho que isso parece bom e claro, se você olhar para ele.

Demo:

Captura de tela dele em execução

Phinet
fonte
7
O que há com o pequeno peixe ASCII acima do seu terminal?
36
@LegoStormtroopr Eu uso a casca de peixe, então eu a decorei com um tanque de peixes ascii para me fazer feliz. :)
Phinet
3
Agradecimentos para o peixe fino (e as cores a propósito, o editor é isso?)
mathreadler
4
Você pode obter peixes em fishshell.com e o arquivo de configuração para ascii aqui pastebin.com/yYVYvVeK , também o editor é um texto sublime.
Phinet 21/03
9
x if x else <something>só pode ser reduzido para #x or <something>
5

Para mim, a melhor resposta é a do @ phil-frost, seguida pelo @ wayne-werner's.

O que acho interessante é que ninguém disse nada sobre o fato de que uma função retornará muitos tipos de dados diferentes, o que tornará obrigatório fazer verificações no tipo de x em si para realizar qualquer trabalho adicional.

Então, eu misturaria a resposta do @ PhilFrost com a idéia de manter um único tipo:

def all_conditions(x):
    yield check_size(x)
    yield check_color(x)
    yield check_tone(x)
    yield check_flavor(x)

def assessed_x(x,func=all_conditions):
    for condition in func(x):
        if condition:
            return x
    return None

Observe que isso xé passado como argumento, mas também all_conditionsé usado como um gerador passado de funções de verificação, onde todas elas recebem uma xverificação e retornam Trueou False. Usando funccom all_conditionso valor padrão, você pode usarassessed_x(x) , ou você pode passar um gerador de ainda mais personalizado viafunc .

Dessa forma, você recebe xassim que um cheque passa, mas sempre será do mesmo tipo.

suculento
fonte
4

Idealmente, eu reescreveria as check_ funções para retornar Trueou, em Falsevez de um valor. Seus cheques então se tornam

if check_size(x):
    return x
#etc

Supondo que você xnão seja imutável, sua função ainda poderá modificá-la (embora não possa reatribuí-la) - mas uma função chamada checknão deve realmente modificá-la de qualquer maneira.

RoadieRich
fonte
3

Uma pequena variação no primeiro exemplo de Martijns acima, que evita o se dentro do loop:

Status = None
for c in [check_size, check_color, check_tone, check_flavor]:
  Status = Status or c();
return Status
mathreadler
fonte
Faz? Você ainda faz uma comparação. Na sua versão, você também verificará todas as condições independentemente e não retornará na primeira instância de um valor verdadeiro, dependendo de quão caras essas funções sejam, que podem não ser desejáveis.
Reti43
4
@ Reti43: Status or c()irá pular / causar um curto-circuito nas chamadas de avaliação c()se Statusfor verdade, de modo que o código nesta resposta não pareça chamar mais funções do que o código do OP. stackoverflow.com/questions/2580136/…
Neil Slater
2
@NeilSlater True. A única desvantagem que vejo é que o melhor caso agora está em O (n) porque o listiterator deve produzir n vezes, quando era O (1) antes, se a primeira função retornar algo verdadeiro em O (1).
timgeb
1
Sim bons pontos. Só espero que o c () leve um pouco mais de tempo para avaliar do que fazer um loop quase vazio. Verificar o sabor pode levar uma noite inteira, pelo menos, se for bom.
mathreadler
3

Eu gosto de @ timgeb's. Enquanto isso, gostaria de acrescentar que a expressão Nonena returndeclaração não é necessária, pois a coleção de orinstruções separadas é avaliada e a primeira zero-zero, nenhuma-vazia, nenhuma-Nenhuma é retornada e, se não houver nenhuma, Noneé retornada. se existe Noneou não!

Então, minha check_all_conditions()função é assim:

def check_all_conditions():
    return check_size() or check_color() or check_tone() or check_flavor()

Usando timeit com number=10**7eu olhei para o tempo de execução de várias sugestões. Para fins de comparação, usei a random.random()função para retornar uma string ou com Nonebase em números aleatórios. Aqui está o código inteiro:

import random
import timeit

def check_size():
    if random.random() < 0.25: return "BIG"

def check_color():
    if random.random() < 0.25: return "RED"

def check_tone():
    if random.random() < 0.25: return "SOFT"

def check_flavor():
    if random.random() < 0.25: return "SWEET"

def check_all_conditions_Bernard():
    x = check_size()
    if x:
        return x

    x = check_color()
    if x:
        return x

    x = check_tone()
    if x:
        return x

    x = check_flavor()
    if x:
        return x
    return None

def check_all_Martijn_Pieters():
    conditions = (check_size, check_color, check_tone, check_flavor)
    for condition in conditions:
        result = condition()
        if result:
            return result

def check_all_conditions_timgeb():
    return check_size() or check_color() or check_tone() or check_flavor() or None

def check_all_conditions_Reza():
    return check_size() or check_color() or check_tone() or check_flavor()

def check_all_conditions_Phinet():
    x = check_size()
    x = x if x else check_color()
    x = x if x else check_tone()
    x = x if x else check_flavor()

    return x if x else None

def all_conditions():
    yield check_size()
    yield check_color()
    yield check_tone()
    yield check_flavor()

def check_all_conditions_Phil_Frost():
    for condition in all_conditions():
        if condition:
            return condition

def main():
    num = 10000000
    random.seed(20)
    print("Bernard:", timeit.timeit('check_all_conditions_Bernard()', 'from __main__ import check_all_conditions_Bernard', number=num))
    random.seed(20)
    print("Martijn Pieters:", timeit.timeit('check_all_Martijn_Pieters()', 'from __main__ import check_all_Martijn_Pieters', number=num))
    random.seed(20)
    print("timgeb:", timeit.timeit('check_all_conditions_timgeb()', 'from __main__ import check_all_conditions_timgeb', number=num))
    random.seed(20)
    print("Reza:", timeit.timeit('check_all_conditions_Reza()', 'from __main__ import check_all_conditions_Reza', number=num))
    random.seed(20)
    print("Phinet:", timeit.timeit('check_all_conditions_Phinet()', 'from __main__ import check_all_conditions_Phinet', number=num))
    random.seed(20)
    print("Phil Frost:", timeit.timeit('check_all_conditions_Phil_Frost()', 'from __main__ import check_all_conditions_Phil_Frost', number=num))

if __name__ == '__main__':
    main()

E aqui estão os resultados:

Bernard: 7.398444877040768
Martijn Pieters: 8.506569201346597
timgeb: 7.244275416364456
Reza: 6.982133448743038
Phinet: 7.925932800076634
Phil Frost: 11.924794811353031
Reza Dodge
fonte
2

Dessa forma, é um pouco fora da caixa, mas acho que o resultado final é simples, legível e com boa aparência.

A idéia básica é raiseuma exceção quando uma das funções é avaliada como verdadeira e retorna o resultado. Aqui está como isso pode parecer:

def check_conditions():
    try:
        assertFalsey(
            check_size,
            check_color,
            check_tone,
            check_flavor)
    except TruthyException as e:
        return e.trigger
    else:
        return None

Você precisará de uma assertFalseyfunção que crie uma exceção quando um dos argumentos de função chamados for avaliado como verdadeiro:

def assertFalsey(*funcs):
    for f in funcs:
        o = f()
        if o:
            raise TruthyException(o)

O exposto acima pode ser modificado para fornecer também argumentos para as funções a serem avaliadas.

E é claro que você precisará do TruthyExceptionpróprio. Esta exceção fornece o objectque acionou a exceção:

class TruthyException(Exception):
    def __init__(self, obj, *args):
        super().__init__(*args)
        self.trigger = obj

Você pode transformar a função original em algo mais geral, é claro:

def get_truthy_condition(*conditions):
    try:
        assertFalsey(*conditions)
    except TruthyException as e:
        return e.trigger
    else:
        return None

result = get_truthy_condition(check_size, check_color, check_tone, check_flavor)

Isso pode ser um pouco mais lento porque você está usando uma ifinstrução e manipulando uma exceção. No entanto, a exceção é tratada apenas no máximo uma vez, portanto, o impacto no desempenho deve ser menor, a menos que você espere executar a verificação e obter um Truevalor muitos milhares de vezes.

Rick apoia Monica
fonte
// , Fofa! É considerado "Pythonic" usar manipulação de exceção para esse tipo de coisa?
Nathan Basanese 23/03
@NathanBasanese Sure- exceções são usadas para controlar o fluxo o tempo todo. StopIterationé um bom exemplo: uma exceção é gerada toda vez que você esgotar um iterável. O que você deseja evitar é gerar sucessivas exceções repetidas vezes, o que seria caro. Mas fazer isso uma vez não é.
Rick suporta Monica
// Ah, acho que você está se referindo a algo como programmers.stackexchange.com/questions/112463/… . Votei nessa pergunta e nesta resposta. Os documentos do Python 3 para isso estão aqui: docs.python.org/3/library/stdtypes.html#iterator-types , eu acho.
Nathan Basanese 23/03
1
Você deseja definir uma função de uso geral e uma exceção, apenas para fazer algumas verificações em alguma outra função em algum lugar? Eu acho que é um pouco demais.
Blacklight Shining
@BacklightShining Concordo. Eu nunca faria isso sozinho. O OP pediu maneiras de evitar o código repetido, mas acho que o que ele começou é perfeitamente correto.
Rick suporta Monica
2

A maneira pitônica é usar o comando reduzir (como alguém já mencionado) ou ferramentas (como mostrado abaixo), mas parece-me que o simples uso do curto-circuito do oroperador produz um código mais claro

from itertools import imap, dropwhile

def check_all_conditions():
    conditions = (check_size,\
        check_color,\
        check_tone,\
        check_flavor)
    results_gen = dropwhile(lambda x:not x, imap(lambda check:check(), conditions))
    try:
        return results_gen.next()
    except StopIteration:
        return None
Dmitry Rubanovich
fonte
0

Vou pular aqui e nunca escrevi uma única linha de Python, mas presumo que if x = check_something(): return xseja válido?

se então:

def check_all_conditions():

    if (x := check_size()): return x
    if (x := check_color()): return x
    if (x := check_tone()): return x
    if (x := check_flavor()): return x

    return None
Richard87
fonte
1
Não é um Python válido, não. O Python não permite que você use o operador de atribuição assim. No entanto, uma nova expressão de atribuição especial foi adicionada muito recentemente, para que você possa escrever agora if ( x := check_size() ) :para o mesmo efeito.
Jack Aidley
0

Ou use max:

def check_all_conditions():
    return max(check_size(), check_color(), check_tone(), check_flavor()) or None
Sub-10
fonte
-2

Eu já vi algumas implementações interessantes de declarações de troca / caso com ditados no passado que me levaram a essa resposta. Usando o exemplo que você forneceu, você obteria o seguinte. (É loucura using_complete_sentences_for_function_names, então check_all_conditionsé renomeado para status. Veja (1))

def status(k = 'a', s = {'a':'b','b':'c','c':'d','d':None}) :
  select = lambda next, test : test if test else next
  d = {'a': lambda : select(s['a'], check_size()  ),
       'b': lambda : select(s['b'], check_color() ),
       'c': lambda : select(s['c'], check_tone()  ),
       'd': lambda : select(s['d'], check_flavor())}
  while k in d : k = d[k]()
  return k

A função de seleção elimina a necessidade de chamar cada check_FUNCTIONduas vezes, ou seja, você evitacheck_FUNCTION() if check_FUNCTION() else next adicionando outra camada de função. Isso é útil para funções de execução longa. As lambdas no ditado atrasam a execução de seus valores até o loop while.

Como bônus, você pode modificar a ordem de execução e até pular alguns dos testes alterando ke, spor exemplo,k='c',s={'c':'b','b':None} reduzindo o número de testes e revertendo a ordem de processamento original.

o timeit bolsistas podem pechinchar sobre o custo de adicionar uma ou duas camadas extras à pilha e o custo do ditado procurar, mas você parece mais preocupado com a beleza do código.

Como alternativa, uma implementação mais simples pode ser a seguinte:

def status(k=check_size) :
  select = lambda next, test : test if test else next
  d = {check_size  : lambda : select(check_color,  check_size()  ),
       check_color : lambda : select(check_tone,   check_color() ),
       check_tone  : lambda : select(check_flavor, check_tone()  ),
       check_flavor: lambda : select(None,         check_flavor())}
  while k in d : k = d[k]()
  return k
  1. Quero dizer isso não em termos de pep8, mas em termos de usar uma palavra descritiva concisa no lugar de uma frase. Concedido que o OP pode estar seguindo alguma convenção de codificação, trabalhando em uma base de código existente ou não se importando com termos concisos em sua base de código.
Carel
fonte
1
Às vezes, as pessoas ficam realmente loucas com o nome quando uma palavra serve. Usando o código do OP como exemplo, é improvável que ele tenha funções chamadas, check_no/some/even/prime/every_third/fancy_conditionsmas apenas essa função; por que não chamá-lo statusou se alguém insistir check_status. Usar _all_é supérfluo, ele não garante a integridade dos universos. A nomeação certamente deve usar um conjunto consistente de palavras-chave, aproveitando o espaçamento de nomes sempre que possível. Frases longas servem melhor como doutrinas. Raramente é necessário mais de 8 a 10 caracteres para descrever algo de forma sucinta.
Carel 23/03
1
Sou fã de nomes longos de funções, porque quero que as funções de nível superior sejam auto-documentadas. Mas check_all_conditionsé um nome ruim, porque é não verificar todas as condições, se uma é verdadeira. Eu usaria algo como matches_any_condition.
John Hazen
Esse é um tato interessante a ser adotado. Eu tento minimizar o número de letras que digitarei erros mais tarde :) Parece que coloquei um monte de opiniões na minha solução, quando eu estava realmente tentando fornecer uma dica útil. Isso deve ser editado?
Carel
2
Isso parece muito hacky, especialmente considerando as outras soluções nessa questão. O que o OP está tentando fazer não é nada complicado; a solução deve ser simples o suficiente para entender meio adormecido. E eu não tenho ideia do que está acontecendo aqui.
Blacklight Shining
Eu estava buscando flexibilidade. Resposta modificado para incluir um menos 'hacky' variante
Carel