Python - detect when my object is written to stdout?

前端 未结 2 2109
深忆病人
深忆病人 2021-02-20 11:07

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

2条回答
  •  难免孤独
    2021-02-20 11:47

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

提交回复
热议问题