def universal_summary(x, depth=0, max_depth=4, max_items=5, max_chars=350): """ Provides a concise summary of any Python object. Args: x: Any Python object depth: Current recursion depth (internal use) max_depth: Maximum recursion depth max_items: Maximum number of items to show for collections max_chars: Maximum characters in the final output Returns: A string summary of the object """ import inspect # Track the full result and truncate at the end result = [] def append(s): result.append(str(s)) def get_type_name(obj): return type(obj).__name__ def summarize_collection(collection, open_char, close_char): if depth >= max_depth: append(f"{open_char}...{close_char}") return append(open_char) items = list(collection) for i, item in enumerate(items[:max_items]): if i > 0: append(", ") universal_summary(item, depth + 1, max_depth, max_items, max_chars) if len(items) > max_items: append(f", ... ({len(items) - max_items} more)") append(close_char) def summarize_mapping(mapping): if depth >= max_depth: append("{...}") return append("{") items = list(mapping.items()) for i, (k, v) in enumerate(items[:max_items]): if i > 0: append(", ") universal_summary(k, depth + 1, max_depth, max_items, max_chars) append(": ") universal_summary(v, depth + 1, max_depth, max_items, max_chars) if len(items) > max_items: append(f", ... ({len(items) - max_items} more)") append("}") def summarize_class_instance(obj): cls = type(obj) class_hierarchy = [] for c in inspect.getmro(cls): if c is not object: class_hierarchy.append(c.__name__) append(f"<{' -> '.join(class_hierarchy)} instance") # Show a few methods methods = [name for name, value in inspect.getmembers(obj, inspect.ismethod) if not name.startswith('__')] if methods: methods_str = ", ".join(methods[:max_items]) if len(methods) > max_items: methods_str += f", ... ({len(methods) - max_items} more)" append(f" methods: [{methods_str}]") # Try to show some attributes try: attrs = vars(obj) if attrs and depth < max_depth: append(" attrs: ") summarize_mapping(attrs) except: pass append(">") # Handle different types if x is None: append("None") elif isinstance(x, (bool, int, float, str)): append(repr(x)) elif isinstance(x, (list, tuple, set)): collection_type = get_type_name(x) if isinstance(x, list): summarize_collection(x, "[", "]") elif isinstance(x, tuple): summarize_collection(x, "(", ")") elif isinstance(x, set): summarize_collection(x, "{", "}") elif isinstance(x, dict): summarize_mapping(x) else: # Try to handle special types type_name = get_type_name(x) # NumPy arrays if type_name == 'ndarray': append(f"ndarray(shape={x.shape}, dtype={x.dtype}") if x.size > 0 and depth < max_depth: append(", values=[") flat_x = x.flatten() sample_values = flat_x[:max_items] for i, val in enumerate(sample_values): if i > 0: append(", ") append(val) if flat_x.size > max_items: append(f", ... ({flat_x.size - max_items} more)") append("]") append(")") # PyTorch tensors elif 'Tensor' in type_name: try: shape = tuple(x.shape) dtype = str(x.dtype).split(".")[-1] append(f"Tensor(shape={shape}, dtype={dtype}") if x.numel() > 0 and depth < max_depth: append(", values=[") # Try to convert to numpy first for safer handling try: flat_x = x.detach().cpu().numpy().flatten() except: flat_x = x.flatten().tolist() sample_values = flat_x[:max_items] for i, val in enumerate(sample_values): if i > 0: append(", ") append(val) if len(flat_x) > max_items: append(f", ... ({len(flat_x) - max_items} more)") append("]") append(")") except: append(f"<{type_name} object>") # Other class instances else: summarize_class_instance(x) # Join all parts and truncate if needed full_result = "".join(result) if len(full_result) > max_chars: return full_result[:max_chars-3] + "..." return full_result