17

鉴于 Python 的动态性,如果这不可能,我会感到震惊:

我想更改sys.stdout.write.

我从这个答案中得到了我的另一个问题的想法:https ://stackoverflow.com/a/24492990/901641

我试着简单地写这个:

original_stdoutWrite = sys.stdout.write

def new_stdoutWrite(*a, **kw):
    original_stdoutWrite("The new one was called! ")
    original_stdoutWrite(*a, **kw)

sys.stdout.write = new_stdoutWrite

但它告诉我AttributeError: 'file' object attribute 'write' is read-only

这是一个很好的尝试,可以防止我做一些可能(可能)愚蠢的事情,但我真的很想继续做下去。我怀疑解释器有某种我可以修改的查找表,但我在谷歌上找不到类似的东西。__setattr__也不起作用 - 它返回了关于属性为只读的完全相同的错误。

我特别在寻找 Python 2.7 解决方案,如果这很重要的话,尽管没有理由拒绝提供适用于其他版本的答案,因为我怀疑其他人将来会在这里看到关于其他版本的类似问题。

4

2 回答 2

27

尽管 Python 具有动态性,但它不允许猴子修补内置类型,例如file. __dict__它甚至可以通过修改此类类型来阻止您这样做——该__dict__属性返回包装在只读代理中的 dict,因此赋值file.writefile.__dict__['write']失败。至少有两个充分的理由:

  1. C 代码期望file内置类型对应于PyFile类型结构和内部使用file.writePyFile_Write()函数。

  2. Python 实现了对类型的属性访问的缓存,以加快方法查找和实例方法的创建。如果允许直接分配给类型字典,则此缓存将被破坏。

对于在 Python 中实现的可以很好地处理动态修改的类,当然允许使用猴子补丁。

但是...如果您真的知道自己在做什么,则可以使用低级 API,例如ctypes挂钩到实现并获取类型 dict。例如:

# WARNING: do NOT attempt this in production code!

import ctypes

def magic_get_dict(o):
    # find address of dict whose offset is stored in the type
    dict_addr = id(o) + type(o).__dictoffset__

    # retrieve the dict object itself
    dict_ptr = ctypes.cast(dict_addr, ctypes.POINTER(ctypes.py_object))
    return dict_ptr.contents.value

def magic_flush_mro_cache():
    ctypes.PyDLL(None).PyType_Modified(ctypes.py_object(object))

# monkey-patch file.write
dct = magic_get_dict(file)
dct['write'] = lambda f, s, orig_write=file.write: orig_write(f, '42')

# flush the method cache for the monkey-patch to take effect
magic_flush_mro_cache()

# magic!
import sys
sys.stdout.write('hello world\n')
于 2014-06-30T20:36:30.607 回答
3

尽管 Python 主要是一种动态语言,但也有一些原生对象类型,例如strfile(包括stdout)、dictlist它们实际上是在低级 C 中实现的,并且是完全静态的:

>>> a = []
>>> a.append = 'something else'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object attribute 'append' is read-only

>>> a.hello = 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute 'hello'

>>> a.__dict__  # normal python classes would have this
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute '__dict__'

如果您的对象是本机 C 代码,那么您唯一的希望是使用实际的常规类。对于您的情况,如前所述,您可以执行以下操作:

class NewOut(type(sys.stdout)):
    def write(self, *args, **kwargs):
        super(NewOut, self).write('The new one was called! ')
        super(NewOut, self).write(*args, **kwargs)
sys.stdout = NewOut()

或者,执行与原始代码类似的操作:

original_stdoutWrite = sys.stdout.write
class MyClass(object):
    pass
sys.stdout = MyClass()
def new_stdoutWrite(*a, **kw):
    original_stdoutWrite("The new one was called! ")
    original_stdoutWrite(*a, **kw)
sys.stdout.write = new_stdoutWrite
于 2014-06-30T20:31:13.237 回答