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
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
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 ...
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.
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.
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).