Qual è l'equivalente Python delle variabili statiche all'interno di una funzione?


631

Qual è l'equivalente Python idiomatico di questo codice C / C ++?

void foo()
{
    static int counter = 0;
    counter++;
    printf("counter is %d\n", counter);
}

in particolare, come si implementa il membro statico a livello di funzione, a differenza del livello di classe? E inserendo la funzione in una classe cambia qualcosa?


22
C'è NO equivalenza ho paura. Anche se esegui l'hacking del decoratore con attributi di funzione, sarai in grado di accedere alla variabile all'esterno, che in qualche modo sconfigge il punto, purtroppo. Inoltre, dovrai codificare il nome della funzione nella funzione, il che è molto fastidioso. Suggerirei di utilizzare una classe o un modulo variabili globali invece con il _prefisso convenzionale .
lpapp,

8
Per i programmatori non-C, [ stackoverflow.com/questions/5033627/… la variabile statica all'interno di una funzione è visibile solo all'interno dell'ambito di tale funzione, ma la sua durata è l'intera vita del programma ed è inizializzata una sola volta). Fondamentalmente, un contatore persistente o una variabile di memoria che vive tra le chiamate di funzione.
smci,

2
@lpapp: esiste, è un membro della classe . Hai ragione sul fatto che non possiamo impedire ad altri codici di visualizzarlo o modificarlo.
smci,

Risposte:


681

Un po 'invertito, ma questo dovrebbe funzionare:

def foo():
    foo.counter += 1
    print "Counter is %d" % foo.counter
foo.counter = 0

Se si desidera il codice di inizializzazione del contatore in alto anziché in basso, è possibile creare un decoratore:

def static_vars(**kwargs):
    def decorate(func):
        for k in kwargs:
            setattr(func, k, kwargs[k])
        return func
    return decorate

Quindi utilizzare il codice in questo modo:

@static_vars(counter=0)
def foo():
    foo.counter += 1
    print "Counter is %d" % foo.counter

foo.Sfortunatamente ti richiederà comunque di usare il prefisso.

(Credito: @ony )


23
c'è solo un'istanza di foo: questa funzione. tutte le invocazioni accedono alla stessa variabile.
Claudiu,

121
Scusami se ho scoperto questo, ma preferirei mettere if "counter" not in foo.__dict__: foo.counter = 0le prime righe di foo(). Ciò contribuirebbe ad evitare il codice al di fuori della funzione. Non sono sicuro se ciò fosse possibile nel 2008. PS Ho trovato questa risposta mentre cercavo la possibilità di creare variabili di funzione statica, quindi questo thread è ancora "vivo" :)
binaryLV

8
@binaryLV: Probabilmente preferirei questo al primo approccio. Il problema con il primo approccio è che non è immediatamente ovvio fooe foo.counter = sono intimamente correlati. tuttavia, alla fine preferisco l'approccio del decoratore, poiché non è possibile che il decoratore non venga chiamato ed è semanticamente più ovvio ciò che fa ( @static_var("counter", 0)è più facile e ha più senso per i miei occhi rispetto a if "counter" not in foo.__dict__: foo.counter = 0, soprattutto come in quest'ultimo che devi usare il nome della funzione (due volte) che potrebbe cambiare).
Claudiu

6
@lpapp: dipende da quale sia il punto delle variabili statiche. Ho sempre pensato che sarebbe stato lo stesso valore su più chiamate di funzione, cosa che soddisfa. Non ho mai pensato che si trattasse di nascondersi variabile, cosa che non succede, come hai detto.
Claudiu

3
def foo(): if not hasattr(foo,"counter"): foo.counter=0 foo.counter += 1
Erik Aronesty,

222

È possibile aggiungere attributi a una funzione e usarla come variabile statica.

def myfunc():
  myfunc.counter += 1
  print myfunc.counter

# attribute must be initialized
myfunc.counter = 0

In alternativa, se non si desidera impostare la variabile all'esterno della funzione, è possibile utilizzare hasattr()per evitare AttributeErrorun'eccezione:

def myfunc():
  if not hasattr(myfunc, "counter"):
     myfunc.counter = 0  # it doesn't exist yet, so initialize it
  myfunc.counter += 1

Le variabili statiche sono comunque piuttosto rare e dovresti trovare un posto migliore per questa variabile, molto probabilmente all'interno di una classe.


6
Perché non provare invece dell'istruzione if?
Ravwojdyla,

12
try: myfunc.counter += 1; except AttributeError: myfunc.counter = 1dovrebbe fare lo stesso, usando invece le eccezioni.
Slitta

Le eccezioni dovrebbero essere utilizzate per situazioni eccezionali, vale a dire quelle che il programmatore si aspetta non accadranno, come un file di input che è stato aperto con successo all'improvviso non disponibile. Questa è una situazione prevista, un'istruzione if ha più senso.
Hack vide il

11
@Hack_Saw: Beh, questo è Pythonic (meglio chiedere perdono che permesso). Questo è in realtà raccomandato nelle tecniche di ottimizzazione di Python poiché consente di risparmiare il costo di un if (anche se non sto raccomandando l'ottimizzazione prematura). La tua regola su casi eccezionali: 1. Il fallimento È un caso eccezionale qui, in un certo senso. Succede solo una volta. 2. Penso che la regola riguardi l'uso (ovvero la raccolta) di eccezioni. Questa è un'eccezione per qualcosa che ti aspetti di funzionare ma per cui hai un piano di backup, che è una cosa comune nella maggior parte delle lingue.
leewz,

@leewangzhong: racchiudere un blocco che non solleva un'eccezione comporta trycosti aggiuntivi? Solo curioso.
trss,

202

Si potrebbe anche considerare:

def foo():
    try:
        foo.counter += 1
    except AttributeError:
        foo.counter = 1

Ragionamento:

  • molto pitone ("chiedi perdono, non permesso")
  • usa l'eccezione (generata una sola volta) invece del iframo (pensa l' eccezione StopIteration )

11
Non sto facendo Python da molto tempo, ma questo soddisfa uno dei concetti impliciti del linguaggio: se non è (abbastanza) facile, lo stai facendo male .
ZX9,

Non ha funzionato immediatamente con i metodi di classe, "self.foo.counter = 1" solleva nuovamente AttributeError.
villasv,

16
Questa è la soluzione corretta e dovrebbe essere la risposta accettata perché il codice di inizializzazione verrà eseguito quando viene chiamata la funzione e non quando viene eseguito il modulo o quando viene importato qualcosa da esso, il che è il caso se si utilizza l'approccio decorator da la risposta attualmente accettata. Vedi l' esecuzione della funzione decoratore Python . Se hai un enorme modulo di libreria, verrà eseguito ogni decoratore, inclusi quelli delle funzioni che non importi.
Nils Lindemann,

3
Un approccio più semplice: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCuriousOne,

5
@MANU L'utilizzo hasattr()per questo non è più semplice e anche meno efficiente.
Moooeeeep

48

Altre risposte hanno dimostrato il modo in cui dovresti farlo. Ecco un modo non dovresti:

>>> def foo(counter=[0]):
...   counter[0] += 1
...   print("Counter is %i." % counter[0]);
... 
>>> foo()
Counter is 1.
>>> foo()
Counter is 2.
>>> 

I valori predefiniti vengono inizializzati solo quando la funzione viene valutata per la prima volta, non ogni volta che viene eseguita, quindi è possibile utilizzare un elenco o qualsiasi altro oggetto mutabile per memorizzare valori statici.


L'ho provato, ma per qualche ragione, il parametro della funzione si stava inizializzando a 140, non a 0. Perché sarebbe questo?
andrewdotnich,

1
@bouvard Per le funzioni ricorsive che richiedono una variabile statica, questa è l'unica che legge davvero bene.
lifebalance

