attrs icon indicating copy to clipboard operation
attrs copied to clipboard

attr'd classes don't have default attributes?

Open smurfix opened this issue 4 months ago • 2 comments

Today I stumbled over this one:

from attrs import define,field
from dataclasses import dataclass

@define
class N1:
    foo:int|None = field(default=None)
class N2:
    foo:int|None = None
    def __init__(self, foo):
        self.foo = 123
@dataclass
class N3:
    foo:int|None = None

n3 = N3(foo=123)
del n3.foo
assert n3.foo is None

n2 = N2(foo=123)
del n2.foo
assert n2.foo is None

n1 = N1(foo=123)
del n1.foo
n1.foo  # AttributeError

This is not what I'd expect when I decide to attrs-ize an existing class. Thus please either fix this, or add a reasonably prominent section that documents this (and other possible pitfalls) to the manual.

smurfix avatar Oct 10 '25 06:10 smurfix

This has nothing to do with default arguments; what you're accessing there are class variables.

To be concrete: you're deleting the instance field n2.foo and therefore get N2.foo.

I'll have to have a closer look at what exactly happens there. We do some class sanitization (due to pre-typing attr.s/attr.ib times) and I'm not 100% sure how we handle class vars for slotted classes – we'll have to investigate how much of this is on purpose.


N.B. switching the DC example to slots makes it fail too:

@dataclasses.dataclass(slots=True)
class N3:
    foo: int | None = None

n3 = N3(foo=123)
del n3.foo
print(n3.foo)  # AttributeError: 'N3' object has no attribute 'foo'

hynek avatar Oct 10 '25 07:10 hynek

This has nothing to do with default arguments; what you're accessing there are class variables.

Sure. I know. Sorry for being kindof sloppy expressing myself.

Thanks for looking into this. Even if you ultimately decide it's on purpose, having a chance to discover this issue by R'ing TFM instead of a lengthy debugging session would be helpful.

smurfix avatar Oct 10 '25 08:10 smurfix