Python: Cómo ignorar las líneas de #comment cuando se lee 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 los 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 syntax

Related of "Python: Cómo ignorar las líneas de #comment cuando se lee en un archivo"

puedes usar startswith ()

p.ej

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

Te recomiendo que no ignores toda la línea cuando veas un carácter # ; simplemente ignora el rest de la línea. Puedes 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. Entonces, al indexar con [0] tomamos solo la parte antes de la cadena de partición.

EDITAR: Si está usando una versión de Python que no tiene partition() , aquí está el código que podría 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 capturando la subcadena 0 (al indexar con [0] ) obtendría la misma respuesta sin el argumento 1 , pero esto podría ser un poco más rápido. (Simplificado a partir de mi código original gracias a un comentario de @gnr. Mi código original fue más desordenado sin ninguna buena razón; gracias, @gnr.)

También puedes escribir tu propia versión de la 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 suficientemente simples para cadenas citadas, esto no es difícil. Sería difícil si aceptara cualquier cadena legal citada de Python, porque hay comillas multilínea de comillas simples y comillas dobles con una barra invertida que se escapa de las cadenas de comillas triples de fin de línea (usando comillas simples o dobles), y incluso cuerdas en bruto! 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 que esto se complicara en una pregunta etiquetada como “principiante”, pero esta máquina de estados es bastante simple, y espero que sea interesante.

Llegaré a esta hora, pero el problema de manejar los comentarios de estilo shell (o 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 citas y los escapes de manera adecuada, sino que también agrega una gran cantidad de funciones geniales (como la capacidad de tener archivos de otros archivos si lo desea). No lo he probado para la velocidad en archivos grandes, pero es lo suficientemente rápido como para cosas pequeñas.

El caso común cuando también se divide cada línea de entrada en campos (en espacios en blanco) es incluso más sencillo:

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

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 lo llama comienza con la cadena que ingresó.

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' , que significa ‘leer el archivo en modo binario’. Como está esperando un archivo de texto, es mejor abrirlo con 'rt' . Aunque esta distinción es irrelevante en los sistemas operativos similares a UNIX, es importante en Windows (y en Macs con OS X anteriores).

El segundo problema es el manejador de archivo abierto. La función open() devuelve un objeto de archivo, y se considera una buena práctica cerrar los 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 se cuentan como referencia, y cuando el recuento de referencias de un objeto llega a cero, se libera, y en algún momento después de que se libera un objeto, Python llamará a su destructor (un método especial llamado __del__ ). Tenga en cuenta que probablemente dije : Python tiene el mal hábito de no llamar al destructor a los objetos cuyo recuento de referencia cae a cero poco antes de que finalice el progtwig. ¡Supongo que tiene prisa!

Para progtwigs de corta duración como shell scripts, y particularmente para objetos de archivo, esto no importa. Su sistema operativo limpiará automáticamente cualquier manija de archivo que quede abierta cuando finalice el progtwig. Pero si abrió el archivo, leyó el contenido y luego inició un cálculo largo sin cerrar explícitamente el identificador de archivo, es probable que Python deje el identificador de archivo abierto durante el cálculo. Y eso es una mala práctica.

Esta versión funcionará en cualquier versión 2.x de Python, y soluciona los problemas que mencioné 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.

Según lo sugerido por steveha, el uso de la statement “con” ahora se considera la mejor práctica. Si estás usando 2.6 o más, deberías escribirlo de esta manera:

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

La statement “con” limpiará el identificador 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 luego un ‘#’, debe eliminar los espacios en blanco antes de buscar el ‘#’. En ese caso, deberías 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 la line . El método lstrip() devuelve una copia de la cadena con todos sus espacios en blanco iniciales eliminados.

Recientemente he encontrado que una función de generador hace un gran trabajo de esto. He usado 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. Que yo necesito.

Una versión más compacta de una expresión de filtrado también puede verse así:

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

(l for ... ) se denomina “expresión generadora”, que actúa aquí como un iterador de ajuste que filtrará todas las líneas innecesarias del archivo al iterar sobre él. No lo confunda con la misma cosa en correas cuadradas [l for ... ] que es una “lista de comprensión” 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 que sea menos de 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.

Sé que este es un hilo antiguo, pero esta es una función de generador que uso para mis propios propósitos. Elimina los comentarios sin importar dónde aparezcan en la línea, al igual que elimina los espacios en blanco iniciales y finales y las 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! 

rendirá:

 host02 host03 host04 

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

 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 de hosts, como en mi ejemplo anterior). Si este es el caso, entonces el final de la cola del código anterior se modificaría para:

 if __name__ == '__main__': with open('hosts.txt', 'r') as f: hosts = strip_comments(f) for host in hosts: print('\'%s\'' % host) 

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

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 a favor, ya que puede incluir cualquier información antes de #