Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Find better name for attr.dataclass #408

Closed
hynek opened this issue Jul 13, 2018 · 52 comments
Closed

Find better name for attr.dataclass #408

hynek opened this issue Jul 13, 2018 · 52 comments
Labels
Milestone

Comments

@hynek
Copy link
Member

hynek commented Jul 13, 2018

So attr.dataclass was meant more or less as a joke, but I just caught myself using it too but the name is bad, because it’s confusing. It implies some kind of symmetry or compatibility to DCs which it doesn’t.

So here’s a nice bike shed, please help me finding a short, memorable, and meaningful color for it!

@hynek hynek added this to the 18.2 milestone Jul 13, 2018
@euresti
Copy link
Contributor

euresti commented Jul 13, 2018

attr.auto?

@hynek
Copy link
Member Author

hynek commented Jul 14, 2018

attr.auto?

That’s kinda my favorite too. It’s just sad that it doesn’t work with the attr prefix but that ship has somewhat sailed. I think I’ll have to re-visit the introduction of an attrs namespace.

@hynek
Copy link
Member Author

hynek commented Jul 14, 2018

Which opens the question: if the namespace becomes attrs and you can do from attrs import X, what would be the best name for it? auto seems too short…OTOH @attrs.auto looks perfect. I wonder if in a perfect world, we could come up with something nice and symmetric to it – like @attrs.explicit?

The world around attrs has changed considerably since the invention of it's original names. It outgrew it’s scope more than I could ever predict when I was scheming with @glyph on IRC and the introduction of static types opened it up to new interesting use cases.

While I realize that introducing new APIs brings some confusion, there’s always the cautionary tale of why Makefiles use tab characters.


After using it a bit, I believe that if Python has a future, static typing is unequivocally part of it and I want attrs to embrace it fully and not to feel like we’re dragging our heels. Obviously I do not intend to break backward compatibility but unless someone can talk me out of it, I’m very close to add a new, more forward-looking API and make it the primary one.

I for one, am very annoyed whenever I use the attr package name with names that don’t make any sense (like attr.validators) and the last time I got annoyed on each import, I abandoned the project in favor of attrs. ;)

@hynek
Copy link
Member Author

hynek commented Jul 14, 2018

So I’ve been playing and I’m absolutely in love with this being the future canonical API for attrs:

>>> import attrs
>>> import typing

>>> @attrs.auto
... class SomeClass:
...     a_number: int = 42
...     list_of_numbers: typing.List[int] = attrs.Factory(list)
...     a_number = attrs.attrib(converter=int)

>>> attrs.fields(SomeClass).a_number.type
<class 'int'>

Which kind of means that the question is reversed – should there be another name for attr.s? We will have a conflict with the serious business crowd anyways since they tend to write from attr import attrs, attrib.

That’s the only thing that annoys me but I don’t think we can fix it: it’s impossible to both from attrs import attrs and import attrs. However it’s not solvable and naming the main function after the project name is not that special…so…attrs.attrs/attr.s it is I guess? (or import everything).

Which gives us:

>>> from attrs import attrs, attrib, Factory

>>> @attrs
... class SomeClass(object):
...     a_number = attrib(default=42)
...     list_of_numbers = attrib(default=Factory(list))

>>> sc = SomeClass(1, [1, 2, 3])

>>> attrs.asdict(sc)
{'a_number': 1, 'list_of_numbers': [1, 2, 3]}

>>> C = attrs.make_class("C", ["a", "b"])
>>> C("foo", "bar")
C(a='foo', b='bar')

Which I think I can live with? At least I don’t have a better idea.


One thing that’s notable tho and that makes me feel quite good about attr.s/attr.ib is how much easier they’re to distinguish. So there’s definitely gonna be a from attrs import attr and I’m gonna use it.

@glyph
Copy link
Contributor

glyph commented Jul 14, 2018

