Function foo prints to console. I want to test the console print. How can I achieve this in python?
Need to test this function, has NO return statement :
def foo(inStr):
print "hi"+inStr
My test :
def test_foo():
cmdProcess = subprocess.Popen(foo("test"), stdout=subprocess.PIPE)
cmdOut = cmdProcess.communicate()[0]
self.assertEquals("hitest", cmdOut)
You can easily capture standard output by just temporarily redirecting sys.stdout to a StringIO object, as follows:
import StringIO
import sys
def foo(inStr):
print "hi"+inStr
def test_foo():
capturedOutput = StringIO.StringIO() # Create StringIO object
sys.stdout = capturedOutput # and redirect stdout.
foo('test') # Call unchanged function.
sys.stdout = sys.__stdout__ # Reset redirect.
print 'Captured', capturedOutput.getvalue() # Now works as before.
test_foo()
The output of this program is:
Captured hitest
showing that the redirection successfully captured the output and that you were able to restore the output stream to what it was before you began the capture.
Note that the code above in for Python 2.7, as the question indicates. Python 3 is slightly different:
import io
import sys
def foo(inStr):
print ("hi"+inStr)
def test_foo():
capturedOutput = io.StringIO() # Create StringIO object
sys.stdout = capturedOutput # and redirect stdout.
foo('test') # Call function.
sys.stdout = sys.__stdout__ # Reset redirect.
print ('Captured', capturedOutput.getvalue()) # Now works as before.
test_foo()
This Python 3 answer uses unittest.mock. It also uses a reusable helper method assert_stdout, although this helper is specific to the function being tested.
import io
import unittest
import unittest.mock
from .solution import fizzbuzz
class TestFizzBuzz(unittest.TestCase):
#unittest.mock.patch('sys.stdout', new_callable=io.StringIO)
def assert_stdout(self, n, expected_output, mock_stdout):
fizzbuzz(n)
self.assertEqual(mock_stdout.getvalue(), expected_output)
def test_only_numbers(self):
self.assert_stdout(2, '1\n2\n')
Note that the mock_stdout arg is passed automatically by the unittest.mock.patch decorator to the assert_stdout method.
A general-purpose TestStdout class, possibly a mixin, can in principle be derived from the above.
For those using Python ≥3.4, contextlib.redirect_stdout also exists, but it seems to serve no benefit over unittest.mock.patch.
If you happen to use pytest, it has builtin output capturing. Example (pytest-style tests):
def eggs():
print('eggs')
def test_spam(capsys):
eggs()
captured = capsys.readouterr()
assert captured.out == 'eggs\n'
You can also use it with unittest test classes, although you need to passthrough the fixture object into the test class, for example via an autouse fixture:
import unittest
import pytest
class TestSpam(unittest.TestCase):
#pytest.fixture(autouse=True)
def _pass_fixtures(self, capsys):
self.capsys = capsys
def test_eggs(self):
eggs()
captured = self.capsys.readouterr()
self.assertEqual('eggs\n', captured.out)
Check out Accessing captured output from a test function for more info.
You can also use the mock package as shown below, which is an example from
https://realpython.com/lessons/mocking-print-unit-tests.
from mock import patch
def greet(name):
print('Hello ', name)
#patch('builtins.print')
def test_greet(mock_print):
# The actual test
greet('John')
mock_print.assert_called_with('Hello ', 'John')
greet('Eric')
mock_print.assert_called_with('Hello ', 'Eric')
The answer of #Acumenus says:
It also uses a reusable helper method assert_stdout, although this helper is specific to the function being tested.
the bold part seems a big drawback, thus I would do the following instead:
# extend unittest.TestCase with new functionality
class TestCase(unittest.TestCase):
def assertStdout(self, expected_output):
return _AssertStdoutContext(self, expected_output)
# as a bonus, this syntactical sugar becomes possible:
def assertPrints(self, *expected_output):
expected_output = "\n".join(expected_output) + "\n"
return _AssertStdoutContext(self, expected_output)
class _AssertStdoutContext:
def __init__(self, testcase, expected):
self.testcase = testcase
self.expected = expected
self.captured = io.StringIO()
def __enter__(self):
sys.stdout = self.captured
return self
def __exit__(self, exc_type, exc_value, tb):
sys.stdout = sys.__stdout__
captured = self.captured.getvalue()
self.testcase.assertEqual(captured, self.expected)
this allows for the much nicer and much more re-usable:
# in a specific test case, the new method(s) can be used
class TestPrint(TestCase):
def test_print1(self):
with self.assertStdout("test\n"):
print("test")
by using a straight forward context manager. (It might also be desirable to append "\n" to expected_output since print() adds a newline by default. See next example...)
Furthermore, this very nice variant (for an arbitrary number of prints!)
def test_print2(self):
with self.assertPrints("test1", "test2"):
print("test1")
print("test2")
is possible now.
You can also capture the standard output of a method using contextlib.redirect_stdout:
import unittest
from contextlib import redirect_stdout
from io import StringIO
class TestMyStuff(unittest.TestCase):
# ...
def test_stdout(self):
with redirect_stdout(StringIO()) as sout:
my_command_that_prints_to_stdout()
# the stream replacing `stdout` is available outside the `with`
# you may wish to strip the trailing newline
retval = sout.getvalue().rstrip('\n')
# test the string captured from `stdout`
self.assertEqual(retval, "whatever_retval_should_be")
Gives you a locally scoped solution. It is also possible to capture the standard error using contextlib.redirect_stderr().
Another variant is leaning on the logging module rather than print(). This module also has a suggestion of when to use print in the documentation:
Display console output for ordinary usage of a command line script or program
PyTest has built-in support for testing logging messages.
Related
I have a console program written in Python. It asks the user questions using the command:
some_input = input('Answer the question:', ...)
How would I test a function containing a call to input using pytest?
I wouldn't want to force a tester to input text many many times only to finish one test run.
As The Compiler suggested, pytest has a new monkeypatch fixture for this. A monkeypatch object can alter an attribute in a class or a value in a dictionary, and then restore its original value at the end of the test.
In this case, the built-in input function is a value of python's __builtins__ dictionary, so we can alter it like so:
def test_something_that_involves_user_input(monkeypatch):
# monkeypatch the "input" function, so that it returns "Mark".
# This simulates the user entering "Mark" in the terminal:
monkeypatch.setattr('builtins.input', lambda _: "Mark")
# go about using input() like you normally would:
i = input("What is your name?")
assert i == "Mark"
You should probably mock the built-in input function, you can use the teardown functionality provided by pytest to revert back to the original input function after each test.
import module # The module which contains the call to input
class TestClass:
def test_function_1(self):
# Override the Python built-in input method
module.input = lambda: 'some_input'
# Call the function you would like to test (which uses input)
output = module.function()
assert output == 'expected_output'
def test_function_2(self):
module.input = lambda: 'some_other_input'
output = module.function()
assert output == 'another_expected_output'
def teardown_method(self, method):
# This method is being called after each test case, and it will revert input back to original function
module.input = input
A more elegant solution would be to use the mock module together with a with statement. This way you don't need to use teardown and the patched method will only live within the with scope.
import mock
import module
def test_function():
with mock.patch.object(__builtins__, 'input', lambda: 'some_input'):
assert module.function() == 'expected_output'
You can replace sys.stdin with some custom Text IO, like input from a file or an in-memory StringIO buffer:
import sys
class Test:
def test_function(self):
sys.stdin = open("preprogrammed_inputs.txt")
module.call_function()
def setup_method(self):
self.orig_stdin = sys.stdin
def teardown_method(self):
sys.stdin = self.orig_stdin
this is more robust than only patching input(), as that won't be sufficient if the module uses any other methods of consuming text from stdin.
This can also be done quite elegantly with a custom context manager
import sys
from contextlib import contextmanager
#contextmanager
def replace_stdin(target):
orig = sys.stdin
sys.stdin = target
yield
sys.stdin = orig
And then just use it like this for example:
with replace_stdin(StringIO("some preprogrammed input")):
module.call_function()
This can be done with mock.patch and with blocks in python3.
import pytest
import mock
import builtins
"""
The function to test (would usually be loaded
from a module outside this file).
"""
def user_prompt():
ans = input('Enter a number: ')
try:
float(ans)
except:
import sys
sys.exit('NaN')
return 'Your number is {}'.format(ans)
"""
This test will mock input of '19'
"""
def test_user_prompt_ok():
with mock.patch.object(builtins, 'input', lambda _: '19'):
assert user_prompt() == 'Your number is 19'
The line to note is mock.patch.object(builtins, 'input', lambda _: '19'):, which overrides the input with the lambda function. Our lambda function takes in a throw-away variable _ because input takes in an argument.
Here's how you could test the fail case, where user_input calls sys.exit. The trick here is to get pytest to look for that exception with pytest.raises(SystemExit).
"""
This test will mock input of 'nineteen'
"""
def test_user_prompt_exit():
with mock.patch.object(builtins, 'input', lambda _: 'nineteen'):
with pytest.raises(SystemExit):
user_prompt()
You should be able to get this test running by copy and pasting the above code into a file tests/test_.py and running pytest from the parent dir.
Since I need the input() call to pause and check my hardware status LEDs, I had to deal with the situation without mocking. I used the -s flag.
python -m pytest -s test_LEDs.py
The -s flag essentially means: shortcut for --capture=no.
You can do it with mock.patch as follows.
First, in your code, create a dummy function for the calls to input:
def __get_input(text):
return input(text)
In your test functions:
import my_module
from mock import patch
#patch('my_module.__get_input', return_value='y')
def test_what_happens_when_answering_yes(self, mock):
"""
Test what happens when user input is 'y'
"""
# whatever your test function does
For example if you have a loop checking that the only valid answers are in ['y', 'Y', 'n', 'N'] you can test that nothing happens when entering a different value instead.
In this case we assume a SystemExit is raised when answering 'N':
#patch('my_module.__get_input')
def test_invalid_answer_remains_in_loop(self, mock):
"""
Test nothing's broken when answer is not ['Y', 'y', 'N', 'n']
"""
with self.assertRaises(SystemExit):
mock.side_effect = ['k', 'l', 'yeah', 'N']
# call to our function asking for input
I don't have enough points to comment, but this answer: https://stackoverflow.com/a/55033710/10420225
doesn't work if you just copy/pasta.
Part One
For Python3, import mock doesn't work.
You need import unittest.mock and call it as unittest.mock.patch.object(), or from unittest import mock mock.patch.object()...
If using Python3.3+ the above should "just work". If using Python3.3- you need to pip install mock. See this answer for more info: https://stackoverflow.com/a/11501626/10420225
Part Two
Also, if you want to make this example more realistic, i.e. importing the function from outside the file and using it, there's more assembly required.
This is general directory structure we'll use
root/
src/prompt_user.py
tests/test_prompt_user.py
If function in external file
# /root/src/prompt_user.py
def user_prompt():
ans = input("Enter a number: ")
try:
float(ans)
except:
import sys
sys.exit("NaN")
return "Your number is {}".format(ans)
# /root/tests/test_prompt_user.py
import pytest
from unittest import mock
import builtins
from prompt_user import user_prompt
def test_user_prompt_ok():
with mock.patch.object(builtins, "input", lambda _: "19"):
assert user_prompt() == "Your number is 19"
If function in a class in external file
# /root/src/prompt_user.py
class Prompt:
def user_prompt(self):
ans = input("Enter a number: ")
try:
float(ans)
except:
import sys
sys.exit("NaN")
return "Your number is {}".format(ans)
# /root/tests/test_prompt_user.py
import pytest
from unittest import mock
import builtins
from mocking_test import Prompt
def test_user_prompt_ok():
with mock.patch.object(builtins, "input", lambda _: "19"):
assert Prompt.user_prompt(Prompt) == "Your number is 19"
Hopefully this helps people a bit more. I find these very simple examples almost useless because it leaves a lot out for real world use cases.
Edit: If you run into pytest import issues when running from external files, I would recommend looking over this answer: PATH issue with pytest 'ImportError: No module named YadaYadaYada'
A different alternative that does not require using a lambda function and provides more control during the tests is to use the mock decorator from the standard unittest module.
It also has the additional advantage of patching just where the object (i.e. input) is looked up, which is the recommended strategy.
# path/to/test/module.py
def my_func():
some_input = input('Answer the question:')
return some_input
# tests/my_tests.py
from unittest import mock
from path.to.test.module import my_func
#mock.patch("path.to.test.module.input")
def test_something_that_involves_user_input(mock_input):
mock_input.return_value = "This is my answer!"
assert my_func() == "This is my answer!"
mock_input.assert_called_once() # Optionally check one and only one call
The simplest way that works without mocking and easily in doctest for lightweight testing, is just making the input_function a parameter to your function and passing in this FakeInput class with the appropriate list of inputs that you want:
class FakeInput:
def __init__(self, input):
self.input = input
self.index = 0
def __call__(self):
line = self.input[self.index % len(self.input)]
self.index += 1
return line
Here is an example usage to test some functions using the input function:
import doctest
class FakeInput:
def __init__(self, input):
self.input = input
self.index = 0
def __call__(self):
line = self.input[self.index % len(self.input)]
self.index += 1
return line
def add_one_to_input(input_func=input):
"""
>>> add_one_to_input(FakeInput(['1']))
2
"""
return int(input_func()) + 1
def add_inputs(input_func=input):
"""
>>> add_inputs(FakeInput(['1', '5']))
6
"""
return int(input_func()) + int(input_func())
def return_ten_inputs(input_func=input):
"""
>>> return_ten_inputs(FakeInput(['1', '5', '7']))
[1, 5, 7, 1, 5, 7, 1, 5, 7, 1]
"""
return [int(input_func()) for _ in range(10)]
def print_4_inputs(input_func=input):
"""
>>> print_4_inputs(FakeInput(['1', '5', '7']))
1
5
7
1
"""
for i in range(4):
print(input_func())
if __name__ == '__main__':
doctest.testmod()
This also makes your functions more general so you can easily change them to take input from a file rather than the keyboard.
You can also use environment variables in your test code. For example if you want to give path as argument you can read env variable and set default value if it's missing.
import os
...
input = os.getenv('INPUT', default='inputDefault/')
Then start with default argument
pytest ./mytest.py
or with custom argument
INPUT=newInput/ pytest ./mytest.py
Not sure how I would accomplish overriding the print('something') function do do something else in the class I am in.
For example, I have the following code:
import app
from app.helper import ws_send
class TemplateSubJob:
def __init__(self, sessiondata, sid, payload):
self.session = sessiondata
self.sid = sid
self.payload = payload
def startme(self):
ws_send(self.sid, self.payload, 'Send some output to the user...')
ws_send(self.sid, self.payload, 'Send something else to the user...')
print('test')
return b'xlsx_bytes_output'
I want to override the function print('something') to take what is passed and do something with it.
In my case I want to create a print function that does what ws_send() is doing, except only take a string.
Something like the following:
def print(string):
ws_send(self.sid, self.payload, string)
print('now i am being sent through ws_send instead of stdout')
How can I accomplish this?
UPDATE:
The reasoning for this is so anyone who is adding code to mine, does not need to modify their code or script to use my functions. I can hijack the print function that they are already using.
You can overload the print function with the following syntax:
from __future__ import print_function
try:
# python2
import __builtin__
except ImportError:
# python3
import builtins as __builtin__
def print(*args, **kwargs):
__builtin__.print('New print function')
return __builtin__.print(*args, **kwargs)
E: Fixed bad import, as pointed out in the comment
So I'm not going to go over the why you're using a print statement in that given case, but for Python 3, within your class description
class TemplateSubJob:
def __init(self, ):
# and other methods
def __str__(self, ):
return 'String description here'
which will return the given string when someone tries to print the given object. For example, when I instantiate, I can then call the print function directly following the instantiation, which will return any strings returned by the str function above
myobject = TemplateSubJob()
print(myobject)
For redirecting print to variable(string), use this:
from io import StringIO # Python2 use: from cStringIO import StringIO
import sys
old_stdout = sys.stdout
sys.stdout = mystdout = StringIO()
# blah blah lots of code ...
sys.stdout = old_stdout
# examine mystdout.getvalue()
Source: https://stackoverflow.com/a/1218951/4718434
For redirect it to file, use this:
https://stackoverflow.com/a/4675744/4718434
I have a console program written in Python. It asks the user questions using the command:
some_input = input('Answer the question:', ...)
How would I test a function containing a call to input using pytest?
I wouldn't want to force a tester to input text many many times only to finish one test run.
As The Compiler suggested, pytest has a new monkeypatch fixture for this. A monkeypatch object can alter an attribute in a class or a value in a dictionary, and then restore its original value at the end of the test.
In this case, the built-in input function is a value of python's __builtins__ dictionary, so we can alter it like so:
def test_something_that_involves_user_input(monkeypatch):
# monkeypatch the "input" function, so that it returns "Mark".
# This simulates the user entering "Mark" in the terminal:
monkeypatch.setattr('builtins.input', lambda _: "Mark")
# go about using input() like you normally would:
i = input("What is your name?")
assert i == "Mark"
You should probably mock the built-in input function, you can use the teardown functionality provided by pytest to revert back to the original input function after each test.
import module # The module which contains the call to input
class TestClass:
def test_function_1(self):
# Override the Python built-in input method
module.input = lambda: 'some_input'
# Call the function you would like to test (which uses input)
output = module.function()
assert output == 'expected_output'
def test_function_2(self):
module.input = lambda: 'some_other_input'
output = module.function()
assert output == 'another_expected_output'
def teardown_method(self, method):
# This method is being called after each test case, and it will revert input back to original function
module.input = input
A more elegant solution would be to use the mock module together with a with statement. This way you don't need to use teardown and the patched method will only live within the with scope.
import mock
import module
def test_function():
with mock.patch.object(__builtins__, 'input', lambda: 'some_input'):
assert module.function() == 'expected_output'
You can replace sys.stdin with some custom Text IO, like input from a file or an in-memory StringIO buffer:
import sys
class Test:
def test_function(self):
sys.stdin = open("preprogrammed_inputs.txt")
module.call_function()
def setup_method(self):
self.orig_stdin = sys.stdin
def teardown_method(self):
sys.stdin = self.orig_stdin
this is more robust than only patching input(), as that won't be sufficient if the module uses any other methods of consuming text from stdin.
This can also be done quite elegantly with a custom context manager
import sys
from contextlib import contextmanager
#contextmanager
def replace_stdin(target):
orig = sys.stdin
sys.stdin = target
yield
sys.stdin = orig
And then just use it like this for example:
with replace_stdin(StringIO("some preprogrammed input")):
module.call_function()
This can be done with mock.patch and with blocks in python3.
import pytest
import mock
import builtins
"""
The function to test (would usually be loaded
from a module outside this file).
"""
def user_prompt():
ans = input('Enter a number: ')
try:
float(ans)
except:
import sys
sys.exit('NaN')
return 'Your number is {}'.format(ans)
"""
This test will mock input of '19'
"""
def test_user_prompt_ok():
with mock.patch.object(builtins, 'input', lambda _: '19'):
assert user_prompt() == 'Your number is 19'
The line to note is mock.patch.object(builtins, 'input', lambda _: '19'):, which overrides the input with the lambda function. Our lambda function takes in a throw-away variable _ because input takes in an argument.
Here's how you could test the fail case, where user_input calls sys.exit. The trick here is to get pytest to look for that exception with pytest.raises(SystemExit).
"""
This test will mock input of 'nineteen'
"""
def test_user_prompt_exit():
with mock.patch.object(builtins, 'input', lambda _: 'nineteen'):
with pytest.raises(SystemExit):
user_prompt()
You should be able to get this test running by copy and pasting the above code into a file tests/test_.py and running pytest from the parent dir.
Since I need the input() call to pause and check my hardware status LEDs, I had to deal with the situation without mocking. I used the -s flag.
python -m pytest -s test_LEDs.py
The -s flag essentially means: shortcut for --capture=no.
You can do it with mock.patch as follows.
First, in your code, create a dummy function for the calls to input:
def __get_input(text):
return input(text)
In your test functions:
import my_module
from mock import patch
#patch('my_module.__get_input', return_value='y')
def test_what_happens_when_answering_yes(self, mock):
"""
Test what happens when user input is 'y'
"""
# whatever your test function does
For example if you have a loop checking that the only valid answers are in ['y', 'Y', 'n', 'N'] you can test that nothing happens when entering a different value instead.
In this case we assume a SystemExit is raised when answering 'N':
#patch('my_module.__get_input')
def test_invalid_answer_remains_in_loop(self, mock):
"""
Test nothing's broken when answer is not ['Y', 'y', 'N', 'n']
"""
with self.assertRaises(SystemExit):
mock.side_effect = ['k', 'l', 'yeah', 'N']
# call to our function asking for input
I don't have enough points to comment, but this answer: https://stackoverflow.com/a/55033710/10420225
doesn't work if you just copy/pasta.
Part One
For Python3, import mock doesn't work.
You need import unittest.mock and call it as unittest.mock.patch.object(), or from unittest import mock mock.patch.object()...
If using Python3.3+ the above should "just work". If using Python3.3- you need to pip install mock. See this answer for more info: https://stackoverflow.com/a/11501626/10420225
Part Two
Also, if you want to make this example more realistic, i.e. importing the function from outside the file and using it, there's more assembly required.
This is general directory structure we'll use
root/
src/prompt_user.py
tests/test_prompt_user.py
If function in external file
# /root/src/prompt_user.py
def user_prompt():
ans = input("Enter a number: ")
try:
float(ans)
except:
import sys
sys.exit("NaN")
return "Your number is {}".format(ans)
# /root/tests/test_prompt_user.py
import pytest
from unittest import mock
import builtins
from prompt_user import user_prompt
def test_user_prompt_ok():
with mock.patch.object(builtins, "input", lambda _: "19"):
assert user_prompt() == "Your number is 19"
If function in a class in external file
# /root/src/prompt_user.py
class Prompt:
def user_prompt(self):
ans = input("Enter a number: ")
try:
float(ans)
except:
import sys
sys.exit("NaN")
return "Your number is {}".format(ans)
# /root/tests/test_prompt_user.py
import pytest
from unittest import mock
import builtins
from mocking_test import Prompt
def test_user_prompt_ok():
with mock.patch.object(builtins, "input", lambda _: "19"):
assert Prompt.user_prompt(Prompt) == "Your number is 19"
Hopefully this helps people a bit more. I find these very simple examples almost useless because it leaves a lot out for real world use cases.
Edit: If you run into pytest import issues when running from external files, I would recommend looking over this answer: PATH issue with pytest 'ImportError: No module named YadaYadaYada'
A different alternative that does not require using a lambda function and provides more control during the tests is to use the mock decorator from the standard unittest module.
It also has the additional advantage of patching just where the object (i.e. input) is looked up, which is the recommended strategy.
# path/to/test/module.py
def my_func():
some_input = input('Answer the question:')
return some_input
# tests/my_tests.py
from unittest import mock
from path.to.test.module import my_func
#mock.patch("path.to.test.module.input")
def test_something_that_involves_user_input(mock_input):
mock_input.return_value = "This is my answer!"
assert my_func() == "This is my answer!"
mock_input.assert_called_once() # Optionally check one and only one call
The simplest way that works without mocking and easily in doctest for lightweight testing, is just making the input_function a parameter to your function and passing in this FakeInput class with the appropriate list of inputs that you want:
class FakeInput:
def __init__(self, input):
self.input = input
self.index = 0
def __call__(self):
line = self.input[self.index % len(self.input)]
self.index += 1
return line
Here is an example usage to test some functions using the input function:
import doctest
class FakeInput:
def __init__(self, input):
self.input = input
self.index = 0
def __call__(self):
line = self.input[self.index % len(self.input)]
self.index += 1
return line
def add_one_to_input(input_func=input):
"""
>>> add_one_to_input(FakeInput(['1']))
2
"""
return int(input_func()) + 1
def add_inputs(input_func=input):
"""
>>> add_inputs(FakeInput(['1', '5']))
6
"""
return int(input_func()) + int(input_func())
def return_ten_inputs(input_func=input):
"""
>>> return_ten_inputs(FakeInput(['1', '5', '7']))
[1, 5, 7, 1, 5, 7, 1, 5, 7, 1]
"""
return [int(input_func()) for _ in range(10)]
def print_4_inputs(input_func=input):
"""
>>> print_4_inputs(FakeInput(['1', '5', '7']))
1
5
7
1
"""
for i in range(4):
print(input_func())
if __name__ == '__main__':
doctest.testmod()
This also makes your functions more general so you can easily change them to take input from a file rather than the keyboard.
You can also use environment variables in your test code. For example if you want to give path as argument you can read env variable and set default value if it's missing.
import os
...
input = os.getenv('INPUT', default='inputDefault/')
Then start with default argument
pytest ./mytest.py
or with custom argument
INPUT=newInput/ pytest ./mytest.py
Are there any built-in ways to have different threads have different destinations for print() and similar?
I'm exploring the creation of an interactive Python environment, so I can't just use print() from module spamegg. It has to be the globally available one with no arguments.
You can replace sys.stdout with an object that checks the current thread and writes to the appropriate file:
import sys, threading
class CustomOutput(object):
def __init__(self):
# the "softspace" slot is used internally by Python's print
# to keep track of whether to prepend space to the
# printed expression
self.softspace = 0
self._old_stdout = None
def activate(self):
self._old_stdout = sys.stdout
sys.stdout = self
def deactivate(self):
sys.stdout = self._old_stdout
self._old_stdout = None
def write(self, s):
# actually write to an open file obtained from an attribute
# on the current thread
threading.current_thread().open_file.write(s)
def writelines(self, seq):
for s in seq:
self.write(s)
def close(self):
pass
def flush(self):
pass
def isatty(self):
return False
It is possible to do what you're asking, although it's complicated and clunky and possibly not portable, and I don't think it's what you want to do.
Your objection to just using spamegg.print is:
I'm exploring the creation of an interactive Python environment, so I can't just use print() from module spamegg. It has to be the globally available one with no arguments.
But the solution to that is easy: Just use print from module spamegg in your code, and from spamegg import print in the interactive interpreter. That's all there is to it.
For that matter, there's no good reason this even needs to be called print in the first place. If all of your code used some other output function with a different name, you could do the same thing in the interactive interpreter.
But how does that let each thread have a different destination?
The easy way to do that is to just look up the destination in a threading.local().
But if you really want to do both parts of this the hard way, you can.
To do the global print the hard way, you can either have spamegg replace the builtin print instead of just giving you a way to shadow it, or have it replace sys.stdout, so the builtin print with default arguments will print somewhere else.
import builtins
_real_print = builtins.print
def _print(*args, **kwargs):
kwargs.setdefault('file', my_output_destination)
_real_print(*args, **kwargs)
builtins.print = _print
import io
import sys
class MyStdOut(io.TextIOBase):
# ... __init__, write, etc.
sys.stdout = MyStdOut()
That still requires having MyStdOut use a thread-local target.
Alternatively, you can compile or wrap each thread function in its own custom globals environment that replaces __builtins__ and/or sys from the default, allowing you to give a different one to each thread from the start. For example:
from functools import partial
from threading import Thread
from types import FunctionType
class MyThread(Thread):
def __init__(self, group=None, target=None, *args, **kwargs):
if target:
g = target.__globals__.copy()
g['__builtins__'] = g['__builtins__'].copy()
output = make_output_for_new_thread()
g['__builtins__']['print'] = partial(print, file=output)
target = FunctionType(thread_func.__code__, g, thread_func.__name__,
thread_func.__defaults__, thread_func.__closure__)
super().__init__(self, group, target, *args, **kwargs)
I might have solution for you, but it's quite more complicated than just print.
class ClusteredLogging(object):
'''
Class gathers all logs performed inside with statement and flush
it to mainHandler on exit at once.
Good for multithreaded applications that has to log several
lines at once.
'''
def __init__(self, mainHandler, formatter):
self.mainHandler = mainHandler
self.formatter = formatter
self.buffer = StringIO()
self.handler = logging.StreamHandler(self.buffer)
self.handler.setFormatter(formatter)
def __enter__(self):
rootLogger = logging.getLogger()
rootLogger.addHandler(self.handler)
def __exit__(self, t, value, tb):
rootLogger = logging.getLogger()
rootLogger.removeHandler(self.handler)
self.handler.flush()
self.buffer.flush()
rootLogger.addHandler(self.mainHandler)
logging.info(self.buffer.getvalue().strip())
rootLogger.removeHandler(self.mainHandler)
Using this, you can create log handler for each thread and configure them to store logs to different locations.
Keep in mind that this is developed with slightly different goal in mind (see comments) but you can adapt it by taking handler juggling feature of ClusteredLogging as a start.
And some test code:
import concurrent.futures
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
import logging
import sys
# put ClusteredLogging here
if __name__ == "__main__":
formatter = logging.Formatter('%(asctime)s %(levelname)8s\t%(message)s')
onlyMessageFormatter = logging.Formatter("%(message)s")
mainHandler = logging.StreamHandler(sys.stdout)
mainHandler.setFormatter(onlyMessageFormatter)
mainHandler.setLevel(logging.DEBUG)
rootLogger = logging.getLogger()
rootLogger.setLevel(logging.DEBUG)
def logSomethingLong(label):
with ClusteredLogging(mainHandler, formatter):
for i in range(15):
logging.info(label + " " + str(i))
labels = ("TEST", "EXPERIMENT", "TRIAL")
executor = concurrent.futures.ProcessPoolExecutor()
futures = [executor.submit(logSomethingLong, label) for label in labels]
concurrent.futures.wait(futures)
I have this somewhat complicated command line function in Python (lets call it myFunction()), and I am working to integrate it in a graphical interface (using PySide/Qt).
The GUI is used to help select inputs, and display outputs. However, myFunction is designed to work as a stand-alone command line function, and it occasionnaly prints out the progress.
My question is: how can I intercept these print calls and display them in the GUI?
I know it would be possible to modify myFunction() to send processEvents() to the GUI, but I would then lose the ability to execute myFunction() in a terminal.
Ideally, I would like something similar to Ubuntu's graphical software updater, which has a small embeded terminal-looking widget displaying what apt-get would display were it executed in a terminal.
you could redirect stdout and restore after. for example:
import StringIO
import sys
# somewhere to store output
out = StringIO.StringIO()
# set stdout to our StringIO instance
sys.stdout = out
# print something (nothing will print)
print 'herp derp'
# restore stdout so we can really print (__stdout__ stores the original stdout)
sys.stdout = sys.__stdout__
# print the stored value from previous print
print out.getvalue()
Wrap it with a function that hijacks stdout:
def stdin2file(func, file):
def innerfunc(*args, **kwargs):
old = sys.stdout
sys.stdout = file
try:
return func(*args, **kwargs)
finally:
sys.stdout = old
return innerfunc
Then simply provide a file like object that supports write():
class GUIWriter:
def write(self, stuff):
#send stuff to GUI
MyFunction = stdin2file(MyFunction, GUIWriter())
The wrapper can be turned into a decorator too:
def redirect_stdin(file):
def stdin2file(func, file):
def innerfunc(*args, **kwargs):
old = sys.stdout
sys.stdout = file
try:
return func(*args, **kwargs)
finally:
sys.stdout = old
return innerfunc
return stdin2file
The use it when declaring MyFunction():
#redirect_stdin(GUIWriter())
def MyFunction(a, b, c, d):
# any calls to print will call the 'write' method of the GUIWriter
# do stuff
Here is a Python 3 pattern using contextmanager that both encapsulates the monkey-patch technique and also ensures that sys.stdout is restored in case of an exception.
from io import StringIO
import sys
from contextlib import contextmanager
#contextmanager
def capture_stdout():
"""
context manager encapsulating a pattern for capturing stdout writes
and restoring sys.stdout even upon exceptions
Examples:
>>> with capture_stdout() as get_value:
>>> print("here is a print")
>>> captured = get_value()
>>> print('Gotcha: ' + captured)
>>> with capture_stdout() as get_value:
>>> print("here is a print")
>>> raise Exception('oh no!')
>>> print('Does printing still work?')
"""
# Redirect sys.stdout
out = StringIO()
sys.stdout = out
# Yield a method clients can use to obtain the value
try:
yield out.getvalue
finally:
# Restore the normal stdout
sys.stdout = sys.__stdout__
All printing is done via sys.stdout, which is a ordinary file-like object: iirc, it requires a method write(str). As long as your replacement has that method, it's quite easy to drop in your hook:
import sys
class CaptureOutput:
def write(self, message):
log_message_to_textbox(message)
sys.stdout = CaptureOutput()
The actual contents of log_message_to_textbox are up to you.