Python - обнаруживает, когда мой объект записывается в stdout?

У меня довольно необычный запрос, я думаю... Я объясню, почему после того, как я объясню, что.

Что

Я хочу, чтобы каждый раз, когда мой объект записывался в stdout, я мог выполнять побочные эффекты в это время. Так, например, когда я печатаю:

sys.stdout.write(instance_of_my_class)

он должен выполнять побочные эффекты. Я сделал свой класс подклассом str и переделал __call__, __unicode__, __str__, __repr__, index, decode, encode, format, __format__, __getattribute__, __getitem__ и __len__, чтобы каждый из них печатал оператор, указывающий, что они были вызваны, но кажется, что sys.stdout.write не вызывает ни одного из них для печати объекта.

Обратите внимание, что я специально говорю о sys.stdout.write, а не, например, print - я обнаружил, что print вызывает __str__ на все, что он задан.

Почему

Этот вопрос продолжается, после того, как был отключен ответ Цветной запрос на Python в Windows?.

Я обнаружил, что каждый раз, когда python должен отображать интерактивное приглашение, он вызывает __str__ на sys.ps1 и sys.ps2, а затем сохраняет результаты, которые будут отображаться в командной строке. Это означает, что любые побочные эффекты в sys.ps2.__str__ вызываются сразу после тех, что находятся в sys.ps1.__str__, но я хочу, чтобы они подождали, пока не покажут sys.ps2.

Вместо того, чтобы возвращать str в sys.ps2.__str__, я возвращаю свой подкласс str, который, как я надеюсь, каким-то образом сможет поймать, когда на него вызывается sys.stdout.write.

Ответ 1

Почему не 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

Ответ 2

Интригующая проблема! Мое первое предположение заключается в том, что sys.stdout.write не вызывает метод __str__, потому что ваш объект уже a str (или, по крайней мере, его подкласс, который достаточно хорош для всех целей и цели)... поэтому не нужны методы литья.

Дальнейшее исследование предполагает, что sys.stdout.write действительно никогда не хочет вызывать метод __str__...

Подкласс подход

С небольшой интроспекцией вы можете узнать, какие методы вашего подкласса str вызывают sys.stdout.write (ответа не так много):

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)

Запуск в среде Unicode (Python 2.7, iPython notebook), это печатает:

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

Это скорее kludge-y, но вы можете переопределить метод подкласса decode для выполнения желаемых побочных эффектов.

Однако в среде, отличной от Unicode , нет поиска атрибутов.

Подход к использованию

Вместо того, чтобы использовать подкласс str, возможно, вам понадобится какая-то "обертка" вокруг str. Здесь уродливый разведывательный хак, который создает класс, который делегирует большинство своих атрибутов str, но не является строгим подклассом:

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)

В среде Unicode это дает в основном те же результаты:

*** 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> 

Однако, когда я запускаю в среде, отличной от Unicode, definitely_not_a_string выводит сообщение об ошибке:

TypeError: expected a character buffer object

... это показывает, что метод .write идет прямо к буферному интерфейсу уровня C, когда ему не нужно выполните любое Unicode-декодирование.

Мое заключение

Кажется, что переопределение метода decode является возможным kludge в средах Unicode, так как sys.stdout.write вызывает этот метод, когда ему нужно декодировать str в Unicode.

Однако в средах, не относящихся к Unicode, кажется, что .write не выполняет поиск атрибутов вообще, а просто переходит к протоколу символьного буфера уровня C, поэтому нет способа перехватить его доступ из кода Python. Действительно, help(sys.stdout.write) проверяет, что это встроенная функция (она написана на C, а не на Python).