I recently ran into a problem where I was trying to automate unit testing for a function that paused, mid-execution, and waited for a user to input some value.
def dummy_fn(): name = input() return('Hello, ', name)
Nick ('Hello, ', 'Nick')
Simulating a user input wound up being a non-trivial thing to figure out, so I figured it beared writing a note involving:
- Temporarily overwriting
io.StringIO is used to convert your typical string into something that can be read as a stream of inputs, much like reading lines in a file.
For instance, imagine we’re trying to simulate loading a
from io import StringIO import csv
Here, the iterator is considering each unique character. Because every other “line” is “a comma that splits two empty strings” we get a bunch of nonsense.
for char in csv.reader('a,b,c,d,e'): print(char)
['a'] ['', ''] ['b'] ['', ''] ['c'] ['', ''] ['d'] ['', ''] ['e']
StringIO, we can tell python to read our input as one unified line.
f = csv.reader(StringIO('a,b,c,d,e')) for char in f: print(char)
['a', 'b', 'c', 'd', 'e']
sys.stdin is the default that gets called when you find yourself writing something that that uses the standard
input() function. It’s got this cryptic
TextIOWrapper for a repr, but it essentially takes whatever a user submits to standard in by typing and hitting Enter.
import sys sys.stdin
<_io.TextIOWrapper name='<stdin>' mode='r' encoding='cp1252'>
But if we inspect, this
TextIOWrapper inherits from the same base class as
Meaning we can leverage the same underlying functionality if we spoof a function designed to call
sys.stdin. In this case,
with StringIO('asdf') as f: stdin = sys.stdin sys.stdin = f print("'" + input() + "' wasn't actually typed at the command line") sys.stdin = stdin
'asdf' wasn't actually typed at the command line
(Note: Because Jupyter Notebooks use a different
stdin scheme, this, ironically, is just markdown. But running it in IPython or regular ol’ Python works just fine)
Putting it All Together
with StringIO('Nick') as f: stdin = sys.stdin sys.stdin = f dummy_fn() sys.stdin = stdin