I think it would be helpful to begin by enumerating the problems that this renaming is attempting to solve.

  • PyPI name ≠ import name
    This is certainly a pet peeve of many people and perhaps rightly so.

  • We a more "forward-looking" API.
    My interpretation of this vague sentiment is that in the past, @attr.s was the only way to do things, and as a result @attr.s is now the default way to do things, whereas the lengthier attr.dataclass and attr.s(auto_attribs=True) feel like tack-ons. So, to be specific: being "forward-looking" means organizing the library around type annotations rather than attrib objects.

  • Maybe it's time for srs bsns.
    The popularity of the project has perhaps expanded to a scale where attr.ib and attr.s look too cutesy. I don't really buy this, but, ok, fine.

  • The attr name is insufficiently mellifluous.
    I am not quite sure I understand why from attrs import validators is so much better than from attr import validators. Can you say more about why you feel this is the case?

  • If we're going to do this, we should do it all at once so we have only one transition to manage rather than N.
    While normally conflating issues is a bad thing, I do agree that this sort of reorganization should be done either zero or one times, not 2+ :).

Am I missing anything?

@glyph
Copy link
Contributor

glyph commented Jul 14, 2018

My own feeling on attr.auto is that it may be a manifestation of a local maximum, and if @hynek is talking about a Grand Reorganizing here then it might not be a good name.

attr.auto is better than attr.dataclass because it articulates the salient difference between it and attr.s more succinctly: auto_attribs is a big distinction and worthy of being reflected in the name.

But, it's worse as the new default thing that attrs does. My feeling is that if we want the API to be centered around type annotations, then attrs.attrs should simply have auto_attribs default to True, and that's one of the differences between the old and the new API.

@glyph
Copy link
Contributor

glyph commented Jul 14, 2018

Alternately, thinking from the perspective of someone approaching attrs for the first time, why does one use this decorator? Because one is defining a type. So perhaps something like this would make more sense:

  • @attrs.type
  • @attrs.cls
  • @attrs.Class

Or, resurrecting one of my own personal bugaboos from long ago:

  • @attrs.mutable
  • @attrs.immutable
  • @attrs.value
  • @attrs.const

Or, for ultimate style points:

  • @attrib.utes
  • @attrib.uted

@glyph
Copy link
Contributor

glyph commented Jul 14, 2018

OK, last thought for the moment: please don't land this without having a typeshed / mypy update ready and waiting :)

@wsanchez
Copy link

Which kind of means that the question is reversed – should there be another name for attr.s? We will have a conflict with the serious business crowd anyways since they tend to write from attr import attrs, attrib.

I'm in the serious business camp (can't help myself, sorry) and I don't have a problem with the above. If I port a file, I'll have clean that up. This doesn't sound like a big deal to me.

@wsanchez
Copy link

Mmmm… @attrs.mutable and… I'd say @attrs.frozen, just based on following typing's lead, eg: typing.MutableSet and typing.FrozenSet.

@hynek
Copy link
Member Author

hynek commented Jul 15, 2018

@attrs.mutable & @attrs.frozen are definitely hot contenders right now.

@euresti
Copy link
Contributor

euresti commented Jul 15, 2018

Another suggestion if we're pushing type annotations. typed=True as the default? or type_annotated=True

@hynek
Copy link
Member Author

hynek commented Jul 16, 2018

typed=True as the default?

What would that mean?

@euresti
Copy link
Contributor

euresti commented Jul 16, 2018

Oh I'm sorry. I meant the typed=True -> foo: int, vs typed=False -> foo = attrs.attrib()

Unless you're really partial to auto_attrib

@hynek
Copy link
Member Author

hynek commented Jul 16, 2018

I’m still a bit confused – are you suggesting to rename/alias the existing auto_attrib?

@euresti
Copy link
Contributor

euresti commented Jul 16, 2018

I am suggesting that. But I guess we don't have to. Though I do think we wanted to at least change the default to True for the new class creator decorators.

@attrs.mutable
def Foo
    foo: int
    bar: str

Instead of

@attrs.mutable(auto_attrib=True)
def Foo
    foo: int
    bar: str

What I'm suggesting is:

@attrs.mutable(typed=False)
def Foo
    foo = attrs.attrib()
    bar = attrs.attrib()

Hmm. But now that I look at it typed is not the best choice of word. Because you could do:

@attrs.mutable(typed=True)
def Foo
    foo = attrs.attrib(type=str)

And I believe this wouldn't work with the current implementation.

Anyway whatever we do. If we can try to only use bool for the arguments then it's much easier to handle in the mypy plugin.

