Skip to content

Commit 93bc1bf

Browse files
committed
update learning
1 parent 67ac275 commit 93bc1bf

File tree

17 files changed

+2354
-1354
lines changed

17 files changed

+2354
-1354
lines changed

docs/learning/better_code/mypy/index.md

Lines changed: 253 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,260 @@ Mypy è un **static type checker** su Python. Agisce come un Linter e consente d
44

55
Il codice con Mypy verifica quelli che vengono chiamati **type hints** su Python. Essendo Python un linguaggio non tipizzato la violazione del controllo di Mypy non provoca degli errori di interpretazione o di compilazione, ma causa semplicemente dei warning che possono essere ignorati o analizzati più nel dettagli dal programmatore all'interno dell'IDE.
66

7-
## Python types
7+
Mypy esegue tutti i controlli di tipo senza mai eseguire il codice. Si tratta di quello che viene definito uno **strumento di analisi statica** (questo statico è diverso dallo statico di "tipizzazione statica"), significa che lo strumento funziona non eseguendo il codice python, ma valutando la struttura del programma.
8+
9+
Questo vuol dire che se il vostro programma fa cose particolari come fare chiamate API o cancellare file sul vostro sistema, potete comunque eseguire mypy sui vostri file e non avrà alcun effetto sul mondo reale.
10+
11+
## Usare mypy
12+
13+
È possibile usare **mypy** da terminale, ad esempio:
14+
15+
```python
16+
def double(n):
17+
return n * 2
18+
19+
num = double(21)
20+
print(num)
21+
```
22+
23+
Se lanciamo da terminale **mypy** ci aspettiamo un errore vero?
24+
25+
```bash
26+
$ mypy test.py
27+
Success: no issues found in 1 source file
28+
```
29+
30+
Ma come puoi vedere non succede niente, questo perchè come abbiamo detto nel capitolo generale non si forza mai il typings su python, quindi anche mypy non ci forza ad aggiungere i tipi al nostro codice, ma si limita a controllare quando ci sono.
31+
32+
È tuttavia possibile customizzare mypy in modo che controlli anche questi errori, facendo:
33+
34+
```bash
35+
$ mypy --disallow-untyped-defs test.py
36+
test.py:1: error: Function is missing a return type annotation
37+
Found 1 error in 1 file (checked 1 source file)
38+
```
39+
40+
Ci sono moltissimi `--disallow-` che si possono usare, ma esiste tuttavia un extra che li racchiude tutti: `--strict`. Usando questo argomento di effettua un controllo "stringente" dei tipi.
41+
42+
```bash
43+
# per iniziare provare ad usare:
44+
mypy --strict myfile.py
45+
```
46+
47+
## Configurare mypy
48+
49+
Ci sono diversi modi per configurare Mypy, qui ne vedremo 2 principalmente:
50+
51+
- Con VSCode
52+
- Configurazione di default con i file di configurazione
53+
54+
### Configurazione con VSCode
55+
56+
È possibile all'interno del proprio file nella repository `.vscode/settings.json` inserire i parametri di configurazione di Mypy, installando anche l'estensione dedicata sullo store.
57+
58+
Ad esempio:
59+
60+
```json
61+
...
62+
"python.linting.mypyEnabled": true,
63+
"python.linting.mypyArgs": [
64+
"--ignore-missing-imports",
65+
"--follow-imports=silent",
66+
"--show-column-numbers",
67+
"--strict"
68+
],
69+
...
70+
```
71+
72+
### Configurazione con mypy.ini
73+
74+
Questa è la modalità di default, è possibile creare un file: `mypy.ini` all'interno della propria repository, nel quale è possibile inserire alcune regole che vogliamo controllare e modalità di utilizzo di Mypy.
75+
76+
Per la definizione di tutte le regole e modalità di utilizzo ovviamente facciamo riferimento alla guida ufficiale.
77+
78+
```ini
79+
[mypy]
80+
plugins = pydantic.mypy, sqlmypy
81+
ignore_missing_imports = True
82+
disallow_untyped_defs = True
83+
follow_imports = True
84+
show_column_numbers = True
85+
strict = False
86+
exclude = ['volumes/', "alembic/", "scripts/", "docs/", "settings/", ".vscode/", ".venv/", ".pytest_cache/", ".mypy_cache/", ".gitlab/", ".github/", ".devcontainer/", "Docker/", "dashboards/"]
87+
```
88+
89+
Come puoi vedere è anche possibile su Mypy utilizzare dei plugin. Anche riguardo a questo facciamo riferimento alla guida ufficiale.
90+
91+
## Esempi di Types
92+
93+
Facciamo quindi una serie di esempi di Types che si possono controllare e verificare con Mypy
94+
95+
### Primitive
96+
97+
Quando parliamo di primitive, parliamo dei tipi di default che si trovano nel linguaggio, come ad esempio: `int`, `str`, `float`, `bool`, ...
98+
99+
Ad esempio:
100+
101+
```python
102+
def double(n: int) -> int:
103+
return n * 2
104+
105+
106+
num = double(21)
107+
print(num)
108+
```
109+
110+
Lanciando quindi mypy otteniamo
111+
112+
```bash
113+
$ mypy --strict test.py
114+
Success: no issues found in 1 source file
115+
```
116+
117+
### Collections
118+
119+
Le collections sono anche chiamate **strutture dati** come ad esempio: `List`, `Dict`, `Set`, ...
120+
121+
Nonostante queste collections ricordano le strutture dati come liste, dizionari, set, etc... non sono esattamente gli stessi **builtin collection types** ovvero quelli utilizzati di default dal linguaggio, ma sono delle astrazioni costruite apposta per effettuare dei controlli da mypy.
122+
123+
Un esempio:
124+
125+
```python
126+
from typing import List, Set
127+
128+
def unique_count(nums: List[int]) -> int:
129+
"""counts the number of unique items in the list"""
130+
uniques: Set[int] = set() # Manually added type information
131+
for num in nums:
132+
uniques.add(num)
133+
134+
return len(uniques)
135+
136+
print(unique_count([1, 2, 1, 3, 1, 2, 4, 3, 1])) # 4
137+
```
138+
139+
Un altro esempio utilizzando i dizionari
140+
141+
```python
142+
from typing import Dict
143+
144+
def get_total_marks(scorecard: Dict[str, int]) -> int:
145+
marks = list(scorecard.values()) # marks : List[int]
146+
return sum(marks)
147+
148+
scores = {'english': 84, 'maths': 92, 'history': 75}
149+
print(get_total_marks(scores)) # 251
150+
```
151+
152+
A partire da Python 3.7 tuttavia è possibile anche importare le annotazioni, ovvero avere la possibilità all'inizio del file di usare i tipi builtin come generic. Da Python 3.9 in poi è necessario solamente importare future per importarli tutti.
153+
Un esempio con Python 3.9
154+
155+
```python
156+
from typing import List
157+
import __future__
158+
159+
# With mypy
160+
my_var: List[int]
161+
162+
## With future (normal python built-in)
163+
my_var_normal: list[int]
164+
```
165+
166+
Ci sono degli edge-cases dove questo non funziona, ma sono rari. Tutto questo è descritto all'interno del [PEP 585](https://peps.python.org/pep-0585/)
167+
168+
### Debug
169+
170+
Oltre a tipizzare il codice, si può anche usare **mypy** come strumento di **debugger**, ovvero quando leggi codice di altri o di cui non conosci il tipo, è possibile utilizzare la funzione: `reveal_type` che consente di rivelare il tipo di variabile che si utilizza all'interno del codice, un esempio:
171+
172+
```python
173+
from typing import List, Set
174+
175+
def unique_count(nums: List[int]) -> int:
176+
"""counts the number of unique items in the list"""
177+
uniques: Set[int] = set()
178+
for num in nums:
179+
uniques.add(num)
180+
181+
return len(uniques)
182+
183+
counts = unique_count([1, 2, 1, 3, 1, 2, 4, 3, 1])
184+
185+
reveal_type(counts) # The special magic reveal_type method
186+
```
187+
188+
```bash
189+
$ mypy --strict test.py
190+
test.py:12: note: Revealed type is 'builtins.int'
191+
```
192+
193+
`reveal_type` è una funzione speciale di mypy che non è usata nel codice. Tuttavia è importante ricordarsi di **rimuovere questa funzione una volta finito il debugging e il check del codice**.
194+
195+
### Union e Optional
196+
197+
Fino ad ora abbiamo visto come forzare l'utilizzo di un tipo, ma possono capitare dei casi dove è necessario specificare più di un tipo di variabile per un determinato parametro o metodo.
198+
199+
Per fare questo è possibile utilizzare:
200+
201+
- Union: quando vogliamo dire che si possono usare più parametri
202+
- Optional: quando questi tipe hints sono opzionali e non strettamente necessari
203+
204+
Facciamo degli esempi
205+
206+
```python
207+
from typing import List, Union
208+
209+
def print_item(item: Union[str, List[str]]) -> None:
210+
reveal_type(item)
211+
212+
if isinstance(item, list):
213+
for data in item:
214+
reveal_type(item)
215+
print(data)
216+
else:
217+
reveal_type(item)
218+
print(item)
219+
220+
print_item('Hi!')
221+
print_item(['This is a test', 'of polymorphism'])
222+
```
223+
224+
```bash
225+
$ mypy test.py
226+
test.py:4: note: Revealed type is 'Union[builtins.str, builtins.list[builtins.str]]'
227+
test.py:8: note: Revealed type is 'builtins.list[builtins.str]'
228+
test.py:11: note: Revealed type is 'builtins.str'
229+
```
230+
231+
### Any
232+
233+
Quando non sappiamo nello specifico un tipo di dato, come nel caso di una lettura di un file che può contenere diversi tipi, è possibile utilizzare in Mypy: `Any`.
234+
235+
`Any` in realtà quello che fa è disabilitare il type checking, consentendoci di gestire la variabile o l'oggetto come vogliamo.
236+
237+
```python
238+
import json
239+
from typing import Any
240+
241+
import requests
242+
243+
def post_data_to_api(data: Any) -> None:
244+
requests.post('https://example.com/post', json=data)
245+
246+
data = '{"num": 42, "info": null}'
247+
parsed_data = json.loads(data)
248+
reveal_type(parsed_data) # Revealed type is 'Any'
249+
250+
post_data_to_api(data)
251+
```
252+
253+
Ovviamente ci sono dei concetti molto più avanzati e approfonditi, come i generics, l'uso dei typings nelle classi e moltissimo altro all'interno di Mypy.
254+
255+
Vi rimandiamo alla sezione di approfondimenti qui sotto per seguire i link che ti abbiamo messo per approfondire questo tema.
256+
257+
## Approfondimenti
8258

