【问题标题】:Set a Read-Only Attribute in Python?在 Python 中设置只读属性?
【发布时间】:2014-06-30 19:16:01
【问题描述】:

鉴于 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 解决方案,如果这很重要的话,尽管没有理由拒绝提供适用于其他版本的答案,因为我怀疑其他人将来会在这里看到关于其他版本的类似问题。

【问题讨论】:

  • 您必须将所有 sys.stdout 替换为行为符合您要求的新对象。
  • @Wooble - 这是一个有趣的想法。想在更完整的答案中展示吗?
  • stackoverflow.com/questions/14986490/… 可能会有所帮助。
  • Wooble,完美地回答了我的问题。谢谢!我已经回滚了这个问题的标题更改。提出的更具体的标题让我觉得太具体了——我想要一个关于如何更改只读属性(我已提供)的一般答案,而不是关于如何仅更改我给出的属性的具体答案问题中的一个例子。

标签: python python-2.7 stdout readonly-attribute


【解决方案1】:

尽管 Python 具有动态性,但它不允许猴子修补内置类型,例如 file。它甚至可以通过修改这种类型的__dict__ 来阻止您这样做——__dict__ 属性返回包装在只读代理中的字典,因此对file.write 和file.__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')

【讨论】:

  • 我认为这是我一直在寻找的巫毒教类型,它应该适用于任何情况。 +1 并被接受。
  • 使用ctypes.cast(id(object), ctypes.py_object)而不是object的目的是什么?
  • @user2357112 PyType_Modified(object) 被 ctypes 拒绝。显式转换告诉 ctypes 如何处理类型参数 - 在这种情况下,只需将其作为普通的 PyObject 传递给函数。
  • @user4815162342:看起来它确实对此感到困惑。就个人而言,我会将函数的argtypes 设置为(py_object,),但如果你想明确地构造ctypes.py_object,py_object(object) 会比ctypes.cast 更容易。 (将restype 设置为None 也是一个不错的举措。)
  • @user2357112 谢谢,我现在修改了答案以使用py_object(object) 而不是演员表。更改PyType_Modified 的argtypes 和restype 有点超出此答案的范围。
【解决方案2】:

尽管 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

【讨论】:

  • 回答我询问的具体示例,所以 +1,但它似乎不是适用于 Python 中所有可能类型的通用通用解决方案。
猜你喜欢
  • 1970-01-01
  • 2016-02-25
  • 1970-01-01
  • 1970-01-01
  • 2016-02-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-02
相关资源
最近更新 更多