@hynek
Copy link
Member Author

hynek commented Jul 16, 2018

Yeah, new default is gonna be true and typed has some downsides I don’t think I’d like that road + a new argument. To us it’s not even that important what those annotations are. We just need something to collect and store. :) Calling it typed IMHO conveys a slightly different picture?

@euresti
Copy link
Contributor

euresti commented Jul 16, 2018

Yeah. Definitely not typed. auto_attribs is a fine name, and not changing it means fewer changes to mypy. :) Though changing the default will involve some. Oh but not that many if we're just adding new functions with different defaults. Ok I'm good. Carry on!

@hynek
Copy link
Member Author

hynek commented Jul 16, 2018

You’ve just summed up my line of thinking perfectly. :)

@hynek
Copy link
Member Author

hynek commented Jul 22, 2018

The popularity of the project has perhaps expanded to a scale where attr.ib and attr.s look too cutesy.

I think it’s fair to say, that while I still think the readability of it is superior compared to attrs/attrib, I also have to be honest with myself and accept, that it definitely cost us users/popularity.

The attr name is insufficiently mellifluous. […] Can you say more about why you feel this is the case?

Yes: attr was never intended to stand for itself. It was either attr.s or attr.ib. The moment I started adding stuff to the attr namespace, I started getting uncomfortable.

If we’re going to do this, we should do it all at once so we have only one transition to manage rather than N.

Absolutely! The question is whether or not we release 18.2 first.

The good/interesting thing here is that we literally don’t have to write any code. It’s a purely design thing of introducing a new package and move things into it.

That doesn’t mean it’s easy, but at least we don’t have to review complex code.

OK, last thought for the moment: please don’t land this without having a typeshed / mypy update ready and waiting :)

I’m a heavy mypy user myself so that’s not gonna happen. :) Fortunately, typeshed doesn’t apply to us anymore since the recent merge of our own stubs. 🎉

Naming

My own feeling on attr.auto is that it may be a manifestation of a local maximum, and if @hynek is talking about a Grand Reorganizing here then it might not be a good name.

Giving it a thought I very much agree with this. It’s like calling a module/package “new”. Given enough time, you end up with Pont Neuf.

Since I feel we do have a rough consensus that a new shell around the API would be beneficial, it’a time to paint the shed.

  • I think I’m totally down with @attrs.frozen

  • I’m not so sure about @attrs.mutable. While in our bubble, we understand what that means, attrs is used so widely nowadays that I have a bit of a problem for being too jargony. This is the default case – this is what a average Python programmer expects to have/use.

    Therefore the search for the perfect default name is still on. I’m kinda partial towards @attrs.cls but it’s not perfect either.

  • Bonus point: we could add something like @attrs.exception for Add a convenience flag for upcalling on Exception subclasses #368, Going down this road (and credit where credit is due: glyph has been pining for it since basically the beginning) opens us up for more optional niceties and syntactic sugar for our users.


Please let me know if I missed something from above, it took me two days to write this. :)

@euresti
Copy link
Contributor

euresti commented Jul 22, 2018

The only thing odd about attrs.cls is that cls typically shows up as an argument in classmethod. So if you're using from imports you have to remember that.

from attrs import cls

@cls
class Foo:
    foo: int
    @classmethod
    def from_string(cls, foo: str) -> 'Foo':
        return cls(int(foo))

But in practice this probably won't matter. Since most people won't be creating classes inside classmethods.

The only other names I can come up with are:

attrs.aclass  # short for "attrs class"
attrs.sclass  # short for "smart or simple class"
attrs.klass

But all are pretty bad. I think cls is pretty good in comparison.

@hynek hynek removed this from the 18.2 milestone Jul 31, 2018
@hynek
Copy link
Member Author

hynek commented Jul 31, 2018

Random thought: @attrs.cls is also nice because it kind of mirrors @dataclass. 🤔

@chadrik
Copy link
Contributor

chadrik commented Jul 31, 2018

My favorite decorator names so far are:

  • @attrs.mutable
  • @attrs.immutable (followed closely by @attr.frozen)

you could also consider verb names:

  • @attrs.build
  • @attrs.bind
  • @attrs.make
  • @attrs.augment

