Last active
May 30, 2025 15:26
-
-
Save i-am-unknown-81514525/d3e2a8e57e4fcbd487f47922155950a1 to your computer and use it in GitHub Desktop.
Python Class Mutex via MRO
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 typing import Callable | |
| from random import choices | |
| from itertools import permutations | |
| registry: dict[type, tuple[type, Callable, Callable]] = {} # old: (new, ori_init, ori_init_subclass) | |
| rev_registry: dict[type, type] = {} | |
| __lock: bool = False | |
| __inner: bool = False | |
| def get_latest_from_reg(cls: type): | |
| while cls in registry: | |
| cls = registry[cls][0] | |
| return cls | |
| def new_init(v: type, *args, **kwargs): # old | |
| r = registry[v][1](*args, **kwargs) | |
| args[0].__class__ = get_latest_from_reg(v) # registry[v][0] | |
| return r | |
| def is_subcls_unmod(cls: type): | |
| class __internal: ... | |
| return type(cls.__init_subclass__) == type(__internal.__init_subclass__) | |
| def new_init_subclass(v: type, cls: type, *args, **kwargs): # old, unmod_subclass? | |
| global __lock, __inner | |
| try: | |
| __set_inner = False | |
| if not __inner: | |
| ori_cls = cls | |
| cls = get_latest_from_reg(cls) | |
| v = get_latest_from_reg(v) | |
| __inner = True | |
| __set_inner = True | |
| try: | |
| __lock = True | |
| mro = tuple(map(get_latest_from_reg, cls.mro())) | |
| new_cls = type(cls.__name__, mro, {}) | |
| finally: | |
| __lock = False | |
| ori_init = ori_cls.__init__ | |
| ori_init_subclass = ori_cls.__init_subclass__ | |
| cls.__init__ = init_closure(cls) | |
| cls.__init_subclass__ = init_subclass_closure(cls) | |
| registry[cls] = (new_cls, ori_init, ori_init_subclass) | |
| rev_registry[new_cls] = cls | |
| if is_subcls_unmod(ori_cls): | |
| return ori_init_subclass(*args, **kwargs) | |
| else: | |
| return ori_init_subclass.__func__(cls, *args, **kwargs) | |
| finally: | |
| if __set_inner: | |
| __inner = False | |
| def init_closure(v: type): | |
| def inner(*args, **kwargs): | |
| return new_init(v, *args, **kwargs) | |
| inner.__name__ = "__init__" | |
| inner.__qualname__ = v.__qualname__ | |
| return inner | |
| def init_subclass_closure(v: type): | |
| def inner(cls, *args, **kwargs): | |
| return new_init_subclass(v, cls, *args, **kwargs) | |
| inner.__name__ = "__init_subclass__" | |
| inner.__qualname__ = v.__qualname__ | |
| return classmethod(inner) | |
| def factorial(x): | |
| t = 1 | |
| for v in range(1, x+1): t *= v | |
| return t | |
| def as_mutex(*t): | |
| global __inner | |
| size = 1 | |
| while factorial(size) < len(t): size += 1 | |
| rnd = "".join(choices("0123456789abcdef", k=16)) | |
| prefix = "__Mutex_" + "_".join(map(lambda x: x.__name__, t)) + "_" + rnd + "_" | |
| mutex = [type(f"{prefix}_{i}", (), {}) for i in range(size)] | |
| for curr, mutex_comb in zip(t, permutations(mutex, len(mutex))): | |
| ori_curr = curr | |
| curr = get_latest_from_reg(curr) | |
| reso = tuple(curr.mro()[:-1] + list(mutex_comb) + [object]) | |
| try: | |
| __inner = True | |
| new_cls = type(curr.__name__, reso, {}) # + "_mutex_" + rnd | |
| finally: | |
| __inner = False | |
| ori_init = ori_curr.__init__ | |
| ori_init_subclass = ori_curr.__init_subclass__ | |
| curr.__init__ = init_closure(curr) | |
| curr.__init_subclass__ = init_subclass_closure(curr) | |
| registry[curr] = (new_cls, ori_init, ori_init_subclass) | |
| rev_registry[new_cls] = curr | |
| if __name__ == '__main__': | |
| class A:... | |
| class B:... | |
| as_mutex(A, B) | |
| class C(A):... | |
| C() | |
| class D: | |
| def __init__(self, t): | |
| self.t = 1 | |
| as_mutex(A, B, D) | |
| class E(D): | |
| def __init__(self, t): | |
| super().__init__(t) | |
| self.t | |
| E(1) | |
| class F: | |
| def __init_subclass__(cls): | |
| print(cls) | |
| as_mutex(A, B, F) | |
| class G(D, F): ... | |
| print(G(1).__class__.mro()) | |
| try: | |
| class H(B, F): ... | |
| raise AssertionError | |
| except TypeError: | |
| ... |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It basically modify the
__init__and__init_subclass__, so that they would be redirected for handling the modification.On creating the mutex system, the minimal amount of mutex class is used (calculated by the factorial, which for x mutex, it have x! to arrange the class in different order)
It then gives each class a new inheritance of different ordered mutex, such that if 2 class being inherit at the same time, no valid MRO order can be form for the mutex.
For example
Which therefore when you attempt to inherit
AandBat the same time, Python MRO rule would lead to no valid way to resolved an order of method resolution (i.e. should__Mutex_A_B__0be first or__Mutex_A_B__1because they claim differentlyYou can noted that I do
A().__class__.mro()instead ofA.mro(), and the above MRO result shownA_modandA_ori, this is because the class inheritance ofAis not the one that changed, but the__init__is injected such that the class of the object (viaobj.__class__ = ...) would be replaced to the class with the mutex. TheA_modandA_oriis only for pure demonstration purpose, in which in the code, you would just see 2A.Strictly, injecting the
__init__is not necessary as the MRO check is being done before hand, but it would be really difficult to demonstrate the mutex so the__init__injection is kept.As the class inheritance of
AandBisn't modified, how does it prevent it?It was done by injecting the classmethod
__init_subclass__, where it would check the initial MRO of the class, and then create a new class that contain the mutex, then inject the__init__and__init_subclass__so the cycle continues.