Установить атрибут "Только для чтения" в Python?
Учитывая динамический Python, я буду потрясен, если это не так возможно:
Я хотел бы изменить реализацию sys.stdout.write
.
Я получил эту идею от ответа на другой вопрос: qaru.site/info/547740/...
Я попытался просто написать это:
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, если это важно, хотя нет никаких оснований сопротивляться бросанию ответов, которые работают для других версий, так как я подозреваю, что другие люди в будущем будут искать здесь похожие вопросы относительно других версий.
Ответы
Ответ 1
Несмотря на свою динамичность, Python не позволяет использовать встроенные типы обезьян, такие как file
. Это даже мешает вам сделать это, изменив __dict__
такого типа - свойство __dict__
возвращает dict, завернутый в прокси-сервер, доступный только для чтения, поэтому оба назначения в file.write
и file.__dict__['write']
завершаются с ошибкой. И по крайней мере по двум причинам:
-
код C ожидает, что встроенный тип file
соответствует структуре типа PyFile
, а file.write
- к функции PyFile_Write()
, используемой внутренне.
-
Python реализует кэширование доступа атрибутов к типам, чтобы ускорить поиск метода и создание метода экземпляра. Этот кеш был бы сломан, если бы ему было разрешено напрямую назначать тип dicts.
Конечно же, исправление обезьян допускается для классов, реализованных в 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')
Ответ 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