Python Forum
Saving text file with a click: valueerror i/o operation on closed file
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Saving text file with a click: valueerror i/o operation on closed file
#1
Hi guys, first post here. I hope I'm in the right place, since it seems to be more of a Python issue than a PyQt issue.

I've been trying to import the text generated from the console into a text file with a click.

So every time I do it, I'll notify through the console "Beginning measurement". And the data will be streaming into a text file, then I'd like to tell via the console that it's done. Here's the code:

def on_click(self):
            


        clear = lambda: os.system('cls')
        clear()
        print("Beginning measurement...")


        now = datetime.now()
        current_time = now.strftime("%Y%m%d-%H%M%S")

        
        parser = argparse.ArgumentParser(description="Read and print data from MAX30102")
        parser.add_argument("-r", "--raw", action="store_true",
                            help="print raw data instead of calculation result")
        parser.add_argument("-t", "--time", type=int, default=30,
                            help="duration in seconds to read from sensor, default 30")
        args = parser.parse_args()

        with open("/home/pi/Desktop/Output data - " + current_time + self.textbox.text() + ".txt", 'w') as file:
        
                sys.stdout = file
        
                print(self.textbox.text())
                hrm = HeartRateMonitor(print_raw=args.raw, print_result=(not args.raw))
                hrm.start_sensor()
                try:
                    time.sleep(args.time)
                except KeyboardInterrupt:
                    print('keyboard interrupt detected, exiting...')

                hrm.stop_sensor()
                file.close()

        print("Measurement complete.")

        
There are two things which happened:
1. The "measurement complete" printout does not occur
2. I can't restart my measurements unless I re-debug the program, in which the error generated from here is "valueerror: i/o operation on closed file"

I'm a beginner, so anyone who can point me in the right direction here is greatly appreciated.

Thanks!
Vizier87
Reply
#2
You can redirect sys.stdout by defining your own context. The following code prints 'Hello there!' in file 'foo.txt' and then 'Done' to standard output. Don't close files explicitly when using the 'with' statement. They are closed automatically when the context exits
from contextlib import contextmanager
import sys

@contextmanager
def redirect_stdout(file):
    old, sys.stdout = sys.stdout, file
    try:
        yield
    finally:
        sys.stdout = old

def main():
    with open('foo.txt', 'w') as file, redirect_stdout(file):
        print('Hello there!')
    print('Done')

if __name__ == '__main__':
    main()
That said, I don't know how the HeartRateMonitor class works, but you could consider injecting a file argument in this class instead of redirecting the global stdout.
vizier87 likes this post
Reply
#3
(Nov-12-2020, 04:46 PM)Gribouillis Wrote: You can redirect sys.stdout by defining your own context. The following code prints 'Hello there!' in file 'foo.txt' and then 'Done' to standard output. Don't close files explicitly when using the 'with' statement. They are closed automatically when the context exits
from contextlib import contextmanager
import sys

@contextmanager
def redirect_stdout(file):
    old, sys.stdout = sys.stdout, file
    try:
        yield
    finally:
        sys.stdout = old

def main():
    with open('foo.txt', 'w') as file, redirect_stdout(file):
        print('Hello there!')
    print('Done')

if __name__ == '__main__':
    main()
That said, I don't know how the HeartRateMonitor class works, but you could consider injecting a file argument in this class instead of redirecting the global stdout.

Hi there. Thanks a bunch.

It works nicely if I'm making a simple Python script, but currently I'm running PyQt and I have to admit, the whole thing about structures of the programming are a bit hazy for me so please bear with me for a while.

So I've been modifying it a bit:
@contextmanager
    def redirect_stdout(file):
        old, sys.stdout = sys.stdout, file
        try:
            yield
        finally:
            sys.stdout = old

        
    def on_click(self):
       
        
        clear = lambda: os.system('cls')
        clear()
        print("Beginning measurement...")


        now = datetime.now()
        current_time = now.strftime("%Y%m%d-%H%M%S")

        
        parser = argparse.ArgumentParser(description="Read and print data from MAX30102")
        parser.add_argument("-r", "--raw", action="store_true",
                            help="print raw data instead of calculation result")
        parser.add_argument("-t", "--time", type=int, default=30,
                            help="duration in seconds to read from sensor, default 30")
        args = parser.parse_args()


        with open("/home/pi/Desktop/Output data - " + current_time + self.textbox.text() + ".txt", 'w') as file, redirect_stdout(file):
            hrm = HeartRateMonitor(print_raw=args.raw, print_result=(not args.raw))
            hrm.start_sensor()
            try:
                time.sleep(args.time)
            except KeyboardInterrupt:
                print('keyboard interrupt detected, exiting...')

            hrm.stop_sensor()
        
And got this error:
NameError: name 'redirect_stdout' is not defined

And as for your suggestion on injecting something in the class, I'm not sure where I should put it, but here it is :
from max30102 import MAX30102
import hrcalc
import threading
import time
import numpy as np


