docs: make more examplar
This commit is contained in:
parent
8079a35545
commit
a518d9ddfe
|
@ -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
|
||||
|
|
|
@ -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/
|
||||
|
|
Loading…
Reference in New Issue