Установить атрибут только для чтения в 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-->
код C ожидает
file
встроенный тип, соответствующийPyFile
тип структура, иfile.write
доPyFile_Write()
функция используется внутри.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