1
Ho provato diversi approcci e vorrei che questo fosse accettato come pitonico. Con alcuni nomi significativi come def foo(arg1, arg2, _localstorage=DataClass(counter=0))lo trovo ben leggibile. Un altro aspetto positivo è la facile ridenominazione delle funzioni.
VPfB

2
Perché dici che non dovresti farlo in questo modo? Mi sembra perfettamente ragionevole!
Konstantin,

1
@VPfB: per l'archiviazione generale, è possibile utilizzare types.SimpleNamespace, rendendolo def foo(arg1, arg2, _staticstorage=types.SimpleNamespace(counter=0)):senza la necessità di definire una classe speciale.
ShadowRanger,

43

Molte persone hanno già suggerito di provare 'hasattr', ma c'è una risposta più semplice:

def func():
    func.counter = getattr(func, 'counter', 0) + 1

Nessun tentativo / tranne, nessun test hasattr, solo getattr con un valore predefinito.


2
prestare attenzione al terzo parm di getattr quando si inserisce una funzione lì ad esempio: def func (): def foo (): return 1112 func.counter = getattr (func, 'counter', foo ()) + 1 quando si chiama func, il foo sarà sempre chiamato!
Codice del

1
Basta una chiamata a getattr ogni volta che viene chiamato func. Va bene se le prestazioni non sono un problema, se è provare / tranne vincerà a mani basse.
Mark Lawrence,

2
@MarkLawrence: In realtà, almeno sulla mia installazione di Windows x64 3.8.0, la differenza di prestazioni tra questa risposta e l' approccio basato try/ equivalente di ravwojdylaexcept è piuttosto insignificante. Un semplice ipython %%timeitmicrobenchmark ha dato il costo di try/ excepta 255 ns per chiamata, rispetto a 263 ns per la getattrsoluzione di base. Sì, il try/ exceptè più veloce, ma non è esattamente "vincente"; è una micro-ottimizzazione minuscola. Scrivi qualunque codice sembri più chiaro, non preoccuparti delle banali differenze prestazionali come questa.
ShadowRanger,

@ShadowRanger grazie per il benchmarking. Mi sono chiesto della dichiarazione di MarkLawrence per 2 anni e sono molto felice che tu abbia fatto la ricerca. Sono assolutamente d'accordo con la tua frase finale - "scrivi qualunque codice sembri più chiaro" - questo è esattamente il motivo per cui ho scritto questa risposta.
Jonathan,

28

Ecco una versione completamente incapsulata che non richiede una chiamata di inizializzazione esterna:

def fn():
    fn.counter=vars(fn).setdefault('counter',-1)
    fn.counter+=1
    print (fn.counter)

In Python, le funzioni sono oggetti e possiamo semplicemente aggiungere, o patch scimmia, variabili membro ad esse tramite l'attributo speciale __dict__. Il built-in vars()restituisce l'attributo speciale__dict__ .

EDIT: Nota, a differenza della try:except AttributeErrorrisposta alternativa , con questo approccio la variabile sarà sempre pronta per la logica del codice dopo l'inizializzazione. Penso che l' try:except AttributeErroralternativa alla seguente sarà meno ASCIUTTA e / o avrà un flusso imbarazzante:

def Fibonacci(n):
   if n<2: return n
   Fibonacci.memo=vars(Fibonacci).setdefault('memo',{}) # use static variable to hold a results cache
   return Fibonacci.memo.setdefault(n,Fibonacci(n-1)+Fibonacci(n-2)) # lookup result in cache, if not available then calculate and store it

EDIT2: raccomando l'approccio sopra solo quando la funzione verrà chiamata da più posizioni. Se invece la funzione viene chiamata solo in un posto, è meglio usare nonlocal:

def TheOnlyPlaceStaticFunctionIsCalled():
    memo={}
    def Fibonacci(n):
       nonlocal memo  # required in Python3. Python2 can see memo
       if n<2: return n
       return memo.setdefault(n,Fibonacci(n-1)+Fibonacci(n-2))
    ...
    print (Fibonacci(200))
    ...