9259
Alcuni articoli per approfondire questi concetti
10260

11-
- [Type checking with Mypy](https://realpython.com/lessons/type-checking-mypy/)
12261
- [The comprehensive guide to mypy](https://tushar.lol/post/mypy-guide/)
262+
- [Type checking with Mypy](https://realpython.com/lessons/type-checking-mypy/)
263+
- [Python type checking guide](https://realpython.com/python-type-checking/)
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Ruff
2+
3+
Ruff è uno dei Python Linter più veloci disponibili, grazie all'implementazione in Rust e può essere usato per rimpiazzare numerose librerie in Python in quanto contiene al suo interno numerosissime features.
4+
5+
Alcuni tool che consente di rimpiazzare:
6+
7+
- Flake8 (con le estensioni)
8+
- Isort
9+
- pyupgrade
10+
- autoflake
11+
- pep8-naming
12+
- pydocstyle
13+
14+
## Features
15+
16+
Ruff è ancora in sviluppo e in cambiamento, quindi sicuramente si evolverà ancora nel corso del tempo, qui scriviamo alcune features interessanti e che possono essere utili.
17+
18+
### Velocità
19+
20+
Essendo Ruff anche uno strumento di analisi statica del codice è necessario che sia molto veloce ed efficiente per garantire una copertura e un controllo veloce nel nostro codice all'interno dell'IDE senza appesantire i processi, dover attendere o dover per forza lanciare il codice.
21+
22+
Se sei interessato ai benchmark ci sono alcuni benchmark [all'interno del sito ufficiale](https://docs.astral.sh/ruff/contributing/?ref=blog.jerrycodes.com#benchmarking-and-profiling)
23+
24+
### Supporto
25+
26+
Attualmente Ruff racchiude le features di almeno altri 50 Python Package, creando una collezione di almeno 700 regole di lint. Tutte queste regole sono rimplementate in Ruff.
27+
28+
All'interno della [documentazione ufficiale](https://docs.astral.sh/ruff/rules/?ref=blog.jerrycodes.com#rules) è possibile trovare l'elenco esaustivo delle regole che vengono implementate anche in altre librerie. Sarebbe inutile e troppo lungo descriverle tutte qui con esempi.
29+
30+
### Configurazione
31+
32+
È molto facile configurare Ruff, per farlo è possibile creare un file di configurazione dedicato all'interno della repository chiamato `ruff.toml` contenente tutte le regole e configurazioni. Tuttavia se utilizzi `pdm` o `poetry` è anche possibile inserire le regole all'interno dei file di configurazione `pyproject.toml` in modo da tenere in un unico file tutte le regole necessarie per la configurazione del progetto.
33+
34+
Per capire come configurare al meglio Ruff, la documentazione ufficiale offre degli ottimi spunti a riguardo:
35+
36+
- [Configurare ruff](https://docs.astral.sh/ruff/configuration/)
37+
- [Tutte le impostazioni disponibili](https://docs.astral.sh/ruff/settings/)
38+
- [Elenco delle regole disponibili](https://docs.astral.sh/ruff/rules/)
39+
40+
### Integrazione con l'IDE
41+
42+
Vista la crescente popolarità di Ruff, lo strumento è stato integrato all'interno degli IDE più famosi come Pycharm e VSCode.
43+
44+
Su VSCode è possibile installare l'estensione dedicata che consente di utilizzare tutte le potenzialità dello strumento.
45+
46+
### Fix automatico del codice
47+
48+
Oltre a segnalare i problemi Ruff ha integrato uno strumento di **autofix** del codice.
49+
50+
È uno strumento molto potente che consente di applicare le regole di ruff e automaticamente cambiare il codice per adattarsi a queste regole.
51+
52+
Per farlo è necessario installare ruff e chiamare da terminale le sue funzionalità:
53+
54+
```bash
55+
ruff check --fix .
56+
57+
# Lint all files in `/path/to/code` (and any subdirectories)
58+
$ ruff check path/to/code/
59+
60+
# Lint all `.py` files in `/path/to/code`
61+
$ ruff check path/to/code/*.py
62+
63+
# Lint `file.py`
64+
$ ruff check path/to/code/to/file.py
65+
```
66+
67+
### Ignorare gli errori nel codice
68+
69+
Come altri strumenti Ruff consente di ignorare direttamente degli errori nel codice, in un file o in una linea.
70+
71+
Questo metodo è ovviamente non consigliato, è sempre meglio cercare di risolvere tutte le problematiche.
72+
73+
Per farlo è necessario utilizzare il commento `# noqa`, si può usare specificando il tipo di errore (scelta consigliata) con il codice di riferimento, oppure senza specificare il codice in modo generico.
74+
75+
Qui alcuni esempi:
76+
77+
#### Ignorare errori in linea nel codice
78+
79+
```python
80+
from typing import List
81+
82+
def sum_even_numbers(numbers: List[int]) -> int: # noqa: UP006
83+
"""Given a list of integers, return the sum of all even numbers in the list."""
84+
return sum(num for num in numbers if num % 2 == 0)
85+
```
86+
87+
#### Ignorare errori all'interno di tutto il file
88+
89+
In questo modo verranno ignorati tutti gli errori di tipo `UP006` all'interno del file, ricordiamo che è sempre possibile non specificare il tipo di errore anche se non è consigliato, anche perchè a cosa serve altrimenti averlo installato e utilizzarlo?
90+
91+
```python
92+
# ruff: noqa: UP006
93+
from typing import List
94+
95+
def sum_even_numbers(numbers: List[int]) -> int:
96+
"""Given a list of integers, return the sum of all even numbers in the list."""
97+
return sum(num for num in numbers if num % 2 == 0)
98+
```
99+
100+
#### Ignorare errori nel file di configurazione
101+
102+
```ini
103+
# Enable flake8-bugbear (`B`) rules.
104+
select = ["E", "F", "B"]
105+
106+
# Never enforce `E501` (line length violations).
107+
ignore = ["E501"]
108+
109+
# Avoid trying to fix flake8-bugbear (`B`) violations.
110+
unfixable = ["B"]
111+
112+
# Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`.
113+
[per-file-ignores]
114+
"__init__.py" = ["E402"]
115+
"path/to/file.py" = ["E402"]
116+
```
117+
118+
#### Ignorare particolari errori da command line
119+
120+
È anche possibile ignorare particolari errori quando si lancia da command line, facendo
121+
122+
```bash
123+
$ ruff check path/to/code/ --ignore E402
124+
```
125+
126+
## Consigli
127+
128+
Siccome Ruff è uno strumento molto potente e omnicomprensivo, sconsigliamo di creare una configurazione molto estesa fin da subito. È un processo che richiede tempo e tante modifiche prima di trovare una serie di regole che possono adeguarsi al progetto o alle regole all'interno di un team o di una organizzazione.
129+
130+
Se sei interessato ad un esempio di configurazione ti rimandiamo alla nostra configurazione che utilizziamo all'interno della [repository di PythonBiellaGroup, Bear](https://github.com/PythonBiellaGroup/Bear/blob/main/.ruff.toml)

0 commit comments

Comments
 (0)