web-dev-qa-db-fra.com

Python: comment ignorer les lignes #comment lors de la lecture d'un fichier

En Python, je viens de lire une ligne sous forme de fichier texte et je voudrais savoir comment coder pour ignorer les commentaires avec un hachage # au début de la ligne.

Je pense que ça devrait être quelque chose comme ça:

for 
   if line !contain #
      then ...process line
   else end for loop 

Mais je suis nouveau à Python et je ne connais pas la syntaxe

41
John

vous pouvez utiliser commence par ()

par exemple

for line in open("file"):
    li=line.strip()
    if not li.startswith("#"):
        print line.rstrip()
55
ghostdog74

Je vous recommande de ne pas ignorer toute la ligne lorsque vous voyez un caractère #; ignorez simplement le reste de la ligne. Vous pouvez le faire facilement avec une fonction de méthode de chaîne appelée partition:

with open("filename") as f:
    for line in f:
        line = line.partition('#')[0]
        line = line.rstrip()
        # ... do something with line ...

partition renvoie un Tuple: tout ce qui précède la chaîne de partition, la chaîne de partition et tout ce qui se trouve après la chaîne de partition. Donc, en indexant avec [0] Nous prenons juste la partie avant la chaîne de partition.

EDIT: Si vous utilisez une version de Python qui n'a pas partition(), voici le code que vous pouvez utiliser:

with open("filename") as f:
    for line in f:
        line = line.split('#', 1)[0]
        line = line.rstrip()
        # ... do something with line ...

Cela fractionne la chaîne sur un caractère "#", puis conserve tout avant la division. L'argument 1 Fait arrêter la méthode .split() après un split; puisque nous saisissons simplement la sous-chaîne 0 (en indexant avec [0]), vous obtiendriez la même réponse sans l'argument 1, mais cela pourrait être un peu plus rapide. (Simplifié par rapport à mon code d'origine grâce à un commentaire de @gnr. Mon code d'origine était plus malpropre sans raison; merci, @gnr.)

Vous pouvez également écrire votre propre version de partition(). En voici un qui s'appelle part():

def part(s, s_part):
    i0 = s.find(s_part)
    i1 = i0 + len(s_part)
    return (s[:i0], s[i0:i1], s[i1:])

@dalle a noté que '#' peut apparaître dans une chaîne. Ce n'est pas si facile de gérer ce cas correctement, alors je l'ai simplement ignoré, mais j'aurais dû dire quelque chose.

Si votre fichier d'entrée a des règles assez simples pour les chaînes entre guillemets, ce n'est pas difficile. Il serait difficile si vous acceptiez une chaîne juridique Python citée, car il existe des guillemets multilignes entre guillemets simples et doubles avec une barre oblique inversée échappant aux chaînes de fin de ligne triples ( en utilisant des guillemets simples ou doubles), et même des chaînes brutes! La seule façon possible de gérer correctement tout cela serait une machine à états compliquée.

Mais si nous nous limitons à une simple chaîne entre guillemets, nous pouvons la gérer avec une simple machine à états. Nous pouvons même autoriser un guillemet double entre guillemets dans la chaîne.

c_backslash = '\\'
c_dquote = '"'
c_comment = '#'


def chop_comment(line):
    # a little state machine with two state varaibles:
    in_quote = False  # whether we are in a quoted string right now
    backslash_escape = False  # true if we just saw a backslash

    for i, ch in enumerate(line):
        if not in_quote and ch == c_comment:
            # not in a quote, saw a '#', it's a comment.  Chop it and return!
            return line[:i]
        Elif backslash_escape:
            # we must have just seen a backslash; reset that flag and continue
            backslash_escape = False
        Elif in_quote and ch == c_backslash:
            # we are in a quote and we see a backslash; escape next char
            backslash_escape = True
        Elif ch == c_dquote:
            in_quote = not in_quote

    return line

Je ne voulais pas vraiment compliquer les choses dans une question intitulée "débutant", mais cette machine d'état est relativement simple, et j'espère que ce sera intéressant.

42
steveha

J'arrive tard, mais le problème de la gestion du style Shell (ou python) # les commentaires sont très courants.