@hynek
Copy link
Member Author

hynek commented Jul 31, 2018

  • mutable is nice but not mainstream :-/
  • I prefer immutable too but frozen is established both in Python and in attrs
  • I actually like make quite a lot! Doesn’t collide with common stuff and @attrs.make kind makes sense!

@chadrik
Copy link
Contributor

chadrik commented Jul 31, 2018

Some other things I like about@attrs.make: It provides a nice companion to attrs.make_class() and I think it demystifies the process a bit -- or at least helps to frame it conceptually. "Why doesn't my class's __init__ work? Oh, I forgot to make it."

@njsmith
Copy link

njsmith commented Aug 1, 2018

Would @attrs.data be too cheeky? Or @attrs.add, as in, "I'm adding attributes to this class". @attrs.auto? Or if you want to really annoy the srs-bsns people, make the module object callable: @attrs.

(I have to admit, I'm fine with the jokey attr.s and attr.ib, but every time I write attr.Factory I wince.)

@hynek
Copy link
Member Author

hynek commented Aug 1, 2018

Or if you want to really annoy the srs-bsns people, make the module object callable: @attrs.

I’d have to lie if I said I didn’t think about it. 🤡

every time I write attr.Factory I wince.

why is that?

@hynek
Copy link
Member Author

hynek commented Aug 1, 2018

FWIW: in an increasing number of codebases I work on, from attr import Factory as new is a common idiom :)

Yeah, I like it too but it seems too ambiguous for a general purpose API.

OTOH

@attrs.cls
class C:
    x: int
    y: List[int] = attrs.new(list)

is kinda cool. I mean this new package is all about ergonomics.


I’m still very conflicted on cls vs make. Both have upsides and downsides I guess I’ll do a vote on it after it stewed in my head for a while.

@Tinche
Copy link
Member

Tinche commented Aug 1, 2018

I haven't really had time to give to this ticket a much as I wanted to. However lately I've started importing names directly into my modules more and more, both for brevity and performance. So whatever names we choose, I'd like them to be able to stand by themselves.

What I mean is, I try not to do "import attr; attr.s()" but rather "from attr import s; s()".

@hynek
Copy link
Member Author

hynek commented Aug 1, 2018

While I agree, it kinda complicates matters because it forces the name to be longer. @make or @cls are both rather weird on their own, no? With the namespace we can leave out repetition. 🤔

@hynek
Copy link
Member Author

hynek commented Oct 26, 2018

One thing I came up with in the shower:

@attrs.define
class Foo:
    x = attrs.attrib()

which also perfectly works with:

@define
class Foo:
    x = attrib()

It reads perfectly (try to read both out) and also makes perfect sense. In hindsight I’m surprised nobody came up with it before. 🤔

Opinions?

@hynek hynek added this to the import attrs milestone Oct 27, 2018
@glyph
Copy link
Contributor

glyph commented Oct 28, 2018

Hmm. "Define". Maybe we could abbreviate it, to like... def? :)

In all seriousness this is the best proposal I've seen so far. I still don't instantly love it but it reads cleanly and makes sense.

@vlcinsky
Copy link

Both @attrs.define class as well as @define class reads perfectly.

@hynek this sounds to me as the name to use.

I would prefer to keep it as define without abbreviating.

Greetings to your magic shower.

@hynek
Copy link
Member Author

hynek commented Oct 29, 2018

I would prefer to keep it as define without abbreviating.

It’s not always obvious, but glyph was trolling here. ;) You can’t have a variable called def in Python for rather obvious reasons if you think about it:

>>> def = 42
  File "<stdin>", line 1
    def = 42
        ^
SyntaxError: invalid syntax

@glyph
Copy link
Contributor

glyph commented Oct 29, 2018

😜

@wsanchez
Copy link

Well, a month later, I'm still thinking define is pretty damned good. I'd probably bind it to mutable because I'm pedantic and want to be explicit about that, but define is great (a built-in alias would be swell, but I'll shut up). I think we had a consensus on frozen for immutable classes.

Is anyone unhappy with define and frozen as the two (canonical, at least) names?

@chadrik
Copy link
Contributor

chadrik commented Nov 28, 2018 via email

