Good ways to use attrs?

In the thread

the package attrs was mentioned as an alternative to @dataclass.

I’ve had a look at it, and it looks very promising. But it’s also a lot to wrap my head around.

Without rambling too much about slots vs dicts, automatic type checking and private attributes:
How do you use attrs? I’m looking for any techniques or tricks which you feel make your life easier or your code better :slight_smile:



Edit after trying to read through the attrs docs:
Its :red_square: impossible!

I read

attrs.frozen(same_as_define )

Behaves the same as attrs.define but sets frozen=True and on_setattr=None.

New in version 20.1.0.

so I try to find out what on_setattr does. All my searches eventually get back to
https://www.attrs.org/en/stable/api.html#attrs.define

where I couldn’t find the information I was looking for. So it pointed me on towards
https://www.attrs.org/en/stable/api-attr.html#attr.s

where I find

on_setattr (callable, or a list of callables, or None, or attrs.setters.NO_OP) –

A callable that is run whenever the user attempts to set an attribute (either by assignment like i.x = 42 or by using setattr like setattr(i, "x", 42)). It receives the same arguments as validators: the instance, the attribute that is being modified, and the new value.

If no exception is raised, the attribute is set to the return value of the callable.

If a list of callables is passed, they’re automatically wrapped in an attrs.setters.pipe.

Which is still cryptic. Does this mean I can use on_setattr to get type checking when attributes are set? If so, how? If not, what else does this mean?

And to add to all that, I don’t appear to be able to read the source code on github.


None of which is any of your faults of course. So forgive my ranting. But it does add to my desire to see some good example code.

Inheritance is attrs’ super power. In particular, if I want to specify or override the automatically generated instance variable’s default value in a subclass:

import attrs

@attrs.define
class Square:
    x: float
    
class UnitSquare:
    x: float = 1

import dataclasses

@dataclasses.dataclass   
class SquareDataClass:
    x: float
        
class UnitSquareDataClass(SquareDataClass):
    x: float = 1
        
for SquareClass in (UnitSquare, UnitSquareDataClass):
    s = SquareClass()
    print(f'{SquareClass.__name__}:, {s=}, {s.x=}')

I know why this doesn’t work in dataclasses, it’s not a complaint. I just appreciate that subclassing does work the way I first expected it to, in attrs.

I think you made a typo, and I’m not really sure where.

import attrs

@attrs.define
class Square:
    x: float
    
@attrs.define
class UnitSquare(Square):
    x: float = 1

import dataclasses

@dataclasses.dataclass   
class SquareDataClass:
    x: float
        
@dataclasses.dataclass   
class UnitSquareDataClass(SquareDataClass):
    x: float = 1
        
for SquareClass in (UnitSquare, UnitSquareDataClass):
    s = SquareClass()
    print(f'{SquareClass.__name__}:, {s=}, {s.x=}')

#UnitSquare:, s=UnitSquare(x=1), s.x=1
#UnitSquareDataClass:, s=UnitSquareDataClass(x=1), s.x=1

works fine.

But if I comment out either the second @attrs.define or the second @dataclasses.dataclass I get an Error.

Hello! I’m a maintainer of attrs and I’m sure Hynek (the original author) is also around somewhere.

Sorry about this. We can improve based on feedback (or even better, PRs) :wink: I agree this is buried pretty deeply.

Not too differently than dataclasses. I think attrs beats dataclasses at the more complex stuff, like a better version of attrs/dataclasses.fields (with better typing), attrs.resolve_types, being consistent across Python versions, defaulting to slots (the way I like it), being more willing to do hacks for our users (like closure cell rewriting and cached properties for slotted classes), and performance. Although on the performance front I think dataclasses are pulling ahead on the most recent Python versions, so we need to do a little catch-up :wink:

Let me know if you have any other questions.

1 Like

The error from the dataclass but not from the attrs class, was the point.

But thanks - you may well have fixed it :slight_smile: I don’t know why I didn’t think before, to simply apply the decorator again.