Skip to content

treatment of private attributes is obscure, nearly undocumented, perhaps not ideal in all cases #171

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

Closed
glyph opened this issue Mar 28, 2017 · 4 comments

Comments

@glyph
Copy link
Contributor

glyph commented Mar 28, 2017

Some folks, presented with the attr.s API, make the entirely reasonable inference that this:

@attr.s
class Foo:
    _bar = attr.ib()

is a class you should use like this:

Foo(_bar=1)

This is further exacerbated by the fact that Foo's repr actually shows you exactly that string.

The only example in the docs that I can find is the code example at the bottom of the attr.s API doc.

This behavior should be thoroughly documented and explained, and, um, maybe tweaked a little bit, since there are some issues with it. As it stands now, for example, there's no publicly-named attribute for API doc generation tools, or code-completion things, to pick up to present as the constructor argument.

If I could take a stab at the rationale for this behavior, it's something like this:

In Python, if you have a function with "private" arguments, they all have positional names, which makes them effectively public. Therefore, there is no point in trying to "hide" your constructor arguments behind _ prefixes, since a user might happily do Foo(1) and never realize that they've done something unsupported. Therefore attr.s always makes the keyword-arg names of your constructor arguments "public" by removing their underscores; the attributes themselves are of course private and can still have the _ prefix when accessed as self._something.

Except, in Python 3, this isn't true any more, because the advent of keyword-only arguments means you can't pass the argument without actually having to type in an _ in a namespace you don't own, which means it's totally valid to have internal "don't pass me this in the constructor" state. IMHO this is great - it's a way to force public users through a class-method constructor or factory function without hiding the type itself (and all the methods on it) or resorting to any gross shenanigans to make the constructor literally private. So it turns out I really would like "private attributes" that don't show up in the constructor either! (And of course, at the metaprogramming level where Attrs itself lives, the kwarg-only behavior can be emulated on python 2.)

@hynek
Copy link
Member

hynek commented Mar 31, 2017

As a little history lesson, the origin of the behavior is hynek/characteristic#6.

@glyph
Copy link
Contributor Author

glyph commented Mar 31, 2017

To be clear - especially given that history :) - I find this behavior entirely intuitive, and have not previously found it to be problematic. However, having had it pointed out to me that it's confusing, I found it reasonable to expect some other behavior unless you have a view of private attributes with very specific nuances.

@Tinche
Copy link
Member

Tinche commented Mar 31, 2017

This came up recently while discussing evolve(). Should you use evolve(foo, _bar=2) or evolve(foo, bar=2)?

My initial thought was that having private arguments in __init__ doesn't really make sense since init is usually the first interface of a class a user encounters, and you're basically saying "I'm giving you a bunch of arguments to create me, but some of these arguments I'm giving you you shouldn't use". I guess I feel the same way about any function/method with "private" arguments, why are they there if you're not supposed to use them?

However it's also true other languages can have other, private constructors, but Python can't. Also for normal functions, users can define additional private functions in addition and put the private arguments there; not so with __init__ (well, class methods can be used like alternate __init__s, but it's complicated). So I'm ok with kwonly private arguments in __init__.

I'm guessing kw-only args in Python 2 are emulated using **kwargs? It'll obfuscate the init signature a little, but that's what you get for using legacy Python <trollface.jpg>

@hynek
Copy link
Member

hynek commented Apr 25, 2018

fixed in #370

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants