9

I am wondering if the build-in function print could be overridden so that the following statement will write to the console and a file at the same time.

print("test0","test1","test2",sep='\n') 

Also, may I know if it is possible to modify the source code of the build-in print function?

lxjhk
  • 567
  • 2
  • 7
  • 13
  • print is a function so yes you can – Padraic Cunningham Dec 23 '14 at 13:49
  • You seem to be misusing the term "overload". – BartoszKP Dec 23 '14 at 13:51
  • 2
    In addition to overriding `print`, you could redirect `sys.stdout` to a custom-created file-like object, that writes output to wherever you care to send it. – Kevin Dec 23 '14 at 13:52
  • possible duplicate of [overload print python](http://stackoverflow.com/questions/550470/overload-print-python) – PeterE Dec 23 '14 at 14:07
  • To actually change the behaviour of print requires some kind of monkey patching (or patching the Python interpreter itself). Using monkey patching, it's often not possible to fix globally (i.e. change the behaviour of print in modules you don't control). In modules you do control, there are easier ways to do it i.e. write your own print alternative, so monkey patching is frowned on. – spookylukey Dec 23 '14 at 14:55
  • @spookylukey: you don't need to patch the interpreter. It is enough to replace `builtins.print()` function. – jfs Dec 23 '14 at 21:34

4 Answers4

8

Use a decorator. Simplified example:

def my_decorator(func):
    def wrapped_func(*args,**kwargs):
        return func("I've been decorated!",*args,**kwargs)
    return wrapped_func

print = my_decorator(print)

Test:

print("TESTING") #I've been decorated! TESTING

So to print to a file at the same time you might do:

def super_print(filename):
    '''filename is the file where output will be written'''
    def wrap(func):
        '''func is the function you are "overriding", i.e. wrapping'''
        def wrapped_func(*args,**kwargs):
            '''*args and **kwargs are the arguments supplied 
            to the overridden function'''
            #use with statement to open, write to, and close the file safely
            with open(filename,'a') as outputfile:
                outputfile.write(*args,**kwargs)
            #now original function executed with its arguments as normal
            return func(*args,**kwargs)
        return wrapped_func
    return wrap

print = super_print('output.txt')(print)

If you compare this to the example above, you'll see there is an additional closure in this situation (i.e., return wrapped_func AND return wrap instead of just return wrapped_func). This second closure allows us to send an additional argument (filename) into the wrapper/decorator function.

The syntax of this last line looks a little weird, but this is the correct way. The call to super_print('output.txt') returns an object which is then given the print function object as an additional argument. This whole thing works via closures; research them if you aren't up to speed.

Then:

print('test')

test will be written to console output and to output.txt.

Rick
  • 43,029
  • 15
  • 76
  • 119
  • A decorator is an overly complicated way of overriding a single function. `def print(*args): # my body here` is much simpler. – chepner Dec 23 '14 at 14:51
  • 1
    But how would you `print` inside your new `print` function? Would a `print` appearing inside of `print`-prime use the "old" version...? – Rick Dec 23 '14 at 14:53
  • 1
    I just tried it (`def print(*args):`,`print("I've been overridden!",*args)`) and it results in infinite recursion. – Rick Dec 23 '14 at 14:56
  • 2
    You need to use `__builtin__.print` (Python 2.x) or `builtins.print` (Python 3) to reference the original. (Both `__builtin__` and `builtins` need to be imported; the predefined `__builtins__` should not be used.) – chepner Dec 23 '14 at 15:01
  • I am interested - how can I force ALL imported modules will have the `print` overriden ? I want to redefine print in the main file and apply to all calls of all modules I use later... – jaromrax Sep 13 '21 at 16:26
  • @jaromrax I think you can override it this way but I haven't tried it: `__builtin__.print = my_print`. But make sure you import the module where you are performing this surgery first before importing the others. – Rick Sep 13 '21 at 18:53
7

You can create a class with a write method and inside of that method you can print both stdout as well as write to the file.

import sys

class A(object):
    def __init__(self, f):
        self.f = open(f, 'w') 

    def __enter__(self):
        return self   # return instance of A which is assign to `f`.

    def write(self, text):
        sys.stdout.write(text)  # print to the shell
        self.f.write(text) # write in the file

    def __exit__(self, *args):
        self.f.close()
        return True

with A('foo.txt') as f:
    print("test0","test1","test4",sep='\n', file=f) #`file = f` calls `write` method
Vishnu Upadhyay
  • 5,043
  • 1
  • 13
  • 24
1

print function uses sys.stdout unless the explicit file parameter is given.

You could redirect sys.stdout to a file-like object that writes to a console and a file at the same time:

#!/usr/bin/env python3
import sys
from contextlib import redirect_stdout

class TeeFile: # write to multiple files at once
    def __init__(self, *files):
        self.files = files
    def write(self, data):
        for file in self.files:
            file.write(data)
    def flush(self):
        for file in self.files:
            file.flush()

with open('log', 'a') as log, redirect_stdout(TeeFile(log, sys.stdout)):
    print("test0", "test1", "test2", sep='\n')

redirect_stdout is introduced in Python 3.4 but it is easy to implement it on earlier versions.

You could replace builtins.print function if you want to replace the print function globally. Consider whether logging module provides a better solution than print function in your case.

Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670
1

It's probably a little late for this answer, but I've created a package (https://github.com/phohenecker/stream-to-logger) that provides exactly what you are looking for, namely redirecting stdout+stderr to a file (in addition to the usual printing to the screen).

It's super-easy, you just have to add two lines to your code:

import streamtologger
streamtologger.redirect(target="./all-output.log")

You can install the package with pip:

pip install git+https://github.com/phohenecker/stream-to-logger
paho
  • 1,162
  • 9
  • 13