docs: make more examplar

This commit is contained in:
Hynek Schlawack 2024-07-15 11:27:08 +02:00
parent 8079a35545
commit a518d9ddfe
No known key found for this signature in database
2 changed files with 32 additions and 28 deletions

View File

@ -6,7 +6,7 @@ It's a tuple of {class}`attrs.Attribute` carrying metadata about each attribute.
So it is fairly simple to build your own decorators on top of *attrs*:
```{doctest}
>>> from attr import define
>>> from attrs import define
>>> def print_attrs(cls):
... print(cls.__attrs_attrs__)
... return cls
@ -50,8 +50,8 @@ Another common use case is to overwrite *attrs*'s defaults.
### Mypy
Unfortunately, decorator wrapping currently [confuses](https://github.com/python/mypy/issues/5406) mypy's *attrs* plugin.
At the moment, the best workaround is to hold your nose, write a fake *Mypy* plugin, and mutate a bunch of global variables:
Unfortunately, decorator wrapping currently [confuses](https://github.com/python/mypy/issues/5406) Mypy's *attrs* plugin.
At the moment, the best workaround is to hold your nose, write a fake Mypy plugin, and mutate a bunch of global variables:
```python
from mypy.plugin import Plugin
@ -79,7 +79,7 @@ def plugin(version):
return MyPlugin
```
Then tell *Mypy* about your plugin using your project's `mypy.ini`:
Then tell Mypy about your plugin using your project's `mypy.ini`:
```ini
[mypy]
@ -87,21 +87,21 @@ plugins=<path to file>
```
:::{warning}
Please note that it is currently *impossible* to let mypy know that you've changed defaults like *eq* or *order*.
You can only use this trick to tell *Mypy* that a class is actually an *attrs* class.
Please note that it is currently *impossible* to let Mypy know that you've changed defaults like *eq* or *order*.
You can only use this trick to tell Mypy that a class is actually an *attrs* class.
:::
### Pyright
Generic decorator wrapping is supported in [*Pyright*](https://github.com/microsoft/pyright) via `typing.dataclass_transform` / {pep}`681`.
Generic decorator wrapping is supported in [Pyright](https://github.com/microsoft/pyright) via `typing.dataclass_transform` / {pep}`681`.
For a custom wrapping of the form:
```
@typing.dataclass_transform(field_specifiers=(attr.attrib, attr.field))
@typing.dataclass_transform(field_specifiers=(attr.attrib, attrs.field))
def custom_define(f):
return attr.define(f)
return attrs.define(f)
```
## Types
@ -109,16 +109,16 @@ def custom_define(f):
*attrs* offers two ways of attaching type information to attributes:
- {pep}`526` annotations,
- and the *type* argument to {func}`attr.ib`.
- and the *type* argument to {func}`attr.ib` / {func}`attrs.field`.
This information is available to you:
```{doctest}
>>> from attr import attrib, define, field, fields
>>> from attrs import define, field, fields
>>> @define
... class C:
... x: int = field()
... y = attrib(type=str)
... y = field(type=str)
>>> fields(C).x.type
<class 'int'>
>>> fields(C).y.type
@ -127,6 +127,10 @@ This information is available to you:
Currently, *attrs* doesn't do anything with this information but it's very useful if you'd like to write your own validators or serializers!
Originally, we didn't add the *type* argument to the new {func}`attrs.field` API, because type annotations are the preferred way.
But we reintroduced it later, so `field` can be used with the {func}`attrs.make_class` function.
We strongly discourage the use of the *type* parameter outside of {func}`attrs.make_class`.
(extending-metadata)=
## Metadata

View File

