Question

In Python, I have just read a line form a text file and I'd like to know how to code to ignore comments with a hash # at the beginning of the line.

I think it should be something like this:

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

But I'm new to Python and I don't know the syntax

Was it helpful?

Solution

you can use startswith()

eg

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

OTHER TIPS

I recommend you don't ignore the whole line when you see a # character; just ignore the rest of the line. You can do that easily with a string method function called partition:

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

partition returns a tuple: everything before the partition string, the partition string, and everything after the partition string. So, by indexing with [0] we take just the part before the partition string.

EDIT: If you are using a version of Python that doesn't have partition(), here is code you could use:

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

This splits the string on a '#' character, then keeps everything before the split. The 1 argument makes the .split() method stop after a one split; since we are just grabbing the 0th substring (by indexing with [0]) you would get the same answer without the 1 argument, but this might be a little bit faster. (Simplified from my original code thanks to a comment from @gnr. My original code was messier for no good reason; thanks, @gnr.)

You could also just write your own version of partition(). Here is one called 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 noted that '#' can appear inside a string. It's not that easy to handle this case correctly, so I just ignored it, but I should have said something.

If your input file has simple enough rules for quoted strings, this isn't hard. It would be hard if you accepted any legal Python quoted string, because there are single-quoted, double-quoted, multiline quotes with a backslash escaping the end-of-line, triple quoted strings (using either single or double quotes), and even raw strings! The only possible way to correctly handle all that would be a complicated state machine.

But if we limit ourselves to just a simple quoted string, we can handle it with a simple state machine. We can even allow a backslash-quoted double quote inside the string.

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

I didn't really want to get this complicated in a question tagged "beginner" but this state machine is reasonably simple, and I hope it will be interesting.

I'm coming at this late, but the problem of handling shell style (or python style) # comments is a very common one.

I've been using some code almost everytime I read a text file.
Problem is that it doesn't handle quoted or escaped comments properly. But it works for simple cases and is easy.

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

A more robust solution is to use 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

This shlex approach not only handles quotes and escapes properly, it adds a lot of cool functionality (like the ability to have files source other files if you want). I haven't tested it for speed on large files, but it is zippy enough of small stuff.

The common case when you're also splitting each input line into fields (on whitespace) is even simpler:

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

This is the shortest possible form:

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

The startswith() method on a string returns True if the string you call it on starts with the string you passed in.

While this is okay in some circumstances like shell scripts, it has two problems. First, it doesn't specify how to open the file. The default mode for opening a file is 'r', which means 'read the file in binary mode'. Since you're expecting a text file it is better to open it with 'rt'. Although this distinction is irrelevant on UNIX-like operating systems, it's important on Windows (and on pre-OS X Macs).

The second problem is the open file handle. The open() function returns a file object, and it's considered good practice to close files when you're done with them. To do that, call the close() method on the object. Now, Python will probably do this for you, eventually; in Python objects are reference-counted, and when an object's reference count goes to zero it gets freed, and at some point after an object is freed Python will call its destructor (a special method called __del__). Note that I said probably: Python has a bad habit of not actually calling the destructor on objects whose reference count drops to zero shortly before the program finishes. I guess it's in a hurry!

For short-lived programs like shell scripts, and particularly for file objects, this doesn't matter. Your operating system will automatically clean up any file handles left open when the program finishes. But if you opened the file, read the contents, then started a long computation without explicitly closing the file handle first, Python is likely to leave the file handle open during your computation. And that's bad practice.

This version will work in any 2.x version of Python, and fixes both the problems I discussed above:

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

This is the best general form for older versions of Python.

As suggested by steveha, using the "with" statement is now considered best practice. If you're using 2.6 or above you should write it this way:

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

The "with" statement will clean up the file handle for you.

In your question you said "lines that start with #", so that's what I've shown you here. If you want to filter out lines that start with optional whitespace and then a '#', you should strip the whitespace before looking for the '#'. In that case, you should change this:

    if line.startswith('#'):

to this:

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

In Python, strings are immutable, so this doesn't change the value of line. The lstrip() method returns a copy of the string with all its leading whitespace removed.

I've found recently that a generator function does a great job of this. I've used similar functions to skip comment lines, blank lines, etc.

I define my function as

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

That way, I can just do

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

This is reusable across all my code, and I can add any additional handling/logging/etc. that I need.

I know that this is an old thread, but this is a generator function that I use for my own purposes. It strips comments no matter where they appear in the line, as well as stripping leading/trailing whitespace and blank lines. The following source text:

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

will yield:

host02
host03
host04

Here is documented code, which includes a 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 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 __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))

The normal use case will be to strip the comments from a file (i.e., a hosts file, as in my example above). If this is the case, then the tail end of the above code would be modified to:

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

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

A more compact version of a filtering expression can also look like this:

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

(l for ... ) is called "generator expression" which acts here as a wrapping iterator that will filter out all unneeded lines from file while iterating over it. Don't confuse it with the same thing in square brakets [l for ... ] which is a "list comprehension" that will first read all the lines from the file into memory and only then will start iterating over it.

Sometimes you might want to have it less one-liney and more readable:

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

All the filters will be executed on the fly in one iteration.

Use regex re.compile("^(?:\s+)*#|(?:\s+)") to skip the new lines and comments.

I tend to use

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

This will ignore the whole line, though the answer which includes rpartition has my upvote as it can include any information from before the #

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top