i2.footprints¶
Analyzing what attributes of an input object a function actually uses
Functions
|
Extracts the attributes accessed by a function or method. |
|
Extracts attributes from ast tree processing only func_name function or method |
|
Extracts (method_name, accessed_attributes) pairs for a class or instance thereof. |
|
Extracts a list of cls attributes which are used by a method or method_name function |
|
Tracks the access to attributes within an execution. |
|
The |
|
Convert a |
|
The set of dunder names |
|
Casts input object |
|
Get class for unbound/bound method. |
|
Getting imports for an object (usually, module) |
|
Get source string of a python object |
|
Get the list of argument names |
|
Extracts functions and methods called from fn |
Import |
|
|
Map each method of a class (or of an instance's class) to the attributes it reads. |
|
Ctx manager to gracefully start/stop tracking. |
|
Add tracing methods to |
Classes
|
Collect, in |
|
|
Record the operator dunders applied to an instance, as |
|
|
Tracks the attribute access right after |
- class i2.footprints.AttributeVisitor(object_name)[source]¶
Bases:
NodeVisitorCollect, in
.attributes, the attribute names accessed onobject_namein an AST.
- class i2.footprints.Import(module, name, alias)¶
Bases:
tuple- alias¶
Alias for field number 2
- module¶
Alias for field number 0
- name¶
Alias for field number 1
- class i2.footprints.MethodTrace[source]¶
Bases:
objectRecord the operator dunders applied to an instance, as
(name, *args)tuples in.trace.See: https://github.com/i2mint/i2/issues/56 for more details.
>>> t = MethodTrace() >>> ((t + 3) - 2) * 5 / 10 <MethodTrace with .trace = ('__add__', 3), ... ('__truediv__', 10)> >>> assert t.trace == [ ... ('__add__', 3), ('__sub__', 2), ('__mul__', 5), ('__truediv__', 10) ... ] >>> >>> >>> w = t[42] >>> t[42] = 'mol' # an operation with two arguments >>> # ... and now an operation with no arguments: >>> ~t <MethodTrace with .trace = ... ('__setitem__', 42, 'mol'), ('__invert__',)> >>> >>> assert t.trace == [ ... ('__add__', 3), ('__sub__', 2), ('__mul__', 5), ('__truediv__', 10), ... ('__getitem__', 42), ('__setitem__', 42, 'mol'), ('__invert__',) ... ] >>>
- class i2.footprints.Tracker(*args, **kwargs)[source]¶
Bases:
objectTracks the attribute access right after
start_trackis set to True.Add this to __metaclass__ for any class that you need to track attributes for given a target method.
- i2.footprints.accessed_attributes(func, object_name=None)[source]¶
Extracts the attributes accessed by a function or method.
(This is a simpler, but narrow, version of
attrs_used_by_method).>>> def func(a, b, c): ... return a + b.bar + c ... >>> # Commenting out the testing code, as execution is not performed in the PCI >>> def foo(self): ... a = 2 ... self.method(x) ... y = self.prop ... return a + func(x, self, y) ... >>> assert accessed_attributes(foo, 'self') == {'method', 'prop'}
- i2.footprints.attr_list(root, func_name)[source]¶
Extracts attributes from ast tree processing only func_name function or method
- Parameters:
root – root node of ast tree
func_name – name of the function
- Returns:
a list of attributes names
- i2.footprints.attribute_dependencies(cls, filt=<function _is_method_like>, *, name_of_obj=<function name_of_obj>, exclude_names=<function init_argument_names>)[source]¶
Extracts (method_name, accessed_attributes) pairs for a class or instance thereof.
- Parameters:
cls (
type) – The class or instance to analyzefilt – A function that filters the attributes to consider
name_of_obj – A function that returns the name of an object
exclude_names (
Union[Container,Callable]) – A list of names to exclude from the analysis or a function that returns such a list given the class
- Returns:
A generator of (method_name, accessed_attributes) pairs
- i2.footprints.attrs_used_by_method(method, *, src_code=None)[source]¶
Extracts a list of cls attributes which are used by a method or method_name function
Note
The function tries to analyzed the source code deeply, gathering indirect references to the instance attributes. As a result, it is not very robust. You may want to check out the simpler (but narrow) function:
accessed_attributes.- Parameters:
method – The method (object) to analyze
src_code – The source code in which the method’s class is defined, when
inspectcannot retrieve it (for example in a notebook).
- Returns:
A list of attribute names (of the class or instance thereof) that are accessed in the code of the said method.
Consider the method
A.target_methodcoming from the following code ini2.tests.footprints_test:def func(obj): """Accesses attributes 'a' and 'b' of obj""" return obj.a + obj.b class A: e = 2 def __init__(self, a=1, b=0, c=1, d=10): self.a = a self.b = b self.c = c self.d = d def target_method(self, x): """Accesses ['a', 'b', 'c', 'e']""" t = func(self) # and this function will access some attributes! tt = self.other_method(t) return x * tt / self.e def other_method(self, x=1): """Accesses ['c', 'e']""" w = self.c * 2 # c is accessed first return self.e + self.c * x - w # and c is accessed again @classmethod def a_class_method(cls, y): """Accesses ['e']""" return cls.e + y
- i2.footprints.attrs_used_by_method_computation(cls_method, init_kwargs=None, method_kwargs=None, remove_duplicates=True)[source]¶
Tracks the access to attributes within an execution.
- i2.footprints.cls_and_method_name_of_method(method)[source]¶
The
(class, name)pair of a method, bound method or property.>>> from i2.tests.footprints_test import A >>> cls_and_method_name_of_method(A().target_method) == (A, "target_method") True
- i2.footprints.dict_to_graph(graph, from_key_to_values=True, *, kind='graphviz', indent=' ', prefix='', suffix='', display=False)[source]¶
Convert a
{node: neighbours}dictionary to a graphviz (or mermaid) graph string.You provide a graph in the form of a
{from_node: to_nodes, ...}or{to_node: from_nodes, ...}dictionary, and will get a graphviz string. You can use this to visualize a graph (e.g. a dependency graph) in a jupyter notebook.- Parameters:
graph (
dict) – The graph, in the form of a to convert to graphviz.from_key_to_values (
bool) – Whether the keys of the graph are from nodes or to nodes.kind (
Literal['graphviz','mermaid']) – The kind of graphviz string to return. Either “graphviz” or “mermaid”.indent (
str) – The indent to use for the graphviz string.graphviz_template – The template to use for the graphviz string.
display (
bool|Callable) – Whether to display the graphviz string as a graph in a jupyter notebook. Requires graphviz.
- Return type:
- Returns:
The graphviz string.
Example (but bear in mind the order of the nodes in graphviz_str may be different):
>>> graph_dict = { ... "A": ["B", "C"], ... "B": ["D"], # note that "D" is not mentioned as a key ... "C": ["D", "E", "F"], ... "E": [], ... } >>> # Keys are from nodes >>> graphviz_str = dict_to_graph(graph_dict) >>> print(graphviz_str) digraph G { "A" -> "B"; "A" -> "C"; "B" -> "D"; "C" -> "D"; "C" -> "E"; "C" -> "F"; }>>> # Keys are to nodes >>> graphviz_str = dict_to_graph(graph_dict, from_key_to_values=False) >>> print(graphviz_str) digraph G { "B" -> "A"; "C" -> "A"; "D" -> "B"; "D" -> "C"; "E" -> "C"; }The default
kindis graphviz, but you can also use mermaid:>>> graphviz_str = dict_to_graph(graph_dict, kind="mermaid") >>> print(graphviz_str) graph TD A --> B; A --> C; B --> D; C --> D; C --> E; C --> F;
- i2.footprints.dunders_diff(x, y)[source]¶
The set of dunder names
xhas andydoes not (module names are imported).
- i2.footprints.ensure_ast(o, src_code=None)[source]¶
Casts input object
oto a AST node.If the input is an AST node, it is returned as is. If the input is a filepath, the file is read and parsed as source code. If the input is a string, it is parsed as source code. If the input is an object, the source code is extracted and parsed.
Let’s get an AST for the ensure_ast function itself:
- Return type:
>>> assert isinstance(ensure_ast(ensure_ast), ast.AST)Note that sometimes the source code of an object cannot be accessed via normal means (for example, in REPLs like jupyter notebooks) so we need to pass it in.
>>> src_code = ''' ... class MyClass: ... x = 1 ... def my_method(self): ... return self.x + 1 ... a = 10 ... ''' >>> assert isinstance(ensure_ast('MyClass', src_code), ast.AST)
- i2.footprints.get_imports_from_obj(o, recursive=False)[source]¶
Getting imports for an object (usually, module)
- i2.footprints.init_argument_names(cls, *, no_error_action=None)[source]¶
Get the list of argument names
>>> from dataclasses import dataclass >>> @dataclass ... class DataClass: ... x: str ... y: float = 2 ... z = 3 ... >>> init_argument_names(DataClass) ['x', 'y']Note
Some builtin types don’t have signatures, so we get:
ValueError: no signature found for builtin type ...By default, we handle this by returning an empty list, but a callable no_error_action will call that function and return its result. Anything else will result in raising the error.
- i2.footprints.list_func_calls(fn)[source]¶
Extracts functions and methods called from fn
- Parameters:
fn – reference to function or method
- Returns:
a list of functions or methods names
- i2.footprints.module_if_string(x)[source]¶
Import
xif it is a module name string; otherwise return it as is.
- i2.footprints.object_dependencies(obj, *, get_source=<function get_source>)[source]¶
Map each method of a class (or of an instance’s class) to the attributes it reads.
Attributes accessed through the method’s first argument (usually
self) count; attributes that are only assigned to do not. Members for whichget_sourceraisesTypeError(builtins, descriptors) are skipped.>>> class C: ... def __init__(self): ... self.a = 1 ... def m(self): ... return self.a + self.helper() ... def helper(self): ... return 2 >>> object_dependencies(C)["m"] == {"a", "helper"} True
- i2.footprints.start_tracking(tracker_instance)[source]¶
Ctx manager to gracefully start/stop tracking.
- i2.footprints.trace_class_decorator(cls, names_and_sigs=(('__floordiv__', <Sig (self, b, /)>), ('__ior__', <Sig (self, value, /)>), ('__imod__', <Sig (self, b, /)>), ('__inv__', <Sig (self, /)>), ('__ilshift__', <Sig (self, b, /)>), ('__ne__', <Sig (self, b, /)>), ('__eq__', <Sig (self, b, /)>), ('__invert__', <Sig (self, /)>), ('__contains__', <Sig (self, key: KT, /) -> bool>), ('__iconcat__', <Sig (self, b, /)>), ('__le__', <Sig (self, b, /)>), ('__ge__', <Sig (self, b, /)>), ('__matmul__', <Sig (self, b, /)>), ('__abs__', <Sig (self, /)>), ('__delitem__', <Sig (self, key: KT, /) -> Any>), ('__not__', <Sig (self, /)>), ('__add__', <Sig (self, b, /)>), ('__and__', <Sig (self, b, /)>), ('__lshift__', <Sig (self, b, /)>), ('__pow__', <Sig (self, b, /)>), ('__lt__', <Sig (self, b, /)>), ('__ixor__', <Sig (self, b, /)>), ('__index__', <Sig (self, /)>), ('__mod__', <Sig (self, b, /)>), ('__imul__', <Sig (self, b, /)>), ('__call__', <Sig (self, /, *args, **kwargs)>), ('__sub__', <Sig (self, b, /)>), ('__ipow__', <Sig (self, b, /)>), ('__iand__', <Sig (self, b, /)>), ('__isub__', <Sig (self, b, /)>), ('__xor__', <Sig (self, b, /)>), ('__iadd__', <Sig (self, b, /)>), ('__pos__', <Sig (self, /)>), ('__imatmul__', <Sig (self, b, /)>), ('__or__', <Sig (self, value, /)>), ('__truediv__', <Sig (self, b, /)>), ('__gt__', <Sig (self, b, /)>), ('__setitem__', <Sig (self, key: KT, value: VT, /) -> Any>), ('__getitem__', <Sig (self, key: KT, /) -> ~VT>), ('__itruediv__', <Sig (self, b, /)>), ('__neg__', <Sig (self, /)>), ('__concat__', <Sig (self, b, /)>), ('__rshift__', <Sig (self, b, /)>), ('__irshift__', <Sig (self, b, /)>), ('__ifloordiv__', <Sig (self, b, /)>), ('__mul__', <Sig (self, b, /)>), ('__ror__', <Sig (self, value, /)>), ('__reversed__', <Sig (self, /)>), ('__len__', <Sig (self, /) -> int>), ('__iter__', <Sig (self, /) -> collections.abc.Iterator[~KT]>), ('__rsub__, ', <Sig (self, other)>), ('__rmul__, ', <Sig (self, other)>), ('__radd__, ', <Sig (self, other)>), ('__rmod__, ', <Sig (self, other)>), ('__rdivmod__, ', <Sig (self, other)>), ('__rtruediv__, ', <Sig (self, other)>), ('__rdiv__, ', <Sig (self, other)>), ('__rpow__, ', <Sig (self, other)>), ('__rrshift__, ', <Sig (self, other)>), ('__rand__, ', <Sig (self, other)>), ('__rfloordiv__, ', <Sig (self, other)>), ('__rxor__, ', <Sig (self, other)>), ('__rlshift__, ', <Sig (self, other)>)), method_factory=<function _dflt_method_factory>)[source]¶
Add tracing methods to
cls, each appending(name, *args)to the instance’s.trace.By default the methods are the operator, dict and reflected-operator dunders, made by
method_factory(name, sig); each returns the instance so calls can be chained.