From c2aa056c489e3b94ccc7a328cb2b4ca2973fe830 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Mon, 8 Sep 2025 16:33:11 +0200 Subject: [PATCH 1/3] Improve performance of attr.astuple --- src/attr/_funcs.py | 11 +++++++---- tests/test_funcs.py | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/attr/_funcs.py b/src/attr/_funcs.py index c6f0a4cee..1adb50021 100644 --- a/src/attr/_funcs.py +++ b/src/attr/_funcs.py @@ -278,8 +278,11 @@ def astuple( v = getattr(inst, a.name) if filter is not None and not filter(a, v): continue + value_type = type(v) if recurse is True: - if has(v.__class__): + if value_type in _ATOMIC_TYPES: + rv.append(v) + elif has(value_type): rv.append( astuple( v, @@ -289,7 +292,7 @@ def astuple( retain_collection_types=retain, ) ) - elif isinstance(v, (tuple, list, set, frozenset)): + elif issubclass(value_type, (tuple, list, set, frozenset)): cf = v.__class__ if retain is True else list items = [ ( @@ -313,8 +316,8 @@ def astuple( # Workaround for TypeError: cf.__new__() missing 1 required # positional argument (which appears, for a namedturle) rv.append(cf(*items)) - elif isinstance(v, dict): - df = v.__class__ if retain is True else dict + elif issubclass(value_type, dict): + df = value_type if retain is True else dict rv.append( df( ( diff --git a/tests/test_funcs.py b/tests/test_funcs.py index 4c9460eaf..df6a7193f 100644 --- a/tests/test_funcs.py +++ b/tests/test_funcs.py @@ -499,6 +499,22 @@ class A: with pytest.raises(TypeError, match=re.escape(message)): attr.astuple(instance, retain_collection_types=True) + def test_non_atomic_types(self, C): + """ + Non-atomic types that don't have special treatment for are serialized + and the types are retained. + """ + + class Int(int): + pass + + c = C(Int(10), [Int(1), 2]) + expected = (Int(10), [Int(1), 2]) + + result = astuple(c) + assert expected == result + assert type(result[0]) is Int + assert type(result[1][0]) is Int class TestHas: """ From eb3ced2678bf970852e7e1b7ddfb759e813e9ac4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:36:31 +0000 Subject: [PATCH 2/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_funcs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_funcs.py b/tests/test_funcs.py index df6a7193f..b254e9898 100644 --- a/tests/test_funcs.py +++ b/tests/test_funcs.py @@ -516,6 +516,7 @@ class Int(int): assert type(result[0]) is Int assert type(result[1][0]) is Int + class TestHas: """ Tests for `has`. From 57ee5cf0bd6e1f510655b16d6f8beac164156197 Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Mon, 8 Sep 2025 17:44:58 +0200 Subject: [PATCH 3/3] Add news fragment --- changelog.d/1469.change.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1469.change.md diff --git a/changelog.d/1469.change.md b/changelog.d/1469.change.md new file mode 100644 index 000000000..1903d3da2 --- /dev/null +++ b/changelog.d/1469.change.md @@ -0,0 +1 @@ +The performance of `attrs.astuple()` has been improved by 49–270%.