From 09dd44a19a89c21c4df4806cb3e485f122537ca5 Mon Sep 17 00:00:00 2001 From: Matthias Diener <mdiener@illinois.edu> Date: Mon, 13 Mar 2023 19:00:23 -0500 Subject: [PATCH] typedump: type improvements (#174) * typedump: type improvements * isort * spelling * a few more tests * try to tackle format strings --- pytools/__init__.py | 58 ++++++++++++++++++++++++++++++++++---------- test/test_pytools.py | 32 ++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 13 deletions(-) diff --git a/pytools/__init__.py b/pytools/__init__.py index 89baa9f..a2b2d41 100644 --- a/pytools/__init__.py +++ b/pytools/__init__.py @@ -36,8 +36,8 @@ import sys from functools import reduce, wraps from sys import intern from typing import ( - Any, Callable, ClassVar, Dict, Generic, Hashable, Iterable, List, Optional, Set, - Tuple, TypeVar, Union) + Any, Callable, ClassVar, Dict, Generic, Hashable, Iterable, List, Mapping, + Optional, Set, Tuple, Type, TypeVar, Union) try: @@ -2016,7 +2016,24 @@ class StderrToStdout: del self.stderr_backup -def typedump(val, max_seq=5, special_handlers=None): +def typedump(val: Any, max_seq: int = 5, + special_handlers: Optional[Mapping[Type, Callable]] = None, + fully_qualified_name: bool = True) -> str: + """ + Return a string representation of the type of *val*, recursing into + iterable objects. + + :arg val: The object for which the type should be returned. + :arg max_seq: For iterable objects, the maximum number of elements to + include in the return string. Lower this value if you get a + :class:`RecursionError`. + :arg special_handlers: An optional mapping of specific types to special + handlers. + :arg fully_qualified_name: Return fully qualified names, that is, + include module names and use ``__qualname__`` instead of ``__name__``. + + :returns: A string representation of the type of *val*. + """ if special_handlers is None: special_handlers = {} @@ -2027,10 +2044,26 @@ def typedump(val, max_seq=5, special_handlers=None): else: return hdlr(val) + def objname(obj: Any) -> str: + if type(obj).__module__ == "builtins": + if fully_qualified_name: + return type(obj).__qualname__ + else: + return type(obj).__name__ + + if fully_qualified_name: + return type(obj).__module__ + "." + type(obj).__qualname__ + else: + return type(obj).__name__ + + # Special handling for 'str' since it is also iterable + if isinstance(val, str): + return "str" + try: len(val) except TypeError: - return type(val).__name__ + return objname(val) else: if isinstance(val, dict): return "{%s}" % ( @@ -2040,17 +2073,16 @@ def typedump(val, max_seq=5, special_handlers=None): try: if len(val) > max_seq: - return "{}({},...)".format( - type(val).__name__, - ",".join(typedump(x, max_seq, special_handlers) - for x in val[:max_seq])) + t = ",".join(typedump(x, max_seq, special_handlers) + for x in val[:max_seq]) + return f"{objname(val)}({t},...)" else: - return "{}({})".format( - type(val).__name__, - ",".join(typedump(x, max_seq, special_handlers) - for x in val)) + t = ",".join(typedump(x, max_seq, special_handlers) + for x in val) + return f"{objname(val)}({t})" + except TypeError: - return val.__class__.__name__ + return objname(val) def invoke_editor(s, filename="edit.txt", descr="the file"): diff --git a/test/test_pytools.py b/test/test_pytools.py index 8398589..df857be 100644 --- a/test/test_pytools.py +++ b/test/test_pytools.py @@ -730,6 +730,38 @@ def test_strtobool(): assert strtobool(None, False) is False +def test_typedump(): + from pytools import typedump + assert typedump("") == "str" + assert typedump("abcdefg") == "str" + assert typedump(5) == "int" + + assert typedump((5.0, 4)) == "tuple(float,int)" + assert typedump([5, 4]) == "list(int,int)" + assert typedump({5, 4}) == "set(int,int)" + assert typedump(frozenset((1, 2, 3))) == "frozenset(int,int,int)" + + assert typedump([5, 4, 3, 2, 1]) == "list(int,int,int,int,int)" + assert typedump([5, 4, 3, 2, 1, 0]) == "list(int,int,int,int,int,...)" + assert typedump([5, 4, 3, 2, 1, 0], max_seq=6) == "list(int,int,int,int,int,int)" + + assert typedump({5: 42, 7: 43}) == "{'5': int, '7': int}" + + class C: + class D: + pass + + assert typedump(C()) == "test_pytools.test_typedump.<locals>.C" + assert typedump(C.D()) == "test_pytools.test_typedump.<locals>.C.D" + assert typedump(C.D(), fully_qualified_name=False) == "D" + + from pytools.datatable import DataTable + t = DataTable(column_names=[]) + + assert typedump(t) == "pytools.datatable.DataTable()" + assert typedump(t, special_handlers={type(t): lambda x: "foo"}) == "foo" + + if __name__ == "__main__": if len(sys.argv) > 1: exec(sys.argv[1]) -- GitLab