@energizah
Copy link

energizah commented May 21, 2019

I like @chadrik's points in favor of make over define, that
you always have to @make class or make_class().

@hynek

It’s just sad that it doesn’t work with the attr prefix

A new idea then:

@attr.active
class Foo:
    x = attr.actor()
   

:-)

@hynek
Copy link
Member Author

hynek commented May 21, 2019

Heh I think this topic is settled at this point. FWIW, you can always alias our API. There even seem to be at least two ways to make it work with mypy: python/mypy#5406 (comment)

@tigrus
Copy link

tigrus commented Jun 19, 2019

Finally, how we can avoid confusing @attrs.dataclass in our code?

I don't want to confuse colleagues by constructions like this:

@final
@attr.dataclass(frozen=True, slots=True)
class FetchProfiles(object):
    pass

So, is there way to use @attr.auto / @attr.active instead?

@hynek
Copy link
Member Author

hynek commented Jun 22, 2019

Funny you'd ask! So as it's very much obvious, I've been procrastinating on these changes for a while, because I'm afraid to get it wrong.

Now I've been thinking, that maybe I should make a step in between and add attr.auto with the defaults I've been considering so far.


As for the dataclass method itself, I've noticed that for a former easter egg it's hilariously popular so I may be convinced to add it into the attrs namespace too, once it lands.

@micimize
Copy link

what about @attrs.driven, or @attrs.expand? Something that implies more explicitly that the macro will generate methods, etc from the attributes.

On the other side of this, what about making a namespace that actually is intentionally similar to PEP 557 dataclasses and fields, and just has a thin adapter layer between field and attr.ib, etc? How much incompatibility is there beyond the names? Isn't attrs mostly a superset?

@tigrus
Copy link

tigrus commented Jul 24, 2019

Guys, my team is asking "Why we are using dataclass here?" ...

While I want less boilerplate code with attrs. And I don't want to have dataclasses with methods.

We use python dataclasses, when need kind of interface between functions.

That's why attrs.auto, attrs.driven and attrs.expand will work in my case. attrs.dataclass looks like joke.

@hynek
Copy link
Member Author

hynek commented Aug 10, 2019

attrs.dataclass looks like joke.

To be fair, it was a joke…it was an easter egg (note the comment in __init__.py) that people found out about. ¯\_(ツ)_/¯

@hynek
Copy link
Member Author

hynek commented Aug 14, 2020

Please check out #666 y'all.

@jbowen7
Copy link

jbowen7 commented Sep 3, 2020

This is the kind of stuff that keeps me from doing any real work.

@hynek, I realize that you've already packaged v20.0.1 with some of the proposed changes from #408 and #487, but I wanted to suggest a completely different solution.

The new namespace attrs just doesn't smell right. Not for the proposed usage anyway.
It'll be quite confusing for many users, and it being so damn similar only makes it doubly so.
I do, however, agree with @glyph that from attrs import validators sounds really good.

Here it goes:

  1. make attrs an alias to attr for the sake of melody.
  2. create a meaningful submodule in attr for all of the most commonly used structure-esque things.
    How about
from attr.models import Model, Field

@Model
class Dataclass:
   init = Field(default=True)
   repr = Field(default=True)

Firstly, it works.
Secondly, it would be immediately obvious and could capture the attention of a very large audience (Django, if the plagiarism wasn't obvious)

Capturing that audience could lead to fields that have built in validation.
Imagine a attrs package with models like:

from attrs.models import FrozenModel, EnumField, StringField

@Model
class MagicWand:
   wishes = EnumField(choices=list(range(1,7)))

@FrozenModel
class Engraving:
   word = StringField(default='Elbereth')

@micimize
Copy link

micimize commented Sep 3, 2020

I'm perfectly happy with the new API. I extracted my PEP compatibility proposal into #686

@glyph
Copy link
Contributor

glyph commented Oct 20, 2020

Is attr.define it? Should this be closed?

@hynek
Copy link
Member Author

hynek commented Oct 20, 2020

Yes, I think this chapter can be closed after more than 2 years.

attr.define, attr.mutable, attr.frozen, and attr.field it is!

Thanks everyone for playing!

@hynek hynek closed this as completed Oct 20, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests