2009-11-10 90 views
36

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 comienzo de la línea.Python: Cómo ignorar #comment lines al leer en un archivo

creo que debe ser algo como esto:

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

Pero yo soy nuevo en Python y no sé la sintaxis

Respuesta

46

puede utilizar startswith()

por ejemplo

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

si.gracias por notarlo. – ghostdog74

+4

... ignorando los espacios en blanco iniciales: 'if not line.strip(). Startswith (" # ")' – exhuma

+10

Su código tiene 'for line in open (" file "):' que deja un descriptor de archivo abierto. Debes mantener el valor de retorno de 'open (" file ")' y llamar 'close()' sobre él explícitamente cuando hayas terminado, o usar la declaración 'with' (ver http://docs.python.org/ library/stdtypes.html # file.close). –

38

Te recomiendo que no ignores toda la línea cuando veas un # personaje; simplemente ignora el resto de la línea. Puede hacerlo fácilmente con una función llamada método de cadena 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, indexando con [0] tomamos solo la parte antes de la cadena de partición.

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

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 '#', a continuación, 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 agarrando la subcadena número 0 (indexando con [0]) obtendría la misma respuesta sin el argumento 1, pero esto podría ser un poco más rápido. (Simplificado desde mi código original gracias a un comentario de @gnr. Mi código original era más complicado por ninguna razón; gracias, @gnr.)

También podría escribir su propia versión de partition(). Aquí está 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 señaló 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 bastante simples para las cadenas entre comillas, esto no es difícil. Sería difícil aceptar cualquier cadena legal citada de Python, porque hay comillas simples, comillas dobles y líneas múltiples con una barra invertida que escapa del final de línea, cadenas de comillas triples (usando comillas simples o dobles), y ¡incluso cadenas crudas! La única forma posible de manejar correctamente todo eso sería una máquina de estado complicada.

Pero si nos limitamos a una simple cadena entre comillas, podemos manejarlo con una simple máquina de estados. Incluso podemos permitir una comilla doble entre comillas en 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ían obtener esta complicada en una pregunta etiquetado como "principiante" pero esta máquina de estados es razonablemente simple, y espero que será interesante.

+1

Cierto, pero probablemente también deba preocuparse por el # citado, si está fuera de la corrección. – dalle

+1

Una pequeña nota para el OP es que la partición no está disponible en versiones anteriores. – ghostdog74

+0

Oh, diablos, eso es correcto: 'partition()' solo está en Python 2.5 y más reciente. Editaré mi respuesta y agregaré otra solución. – steveha

3

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 "expresión generadora", que actúa aquí como un iterador envoltura que filtrará todas las líneas que no necesite de archivo, mientras que la iteración encima de eso. No lo confundas con lo mismo en los corchetes cuadrados [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 ella.

A veces es posible que desee tener menos de un liney 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.

5

Ésta es la forma más breve 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 que lo llaman en los arranques con la cadena que ha pasado en

Mientras que esto está bien en. En algunas circunstancias, como los guiones 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'. Como espera un archivo de texto, es mejor abrirlo con 'rt'. Aunque esta distinción es irrelevante en los 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 de archivo, y se considera buena práctica cerrar archivos cuando haya terminado con ellos. Para hacerlo, llame al método close() en el objeto. Ahora, Python va a probablemente haga esto por usted, eventualmente; en los objetos de Python se cuentan los recuentos de referencia, y cuando el recuento de referencias de un objeto va a cero se libera, y en algún momento después de liberar un objeto 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 termine el programa. ¡Supongo que tiene prisa!

Para programas de corta vida como scripts de shell, y particularmente para objetos de archivo, esto no importa. Su sistema operativo limpiará automáticamente todos los identificadores de archivos que se hayan dejado abiertos cuando el programa finalice. Pero si abrió el archivo, leyó el contenido y luego inició una computación larga sin cerrar explícitamente el identificador del archivo primero, es probable que Python deje abierto el manejador del archivo durante su cálculo. Y esa es una mala práctica.

Esta versión funcionará en cualquier versión 2.x de Python, y fija tanto los problemas que se discutió anteriormente:

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

Ésta es la mejor forma general para versiones anteriores de Python.

Según lo sugerido por steveha, el uso de la declaración "con" ahora se considera la mejor práctica. Si estás usando 2.6 o superior, debe escribirlo de esta manera:

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

La declaración "con" borrará el identificador del archivo por usted.

En su pregunta usted dijo "las líneas que comienzan con #", así que eso es lo que te he mostrado aquí. Si desea que se corten las líneas que comienzan con el espacio en blanco opcional y luego un '#', se debe quitar el espacio en blanco antes de buscar el '#'. En ese caso, debe cambiar esta situación:

if line.startswith('#'): 

a esto:

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

En Python, las cadenas son inmutables, así que esto no cambia el valor de line. El método lstrip() devuelve una copia de la cadena con toda su líder en el espacio en blanco eliminado.

+0

"Python tiene la mala costumbre de no llamar al destructor en objetos cuyo recuento de referencias cae a cero poco antes de que termine el programa." ¿Tiene evidencia de este reclamo? – gotgenes

+0

"No se garantiza que se invoquen los métodos __del __() para los objetos que aún existen cuando sale el intérprete". Última frase del párrafo en __del __(): http://docs.python.org/reference/datamodel.html#object.__del__ Esa es la documentación de 2.6; lo mismo vale para 3.1. Supongo que lo que escribí no era exactamente exacto. Los hechos exactos son sin embargo relevantes al punto que estaba haciendo. No estoy seguro si vale la pena editar mi respuesta para corregir. –

-1

que tienden a utilizar

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

Esto hará caso de toda la línea, aunque la respuesta que incluye rpartition tiene mi upvote, ya que puede incluir cualquier información desde antes de la #

5

que he encontrado recientemente que una función de generador hace un gran trabajo de esto. He usado funciones similares a saltar las líneas de comentarios, líneas en blanco, etc.

defino mi función de

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

De esa manera, puedo hacer

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

Ésta es reutilizable a través todo mi código, y puedo agregar cualquier manejo/registro/etc adicional. Que yo necesito.

6

Llego tarde, pero el problema de manejar los comentarios de estilo de concha (o estilo de pitón) # es muy común.

He estado usando un código casi cada vez que lee un archivo de texto.
problema es que no maneja comentarios citados o escapado adecuadamente. 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 utilizar 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 sólo se encarga de cotizaciones y escapa correctamente, se añade un montón de funcionalidades fresco (como la capacidad de tener los archivos fuente distinta archivos si quieres). No he probado la velocidad en archivos de gran tamaño, pero sí lo suficiente.

El caso común cuando también está dividiendo cada línea de entrada en los campos (el espacio 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 
+0

¡Esto merece un montón más de votos ascendentes! Incluso la solución sin 'shlex' es más completa que la respuesta aceptada (lo cual está bien para casos de uso simple, pero el problema es que si le dices a los usuarios" puedes poner comentarios ahí, pero solo si comienzan la línea ", tú) seré el primero en olvidar esa restricción). – dlukes

2

Sé que este es un viejo hilo, pero esto es una función de generador que yo usar para mis propios fines.Quita los comentarios sin importar dónde aparezcan en la línea, así como quitando los espacios en blanco iniciales/finales y las líneas en blanco . El siguiente texto de origen:

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

se Rendimiento:

host02 
host03 
host04 

Aquí se documenta código, 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!',] 

    hosts = strip_comments(HOSTS) 
    for host in hosts: 
     print('\'%s\'' % host) 

El caso de uso normal será para despojar a los comentarios de un archivo (es decir, un archivo de hosts, como en mi ejemplo anterior). Si este es el caso, entonces el extremo de la cola del código anterior podría ser modificado para:

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

    for host in hosts: 
     print('\'%s\'' % host) 
1

Uso de expresiones regulares re.compile("^(?:\s+)*#|(?:\s+)") saltarse las nuevas líneas y comentarios.

Cuestiones relacionadas