Skip to content

Instantly share code, notes, and snippets.

@twilligon
Created February 7, 2024 11:54
Show Gist options
  • Save twilligon/e417acf5048c0cc69f23dc30b8a75c46 to your computer and use it in GitHub Desktop.
Save twilligon/e417acf5048c0cc69f23dc30b8a75c46 to your computer and use it in GitHub Desktop.
Interior mutability (like Rust's Cell<T>) in Python
from functools import update_wrapper
def Mutable(inner):
def rebind(unbound):
return lambda self, *args, **kwargs: unbound(
self.inner,
*(a.inner if isinstance(a, type(self)) else a
for a in args),
**{k: v.inner if isinstance(v, type(self)) else v
for k, v in kwargs.items()},
)
attrs = {}
for attr_name in dir(inner):
attr = getattr(inner, attr_name)
if hasattr(attr, "__call__"):
unbound = getattr(attr, "__func__", None) \
or getattr(type(inner), attr_name) or attr
attrs[attr_name] = update_wrapper(rebind(unbound), attr)
else:
attrs[attr_name] = attr
if "__getattribute__" in attrs:
def __getattribute__(self, attr, *args, **kwargs):
try:
return object.__getattribute__(self, attr, *args, **kwargs)
except AttributeError:
try:
return self.inner.__getattribute__(attr, *args, **kwargs)
except Exception as e:
raise e from None
wrapped = update_wrapper(__getattribute__, inner.__getattribute__)
attrs["__getattribute__"] = wrapped
if "__getattr__" in attrs:
def __getattr__(self, attr, *args, **kwargs):
try:
return object.__getattr__(self, attr, *args, **kwargs)
except AttributeError:
try:
return self.inner.__getattr__(attr, *args, **kwargs)
except Exception as e:
raise e from None
wrapped = update_wrapper(__getattr__, inner.__getattr__)
attrs["__getattr__"] = wrapped
if "__setattr__" in attrs:
def __setattr__(self, attr, *args, **kwargs):
if attr == "inner":
object.__setattr__(self, attr, *args, **kwargs)
else:
return self.inner.__setattr__(self, attr, *args, **kwargs)
wrapped = update_wrapper(__setattr__, inner.__setattr__)
attrs["__setattr__"] = wrapped
attrs["__repr__"] = lambda self: f"Mutable({self.inner!r})"
attrs["inner"] = inner
cls = type(f"Mutable({inner.__class__.__name__})", (object,), attrs)
obj = object.__new__(cls)
object.__setattr__(obj, "__class__", cls)
return obj
>>> from mutable import Mutable
>>> x=Mutable(1)
>>> x
Mutable(1)
>>> x.inner
1
>>> x.inner += 1
>>> x
Mutable(2)
>>> mutable_set = set(map(Mutable, {1, 2, 3}))
>>> mutable_set
{Mutable(1), Mutable(2), Mutable(3)}
>>> elem = next(iter(mutable_set))
>>> elem
Mutable(1)
>>> # note: you are really not supposed to do this
>>> elem.inner = 31337
>>> mutable_set
{Mutable(31337), Mutable(2), Mutable(3)}
@twilligon
Copy link
Author

License: CC0-1.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment