Skip to content

Commit 09dd44a

Browse files
typedump: type improvements (#174)
* typedump: type improvements * isort * spelling * a few more tests * try to tackle format strings
1 parent 6f96e67 commit 09dd44a

2 files changed

Lines changed: 77 additions & 13 deletions

File tree

pytools/__init__.py

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@
3636
from functools import reduce, wraps
3737
from sys import intern
3838
from typing import (
39-
Any, Callable, ClassVar, Dict, Generic, Hashable, Iterable, List, Optional, Set,
40-
Tuple, TypeVar, Union)
39+
Any, Callable, ClassVar, Dict, Generic, Hashable, Iterable, List, Mapping,
40+
Optional, Set, Tuple, Type, TypeVar, Union)
4141

4242

4343
try:
@@ -2016,7 +2016,24 @@ def __exit__(self, exc_type, exc_val, exc_tb):
20162016
del self.stderr_backup
20172017

20182018

2019-
def typedump(val, max_seq=5, special_handlers=None):
2019+
def typedump(val: Any, max_seq: int = 5,
2020+
special_handlers: Optional[Mapping[Type, Callable]] = None,
2021+
fully_qualified_name: bool = True) -> str:
2022+
"""
2023+
Return a string representation of the type of *val*, recursing into
2024+
iterable objects.
2025+
2026+
:arg val: The object for which the type should be returned.
2027+
:arg max_seq: For iterable objects, the maximum number of elements to
2028+
include in the return string. Lower this value if you get a
2029+
:class:`RecursionError`.
2030+
:arg special_handlers: An optional mapping of specific types to special
2031+
handlers.
2032+
:arg fully_qualified_name: Return fully qualified names, that is,
2033+
include module names and use ``__qualname__`` instead of ``__name__``.
2034+
2035+
:returns: A string representation of the type of *val*.
2036+
"""
20202037
if special_handlers is None:
20212038
special_handlers = {}
20222039

@@ -2027,10 +2044,26 @@ def typedump(val, max_seq=5, special_handlers=None):
20272044
else:
20282045
return hdlr(val)
20292046

2047+
def objname(obj: Any) -> str:
2048+
if type(obj).__module__ == "builtins":
2049+
if fully_qualified_name:
2050+
return type(obj).__qualname__
2051+
else:
2052+
return type(obj).__name__
2053+
2054+
if fully_qualified_name:
2055+
return type(obj).__module__ + "." + type(obj).__qualname__
2056+
else:
2057+
return type(obj).__name__
2058+
2059+
# Special handling for 'str' since it is also iterable
2060+
if isinstance(val, str):
2061+
return "str"
2062+
20302063
try:
20312064
len(val)
20322065
except TypeError:
2033-
return type(val).__name__
2066+
return objname(val)
20342067
else:
20352068
if isinstance(val, dict):
20362069
return "{%s}" % (
@@ -2040,17 +2073,16 @@ def typedump(val, max_seq=5, special_handlers=None):
20402073

20412074
try:
20422075
if len(val) > max_seq:
2043-
return "{}({},...)".format(
2044-
type(val).__name__,
2045-
",".join(typedump(x, max_seq, special_handlers)
2046-
for x in val[:max_seq]))
2076+
t = ",".join(typedump(x, max_seq, special_handlers)
2077+
for x in val[:max_seq])
2078+
return f"{objname(val)}({t},...)"
20472079
else:
2048-
return "{}({})".format(
2049-
type(val).__name__,
2050-
",".join(typedump(x, max_seq, special_handlers)
2051-
for x in val))
2080+
t = ",".join(typedump(x, max_seq, special_handlers)
2081+
for x in val)
2082+
return f"{objname(val)}({t})"
2083+
20522084
except TypeError:
2053-
return val.__class__.__name__
2085+
return objname(val)
20542086

20552087

20562088
def invoke_editor(s, filename="edit.txt", descr="the file"):

test/test_pytools.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,38 @@ def test_strtobool():
730730
assert strtobool(None, False) is False
731731

732732

733+
def test_typedump():
734+
from pytools import typedump
735+
assert typedump("") == "str"
736+
assert typedump("abcdefg") == "str"
737+
assert typedump(5) == "int"
738+
739+
assert typedump((5.0, 4)) == "tuple(float,int)"
740+
assert typedump([5, 4]) == "list(int,int)"
741+
assert typedump({5, 4}) == "set(int,int)"
742+
assert typedump(frozenset((1, 2, 3))) == "frozenset(int,int,int)"
743+
744+
assert typedump([5, 4, 3, 2, 1]) == "list(int,int,int,int,int)"
745+
assert typedump([5, 4, 3, 2, 1, 0]) == "list(int,int,int,int,int,...)"
746+
assert typedump([5, 4, 3, 2, 1, 0], max_seq=6) == "list(int,int,int,int,int,int)"
747+
748+
assert typedump({5: 42, 7: 43}) == "{'5': int, '7': int}"
749+
750+
class C:
751+
class D:
752+
pass
753+
754+
assert typedump(C()) == "test_pytools.test_typedump.<locals>.C"
755+
assert typedump(C.D()) == "test_pytools.test_typedump.<locals>.C.D"
756+
assert typedump(C.D(), fully_qualified_name=False) == "D"
757+
758+
from pytools.datatable import DataTable
759+
t = DataTable(column_names=[])
760+
761+
assert typedump(t) == "pytools.datatable.DataTable()"
762+
assert typedump(t, special_handlers={type(t): lambda x: "foo"}) == "foo"
763+
764+
733765
if __name__ == "__main__":
734766
if len(sys.argv) > 1:
735767
exec(sys.argv[1])

0 commit comments

Comments
 (0)