warnings - Avvisi Non Fatali

Scopo: Fornisce avvertimenti non fatali all'utente circa problemi incontrati durante l'esecuzione di un programma.

Il modulo warnings era stato introdotto dal PEP 230 come modo per avvertire i programmatori di cambiamenti nel linguaggio o di caratteristiche delle librerie come anticipazione di modifiche di incompatibilità retroattiva che sarebbero intervenute con Python 3.0. Può anche essere usato per segnalare errori di configurazione recuperabili o degrado di caratteristiche dovuto a librerie mancanti. E' meglio tuttavia inviare messaggi all'utente tramite il modulo logging, visto che gli avvertimenti inviati via console potrebbero andare perduti.

Visto che gli avvertimenti non sono bloccanti, un programma potrebbe incorrere nella stessa situazione suscettibile di avvertimenti molte volte nel corso della sua esecuzione. Il modulo warnings sopprime i messaggi ripetuti generati dalla stessa fonte per ridurre il disturbo causato dal vedere lo stesso messaggio più e più volte. E' possibile controllare i messaggi stampati caso per caso usando le opzioni da riga di comando dell'interprete oppure chiamando funzioni del modulo warnings.

Categorizzare e Filtrare

Gli avvertimenti sono categorizzati usando sottoclassi della classe di eccezione built-in Warning. Diversi valori standard sono descritti nella documentazione in linea del modulo exceptions; messaggi personalizzati possono essere aggiunti subclassando da Warning.

Gli avvertimenti sono elaborati in base alle impostazioni di filtri. Un filtro consiste di 5 parti: action (azione), message (messaggio), category (categoria), module (modulo) e line number (numero di riga). La porzione message del filtro è una espressione regolare usata per trovare corrispondenza con il testo di avvertimento. category è un nome di una classe di eccezione. module contiene una espressione regolare per trovare corrispondenza con il nome del modulo che sta generando l'avvertimento. line number può essere usato per modificare la gestione di occorrenze specifiche di un avvertimento.

Quando viene generato un avvertimento, viene confrontato con tutti i filtri registrati. Il primo filtro che trova corrispondenza controlla l'azione intrapresa per l'avvertimento. Se non viene trovata alcuna corrispondenza, viene intrapresa l'azione predefinita. Le azioni comprese dal meccanismo di filtro sono elencate nella tabella sottostante:

AZIONE SIGNIFICATO
error Trasforma l'avvertimento in una eccezione
ignore Ignora l'avvertimento
always Emette sempre l'avvertimento
default Stampa l'avvertimento la prima volta che viene generato da ciascuna locazione
module Stampa l'avvertimento la prima volta che viene generato da ciascun modulo
once Stampa l'avvertimento solo la prima volta che viene generato

Generare Avvertimenti

Il modo più semplice di emettere un avvertimento nel proprio codice è di chiamare warn() passando il messaggio come argomento:

# warnings_warn.py

import warnings

print("Prima dell'avvertimento")
warnings.warn('Questo è un messaggio di avvertimento')
print("Dopo l'avvertimento")

Successivamente, quando il programma viene eseguito, viene stampato il messaggio.

$ python3 warnings_warn.py

Prima dell'avvertimento
warnings_warn.py:6: UserWarning: Questo è un messaggio di avvertimento
  warnings.warn('Questo è un messaggio di avvertimento')
Dopo l'avvertimento

Sebbene il messaggio sia stato stampato, il comportamento predefinito è quello di procedere oltre l'avvertimento ed eseguire il resto del programma. Si può modificare questo comportamento con un filtro.

# warnings_warn_raise.py

import warnings

warnings.simplefilter('error', UserWarning)

print("Prima dell'avvertimento")
warnings.warn('Questo è un messaggio di avvertimento')
print("Dopo l'avvertimento")

In questo esempio la funzione simplefilter() aggiunge una voce alla lista interna dei filtri per dire al modulo warnings di sollevare una eccezione quando viene rilasciato un avvertimento UserWarning.

$ python3 warnings_warn_raise.py

Prima dell'avvertimento
Traceback (most recent call last):
  File "warnings_warn_raise.py", line 8, in <module>
    warnings.warn('Questo è un messaggio di avvertimento')
UserWarning: Questo è un messaggio di avvertimento

Si può anche controllare il comportamento del filtro da riga di comando, tramite l'opzione -W dell'interprete. Si specifichino le proprietà del filtro come stringa con le cinque parti sopra citate separate da :; ad esempio se warnings_warn.py viene eseguito con un filtro impostato per sollevare un UserWarning, viene prodotta una eccezione.

$ python3 -W "error::UserWarning::0" warnings_warn.py

Prima dell'avvertimento
Traceback (most recent call last):
  File "warnings_warn.py", line 6, in <module>
    warnings.warn('Questo è un messaggio di avvertimento')
UserWarning: Questo è un messaggio di avvertimento

Visto che i campi per message e module sono vuoti, sono interpretati per trovare corrispondenza con qualsiasi cosa.

Filtrare con Modelli

Per filtrare da programma su regole più complesse si usi filterwarnings(). Ad esempio per filtrare in base al contenuto del testo del messaggio si passi un modello di espressione regolare come argomento di message.

# warnings_filterwarnings_message.py

import warnings

warnings.filterwarnings('ignore', '.*non mostrare.*',)

warnings.warn('Mostra questo messaggio')
warnings.warn('Non mostrare questo messaggio')

Il modello contiene "non mostrare", ma il vero messaggio usa "Non mostrare". La corrispondenza viene trovata perchè l'espressione regolare è sempre compilata per ignorare il confronto tra maiuscole e minuscole.

$ python3 warnings_filterwarnings_message.py

warnings_filterwarnings_message.py:7: UserWarning: Mostra questo messaggio
  warnings.warn('Mostra questo messaggio')

Il programma di esempio che segue genera due avvertimenti.

# warnings_filter.py

import warnings

warnings.warn('Mostra questo messaggio')
warnings.warn('Non mostrare questo messaggio')

Uno degli avvertimenti può essere ignorato usando l'argomento di filtro da riga di comando.

$ python3 -W "ignore:non mostrare:UserWarning::0" warnings_filtering.py

warnings_filtering.py:6: UserWarning: Mostra questo messaggio
  warnings.warn('Mostra questo messaggio')

Le stesse regole di corrispondenza del modello si applicano al nome del modulo sorgente che contiene la chiamata che ha generato l'avvertimento. E' possibile sopprimere tutti gli avvertimenti dal modulo warnings_filter passando il nome del modulo come modello all'argomento module.

# warnings_filterwarnings_module.py

import warnings

warnings.filterwarnings(
    'ignore',
    '.*',
    UserWarning,
    'warnings_filter',
)

import warnings_filter

Visto che è attivo il filtro, nessun avvertimento viene emesso all'importazione di warnings_filter.

$ python3  warnings_filterwarnings_module.py

Per sopprimere solo l'avvertimento nella riga 6 di warnings_filter, si includa il numero di riga come ultimo argomento per filterwarnings(). Si usi l'effettivo numero di riga dal file sorgente per limitare il filtro, oppure 0 per applicare il filtro a tutte le occorrenze del messaggio.

# warnings_filterwarnings_lineno.py

import warnings

warnings.filterwarnings(
    'ignore',
    '.*',
    UserWarning,
    'warnings_filter',
    6,
)

import warnings_filter

Il modello trova corrispondenza con qualunque messaggio, quindi gli argomenti rilevanti sono il nome del modulo e il numero di riga.

$ python3  warnings_filterwarnings_lineno.py

.../warnings_filter.py:5: UserWarning: Mostra questo messaggio
  warnings.warn('Mostra questo messaggio')

Avvertimenti Ripetuti

Nella modalità predefinita la maggior parte degli avvertimenti sono stampati solo la prima volta che vengono rilevati in una certa locazione, laddove per locazione si intende la combinazione modulo/numero riga dove viene generato l'avvertimento.

# warnings_repeated.py

import warnings


def function_with_warning():
    warnings.warn('Questo è un avvertimento!')


function_with_warning()
function_with_warning()
function_with_warning()

Questo esempio chiama la stessa funzione diverse volte, ma produce un singolo avvertimento.

$ python3 warnings_repeated.py

