127

I have a program that writes a list to a file. The list is a list of pipe delimited lines and the lines should be written to the file like this:

123|GSV|Weather_Mean|hello|joe|43.45
122|GEV|temp_Mean|hello|joe|23.45
124|GSI|Weather_Mean|hello|Mike|47.45

BUT it wrote them line this ahhhh:

123|GSV|Weather_Mean|hello|joe|43.45122|GEV|temp_Mean|hello|joe|23.45124|GSI|Weather_Mean|hello|Mike|47.45

This program wrote all the lines into like one line without any line breaks.. This hurts me a lot and I gotta figure-out how to reverse this but anyway, where is my program wrong here? I thought write lines should write lines down the file rather than just write everything to one line..

fr = open(sys.argv[1], 'r') # source file
fw = open(sys.argv[2]+"/masked_"+sys.argv[1], 'w') # Target Directory Location

for line in fr:
    line = line.strip()
    if line == "":
        continue
    columns = line.strip().split('|')
    if columns[0].find("@") > 1:
        looking_for = columns[0] # this is what we need to search
    else:
        looking_for = "Dummy@dummy.com"
    if looking_for in d:
        # by default, iterating over a dictionary will return keys
            new_line = d[looking_for]+'|'+'|'.join(columns[1:])
            line_list.append(new_line)
    else:
        new_idx = str(len(d)+1)
        d[looking_for] = new_idx
        kv = open(sys.argv[3], 'a')
        kv.write(looking_for+" "+new_idx+'\n')
        kv.close()
        new_line = d[looking_for]+'|'+'|'.join(columns[1:])
        line_list.append(new_line)
fw.writelines(line_list)
daaawx
  • 3,273
  • 2
  • 17
  • 16
user836087
  • 2,271
  • 8
  • 23
  • 33
  • It seems like you are not appending `"\n"` anywhere in your `line_list`. – Rohit Jain Dec 05 '12 at 18:40
  • 2
    You could take a look at [csv](http://docs.python.org/2/library/csv.html) – esauro Dec 05 '12 at 18:41
  • Where do I add the "\n"? Do I add it to the list object?? – user836087 Dec 05 '12 at 18:44
  • 8
    I just wanna say it's really cool coming back to Python in 2022 and finding that a question from 9 years ago about a function called "writelines" that DOESN'T WRITE LINES is still relevant. What was I thinking, trying to write lines to a file with a method called writelines? Silly me. I'm going to upload a module to PyPi and all it's going to do is change the method name to writeline. – leisheng Feb 25 '22 at 01:54

8 Answers8

103

This is actually a pretty common problem for newcomers to Python—especially since, across the standard library and popular third-party libraries, some reading functions strip out newlines, but almost no writing functions (except the log-related stuff) add them.

So, there's a lot of Python code out there that does things like:

fw.write('\n'.join(line_list) + '\n')

(writing a single string) or

fw.writelines(line + '\n' for line in line_list)

Either one is correct, and of course you could even write your own writelinesWithNewlines function that wraps it up…

But you should only do this if you can't avoid it.

It's better if you can create/keep the newlines in the first place—as in Greg Hewgill's suggestions:

line_list.append(new_line + "\n")

And it's even better if you can work at a higher level than raw lines of text, e.g., by using the csv module in the standard library, as esuaro suggests.

For example, right after defining fw, you might do this:

cw = csv.writer(fw, delimiter='|')

Then, instead of this:

new_line = d[looking_for]+'|'+'|'.join(columns[1:])
line_list.append(new_line)

You do this:

row_list.append(d[looking_for] + columns[1:])

And at the end, instead of this:

fw.writelines(line_list)

You do this:

cw.writerows(row_list)

Finally, your design is "open a file, then build up a list of lines to add to the file, then write them all at once". If you're going to open the file up top, why not just write the lines one by one? Whether you're using simple writes or a csv.writer, it'll make your life simpler, and your code easier to read. (Sometimes there can be simplicity, efficiency, or correctness reasons to write a file all at once—but once you've moved the open all the way to the opposite end of the program from the write, you've pretty much lost any benefits of all-at-once.)

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • 112
    all of this because python does not do the obvious with `writelines` – WestCoastProjects Oct 26 '18 at 02:54
  • 94
    Wow. This is ridiculous behaviour on Python's part: readlines() *should strip* newlines, but doesn't, and writelines() *should add* newlines, but doesn't. The whole point of having readlines and writelines is so that you can easily read and write lists. Mildly infuriating! – James Nov 22 '18 at 16:05
  • 15
    Does it worth a PEP like f.writelines(lines, end='\n') ? – John Lin Aug 24 '19 at 03:09
  • 7
    Yeah, gotta say, this is a helpful answer, but writelines not...you know, writing separate lines...is a real headscratcher. – Brionius Jan 20 '22 at 03:05
  • In case line_list is recently prepared it is legit to use fw.writelines([f"{line}\n" for line in line_list]) – Ofer Rahat Aug 31 '22 at 12:27
63

The documentation for writelines() states:

writelines() does not add line separators

So you'll need to add them yourself. For example:

    line_list.append(new_line + "\n")

whenever you append a new item to line_list.

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
48

As others have noted, writelines is a misnomer (it ridiculously does not add newlines to the end of each line).

To do that, explicitly add it to each line:

with open(dst_filename, 'w') as f:
    f.writelines(s + '\n' for s in lines)
Brent Faust
  • 9,103
  • 6
  • 53
  • 57
12

writelines() does not add line separators. You can alter the list of strings by using map() to add a new \n (line break) at the end of each string.

items = ['abc', '123', '!@#']
items = map(lambda x: x + '\n', items)
w.writelines(items)
Jossef Harush Kadouri
  • 32,361
  • 10
  • 130
  • 129
  • This here is byfar the best optimised approach. as the writelines will run the map iterator. – BuzzR Jun 08 '22 at 08:09
9

Credits to Brent Faust.


Python >= 3.6 with format string:

with open(dst_filename, 'w') as f:
    f.writelines(f'{s}\n' for s in lines)

lines can be a set.

If you are oldschool (like me) you may add f.write('\n') below the second line.

qräbnö
  • 2,722
  • 27
  • 40
8

As others have mentioned, and counter to what the method name would imply, writelines does not add line separators. This is a textbook case for a generator. Here is a contrived example:

def item_generator(things):
    for item in things:
        yield item
        yield '\n'

def write_things_to_file(things):
    with open('path_to_file.txt', 'wb') as f:
        f.writelines(item_generator(things))

Benefits: adds newlines explicitly without modifying the input or output values or doing any messy string concatenation. And, critically, does not create any new data structures in memory. IO (writing to a file) is when that kind of thing tends to actually matter. Hope this helps someone!

jtschoonhoven
  • 1,948
  • 1
  • 19
  • 16
2

As we have well established here, writelines does not append the newlines for you. But, what everyone seems to be missing, is that it doesn't have to when used as a direct "counterpart" for readlines() and the initial read persevered the newlines!

When you open a file for reading in binary mode (via 'rb'), then use readlines() to fetch the file contents into memory, split by line, the newlines remain attached to the end of your lines! So, if you then subsequently write them back, you don't likely want writelines to append anything!

So if, you do something like:

with open('test.txt','rb') as f: lines=f.readlines()
with open('test.txt','wb') as f: f.writelines(lines)

You should end up with the same file content you started with.

BuvinJ
  • 10,221
  • 5
  • 83
  • 96
  • 2
    And when exactly do you want to read a file just to write it back? – Private May 14 '19 at 13:27
  • 1
    You are taking the code example far too literally. I didn't include a faux context and extraneous example code for when you would read a file and write it back. There would be something in between normally where you would modify the content. Or, you might be "copying" / tweaking a source file into a destination file. The point is the readlines/writelines counterparts handle this issue implicitly. – BuvinJ May 14 '19 at 14:27
  • Sorry, I didn't mean to insist on the issue. It's just that most of the time when I read a file and write back to it or to another file, one of the methods I call is `.strip` or '.rstrip()` so in my workflow this would just never occur. – Private May 14 '19 at 14:43
  • Np. I have often done the same, but if you landed on this page, you might want to change that practice in some use cases, where you want to preserve the line endings, you can avoid having to explicitly append them by keeping this post in mind. – BuvinJ May 14 '19 at 15:05
1

As we want to only separate lines, and the writelines function in python does not support adding separator between lines, I have written the simple code below which best suits this problem:

sep = "\n" # defining the separator
new_lines = sep.join(lines) # lines as an iterator containing line strings

and finally:

with open("file_name", 'w') as file:
    file.writelines(new_lines)

and you are done.

Hosein Basafa
  • 1,068
  • 8
  • 11
  • Since you are posting an answer to an older question, it would be most helpful that you support your purported answer with some code and the output that results from using your code. – Gray Sep 24 '20 at 20:50
  • 1
    There is no point in calling writelines if you joined the lines to single string beforehand. – Dalibor Filus Dec 21 '20 at 15:41