2
l'unico problema è che non è affatto pulito, e ogni volta che vuoi usare questo modello devi tagliare e incollare il codice ... da qui il mio uso di un decoratore
Claudiu,

2
probabilmente dovrebbe usare qualcosa di similetry: mystaticfun.counter+=10 except AttributeError: mystaticfun.counter=0
endolith il

2
Si prega di utilizzare X not in Ypiuttosto che not X in Y(o consigliare di utilizzarlo se lo si stesse semplicemente utilizzando per un confronto più simile tra quello e hasattr)
Nick T

che ne dici di questo: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCuriousOne, il

non è l'ideale perché la clausola if aggiunge un annidamento non necessario, in questa situazione preferisco setdefault
Riaz Rizvi,

27

Python non ha variabili statiche ma puoi falsificarlo definendo un oggetto classe richiamabile e quindi utilizzandolo come funzione. Vedi anche questa risposta .

class Foo(object):
  # Class variable, shared by all instances of this class
  counter = 0

  def __call__(self):
    Foo.counter += 1
    print Foo.counter

# Create an object instance of class "Foo," called "foo"
foo = Foo()

# Make calls to the "__call__" method, via the object's name itself
foo() #prints 1
foo() #prints 2
foo() #prints 3

Si noti che __call__rende un'istanza di una classe (oggetto) richiamabile con il proprio nome. Ecco perché chiamare foo()sopra chiama il __call__metodo della classe . Dalla documentazione :

Le istanze di classi arbitrarie possono essere richiamate definendo un __call__()metodo nella loro classe.


15
Le funzioni sono già oggetti, quindi questo aggiunge solo un livello non necessario.
DasIch

Vedi questa risposta SO per una lunga opinione che questa è in realtà una buona idea. stackoverflow.com/questions/460586 . Concordo sul fatto che rendere tale classe un singleton, forse come questo stackoverflow.com/questions/6760685 , sarebbe anche una buona idea. Non so cosa significhi @ S.Lott con "... sposta il contatore nella definizione della classe ..." perché sembra che sia già in una posizione variabile per me.
Reb.Cabin

1
Sulla base della mia ricerca, questa tecnica di classe sembra essere il più "pitone" degli approcci presentati in questa pagina e utilizza il minimo inganno. Pertanto, ho in programma di adottarlo come mio sostituto per le variabili simil-C nelle funzioni, come nuovo sviluppatore Python.
Gabriel Staples,

1
Cosa succede se voglio foo1 = Foo () e foo2 = Foo ()?
Mark Lawrence,

@MarkLawrence Quindi hai due diverse istanze di una classe richiamabile, ognuna con il proprio contatore. Quale esattamente cosa dovresti aspettarti se non stai usando l'istanza foofornita come singleton.
Aaron McMillin,

14

Utilizzare una funzione del generatore per generare un iteratore.

def foo_gen():
    n = 0
    while True:
        n+=1
        yield n

Quindi usalo come

foo = foo_gen().next
for i in range(0,10):
    print foo()

Se vuoi un limite superiore:

def foo_gen(limit=100000):
    n = 0
    while n < limit:
       n+=1
       yield n

Se l'iteratore termina (come nell'esempio sopra), puoi anche passare direttamente su di esso, ad esempio

for i in foo_gen(20):
    print i

Naturalmente, in questi semplici casi è meglio usare xrange :)

Ecco la documentazione sulla dichiarazione di rendimento .


11

Altre soluzioni associano un attributo counter alla funzione, generalmente con logica contorta per gestire l'inizializzazione. Questo non è appropriato per il nuovo codice.

In Python 3, il modo giusto è usare nonlocalun'istruzione:

counter = 0
def foo():
    nonlocal counter
    counter += 1
    print(f'counter is {counter}')

Vedere PEP 3104 per le specifiche nonlocaldell'istruzione.

Se il contatore è destinato a essere privato per il modulo, dovrebbe _counterinvece essere nominato .


