Установить атрибут только для чтения в Python?

учитывая, насколько динамичен 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.

это хорошая попытка удержать меня от чего-то потенциально (возможно) глупо, но я бы все равно хотел сделать это. Я подозреваю, что у интерпретатора есть какая-то таблица поиска, которую я могу изменить, но я не мог найти ничего подобного в Google. __setattr__ также не работает - он вернул ту же самую ошибку о том, что атрибут доступен только для чтения.

Я специально ищу решение Python 2.7, если это важно, хотя нет причин отказываться от ответов, которые работают для других версий, так как я подозреваю, что другие люди в будущем будут смотреть здесь с аналогичными вопросами относительно других версий.

2 ответов


несмотря на свою динамичность, Python не позволяет исправлять встроенные типы обезьян, такие как file. Это даже мешает вам сделать это, изменив __dict__ такого типа -__dict__ свойство возвращает dict, завернутый в прокси-сервер только для чтения, поэтому оба назначения file.write и file.__dict__['write'] провал. И по крайней мере по двум причинам:--12-->

  1. код C ожидает file встроенный тип, соответствующий PyFile тип структура, и file.write до PyFile_Write() функция используется внутри.

  2. Python реализует кэширование доступа к атрибутам для типов, чтобы ускорить поиск метода и создание метода экземпляра. Этот кэш будет нарушен, если ему будет разрешено напрямую назначать тип dicts.

monkey-patching, конечно, разрешен для классов, реализованных в 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')

несмотря на то, что Python в основном является динамическим языком, существуют собственные типы объектов, такие как str, file (включая stdout),dict и list которые фактически реализованы на низком уровне C и являются полностью static:

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