Quel est l'équivalent Python des variables statiques à l'intérieur d'une fonction?

631

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

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

en particulier, comment implémenter le membre statique au niveau de la fonction, par opposition au niveau de la classe? Et le fait de placer la fonction dans une classe change-t-il quelque chose?

andrewdotnich
la source
22
Il n'y a AUCUNE équivalence, je le crains. Même si vous faites le hack du décorateur avec des attributs de fonction, vous pourrez accéder à la variable à l'extérieur, ce qui, malheureusement, défait le point. De plus, vous devrez coder en dur le nom de la fonction dans la fonction, ce qui est très ennuyeux. Je suggère d'utiliser à la place une classe ou un module de variables globales avec le _préfixe conventionnel .
lpapp
8
Pour les non-programmeurs C, [ stackoverflow.com/questions/5033627/… la variable statique à l'intérieur d'une fonction n'est visible qu'à l'intérieur de la portée de cette fonction, mais sa durée de vie est la durée de vie entière du programme, et elle n'est initialisée qu'une seule fois). Fondamentalement, un compteur ou une variable de stockage persistant qui vit entre les appels de fonction.
smci
2
@lpapp: il y a en quelque sorte, c'est un membre de la classe . Vous avez raison, nous ne pouvons empêcher un autre code de le visualiser ou de le modifier.
smci

Réponses:

681

Un peu inversé, mais cela devrait fonctionner:

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

Si vous voulez que le code d'initialisation du compteur soit en haut plutôt qu'en bas, vous pouvez créer un décorateur:

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

Utilisez ensuite le code comme ceci:

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

foo.Malheureusement, il vous faudra encore utiliser le préfixe.

(Crédit: @ony )

Claudiu
la source
23
il n'y a qu'une seule instance de foo - cette seule fonction. toutes les invocations accèdent à la même variable.
Claudiu
121
Désolé d'avoir déterré cela, mais je préfère mettre if "counter" not in foo.__dict__: foo.counter = 0les premières lignes de foo(). Cela aiderait à éviter le code en dehors de la fonction. Je ne sais pas si cela était possible en 2008. PS J'ai trouvé cette réponse en cherchant la possibilité de créer des variables de fonction statiques, donc ce fil est toujours "vivant" :)
binaryLV
8
@binaryLV: Je préférerais probablement cela à la première approche. Le problème avec la première approche est qu'elle n'est pas immédiatement évidente fooet qu'elle foo.counter = est intimement liée. cependant, je préfère finalement l'approche du décorateur, car il n'y a aucun moyen de ne pas appeler le décorateur et il est sémantiquement plus évident de ce qu'il fait ( @static_var("counter", 0)est plus facile et plus logique à mes yeux que if "counter" not in foo.__dict__: foo.counter = 0, surtout que dans ce dernier que vous devez utiliser le nom de la fonction (deux fois) qui pourrait changer).
Claudiu
6
@lpapp: Cela dépend de quel est le point des variables statiques. J'ai toujours pensé que ce serait la même valeur sur plusieurs appels de fonction, ce que cela satisfait. Je n'ai jamais pensé qu'il s'agissait de masquage de variables, ce qui n'est pas le cas, comme vous l'avez dit.
Claudiu
3
def foo(): if not hasattr(foo,"counter"): foo.counter=0 foo.counter += 1
Erik Aronesty
222

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

Alternativement, si vous ne souhaitez pas configurer la variable en dehors de la fonction, vous pouvez utiliser hasattr()pour éviter une AttributeErrorexception:

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

Quoi qu'il en soit, les variables statiques sont plutôt rares, et vous devriez trouver un meilleur endroit pour cette variable, très probablement à l'intérieur d'une classe.

vincent
la source
6
Pourquoi ne pas essayer à la place de l'instruction if?
ravwojdyla
12
try: myfunc.counter += 1; except AttributeError: myfunc.counter = 1devrait faire de même, en utilisant des exceptions à la place.
sleblanc
Les exceptions doivent être utilisées pour les situations exceptionnelles, c'est-à-dire celles que le programmeur attend ne se produiront pas, comme un fichier d'entrée qu'il a ouvert avec succès soudainement qui n'est pas disponible. Il s'agit d'une situation attendue, une instruction if a plus de sens.
Hack Saw
11
@Hack_Saw: Eh bien, c'est Pythonic (mieux vaut demander pardon que permission). Ceci est en fait recommandé dans les techniques d'optimisation Python car il permet d'économiser le coût d'un if (bien que je ne recommande pas une optimisation prématurée). Votre règle concernant les cas exceptionnels: 1. L'échec EST un cas exceptionnel ici, dans un sens. Cela n'arrive qu'une seule fois. 2. Je pense que cette règle concerne l'utilisation (c'est-à-dire l'augmentation) des exceptions. Il s'agit d'une exception pour quelque chose que vous prévoyez de travailler, mais pour lequel vous avez un plan de sauvegarde, ce qui est courant dans la plupart des langues.
leewz
@leewangzhong: Est-ce que la mise en place d'un bloc qui ne déclenche pas d'exception tryentraîne des coûts? Juste curieux.
trss
202

On pourrait également envisager:

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

Raisonnement:

  • beaucoup pythonique ("demander pardon et non permission")
  • utiliser l'exception (levée une seule fois) au lieu de la ifbranche (pensez à l' exception StopIteration )
ravwojdyla
la source
11
Je ne fais pas de Python depuis longtemps, mais cela satisfait l'un des principes implicites du langage: si ce n'est pas (assez) facile, vous le faites mal .
ZX9
N'a pas fonctionné immédiatement avec les méthodes de classe, "self.foo.counter = 1" déclenche à nouveau AttributeError.
villasv
16
C'est la bonne solution et ce devrait être la réponse acceptée car le code d'initialisation sera exécuté lorsque la fonction sera appelée et non lorsque le module sera exécuté ou lorsque quelque chose en sera importé, ce qui est le cas si vous utilisez l'approche décoratrice de la réponse actuellement acceptée. Voir Exécution de la fonction de décorateur Python . Si vous avez un énorme module de bibliothèque, tous les décorateurs seront exécutés, y compris ceux des fonctions que vous n'importez pas.
Nils Lindemann
3
Une approche plus simple: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCuriousOne
5
@MANU Utiliser hasattr()pour cela n'est pas plus simple et aussi moins efficace.
moooeeeep
48

D'autres réponses ont démontré la façon dont vous devez procéder. Voici un moyen que vous ne devriez 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 lors de la première évaluation de la fonction, et non à chaque exécution, vous pouvez donc utiliser une liste ou tout autre objet modifiable pour stocker des valeurs statiques.

Jeremy Banks
la source
J'ai essayé cela, mais pour une raison quelconque, le paramètre de fonction s'initialisait à 140, pas à 0. Pourquoi en serait-il ainsi?
andrewdotnich
1
@bouvard Pour les fonctions récursives qui ont besoin d'une variable statique, c'est la seule qui se lit vraiment bien.
lifebalance
1
J'ai essayé plusieurs approches et je souhaite que celle-ci soit acceptée comme pythonique. Avec des noms pleins de sens comme def foo(arg1, arg2, _localstorage=DataClass(counter=0))je le trouve bien lisible. Un autre bon point est le changement de nom facile des fonctions.
VPfB
2
Pourquoi dites-vous que vous ne devriez pas le faire de cette façon? Ça me semble parfaitement raisonnable!
Konstantin
1
@VPfB: Pour le stockage général, vous pouvez l'utiliser types.SimpleNamespace, ce qui en fait def foo(arg1, arg2, _staticstorage=types.SimpleNamespace(counter=0)):sans avoir besoin de définir une classe spéciale.
ShadowRanger
43

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

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

Aucun essai / sauf, aucun test hasattr, juste getattr avec un défaut.

Jonathan
la source
2
faites attention au troisième paramètre de getattr lorsque vous y mettez un func par exemple: def func (): def foo (): return 1112 func.counter = getattr (func, 'counter', foo ()) + 1 lorsque vous appelez func, le foo sera toujours appelé!
Code pour
1
Un simple appel à getattr chaque fois que la fonction est appelée. C'est très bien si les performances ne sont pas un problème, si elles sont essayées / sauf gagneront haut la main.
Mark Lawrence
2
@MarkLawrence: En fait, au moins sur mon installation de Windows x64 3.8.0, la différence de performances entre cette réponse et l' approche équivalente try/ exceptbasée de ravwojdyla n'a pas de sens. Une simple ipython %%timeitmicro-référence a donné le coût du try/ exceptà 255 ns par appel, contre 263 ns pour la getattrsolution basée. Oui, le try/ exceptest plus rapide, mais ce n'est pas exactement "gagner haut la main"; c'est une minuscule micro-optimisation. Écrivez le code qui semble plus clair, ne vous inquiétez pas des différences de performances triviales comme celle-ci.
ShadowRanger
@ShadowRanger merci pour cela. Je m'interroge sur la déclaration de MarkLawrence depuis 2 ans, et je suis très heureux que vous ayez fait la recherche. Je suis définitivement d'accord avec votre dernière phrase - "écrivez le code qui semble plus clair" - c'est exactement pourquoi j'ai écrit cette réponse.
Jonathan
28

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 nous pouvons simplement leur ajouter, ou patcher des singes, des variables membres via l'attribut spécial __dict__. La fonction intégrée vars()renvoie l'attribut spécial __dict__.

EDIT: Notez, contrairement à la try:except AttributeErrorréponse alternative , avec cette approche, la variable sera toujours prête pour la logique du code après l'initialisation. Je pense que l' try:except AttributeErroralternative aux suivantes sera moins sèche et / ou aura un flux maladroit:

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 ne recommande l'approche ci-dessus que lorsque la fonction sera appelée à partir de plusieurs emplacements. Si à la place la fonction n'est appelée qu'à un seul endroit, il vaut mieux 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))
    ...
Riaz Rizvi
la source
2
le seul problème avec ceci est que ce n'est vraiment pas du tout soigné, et chaque fois que vous voulez utiliser ce modèle, vous devez couper et coller le code ... d'où mon utilisation d'un décorateur
Claudiu
2
devrait probablement utiliser quelque chose commetry: mystaticfun.counter+=10 except AttributeError: mystaticfun.counter=0
endolith
2
S'il vous plaît utiliser X not in Yplutôt que not X in Y(ou conseiller d'utiliser si vous l'utilisez juste pour une comparaison plus similaire entre cela et hasattr)
Nick T
que diriez-vous de ceci: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCuriousOne
ce n'est pas idéal parce que la clause if ajoute une imbrication inutile, dans cette situation, je préfère setdefault
Riaz Rizvi
27

Python n'a pas de variables statiques mais vous pouvez le simuler en définissant un objet de classe appelable puis en l'utilisant comme fonction. Voir également 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 __call__méthode de la classe . De la documentation :

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

Daniels
la source
15
Les fonctions sont déjà des objets, ce qui ajoute simplement un calque inutile.
DasIch
Voir cette réponse SO pour une longue opinion qu'il s'agit en fait d'une bonne idée. stackoverflow.com/questions/460586 . Je suis d'accord que faire de cette classe un singleton, peut-être comme ce stackoverflow.com/questions/6760685 , serait également une bonne idée. Je ne sais pas ce que @ S.Lott veut dire par "... déplacer le compteur dans la définition de classe ..." car il me semble qu'il est déjà en position de classe variable pour moi.
Cabine Reb
1
D'après mes recherches, cette technique de classe semble être la plus "pythonique" des approches présentées sur cette page, et utilise le moins de ruse. Je prévois donc de l'adopter comme remplacement de référence pour les variables de type C statique dans les fonctions, en tant que nouveau développeur Python moi-même.
Gabriel Staples
1
Que se passe-t-il si je veux foo1 = Foo () et foo2 = Foo ()?
Mark Lawrence
@MarkLawrence Ensuite, vous avez deux instances différentes d'une classe appelable, chacune avec son propre compteur. Ce à quoi vous devez vous attendre si vous n'utilisez pas l'instance foofournie en tant que singleton.
Aaron McMillin
14

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 souhaitez 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 la déclaration de rendement .

gnud
la source
11

D'autres solutions attachent un attribut de compteur à la fonction, généralement avec une logique alambiquée pour gérer l'initialisation. Ceci est inapproprié pour le nouveau code.

En Python 3, la bonne façon est d'utiliser une nonlocalinstruction:

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

Voir PEP 3104 pour la spécification de l' nonlocalinstruction.

Si le compteur est destiné à être privé du module, il doit être nommé à la _counterplace.

cbarrick
la source
Même avant Python 3, vous pouvez toujours le faire avec une global counterinstruction au lieu de nonlocal counter( nonlocalvous permet simplement d'écrire dans un état de fermeture dans une fonction imbriquée). La raison pour laquelle les gens attachent un attribut à la fonction est d'éviter de polluer l'espace de noms global pour un état spécifique à la fonction, de sorte que vous n'avez pas à faire des choses encore plus piratées lorsque deux fonctions ont besoin de counters indépendants . Cette solution n'est pas évolutive; les attributs de la fonction font. La réponse de kdb est de savoir comment nonlocalpeut aider, mais cela ajoute de la complexité.
ShadowRanger
Eh, je pense que la complexité d'une fonction d'usine ou d'un décorateur est exagérée à moins que vous ne fassiez beaucoup cela, et dans ce cas, le design est déjà un peu malodorant. Pour une seule fois, ajoutez simplement le compteur non local et terminez avec. J'ai ajouté un peu à la réponse sur les conventions de dénomination. En outre, la raison pour laquelle je vous recommande nonlocalplus globalest exactement comme vous le dites - cela fonctionne dans des circonstances strictement plus.
cbarrick
8

L'utilisation d'un attribut d'une fonction en tant que variable statique présente certains inconvénients potentiels:

  • Chaque fois que vous souhaitez accéder à la variable, vous devez écrire le nom complet de la fonction.
  • Le code extérieur peut accéder facilement à la variable et jouer avec la valeur.

Un python idiomatique pour le deuxième problème serait probablement de nommer la variable avec un trait de soulignement de tête pour signaler qu'elle n'est pas destinée à être accédée, tout en la gardant accessible après coup.

Une alternative serait un modèle utilisant des fermetures lexicales, qui sont prises en charge avec le nonlocalmot - clé en python 3.

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

Malheureusement, je ne connais aucun moyen d'encapsuler cette solution dans un décorateur.

kdb
la source
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)

