cdxcore 0.1.13__py3-none-any.whl → 0.1.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cdxcore might be problematic. Click here for more details.
- cdxcore/__init__.py +1 -1
- cdxcore/crman.py +1 -1
- cdxcore/deferred.py +329 -143
- cdxcore/dynaplot.py +14 -264
- cdxcore/dynaplotlimits.py +245 -0
- cdxcore/util.py +5 -5
- cdxcore/version.py +3 -3
- {cdxcore-0.1.13.dist-info → cdxcore-0.1.15.dist-info}/METADATA +1 -1
- {cdxcore-0.1.13.dist-info → cdxcore-0.1.15.dist-info}/RECORD +15 -13
- docs/source/conf.py +1 -6
- tests/test_deferred.py +23 -3
- tmp/deferred-hierarchical.py +682 -0
- {cdxcore-0.1.13.dist-info → cdxcore-0.1.15.dist-info}/WHEEL +0 -0
- {cdxcore-0.1.13.dist-info → cdxcore-0.1.15.dist-info}/licenses/LICENSE +0 -0
- {cdxcore-0.1.13.dist-info → cdxcore-0.1.15.dist-info}/top_level.txt +0 -0
cdxcore/__init__.py
CHANGED
cdxcore/crman.py
CHANGED
|
@@ -142,7 +142,7 @@ class CRMan(object):
|
|
|
142
142
|
"""
|
|
143
143
|
Return current string.
|
|
144
144
|
|
|
145
|
-
This is the string that ``CRMan``is currently visible to the user
|
|
145
|
+
This is the string that ``CRMan`` is currently visible to the user
|
|
146
146
|
since the last time a new line was printed.
|
|
147
147
|
"""
|
|
148
148
|
return self._current
|
cdxcore/deferred.py
CHANGED
|
@@ -1,10 +1,153 @@
|
|
|
1
|
-
"""
|
|
1
|
+
r"""
|
|
2
2
|
Overview
|
|
3
3
|
--------
|
|
4
4
|
|
|
5
5
|
Framework for delayed execution of a Python code tree.
|
|
6
6
|
|
|
7
|
-
Used by :mod:`cdxcore.dnaplot`.
|
|
7
|
+
Used by :mod:`cdxcore.dnaplot`.
|
|
8
|
+
|
|
9
|
+
Illustation
|
|
10
|
+
^^^^^^^^^^^
|
|
11
|
+
|
|
12
|
+
**The Deferred Dependency Graph**
|
|
13
|
+
|
|
14
|
+
Assume we have a class ``A`` and a function ``F``
|
|
15
|
+
operates on instances ``a`` of ``A``:
|
|
16
|
+
|
|
17
|
+
.. code-block:: python
|
|
18
|
+
|
|
19
|
+
class A(object):
|
|
20
|
+
def __init__(self, x):
|
|
21
|
+
self.x = x
|
|
22
|
+
def func(self, y=1):
|
|
23
|
+
return self.x * y
|
|
24
|
+
def __call__(self, y):
|
|
25
|
+
return self.x + y
|
|
26
|
+
def __eq__(self, z):
|
|
27
|
+
return self.x==z
|
|
28
|
+
|
|
29
|
+
def F(a : A):
|
|
30
|
+
_ = a.f(2)
|
|
31
|
+
_ = _+a(3)
|
|
32
|
+
return _, _ == 1
|
|
33
|
+
|
|
34
|
+
Deferred execution means that instead of using an instance of ``A`` to evaluate
|
|
35
|
+
``F``, we use a :class:`cdxcore.deferred.Deferred` object:
|
|
36
|
+
|
|
37
|
+
.. code-block:: python
|
|
38
|
+
|
|
39
|
+
from cdxcore.defer1ed import Deferred
|
|
40
|
+
|
|
41
|
+
a = Deferred("a") # <- name "a" is arbitrary but important for error messages
|
|
42
|
+
r1, r2 = F(a)
|
|
43
|
+
|
|
44
|
+
The returned ``r1`` and ``r2`` are of type :class:`cdxcore.deferred.DeferredAction`. We can print
|
|
45
|
+
the overall dependency tree from ``a`` using :meth:`cdxcore.deferred.DeferredAction.deferred_print_dependency_tree`:
|
|
46
|
+
|
|
47
|
+
.. code-block:: python
|
|
48
|
+
|
|
49
|
+
a.deferred_print_dependency_tree( with_sources = True )
|
|
50
|
+
|
|
51
|
+
yields::
|
|
52
|
+
|
|
53
|
+
00: $a <= $a
|
|
54
|
+
01: $a.f <= $a
|
|
55
|
+
02: $a.f(2) <= $a
|
|
56
|
+
03: ($a.f(2)+$a(3)) <= $a
|
|
57
|
+
04: (($a.f(2)+$a(3))==1) <= $a
|
|
58
|
+
01: $a(3) <= $a
|
|
59
|
+
|
|
60
|
+
The ``$`` sign indicates top level source elements.
|
|
61
|
+
Note that this tree shows the depth of the dependencies, not execution dependencies: the term ``$a(3)``
|
|
62
|
+
must be executed before ``$a.f(2)+=$a(3)``.
|
|
63
|
+
This order of terms a seen from the top level ``a`` can be retrieved using :meth:`cdxcore.deferred.DeferredAction.deferred_source_dependants`:
|
|
64
|
+
calling ``print( [ _.deferred_info for _ in a.deferred_source_dependants ] )`` prints::
|
|
65
|
+
|
|
66
|
+
['$a.f', '$a.f(2)', '$a(3)', '$a.f(2)+=$a(3)', '($a.f(2)==2)']
|
|
67
|
+
|
|
68
|
+
**Evaluating the Graph**
|
|
69
|
+
|
|
70
|
+
To execute the graph, use :meth:`cdxcore.deferred.Deferred.deferred_resolve` at top level (note that graphs can only
|
|
71
|
+
be resolved once):
|
|
72
|
+
|
|
73
|
+
.. code-block:: python
|
|
74
|
+
|
|
75
|
+
a.deferred_resolve(A(x=2))
|
|
76
|
+
print(r1.deferred_result, ",", r2.deferred_result) # -> 9 , False
|
|
77
|
+
|
|
78
|
+
Validation by directly execution ``F`` with an object of type ``A`` confirms the result:
|
|
79
|
+
|
|
80
|
+
.. code-block:: python
|
|
81
|
+
|
|
82
|
+
t1, t2 = F(A(x=2))
|
|
83
|
+
print(, t1, ",", t2) # -> 9 , False
|
|
84
|
+
|
|
85
|
+
Limitations
|
|
86
|
+
^^^^^^^^^^^
|
|
87
|
+
|
|
88
|
+
The framework relies on replacing any Python element by a :class:`cdxcore.deferred.DeferredAction` object and then
|
|
89
|
+
catche and track all actions applied to it, be that reading an attribute, an item, calling ``__call__`` or accessing
|
|
90
|
+
any of the standard Python attributes such as ``__eq__``.
|
|
91
|
+
|
|
92
|
+
This appraoch has a number of short comings:
|
|
93
|
+
|
|
94
|
+
* **Gap in Implementation:** please let us know.
|
|
95
|
+
|
|
96
|
+
* **Flow controls:** any code flow which depends on the actual value of some computation is not supported.
|
|
97
|
+
|
|
98
|
+
* **Core Python Operators:** some Python operators
|
|
99
|
+
must return a certain type: ``__bool__``, ``__str__``, ``__repr__`` and ``__index__`` as far as we are aware.
|
|
100
|
+
That means we cannot wrap the result into a
|
|
101
|
+
:class:`cdxcore.deferred.DeferredAction`.
|
|
102
|
+
|
|
103
|
+
Considering ``__str__`` and ``__repr__`` are used throughtout the Python stack
|
|
104
|
+
a ``DeferredAction`` will return strings describing itself. Be aware of
|
|
105
|
+
corresponding error messages.
|
|
106
|
+
|
|
107
|
+
If ``__bool__`` or ``__index__`` is called a :class:`cdxcore.deferred.NotSupportedError`
|
|
108
|
+
is raised.
|
|
109
|
+
|
|
110
|
+
* **Python atomic types:** Python optimizes various operations for its atomic types such as ``int`` etc. That means if a deferred
|
|
111
|
+
action catches an operation on an element which represents an atomic element, it might not be able to translate the action
|
|
112
|
+
into the atomic's equivalent.
|
|
113
|
+
|
|
114
|
+
Here is an example for ``int`` with ``+=``:
|
|
115
|
+
|
|
116
|
+
.. code-block:: python
|
|
117
|
+
|
|
118
|
+
a = Deferred("a")
|
|
119
|
+
a += 1
|
|
120
|
+
a.deferred_resolve(int(1))
|
|
121
|
+
|
|
122
|
+
This produces an error:
|
|
123
|
+
|
|
124
|
+
.. code-block:: python
|
|
125
|
+
|
|
126
|
+
AttributeError: ("Cannot resolve '$a+=1': the concrete element of type 'int' provided by '$a' does not contain the action '__iadd__'", "'int' object has no attribute '__iadd__'")
|
|
127
|
+
|
|
128
|
+
The reason here is that the operation ``a += 1`` was caught as an ``__iadd__`` action.
|
|
129
|
+
However, ``getattr`` cannot obtain this operator from an ``int``:
|
|
130
|
+
|
|
131
|
+
.. code-block:: python
|
|
132
|
+
|
|
133
|
+
getattr( int(1), "__iadd__" ) # -> AttributeError: 'int' object has no attribute '__iadd__'
|
|
134
|
+
|
|
135
|
+
Interestingly, other operators such as ``__add__`` do work.
|
|
136
|
+
|
|
137
|
+
**Core Python Elements**
|
|
138
|
+
|
|
139
|
+
Some Python functionality requires more complexity: catching and tracing core
|
|
140
|
+
operatores like ``__bool__``, ``is_`` etc does not add value to the use cases we have in mind, and complicate the framework
|
|
141
|
+
considerably.
|
|
142
|
+
|
|
143
|
+
Class Interface
|
|
144
|
+
^^^^^^^^^^^^^^^
|
|
145
|
+
|
|
146
|
+
The class interfaces for both :class:`cdxcore.deferred.Deferred` and :class:`cdxcore.deferred.DeferredAction`
|
|
147
|
+
are pretty clunky in that all member functions and attribues are pre-fixed with ``deferred`` or ``_deferred_``, respectively.
|
|
148
|
+
The reason is that the objects will catch and track all member function calls and attribute access meant for objects
|
|
149
|
+
of type ``A``.
|
|
150
|
+
|
|
8
151
|
|
|
9
152
|
Import
|
|
10
153
|
------
|
|
@@ -13,6 +156,8 @@ Import
|
|
|
13
156
|
|
|
14
157
|
from cdxcore.deferrred import Deferred
|
|
15
158
|
|
|
159
|
+
Documentation
|
|
160
|
+
-------------
|
|
16
161
|
"""
|
|
17
162
|
|
|
18
163
|
from .err import verify
|
|
@@ -31,80 +176,20 @@ class ResolutionDependencyError(RuntimeError):
|
|
|
31
176
|
"""
|
|
32
177
|
pass
|
|
33
178
|
|
|
179
|
+
class NotSupportedError(RuntimeError):
|
|
180
|
+
"""
|
|
181
|
+
Exeception in case of an attempted resolution of unsupported
|
|
182
|
+
operators.
|
|
183
|
+
"""
|
|
184
|
+
pass
|
|
185
|
+
|
|
34
186
|
class DeferredAction(object):
|
|
35
187
|
"""
|
|
36
|
-
A deferred action
|
|
188
|
+
A deferred action
|
|
37
189
|
for an object which has not been created, yet.
|
|
38
|
-
|
|
39
|
-
This class is returned by :class:`cdxcore.deferred.Deferred`.
|
|
40
|
-
|
|
41
|
-
The aim is to be able to record a sequence of actions on a placeholder
|
|
42
|
-
for an object which does not yet exist
|
|
43
|
-
such as function calls, item access, or attribute access,
|
|
44
|
-
and store the resulting logical dependency tree. Once the target
|
|
45
|
-
element is available, execute the dependency tree iteratively.
|
|
46
|
-
|
|
47
|
-
A basic example is as follows: assume we have a class ``A`` of which
|
|
48
|
-
we will create an object ``a``, but only at a later stage. We
|
|
49
|
-
wish to record a list of deferred actions on ``a`` ahead of its creation.
|
|
50
|
-
|
|
51
|
-
Define the class::
|
|
52
|
-
|
|
53
|
-
class A(object):
|
|
54
|
-
def __init__(self, x):
|
|
55
|
-
self.x = x
|
|
56
|
-
def a_func(self, y):
|
|
57
|
-
return self.x * y
|
|
58
|
-
@staticmethod
|
|
59
|
-
def a_static(y):
|
|
60
|
-
return y
|
|
61
|
-
@property
|
|
62
|
-
def a_propery(self):
|
|
63
|
-
return self.x
|
|
64
|
-
def __getitem__(self, y):
|
|
65
|
-
return self.x * y
|
|
66
|
-
def __call__(self, y):
|
|
67
|
-
return self.x * y
|
|
68
|
-
def another(self):#
|
|
69
|
-
return A(x=self.x*2)
|
|
70
|
-
|
|
71
|
-
Use :class:`cdxcore.deferred.Deferred` to create a root ``DeferredAction`` object::
|
|
72
|
-
|
|
73
|
-
from cdxcore.deferred import Deferred
|
|
74
|
-
da = Deferred("A")
|
|
75
|
-
|
|
76
|
-
Record a few actions::
|
|
77
190
|
|
|
78
|
-
|
|
79
|
-
ai = da[2] # defer item access
|
|
80
|
-
aa = da.x # defer attribute access
|
|
81
|
-
|
|
82
|
-
:class:`cdxcore.deferred.DeferredAction` works iteratively, e.g. all values returned from a function call, ``__call__``, or ``__getitem__``
|
|
83
|
-
are themselves deferred automatically.`:class:`cdxcore.deferred.DeferredAction` is also able to defer item and attribute assignments.
|
|
84
|
-
|
|
85
|
-
As an example::
|
|
191
|
+
See :class:`cdxcore.deferred.Deferred` for a full discussion.
|
|
86
192
|
|
|
87
|
-
an = da.another()
|
|
88
|
-
an = an.another() # call a.another().another() --> an.x = 1*2*2 = 4
|
|
89
|
-
|
|
90
|
-
Finally, resolve the execution by instantiating ``A`` and calling :meth:`cdxcore.deferred.DeferredAction.deferred_resolve`::
|
|
91
|
-
|
|
92
|
-
a = A()
|
|
93
|
-
da.deferred_resolve( a )
|
|
94
|
-
|
|
95
|
-
print( an.x ) # -> 4
|
|
96
|
-
|
|
97
|
-
This functionality is used in :mod:`cdxcore.dynaplot`::
|
|
98
|
-
|
|
99
|
-
from cdxcore.dynaplot import figure
|
|
100
|
-
|
|
101
|
-
fig = figure() # the DynanicFig returned by figure() is derived from DeferredAction
|
|
102
|
-
ax = fig.add_subplot() # Deferred call for add_subplot()
|
|
103
|
-
lns = ax.plot( x, y )[0] # This is a deferred call to plot() and then [0].
|
|
104
|
-
fig.render() # renders the figure and executes plot() then [0]
|
|
105
|
-
lns.set_ydata( y2 ) # we can now access the resulting Line2D object via the DeferredAction wrapper
|
|
106
|
-
fig.close() # update graph
|
|
107
|
-
|
|
108
193
|
**Constructor**
|
|
109
194
|
|
|
110
195
|
External applications do not usually need to directly create objects
|
|
@@ -148,7 +233,7 @@ class DeferredAction(object):
|
|
|
148
233
|
self._deferred_live = None # once the object exists, it goes here.
|
|
149
234
|
self._deferred_was_resolved = False # whether the action was executed.
|
|
150
235
|
self._deferred_dependants = [] # list of all direct dependent actions
|
|
151
|
-
self._deferred_src_dependants = src_dependants
|
|
236
|
+
self._deferred_src_dependants = src_dependants #
|
|
152
237
|
|
|
153
238
|
if action == "":
|
|
154
239
|
info = "$"+info
|
|
@@ -160,6 +245,29 @@ class DeferredAction(object):
|
|
|
160
245
|
|
|
161
246
|
self.__dict__["_ready_for_the_deferred_magic_"] = 1
|
|
162
247
|
|
|
248
|
+
def __str__(self) -> str:
|
|
249
|
+
"""
|
|
250
|
+
Return descriptive string.
|
|
251
|
+
|
|
252
|
+
``__str__`` cannot be deferred as it must return a string.
|
|
253
|
+
We therefore return some information on ``self``
|
|
254
|
+
"""
|
|
255
|
+
return self._deferred_info
|
|
256
|
+
|
|
257
|
+
def __repr__(self) -> str:
|
|
258
|
+
"""
|
|
259
|
+
Return description.
|
|
260
|
+
|
|
261
|
+
``__repr__`` cannot be deferred as it must return a string.
|
|
262
|
+
We therefore return some information on ``self``
|
|
263
|
+
"""
|
|
264
|
+
sources = list(self._deferred_sources.values())
|
|
265
|
+
s = ""
|
|
266
|
+
for src in sources:
|
|
267
|
+
s += src + ","
|
|
268
|
+
s = s[:-1]
|
|
269
|
+
return f"DeferredAction[{self._deferred_info} <- {s}]"
|
|
270
|
+
|
|
163
271
|
@property
|
|
164
272
|
def _deferred_parent_info(self) -> str:
|
|
165
273
|
return self._deferred_parent._deferred_info if not self._deferred_parent is None else ""
|
|
@@ -239,6 +347,14 @@ class DeferredAction(object):
|
|
|
239
347
|
""" Retrieve list of dependant :class:`cdxcore.deferred.DeferredAction` objects. """
|
|
240
348
|
return self._deferred_dependants
|
|
241
349
|
|
|
350
|
+
@property
|
|
351
|
+
def deferred_source_dependants(self) -> list:
|
|
352
|
+
"""
|
|
353
|
+
Retrieve list of dependant :class:`cdxcore.deferred.DeferredAction` objects at the level of the original parent source.
|
|
354
|
+
This list is in order.
|
|
355
|
+
"""
|
|
356
|
+
return self._deferred_src_dependants
|
|
357
|
+
|
|
242
358
|
@property
|
|
243
359
|
def deferred_sources(self) -> dict:
|
|
244
360
|
"""
|
|
@@ -253,7 +369,7 @@ class DeferredAction(object):
|
|
|
253
369
|
several :func:`Deferred` elements.
|
|
254
370
|
|
|
255
371
|
Most users will prefer a simple list of names of sources.
|
|
256
|
-
In that case, use :meth:`cdxcore.deferred.
|
|
372
|
+
In that case, use :meth:`cdxcore.deferred.DeferredAction.deferred_sources_names`.
|
|
257
373
|
|
|
258
374
|
"""
|
|
259
375
|
return self._deferred_sources
|
|
@@ -287,7 +403,7 @@ class DeferredAction(object):
|
|
|
287
403
|
return list( self.deferred_sources.values() )
|
|
288
404
|
|
|
289
405
|
@property
|
|
290
|
-
def
|
|
406
|
+
def deferred_result(self):
|
|
291
407
|
"""
|
|
292
408
|
Returns the result of the deferred action, if available.
|
|
293
409
|
|
|
@@ -524,18 +640,19 @@ class DeferredAction(object):
|
|
|
524
640
|
|
|
525
641
|
def __getattr__(self, attr ):
|
|
526
642
|
""" Deferred attribute access """
|
|
527
|
-
private_str = "
|
|
643
|
+
private_str = "deferred_"
|
|
528
644
|
#print("__getattr__", attr, self.__dict__.get(private_str+"info", "?"))
|
|
529
645
|
#print(" ", self.__dict__)
|
|
530
646
|
if attr in self.__dict__ or\
|
|
531
647
|
attr[:2] == "__" or\
|
|
532
648
|
attr[:len(private_str)] == private_str or\
|
|
649
|
+
attr[1:1+len(private_str)] == private_str or\
|
|
533
650
|
not "_ready_for_the_deferred_magic_" in self.__dict__:
|
|
534
651
|
#print("__getattr__: direct", attr)
|
|
535
652
|
try:
|
|
536
653
|
return self.__dict__[attr]
|
|
537
654
|
except KeyError as e:
|
|
538
|
-
raise AttributeError(*e.args, self.__dict__[
|
|
655
|
+
raise AttributeError(*e.args, self.__dict__["_deferred_info"]) from e
|
|
539
656
|
if not self._deferred_live is None:
|
|
540
657
|
#print("__getattr__: live", attr)
|
|
541
658
|
return getattr(self._deferred_live, attr)
|
|
@@ -544,16 +661,17 @@ class DeferredAction(object):
|
|
|
544
661
|
|
|
545
662
|
def __setattr__(self, attr, value):
|
|
546
663
|
""" Deferred attribute access """
|
|
547
|
-
private_str = "
|
|
664
|
+
private_str = "deferred_"
|
|
548
665
|
#print("__getattr__", attr, self.__dict__.get(private_str+"info", "?"))
|
|
549
666
|
if attr in self.__dict__ or\
|
|
550
667
|
attr[:2] == "__" or\
|
|
551
668
|
attr[:len(private_str)] == private_str or\
|
|
669
|
+
attr[1:1+len(private_str)] == private_str or\
|
|
552
670
|
not "_ready_for_the_deferred_magic_" in self.__dict__:
|
|
553
671
|
try:
|
|
554
672
|
self.__dict__[attr] = value
|
|
555
673
|
except KeyError as e:
|
|
556
|
-
raise AttributeError(*e.args, self.__dict__[
|
|
674
|
+
raise AttributeError(*e.args, self.__dict__["_deferred_info"]) from e
|
|
557
675
|
#print("__setattr__: direct", attr)
|
|
558
676
|
return
|
|
559
677
|
if not self._deferred_live is None:
|
|
@@ -569,6 +687,7 @@ class DeferredAction(object):
|
|
|
569
687
|
if attr in self.__dict__ or\
|
|
570
688
|
attr[:2] == "__" or\
|
|
571
689
|
attr[:len(private_str)] == private_str or\
|
|
690
|
+
attr[1:1+len(private_str)] == private_str or\
|
|
572
691
|
not "_ready_for_the_deferred_magic_" in self.__dict__:
|
|
573
692
|
#print("__delattr__: direct", attr)
|
|
574
693
|
try:
|
|
@@ -583,7 +702,7 @@ class DeferredAction(object):
|
|
|
583
702
|
return self._act("__delattr__", args=[attr], num_args=1, fmt="del {parent}.{arg0}")
|
|
584
703
|
|
|
585
704
|
@staticmethod
|
|
586
|
-
def
|
|
705
|
+
def _deferred_handle( action : str,
|
|
587
706
|
return_deferred : bool = True, *,
|
|
588
707
|
num_args : int = None,
|
|
589
708
|
fmt : str = None ):
|
|
@@ -594,80 +713,152 @@ class DeferredAction(object):
|
|
|
594
713
|
act.__doc__ = f"Deferred ``{action}`` action"
|
|
595
714
|
return act
|
|
596
715
|
|
|
716
|
+
@staticmethod
|
|
717
|
+
def _deferred_unsupported( action : str, reason : str ):
|
|
718
|
+
def act(self, *args, **kwargs):
|
|
719
|
+
raise NotSupportedError(action, f"Deferring action `{action}` is not possible: {reason}")
|
|
720
|
+
act.__name__ = action
|
|
721
|
+
act.__doc__ = f"Deferred ``{action}`` action"
|
|
722
|
+
return act
|
|
723
|
+
|
|
724
|
+
# not supported
|
|
725
|
+
__bool__ = _deferred_unsupported("__bool__", "'__bool__' must return a 'bool'.")
|
|
726
|
+
__index__ = _deferred_unsupported("__index__", "'__index__' must return an 'int'.")
|
|
727
|
+
# __str__ : implemented
|
|
728
|
+
# __repr__ : implemented
|
|
729
|
+
|
|
597
730
|
# core functionality
|
|
598
|
-
__call__ =
|
|
599
|
-
__setitem__ =
|
|
600
|
-
__getitem__ =
|
|
731
|
+
__call__ = _deferred_handle("__call__", num_args=None, fmt="{parent}({args})")
|
|
732
|
+
__setitem__ = _deferred_handle("__setitem__", num_args=2, fmt="{parent}[{arg0}]={arg1}")
|
|
733
|
+
__getitem__ = _deferred_handle("__getitem__", num_args=1, fmt="{parent}[{arg0}]")
|
|
601
734
|
|
|
602
735
|
# collections
|
|
603
|
-
__contains__ =
|
|
604
|
-
__iter__ =
|
|
605
|
-
__len__ =
|
|
606
|
-
__hash__ =
|
|
607
|
-
|
|
608
|
-
# cannot implement __bool__, __str__, or, __repr__ as these
|
|
609
|
-
# have defined return types
|
|
736
|
+
__contains__ = _deferred_handle("__contains__", num_args=1, fmt="({arg0} in {parent})")
|
|
737
|
+
__iter__ = _deferred_handle("__iter__")
|
|
738
|
+
__len__ = _deferred_handle("__len__", num_args=0, fmt="len({parent})")
|
|
739
|
+
__hash__ = _deferred_handle("__hash__", num_args=0, fmt="hash({parent})")
|
|
610
740
|
|
|
611
741
|
# comparison operators
|
|
612
|
-
__neq__ =
|
|
613
|
-
__eq__ =
|
|
614
|
-
__ge__ =
|
|
615
|
-
__le__ =
|
|
616
|
-
__gt__ =
|
|
617
|
-
__lt__ =
|
|
742
|
+
__neq__ = _deferred_handle("__neq__", num_args=1, fmt="({parent}!={arg0})")
|
|
743
|
+
__eq__ = _deferred_handle("__eq__", num_args=1, fmt="({parent}=={arg0})")
|
|
744
|
+
__ge__ = _deferred_handle("__ge__", num_args=1, fmt="({parent}>={arg0})")
|
|
745
|
+
__le__ = _deferred_handle("__le__", num_args=1, fmt="({parent}<={arg0})")
|
|
746
|
+
__gt__ = _deferred_handle("__gt__", num_args=1, fmt="({parent}>{arg0})")
|
|
747
|
+
__lt__ = _deferred_handle("__lt__", num_args=1, fmt="({parent}<{arg0})")
|
|
748
|
+
|
|
749
|
+
# unitary
|
|
750
|
+
__abs__ = _deferred_handle("__abs__", True, num_args=0, fmt="|{parent}|")
|
|
618
751
|
|
|
619
752
|
# i*
|
|
620
|
-
__ior__ =
|
|
621
|
-
__iand__ =
|
|
622
|
-
__ixor__ =
|
|
623
|
-
__imod__ =
|
|
624
|
-
__iadd__ =
|
|
625
|
-
__iconcat__ =
|
|
626
|
-
__isub__ =
|
|
627
|
-
__imul__ =
|
|
628
|
-
__imatmul__ =
|
|
629
|
-
__ipow__ =
|
|
630
|
-
__itruediv__ =
|
|
631
|
-
__ifloordiv__ =
|
|
632
|
-
# Py2 __idiv__ =
|
|
753
|
+
__ior__ = _deferred_handle("__ior__", False, num_args=1, fmt="{parent}|={arg0}")
|
|
754
|
+
__iand__ = _deferred_handle("__iand__", False, num_args=1, fmt="{parent}&={arg0}")
|
|
755
|
+
__ixor__ = _deferred_handle("__iand__", False, num_args=1, fmt="{parent}^={arg0}")
|
|
756
|
+
__imod__ = _deferred_handle("__imod__", False, num_args=1, fmt="{parent}%={arg0}")
|
|
757
|
+
__iadd__ = _deferred_handle("__iadd__", False, num_args=1, fmt="{parent}+={arg0}")
|
|
758
|
+
__iconcat__ = _deferred_handle("__iconcat__", False, num_args=1, fmt="{parent}+={arg0}")
|
|
759
|
+
__isub__ = _deferred_handle("__isub__", False, num_args=1, fmt="{parent}-={arg0}")
|
|
760
|
+
__imul__ = _deferred_handle("__imul__", False, num_args=1, fmt="{parent}*={arg0}")
|
|
761
|
+
__imatmul__ = _deferred_handle("__imatmul__", False, num_args=1, fmt="{parent}@={arg0}")
|
|
762
|
+
__ipow__ = _deferred_handle("__ipow__", False, num_args=1, fmt="{parent}**={arg0}")
|
|
763
|
+
__itruediv__ = _deferred_handle("__itruediv__", False, num_args=1, fmt="{parent}/={arg0}")
|
|
764
|
+
__ifloordiv__ = _deferred_handle("__ifloordiv__", False, num_args=1, fmt="{parent}//={arg0}")
|
|
765
|
+
# Py2 __idiv__ = _deferred_handle("__idiv__", False, num_args=1, fmt="{parent}/={arg0}")
|
|
633
766
|
|
|
634
767
|
# binary
|
|
635
|
-
__or__ =
|
|
636
|
-
__and__ =
|
|
637
|
-
__xor__ =
|
|
638
|
-
__mod__ =
|
|
639
|
-
__add__ =
|
|
640
|
-
__concat__ =
|
|
641
|
-
__sub__ =
|
|
642
|
-
__mul__ =
|
|
643
|
-
__pow__ =
|
|
644
|
-
__matmul__ =
|
|
645
|
-
__truediv__ =
|
|
646
|
-
__floordiv__ =
|
|
647
|
-
# Py2__div__ =
|
|
768
|
+
__or__ = _deferred_handle("__or__", num_args=1, fmt="({parent}|{arg0})")
|
|
769
|
+
__and__ = _deferred_handle("__and__", num_args=1, fmt="({parent}&{arg0})")
|
|
770
|
+
__xor__ = _deferred_handle("__xor__", num_args=1, fmt="({parent}^{arg0})")
|
|
771
|
+
__mod__ = _deferred_handle("__mod__", num_args=1, fmt="({parent}%{arg0})")
|
|
772
|
+
__add__ = _deferred_handle("__add__", num_args=1, fmt="({parent}+{arg0})")
|
|
773
|
+
__concat__ = _deferred_handle("__concat__", num_args=1, fmt="({parent}+{arg0})")
|
|
774
|
+
__sub__ = _deferred_handle("__sub__", num_args=1, fmt="({parent}-{arg0})")
|
|
775
|
+
__mul__ = _deferred_handle("__mul__", num_args=1, fmt="({parent}*{arg0})")
|
|
776
|
+
__pow__ = _deferred_handle("__pow__", num_args=1, fmt="({parent}**{arg0})")
|
|
777
|
+
__matmul__ = _deferred_handle("__matmul__", num_args=1, fmt="({parent}@{arg0})")
|
|
778
|
+
__truediv__ = _deferred_handle("__truediv__", num_args=1, fmt="({parent}/{arg0})")
|
|
779
|
+
__floordiv__ = _deferred_handle("__floordiv__", num_args=1, fmt="({parent}//{arg0})")
|
|
780
|
+
# Py2__div__ = _deferred_handle("__div__", num_args=1, fmt="({parent}/{arg0})")
|
|
648
781
|
|
|
649
782
|
# rbinary
|
|
650
|
-
__ror__ =
|
|
651
|
-
__rand__ =
|
|
652
|
-
__rxor__ =
|
|
653
|
-
__rmod__ =
|
|
654
|
-
__radd__ =
|
|
655
|
-
__rconcat__ =
|
|
656
|
-
__rsub__ =
|
|
657
|
-
__rmul__ =
|
|
658
|
-
__rpow__ =
|
|
659
|
-
__rmatmul__ =
|
|
660
|
-
__rtruediv__ =
|
|
661
|
-
__rfloordiv__ =
|
|
662
|
-
# Py2__rdiv__ =
|
|
783
|
+
__ror__ = _deferred_handle("__ror__", num_args=1, fmt="({arg0}|{parent})")
|
|
784
|
+
__rand__ = _deferred_handle("__rand__", num_args=1, fmt="({arg0}&{parent})")
|
|
785
|
+
__rxor__ = _deferred_handle("__rxor__", num_args=1, fmt="({arg0}^{parent})")
|
|
786
|
+
__rmod__ = _deferred_handle("__rmod__", num_args=1, fmt="({arg0}%{parent})")
|
|
787
|
+
__radd__ = _deferred_handle("__radd__", num_args=1, fmt="({arg0}+{parent})")
|
|
788
|
+
__rconcat__ = _deferred_handle("__rconcat__", num_args=1, fmt="({arg0}+{parent})")
|
|
789
|
+
__rsub__ = _deferred_handle("__rsub__", num_args=1, fmt="({arg0}-{parent})")
|
|
790
|
+
__rmul__ = _deferred_handle("__rmul__", num_args=1, fmt="({arg0}*{parent})")
|
|
791
|
+
__rpow__ = _deferred_handle("__rpow__", num_args=1, fmt="({arg0}**{parent})")
|
|
792
|
+
__rmatmul__ = _deferred_handle("__rmatmul__", num_args=1, fmt="({arg0}@{parent})")
|
|
793
|
+
__rtruediv__ = _deferred_handle("__rtruediv__", num_args=1, fmt="({arg0}/{parent})")
|
|
794
|
+
__rfloordiv__ = _deferred_handle("__rfloordiv__", num_args=1,fmt="({arg0}//{parent})")
|
|
795
|
+
# Py2__rdiv__ = _deferred_handle("__rdiv__", num_args=1, fmt="({arg0}/{parent})")
|
|
663
796
|
|
|
664
797
|
class Deferred(DeferredAction):
|
|
665
798
|
"""
|
|
666
|
-
Create a
|
|
667
|
-
of a
|
|
668
|
-
that does not yet exist.
|
|
799
|
+
Create a top level placeholder object to keep track
|
|
800
|
+
of a sequence of actions performed
|
|
801
|
+
on an object that does not yet exist.
|
|
802
|
+
|
|
803
|
+
Actions such as function calls, item access, or attribute access,
|
|
804
|
+
feed the resulting logical dependency tree. Once the target
|
|
805
|
+
element is available, execute the dependency tree iteratively.
|
|
806
|
+
|
|
807
|
+
A basic example is as follows: assume we have a class ``A`` of which
|
|
808
|
+
we will create an object ``a``, but only at a later stage. We
|
|
809
|
+
wish to record a list of deferred actions on ``a`` ahead of its creation.
|
|
810
|
+
|
|
811
|
+
Define the class
|
|
812
|
+
|
|
813
|
+
.. code-block:: python
|
|
814
|
+
|
|
815
|
+
class A(object):
|
|
816
|
+
def __init__(self, x):
|
|
817
|
+
self.x = x
|
|
818
|
+
def a_func(self, y):
|
|
819
|
+
return self.x * y
|
|
820
|
+
@staticmethod
|
|
821
|
+
def a_static(y):
|
|
822
|
+
return y
|
|
823
|
+
@property
|
|
824
|
+
def a_propery(self):
|
|
825
|
+
return self.x
|
|
826
|
+
def __getitem__(self, y):
|
|
827
|
+
return self.x * y
|
|
828
|
+
def __call__(self, y):
|
|
829
|
+
return self.x * y
|
|
830
|
+
def another(self):#
|
|
831
|
+
return A(x=self.x*2)
|
|
832
|
+
|
|
833
|
+
Use :class:`cdxcore.deferred.Deferred` to create a root ``DeferredAction`` object:
|
|
834
|
+
|
|
835
|
+
.. code-block:: python
|
|
836
|
+
|
|
837
|
+
from cdxcore.deferred import Deferred
|
|
838
|
+
da = Deferred("A")
|
|
839
|
+
|
|
840
|
+
Record a few actions:
|
|
841
|
+
|
|
842
|
+
.. code-block:: python
|
|
843
|
+
|
|
844
|
+
af = da.a_func(1) # defer function call to a_func(1)
|
|
845
|
+
ai = da[2] # defer item access
|
|
846
|
+
aa = da.x # defer attribute access
|
|
847
|
+
|
|
848
|
+
:class:`cdxcore.deferred.DeferredAction` works iteratively, e.g. all values returned from a function call, ``__call__``, or ``__getitem__``
|
|
849
|
+
are themselves deferred automatically.`:class:`cdxcore.deferred.DeferredAction` is also able to defer item and attribute assignments.
|
|
669
850
|
|
|
670
|
-
|
|
851
|
+
As an example::
|
|
852
|
+
|
|
853
|
+
an = da.another()
|
|
854
|
+
an = an.another() # call a.another().another() --> an.x = 1*2*2 = 4
|
|
855
|
+
|
|
856
|
+
Finally, resolve the execution by instantiating ``A`` and calling :meth:`cdxcore.deferred.Deferred.deferred_resolve`::
|
|
857
|
+
|
|
858
|
+
a = A()
|
|
859
|
+
da.deferred_resolve( a )
|
|
860
|
+
|
|
861
|
+
print( an.x ) # -> 4
|
|
671
862
|
|
|
672
863
|
Parameters
|
|
673
864
|
----------
|
|
@@ -677,11 +868,6 @@ class Deferred(DeferredAction):
|
|
|
677
868
|
|
|
678
869
|
max_err_len : int, optional
|
|
679
870
|
Maximum length of output information. The default is 100.
|
|
680
|
-
|
|
681
|
-
Returns
|
|
682
|
-
-------
|
|
683
|
-
:class:`cdxcore.deferred.DeferredAction`
|
|
684
|
-
A deferred action.
|
|
685
871
|
"""
|
|
686
872
|
|
|
687
873
|
def __init__(self, info : str,
|