30 votes

Émettre un avertissement en cas de virgule manquante entre les bogues des éléments de la liste

L'Histoire:

Lorsqu'une liste de chaînes de caractères est défini sur plusieurs lignes, il est souvent facile d' oublier une virgule entre les éléments de la liste, comme dans cet exemple de cas:

test = [
    "item1"
    "item2"
]

La liste test aurait un seul élément "item1item2".

Très souvent, le problème apparaît après réorganiser les éléments dans une liste.

Exemple de Dépassement de Pile questions ayant ce problème:

La Question:

Est-il possible d', de préférence à l'aide de l'analyse statique de code, émettre un avertissement dans les cas de ce genre, afin de repérer le problème le plus tôt possible?

19voto

Jim Points 8793

Ce ne sont que des solutions possibles puisque je ne suis pas vraiment apt avec d'analyse statique.

Avec tokenize:

Récemment, j'ai bricolé autour de avec la segmentation du code python et je crois qu'il a toutes les informations nécessaires pour effectuer ces contrôles, lorsque suffisamment de logique est ajouté. Pour votre liste donnée, les jetons générés avec python -m tokenize list1.py sont comme suit:

python -m tokenize list1.py 

1,0-1,4:    NAME    'test'
1,5-1,6:    OP  '='
1,7-1,8:    OP  '['
1,8-1,9:    NL  '\n'
2,1-2,8:    STRING  '"item1"'
2,8-2,9:    NL  '\n'
3,1-3,8:    STRING  '"item2"'
3,8-3,9:    NL  '\n'
4,0-4,1:    OP  ']'
4,1-4,2:    NEWLINE '\n'
5,0-5,0:    ENDMARKER   ''

C'est bien sûr la"problématique"cas où le contenu allez obtenir concaténées. Dans le cas où un , , la sortie légèrement modifié pour refléter cette (j'ai ajouté les tokens pour la liste du corps):

1,7-1,8:    OP  '['
1,8-1,9:    NL  '\n'
2,1-2,8:    STRING  '"item1"'
2,8-2,9:    OP  ','
2,9-2,10:   NL  '\n'
3,1-3,8:    STRING  '"item2"'
3,8-3,9:    NL  '\n'
4,0-4,1:    OP  ']'

Maintenant, nous l' OP ',' jeton signifiant la présence d'un deuxième élément séparés par des virgules.

Compte tenu de cette information, nous pouvons utiliser le vraiment pratique méthode generate_tokens dans la tokenize module. Méthode tokenize.generate_tokens() , tokenize.tokenize() en Py3, a un seul argument readline, une méthode sur fichier-comme des objets qui, essentiellement, retourne la prochaine ligne de ce fichier comme objet (réponse pertinente). Elle retourne un nom de tuple avec 5 éléments au total, avec des informations sur le type de jeton, le jeton de la chaîne avec le numéro de ligne et la position dans la ligne.

À l'aide de cette information, on pourrait en théorie boucle par le biais d'un fichier et quand un OP ',' est absent à l'intérieur d'une liste d'initialisation (dont le début est détecté par la vérification que les jetons NAME, OP '=' et OP '[' existent sur le même numéro de ligne), on peut émettre un avertissement sur les lignes sur lesquelles il a été détecté.

La bonne chose à propos de cette approche est qu'il est assez simple de généraliser. Pour s'adapter à tous les cas où la chaîne de caractères littérale de concaténation prend place (à savoir, à l'intérieur du "groupement" les opérateurs (), {}, [] ), de vérifier si le jeton est de type = 51 (ou 53 pour Python 3) ou qu'une valeur dans l'une de (, [, { existe sur la même ligne (ce sont grossiers, à la tête de l'suggestions atm).

Maintenant, je ne suis pas vraiment sûr de savoir comment les autres gens avec ces sortes de problèmes , mais il semble que cela pourrait être quelque chose que vous pouvez regarder dans. Toutes les informations nécessaires est offert par tokenize, la logique pour détecter c'est la seule chose qui manque.

La mise en œuvre Remarque: Ces valeurs (par exemple, pour type) ne diffèrent entre les versions et sont sujettes à modification donc c'est quelque chose dont on doit être conscient. On pourrait éventuellement tirer parti de ce en travaillant uniquement avec des constantes pour les jetons, si.


Avec parser et ast:

Une autre solution probable qui est probablement plus fastidieux pourrait impliquer l' parser et ast modules. La concaténation de chaînes de caractères est en fait effectuée lors de la création de l'Arbre de Syntaxe Abstraite de sorte que vous pourrait aussi détecter qu'il y a dessus.

Je n'ai pas vraiment envie de faire un dump de la sortie complète des méthodes d' parser et ast que je vais parler, mais, juste pour s'assurer que nous sommes sur la même page, je vais être à l'aide de la liste suivante instruction d'initialisation:

l_init = """
test = [
    "item1"
    "item2",
    "item3"
]
"""

Afin d'obtenir l'arbre d'analyse généré, utilisez p = parser.suite(l_init). Après ceci est fait, vous pouvez obtenir une vue de il avec p.tolist() (de sortie est trop grand pour l'ajouter). Ce que vous remarquerez est que il y aura trois entrées pour les trois différents str objets item1, item2, item3.

