import xinspect
get_func_kwargs = xinspect.get_func_kwargs
[docs]
def get_stack_frame(N=0, strict=True):
"""
Args:
N (int): N=0 means the frame you called this function in.
N=1 is the parent frame.
strict (bool): (default = True)
"""
import inspect
frame_cur = inspect.currentframe()
for idx in range(N + 1):
# always skip the frame of this function
frame_next = frame_cur.f_back # type: ignore
if frame_next is None:
if strict:
raise AssertionError('Frame level {:!r} is root'.format(idx))
else:
break
frame_cur = frame_next
return frame_cur
# if False:
# import sys
# import os
# if hasattr(sys, "_getframe"):
# def currentframe():
# return sys._getframe(1)
# else: # pragma: no cover
# def currentframe():
# """Return the frame object for the caller's stack frame."""
# try:
# raise Exception
# except Exception:
# return sys.exc_info()[2].tb_frame.f_back
# #
# # _srcfile is used when walking the stack to check when we've got the first
# # caller stack frame, by skipping frames whose filename is that of this
# # module's source. It therefore should contain the filename of this module's
# # source file.
# #
# # Ordinarily we would use __file__ for this, but frozen modules don't always
# # have __file__ set, for some reason (see Issue #21736). Thus, we get the
# # filename from a handy code object from a function defined in this module.
# # (There's no particular reason for picking addLevelName.)
# #
# _srcfile = os.path.normcase(currentframe.__code__.co_filename)
# # _srcfile is only used in conjunction with sys._getframe().
# # Setting _srcfile to None will prevent findCaller() from being called. This
# # way, you can avoid the overhead of fetching caller information.
# # The following is based on warnings._is_internal_frame. It makes sure that
# # frames of the import mechanism are skipped when logging at module level and
# # using a stacklevel value greater than one.
# def _is_internal_frame(frame):
# """Signal whether the frame is a CPython or logging module internal."""
# filename = os.path.normcase(frame.f_code.co_filename)
# return filename == _srcfile or (
# "importlib" in filename and "_bootstrap" in filename
# )
# def get_stack_frame2(stacklevel=1):
# """
# Based on findCaller code in stdlib logging
# """
# f = currentframe()
# #On some versions of IronPython, currentframe() returns None if
# #IronPython isn't run with -X:Frames.
# if f is None:
# return "(unknown file)", 0, "(unknown function)", None
# while stacklevel > 0:
# next_f = f.f_back
# if next_f is None:
# ## We've got options here.
# ## If we want to use the last (deepest) frame:
# break
# ## If we want to mimic the warnings module:
# #return ("sys", 1, "(unknown function)", None)
# ## If we want to be pedantic:
# #raise ValueError("call stack is not deep enough")
# f = next_f
# if not _is_internal_frame(f):
# stacklevel -= 1
# return f
[docs]
def distext(obj):
"""
Like dis.dis, but returns the text
Args:
obj : a compiled object (e.g. a function, class, generator, etc...)
Returns:
str: text of the python byte code
Example:
>>> from xdev.introspect import * # NOQA
>>> import ubelt as ub
>>> code = ub.codeblock(
'''
def foo(a, b):
print('hello')
if __debug__:
if a is None or b is None:
raise ValueError
print('world')
# This is not entirely optimized away
if __debug__ and (a is None or b is None):
raise ValueError
return a + b
''')
>>> obj = compile(code, filename='<memory>', mode='exec', dont_inherit=True, optimize=0)
>>> print(' ** UNOPTIMIZED')
>>> text = distext(obj)
>>> print(text)
>>> print(' ** OPTIMIZED')
>>> obj = compile(code, filename='<memory>', mode='exec', dont_inherit=True, optimize=1)
>>> text = distext(obj)
>>> print(text)
"""
import dis
import io
file = io.StringIO()
dis.dis(obj, file=file)
file.seek(0)
text = file.read()
return text
# def import_star(module):
# """
# This is the unholy grail
# Import all contents of the module into the global scope
# Args:
# module (str | module): a path, module name, or module itself
# Example:
# >>> # xdoctest: +DISABLE_DOCTEST
# >>> from xdev.misc import import_star
# >>> module = 'ubelt'
# >>> print(import_star(module).keys())
# """
# import ubelt as ub
# if isinstance(module, str):
# if exists(module):
# module = ub.import_module_from_path(module)
# else:
# module = ub.import_module_from_name(module)
# mod_attrs = {
# key: val for key, val in module.__dict__.items()
# if not key.startswith('_')
# }
# parent_frame = get_stack_frame(N=2)
# print('parent_frame.f_code.co_names = {!r}'.format(parent_frame.f_code.co_names))
# print('parent_frame.f_code.co_name = {!r}'.format(parent_frame.f_code.co_name))
# # Note: locals is usually read only
# # parent_frame.f_locals.update(mod_attrs)
# parent_frame.f_globals.update(mod_attrs)
# return mod_attrs
[docs]
def iter_object_tree(obj):
# ub.IndexableWalker
data = obj.__dict__
prefix = []
list_cls = (list, tuple)
dict_cls = (dict,)
indexable_cls = dict_cls + list_cls
seen_ = set()
stack = [(data, prefix)]
while stack:
_data, _prefix = stack.pop()
# Create an items iterable of depending on the indexable data type
if hasattr(_data, '__dict__'):
items = _data.__dict__.items()
elif isinstance(_data, list_cls):
items = enumerate(_data)
elif isinstance(_data, dict_cls):
items = _data.items()
else:
raise TypeError(type(_data))
for key, value in items:
# Yield the full path to this position and its value
path = _prefix + [key]
message = yield path, value
# If the value at this path is also indexable, then continue
# the traversal, unless the False message was explicitly sent
# by the caller.
if message is False:
# Because the `send` method will return the next value,
# we yield a dummy value so we don't clobber the next
# item in the traversal.
yield None
else:
if isinstance(value, indexable_cls) or hasattr(value, '__dict__'):
objid = id(value)
if objid not in seen_:
seen_.add(objid)
stack.append((value, path))
[docs]
def test_object_pickleability(obj):
# ub.IndexableWalker
import pickle
serialized = pickle.dumps(obj)
recon = pickle.loads(serialized) # NOQA
data = obj.__dict__
prefix = []
list_cls = (list, tuple)
dict_cls = (dict,)
indexable_cls = dict_cls + list_cls
def condition(path, value):
import pickle
dumped = pickle.dumps(value)
try:
recon = pickle.loads(dumped) # NOQA
except Exception:
return True
else:
return False
ignore_types = (float, int, bool, type(None))
return not isinstance(value, ignore_types)
walker = iter_object_tree(obj)
for path, value in walker:
flag = condition(path, value)
if flag:
print('BAD FLAG path = {!r}'.format(path))
walker.send(False)
found = []
keyfn = type
seen_ = set()
stack = [(data, prefix)]
while stack:
_data, _prefix = stack.pop()
# Create an items iterable of depending on the indexable data type
if isinstance(_data, list_cls):
items = enumerate(_data)
elif isinstance(_data, dict_cls):
items = _data.items()
elif hasattr(_data, '__dict__'):
items = _data.__dict__.items()
else:
raise TypeError(type(_data))
for key, value in items:
# Yield the full path to this position and its value
path = _prefix + [key]
flag = condition(path, value)
if flag:
store = keyfn(value)
found.append((path, store))
if isinstance(value, indexable_cls) or hasattr(value, '__dict__'):
objid = id(value)
if objid not in seen_:
seen_.add(objid)
stack.append((value, path))
[docs]
def gen_docstr_from_context(keys, lut):
"""
Ignore:
keys = 'true_dets, pred_dets, iou_lookup, isvalid_lookup, cx_to_matchable_txs, bg_weight, prioritize, iou_thresh_, pdist_priority, cx_to_ancestors, bg_cidx, ignore_classes, max_dets, multiple_assignment'.split(', ')
lut = globals()
"""
for key in keys:
value = lut[key]
typestr = generate_typeannot(value)
print(f'{key} ({typestr}): ')
[docs]
def generate_typeannot(value):
import ubelt as ub
if isinstance(value, (set, list)):
unique_val_types = ub.group_items(value, key=type)
T1 = type(value).__name__
VT = '|'.join([generate_typeannot(vs[0]) for t, vs in unique_val_types.items()])
typestr = f'{T1}[{VT}]'
elif isinstance(value, dict):
unique_key_types = ub.group_items(value.keys(), key=type)
unique_val_types = ub.group_items(value.values(), key=type)
# Oversimplification
kt = '|'.join([generate_typeannot(vs[0]) for t, vs in unique_key_types.items()])
vt = '|'.join([generate_typeannot(vs[0]) for t, vs in unique_val_types.items()])
typestr = f'Dict[{kt}, {vt}]'
else:
typestr = type(value).__name__
return typestr