-
-
Notifications
You must be signed in to change notification settings - Fork 30.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
pythonw should set sys.stdout
and sys.stdin
to valid objects.
#122633
Comments
The current behavior is documented. Maybe some applications or libraries depend on the documented behavior in order to handle the lack of standard I/O. |
Wouldn't it be better if by default |
That would still put the burden on every library maintainer ever to be aware that the standard pipes are not guaranteed to be there. Granted, the chance of it breaking would be less but it would still mean that any direct |
If Thinking about it it might be simplest to assign pythonws I haven't looked into the details of implementing this yet but if there is general support I would be willing to give it a go. |
Assuming that the |
Well, that's always the challenge with software and bugs/misfeatures. Obligatory xkcd reference: But anyway-- this only applies to windowed apps on Windows-- and it's Very unlikely that anyone is using "is None" to mean anything other than "is not a valid object" -- because there are other ways to check if an app is frozen, or a GUI app, or running in windows, etc. So if a user wants to something other than a devnull ( or equivalent) they are probably doing something else - or should be. Note: now that I think about it -- we should probably recommend an ETAFTP approach-- catch the Exception when trying to use it, rather than check for None. |
I'd be more worried about some less obvious side effect. To give an example, PyInstaller used to do a workaround akin to Obviously that particular error shouldn't apply to |
Documented behavior is not a bug. Changing such is usually a feature-request that requires strong justification and a deprecation process. pythonw.exe is Windows specific and intended mostly for GUI apps and their installers. For instance, CPython Windows installers use pythonw.exe in the IDLE icons and context menu entries it installs. IDLE itself, as an IDE, executes user code in contexts that connect the std streams to its GUI console. (That IDLE's handling of messages from IDLE itself could/should be improved is an IDLE issue, which would not be affected by the change proposed here) What current usage problem is the justification for a non-trivial change in python.exe? The similar issues with the 3rd-party cross-platform PyInstaller '--noconsole' command line option, which seems to have a somewhat different purpose, is not directly relevant here. Ditto for comments about macOS behavior. Maybe when the option is given, they should set the streams to devnull for users, but that is their call. Perhaps they do not because doing so is not as trivial as suggested here. |
We (I'm a PyInstaller maintainer) don't do it because Python doesn't do it. It would be trivial for us to but we're reluctant to deviate from how regular Python behaves, even if it would save us from a lot of invalid reports (write, flush). |
Note that this isn't just about "pythonw.exe", or any other GUI app that embeds the Python DLL on Windows. The process may lack standard I/O for various reasons. The standard handles could be implicitly inherited as On Unix systems, it's trivial to spawn a Python process that lacks one or more of the standard I/O files, though it's not normal to do so. For example:
|
No one said it is. I started this issue with that very sentence. Perhaps there's a better category for this issue -- I'm happy to re-categories it (though I can't see how) -- but this doesn't really feel like it rises to "feature request" exactly either.
Sure -- how to start that process than in an issue with a discussion like this? Sure, changing long-standing behavior could break folks' code -- and that shouldn't be done lightly. But we also could never make anything better, ever, if we were not willing to make some changes. So every time a change is suggested we have to balance the potential benefit and the potential damage. Finally (for the meta-conversation), maybe I'm just being pedantic or unimaginative, but I'd really appreciate it if folks could make the distinction between: Yes, this change would be better, but it's not worth breaking folks' code over. and The current behavior is a good (best?) option. Either way, nothing changes, but I think it's helpful, particularly if it ever comes up again to be clear about it -- so folks don't think they haven't been heard or understood. On to the actual topic: Yes, this is only about pythonw on Windows. But that doesn't mean that pythonw on MacOS (or Linux behavior) is irrelevant to the conversation -- I think it's a goal of Python to be as platform independent as possible, and at this point, folks can make, test, run, and distribute a desktop GUI app on the Mac, and then have the exact same code fail on Windows -- very annoying (this happened to me recently, hence this issue). I presume the reason for the current pythonw behavior is that there is no default But now, a few key points: First:
Setting them to In practice there are two cases for desktop apps that might use
So -- the proposed change would be better for some folks, and make no difference to others. In short setting the streams to I hope I've made the case that the proposed change would have been a better way to handle this in the first place -- but it is worth potentially breaking code to make the change now? As pointed out earlier in the thread the proposed change would break code that DOES care about the std streams, AND uses I suggest that that's a very small set of use-cases -- it's very hard to know for sure of course, but I'm having a hard time imagining an app author caring about where the std streams go, but ONLY if they're One thing I can imagine is a developer that is only testing on Windows -- then they may use the Even for those that do use this heuristic, it could be a fairly easy to find and fix if need be. I know it's better to not introduce a breaking change at all, but in fact, this isn't all that different than the situation now. My example: This came up for me with an app of mine that's been working fine for over 20 years. I just updated it with some small changes, and to use a recent Python version and libraries. First of all: updating to recent versions required a number of changes -- that shouldn't surprise anyone -- it's the cost of keeping current. But relevant to the thread, this issue suddenly arose for me, not because of anything I'd changed, but because one of the libraries I'm using introduced a use of (and it was hard for me to detect, because I was testing on a Mac) The point being -- making this change will introduce some bugs that need to be fixed, but it also will reduce some bugs as well. The fact is that poorly chosen defaults cause problems -- particularly when authors of the code that uses those defaults (e.g. library authors) is different from the person that actually has to override the defaults (window'd application authors). @eryksun wrote:
I don't understand the implications of this. if one starts up a python app in a process without standard I/O -- will Are you suggesting that they could be set to In short -- how does this pertain to this proposal?
Hmm -- unless someone does come up with a reason, "Python doesn't do it" because of legacy, and the desire not to make a breaking change. |
If any of The relevant code is in Lines 2569 to 2584 in 151934a
|
Thanks Eryk, that all makes sense. In most cases, having invalid (or non-existent) stdout and stderr is unusual, and in the hands of the person running the application -- if they are trying to run in an invalid environment, then it won't work, and the error they get should be clear. So all good. And folks writing the code can expect the std streams will be there. I do think it's odd that Windows doesn't have a default for these -- but I guess the expectation (and usual practice) is that the application will set things up the way it needs it. A bit of a throw back to the early days of development, when people wrote apps more from scratch ... In the case at hand, pythonw IS the application framework -- that's its intent, to provide a python interpreter set up to run GUI apps. It's the same on the Mac -- pythonw is customized to run GUI apps. On Linux, I think there's absolutely no difference, yes? Which is why I think it should, by default be set up to have valid io streams. Maybe not useful ones, but at least not invalid -- hence os.devnull. @GadgetSteve: I kinda like the logger approach, but it feels a bit heavyweight to me -- in effect, it's the same as Also -- in the spirit of platform independence, it's better not to set up a logger by default unless that was the case on all platforms. [*] -- Hmm, maybe then, the GUI application frameworks should set these up -- I use wxPython, I may look and see what we could add there. OK - I've reminded myself -- But I still think it's better for pythonw to do it, rather than each GUI framework doing it for you. |
I'm not totally against initialising I'm -1 on our default being anything other than a "drop everything" no-op implementation. If there's no stream connected, there's nothing sensible we can do with the output, so it's still up to the app to detect that scenario and replace the IO objects. But at least it won't error out. (I'd kind of like if we had a nice native initialization API to replace Footnotes
|
I 100% agree that the default behaviour should be a No-Op with no errors. I am wondering if it might be possible to have stdout & stderr connected to a null stream that also gives True for Either of these should allow all existing code with the correct checks to carry on as before while resolving the issue. Documenting this in the place(s) that it noticed by library developers is probably more of a challenge of course which is why I quite like the above ideas. Maybe trying to get a check for the |
I halfheartedly proposed python/typeshed#12485 but it looks like it'll hit a lot of (legitimately not pythonw-safe) projects so I'd be surprised if it gets accepted. |
Just to clarify I wasn't thinking of asking for a warning if the current check was missing but rather a "you don't necessarily need this anymore after python 3.x" message if the check was present.
Sent from Outlook for Android<https://aka.ms/AAb9ysg>
…________________________________
From: Brénainn Woodsend ***@***.***>
Sent: Monday, August 5, 2024 10:11:56 PM
To: python/cpython ***@***.***>
Cc: Steve (Gadget) Barnes ***@***.***>; Mention ***@***.***>
Subject: Re: [python/cpython] pythonw should set `sys.stdout` and `sys.stdin` to valid objects. (Issue #122633)
Maybe trying to get a check for the if std* is None added to tools such as pylint, ruff, etc., could be a way to go?
I halfheartedly proposed python/typeshed#12485<python/typeshed#12485> but it looks like it'll hit a lot of (legitimately not pythonw-safe) projects so I'd be surprised if it gets accepted.
—
Reply to this email directly, view it on GitHub<#122633 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/ABKVUWUWACSQWIWNIAHVX4DZP7TBXAVCNFSM6AAAAABL5MDQXOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENRZHEZDMOBUGM>.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
Hmm -- I had to look up what "We use MaybeNone in cases where something could possibly be None but we don't want to force all users to check for it." So yeah, I agree that if they could be None, and you want static type checking, then you code should fail the check if you don't handle None. I suspect that choice was made because they were thinking that they would only be None if the "host" system were misconfigured -- and they very much don't want every use of the stdio. streams to be wrapped with a None check. Which is the case, except for pythonw, in which case the None is being set by cPython code. Which is why I think it's a misfeature. |
Worse, it requires a special case for
You mean, except for cases where you don't have standard IO streams. It's not at all unique to And while making all the people who do type checking aware that they need to double-check these attributes (once, at startup, which AFAIK no type checkers can handle right now) would certainly go a long way towards making libraries less likely to assume them, I don't think it's the right way to approach it. But unfortunately, we can't exactly print a warning either. Possibly what we need is extra (platform-specific) handling in the final exception handler, so that an unhandled error gets displayed in some useful way even when |
Nor do I -- do we really think that every use case of stdout and stderr should be wrapped with the possibility of them not being valid? I think the assumption, by library authors, that they are valid is a reasonable one. Yes, if we can come up with a way to wrap the invalid streams with a smarter object, so that library authors don't need to do anything special, great. But until then, maybe we can make a much simpler change that will help some folks.
If you can solve the root cause, great. But
|
The standard I/O handle values of a process that's spawned for an executable that is not flagged as a console application, such as "pythonw.exe", default to The standard I/O handle values of a process that's spawned for an executable that is flagged as a console application, such as "python.exe", may be explicitly set to At process startup, if a standard I/O handle is The C runtime startup behavior is implemented by the function // This handle has not yet been initialized, so let's see if we can get
// the handle from the OS:
intptr_t const os_handle = reinterpret_cast<intptr_t>(GetStdHandle(get_std_handle_id(fh)));
bool const is_valid_handle =
os_handle != reinterpret_cast<intptr_t>(INVALID_HANDLE_VALUE) &&
os_handle != reinterpret_cast<intptr_t>(nullptr);
DWORD const handle_type = is_valid_handle
? GetFileType(reinterpret_cast<HANDLE>(os_handle))
: FILE_TYPE_UNKNOWN;
if (handle_type != FILE_TYPE_UNKNOWN)
{
// The file type is known, so we obtained a valid handle from the
// OS. Finish initializing the lowio file object for this handle,
// including the flag specifying whether this is a character device
// or a pipe:
pio->osfhnd = os_handle;
if ((handle_type & 0xff) == FILE_TYPE_CHAR)
pio->osfile |= FDEV;
else if ((handle_type & 0xff) == FILE_TYPE_PIPE)
pio->osfile |= FPIPE;
}
else
{
// We were unable to get the handles from the OS. For stdin, stdout,
// and stderr, if there is no valid OS handle, treat the CRT handle
// as being open in text mode on a device with _NO_CONSOLE_FILENO
// underlying it. We use this value instead of INVALID_HANDLE_VALUE
// to distinguish between a failure in opening a file and a program
// run without a console:
pio->osfile |= FDEV;
pio->osfhnd = _NO_CONSOLE_FILENO;
// Also update the corresponding stdio stream, unless stdio was
// already terminated:
if (__piob)
__piob[fh]->_file = _NO_CONSOLE_FILENO;
} |
Surely the way to handle the discrepancies in the handles are none <==> |
sys.stdin, sys.stdout and sys.stderr are also set to None on Unix if their file descriptor is closed, which is a common behavior when running a "daemon" process/service. Example:
I don't think that changing this behavior is a good idea. Too much code rely on the current assumption that standard streams are set to None if their file descriptor is set to None. |
We might (with a deprecation period) be able to substitute But I'm not sure it solves the original problem, which seems to swing somewhere between "there's no output when there's no output streams" and "things crash when there's no output streams". Fixing the second is easier, though it likely defers a problem to a later point, while the first is more valuable if we can do something useful there. We can't make an absent stdin do anything better than returning EOF, so it's just stdout and stderr that would benefit from being able to show the output. Maybe the most effective way to handle this would be to recommend/contribute to the GUI frameworks something that checks for |
well, there's also too much code that assumes that the standard streams are always valid :( But I'm only suggesting changing it for pythonw -- and I'm suggesting that there is very little code that, when run under pythonw, assumes that the None means anything other than "Not valid, might as well be os.devnull". My assessment may be wrong, of course, and there is surely at least some code that is using @eryksun: thanks for the detailed explanation -- clearly this is a pretty complex issue [*], but I don't think that changes my logic:
I think it's best that Python well supports the default, most likely use cases well. Again -- one could fully agree with me about this, and still think it's not enough gain for a breaking change -- fair enough, that's always a tough call. [*] I do note that C has the _NO_CONSOLE_FILENO sentinel -- too bad we don't have a Python equivalent -- the challenge with None is that can mean too many things :-( |
The question here though is what are they doing instead? This scenario is the most likely to have another framework in play that could check and replace |
Sure -- wxPython has that as an option, and I've jsut started an issue suggesting that the default be changed to os.devnull. But my point is that pythonw IS a nano GUI framework, and it is the "framework" on which the other frameworks build on -- so it should have the best-we-can-have default behavior. So here's a non-breaking change idea: Add a start-up flag for Python: SET_INVALID_IO_STREAMS_TO_DEVNULL: False by default, if True, then invalid IO Streams will be set to os/devnull. If this is there:
Potentially, after a deprecation (is that the right word?) period , the default could be changes to True -- at least for pythonw. Or not. And yes -- this is what @GadgetSteve suggested right up front :-) |
They are -- certainly in most cases, I'm having trouble imagining an app designed to be run with a point-and-click-on-the-gui that doesn't either:
However, the GUI frameworks may well provide a way to handle this, but at least one (pythonw) doesn't do it by default. And this comes up in the PyInstaller list. enough that I think it's common enough problem (given that the pool of GUI app developers is small as is). Anyone know what tkInter does? But my point is that python itself (at least pythonw) should do the right thing, rather than expecting all the downstream libs (OK -- it's a pretty small list, but ...) to handle it. |
Okay, except it's not :) It's an executable with the system flag that says "don't create or attach a console" set, since there's no feasible way for a regular user to do that themselves. Code-wise it is literally identical to python.exe. I'm not totally opposed to a |
me too -- I didn't mean to suggest that. if it's a flag or environment variable, it should be usable everywhere. |
What exactly are these use cases where a piece of code would do something differently based on a if sys.stdout is not None:
print("some output")
else:
# The user can't see what we print. Better display it some other way...
something_involving_log_files_or_pop_up_dialogs("some output") ... but that doesn't even work on Linux or macOS since a GUI application launched by the desktop still has valid A search for |
The "something different" is only really going to be deciding to set it to something else. If it's already set, that indicates that the caller has overridden it in some way, and if not then it's up to the app to pick a reasonable default (such as devnull if it doesn't matter, or some GUI element or log file if it does). If someone's log files disappear because we change this, they won't be very happy, and it will be our fault. |
But why would someone selectively attach |
I hate to argue against my own point, but: someone would selectively attach sys.stdout to a log file based on a conditional that so inaccurately reflects whether the current sys.stdout is going anywhere readable because that's the only obvious way to do it with the current code base. None means "invalid stream" -- and it's pretty standard practice in Python to use that sentinel to switch on: If there's a valid stream, the user set it up the way they want, if not, I'd better set it up to a log file. Which is why I'm only advocating for changing it in pythonw -- in that case, it's far less likely that application writers are doing anything with "is None" than making they error go away. All the being said -- I suspect it FAR more common for library writers to assume they will be valid, and top-level app writers to set up a redirect to logfile or whatever in a more robust way. |
Being able to argue against your own point is a very good thing :) You still get to dismiss the argument. And you're spot on. The conditional is literally "is stdout currently valid; if not, I'll make it valid", so there's nothing inaccurate about it. Inaccurate conditionals would be "am I running under pythonw.exe? If so, sys.stdout must be invalid" or the inverse: "if sys.stdout None? If so, I must be running under pythonw.exe". So as long as we're not encouraging that line of thinking, we won't be making things worse.
Yeah, and this is the core point. The choice we have to make is whether to make this assumption more likely, more obvious, or always correct. I'm of the opinion that "more obvious" is the only option, because part of the assumption is that anything written to stdout will be seen by the user, which we can't achieve consistently when there's no console available. |
Hmm, fair enough. Although I still think we'd be fixing way more problems than we'd create by making this change. Like I said before, the github search shows many many cases of people being burnt by the current model and not a single case that would lose its log file or be otherwise negatively affected by this change. |
GitHub searches are a biased sample, so they only get weighed carefully (though I didn't have to go far to find one that would break). What's more important is what we've told people to do in the past: 58cb1b8
I haven't tracked down the original discussion from 17 years ago, but the decision is clear enough. So rather than just looking at code that people have published publicly in, really, the last few years, we need to weigh that for almost two decades, the behaviour has been for these to be None. So what we need to do is figure out how to let people know that this is changing, bearing in mind that many apps are closed source, many are going to skip versions, and most already work today and shouldn't stop working just to make it so other peoples' new code appears to work first time. The challenge is the transition plan, not coming up with strong enough rationale. |
In macOS Terminal: |
The difference between a command line app and a GUI app on the Mac is that a GUI app must be run from "within an application bundle". My information is old, but back in the day, the "Framework Build" for Mac (the one distributed by python.org) had a hack to the python executable that made it appear to be inside an application bundle. It was decided that there was no point in having a separate python executable, as there was essentially no cost to the redirection. So yes, pythonw and python are the same thing on a Framework build on the Mac. But they are BOTH "gui apps" as opposed to both being "console apps" (which I suppose is the case on Linux, but for the opposite reason) The core problem here is that Windows doesn't have a default console for GUI apps -- the Mac does, so on the Mac, there's nothing special to be done for the std streams for GUI apps. @ned-deily: Is this all the same as it once was? NOTE: the python builds for the Mac on conda-forge DO have a separate pythonw executable -- the "regular" Python is a straight *nix build, and won't work with GUI apps -- there is a separate python.app package that you need to get an executable you can run a GUI app with. |
Sorry, I haven't been following this issue closely and I am not sure how relevant my reply will be because I'm not intimately familiar with the differences between AFAIK, it is true that, for python3, on non-Windows platforms built using the cpython There is a separate issue of what happens when that program uses a GUI framework to create its own windows, like using Tk in a python tkinter application, including IDLE. Typically, on non-Windows platforms, IDLE can be launched from a terminal window (using with one of the Some Python distributions, including the python.org macOS installer, provide an additional way to launch IDLE; for macOS, there may be an IDLE.app macOS application with a clickable icon. When IDLE is launched this way, there is no terminal or Unix shell involved and thus the python interpreter's process does not have a usable stdin or stdout and stderr is directed by the OS to the system log (I believe). So some relevant messages may not be obvious to the user (see related issue #57791). Also, on macOS, I believe that whether or not python is built as a Hope that helps! |
Thanks @ned-deily! Am I correct that the "python" executable in the current python.org build still have the hack that lets it run as a GUI app? i.e. it's "inside an app bundle"? |
You are correct but, as I noted in my edited comment, I don't think that is relevant to this issue; it should have no impact on stdin, stdout, and stderr usage and behavior. |
Thanks Ned. This means that (paraphrased) "IDLE cannot import tkinter. Bye" should go to stderr, not stdout. I should check. |
Bug report
Bug description:
So not exactly a bug -- but I wouldn't call it a feature request either, so I filed it as a bug.
The issue is that
pythonw
on Windows setssys.stdout
andsys.stdin
toNone
.This is problematic because while, e.g.
print()
will not fail, if not do anything useful, other things like warnings and logging, etc., may try to usestdout
orstderr
, and they will then fail.See #107792 and #57791
So one might say: "don't do that" -- fair enough, except the user suffering from the problem may not be the one that wrote the code that's calling
stderr
-- it could be in a library, for instance. Library writers are often not thinking about windowed apps, and shouldn't have to do something special to accommodate them in any case.See this note in the PyInstaller docs:
https://pyinstaller.org/en/stable/common-issues-and-pitfalls.html?highlight=os.devnull#sys-stdin-sys-stdout-and-sys-stderr-in-noconsole-windowed-applications-windows-only
In which the solution is:
It's not unreasonable for library authors to expect that
stdout
andstderr
to be valid.So couldn't
pythonw
add that code by default? users could override it themselves, of course.NOTE: tested recently on py3.12 -- but this has been an issue for quite some time.
NOTE2: this isn't an issue on the Mac, because its pythonw redirects to the system off-screen console.
CPython versions tested on:
3.12
Operating systems tested on:
Windows
The text was updated successfully, but these errors were encountered: