Here's a simple example of a hide function that takes an object and returns a proxy. The proxy allows you to access any attribute of the original, but not to set or change any attributes.
def hide(obj): class Proxy(object): __slots__ = () def __getattr__(self, name): return getattr(obj, name) return Proxy()
Here it is in action:
>>> class Foo(object): ... def __init__(self, a, b): ... self.a = a ... self.b = b ... >>> f = Foo(1, 2) >>> p = hide(f) >>> p.a, p.b (1, 2) >>> p.a = 3 Traceback (most recent call last): ... AttributeError: 'Proxy' object has no attribute 'a'
After the hide function has returned the proxy object the __getattr__ method is able to access the original object through the closure. This is stored on the __getattr__ method as the func_closure attribute (Python 2) or the __closure__ attribute (Python 3). This is a "cell object" and you can access the contents of the cell using the cell_contents attribute:
>>> cell_obj = p.__getattr__.func_closure >>> cell_obj.cell_contents <__main__.Foo object at 0x...>
This makes hide useless for actually preventing access to the original object. Anyone who wants access to it can just fish it out of the cell_contents.
What we can't do from pure-Python is*set* the contents of the cell, but nothing is really private in Python - or at least not in CPython.
There are two Python C API functions, PyCell_Get and PyCell_Set, that provide access to the contents of closures. From ctypes we can call these functions and both introspect and modify values inside the cell object:
>>> import ctypes >>> ctypes.pythonapi.PyCell_Get.restype = ctypes.py_object >>> py_obj = ctypes.py_object(cell_obj) >>> f2 = ctypes.pythonapi.PyCell_Get(py_obj) >>> f2 is f True >>> new_py_obj = ctypes.py_object(Foo(5, 6)) >>> ctypes.pythonapi.PyCell_Set(py_obj, new_py_obj) 0 >>> p.a, p.b (5, 6)
As you can see, after the call to PyCell_Set the proxy object is using the new object we put in the closure instead of the original. Using ctypes may seem like cheating, but it would only take a trivial amount of C code to do the same.
Two notes about this code.
- It isn't (of course) portable across different Python implementations
- Don't ever do this, it's for illustration purposes only!
Still, an interesting poke around the CPython internals with ctypes. Interestingly I have heard of one potential use case for code like this. It is alleged that at some point Armin Ronacher was using a similar technique in Jinja2 for improving tracebacks. (Tracebacks from templating languages can be very tricky because the compiled Python code usually bears a quite distant relationship to the original text based template.) Just because Armin does it doesn't mean you can though...