Loading src/apren/basics/advanced_python.md +47 −5 Changes for src/apren/basics/advanced_python.md: 47 added lines, 5 removed lines. Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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. Loading Loading
src/apren/basics/advanced_python.md +47 −5 Changes for src/apren/basics/advanced_python.md: 47 added lines, 5 removed lines. Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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. Loading