Ghost Ark
Ghost Ark

Reputation: 61

My file keeps on being closed after .write()

Here is this block of code I'm trying to finish:

elif parameter == 'statistics':
    outfile.write(stats(infile))
    for line in infile:
        outfile.write(line)

So essentially, I am trying to write the statistics of the file into the new file that is being copied. The statistics works and everything as when I open the file, the statistics are written in. However, I noticed because of the two outfile.write it seems to close after the first one, so only the statistics go in and not the rest of the content in the original file.

The error that I am getting is this:

ValueError: I/O operation on closed file.

I am unsure why the file is closing.

EDIT: Here is the whole code, as requested

def copy_file():
    infile_name = input("Please enter the name of the file to copy: ")
    infile = open(infile_name, 'r', encoding='utf8')
    parameter = input("Please enter a parameter(line numbers, Gutenberg trim, statistics, none): ")
    outfile_name = input("Please enter the name of the new copy:  ")
    outfile = open(outfile_name, 'w', encoding='utf8')
    counter = 1
    if parameter == 'line numbers':
        for line in infile:
            outfile.write(f' {counter:6}: {line}')
            counter += 1
    elif parameter == 'Gutenberg trim':
        copyStart = False
        for line in infile:
            #print(line.strip())
            if '*** START' in line.strip():
                copyStart = True
                continue
            elif '*** END' in line.strip():
                copyStart = False
                break
            if copyStart == True:
                outfile.write(line)
    elif parameter == 'statistics':
        outfile.write(stats(infile))
        for line in infile:
            outfile.write(line)
    else:
        for line in infile:
            outfile.write(line)
    infile.close()
    outfile.close()
copy_file()

EDIT2: So sorry for not including it. Here is the stats function:

def stats(text) -> str:
    with text as infile:
        totallines = 0
        emplines = 0
        characters = 0
        for line in infile:
            totallines += 1
            characters += len(line)
            if len(line.strip()) == 0:
                emplines += 1
        lines = totallines - emplines
        totalaveChars = characters/totallines
        nonempaveChars = characters/lines
        result = (f'{totallines:5} lines in list \n'
                  f'{emplines:5} empty lines in list \n'
                  f'{totalaveChars:5.1f} average characters per line \n'
                  f'{nonempaveChars:5.1f} average chars per non-empty line')
        return result
print(stats(open('ASH.txt', 'r', encoding='utf8')))

Here is the result from stats:

13052 lines in list 
2666 empty lines in list 
44.6 average characters per line 
56.0 average chars per non-empty line

Upvotes: 0

Views: 84

Answers (3)

gboffi
gboffi

Reputation: 25023

     elif parameter == 'statistics':
         outfile.write(stats(infile))
         for line in infile:
             outfile.write(line)

... only the statistics go in and not the rest of the content in the original file ...

My educated guess is that the stats function consumes and possibly closes the input stream (IS).

If stats is somehow well behaved and limits itself to consuming the IS, one can rewind it

           ...
           infile.seek(0) # rewind the input stream
           for line in infile:
               outfile.write(line)

If, on the other hand, stats is a bit disruptive and closes altogether the IS one can use the .name attribute of the file object to reopen it, like this

           ...
           for line in open(infile.name):
               outfile.write(line)

This second solution works even in the first, milder hypotesis and works even if the code was passed the infile file object from a outer call.

Another possibility, if you can access and modify the stats source code, is to undo the reading performed by the function, memorizing the current position in the input stream before any read operation and later rewind the IS to that position

def stats(infile):
    ...
    current_pos = infile.tell()
    # do your stuff
    ...
    infile.seek(current_pos)
    return workload

For this to work, of course, the file object has not to be closed before the .seek(), either explicitly (by a .close()) or implicitly (by falling outside the scope of a with block).
If this is your situation (closed file), please remove either the explicit infile.close() or the (unnecessary) with statement and the rewind will be correct.

Upvotes: 0

Mathieu
Mathieu

Reputation: 5746

The issue is in the stats function. The with statement will close the file with the local name text, which is infile in your case!

def stats(text) -> str:
    totallines = 0
    emplines = 0
    characters = 0
    for line in text:
        totallines += 1
        characters += len(line)
        if len(line.strip()) == 0:
            emplines += 1
    lines = totallines - emplines
    totalaveChars = characters/totallines
    nonempaveChars = characters/lines
    result = (f'{totallines:5} lines in list \n'
              f'{emplines:5} empty lines in list \n'
              f'{totalaveChars:5.1f} average characters per line \n'
              f'{nonempaveChars:5.1f} average chars per non-empty line')
    return result

In your main program, you passed to the function stats the variable infile, which is a file. You do not need to reopen it with with inside the stats functions. Moreover, with will ensure the closing at the end. Thus in your main loop, the infile is closed after the call on stats.

Upvotes: 4

Nordle
Nordle

Reputation: 3001

Try the following;

def copy_file():
    infile_name = input("Please enter the name of the file to copy: ")
    parameter = input("Please enter a parameter(line numbers, Gutenberg trim, statistics, none): ")
    outfile_name = input("Please enter the name of the new copy:  ")
    counter = 1
    with open(infile_name, 'r', encoding='utf8') as infile:
      with open(outfile_name, 'w', encoding='utf8') as outfile:
          if parameter == 'line numbers':
              for line in infile:
                  outfile.write(f' {counter:6}: {line}')
                  counter += 1
          elif parameter == 'Gutenberg trim':
              copyStart = False
              for line in infile:
                  #print(line.strip())
                  if '*** START' in line.strip():
                      copyStart = True
                      continue
                  elif '*** END' in line.strip():
                      copyStart = False
                      break
                  if copyStart == True:
                      outfile.write(line)
          elif parameter == 'statistics':
              outfile.write(stats(infile))
              for line in infile:
                  outfile.write(line)
          else:
              for line in infile:
                  outfile.write(line)
    copy_file()

Using with open(filename, 'r') as file: it will automatically close the file once the operation has finished, and not before.

Upvotes: 0

Related Questions