Set read-only attribute in Python?

Given dynamic Python, I will be shocked if this is not possible:

I would like to change the implementation of sys.stdout.write .

I got this idea from an answer to another question: stack overflow

I tried just writing this:

 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 

But he tells me AttributeError: 'file' object attribute 'write' is read-only .

This is a good attempt to prevent me from doing something potentially (possibly) stupid, but I would really like to go further and do it anyway. I suspect the interpreter has some sort of lookup table that I can modify, but I could not find anything like it on Google. __setattr__ didn’t work either - it returned the same error as the read-only attribute.

I am specifically looking for a Python 2.7 solution if this is important, although there is no reason to resist throwing answers that work for other versions, since I suspect that other people will look for similar questions here in relation to other versions in the future.

+13
source share
2 answers

Despite its dynamism, Python does not allow the use of built-in types of monkeys, such as file . It even prevents you from doing this by changing this type of __dict__ - the __dict__ property returns a dict wrapped in a read-only proxy, so both destinations in file.write and file.__dict__['write'] fail. And for at least two reasons:

  • C code expects the built-in file type to match the structure of the PyFile type, and file.write to the PyFile_Write() function used internally.

  • Python implements caching of attribute access to types to speed up the search for a method and the creation of an instance method. This cache would be broken if it were allowed to directly assign the dicts type.

Of course, fixing monkeys is allowed for classes implemented in Python that handle dynamic changes very well.

However ... if you really know what you are doing, you can use low-level APIs such as ctypes to connect to the implementation and switch to the dict type. For instance:

 # 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') 
+16
source

Despite the fact that Python is mainly a dynamic language, there are object types such as str , file (including stdout ), dict and list , which are actually implemented in low-level C and are completely 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__' 

If your object is native C code, your only hope is to use a real regular class. For your case, as already mentioned, you can do something like:

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

or, to do something similar to your source code:

 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 
+3
source

All Articles