Anche prima di Python 3, puoi sempre farlo con global counterun'istruzione invece di nonlocal counter( nonlocalti consente di scrivere nello stato di chiusura in una funzione nidificata). Il motivo per cui le persone stanno associando un attributo alla funzione è quello di evitare di inquinare lo spazio dei nomi globale per uno stato specifico della funzione, quindi non è necessario fare cose ancora più hacker quando due funzioni hanno bisogno di counters indipendenti . Questa soluzione non si ridimensiona; gli attributi sulla funzione fanno. La risposta di kdb è come nonlocalpuò aiutare, ma aggiunge complessità.
ShadowRanger,

Eh, penso che la complessità di una funzione di fabbrica o di un decoratore sia eccessiva a meno che tu non lo stia facendo molto, e in quel caso il design è già un po 'puzzolente. Per una tantum, basta aggiungere il contatore non locale ed essere fatto con esso. Ho aggiunto un po 'alla risposta sulle convenzioni di denominazione. Inoltre, la ragione mi raccomando nonlocalsopra globalè esattamente come fai notare - funziona in stretto più circostanze.
cbarrick il

8

L'uso di un attributo di una funzione come variabile statica presenta alcuni potenziali svantaggi:

  • Ogni volta che si desidera accedere alla variabile, è necessario scrivere il nome completo della funzione.
  • Il codice esterno può accedere facilmente alla variabile e pasticciare con il valore.

Python idiomatico per il secondo problema probabilmente darebbe un nome alla variabile con un carattere di sottolineatura iniziale per segnalare che non si intende accedervi, mantenendolo accessibile dopo il fatto.

Un'alternativa sarebbe un modello che utilizza chiusure lessicali, che sono supportate con la nonlocalparola chiave in python 3.

def make_counter():
    i = 0
    def counter():
        nonlocal i
        i = i + 1
        return i
    return counter
counter = make_counter()

Purtroppo non so come incapsulare questa soluzione in un decoratore.


7
def staticvariables(**variables):
    def decorate(function):
        for variable in variables:
            setattr(function, variable, variables[variable])
        return function
    return decorate

@staticvariables(counter=0, bar=1)
def foo():
    print(foo.counter)
    print(foo.bar)

Proprio come il codice di vincent sopra, questo sarebbe usato come decoratore di funzioni e si dovrebbe accedere alle variabili statiche con il nome della funzione come prefisso. Il vantaggio di questo codice (anche se è vero che chiunque potrebbe essere abbastanza intelligente da capirlo) è che puoi avere più variabili statiche e inizializzarle in un modo più convenzionale.


7

Un po 'più leggibile, ma più dettagliato (Zen of Python: esplicito è meglio che implicito):

>>> def func(_static={'counter': 0}):
...     _static['counter'] += 1
...     print _static['counter']
...
>>> func()
1
>>> func()
2
>>>

Vedi qui per una spiegazione di come funziona.


puoi approfondire il motivo per cui questo codice funziona? Il secondo foo()dovrebbe reinizializzare il dizionario sul valore specificato nella definizione della funzione (quindi con la chiave del contatore con un valore pari a 0). Perché no?
Raffaem,

3
@raffamaiden: gli argomenti predefiniti vengono valutati una sola volta quando viene definita la funzione e non ogni volta che viene chiamata la funzione.
Daniel K.

6
_counter = 0
def foo ():
   _counter globale
   _counter + = 1
   stampa 'counter is', _counter

Python usa abitualmente caratteri di sottolineatura per indicare variabili private. L'unico motivo in C per dichiarare la variabile statica all'interno della funzione è nasconderla al di fuori della funzione, che in realtà non è Python idiomatica.


4

Dopo aver provato diversi approcci, finisco per usare una versione migliorata della risposta di @ warvariuc:

import types

def func(_static=types.SimpleNamespace(counter=0)):
    _static.counter += 1
    print(_static.counter)

3

Il modo idiomatico è usare una classe , che può avere attributi. Se è necessario che le istanze non siano separate, utilizzare un singleton.

Esistono diversi modi in cui è possibile falsificare o confondere variabili "statiche" in Python (una non menzionata finora è avere un argomento predefinito mutabile), ma questo non è il modo idiomatico di Pythonic per farlo. Usa solo una lezione.

O forse un generatore, se il tuo modello di utilizzo si adatta.


Per le funzioni ricorsive autonome, l' defaultargomento è il più elegante.
lifebalance

3

Spinto da questa domanda , posso presentare un'altra alternativa che potrebbe essere un po 'più piacevole da usare e che sembrerà la stessa sia per i metodi che per le funzioni:

@static_var2('seed',0)
def funccounter(statics, add=1):
    statics.seed += add
    return statics.seed

print funccounter()       #1
print funccounter(add=2)  #3
print funccounter()       #4

class ACircle(object):
    @static_var2('seed',0)
    def counter(statics, self, add=1):
        statics.seed += add
        return statics.seed

c = ACircle()
print c.counter()      #1
print c.counter(add=2) #3
print c.counter()      #4
d = ACircle()
print d.counter()      #5
print d.counter(add=2) #7
print d.counter()      #8    

Se ti piace l'utilizzo, ecco l'implementazione:

class StaticMan(object):
    def __init__(self):
        self.__dict__['_d'] = {}

    def __getattr__(self, name):
        return self.__dict__['_d'][name]
    def __getitem__(self, name):
        return self.__dict__['_d'][name]
    def __setattr__(self, name, val):
        self.__dict__['_d'][name] = val
    def __setitem__(self, name, val):
        self.__dict__['_d'][name] = val

def static_var2(name, val):
    def decorator(original):
        if not hasattr(original, ':staticman'):    
            def wrapped(*args, **kwargs):
                return original(getattr(wrapped, ':staticman'), *args, **kwargs)
            setattr(wrapped, ':staticman', StaticMan())
            f = wrapped
        else:
            f = original #already wrapped

        getattr(f, ':staticman')[name] = val
        return f
    return decorator

3

Un'altra svolta (non consigliata!) Sull'oggetto richiamabile come https://stackoverflow.com/a/279598/916373 , se non ti dispiace usare una firma di chiamata funky, sarebbe fare

class foo(object):
    counter = 0;
    @staticmethod
    def __call__():
        foo.counter += 1
        print "counter is %i" % foo.counter

>>> foo()()
counter is 1
>>> foo()()
counter is 2

3

Invece di creare una funzione con una variabile locale statica, è sempre possibile creare quello che viene chiamato un "oggetto funzione" e assegnargli una variabile membro standard (non statica).

Dato che hai dato un esempio scritto in C ++, spiegherò innanzitutto che cos'è un "oggetto funzione" in C ++. Un "oggetto funzione" è semplicemente qualsiasi classe con un sovraccarico operator(). Le istanze della classe si comporteranno come funzioni. Ad esempio, puoi scrivere int x = square(5);anche se squareè un oggetto (con sovraccarico operator()) e non tecnicamente non è una "funzione". È possibile assegnare a un oggetto funzione una delle caratteristiche che è possibile assegnare a un oggetto classe.

# C++ function object
class Foo_class {
    private:
        int counter;     
    public:
        Foo_class() {
             counter = 0;
        }
        void operator() () {  
            counter++;
            printf("counter is %d\n", counter);
        }     
   };
   Foo_class foo;

In Python, possiamo anche sovraccaricare, operator()tranne per il fatto che il metodo è invece chiamato __call__:

Ecco una definizione di classe:

class Foo_class:
    def __init__(self): # __init__ is similair to a C++ class constructor
        self.counter = 0
        # self.counter is like a static member
        # variable of a function named "foo"
    def __call__(self): # overload operator()
        self.counter += 1
        print("counter is %d" % self.counter);
foo = Foo_class() # call the constructor

Ecco un esempio della classe utilizzata:

from foo import foo

for i in range(0, 5):
    foo() # function call

L'output stampato sulla console è:

counter is 1
counter is 2
counter is 3
counter is 4
counter is 5

Se vuoi che la tua funzione accetti argomenti di input, puoi aggiungerli __call__anche a:

# FILE: foo.py - - - - - - - - - - - - - - - - - - - - - - - - -

class Foo_class:
    def __init__(self):
        self.counter = 0
    def __call__(self, x, y, z): # overload operator()
        self.counter += 1
        print("counter is %d" % self.counter);
        print("x, y, z, are %d, %d, %d" % (x, y, z));
foo = Foo_class() # call the constructor

# FILE: main.py - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

from foo import foo

for i in range(0, 5):
    foo(7, 8, 9) # function call

# Console Output - - - - - - - - - - - - - - - - - - - - - - - - - - 

counter is 1
x, y, z, are 7, 8, 9
counter is 2
x, y, z, are 7, 8, 9
counter is 3
x, y, z, are 7, 8, 9
counter is 4
x, y, z, are 7, 8, 9
counter is 5
x, y, z, are 7, 8, 9

3

Soulution n + = 1

def foo():
  foo.__dict__.setdefault('count', 0)
  foo.count += 1
  return foo.count

3

Una dichiarazione globale fornisce questa funzionalità. Nell'esempio seguente (python 3.5 o versioni successive per utilizzare la "f"), la variabile contatore è definita al di fuori della funzione. Definirlo come globale nella funzione significa che la versione "globale" al di fuori della funzione dovrebbe essere resa disponibile per la funzione. Quindi ogni volta che la funzione viene eseguita, modifica il valore all'esterno della funzione, preservandolo oltre la funzione.

counter = 0

def foo():
    global counter
    counter += 1
    print("counter is {}".format(counter))

foo() #output: "counter is 1"
foo() #output: "counter is 2"
foo() #output: "counter is 3"

Funziona allo stesso modo se usato correttamente. La differenza con il codice c è che nell'esempio c dell'OP, la variabile contatore poteva essere toccata solo dalla funzione. Una variabile globale in Python può essere utilizzata o modificata in qualsiasi punto dello script
MortenSickel,

2

Una variabile statica all'interno di un metodo Python

class Count:
    def foo(self):
        try: 
            self.foo.__func__.counter += 1
        except AttributeError: 
            self.foo.__func__.counter = 1

        print self.foo.__func__.counter

m = Count()
m.foo()       # 1
m.foo()       # 2
m.foo()       # 3

1

Personalmente preferisco quanto segue ai decoratori. A ognuno il suo.

def staticize(name, factory):
    """Makes a pseudo-static variable in calling function.

    If name `name` exists in calling function, return it. 
    Otherwise, saves return value of `factory()` in 
    name `name` of calling function and return it.

    :param name: name to use to store static object 
    in calling function
    :type name: String
    :param factory: used to initialize name `name` 
    in calling function
    :type factory: function
    :rtype: `type(factory())`

    >>> def steveholt(z):
    ...     a = staticize('a', list)
    ...     a.append(z)
    >>> steveholt.a
    Traceback (most recent call last):
    ...
    AttributeError: 'function' object has no attribute 'a'
    >>> steveholt(1)
    >>> steveholt.a
    [1]
    >>> steveholt('a')
    >>> steveholt.a
    [1, 'a']
    >>> steveholt.a = []
    >>> steveholt.a
    []
    >>> steveholt('zzz')
    >>> steveholt.a
    ['zzz']

    """
    from inspect import stack
    # get scope enclosing calling function
    calling_fn_scope = stack()[2][0]
    # get calling function
    calling_fn_name = stack()[1][3]
    calling_fn = calling_fn_scope.f_locals[calling_fn_name]
    if not hasattr(calling_fn, name):
        setattr(calling_fn, name, factory())
    return getattr(calling_fn, name)

3
Per favore, non essere offeso, ma questa soluzione mi ricorda un po 'il "grande stile aziendale" :-) willa.me/2013/11/the-six-most-common-species-of-code.html
JJC

Sì, l'utilizzo non portatile (la manipolazione dello stack in generale è un dettaglio dell'implementazione di CPython, non qualcosa su cui puoi fare affidamento in PyPy, Jython, IronPython, what-have-you), fragile manipolazione dello stack, con mezza dozzina di chiamate di funzione ad ogni utilizzo è molto meglio di un semplice decoratore ... </s>
ShadowRanger il

1

Questa risposta si basa sulla risposta di @claudiu.

Ho scoperto che il mio codice stava diventando meno chiaro quando dovevo sempre anteporre il nome della funzione, ogni volta che ho intenzione di accedere a una variabile statica.

Vale a dire, nel mio codice funzione preferirei scrivere:

print(statics.foo)

invece di

print(my_function_name.foo)

Quindi, la mia soluzione è:

  1. aggiungi un staticsattributo alla funzione
  2. nell'ambito della funzione, aggiungere una variabile locale staticscome alias amy_function.statics
from bunch import *

def static_vars(**kwargs):
    def decorate(func):
        statics = Bunch(**kwargs)
        setattr(func, "statics", statics)
        return func
    return decorate

@static_vars(name = "Martin")
def my_function():
    statics = my_function.statics
    print("Hello, {0}".format(statics.name))

osservazione

Il mio metodo utilizza una classe denominata Bunch, che è un dizionario che supporta l'accesso in stile attributo, come JavaScript (vedere l' articolo originale al riguardo, intorno al 2000)

Può essere installato tramite pip install bunch

Può anche essere scritto a mano in questo modo:

class Bunch(dict):
    def __init__(self, **kw):
        dict.__init__(self,kw)
        self.__dict__ = self

Nota: types.SimpleNamespace(disponibile dalla 3.3) supporta questo comportamento immediatamente (ed è implementato in C su CPython, quindi è il più veloce possibile).
ShadowRanger il

0

Sulla base della risposta di Daniel (aggiunte):

class Foo(object): 
    counter = 0  

def __call__(self, inc_value=0):
    Foo.counter += inc_value
    return Foo.counter

foo = Foo()

def use_foo(x,y):
    if(x==5):
        foo(2)
    elif(y==7):
        foo(3)
    if(foo() == 10):
        print("yello")


use_foo(5,1)
use_foo(5,1)
use_foo(1,7)
use_foo(1,7)
use_foo(1,1)

Il motivo per cui volevo aggiungere questa parte è che le variabili statiche vengono utilizzate non solo per incrementare di un valore, ma anche per verificare se la var statica è uguale a un valore, come un esempio di vita reale.

La variabile statica è ancora protetta e utilizzata solo nell'ambito della funzione use_foo ()

In questo esempio, call to foo () funziona esattamente come (rispetto al corrispondente equivalente c ++):

stat_c +=9; // in c++
foo(9)  #python equiv

if(stat_c==10){ //do something}  // c++

if(foo() == 10):      # python equiv
  #add code here      # python equiv       

Output :
yello
yello

se la classe Foo è definita restrittivamente come una classe singleton, sarebbe l'ideale. Ciò renderebbe più pitonico.


-1

Certo, questa è una vecchia domanda, ma penso che potrei fornire qualche aggiornamento.

Sembra che l'argomento delle prestazioni sia obsoleto. La stessa suite di test sembra fornire risultati simili per siInt_try e isInt_re2. Naturalmente i risultati variano, ma questa è una sessione sul mio computer con Python 3.4.4 sul kernel 4.3.01 con Xeon W3550. L'ho eseguito più volte e i risultati sembrano essere simili. Ho spostato il regex globale in funzione statica, ma la differenza di prestazioni è trascurabile.

isInt_try: 0.3690
isInt_str: 0.3981
isInt_re: 0.5870
isInt_re2: 0.3632

Con il problema delle prestazioni fuori mano, sembra che try / catch produca il codice più a prova di futuro e maiuscolo, quindi forse basta avvolgerlo in funzione


1
Cosa stai anche confrontando qui? Questo sembra un commento su altre risposte, ma non è chiaro quali, e non risponde alla domanda stessa.
ShadowRanger il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.