class HeartRateMonitor(object):
    """
    A class that encapsulates the max30102 device into a thread
    """

    LOOP_TIME = 0.01

    def __init__(self, print_raw=False, print_result=False):
        self.bpm = 0
        if print_raw is True:
            print('IR, Red')
        self.print_raw = print_raw
        self.print_result = print_result

    def run_sensor(self):
        sensor = MAX30102()
        ir_data = []
        red_data = []
        bpms = []

        # run until told to stop
        while not self._thread.stopped:
            # check if any data is available
            num_bytes = sensor.get_data_present()
            if num_bytes > 0:
                # grab all the data and stash it into arrays
                while num_bytes > 0:
                    red, ir = sensor.read_fifo()
                    num_bytes -= 1
                    ir_data.append(ir)
                    red_data.append(red)
                    if self.print_raw:
                        print("{0}, {1}".format(ir, red))

                while len(ir_data) > 100:
                    ir_data.pop(0)
                    red_data.pop(0)

                if len(ir_data) == 100:
                    bpm, valid_bpm, spo2, valid_spo2 = hrcalc.calc_hr_and_spo2(ir_data, red_data)
                    if valid_bpm:
                        bpms.append(bpm)
                        while len(bpms) > 4:
                            bpms.pop(0)
                        self.bpm = np.mean(bpms)
                        if (np.mean(ir_data) < 50000 and np.mean(red_data) < 50000):
                            self.bpm = 0
                            if self.print_result:
                                print("Finger not detected")
                        if self.print_result:
                            print("BPM: {0}, SpO2: {1}".format(self.bpm, spo2))

            time.sleep(self.LOOP_TIME)

        sensor.shutdown()

    def start_sensor(self):
        self._thread = threading.Thread(target=self.run_sensor)
        self._thread.stopped = False
        self._thread.start()

    def stop_sensor(self, timeout=2.0):
        self._thread.stopped = True
        self.bpm = 0
        self._thread.join(timeout)
It was code made available anyway, but I haven't changed anything here because I'll just mess it up.

And, if it is not too much, is it possible that the saving of the text occurs after the console is done loading the texts, and so that I can also see in both the console AND immediately in the text?

I'm doing some data collection with the GUI I'm making, so I'd like to make it as efficient as possible.

Sorry if PyQt is not supposed to be discussed here.

Thanks.
Reply
#4
For the first question, you still have the error because you must not indent the
@contextmanager
def redirect_stdout(file):
    ...
Don't write this definition in a class, write it at module level outside of any class and without indentation.

If you want to print simultaneously to the file and to the console, here is an updated version
from contextlib import contextmanager
import sys

@contextmanager
def redirect_stdout(file):
    old, sys.stdout = sys.stdout, file
    try:
        yield
    finally:
        sys.stdout = old

class FileTee:
    """Stores a sequence of file objects to allow printing to several files at a time"""
    def __init__(self, ifile):
        self.ifile = list(ifile)
        
    def write(self, s):
        for f in self.ifile:
            f.write(s)

def main():
    with open('foo.txt', 'w') as file, redirect_stdout(
        FileTee([sys.stdout, file])):
        print('Hello there!')
    print('Done')

if __name__ == '__main__':
    main()
Reply
#5
Phew that worked. Thanks. You made my day. I placed the contextmanager outside the class and it worked like a charm.

Next I'm incorporating the FileTee.

Though I wonder why it works if it's out of the class?
Reply
#6
vizier87 Wrote:Though I wonder why it works if it's out of the class?
That's the way scopes work in Python, or rather namespaces. Inside a method, the names that are defined in the class are not directly available.
foo = 1  # name in the **global** namespace

class Spam:
    bar = 2  # in the namespace of the class body

    def method(self):
        qux = 3 # in the method's **local** namespace

        print(qux) # works. We see the local namespace
        print(foo) # also works. We see the global namespace
        print(bar) # doesn't work. We don't see the class' namespace
        print(self.bar) # works. We can access the class' members through the instance self.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  How can I write formatted (i.e. bold, italic, change font size, etc.) text to a file? JohnJSal 12 28,115 Feb-13-2025, 04:48 AM
Last Post: tomhansky
  asyncio: WebSocketClient:connection closed (ERR): sent 1011 Michel777 0 504 Feb-04-2025, 09:22 PM
Last Post: Michel777
  How to umount on a closed distro ebolisa 5 1,253 Jan-03-2025, 03:50 AM
Last Post: DeaD_EyE
  How to write variable in a python file then import it in another python file? tatahuft 4 953 Jan-01-2025, 12:18 AM
Last Post: Skaperen
  Problems writing a large text file in python Vilius 4 1,011 Dec-21-2024, 09:20 AM
Last Post: Pedroski55
  Get an FFMpeg pass to subprocess.PIPE to treat list as text file? haihal 2 1,052 Nov-21-2024, 11:48 PM
Last Post: haihal
  A question about 'Event loop is closed' fc5igm 3 5,265 Oct-01-2024, 09:12 AM
Last Post: skdaro
  JSON File - extract only the data in a nested array for CSV file shwfgd 2 1,084 Aug-26-2024, 10:14 PM
Last Post: shwfgd
  FileNotFoundError: [Errno 2] No such file or directory although the file exists Arnibandyo 0 962 Aug-12-2024, 09:11 AM
Last Post: Arnibandyo
  "[Errno 2] No such file or directory" (.py file) IbrahimBennani 13 6,433 Jun-17-2024, 12:26 AM
Last Post: AdamHensley

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020