Created
February 7, 2024 11:54
-
-
Save twilligon/e417acf5048c0cc69f23dc30b8a75c46 to your computer and use it in GitHub Desktop.
Interior mutability (like Rust's Cell<T>) in Python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| >>> 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)} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
License: CC0-1.0