Python :Cómo ignorar # líneas de comentario al leer en un archivo


En Python, acabo de leer una línea de un archivo de texto y me gustaría saber cómo codificar para ignorar comentarios con un hash # al principio de la línea.

Creo que debería ser algo como esto:

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

Pero soy nuevo en Python y no conozco la sintaxis

Author: Alex Stoddard, 2009-11-10

9 answers

Puedes usar startswith()

Eg

for line in open("file"):
    li=line.strip()
    if not li.startswith("#"):
        print line.rstrip()
 53
Author: ghostdog74,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-10 08:28:40

Le recomiendo que no ignore toda la línea cuando vea un carácter #; simplemente ignore el resto de la línea. Puede hacerlo fácilmente con una función de método de cadena llamada partition:

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

partition devuelve una tupla: todo antes de la cadena de partición, la cadena de partición y todo después de la cadena de partición. Por lo tanto, al indexar con [0] tomamos solo la parte antes de la cadena de partición.

EDITAR: Si está utilizando una versión de Python que no tiene partition(), aquí es el código que podrías usar:

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

Esto divide la cadena en un carácter'#', luego mantiene todo antes de la división. El argumento 1 hace que el método .split() se detenga después de una división; ya que solo estamos tomando la 0a subcadena (indexando con [0]) obtendrías la misma respuesta sin el argumento 1, pero esto podría ser un poco más rápido. (Simplificado de mi código original gracias a un comentario de @ gnr. Mi código original era messier sin ninguna buena razón; gracias, @ gnr.)

También podrías escribir tu propia versión de partition(). Aquí hay uno llamado 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 notó que '#' puede aparecer dentro de una cadena. No es tan fácil manejar este caso correctamente, así que simplemente lo ignoré, pero debería haber dicho algo.

Si su archivo de entrada tiene reglas lo suficientemente simples para las cadenas entrecomilladas, esto no es difícil. Sería difícil si aceptara cualquier cadena de texto legal entre comillas de Python, porque hay comillas simples, dobles y multilíneas comillas con una barra invertida que escapa del final de la línea, cadenas entrecomilladas triples (usando comillas simples o dobles), e incluso cadenas sin procesar! La única manera posible de manejar correctamente todo eso sería una máquina de estados complicada.

Pero si nos limitamos a una simple cadena entre comillas, podemos manejarla con una simple máquina de estados. Incluso podemos permitir una comilla doble con comillas invertidas dentro de la cadena.

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

Realmente no quería complicarme esto en una pregunta etiquetada "principiante" pero esta máquina de estado es razonablemente simple, y espero que sea interesante.

 39
Author: steveha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-08 21:10:13

Esta es la forma más corta posible:

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

El método startswith() en una cadena devuelve True si la cadena en la que la llamas comienza con la cadena que pasaste.

Si bien esto está bien en algunas circunstancias, como los scripts de shell, tiene dos problemas. Primero, no especifica cómo abrir el archivo. El modo predeterminado para abrir un archivo es 'r', lo que significa 'leer el archivo en modo binario'. Ya que está esperando un archivo de texto, es mejor abrirlo con 'rt'. Aunque esto la distinción es irrelevante en sistemas operativos tipo UNIX, es importante en Windows (y en Mac pre-OS X).

El segundo problema es el identificador de archivo abierto. La función open() devuelve un objeto file, y se considera una buena práctica cerrar archivos cuando haya terminado con ellos. Para hacer eso, llame al método close() en el objeto. Ahora, Python probablemente hará esto por ti, eventualmente; en Python los objetos son contados por referencia, y cuando el recuento de referencia de un objeto va a cero se libera, y en algún momento después de que un objeto es liberado Python llamará a su destructor (un método especial llamado __del__). Tenga en cuenta que dije probablemente: Python tiene la mala costumbre de no llamar al destructor en objetos cuyo recuento de referencias cae a cero poco antes de que finalice el programa. ¡Supongo que tiene prisa!

Para programas de corta duración como scripts de shell, y particularmente para objetos file, esto no importa. Su sistema operativo limpie automáticamente cualquier manejador de archivos que quede abierto cuando finalice el programa. Pero si abrió el archivo, leyó el contenido y luego inició un cálculo largo sin cerrar explícitamente el controlador de archivo primero, es probable que Python deje el controlador de archivo abierto durante su cálculo. Y eso es una mala práctica.