J'utilise du code presque à chaque fois que je lis un fichier texte.
Le problème est qu'il ne gère pas correctement les commentaires entre guillemets ou échappés . Mais cela fonctionne pour les cas simples et c'est facile.

for line in whatever:
    line = line.split('#',1)[0].strip()
    if not line:
        continue
    # process line

Une solution plus robuste consiste à utiliser shlex :

import shlex
for line in instream:
    Lex = shlex.shlex(line)
    Lex.whitespace = '' # if you want to strip newlines, use '\n'
    line = ''.join(list(Lex))
    if not line:
        continue
    # process decommented line

Cette approche Shlex gère non seulement les guillemets et les échappements correctement, mais ajoute également de nombreuses fonctionnalités intéressantes (comme la possibilité d'avoir des fichiers source d'autres fichiers si vous le souhaitez). Je ne l'ai pas testé pour la vitesse sur de gros fichiers, mais il est assez zippé de petites choses.

Le cas courant lorsque vous divisez également chaque ligne d'entrée en champs (sur des espaces) est encore plus simple:

import shlex
for line in instream:
    fields = shlex.split(line, comments=True)
    if not fields:
        continue
    # process list of fields 
8
travc

C'est la forme la plus courte possible:

for line in open(filename):
  if line.startswith('#'):
    continue
  # PROCESS LINE HERE

La méthode startswith() sur une chaîne renvoie True si la chaîne sur laquelle vous l'appelez commence par la chaîne que vous avez transmise.

Bien que cela soit correct dans certaines circonstances, comme les scripts Shell, cela pose deux problèmes. Tout d'abord, il ne spécifie pas comment ouvrir le fichier. Le mode par défaut pour ouvrir un fichier est 'r', Ce qui signifie 'lire le fichier en mode binaire'. Puisque vous attendez un fichier texte, il est préférable de l'ouvrir avec 'rt'. Bien que cette distinction ne soit pas pertinente sur les systèmes d'exploitation de type UNIX, elle est importante sous Windows (et sur les Mac pré-OS X).

Le deuxième problème est le descripteur de fichier ouvert. La fonction open() renvoie un objet fichier, et il est considéré comme une bonne pratique de fermer les fichiers lorsque vous en avez terminé avec eux. Pour ce faire, appelez la méthode close() sur l'objet. Maintenant, Python va probablement faire cela pour vous, éventuellement; in Python sont des références- compté, et lorsque le nombre de références d'un objet atteint zéro, il est libéré, et à un moment donné après qu'un objet est libéré Python appellera son destructeur (une méthode spéciale appelée __del__) . Notez que j'ai dit probablement: Python a la mauvaise habitude de ne pas réellement appeler le destructeur sur des objets dont le nombre de références tombe à zéro peu de temps avant la fin du programme. Je suppose c'est pressé!

Pour les programmes de courte durée comme les scripts Shell, et en particulier pour les objets fichier, cela n'a pas d'importance. Votre système d'exploitation nettoiera automatiquement tous les descripteurs de fichiers laissés ouverts à la fin du programme. Mais si vous ouvrez le fichier, lisez le contenu, puis commencez un long calcul sans fermer explicitement le descripteur de fichier au préalable, Python est susceptible de laisser le descripteur de fichier ouvert pendant votre calcul. Et c'est une mauvaise pratique .

Cette version fonctionnera dans n'importe quelle version 2.x de Python et corrige les deux problèmes dont j'ai discuté ci-dessus:

f = open(file, 'rt')
for line in f:
  if line.startswith('#'):
    continue
  # PROCESS LINE HERE
f.close()

Il s'agit de la meilleure forme générale pour les anciennes versions de Python.

Comme suggéré par steveha, l'utilisation de la déclaration "with" est désormais considérée comme la meilleure pratique. Si vous utilisez 2.6 ou supérieur, vous devez l'écrire de cette façon:

with open(filename, 'rt') as f:
  for line in f:
    if line.startswith('#'):
      continue
    # PROCESS LINE HERE

L'instruction "with" nettoiera le descripteur de fichier pour vous.