D'autre part, lorsque l'AST est créé avec node = ast.parse(l_init) et visualisées avec ast.dump(node) il y a seulement deux entrées: l'une pour la concaténation strs item1item2 et l'un pour l'autre entrée item3.

Donc, c'est une autre probable façon de le faire mais, comme je l'ai mentionné précédemment, c'est plus fastidieux. Je ne sais pas si l'information est disponible et vous traitez avec les deux modules différents. Juste comme une arrière pensée, si vous peut-être envie de jouer avec les objets internes plus élevés dans le compilateur de la chaîne.


Commentaires de fermeture: une fermeture de la note, l' tokenize approche semble être la plus logique dans ce cas. Au contraire même, il semble qu' pylint fonctionne réellement avec astroid une lib python, qui facilite l'analyse de l'arbre de Syntaxe Abstraite pour le code python. Donc, on devrait, idéalement, la regarde et la façon dont il est utilisé à l'intérieur de pylint.

Remarque: bien sûr, je suis peut-être complètement à l'analyser et d'un simple "vérifier blanc-espace ou saut de ligne' solution que vous les gars pourrait suffire. :-)

2voto

keksnicoh Points 602

J'ai mis en place le code basé sur @Jim post. Peut il fonctionne dans toutes les situations:

import tokenize
from io import BytesIO

def my_checker(pycode):
    """
    tokenizes python code and yields 
    start, end, strline of any position where 
    a scenario like this happens (missing string seperator):
      [..., "a string" "derp", ...]
    """
    IDLE = 0
    WAITING_STRING = 1
    CHECKING_SEPARATOR = 2

    tokenizer = tokenize.tokenize(BytesIO(pycode.encode('utf-8')).readline)
    state = IDLE

    for toknum, tokval, start, end, strcode  in tokenizer:
        if state == IDLE:
            if toknum == tokenize.OP and tokval == '[':
                state = WAITING_STRING

        elif state == WAITING_STRING:
            if toknum == tokenize.STRING:
                state = CHECKING_SEPARATOR
            elif toknum == tokenize.OP and tokval == [']']:
                state = IDLE

        elif state == CHECKING_SEPARATOR:
            if toknum == tokenize.STRING:
                yield (start, end, strcode)
            elif toknum == tokenize.OP and tokval in ['+', ',']:
                state = WAITING_STRING
            elif toknum == tokenize.OP and tokval == ']':
                state = IDLE

my_code = """
foo = "derp"
def derp(a,x): 
    return str('dingdong'+str(a*x))
[
    "derp"+"FOO22"  , "FOO", "donk" "slurp",0, 0
]

class extreme_logical_class():
    STATIC_BAD_LIST = [0,
        "BLA,",
        "FOO"
        "derp"
    ] 
    def __init__(self):
        self._in_method_check = ["A" "B"]

nested_list = [
    ['DERP','FOO'],
    [0,'hello', 'peter' 'pan'],
    ['this', 'is', ['ultra', 'mega'
        'nested']] 
]
"""

for error in my_checker(my_code):
    print('missing , in list at: line {}@{} to line {}@{}: "{}"'.format(
        error[0][0],error[0][1],error[1][0],error[1][1], error[2].strip()
    ))

Le résultat est:

keksnicoh@localhost ~ % python3 find_bad_lists.py
missing , in list at: line 6@36 to line 6@43: ""derp"+"FOO22"  , "FOO", "donk" "blurp",0 0"
missing , in list at: line 13@8 to line 13@14: ""derp""
missing , in list at: line 16@37 to line 16@40: "self._in_method_check = ["A" "B"]"
missing , in list at: line 20@24 to line 20@29: "[0,'hello', 'peter' 'pan'],"
missing , in list at: line 22@8 to line 22@16: "'nested']]"

Dans la vraie vie, je préfère éviter de faire de telles erreurs; il y a une bonne IDE est comme Sublime Text qui vous permettent de modifier et mettre en forme des listes avec multi curseur. Si vous vous habituez à ces concepts, ces sortes de "séparation" erreurs ne se produisent pas dans votre code.

Bien sûr si l'on a une Équipe de Développeurs on pourrait intégrer un tel outil dans l'environnement de test.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X