Set a Read-Only Attribute in Python?

前端 未结 2 1568
挽巷
挽巷 2020-12-03 08:14

Given how dynamic Python is, I\'ll be shocked if this isn\'t somehow possible:

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

I

相关标签:
2条回答
  • 2020-12-03 08:24

    Despite Python mostly being a dynamic language, there are native objects types like str, file (including stdout), dict, and list that 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 an actual regular class. For your case, like already mentioned, you could 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 original 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
    
    0 讨论(0)
  • 2020-12-03 08:30

    Despite its dynamicity, Python does not allow monkey-patching built-in types such as file. It even prevents you to do so by modifying the __dict__ of such a type — the __dict__ property returns the dict wrapped in a read-only proxy, so both assignment to file.write and to file.__dict__['write'] fail. And for at least two good reasons:

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

    2. Python implements caching of attribute access on types to speed up method lookup and instance method creation. This cache would be broken if it were allowed to directly assign to type dicts.

    Monkey-patching is of course allowed for classes implemented in Python which can handle dynamic modifications just fine.

    However... if you really know what you are doing, you can use the low-level APIs such as ctypes to hook into the implementation and get to the type dict. For example:

    # 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')
    
    0 讨论(0)
提交回复
热议问题