Dans votre question, vous avez dit "lignes qui commencent par #", c'est donc ce que je vous ai montré ici. Si vous souhaitez filtrer les lignes commençant par espaces facultatifs et alors un '#', vous devez supprimer les espaces avant de rechercher le '#'. Dans ce cas, vous devez modifier ceci:

    if line.startswith('#'):

pour ça:

    if line.lstrip().startswith('#'):

En Python, les chaînes sont immuables, donc cela ne change pas la valeur de line. La méthode lstrip() renvoie une copie de la chaîne avec tous ses espaces de tête supprimés.

6
Larry Hastings

J'ai découvert récemment qu'une fonction de générateur fait un excellent travail. J'ai utilisé des fonctions similaires pour ignorer les lignes de commentaire, les lignes vides, etc.

Je définis ma fonction comme

def skip_comments(file):
    for line in file:
        if not line.strip().startswith('#'):
            yield line

De cette façon, je peux juste faire

f = open('testfile')
for line in skip_comments(f):
    print line

Ceci est réutilisable dans tout mon code, et je peux ajouter toute manipulation/journalisation/etc supplémentaire. ce dont j'ai besoin.

5
Tim Whitcomb

Je sais que c'est un vieux fil, mais c'est une fonction de générateur que j'utilise à mes propres fins. Il supprime les commentaires, peu importe où ils apparaissent dans la ligne, ainsi que la suppression des espaces blancs de début/fin et des lignes vides. Le texte source suivant:

# Comment line 1
# Comment line 2

# Host01  # This Host commented out.
Host02  # This Host not commented out.
Host03
  Host04  # Oops! Included leading whitespace in error!

donnera:

Host02
Host03
Host04

Voici le code documenté, qui comprend une démo:

def strip_comments(item, *, token='#'):
    """Generator. Strips comments and whitespace from input lines.

    This generator strips comments, leading/trailing whitespace, and
    blank lines from its input.

    Arguments:
        item (obj):  Object to strip comments from.
        token (str, optional):  Comment delimiter.  Defaults to ``#``.

    Yields:
        str:  Next uncommented non-blank line from ``item`` with
            comments and leading/trailing whitespace stripped.

    """

    for line in item:
        s = line.split(token, 1)[0].strip()
        if s:
            yield s


if __== '__main__':
    HOSTS = """# Comment line 1
    # Comment line 2

    # Host01  # This Host commented out.
    Host02  # This Host not commented out.
    Host03
      Host04  # Oops! Included leading whitespace in error!""".split('\n')


    hosts = strip_comments(HOSTS)
    print('\n'.join(h for h in hosts))

Le cas d'utilisation normal consiste à supprimer les commentaires d'un fichier (c'est-à-dire un fichier hosts, comme dans mon exemple ci-dessus). Si tel est le cas, la fin du code ci-dessus serait modifiée pour:

if __== '__main__':
    with open('hosts.txt', 'r') as f:
        hosts = strip_comments(f)

    for Host in hosts:
        print('\'%s\'' % Host)
4
Doug R.

Une version plus compacte d'une expression de filtrage peut également ressembler à ceci:

for line in (l for l in open(filename) if not l.startswith('#')):
    # do something with line

(l for ... ) est appelé "expression de générateur" qui agit ici comme un itérateur d'habillage qui filtrera toutes les lignes inutiles du fichier tout en itérant dessus. Ne le confondez pas avec la même chose dans des paniers carrés [l for ... ] qui est une "compréhension de liste" qui lira d'abord toutes les lignes du fichier en mémoire et commencera ensuite à itérer dessus.

Parfois, vous voudrez peut-être qu'il soit moins linéaire et plus lisible:

lines = open(filename)
lines = (l for l in lines if ... )
# more filters and mappings you might want
for line in lines:
    # do something with line

Tous les filtres seront exécutés à la volée en une seule itération.

3
isagalaev

Utilisez regex re.compile("^(?:\s+)*#|(?:\s+)") pour ignorer les nouvelles lignes et les commentaires.

1
Revanth

J'ai tendance à utiliser

for line  in lines:
    if '#' not in line:
        #do something

Cela ignorera toute la ligne, bien que la réponse qui inclut la répartition ait mon vote positif car elle peut inclure toutes les informations d'avant le #

0
Simon Walker