-
-
Save i-am-unknown-81514525/d3e2a8e57e4fcbd487f47922155950a1 to your computer and use it in GitHub Desktop.
| 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: | |
| ... |
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
class A:...
class B:...
as_mutex(A, B)
A().__class__.mro() # [A_mod, A_ori, __Mutex_A_B__0, __Mutex_A_B__1, object]
B().__class__.mro() # [B_mod, B_ori, __Mutex_A_B__1, __Mutex_A_B__0, object]Which therefore when you attempt to inherit A and B at 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__0 be first or __Mutex_A_B__1 because they claim differently
You can noted that I do A().__class__.mro() instead of A.mro(), and the above MRO result shown A_mod and A_ori, this is because the class inheritance of A is not the one that changed, but the __init__ is injected such that the class of the object (via obj.__class__ = ...) would be replaced to the class with the mutex. The A_mod and A_ori is only for pure demonstration purpose, in which in the code, you would just see 2 A.
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 A and B isn'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.
What the fuck? 😭😭😭