@@ -34,6 +34,22 @@ def __enter__(self):
3434 self .old_stdout = self .sys .stdout
3535 self .old_stderr = self .sys .stderr
3636
37+ # In Jupyter notebooks, ipykernel replaces sys.stdout/stderr with
38+ # OutStream objects that hold their own copy of the original fd in
39+ # _original_stdstream_copy. This bypasses our dup2 redirect, so we
40+ # need to point that copy at the real fd temporarily.
41+ # https://github.com/ipython/ipykernel/blob/912923542d55e6c80c0e7c1f94c648b52a225011/ipykernel/iostream.py#L618-L622
42+ self ._saved_stdout_copy = getattr (
43+ self .sys .stdout , "_original_stdstream_copy" , None
44+ )
45+ self ._saved_stderr_copy = getattr (
46+ self .sys .stderr , "_original_stdstream_copy" , None
47+ )
48+ if self ._saved_stdout_copy is not None :
49+ self .sys .stdout ._original_stdstream_copy = self .old_stdout_fileno_undup
50+ if self ._saved_stderr_copy is not None :
51+ self .sys .stderr ._original_stdstream_copy = self .old_stderr_fileno_undup
52+
3753 self .os .dup2 (outnull_file .fileno (), self .old_stdout_fileno_undup )
3854 self .os .dup2 (errnull_file .fileno (), self .old_stderr_fileno_undup )
3955
@@ -45,7 +61,6 @@ def __exit__(self, *_):
4561 if self .disable :
4662 return
4763
48- # Check if sys.stdout and sys.stderr have fileno method
4964 self .sys .stdout = self .old_stdout
5065 self .sys .stderr = self .old_stderr
5166
@@ -55,6 +70,12 @@ def __exit__(self, *_):
5570 self .os .close (self .old_stdout_fileno )
5671 self .os .close (self .old_stderr_fileno )
5772
73+ # Restore ipykernel's OutStream fd copies
74+ if self ._saved_stdout_copy is not None :
75+ self .sys .stdout ._original_stdstream_copy = self ._saved_stdout_copy
76+ if self ._saved_stderr_copy is not None :
77+ self .sys .stderr ._original_stdstream_copy = self ._saved_stderr_copy
78+
5879
5980class MetaSingleton (type ):
6081 """
0 commit comments