如何在Python中设置只读属性?

22 投票
2 回答
16755 浏览
提问于 2025-04-18 11:38

考虑到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的解决方案,如果这很重要,虽然我也欢迎其他版本的解决方案,因为我怀疑将来会有其他人带着类似的问题来这里。

2 个回答

3

尽管Python大部分是动态语言,但它有一些原生对象类型,比如 str(字符串)、file(文件,包括 stdout,也就是标准输出)、dict(字典)和 list(列表),这些实际上是用底层的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
33

尽管Python是一种动态语言,但它不允许对内置类型进行“猴子补丁”,比如file。甚至连修改这种类型的__dict__属性都不行——因为__dict__返回的是一个只读的代理字典,所以你不能直接给file.writefile.__dict__['write']赋值。这主要有两个原因:

  1. 因为C语言的代码期望file这个内置类型对应于PyFile类型的结构,而file.write对应于内部使用的PyFile_Write()函数。

  2. Python在访问类型的属性时会进行缓存,以加快方法查找和实例方法创建的速度。如果允许直接对类型字典赋值,这个缓存就会被破坏。

当然,对于用Python实现的类,猴子补丁是被允许的,因为它们可以很好地处理动态修改。

不过……如果你真的知道自己在做什么,可以使用一些低级API,比如ctypes,来进入实现内部并访问类型字典。例如:

# 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')

撰写回答