Spesso può capitare di dover "portare" codice scritto in un linguaggio all'interno di un progetto scritto con un altro linguaggio: si tratta di un lavoro interessante che spesso porta a conoscere aspetti inattesi del linguaggio di destinazione.
Nel tradurre un programma da C a Python mi sono imbattuto in una funzione che utilizzava una variabile static e quindi è sorta la domanda: "in Python esistono variabili statiche?".
In questo articolo troverete alcune soluzioni che ho pescato dalla rete.

python 400

In C una variabile locale static è una variabile che mantiene il suo valore anche dopo l'uscita dalla funzione; può essere pensata come una variabile globale visibile solo alla funzione.
Nell'esempio qui sotto la variabile varstat verrà incrementata ad ogni chiamata: compilando ed eseguendo il programma si otterrà la stampa dei valori da 1 a 5:

void miafunc(void)
{
    static int varstat=0;
    varstat++;
    printf("%d\n",varstat);
}

void main(void)

{
    miafunc();
    miafunc();
    miafunc();
    miafunc();
    miafunc();
}

Premesso che in Python tale concetto non esiste, è poco "pythonico", vediamo alcuni metodi per tradurre tale funzione.

1) RENDERE GLOBALE LA VARIABILE

La soluzione più semplice è quella di rendere globale la variabile: questa è stata soluzione che ho adottato, prima di effettuare ulteriori approfondimenti.
Eseguendo l'esempio qui sotto si otterrà la stampa dei valori da 1 a 5, come nel sorgente C:

varstat=0

def miafunc():
    global varstat
    varstat += 1
    print(varstat)

if __name__ == "__main__":
    miafunc()
    miafunc()
    miafunc()
    miafunc()
    miafunc()

2) USARE GLI ATTRIBUTI

No, non preoccupatevi, non intendo soluzioni in cui è necessario mostrare il proprio orgoglio!!!

In Python le funzioni sono oggetti e quindi come tali hanno degli attributi, come gli altri oggetti. Un interessante riferimento lo potete trovare qui https://stackoverflow.com/questions/338101/python-function-attributes-uses-and-abuses

def miafunc():
    miafunc.varstat += 1
    print(miafunc.varstat)

miafunc.varstat=0
    
if __name__ == "__main__":
    miafunc()
    miafunc()
    miafunc()
    miafunc()
    miafunc()

Questa soluzione è molto più "pythonica" ma l'aspetto negativo è che l'attributo deve comunque essere inizializzato fuori dalla funzione.

3) UTILIZZARE GLI ATTRIBUTI E L'ECCEZIONE AttributeError PER L'INIZIALIZZAZIONE

Un miglioramento della precedente soluzione è quello di sfruttare l'eccezione AttributeError che viene sollevata quando si utilizza un attributo non inizializzato

def miafunc():
    try:
        miafunc.varstat += 1
    except AttributeError:
        miafunc.varstat = 1      
    print(miafunc.varstat)

if __name__ == "__main__":
    miafunc()
    miafunc()
    miafunc()
    miafunc()
    miafunc()

Questa soluzione a mio avviso è la migliore: tutto è implementato all'interno della funzione e il funzionamento è evidente.

4) USARE GLI ATTRIBUTI E getattr() PER L'INIZIALIZZAZIONE

In questo caso si usa la funzione getattr() per leggere il valore dell'attributo; la funzione getattr() può essere richiamata con il valore di inizializzazione dell'attributo:

def miafunc():
    miafunc.varstat = getattr(miafunc, 'varstat', 0) + 1
    print(miafunc.varstat)

if __name__ == "__main__":
    miafunc()
    miafunc()
    miafunc()
    miafunc()
    miafunc()

5) USARE UNA LISTA (mutable object) CON VALORE DI DEFAULT

Questa soluzione (spiegata qui http://effbot.org/zone/default-values.htm ) mi sembrava molto elegante, ma forse un po' difficile da capire.
Si basa sul fatto che i valori di default degli argomenti di funzione vengono creati alla prima chiamata.
Nel caso di oggetti non mutable tale comportamento non genera effetti collaterali in quanto eventuali modifiche della variabile all'interno della funzione non ne cambieranno il valore "globale"; invece nel caso di mutable object (ad esempio una lista) sarà possibile modificare il valore anche per le successive chiamate.
Utilizzando una lista possiamo quindi scrivere

def miafunc(varstat=[0]):
    varstat[0]+=1
    print(varstat[0])

if __name__ == "__main__":
    miafunc()
    miafunc()
    miafunc()
    miafunc()
    miafunc()

In questo caso sfruttiamo il primo elemento (varstat[0] inizializzato a 0) per tenere traccia del nostro contatore.
Se cercate "Python mutable defaults" troverete diversi articoli che vi mettono in guardia sul comportamento dei mutable object come argomenti di default; in questo caso, però, sfruttiamo questa caratteristica a nostro vantaggio!

CONCLUSIONI

Naturalmente esistono altri modi per ottenere qualcosa di simile alle variabili static; controllando i link qui in fondo troverete molti spunti interessanti. Buono studio!!!

FONTI

https://stackoverflow.com/questions/338101/python-function-attributes-uses-and-abuses

http://effbot.org/zone/default-values.htm

http://it.comp.lang.python.narkive.com/zj4F1Wa1/variabili-statiche

https://stackoverflow.com/questions/279561/what-is-the-python-equivalent-of-static-variables-inside-a-function