@ -2,7 +2,7 @@
*attrs* comes with first-class support for type annotations for both {pep}`526` and legacy syntax.
However they will forever remain *optional*, therefore the example from the README could also be written as:
However, they will remain *optional* forever, therefore the example from the README could also be written as:
```{doctest}
>>> from attrs import define, field
@ -32,7 +32,7 @@ SomeClass(a_number=42)
```
:::
Even when going all-in on type annotations, you will need {func}`attrs.field` for some advanced features though.
Even when going all-in on type annotations, you will need {func}`attrs.field` for some advanced features, though.
One of those features are the decorator-based features like defaults.
It's important to remember that *attrs* doesn't do any magic behind your back.
@ -46,20 +46,18 @@ Please note that types -- regardless how added -- are *only metadata* that can b
Because Python does not allow references to a class object before the class is defined,
types may be defined as string literals, so-called *forward references* ({pep}`526`).
You can enable this automatically for a whole module by using `from __future__ import annotations` ({pep}`563`) as of Python 3.7.
You can enable this automatically for a whole module by using `from __future__ import annotations` ({pep}`563`).
In this case *attrs* simply puts these string literals into the `type` attributes.
If you need to resolve these to real types, you can call {func}`attrs.resolve_types` which will update the attribute in place.
In practice though, types show their biggest usefulness in combination with tools like [*Mypy*], [*pytype*], or [*Pyright*] that have dedicated support for *attrs* classes.
In practice though, types show their biggest usefulness in combination with tools like [Mypy], [*pytype*], or [Pyright] that have dedicated support for *attrs* classes.
The addition of static types is certainly one of the most exciting features in the Python ecosystem and helps you write *correct* and *verified self-documenting* code.
If you don't know where to start, Carl Meyer gave a great talk on [*Type-checked Python in the Real World*](https://www.youtube.com/watch?v=pMgmKJyWKn8) at PyCon US 2018 that will help you to get started in no time.
## Mypy
While having a nice syntax for type metadata is great, it's even greater that [*Mypy*] as of 0.570 ships with a dedicated *attrs* plugin which allows you to statically check your code.
While having a nice syntax for type metadata is great, it's even greater that [Mypy] ships with a dedicated *attrs* plugin which allows you to statically check your code.
Imagine you add another line that tries to instantiate the defined class using `SomeClass("23")`.
Mypy will catch that error for you:
@ -71,8 +69,8 @@ t.py:12: error: Argument 1 to "SomeClass" has incompatible type "str"; expected
This happens *without* running your code!
And it also works with *both* Python 2-style annotation styles.
To *Mypy*, this code is equivalent to the one above:
And it also works with *both* legacy annotation styles.
To Mypy, this code is equivalent to the one above:
```python
@attr.s
@ -81,31 +79,33 @@ class SomeClass:
list_of_numbers = attr.ib(factory=list, type=list[int])
```
The approach used for `list_of_numbers` one is only a available in our [old-style API](names.md) which is why the example still uses it.
## Pyright
*attrs* provides support for [*Pyright*] through the `dataclass_transform` / {pep}`681` specification.
*attrs* provides support for [Pyright] through the `dataclass_transform` / {pep}`681` specification.
This provides static type inference for a subset of *attrs* equivalent to standard-library {mod}`dataclasses`,
and requires explicit type annotations using the {func}`attrs.define` or `@attr.s(auto_attribs=True)` API.
Given the following definition, *Pyright* will generate static type signatures for `SomeClass` attribute access, `__init__`, `__eq__`, and comparison methods:
Given the following definition, Pyright will generate static type signatures for `SomeClass` attribute access, `__init__`, `__eq__`, and comparison methods:
```
@attr.define
@attrs.define
class SomeClass:
a_number: int = 42
list_of_numbers: list[int] = attr.field(factory=list)
```
:::{warning}
The *Pyright* inferred types are a tiny subset of those supported by *Mypy*, including:
The Pyright inferred types are a tiny subset of those supported by Mypy, including:
- The `attrs.frozen` decorator is not typed with frozen attributes, which are properly typed via `attrs.define(frozen=True)`.
Your constructive feedback is welcome in both [attrs#795](https://github.com/python-attrs/attrs/issues/795) and [pyright#1782](https://github.com/microsoft/pyright/discussions/1782).
Generally speaking, the decision on improving *attrs* support in *Pyright* is entirely Microsoft's prerogative, though.
Generally speaking, the decision on improving *attrs* support in Pyright is entirely Microsoft's prerogative and they unequivocally indicated that they'll only add support for features that go through the PEP process, though.
:::
[*Mypy*]: http://mypy-lang.org
[*Pyright*]: https://github.com/microsoft/pyright
[Mypy]: http://mypy-lang.org
[Pyright]: https://github.com/microsoft/pyright
[*pytype*]: https://google.github.io/pytype/