Question Quel est l'équivalent Python des variables statiques dans une fonction?


Quel est l'équivalent Python idiomatique de ce code C / C ++?

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

spécifiquement, comment peut-on implémenter le membre statique au niveau de la fonction, par opposition au niveau de la classe? Et placer la fonction dans une classe change-t-il quelque chose?


471
2017-11-10 23:33


origine


Réponses:


Un peu inversé, mais cela devrait fonctionner:

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

Si vous souhaitez que le code d’initialisation du compteur se trouve en haut et non en bas, vous pouvez créer un décorateur:

def static_var(varname, value):
    def decorate(func):
        setattr(func, varname, value)
        return func
    return decorate

Ensuite, utilisez le code comme ceci:

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

Cela va encore vous obliger à utiliser le foo. préfixe, malheureusement.


EDIT (merci à ony): Cela semble encore plus agréable:

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

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

540
2017-11-10 23:46



Vous pouvez ajouter des attributs à une fonction et l'utiliser comme variable statique.

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

# attribute must be initialized
myfunc.counter = 0

Sinon, si vous ne voulez pas configurer la variable en dehors de la fonction, vous pouvez utiliser hasattr() pour éviter une AttributeError exception:

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

De toute façon les variables statiques sont plutôt rares, et vous devriez trouver une meilleure place pour cette variable, très probablement dans une classe.


180
2017-11-10 23:53



On pourrait aussi considérer:

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

Raisonnement:

  • beaucoup pythonique (ask for forgiveness not permission)
  • utiliser l'exception (lancée une seule fois) au lieu de if branche (pense StopIteration exception)

153
2018-04-25 12:16



D'autres réponses ont montré comment vous devriez faire cela. Voici une façon de ne pas:

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

Les valeurs par défaut ne sont initialisées que lorsque la fonction est évaluée pour la première fois et non à chaque exécution. Vous pouvez donc utiliser une liste ou tout autre objet modifiable pour stocker des valeurs statiques.


36
2017-11-10 23:47



Beaucoup de gens ont déjà suggéré de tester 'hasattr', mais il y a une réponse plus simple:

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

Pas d'essai / sauf, pas de test hasattr, juste getattr avec un défaut.


28
2018-01-05 16:24



Voici une version entièrement encapsulée qui ne nécessite pas d'appel d'initialisation externe:

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

En Python, les fonctions sont des objets et on peut simplement leur ajouter, ou leur appliquer un patch, des variables membres via l'attribut spécial __dict__. Le built-in vars() renvoie l'attribut spécial __dict__.

EDIT: Remarquez, contrairement à l'alternative try:except AttributeError répondre, avec cette approche, la variable sera toujours prête pour la logique du code après l'initialisation. Je pense que le try:except AttributeError alternative à ce qui suit sera moins sec et / ou avoir un flux gênant:

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: Je recommande seulement l'approche ci-dessus lorsque la fonction sera appelée à partir de plusieurs endroits. Si, au lieu de cela, la fonction est uniquement appelée à un endroit, il est préférable d'utiliser 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))
    ...

20
2017-09-04 19:51



Python n'a pas de variables statiques mais vous pouvez le faire en définissant un objet de classe appelable, puis en l'utilisant comme une fonction. Voir aussi cette réponse.

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

Notez que __call__ rend une instance d'une classe (objet) appelable par son propre nom. C'est pourquoi appeler foo() ci-dessus appelle la classe ' __call__ méthode. À partir de la documentation:

Des instances de classes arbitraires peuvent être rendues appelables en définissant un __call__() méthode dans leur classe.


19
2017-11-10 23:53



Utilisez une fonction de générateur pour générer un itérateur.

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

Ensuite, utilisez-le comme

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

Si vous voulez une limite supérieure:

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

Si l'itérateur se termine (comme dans l'exemple ci-dessus), vous pouvez également le boucler directement, comme

for i in foo_gen(20):
    print i

Bien sûr, dans ces cas simples, il vaut mieux utiliser xrange :)

Voici la documentation sur le déclaration de rendement.


10
2017-11-10 23:37