pygx.detouring¶
Symbolic detour.
detouring
¶
Symbolic detour.
It is straighforward to symbolize existing classes and functions, but in order to use them, we need to replace the classes that were used in existing code with the symbolic ones. Sometimes, we just cannot modify existing source code. Or in some other cases, objects created within a function or a class method are not exposed to the external, therefore we cannot manipulate them as a part of the symbolic tree. For example::
@pg.symbolize
def foo():
# Object a is not a part of foo's interface,
# therefore it cannot be seen from the symbolic tree
# that contains a foo object.
a = A(1)
return a.do_something()
Symbolic detour is introduced to address these use cases, which redirects
the __new__ method of a class to another class or function when it’s
evaluated under a context manager. Symbolic detour is not dependent on
symbolization, so in theory it can be used for detouring any classes.
Therefore, it does not require the presence of symbolic objects for mutating
the program.
current_mappings
¶
detour
¶
detour(
mappings: Sequence[tuple[type[Any], type[Any] | FunctionType]],
) -> Iterator[Sequence[tuple[type[Any], type[Any] | FunctionType]]]
Context manager for detouring object creation.
At times, we want to replace an object of a class to an object of a different class. Usually, we do so by passing the object as a function argument using dependency injection. However, it's not always possible to expose those internal objects as parameters to the class, as we cannot predict what needs to be customized in future. Also, exposing too many arguments will hurt usability, it's big burden to figure out 20 arguments of a function for a user to get started.
pg.detour provides another option for object replacement in Python, which
creates a context in which some source classes can be detoured to specified
destination classes or functions. For example, the code snippet below will
detour instantation of class A to class B, and vice-versa::
class A: pass
class B: pass
# Exchange class A and class B. with pg.detour([(A, B), (B, A)]): a = A() # a is a B object. b = B() # b is an A object.
Detour destination can be a function, which allows users to intercept the arguments passed to the class constructor. For example::
class Foo: def init(self, value): self.value = value
class Bar: def init(self, value): self.value = value
def detoured_foo(cls, value): # cls is the original class before detour. return Bar(value + 1)
with pg.detour([(Foo, detoured_foo)]): f = Foo(1) # f will be Bar(2).
Detour can be nested. The outer scope mappings take precedence over the mappings from the inner loop, allowing users to change object creation behaviors from the outside. For example, the following code will detour class A to class C::
with pg.detour([(A, C)]): with pg.detour([A, B]): a = A() # a is a C object.
Detour is transisive across the inner and outer scope. For example, the code below will detour class A to class C through B::
with pg.detour([(B, C)]): a1 = A() # a1 is an A object. with pg.detour([A, B]): a2 = A() # a2 is a C object. (A -> B -> C)
Detour is thread-sfe.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mappings
|
Sequence[tuple[type[Any], type[Any] | FunctionType]]
|
A sequence of tuple (src_cls, dest_cls_or_fn) as mappings for the
detour - 'src_cls' is the source class to be detoured, while
'dest_cls_or_fn' is the destination class or function. When it's a class,
its |
required |
Yields:
| Type | Description |
|---|---|
Sequence[tuple[type[Any], type[Any] | FunctionType]]
|
Resolved detour mappings. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If the first item in each mapping is not a class, or the second item in each mapping is neither a class nor a function. |
Source code in pygx/detouring/_class_detour.py
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | |
undetoured_new
¶
Create a new instance of cls without detouring.
If cls.init creates sub-objects, creation of sub-objects maybe detoured based on current context. For example::
class A:
def __init__(self, x):
if x < 0:
self.child = A(x)
else:
self.x = x
with pg.detour([A, B]): a = A(-1) assert isinstance(a, A) assert isinstance(a.child, B)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cls
|
type[Any]
|
The class whose instance will be created. |
required |
*args
|
Any
|
Positional arguments to be passed to class init method. |
()
|
**kwargs
|
Any
|
Keyword arguments to be passed to class init method. |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
A instance of |
Source code in pygx/detouring/_class_detour.py
options: show_root_heading: false show_root_toc_entry: false members_order: source heading_level: 2