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__
方法。
我为什么这么做
这个问题是从关于 Windows中的彩色Python提示符 的回答延续下来的。
我发现每次 Python 需要显示交互式提示符时,它会调用 sys.ps1
和 sys.ps2
的 __str__
方法,然后将结果保存以在命令行上显示。这意味着在 sys.ps2.__str__
中的任何额外操作都是在 sys.ps1.__str__
的操作之后立即发生的,但我希望这些操作等到显示 sys.ps2
时再执行。
所以,我在 sys.ps2.__str__
中没有返回一个普通的 str
,而是返回了我自己定义的 str
的子类,我希望它能在调用 sys.stdout.write
时捕捉到这个事件。
2 个回答
这个问题很有趣!我首先猜测,sys.stdout.write
不会调用 __str__
方法,因为你的对象已经是一个 str
(或者至少是它的一个子类,这在大多数情况下已经足够了)……所以不需要进行类型转换。
进一步调查显示,sys.stdout.write
确实从来不想调用 __str__
方法……
子类方法
通过一些简单的检查,你可以发现 sys.stdout.write
调用你的 str
子类的哪些方法(答案是,不多):
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>
这看起来有点笨拙,但你可以重写子类的 decode
方法来实现想要的效果。
不过,在非 Unicode 环境中是没有属性查找的。
包装器方法
与其使用 str
的子类,也许你需要的是某种“包装器”来包裹 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 解码时。
我的结论
看起来在 Unicode 环境中重写 decode
方法是一个可能的权宜之计,因为 sys.stdout.write
在需要将 str
解码为 Unicode 时会调用这个方法。
然而,在非 Unicode 环境中,.write
似乎根本不进行任何属性查找,而是直接访问 C 级别的字符缓冲协议,所以没有办法从 Python 代码中拦截它的访问。实际上,help(sys.stdout.write)
验证了它是一个内置函数(也就是说是用 C 写的,而不是 Python)。
为什么不直接修改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