@@ -151,6 +151,11 @@ class IPKernelApp(BaseIPythonApplication, InteractiveShellApp, ConnectionFileMix
151151
152152 _ports = Dict ()
153153
154+ _original_io = Any ()
155+ _log_map = Any ()
156+ _io_modified = Bool (False )
157+ _blackhole = Any ()
158+
154159 subcommands = {
155160 "install" : (
156161 "ipykernel.kernelspec.InstallIPythonKernelSpecApp" ,
@@ -470,56 +475,93 @@ def log_connection_info(self):
470475
471476 def init_blackhole (self ):
472477 """redirects stdout/stderr to devnull if necessary"""
478+ self ._save_io ()
473479 if self .no_stdout or self .no_stderr :
474- blackhole = open (os .devnull , "w" ) # noqa: SIM115
480+ # keep reference around so that it would not accidentally close the pipe fds
481+ self ._blackhole = open (os .devnull , "w" ) # noqa: SIM115
475482 if self .no_stdout :
476- sys .stdout = sys .__stdout__ = blackhole # type:ignore[misc]
483+ if sys .stdout is not None :
484+ sys .stdout .flush ()
485+ sys .stdout = self ._blackhole
477486 if self .no_stderr :
478- sys .stderr = sys .__stderr__ = blackhole # type:ignore[misc]
487+ if sys .stderr is not None :
488+ sys .stderr .flush ()
489+ sys .stderr = self ._blackhole
479490
480491 def init_io (self ):
481492 """Redirect input streams and set a display hook."""
493+ self ._save_io ()
482494 if self .outstream_class :
483495 outstream_factory = import_item (str (self .outstream_class ))
484- if sys .stdout is not None :
485- sys .stdout .flush ()
486496
487- e_stdout = None if self .quiet else sys .__stdout__
488- e_stderr = None if self .quiet else sys .__stderr__
497+ e_stdout = None if self .quiet else sys .stdout
498+ e_stderr = None if self .quiet else sys .stderr
489499
490500 if not self .capture_fd_output :
491501 outstream_factory = partial (outstream_factory , watchfd = False )
492502
503+ if sys .stdout is not None :
504+ sys .stdout .flush ()
493505 sys .stdout = outstream_factory (self .session , self .iopub_thread , "stdout" , echo = e_stdout )
506+
494507 if sys .stderr is not None :
495508 sys .stderr .flush ()
496509 sys .stderr = outstream_factory (self .session , self .iopub_thread , "stderr" , echo = e_stderr )
510+
497511 if hasattr (sys .stderr , "_original_stdstream_copy" ):
498512 for handler in self .log .handlers :
499- if isinstance (handler , StreamHandler ) and (handler .stream .buffer .fileno () == 2 ):
513+ if (
514+ isinstance (handler , StreamHandler )
515+ and (buffer := getattr (handler .stream , "buffer" , None ))
516+ and (fileno := getattr (buffer , "fileno" , None ))
517+ and fileno () == sys .stderr ._original_stdstream_fd # type:ignore[attr-defined]
518+ ):
500519 self .log .debug ("Seeing logger to stderr, rerouting to raw filedescriptor." )
501-
502- handler .stream = TextIOWrapper (
503- FileIO (
504- sys .stderr ._original_stdstream_copy ,
505- "w" ,
506- )
520+ io_wrapper = TextIOWrapper (
521+ FileIO (sys .stderr ._original_stdstream_copy , "w" , closefd = False )
507522 )
523+ self ._log_map [id (io_wrapper )] = handler .stream
524+ handler .stream = io_wrapper
508525 if self .displayhook_class :
509526 displayhook_factory = import_item (str (self .displayhook_class ))
510527 self .displayhook = displayhook_factory (self .session , self .iopub_socket )
511528 sys .displayhook = self .displayhook
512529
513530 self .patch_io ()
514531
532+ def _save_io (self ):
533+ if not self ._io_modified :
534+ self ._original_io = sys .stdout , sys .stderr , sys .displayhook
535+ self ._log_map = {}
536+ self ._io_modified = True
537+
515538 def reset_io (self ):
516539 """restore original io
517540
518541 restores state after init_io
519542 """
520- sys .stdout = sys .__stdout__
521- sys .stderr = sys .__stderr__
522- sys .displayhook = sys .__displayhook__
543+ if not self ._io_modified :
544+ return
545+ stdout , stderr , displayhook = sys .stdout , sys .stderr , sys .displayhook
546+ sys .stdout , sys .stderr , sys .displayhook = self ._original_io
547+ self ._original_io = None
548+ self ._io_modified = False
549+ if finish_displayhook := getattr (displayhook , "finish_displayhook" , None ):
550+ finish_displayhook ()
551+ if hasattr (stderr , "_original_stdstream_copy" ):
552+ for handler in self .log .handlers :
553+ if orig_stream := self ._log_map .get (id (handler .stream )):
554+ self .log .debug ("Seeing modified logger, rerouting back to stderr" )
555+ handler .stream = orig_stream
556+ self ._log_map = None
557+ if self .outstream_class :
558+ outstream_factory = import_item (str (self .outstream_class ))
559+ if isinstance (stderr , outstream_factory ):
560+ stderr .close ()
561+ if isinstance (stdout , outstream_factory ):
562+ stdout .close ()
563+ if self ._blackhole :
564+ self ._blackhole .close ()
523565
524566 def patch_io (self ):
525567 """Patch important libraries that can't handle sys.stdout forwarding"""
0 commit comments