Skip to content

Commit 4a620f8

Browse files
Update README with more detailed implementation section.
1 parent accf89d commit 4a620f8

File tree

1 file changed

+79
-8
lines changed

1 file changed

+79
-8
lines changed

README.md

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Otherwise you can clone this repository and import the `aexpr.py` file in the su
1515

1616
## Usage
1717

18+
If you want to see this tutorial visually you can watch the [screencast](https://github.com/active-expressions/active-expressions-static-python/tree/master/screencast).
19+
1820
First you have to import the library:
1921

2022
```
@@ -65,24 +67,93 @@ The on-change lambda expression gets three arguments.
6567
**Be careful:** To get the old and the new value the expression will be executed twice. It should be side-effect free and fast.
6668

6769
You can find more examples in the [Example.ipynb](https://github.com/active-expressions/active-expressions-static-python/blob/master/Examples.ipynb)-notebook.
68-
If you want to see this tutorial visually you can watch the [screencast](https://github.com/active-expressions/active-expressions-static-python/tree/master/screencast).
6970

7071
## Implementation
7172

72-
This library performs a static byte-code analysis of the expression and all nested methods.
73-
Therefore it simulates an object-stack and an own variable mapping and processes all byte-code instruction itself.
73+
This library analysis the given lambda expression and all nested methods to find all dependencies of the result of the lambda expression.
74+
Dependencies of an expression are in this case all fields which are used in the expression or in nested methods.
75+
**Example** (from the [presentation](https://github.com/active-expressions/active-expressions-static-python/blob/master/presentation/presentation.pdf)):
76+
77+
```
78+
class Example:
79+
def __init__(self):
80+
self.f = 5
81+
self.g = 10
82+
83+
def method(self):
84+
t = self.f + 2
85+
return t + self.get_g()
86+
87+
def get_g(self):
88+
return self.g
89+
90+
tmp = Example()
91+
aexpr(lambda: tmp.method())
92+
```
93+
94+
The dependencies are `self.f` and `self.g` (from object `tmp`).
95+
To be able to monitor the dependencies of the expression, we have to find them first.
96+
97+
## Step 1: Find the dependencies
98+
99+
Therefore this library performs a static byte-code analysis of the expression and all nested methods.
100+
It converts the binary byte-code of the expression and all nested methods with the library [`dis`](https://docs.python.org/2/library/dis.html).
101+
Afterwards it simulates an object-stack and an own variable mapping and processes all byte-code instruction itself.
102+
103+
**Short Example** (the complete one is in the [presentation](https://github.com/active-expressions/active-expressions-static-python/blob/master/presentation/presentation.pdf)):
104+
```
105+
...
106+
LOAD_FAST (self)
107+
LOAD_ATTR (f)
108+
LOAD_CONST (2)
109+
...
110+
```
111+
112+
`LOAD_FAST` pushes an object (`self` in this case which is equals to `tmp`) to the object stack.
113+
Afterwards `LOAD_ATTR` pulls the top of stack object and gets the attribute `f` from this attribute.
114+
Now we found a dependency of the expression.
115+
More general: **Always when we process a `LOAD_ATTR` instruction we find a dependency.**
116+
The attribute will be pushed on the stack and the simulation continues with `LOAD_CONST`.
117+
The [`aexpr`-method](https://github.com/active-expressions/active-expressions-static-python/blob/master/aexpr/aexpr.py#L72) performs this static byte-code analysis.
118+
119+
This implementation performs not all byte-codes in all details.
120+
It abstracts some of the instructions.
121+
An addition for example only takes two elements from the object stack and pushes a placeholder on this since we do not really care about the result.
122+
A multiplication does the same.
123+
Thats the reason why all elements on our own object stack are wrapped in an [ObjectWrapper](https://github.com/active-expressions/active-expressions-static-python/blob/master/aexpr/aexpr.py#L29), which can be an real object or a placeholder.
124+
125+
## Step 2: Monitor the dependencies
126+
127+
When we found the dependencies we have to monitor them to be able to trigger if something changes.
128+
For all dependencies (attribute of a object) we modify the `__setattr__`-method of the object, which will be called when setting a attribute of that object.
129+
We install a hook in that `__setattr__`-method which checks if we monitor that specific attribute and calls all triggers if so.
130+
The [method `placeaexpr`](https://github.com/active-expressions/active-expressions-static-python/blob/master/aexpr/aexpr.py#L46) installs these hooks.
131+
74132
Currently around 54% of all byte-code instructions are supported.
75133
See the contribution section if you want to increase this number ;)
76134

77-
An example for the analysis is shown in the [presentation](https://github.com/active-expressions/active-expressions-static-python/blob/master/presentation/presentation.pdf).
135+
The full example for this analysis is shown in the [presentation](https://github.com/active-expressions/active-expressions-static-python/blob/master/presentation/presentation.pdf).
78136

79137
## Limitations
80138

81-
This library does not support data structures like lists, sets, maps, ...
82-
It also can not monitor local variables.
83-
See some more limitations in the [presentation](https://github.com/active-expressions/active-expressions-static-python/blob/master/presentation/presentation.pdf).
139+
This library has the few following limitations. Feel free to contribute and fix these limitations:
140+
141+
- **Lists, Sets, Maps:** Datastructures are not supported so far. Means if you store a dependency in a list and access the an attribute later, you can not monitor on that attribute.
142+
- **Local Variables:** Local Variables are not instrumentable since they do not have a `__setattr__` or something else. Only fields of objects are instrumentable.
143+
- **External Resources:** Monitoring if a server is available or a file exists would require to poll this information repeatedly. This is not supported.
144+
- **Transactions:** Each time a dependency changes all triggers are triggered. Its not possible to pause this to change more attributes at once.
145+
- **Other language features:** Not supported are for examples *exceptions* and *closures**; *concurreny*, *asynchrony* and *meta-programming* can cause issues as well.
146+
147+
You can find some code examples for some of them in the [presentation](https://github.com/active-expressions/active-expressions-static-python/blob/master/presentation/presentation.pdf).
84148

85149
## Contribution
86150

87151
If you have some complex expression to monitor it can happen that you get an `UnimplementedInstructionException`.
88-
Afterwards you see the unsupported byte-code-instruction. Feel free to create pull request to this repository to support that instruction.
152+
This means that you try to perform an instruction which is not so far supported.
153+
Afterwards you see the unsupported byte-code-instruction.
154+
Feel free to create pull request to this repository to support that instruction.
155+
156+
To support a new instruction you have to modify the content of the `aexpr`-method.
157+
Call the method `opcode` once with the new supported op-codes of the instruction and call the result with a method which performs the required actions.
158+
Therefore you get the instruction, the rest of the instruction queue, the object stack and the variable mapping as parameters.
159+
There are already a lot of examples for this in this method.

0 commit comments

Comments
 (0)