AttrDict / AD

from torch_snippets import AD
env: AD_MAX_ITEMS=30

Basic Invocations

Just replace dict with AD

AD is simply a dictionary, so you can create one in the same way you would create any dictionary.

ad = AD(
    x="1",
    y=2.0,
    z=3 + 5j,
    k=AD(
        l={"you": "can", "nest": "dictionaries"},
        m=2,
        n=3,
        _tuple=(1, 2, 3, (4, 5, 6)),
        _set={1, 2, 3},
        _list=[1, 2, 3, [4, 5, 6]],
    ),
)

print(ad)

```↯ AttrDict ↯
x - 1 (🏷️ str)
y - 2.0 (🏷️ float)
z - (3+5j) (🏷️ complex)
k
  l
    you - can (🏷️ str)
    nest - dictionaries (🏷️ str)
  m - 2 (🏷️ int)
  n - 3 (🏷️ int)
  _tuple()
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
    3()
      0 - 4 (🏷️ int)
      1 - 5 (🏷️ int)
      2 - 6 (🏷️ int)
  _set{}
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
  _list[]
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
    3[]
      0 - 4 (🏷️ int)
      1 - 5 (🏷️ int)
      2 - 6 (🏷️ int)

```

AD supports args

AD(x,y,z) == AD(x=x, y=y, z=z)
If you want to create a dictionary from variables, there’s a good chance that the key you’d want to assign to that variable is the same as your variable name. AttrDict introspects the args intelligently (thanks to icecream module) and assigns the variable itself as the key name

x, y, z = "1", 2.0, 3 + 5j
l, m, n = (
    {"y": {"c": {"n": "d", "greet": "hello", "o": [1, 2, 3, {"m": {"n": [4, 5, 6]}}]}}},
    2,
    3,
)
_tuple=(1, 2, 3, (4, 5, 6))
_set={1, 2, 3}
_list=[1, 2, 3, [4, 5, 6]]
k = AD(l, m, n, _tuple, _set, _list)
ad = AD(x, y, z, k)
print(ad)

```↯ AttrDict ↯
x - 1 (🏷️ str)
y - 2.0 (🏷️ float)
z - (3+5j) (🏷️ complex)
k
  l
    y
      c
        n - d (🏷️ str)
        greet - hello (🏷️ str)
        o[]
          0 - 1 (🏷️ int)
          1 - 2 (🏷️ int)
          2 - 3 (🏷️ int)
          3
            m
              n[]
                0 - 4 (🏷️ int)
                1 - 5 (🏷️ int)
                2 - 6 (🏷️ int)
  m - 2 (🏷️ int)
  n - 3 (🏷️ int)
  _tuple()
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
    3()
      0 - 4 (🏷️ int)
      1 - 5 (🏷️ int)
      2 - 6 (🏷️ int)
  _set{}
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
  _list[]
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
    3[]
      0 - 4 (🏷️ int)
      1 - 5 (🏷️ int)
      2 - 6 (🏷️ int)

```

Don’t worry if you want to control the key names, you can still give your own kwargs, or even mix it up with both args and kwargs

_tuple=(1, 2, 3, (4, 5, 6))
_set={1, 2, 3}
_list=[1, 2, 3, [4, 5, 6]]
k = AD(l, m, n, _tuple, _set, _list)
ad = AD(x, y, zed=z, kay=k)
print(ad)

```↯ AttrDict ↯
x - 1 (🏷️ str)
y - 2.0 (🏷️ float)
zed - (3+5j) (🏷️ complex)
kay
  l
    y
      c
        n - d (🏷️ str)
        greet - hello (🏷️ str)
        o[]
          0 - 1 (🏷️ int)
          1 - 2 (🏷️ int)
          2 - 3 (🏷️ int)
          3
            m
              n[]
                0 - 4 (🏷️ int)
                1 - 5 (🏷️ int)
                2 - 6 (🏷️ int)
  m - 2 (🏷️ int)
  n - 3 (🏷️ int)
  _tuple()
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
    3()
      0 - 4 (🏷️ int)
      1 - 5 (🏷️ int)
      2 - 6 (🏷️ int)
  _set{}
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
  _list[]
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
    3[]
      0 - 4 (🏷️ int)
      1 - 5 (🏷️ int)
      2 - 6 (🏷️ int)

```

Methods

Since AD is an extension of a dictionary, all the dictionary methods such as .keys(), .values(), .items() work exactly as expected

.keys

ad.keys()
dict_keys(['x', 'y', 'zed', 'kay'])

.values

ad.values()
dict_values(['1', 2.0, (3+5j), 
```↯ AttrDict ↯
l
  y
    c
      n - d (🏷️ str)
      greet - hello (🏷️ str)
      o[]
        0 - 1 (🏷️ int)
        1 - 2 (🏷️ int)
        2 - 3 (🏷️ int)
        3
          m
            n[]
              0 - 4 (🏷️ int)
              1 - 5 (🏷️ int)
              2 - 6 (🏷️ int)
m - 2 (🏷️ int)
n - 3 (🏷️ int)
_tuple()
  0 - 1 (🏷️ int)
  1 - 2 (🏷️ int)
  2 - 3 (🏷️ int)
  3()
    0 - 4 (🏷️ int)
    1 - 5 (🏷️ int)
    2 - 6 (🏷️ int)
_set{}
  0 - 1 (🏷️ int)
  1 - 2 (🏷️ int)
  2 - 3 (🏷️ int)
_list[]
  0 - 1 (🏷️ int)
  1 - 2 (🏷️ int)
  2 - 3 (🏷️ int)
  3[]
    0 - 4 (🏷️ int)
    1 - 5 (🏷️ int)
    2 - 6 (🏷️ int)

```
])

.items

ad.items()
dict_items([('x', '1'), ('y', 2.0), ('zed', (3+5j)), ('kay', 
```↯ AttrDict ↯
l
  y
    c
      n - d (🏷️ str)
      greet - hello (🏷️ str)
      o[]
        0 - 1 (🏷️ int)
        1 - 2 (🏷️ int)
        2 - 3 (🏷️ int)
        3
          m
            n[]
              0 - 4 (🏷️ int)
              1 - 5 (🏷️ int)
              2 - 6 (🏷️ int)
m - 2 (🏷️ int)
n - 3 (🏷️ int)
_tuple()
  0 - 1 (🏷️ int)
  1 - 2 (🏷️ int)
  2 - 3 (🏷️ int)
  3()
    0 - 4 (🏷️ int)
    1 - 5 (🏷️ int)
    2 - 6 (🏷️ int)
_set{}
  0 - 1 (🏷️ int)
  1 - 2 (🏷️ int)
  2 - 3 (🏷️ int)
_list[]
  0 - 1 (🏷️ int)
  1 - 2 (🏷️ int)
  2 - 3 (🏷️ int)
  3[]
    0 - 4 (🏷️ int)
    1 - 5 (🏷️ int)
    2 - 6 (🏷️ int)

```
)])

Use .d/.dict()/.to_dict() to Create a Vanilla Dict

All the three are identical. The latter two are present mostly for backward compatibility.

assert ad.d == ad.dict() == ad.to_dict()
d = ad.d
d
{'x': '1',
 'y': 2.0,
 'zed': (3+5j),
 'kay': {'l': {'y': {'c': {'n': 'd',
     'greet': 'hello',
     'o': (#4) [1,2,3,{'m': {'n': [4, 5, 6]}}]}}},
  'm': 2,
  'n': 3,
  '_tuple': (1, 2, 3, (4, 5, 6)),
  '_set': {1, 2, 3},
  '_list': (#4) [1,2,3,[4, 5, 6]]}}

AD From Vanilla Dict (Another Basic Invocation)

assert AD(ad.d) == ad
ad = AD(ad.d)
ad

```↯ AttrDict ↯
x - 1 (🏷️ str)
y - 2.0 (🏷️ float)
zed - (3+5j) (🏷️ complex)
kay
  l
    y
      c
        n - d (🏷️ str)
        greet - hello (🏷️ str)
        o[]
          0 - 1 (🏷️ int)
          1 - 2 (🏷️ int)
          2 - 3 (🏷️ int)
          3
            m
              n[]
                0 - 4 (🏷️ int)
                1 - 5 (🏷️ int)
                2 - 6 (🏷️ int)
  m - 2 (🏷️ int)
  n - 3 (🏷️ int)
  _tuple()
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
    3()
      0 - 4 (🏷️ int)
      1 - 5 (🏷️ int)
      2 - 6 (🏷️ int)
  _set{}
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
  _list[]
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
    3[]
      0 - 4 (🏷️ int)
      1 - 5 (🏷️ int)
      2 - 6 (🏷️ int)

```

. Accessing a key

As the name of the class suggests, keys can be accessed as if they are attributes

assert ad.x == d["x"]
assert ad.kay.l.y.c.n == d["kay"]["l"]["y"]["c"]["n"]
assert ad.kay.l.y.c.o[3].m.n == d["kay"]["l"]["y"]["c"]["o"][3]["m"]["n"]

in Searching for keys

Highlevel keys are anyway accessable just like, in a normal dictionary

assert "zed" in ad

you can check for presence/absence of nested keys by joining them with a ‘.’

assert "kay.l.y.c.n" in ad

.find_address Find if a key exists and return where it is, i.e., the address of the key

The method always returns a list of addresses

ad.find_address("c")
['kay.l.y.c']
ad.find_address("n")
['kay.l.y.c.n', 'kay.l.y.c.o.3.m.n', 'kay.n']
ad.find_address("hello")
[]

.fetch fetch all the addresses

ad.fetch(ad.find_address("n"))
(#3) ['d',[4, 5, 6],3]
ad.fetch(["kay.l.y.c.n", "kay.l.y.c.o.3.m.n", "kay.n"])
(#3) ['d',[4, 5, 6],3]

.fetch2 fetches all the addresses while preserving the key hierarchy

ad.fetch2(addrs=["kay.l.y.c.n", "kay.l.y.c.o.3.m.n", "kay.n"])

```↯ AttrDict ↯
kay
  l
    y
      c
        n - d (🏷️ str)
        o
          3
            m
              n[]
                0 - 4 (🏷️ int)
                1 - 5 (🏷️ int)
                2 - 6 (🏷️ int)
  n - 3 (🏷️ int)

```

.fetch2 can also directly fetch all the keys at once (by first finding all addresses and then fetching all of them)

ad.fetch2(key="n")

```↯ AttrDict ↯
kay
  l
    y
      c
        n - d (🏷️ str)
        o
          3
            m
              n[]
                0 - 4 (🏷️ int)
                1 - 5 (🏷️ int)
                2 - 6 (🏷️ int)
  n - 3 (🏷️ int)

```

.slice make a dictionary out of all keys present anywhere in the dictionary

ad.slice("n")

```↯ AttrDict ↯
kay.l.y.c.n - d (🏷️ str)
kay.l.y.c.o.3.m.n[]
  0 - 4 (🏷️ int)
  1 - 5 (🏷️ int)
  2 - 6 (🏷️ int)
kay.n - 3 (🏷️ int)

```

.get

Get works as usual but can also work with nested keys

ad.get("x", 10)
'1'
ad.get("yolo", 10)
10
ad.get("kay.l.y.c", 20)

```↯ AttrDict ↯
n - d (🏷️ str)
greet - hello (🏷️ str)
o[]
  0 - 1 (🏷️ int)
  1 - 2 (🏷️ int)
  2 - 3 (🏷️ int)
  3
    m
      n[]
        0 - 4 (🏷️ int)
        1 - 5 (🏷️ int)
        2 - 6 (🏷️ int)

```
ad.get("kay.l.y.hello", 20)
20

.set

Will also work similarly as get

ad.set("bee.sea.dee", "e")
ad

```↯ AttrDict ↯
x - 1 (🏷️ str)
y - 2.0 (🏷️ float)
zed - (3+5j) (🏷️ complex)
kay
  l
    y
      c
        n - d (🏷️ str)
        greet - hello (🏷️ str)
        o[]
          0 - 1 (🏷️ int)
          1 - 2 (🏷️ int)
          2 - 3 (🏷️ int)
          3
            m
              n[]
                0 - 4 (🏷️ int)
                1 - 5 (🏷️ int)
                2 - 6 (🏷️ int)
  m - 2 (🏷️ int)
  n - 3 (🏷️ int)
  _tuple()
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
    3()
      0 - 4 (🏷️ int)
      1 - 5 (🏷️ int)
      2 - 6 (🏷️ int)
  _set{}
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
  _list[]
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
    3[]
      0 - 4 (🏷️ int)
      1 - 5 (🏷️ int)
      2 - 6 (🏷️ int)
bee
  sea
    dee - e (🏷️ str)

```

.map Map a function on all leaf nodes

from torch_snippets import h4

def into_two(x):
    try:
        return 2 * x
    except:
        return x

ad2 = ad.map(into_two)
h4("Original")
print(ad)
h4("New")
print(ad2)

Original


```↯ AttrDict ↯
x - 1 (🏷️ str)
y - 2.0 (🏷️ float)
zed - (3+5j) (🏷️ complex)
kay
  l
    y
      c
        n - d (🏷️ str)
        greet - hello (🏷️ str)
        o[]
          0 - 1 (🏷️ int)
          1 - 2 (🏷️ int)
          2 - 3 (🏷️ int)
          3
            m
              n[]
                0 - 4 (🏷️ int)
                1 - 5 (🏷️ int)
                2 - 6 (🏷️ int)
  m - 2 (🏷️ int)
  n - 3 (🏷️ int)
  _tuple()
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
    3()
      0 - 4 (🏷️ int)
      1 - 5 (🏷️ int)
      2 - 6 (🏷️ int)
  _set{}
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
  _list[]
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
    3[]
      0 - 4 (🏷️ int)
      1 - 5 (🏷️ int)
      2 - 6 (🏷️ int)
bee
  sea
    dee - e (🏷️ str)

```

New


```↯ AttrDict ↯
x - 11 (🏷️ str)
y - 4.0 (🏷️ float)
zed - (6+10j) (🏷️ complex)
kay
  l
    y
      c
        n - dd (🏷️ str)
        greet - hellohello (🏷️ str)
        o[]
          0 - 2 (🏷️ int)
          1 - 4 (🏷️ int)
          2 - 6 (🏷️ int)
          3
            m
              n[]
                0 - 8 (🏷️ int)
                1 - 10 (🏷️ int)
                2 - 12 (🏷️ int)
  m - 4 (🏷️ int)
  n - 6 (🏷️ int)
  _tuple[]
    0 - 2 (🏷️ int)
    1 - 4 (🏷️ int)
    2 - 6 (🏷️ int)
    3()
      0 - 4 (🏷️ int)
      1 - 5 (🏷️ int)
      2 - 6 (🏷️ int)
      3 - 4 (🏷️ int)
      4 - 5 (🏷️ int)
      5 - 6 (🏷️ int)
  _set[]
    0 - 2 (🏷️ int)
    1 - 4 (🏷️ int)
    2 - 6 (🏷️ int)
  _list[]
    0 - 2 (🏷️ int)
    1 - 4 (🏷️ int)
    2 - 6 (🏷️ int)
    3[]
      0 - 4 (🏷️ int)
      1 - 5 (🏷️ int)
      2 - 6 (🏷️ int)
bee
  sea
    dee - ee (🏷️ str)

```

.trymap Map a function on all leaf nodes and preserve the leaf as it is, if the function fails

def plus_thousand(x):
    return x + 1000


ad2 = ad.trymap(plus_thousand)
print(ad2)

```↯ AttrDict ↯
x - 1 (🏷️ str)
y - 1002.0 (🏷️ float)
zed - (1003+5j) (🏷️ complex)
kay
  l
    y
      c
        n - d (🏷️ str)
        greet - hello (🏷️ str)
        o[]
          0 - 1001 (🏷️ int)
          1 - 1002 (🏷️ int)
          2 - 1003 (🏷️ int)
          3
            m
              n[]
                0 - 1004 (🏷️ int)
                1 - 1005 (🏷️ int)
                2 - 1006 (🏷️ int)
  m - 1002 (🏷️ int)
  n - 1003 (🏷️ int)
  _tuple[]
    0 - 1001 (🏷️ int)
    1 - 1002 (🏷️ int)
    2 - 1003 (🏷️ int)
    3()
      0 - 4 (🏷️ int)
      1 - 5 (🏷️ int)
      2 - 6 (🏷️ int)
  _set[]
    0 - 1001 (🏷️ int)
    1 - 1002 (🏷️ int)
    2 - 1003 (🏷️ int)
  _list[]
    0 - 1001 (🏷️ int)
    1 - 1002 (🏷️ int)
    2 - 1003 (🏷️ int)
    3[]
      0 - 4 (🏷️ int)
      1 - 5 (🏷️ int)
      2 - 6 (🏷️ int)
      3 - 1000 (🏷️ int)
bee
  sea
    dee - e (🏷️ str)

```

.drop Drop a key, even if it is present somewhere nested

ad.find_address("n")
['kay.l.y.c.n', 'kay.l.y.c.o.3.m.n', 'kay.n']
from copy import deepcopy

ad2 = deepcopy(ad)
ad2.drop("n")
ad2

```↯ AttrDict ↯
x - 1 (🏷️ str)
y - 2.0 (🏷️ float)
zed - (3+5j) (🏷️ complex)
kay
  l
    y
      c
        greet - hello (🏷️ str)
        o[]
          0 - 1 (🏷️ int)
          1 - 2 (🏷️ int)
          2 - 3 (🏷️ int)
          3
            m
  m - 2 (🏷️ int)
  _tuple()
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
    3()
      0 - 4 (🏷️ int)
      1 - 5 (🏷️ int)
      2 - 6 (🏷️ int)
  _set{}
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
  _list[]
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
    3[]
      0 - 4 (🏷️ int)
      1 - 5 (🏷️ int)
      2 - 6 (🏷️ int)
bee
  sea
    dee - e (🏷️ str)

```

.update

ad2.update(
    {"y": "γ", "greek": {"alpha": "α", "beta": "β", "gamma": [1, 2, {"theta": "θ"}]}}
)
ad2

```↯ AttrDict ↯
x - 1 (🏷️ str)
y - γ (🏷️ str)
zed - (3+5j) (🏷️ complex)
kay
  l
    y
      c
        greet - hello (🏷️ str)
        o[]
          0 - 1 (🏷️ int)
          1 - 2 (🏷️ int)
          2 - 3 (🏷️ int)
          3
            m
  m - 2 (🏷️ int)
  _tuple()
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
    3()
      0 - 4 (🏷️ int)
      1 - 5 (🏷️ int)
      2 - 6 (🏷️ int)
  _set{}
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
  _list[]
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
    3[]
      0 - 4 (🏷️ int)
      1 - 5 (🏷️ int)
      2 - 6 (🏷️ int)
bee
  sea
    dee - e (🏷️ str)
greek
  alpha - α (🏷️ str)
  beta - β (🏷️ str)
  gamma[]
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2
      theta - θ (🏷️ str)

```

.flatten will flatten all the nests into a single level

ad2.flatten()

```↯ AttrDict ↯
x - 1 (🏷️ str)
y - γ (🏷️ str)
zed - (3+5j) (🏷️ complex)
kay.l.y.c.greet - hello (🏷️ str)
kay.l.y.c.o.0 - 1 (🏷️ int)
kay.l.y.c.o.1 - 2 (🏷️ int)
kay.l.y.c.o.2 - 3 (🏷️ int)
kay.m - 2 (🏷️ int)
kay._tuple.0 - 1 (🏷️ int)
kay._tuple.1 - 2 (🏷️ int)
kay._tuple.2 - 3 (🏷️ int)
kay._tuple.3()
  0 - 4 (🏷️ int)
  1 - 5 (🏷️ int)
  2 - 6 (🏷️ int)
kay._set.0 - 1 (🏷️ int)
kay._set.1 - 2 (🏷️ int)
kay._set.2 - 3 (🏷️ int)
kay._list.0 - 1 (🏷️ int)
kay._list.1 - 2 (🏷️ int)
kay._list.2 - 3 (🏷️ int)
kay._list.3[]
  0 - 4 (🏷️ int)
  1 - 5 (🏷️ int)
  2 - 6 (🏷️ int)
bee.sea.dee - e (🏷️ str)
greek.alpha - α (🏷️ str)
greek.beta - β (🏷️ str)
greek.gamma.0 - 1 (🏷️ int)
greek.gamma.1 - 2 (🏷️ int)
greek.gamma.2.theta - θ (🏷️ str)

```

.flatten_and_make_dataframe is self explanatory

ad2.flatten_and_make_dataframe()
0 1 2 3 4 5 6
0 x 1 None None None None NaN
1 y γ None None None None NaN
2 zed (3+5j) None None None None NaN
3 kay l y c greet hello NaN
4 kay l y c o 0 1.0
5 kay l y c o 1 2.0
6 kay l y c o 2 3.0
7 kay m 2 None None None NaN
8 kay _tuple 0 1 None None NaN
9 kay _tuple 1 2 None None NaN
10 kay _tuple 2 3 None None NaN
11 kay _tuple 3 (4, 5, 6) None None NaN
12 kay _set 0 1 None None NaN
13 kay _set 1 2 None None NaN
14 kay _set 2 3 None None NaN
15 kay _list 0 1 None None NaN
16 kay _list 1 2 None None NaN
17 kay _list 2 3 None None NaN
18 kay _list 3 [4, 5, 6] None None NaN
19 bee sea dee e None None NaN
20 greek alpha α None None None NaN
21 greek beta β None None None NaN
22 greek gamma 0 1 None None NaN
23 greek gamma 1 2 None None NaN
24 greek gamma 2 theta θ None NaN

.diff on other ADs/dicts

a = AD(w=0, x=1, y=3, _list=[1,2,3], _set={1,2,3}, _tuple=(1,2,3), z=10, a=AD(a=1))
b = AD(w=0, x=2, z=2, _list=[1,3,4,5], _set={1,3,4}, _tuple=(1,3,4,5), k=20, b=AD(b=1))
a.diff(b)

```↯ AttrDict ↯
dictionary_item_added - SetOrdered(["root['k']", "root['b']"]) (🏷️ SetOrdered)
dictionary_item_removed - SetOrdered(["root['y']", "root['a']"]) (🏷️ SetOrdered)
values_changed
  root['x']
    new_value - 2 (🏷️ int)
    old_value - 1 (🏷️ int)
  root['z']
    new_value - 2 (🏷️ int)
    old_value - 10 (🏷️ int)
  root['_list'][1]
    new_value - 3 (🏷️ int)
    old_value - 2 (🏷️ int)
  root['_list'][2]
    new_value - 4 (🏷️ int)
    old_value - 3 (🏷️ int)
  root['_tuple'][1]
    new_value - 3 (🏷️ int)
    old_value - 2 (🏷️ int)
  root['_tuple'][2]
    new_value - 4 (🏷️ int)
    old_value - 3 (🏷️ int)
iterable_item_added
  root['_list'][3] - 5 (🏷️ int)
  root['_tuple'][3] - 5 (🏷️ int)
set_item_removed - SetOrdered(["root['_set'][2]"]) (🏷️ SetOrdered)
set_item_added - SetOrdered(["root['_set'][4]"]) (🏷️ SetOrdered)

```

Display exotic objects

from dataclasses import dataclass

@dataclass
class DC:
    w: int
    x: int
    y: int
    _list: list
    _set: set

dc = DC(1,2,3,[1,2,3],{1,2,3})
print(dc)
DC(w=1, x=2, y=3, _list=[1, 2, 3], _set={1, 2, 3})

not bad, but we can always do this

print(AD(dc))

```↯ AttrDict ↯
dc(🏷️ DC:dataclass)
  w - 1 (🏷️ int)
  x - 2 (🏷️ int)
  y - 3 (🏷️ int)
  _list[]
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
  _set{}
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)

```
import pandas as pd
import numpy as np

df = pd.DataFrame({'x': np.random.randint(0, 100, size=10), 'y': np.random.uniform(0, 1, size=10)})
print(df)
    x         y
0  76  0.492719
1  89  0.091474
2   9  0.428809
3  46  0.359179
4  43  0.839773
5  58  0.344325
6  66  0.563592
7  33  0.512554
8  81  0.813390
9  44  0.932230
print(AD(df))

```↯ AttrDict ↯
df - DataFrame - shape (10, 2) - columns Index(['x', 'y'], dtype='object') - ID:#06c311

```

Can show torch and numpy tensors in a really pretty way thanks to lovely-tensors module

from torch_snippets import *
init_torch()

t1 = torch.Tensor([1,2,3]).long()
t2 = torch.Tensor([1,2,3])
n1 = np.array([4,5,6])
n1 = np.array([4,5,6]).astype(float)
n2 = np.array([4,5,6]).astype(np.float32)
ts = AD(t=AD(t1, t2), n=AD(n1, n2))
ts

```↯ AttrDict ↯
t
  t1 - 🔦tensor[3] i64 x∈[1, 3] μ=2.000 σ=1.000 [1, 2, 3] - ID:#e2e2033a
  t2 - 🔦tensor[3] x∈[1.000, 3.000] μ=2.000 σ=1.000 [1.000, 2.000, 3.000] - ID:#8e628779
n
  n1 - np.tensor[3] f64 x∈[4.000, 6.000] μ=5.000 σ=1.000 [4.000, 5.000, 6.000] - ID:#88d24967
  n2 - np.tensor[3] x∈[4.000, 6.000] μ=5.000 σ=1.000 [4.000, 5.000, 6.000] - ID:#3928b709

```
from torch_snippets import P
p = AD(p=P().resolve())
p

```↯ AttrDict ↯
p - /Users/yeshwanth/Code/Personal/torch_snippets/nbs (🏷️ PosixPath)

```
small_string = '123'
big_string = '123'*100
multiline_big_string = '\n'.join([big_string]*100)
strs = AD(small_string, big_string, multiline_big_string)
strs

```↯ AttrDict ↯
small_string - 123 (🏷️ str)
big_string - 12312312312312312312312312312312312.........23123123123123123123123123123123123 (🏷️ str)
multiline_big_string - ↓
  ```
  12312312312312312312312312312312312 ...
  ...
  ...
  ...
  ... 23123123123123123123123123123123123
  ``` (🏷️ Multiline str)

```

And cmbining all of them we have

AD(dc, df, ts, p, strs)

```↯ AttrDict ↯
dc(🏷️ DC:dataclass)
  w - 1 (🏷️ int)
  x - 2 (🏷️ int)
  y - 3 (🏷️ int)
  _list[]
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
  _set{}
    0 - 1 (🏷️ int)
    1 - 2 (🏷️ int)
    2 - 3 (🏷️ int)
df - DataFrame - shape (10, 2) - columns Index(['x', 'y'], dtype='object') - ID:#06c311
ts
  t
    t1 - 🔦tensor[3] i64 x∈[1, 3] μ=2.000 σ=1.000 [1, 2, 3] - ID:#e2e2033a
    t2 - 🔦tensor[3] x∈[1.000, 3.000] μ=2.000 σ=1.000 [1.000, 2.000, 3.000] - ID:#8e628779
  n
    n1 - np.tensor[3] f64 x∈[4.000, 6.000] μ=5.000 σ=1.000 [4.000, 5.000, 6.000] - ID:#88d24967
    n2 - np.tensor[3] x∈[4.000, 6.000] μ=5.000 σ=1.000 [4.000, 5.000, 6.000] - ID:#3928b709
p
  p - /Users/yeshwanth/Code/Personal/torch_snippets/nbs (🏷️ PosixPath)
strs
  small_string - 123 (🏷️ str)
  big_string - 12312312312312312312312312312312312.........23123123123123123123123123123123123 (🏷️ str)
  multiline_big_string - ↓
    ```
    12312312312312312312312312312312312 ...
    ...
    ...
    ...
    ... 23123123123123123123123123123123123
    ``` (🏷️ Multiline str)

```