Esta versión funcionará en cualquier 2.x versión de Python, y corrige los dos problemas que comenté anteriormente:

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

Esta es la mejor forma general para versiones anteriores de Python.

Como sugiere steveha, usar la declaración "con" ahora se considera una mejor práctica. Si estás usando 2.6 o superior deberías escribirlo de esta manera:

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

La instrucción "with" limpiará el controlador de archivo por usted.

En tu pregunta dijiste "líneas que comienzan con #", así que eso es lo que te he mostrado aquí. Si desea filtrar las líneas que comienzan con espacios en blanco opcionales y entonces un'#', debe eliminar los espacios en blanco antes buscando el '#'. En ese caso, usted debe cambiar esto:

    if line.startswith('#'):

A esto:

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

En Python, las cadenas son inmutables, por lo que esto no cambia el valor de line. El método lstrip() devuelve una copia de la cadena con todos sus espacios en blanco iniciales eliminados.

 6
Author: Larry Hastings,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-11 14:40:08

Llego tarde, pero el problema de manejar los comentarios del estilo shell (o del estilo python) # es muy común.

He estado usando algún código casi cada vez que leo un archivo de texto.
El problema es que no maneja correctamente los comentarios citados o escapados. Pero funciona para casos simples y es fácil.

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

Una solución más robusta es usar 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

Este enfoque shlex no solo maneja las comillas y los escapes correctamente, sino que agrega un mucha funcionalidad genial (como la capacidad de tener archivos fuente de otros archivos si lo desea). No lo he probado para la velocidad en archivos grandes, pero es lo suficientemente zippy de cosas pequeñas.

El caso común cuando también está dividiendo cada línea de entrada en campos (en espacios en blanco) es aún más simple:

import shlex
for line in instream:
    fields = shlex.split(line, comments=True)
    if not fields:
        continue
    # process list of fields 
 6
Author: travc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-27 21:48:20

He encontrado recientemente que una función de generador hace un gran trabajo de esto. He utilizado funciones similares para omitir líneas de comentarios, líneas en blanco, etc.

Defino mi función como

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

De esa manera, solo puedo hacer

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

Esto es reutilizable en todo mi código, y puedo agregar cualquier manejo/registro/etc. adicional. eso lo necesito.

 5
Author: Tim Whitcomb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-20 22:36:14

Una versión más compacta de una expresión de filtrado también puede tener este aspecto:

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

(l for ... ) se llama "generator expression" que actúa aquí como un iterador de envoltura que filtrará todas las líneas innecesarias del archivo mientras itera sobre él. No lo confunda con lo mismo en square brakets [l for ... ] que es una "comprensión de lista" que primero leerá todas las líneas del archivo en la memoria y solo entonces comenzará a iterar sobre él.

A veces es posible que desee tenerlo menos una línea y más legible:

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

Todos los filtros se ejecutarán sobre la marcha en una iteración.

 3
Author: isagalaev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-10 14:08:59

Sé que este es un hilo viejo, pero esta es una función de generador que usar para mis propios fines. Elimina los comentarios sin importar dónde aparecen en la línea, así como eliminar los espacios en blanco iniciales/finales y líneas en blanco. El siguiente texto fuente:

# 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!

Cederá:

host02
host03
host04

Aquí está el código documentado, que incluye una demo:

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 non-blank line from ``item`` with comments and
            leading/trailing whitespace removed.

    """

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


if __name__ == '__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))

El caso de uso normal será eliminar los comentarios de un archivo (es decir, un archivo hosts, como en mi ejemplo anterior). Si esto es el caso, entonces el final de la cola del código anterior se modificaría a:

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

    for host in hosts:
        print('\'%s\'' % host)
 3
Author: Doug R.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-08 13:16:52

Use regex re.compile("^(?:\s+)*#|(?:\s+)") para omitir las nuevas líneas y comentarios.

 1
Author: Revanth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-10 20:34:09

Tiendo a usar

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

Esto ignorará toda la línea, aunque la respuesta que incluye rpartition tiene mi voto favorable, ya que puede incluir cualquier información anterior a la #

 0
Author: Simon Walker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-17 00:27:07