Logique de nouvelle tentative Python basée sur le code de retour et la sortie

Sep 09 2020

J'essaie d'écrire une fonction qui exécute la commande donnée avec un nombre donné de tentatives et de délais. Si le code de retour est 0 et que la chaîne de sortie a attendu_output (partiel) doit rompre la boucle et renvoyer le code de retour et la sortie. Y a-t-il mieux pour écrire ce code?

cmd check_output_and_retry(cmd, expected_output, delay=1, retry=3):
    for i in range(retry):
        # Run the given command.
        # Return value will be return code with cmd output.
        ret, output = execute_cmd(cmd)
        if ret == 0 and expected_out in output:
            break
        else:
            time.sleep(delay)
    return ret, output

Réponses

2 scnerd Sep 09 2020 at 00:20

Sans entrer dans un code golf'esque "le moyen le plus court possible d'écrire cette fonction", je vais juste noter quelques alternatives à votre implémentation particulière, et vous pourrez vous demander si vous pensez qu'elles sont plus simples ou non.

  1. if ret == 0équivaut à if not ret. Bien que ce ne soit probablement pas plus lisible dans l' ifinstruction, il se peut que vous ayez nommé cette variable, par exemple program_failed = not ret.

  2. expected_out in outputest juste une chose que quelqu'un pourrait vouloir faire pour évaluer si la sortie était ce qu'il voulait (par exemple, peut-être à la place qu'il veut faire une vérification regex). Vous voudrez peut-être remplacer cela par une fonction totalement libre, puis appelez simplement cette fonction et utilisez son résultat; par exemple,if ret == 0 and output_ok(output):

  3. En s'appuyant sur ces deux éléments, il serait peut-être plus joli de nommer les conditions:

    status_ok = ret == 0
    output_ok = check_output(output)
    if status_ok and output_ok:  # ...
    
  4. En raison de la nature de la ifdéclaration, la elsecondition est superflue. Ce serait équivalent à écrire:

    if condition:
        break
    
    time.sleep(delay)
    
  5. Vous savez return ret, outputsi le programme réussit ou non, ce qui peut prêter à confusion. Puisque le but de votre fonction est de vérifier la sortie du programme et de réessayer en cas d'échec, il semble étrange de retourner "avec succès" si le programme échoue à ces vérifications simplement parce qu'il n'a plus de tentatives. Envisagez plutôt de lever une exception.

    for _ in range(retry):
        # ...
        if condition:
            break
        # ...
    else:
        raise ValueError("Failed to run program successfully, <some useful information about ret, output, etc.>")
    return output  # Only output, since ``ret`` is guaranteed to be 0
    
  6. PyCharm et potentiellement d' autres stylers Python pourraient se fâcher à vous pour avoir une variable nommée vous ne l' utilisez pas: i. Pensez à le remplacer par _:

    for _ in range(retry):  # ...
    
  7. Je vais simplement supposer que ce cmd check_output...n'est qu'une faute de frappe, puisque Python utilise def.

  8. Écrivez un docstring!

Encore une fois, vous ne pensez peut-être pas que tout ce qui précède est des améliorations, juste des alternatives. Si vous les avez tous suivis, vous pourriez vous retrouver avec ce qui suit:

def check_output_and_retry(cmd, check_output: lambda out: True, delay=1, retry=3):
    """Runs command until it succeeds. Raises a ValueError if it can't get the program to succeed.

    Args:
        cmd (str): The command to run
        check_output (callable): A function that takes the output string and returns whether or not it's ok
        delay (int|float): The number of seconds to wait between attempts
        retry (int): The number of times to try running the command before failing

    Returns:
        str: The program output. The program return code is guaranteed to be 0.
    """
    for _ in range(retry):
        # Run the given command.
        ret, output = execute_cmd(cmd)
        status_ok = ret == 0
        output_ok = check_output(output)
        if status_ok and output_ok:
            break

        time.sleep(delay)
    else:
        raise ValueError(f"Program failed to execute within {retry} attempts (status: {ret}: {output}")

    # Return value will be cmd output, since the return code is guaranteed to always be 0
    return output