diff --git a/changelog.d/523.change.rst b/changelog.d/523.change.rst new file mode 100644 index 00000000..86bf8cda --- /dev/null +++ b/changelog.d/523.change.rst @@ -0,0 +1 @@ +When collecting attributes using ``@attr.s(auto_attribs=True)``, attributes with a default of ``None`` are now deleted too. diff --git a/changelog.d/556.change.rst b/changelog.d/556.change.rst new file mode 100644 index 00000000..86bf8cda --- /dev/null +++ b/changelog.d/556.change.rst @@ -0,0 +1 @@ +When collecting attributes using ``@attr.s(auto_attribs=True)``, attributes with a default of ``None`` are now deleted too. diff --git a/src/attr/_make.py b/src/attr/_make.py index 2e965420..283aec66 100644 --- a/src/attr/_make.py +++ b/src/attr/_make.py @@ -42,6 +42,9 @@ _hash_cache_field = "_attrs_cached_hash" _empty_metadata_singleton = metadata_proxy({}) +# Unique object for unequivocal getattr() defaults. +_sentinel = object() + class _Nothing(object): """ @@ -504,7 +507,7 @@ class _ClassBuilder(object): for name in self._attr_names: if ( name not in base_names - and getattr(cls, name, None) is not None + and getattr(cls, name, _sentinel) != _sentinel ): try: delattr(cls, name) diff --git a/tests/test_annotations.py b/tests/test_annotations.py index cbf5bd02..1f23f1d4 100644 --- a/tests/test_annotations.py +++ b/tests/test_annotations.py @@ -265,3 +265,20 @@ class TestAnnotations: x: int assert 1 == C(1).x + + def test_removes_none_too(self): + """ + Regression test for #523: make sure defaults that are set to None are + removed too. + """ + + @attr.s(auto_attribs=True) + class C: + x: int = 42 + y: typing.Any = None + + with pytest.raises(AttributeError): + C.x + + with pytest.raises(AttributeError): + C.y