From a785e6b4aec71379346c137ade6d04c85c5fce9b Mon Sep 17 00:00:00 2001 From: Geoffrey Thomas Date: Mon, 23 Sep 2024 00:16:08 -0400 Subject: [PATCH] Rephrase the "Private Attributes and Aliases" section (#1351) It took me a bit to figure out what was meant by the link to the characteristic issue and why there is no such thing as a private argument. Incorporate (my understanding of) the meaning inline and also provide an example of how to work around the _1 syntax error. --- docs/init.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/init.md b/docs/init.md index 463edd9a..d3b9b0fa 100644 --- a/docs/init.md +++ b/docs/init.md @@ -66,20 +66,23 @@ We don't think that your business model and your serialization format should be ## Private Attributes and Aliases One thing people tend to find confusing is the treatment of private attributes that start with an underscore. -*attrs* follows the doctrine that [there is no such thing as a private argument](https://github.com/hynek/characteristic/issues/6) and strips the underscores from the name when writing the `__init__` method signature: +Although there is [a convention](https://docs.python.org/3/tutorial/classes.html#tut-private) that members of an object that start with an underscore should be treated as private, consider that a core feature of *attrs* is to automatically create an `__init__` method whose arguments correspond to the members. +There is no corresponding convention for private arguments: the entire signature of a function is its public interface to be used by callers. + +However, it is sometimes useful to accept a public argument when an object is constructed, but treat that attribute as private after the object is created, perhaps to maintain some invariant. +As a convenience for this use case, the default behavior of *attrs* is that if you specify a member that starts with an underscore, it will strip the underscore from the name when it creates the `__init__` method signature: ```{doctest} >>> import inspect >>> from attrs import define >>> @define -... class C: -... _x: int ->>> inspect.signature(C.__init__) - None> +... class FileDescriptor: +... _fd: int +>>> inspect.signature(FileDescriptor.__init__) + None> ``` -There really isn't a right or wrong, it's a matter of taste. -But it's important to be aware of it because it can lead to surprising syntax errors: +Even if you're not using this feature, it's important to be aware of it because it can lead to surprising syntax errors: ```{doctest} >>> @define @@ -92,6 +95,7 @@ SyntaxError: invalid syntax In this case a valid attribute name `_1` got transformed into an invalid argument name `1`. +Whether this feature is useful to you is a matter of taste. If your taste differs, you can use the *alias* argument to {func}`attrs.field` to explicitly set the argument name. This can be used to override private attribute handling, or make other arbitrary changes to `__init__` argument names. @@ -101,8 +105,9 @@ This can be used to override private attribute handling, or make other arbitrary ... class C: ... _x: int = field(alias="_x") ... y: int = field(alias="distasteful_y") +... _1: int = field(alias="underscore1") >>> inspect.signature(C.__init__) - None> + None> ``` (defaults)=