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