Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python - detect when my object is written to stdout?

Tags:

python

stdout

I have a rather unusual request, I think... I'll explain the why after I explain the what.

What

I want to detect whenever my object is written to stdout, so that I can perform side effects at that time. So, for example, when I type:

sys.stdout.write(instance_of_my_class)

it should perform side effects. I've made my class be a subclass of str and overrode __call__, __unicode__, __str__, __repr__, index, decode, encode, format, __format__, __getattribute__,__getitem__, and __len__ so that each of them prints a statement indicating that they've been called, but it seems that sys.stdout.write calls none of those in order to print an object.

Note that I'm specifically talking about sys.stdout.write and not, for example, print - I have found that print calls __str__ on whatever it is given.

Why

This question continues from where the answer to Colored Python Prompt in Windows? left off.

I have found that each time python needs to display an interactive prompt, it calls __str__ on sys.ps1 and sys.ps2, and then it saves the results to be displayed on the command line. This means any side effects in sys.ps2.__str__ are caused right after the ones in sys.ps1.__str__, but I want to have those wait until it's time to display sys.ps2.

So rather than return a str in sys.ps2.__str__, I've been returning my subclass of str, which I'm hoping will somehow be capable of catching when sys.stdout.write is called on it.

like image 837
ArtOfWarfare Avatar asked Jun 19 '14 18:06

ArtOfWarfare


People also ask

What is stdout in Python?

A built-in file object that is analogous to the interpreter’s standard output stream in Python. stdout is used to display output directly to the screen console. Output can be of any form, it can be output from a print statement, an expression statement, and even a prompt direct for input. By default, streams are in text mode.

How to read from stdin in Python?

Read from stdin in python Python stderr is known as a standard error stream. It is similar to stdout because it also directly prints to the console but the main difference is that it only prints error messages. After writing the above code (python print to stderr), you can observe that it print debug message using sys.stderr.

How to write to stdout or stderr in C++?

Writing to Standard Output ( stdout) using print is simple: print ( "Hello Standard Output!" ) print( "Hello Standard Output!" ) But if you want to write to Standard Error ( stderr ), you'll have to import the sys library: You can also write to stdout or stderr using the sys.stdout and sys.stderr file objects:

How to redirect stdout in Python?

The easiest way to redirect stdout in Python is to just assign it an open file object. Let’s take a look at a simple example: print('This string goes to stdout, NOT the file!') redirect_to_file('Python rocks!') import sys def redirect_to_file (text): original = sys.stdout sys.stdout = open ('/path/to/redirect.txt', ...


2 Answers

Intriguing problem! My first guess is that sys.stdout.write doesn't call the __str__ method because your object already is a str (or at least a subclass of it, which is good enough for all intents and purposes)... so no casting methods are needed.

Further investigation suggests that sys.stdout.write really doesn't ever want to call the __str__ method ...

Subclass approach

With a little introspection, you can find out which methods of your str subclass are called by sys.stdout.write (the answer is, not many):

class superstring(str):
    def __getattribute__(self, name):
        print "*** lookup attribute %s of %s" % (name, repr(self))
        return str.__getattribute__(self, name)

foo = superstring("UberL33tPrompt> ")
sys.stdout.write(foo)

Running in a Unicode environment (Python 2.7, iPython notebook), this prints:

*** lookup attribute __class__ of 'UberL33tPrompt> '
*** lookup attribute decode of 'UberL33tPrompt> '
UberL33tPrompt> 

It seems rather kludge-y, but you could override the subclass's decode method to perform the desired side effects.

However, in a non-Unicode environment there are no attribute lookups.

Wrapper approach

Rather than using a subclass of str, maybe what you need is some kind of "wrapper" around str. Here's an ugly exploratory hack which creates a class that delegates most of its attributes to str, but which is not strictly a subclass thereof:

class definitely_not_a_string(object):
    def __init__(self, s):
        self.s = s
    def __str__(self):
        print "*** Someone wants to see my underlying string object!"
        return self.s
    def decode(self, encoding, whatever):
        print "*** Someone wants to decode me!"
        return self.s.decode(encoding, whatever)
    def __getattribute__(self, name):
        print "*** lookup attribute %s of %s" % (name, repr(self))
        if name in ('s', '__init__', '__str__', 'decode', '__class__'):
            return object.__getattribute__(self, name)
        else:
            return str.__getattribute__(self, name)

foo = definitely_not_a_string("UberL33tPrompt> ")
sys.stdout.write(foo)

In the Unicode environment, this gives basically the same results:

*** lookup attribute __class__ of <__main__.definitely_not_a_string object at 0x00000000072D79B0>
*** lookup attribute decode of <__main__.definitely_not_a_string object at 0x00000000072D79B0>
*** Someone wants to decode me!
*** lookup attribute s of <__main__.definitely_not_a_string object at 0x00000000072D79B0>
UberL33tPrompt> 

However, when I run in a non-Unicode environment, definitely_not_a_string gives an error message:

TypeError: expected a character buffer object

... this shows that the .write method is going straight to the C-level buffer interface when it doesn't need to do any Unicode decoding.

My conclusion

It seems that overriding the decode method is a possible kludge in Unicode environments, since sys.stdout.write calls this method when it needs to decode a str into Unicode.

However, in non-Unicode environments it appears that .write doesn't do any attribute lookups whatsoever, but simply goes straight to the C-level character buffer protocol, so there's no way to intercept its access from Python code. Indeed, help(sys.stdout.write) verifies that it's a built-in function (aka written in C, not Python).

like image 149
Dan Lenski Avatar answered Oct 16 '22 19:10

Dan Lenski


Why not monkeypatch stdout.write?

stdoutRegistry = set()

class A(object):
    def __init__(self):
        self.stdoutRegistry.add(self)

    def stdoutNotify(self):
        pass

original_stdoutWrite = sys.stdout.write
def stdoutWrite(*a, **kw):
    if a in stdoutRegistry:
        a.stdoutNotify()
    original_stdoutWrite(*a, **kw)
sys.stdout.write = stdoutWrite
like image 24
Ben Avatar answered Oct 16 '22 17:10

Ben