Commit d473117f authored by jg5dev's avatar jg5dev 💬
Browse files

better

parent e4f5514d
Loading
Loading
Loading
Loading
+47 −5
Changes for src/apren/basics/advanced_python.md: 47 added lines, 5 removed lines.
Original line number Diff line number Diff line
@@ -617,12 +617,53 @@ falla en executar, amb les dades reals al davant; el protocol falla mentre escri
sense executar res. Cap dels dos obliga la classe a heretar de res: n'hi ha prou que
tingui la forma.

### On viuen els atributs: `__dict__`

Un objecte de Python no guarda els atributs en unes caselles fixes decidides en compilar:
els guarda en un diccionari corrent, accessible com a `obj.__dict__`. Assignar un atribut
és, literalment, posar una clau en aquest diccionari.

```python
class Punt:
    dim = 2                     # atribut de classe: viu al __dict__ de la classe

    def __init__(self, x: float, y: float) -> None:
        self.x = x              # equival a self.__dict__["x"] = x
        self.y = y

p = Punt(1.0, 2.0)
print(p.__dict__)               # {'x': 1.0, 'y': 2.0}
print(vars(p))                  # el mateix: vars() és la manera educada de mirar-lo

p.color = "roig"                # ningú no ho impedeix: la clau s'hi afegeix i ja està
print(vars(p))                  # {'x': 1.0, 'y': 2.0, 'color': 'roig'}

print("dim" in vars(p))         # False: dim no és a la instància...
print(p.dim)                    # ...i tot i així p.dim val 2
```

Escriure `p.x` no és llegir una posició de memòria, és una cerca: primer al `__dict__` de
la instància i, si no hi és, al de la classe i al dels ancestres. Per això `p.dim`
funciona sense ser a la instància, i per això els mètodes no es dupliquen a cada objecte
—viuen una sola vegada al `__dict__` de la classe—. Les classes i els mòduls també en
tenen un, de `__dict__`: és el mateix mecanisme a tot arreu.

Aquest disseny explica tres coses que ja hauràs vist. Que puguis afegir atributs a una
instància en qualsevol moment, encara que la classe no els declari. Que `p.cordenada = 3`
—amb la falta d'ortografia inclosa— no falli: només crea una clau nova. I que cada
instància carregui amb un diccionari propi, que ocupa memòria i s'ha de consultar a cada
accés. Més endavant, [`slots`](#dataclass-amb-slots-i-frozen) és exactament l'opció de
renunciar a aquest diccionari: perds poder afegir atributs sobre la marxa i, a canvi,
guanyes memòria, velocitat i un error immediat quan t'equivoques de nom.

### 🔬 Descriptor Protocol

> **Profunditat opcional.** Els descriptors són la maquinària que hi ha sota `@property`,
> sota els mètodes lligats i sota els camps dels ORM. Saber que existeixen explica per
> què `@property` funciona; escriure'n un de propi és rar, i normalment `@property` o un
> validador de Pydantic resolen el mateix amb menys peces.
> **Profunditat opcional.** Un descriptor és un objecte que s'interposa en la cerca que
> acabem de veure: quan el nom es troba a la classe i el que hi ha té `__get__` o
> `__set__`, Python el crida en comptes de retornar-lo tal qual. És la maquinària sota
> `@property`, sota els mètodes lligats i sota els camps dels ORM. Saber que existeixen
> explica per què `@property` funciona; escriure'n un de propi és rar, i normalment
> `@property` o un validador de Pydantic resolen el mateix amb menys peces.

```python
from collections.abc import Callable
@@ -836,7 +877,8 @@ class Punt:

`frozen=True` fa l'objecte immutable: intentar `p.x = 3` llança `FrozenInstanceError`, i
com que ja no pot canviar, l'objecte esdevé *hashable* i pot ser clau d'un `dict` o
element d'un `set`. `slots=True` elimina el `__dict__` per instància: menys memòria,
element d'un `set`. `slots=True` elimina el
[`__dict__` per instància](#on-viuen-els-atributs-__dict__): menys memòria,
accés als camps més ràpid, i un error immediat si escrius malament el nom d'un camp en
comptes de crear-ne un de nou en silenci.