Tout comme le code de vincent ci-dessus, il serait utilisé comme décorateur de fonction et les variables statiques doivent être accédées avec le nom de la fonction comme préfixe. L'avantage de ce code (bien que tout le monde puisse être assez intelligent pour le comprendre) est que vous pouvez avoir plusieurs variables statiques et les initialiser de manière plus conventionnelle.

Giorgian Borca-Tasciuc
la source
7

Un peu plus lisible, mais plus verbeux (Zen of Python: explicite vaut mieux qu'implicite):

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

Voir ici pour une explication de comment cela fonctionne.

warvariuc
la source
pouvez-vous expliquer pourquoi ce code fonctionne? Le second foo()doit réinitialiser le dictionnaire à la valeur spécifiée dans la définition de la fonction (donc avec la clé de compteur ayant une valeur de 0). Pourquoi pas?
raffaem
3
@raffamaiden: Les arguments par défaut ne sont évalués qu'une seule fois lorsque la fonction est définie et non à chaque appel de la fonction.
Daniel K.
6
_counter = 0
def foo ():
   _counter global
   _counter + = 1
   imprimer «le compteur est», _counter

Python utilise habituellement des traits de soulignement pour indiquer des variables privées. La seule raison en C pour déclarer la variable statique à l'intérieur de la fonction est de la cacher en dehors de la fonction, ce qui n'est pas vraiment du Python idiomatique.

Dave
la source
4

Après avoir essayé plusieurs approches, je finis par utiliser une version améliorée de la réponse de @ warvariuc:

import types

def func(_static=types.SimpleNamespace(counter=0)):
    _static.counter += 1
    print(_static.counter)
VPfB
la source
3

La manière idiomatique est d'utiliser une classe , qui peut avoir des attributs. Si vous avez besoin que les instances ne soient pas séparées, utilisez un singleton.

Il existe un certain nombre de façons de simuler ou de fusionner des variables "statiques" dans Python (une qui n'est pas mentionnée jusqu'à présent est d'avoir un argument par défaut modifiable), mais ce n'est pas la manière idiomatique Pythonique de le faire. Utilisez simplement une classe.

Ou éventuellement un générateur, si votre modèle d'utilisation convient.

Teddy
la source
Pour les fonctions récursives autonomes, l' defaultargument est le plus élégant.
lifebalance
3

Invité par cette question , puis-je présenter une autre alternative qui pourrait être un peu plus agréable à utiliser et qui se ressemblera pour les méthodes et les fonctions:

@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    

Si vous aimez l'utilisation, voici l'implémentation:

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
Claudiu
la source
3

Une autre torsion (non recommandée!) Sur l'objet appelable comme https://stackoverflow.com/a/279598/916373 , si cela ne vous dérange pas d'utiliser une signature d'appel géniale, serait de faire

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
perdu
la source
3

Au lieu de créer une fonction ayant une variable locale statique, vous pouvez toujours créer ce qu'on appelle un "objet fonction" et lui donner une variable membre standard (non statique).

Puisque vous avez donné un exemple écrit en C ++, je vais d'abord expliquer ce qu'est un "objet fonction" en C ++. Un "objet fonction" est simplement n'importe quelle classe avec une surcharge operator(). Les instances de la classe se comporteront comme des fonctions. Par exemple, vous pouvez écrire int x = square(5);même s'il squares'agit d'un objet (surchargé operator()) et non techniquement pas une "fonction". Vous pouvez donner à un objet-fonction n'importe laquelle des fonctionnalités que vous pourriez donner à un objet de 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;

En Python, nous pouvons également surcharger operator()sauf que la méthode est nommée à la place __call__:

Voici une définition de 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

Voici un exemple de la classe utilisée:

from foo import foo

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

La sortie imprimée sur la console est:

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

Si vous souhaitez que votre fonction accepte des arguments d'entrée, vous pouvez également les ajouter à __call__:

# 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
IdleCustard
la source
3

Soulution n + = 1

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

Une déclaration globale fournit cette fonctionnalité. Dans l'exemple ci-dessous (python 3.5 ou supérieur pour utiliser le "f"), la variable compteur est définie en dehors de la fonction. Le définir comme global dans la fonction signifie que la version "globale" en dehors de la fonction doit être mise à la disposition de la fonction. Ainsi, chaque fois que la fonction s'exécute, elle modifie la valeur en dehors de la fonction, la préservant au-delà de la fonction.

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"
Richard Merren
la source
Cela fonctionne de la même manière s'il est utilisé correctement. La différence avec le code c est que dans l'exemple c de l'OP, la variable compteur ne peut être touchée que par la fonction. Une variable globale en python peut être utilisée ou modifiée n'importe où dans le script
MortenSickel
2

Une variable statique à l'intérieur d'une méthode 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
wannik
la source
1

Personnellement, je préfère ce qui suit aux décorateurs. À chacun son bonheur.

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)
dépenses
la source
3
Veuillez ne pas être offensé, mais cette solution me rappelle un peu le "style de grande entreprise" :-) willa.me/2013/11/the-six-most-common-species-of-code.html
JJC
Oui, en utilisant non portable (la manipulation de pile en général est un détail d'implémentation CPython, pas quelque chose sur lequel vous pouvez compter dans PyPy, Jython, IronPython, what-have-you), une manipulation de pile fragile, avec une demi-douzaine d'appels de fonction à chaque utilisation est bien meilleur qu'un simple décorateur ... </s>
ShadowRanger
1

Cette réponse s'appuie sur la réponse de @claudiu.

J'ai trouvé que mon code devenait moins clair quand je devais toujours ajouter le nom de la fonction, chaque fois que j'avais l'intention d'accéder à une variable statique.

À savoir, dans mon code de fonction, je préférerais écrire:

print(statics.foo)

au lieu de

print(my_function_name.foo)

Donc, ma solution est de:

  1. ajouter un staticsattribut à la fonction
  2. dans l'étendue de la fonction, ajoutez une variable locale staticscomme alias àmy_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))

Remarque

Ma méthode utilise une classe nommée Bunch, qui est un dictionnaire qui prend en charge l'accès de style attribut, à la JavaScript (voir l' article d'origine à ce sujet, vers 2000)

Il peut être installé via pip install bunch

Il peut également être écrit à la main comme suit:

class Bunch(dict):
    def __init__(self, **kw):
        dict.__init__(self,kw)
        self.__dict__ = self
Pascal T.
la source
Remarque: types.SimpleNamespace(disponible depuis 3.3) prend en charge ce comportement hors de la boîte (et est implémenté en C sur CPython, donc c'est à peu près aussi rapide que possible).
ShadowRanger
0

S'appuyant sur la réponse de Daniel (ajouts):

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)

La raison pour laquelle j'ai voulu ajouter cette partie est que les variables statiques sont utilisées non seulement pour incrémenter d'une certaine valeur, mais aussi pour vérifier si la var statique est égale à une certaine valeur, comme exemple réel.

La variable statique est toujours protégée et utilisée uniquement dans le cadre de la fonction use_foo ()

Dans cet exemple, l'appel à foo () fonctionne exactement comme (par rapport à l'équivalent c ++ correspondant):

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

si la classe Foo est définie de manière restrictive comme une classe singleton, ce serait idéal. Cela le rendrait plus pythonique.

yash
la source
-1

Bien sûr, c'est une vieille question, mais je pense que je pourrais fournir une mise à jour.

Il semble que l'argument de la performance soit obsolète. La même suite de tests semble donner des résultats similaires pour siInt_try et isInt_re2. Bien sûr, les résultats varient, mais il s'agit d'une session sur mon ordinateur avec python 3.4.4 sur le noyau 4.3.01 avec Xeon W3550. Je l'ai exécuté plusieurs fois et les résultats semblent similaires. J'ai déplacé l'expression régulière globale en fonction statique, mais la différence de performances est négligeable.

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

Avec un problème de performance à l'écart, il semble que try / catch produirait le code le plus futur et le plus à l'épreuve des coins, alors peut-être simplement l'envelopper dans la fonction

Keji Li
la source
1
Que comparez-vous même ici? Cela semble être un commentaire sur d'autres réponses, mais il n'est pas clair lesquelles, et cela ne répond pas à la question elle-même.
ShadowRanger