-
-
Notifications
You must be signed in to change notification settings - Fork 30.1k
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
Unexpected call to readline's add_history in call_readline #71057
Comments
I was implementing a REPL using the readline module and noticed that there are extraneous calls to readline's add_history function in call_readline1. This was a problem because there were some lines, that, based on their compositions, I might not want in the history. Figuring out why I was getting two entries for every The function call has been around ever since Python started supporting GNU Readline (first appeared in Python 1.4 or so, I believe)2. This behavior doesn't seem to be documented anywhere. I can't seem to find any code that depends on a line that is read in by call_readline to be added to the history. I guess the user might rely on the interactive interpreter to use the history feature. Beyond that, I can't think of any critical purpose for it. There are four potential workarounds:
I think that it's fair to say that none of the above options are desirable. So let's discuss potential solutions.
I think that the last option would be a pretty clean change that would cause the least number of issues (if any) for existing code bases. Regardless of the implementation details, I think that this would be the best route—to add a Python function called readline to the readline module. I would imagine that this would be an easy change/addition. I'm attaching a sample script that demonstrates the described issue. |
Solution 3 (documentation) can easily be done for existing versions of Python. Any other fix I think would have to be done in the next Python version (3.6). By solution 2 (dynamically swap histories), do you mean add an API to save and load the entire history list somewhere, like read/write_history_file(), but maybe to a Python list instead? A variation on solution 4 came to my mind: Add a global variable or function to the readline module to enable or disable automatic history addition. Or maybe a callback that is run after a line is entered, with it set to the current implementation by default. If I understand solution 5 (new version of call_readline), that also seems reasonable. I guess you mean to add a new function to replace the Python call to input()? A minor downside would be that the caller has to do extra work if it wants to use input() when the Readline library is unavailable. |
I agree about the documentation. It would only take a few minutes to do. In regard to your inquiry about the second solution, more or less, yes. I left that one a bit ambiguous since there are many ways to skin that cat. But in retrospect, I probably shouldn't have included that potential solution since it'd be a bit goofy and wouldn't necessarily be much different (in terms of code conciseness) than the third workaround that I mentioned. As for your suggestion about the fourth solution, I like that idea. I feel that it's actually more similar to the fifth solution than the fourth, but I feel that we're getting close to coming up with something that should be easy and effective. Lastly, in regard to the fifth solution, yes. But I see what you're saying about the downside. Yeah, that would be rather annoying. For a moment, I thought that it could fall back to standard IO in the absence of Readline/libedit, but that would be a bit misleading for a function in the readline module. Besides, input already does that anyway. I would imagine that in the vast majority of cases of using such a new function, the developer would fallback to input, because they'd likely prioritize getting the content from the user over ensuring Readline/libedit functionality. Since we already have a function that automatically does that, I think your suggestion to add a function to the readline module to enable/disable automatic history addition would be ideal. I'd be reluctant to use a global Python variable since it would be out of uniform with the rest of the members (i.e., functions) of the module. I like the idea of adding a set_auto_history(flag=True|False) or something to that effect. |
I couldn't think of a way to test input() with the unittest module without bypassing readline or requesting the input from the user. With that said, this informal script verifies proper behavior. |
I left a few minor comments in the code review. I agree automated testing would be awkward for Readline. It should be possible using a pseudoterminal (pty). In fact there is already very basic testing that does this in /Lib/test/test_builtin.py, class PtyTests. It only tests the input() prompt. I could have a go at writing a test. I guess pseudocode for a test would look a bit like: def run_pty(script):
[master, slave] = pty.openpty()
with subprocess.Popen(script, stdin=slave, stdout=slave, stderr=slave)
# Read and write concurrently like proc.communicate()
master.write(b"dummy input\r")
return slave.read()
template = """\
import readline
readline.set_auto_history({})
input()
print("History length:", readline.get_current_history_length())
"""
def test_auto_history_enabled(self):
output = run_session(template.format(True))
self.assertIn(b"History length: 1\n", output)
def test_auto_history_disabled(self):
output = run_session(template.format(False))
self.assertIn(b"History length: 0\n", output) |
Here is a patch including a unit test. I have only tested it on Linux. It would be awesome of other people could test it on other Unix platforms, in case the pseudoterminal stuff needs fixing. |
Works fine on Darwin (OS X Yosemite 10.10.5): drago:cpython meadori$ uname -a |
Thankyou for testing this Meador, it increases my confidence in my code. I’ll try to commit this soon if there are no objections. |
New changeset 4195fa81b188 by Martin Panter in branch 'default': |
New changeset 27a49daf7925 by Martin Panter in branch 'default': |
My test locked up on an OS X buildbot <http://buildbot.python.org/all/builders/AMD64%20Snow%20Leop%203.x/builds/4623/steps/test/logs/stdio\>: Timeout (0:15:00)! The parent was hanging as it exits the child proc context manager, waiting for the child to exit. Perhaps some exception happened in the parent before it could write to the child, so the child is still waiting on input. So I added code to close the master in case of exception, which may at least help understand the situation. |
I suppose the only thing that could be left is adding remarks in the documentations for previous versions. If I understand correctly, this would only be added to the documentations for Python 2.7 and 3.5. Is this correct? Since this is the first issue in which I've submitted a patch, I'm still quite new to the CPython development workflow; is there a special way to indicate that a patch should be applied to a branch other than default? Or is that done simply by informally indicating so in a message? |
Yes 3.5 and 2.7 are open for documentation fixes. As long as there are no major differences, it is usually easier to make one patch against 3.5 or 3.6, and then I can fix any minor differences when applying it to 2.7. |
New changeset b8b2c5cc7e9d by Martin Panter in branch 'default': |
Unfortunately, I think my debugging messages needed a flush() call to be effective when the deadlock happens. So the test is failing on three buildbots:
On the other hand, the test is passing on Free BSD, Open Indiana, AIX, and Linux (+ Meador’s OS X Yosemite). I think I might just skip the test on the problematic platforms, unless anyone has any suggestions for these other platforms: @unittest.skipIf(sys.platform.startswith(("darwin", "openbsd"))) |
New changeset 816e1fe72c1e by Martin Panter in branch 'default': |
New changeset daaead1dc3e0 by Martin Panter in branch 'default': |
Looks like I have ironed all the bugs out. These OS bugs/quirks have already been documented in the Python bug tracker:
I will leave this open for a documentation patch. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: