-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy path_ls.py
More file actions
115 lines (93 loc) · 3.92 KB
/
Copy path_ls.py
File metadata and controls
115 lines (93 loc) · 3.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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
try:
import pandas as pd
except ImportError:
has_pandas = False
else:
has_pandas = True
# sentinel
BAD = object()
def ls(obj, attr=None, depth=None, dunder=False, under=True, unsafe=False):
"""
Run a recursive find for a named attribute
:param obj: Root object to search
:param attr: Name of the attribute to search for
:param depth: Maximum search depth, defaults to unlimited
:param dunder: If True double underscore prefixed attributes are ignored, default is disabled
:param under: If True single underscore prefixed attributes are ignored, default is enabled
:return: None
"""
if depth is None:
depth = 1
for attr, value in iter_ls(obj, attr=attr, depth=depth,
dunder=dunder, under=under, unsafe=unsafe):
size = ''
if has_pandas and isinstance(value, pd.DataFrame):
size = '{0}x{1}'.format(*value.shape)
elif hasattr(value, '__len__'):
size = len(value)
type_name = type(value).__name__
print('{:<60}{:>20}{:>7}'.format(attr, type_name, size))
def xdir(obj, attr=None, depth=None, dunder=False, under=True):
if depth is None and attr is None:
depth = 1
return list((x[0] for x in iter_ls(obj, attr=attr, depth=depth,
dunder=dunder, under=under)))
def iter_ls(obj, attr=None, depth=1, dunder=False, under=True, unsafe=False,
visited=None, current_depth=1, path=''):
visited = visited or set()
if (depth is None) or (current_depth <= depth):
if id(obj) not in visited:
visited.add(id(obj))
callbacks = []
def include(a):
for c in callbacks:
if not c(a):
return False
return True
def exclude_dunders(a):
return not a.startswith('__')
def exclude_unders(a):
return not a.startswith('_')
def attr_filter_callback(a):
return attr in a
if attr:
callbacks.append(attr_filter_callback)
if not dunder:
callbacks.append(exclude_dunders)
if not under:
callbacks.append(exclude_unders)
if isinstance(obj, dict):
attrs = [str(k) for k in obj.keys()]
elif has_pandas and isinstance(obj, pd.DataFrame):
attrs = [str(c) for c in obj.columns]
else:
attrs = dir(obj)
for a in attrs:
if isinstance(obj, dict) or (has_pandas and isinstance(obj, pd.DataFrame)):
new_path = path + '[%r]' % a
else:
if path:
new_path = '.'.join([path, a])
else:
new_path = a
try:
if isinstance(obj, dict) or (has_pandas and isinstance(obj, pd.DataFrame)):
val = obj[a]
else:
if unsafe or not isinstance(getattr(type(obj), a, None), property):
val = getattr(obj, a)
else:
val = BAD
except Exception as exc:
val = BAD
if include(a):
suffix = ''
if val is not BAD:
if callable(val):
suffix = '()'
yield new_path + suffix, val
if val is not BAD and not a.startswith('__'):
for sub_a, sub_val in iter_ls(val, attr=attr, depth=depth, dunder=dunder,
under=under, unsafe=unsafe, visited=visited,
current_depth=current_depth + 1, path=new_path):
yield sub_a, sub_val