warnings_repeated.py:7: UserWarning: Questo è un avvertimento!
  warnings.warn('Questo è un avvertimento!')

L'azione "once" (si veda tabella qui sopra) può essere usata per sopprimere istanze dello stesso messaggio da locazioni diverse.

# warnings_once.py

import warnings

warnings.simplefilter('once', UserWarning)

warnings.warn('Questo è un avvertimento!')
warnings.warn('Questo è un avvertimento!')
warnings.warn('Questo è un avvertimento!')

Il testo del messaggio per tutti gli avvertimenti viene salvato e sono stampati solo i messaggi univoci.

$ python3 warnings_once.py

warnings_once.py:7: UserWarning: Questo è un avvertimento!
  warnings.warn('Questo è un avvertimento!')

Similarmente, module sopprimerà messaggi ripetuti dallo stesso modulo, non importa da quale numero di riga.

Funzioni di Consegna Messaggio Alternative

Normalmente i messaggi vengono stampati verso sys.stderr. E' possibile modificare questo comportamento sostituendo la funzione showwarning() all'interno del modulo warnings. Ad esempio se si vuole che i messaggi siano destinati a un file di log invece che al canale di errore standard, si rimpiazzi showwarnings() con una funzione che registra gli avvertimenti.

# warnings_showwarning.py

import warnings
import logging


def send_warnings_to_log(message, category, filename, lineno,
                         file=None, line=None):
    logging.warning(
        '%s:%s: %s:%s',
        filename, lineno,
        category.__name__, message,
    )


logging.basicConfig(level=logging.INFO)

old_showwarning = warnings.showwarning
warnings.showwarning = send_warnings_to_log

warnings.warn('message')

Gli avvertimenti sono emessi assieme al resto dei messaggi di registrazione quando viene chiamato warn(),.

$ python3 warnings_showwarning.py

WARNING:root:warnings_showwarning.py:21: UserWarning:message

Formattazione

Se sta bene che gli avvertimenti vadano verso il canale di errore standard, ma non piace la formattazione, è possibile sostituire formatwarning().

# warnings_formatwarning.py

import warnings


def warning_on_one_line(message, category, filename, lineno,
                        file=None, line=None):
    return '-> {}:{}: {}:{}'.format(
        filename, lineno, category.__name__, message)


warnings.warn('Messaggio di avvertimento, prima')
warnings.formatwarning = warning_on_one_line
warnings.warn('Messaggio di avvertimento, dopo')

La funzione di formattazione deve restituire una singola stringa che contiene la rappresentazione dell'avvertimento da visualizzare all'utente.

$ python3 warnings_formatwarning.py

warnings_formatwarning.py:12: UserWarning: Messaggio di avvertimento, prima
  warnings.warn('Messaggio di avvertimento, prima')
-> warnings_formatwarning.py:14: UserWarning:Messaggio di avvertimento, dopo

Livello di Stack negli Avvertimenti

Nella modalità predefinita il messaggio di avvertimento include la riga sorgente che lo ha generato, quando disponibile. Non è sempre così utile vedere la riga di codice con l'effettivo messaggio di avvertimento. Si può dire a warn() di quanto risalire lo stack per trovare la riga che ha chiamato la funzione contenente l'avvertimento. In questo modo gli utilizzatori di funzioni deprecate vedono dove è stata chiamata la funzione e non l'implementazione della funzione.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# warnings_warn_stacklevel.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import warnings

def old_function():
    warnings.warn(
        'old_function() è deprecata, utilizzare new_function() al suo posto',
        stacklevel=2)

def caller_of_old_function():
    old_function()

caller_of_old_function()

In questo esempio warn() deve risalire 2 livelli di stack, uno per se stesso e un altro per old_function().

$ python3 warnings_warn_stacklevel.py

warnings_warn_stacklevel.py:14: UserWarning: old_function() è deprecata, utilizzare new_function() al suo posto
  old_function()

Vedere anche:

warnings
La documentazione della libreria standard per questo modulo
PEP 230
Warning Framework
exceptions
Classi base per eccezioni e avvertimenti
logging
Un meccanismo alternativo per consegnare messaggi è di scriverli nel registro.