diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index cba8264b79a..7bd9422e691 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -350,64 +350,64 @@ def safe_getattr(obj: Any, name: str, *defargs: Any) -> Any: raise AttributeError(name) from exc -def object_description(object: Any, seen: frozenset = frozenset()) -> str: +def object_description(obj: Any, *, _seen: frozenset = frozenset()) -> str: """A repr() implementation that returns text safe to use in reST context. Maintains a set of 'seen' object IDs to detect and avoid infinite recursion. """ - if isinstance(object, dict): - if id(object) in seen: - return "dict(...)" - seen |= {id(object)} + seen = _seen + if isinstance(obj, dict): + if id(obj) in seen: + return 'dict(...)' + seen |= {id(obj)} try: - sorted_keys = sorted(object) + sorted_keys = sorted(obj) except TypeError: # Cannot sort dict keys, fall back to using descriptions as a sort key - sorted_keys = sorted(object, key=lambda x: object_description(x, seen)) - else: - items = ("%s: %s" % - (object_description(key, seen), object_description(object[key], seen)) - for key in sorted_keys) - return "{%s}" % ", ".join(items) - elif isinstance(object, set): - if id(object) in seen: - return "set(...)" - seen |= {id(object)} + sorted_keys = sorted(obj, key=lambda k: object_description(k, _seen=seen)) + + items = (f'{object_description(key, _seen=seen)}: {object_description(obj[key], _seen=seen)}' + for key in sorted_keys) + return '{%s}' % ', '.join(items) + elif isinstance(obj, set): + if id(obj) in seen: + return 'set(...)' + seen |= {id(obj)} try: - sorted_values = sorted(object) + sorted_values = sorted(obj) except TypeError: # Cannot sort set values, fall back to using descriptions as a sort key - sorted_values = sorted(object, key=lambda x: object_description(x, seen)) - return "{%s}" % ", ".join(object_description(x, seen) for x in sorted_values) - elif isinstance(object, frozenset): - if id(object) in seen: - return "frozenset(...)" - seen |= {id(object)} + sorted_values = sorted(obj, key=lambda x: object_description(x, _seen=seen)) + return '{%s}' % ', '.join(object_description(x, _seen=seen) for x in sorted_values) + elif isinstance(obj, frozenset): + if id(obj) in seen: + return 'frozenset(...)' + seen |= {id(obj)} try: - sorted_values = sorted(object) + sorted_values = sorted(obj) except TypeError: # Cannot sort frozenset values, fall back to using descriptions as a sort key - sorted_values = sorted(object, key=lambda x: object_description(x, seen)) - return "frozenset({%s})" % ", ".join(object_description(x, seen) + sorted_values = sorted(obj, key=lambda x: object_description(x, _seen=seen)) + return 'frozenset({%s})' % ', '.join(object_description(x, _seen=seen) for x in sorted_values) - elif isinstance(object, enum.Enum): - return f"{object.__class__.__name__}.{object.name}" - elif isinstance(object, tuple): - if id(object) in seen: - return "tuple(...)" - seen |= frozenset([id(object)]) - return "(%s%s)" % ( - ", ".join(object_description(x, seen) for x in object), - "," if len(object) == 1 else "", + elif isinstance(obj, enum.Enum): + return f'{obj.__class__.__name__}.{obj.name}' + elif isinstance(obj, tuple): + if id(obj) in seen: + return 'tuple(...)' + seen |= frozenset([id(obj)]) + return '(%s%s)' % ( + ', '.join(object_description(x, _seen=seen) for x in obj), + ',' * (len(obj) == 1), ) - elif isinstance(object, list): - if id(object) in seen: - return "list(...)" - seen |= {id(object)} - return "[%s]" % ", ".join(object_description(x, seen) for x in object) + elif isinstance(obj, list): + if id(obj) in seen: + return 'list(...)' + seen |= {id(obj)} + return '[%s]' % ', '.join(object_description(x, _seen=seen) for x in obj) try: - s = repr(object) + s = repr(obj) except Exception as exc: raise ValueError from exc # Strip non-deterministic memory addresses such as diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index 834800d4ade..70201862add 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -517,20 +517,18 @@ class MyEnum(enum.Enum): def test_set_sorting_fallback(): set_ = {None, 1} description = inspect.object_description(set_) - assert description in ("{1, None}", "{None, 1}") assert description == "{1, None}" def test_deterministic_nested_collection_descriptions(): - _desc = inspect.object_description # sortable - assert _desc([{1, 2, 3, 10}]) == "[{1, 2, 3, 10}]" - assert _desc(({1, 2, 3, 10},)) == "({1, 2, 3, 10},)" + assert inspect.object_description([{1, 2, 3, 10}]) == "[{1, 2, 3, 10}]" + assert inspect.object_description(({1, 2, 3, 10},)) == "({1, 2, 3, 10},)" # non-sortable (elements of varying datatype) - assert _desc([{None, 1}]) == "[{1, None}]" - assert _desc(({None, 1},)) == "({1, None},)" - assert _desc([{None, 1, 'A'}]) == "[{'A', 1, None}]" - assert _desc(({None, 1, 'A'},)) == "({'A', 1, None},)" + assert inspect.object_description([{None, 1}]) == "[{1, None}]" + assert inspect.object_description(({None, 1},)) == "({1, None},)" + assert inspect.object_description([{None, 1, 'A'}]) == "[{'A', 1, None}]" + assert inspect.object_description(({None, 1, 'A'},)) == "({'A', 1, None},)" def test_frozenset_sorting(): @@ -542,7 +540,6 @@ def test_frozenset_sorting(): def test_frozenset_sorting_fallback(): frozenset_ = frozenset((None, 1)) description = inspect.object_description(frozenset_) - assert description in ("frozenset({1, None})", "frozenset({None, 1})") assert description == "frozenset({1, None})"