Was ist das Python-Äquivalent zu statischen Variablen innerhalb einer Funktion?

Lesezeit: 10 Minuten

Benutzer-Avatar
andrewdotnich

Was ist das idiomatische Python-Äquivalent dieses C/C++-Codes?

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

Insbesondere, wie implementiert man das statische Mitglied auf Funktionsebene, im Gegensatz zur Klassenebene? Und ändert das Platzieren der Funktion in einer Klasse irgendetwas?

  • Es gibt NEIN Äquivalenz, fürchte ich. Selbst wenn Sie den Decorator-Hack mit Funktionsattributen durchführen, können Sie von außen auf die Variable zugreifen, was den Punkt leider zunichte macht. Außerdem müssen Sie den Funktionsnamen in der Funktion fest codieren, was sehr ärgerlich ist. Ich würde vorschlagen, anstelle der herkömmlichen Variablen eine Klasse oder ein Modul mit globalen Variablen zu verwenden _ Präfix.

    – László Papp

    3. September 2014 um 10:07 Uhr


  • Für Nicht-C-Programmierer, [stackoverflow.com/questions/5033627/… static variable inside a function is only visible inside that function’s scope, but its lifetime is the entire life of the program, and it’s only initialized once). Basically, a persistent counter or storage variable that lives between function calls.

    – smci

    Jun 1, 2018 at 8:28

  • @lpapp: there kind-of is, it’s a class member. You are correct that we can’t prevent other code viewing it or changing it.

    – smci

    Jun 1, 2018 at 8:37

  • I found answer given by Claudiu useful.

    – Vinit Ramesh Gore

    Nov 2, 2021 at 20:26

user avatar
Claudiu

A bit reversed, but this should work:

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

If you want the counter initialization code at the top instead of the bottom, you can create a decorator:

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

Dann verwenden Sie den Code wie folgt:

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

Sie müssen weiterhin die verwenden foo. Präfix, leider.

(Bildnachweis: @ony)

  • es gibt nur eine Instanz von foo – diese eine Funktion. alle Aufrufe greifen auf dieselbe Variable zu.

    – Claudius

    10. November 2008 um 23:49 Uhr

  • Tut mir leid, dass ich das ausgegraben habe, aber ich würde es lieber sagen if "counter" not in foo.__dict__: foo.counter = 0 wie die ersten Zeilen von foo(). Dies würde helfen, Code außerhalb der Funktion zu vermeiden. Ich bin mir jedoch nicht sicher, ob dies im Jahr 2008 möglich war. PS Fand diese Antwort bei der Suche nach der Möglichkeit, statische Funktionsvariablen zu erstellen, also ist dieser Thread noch “lebendig” 🙂

    – binärLV

    9. August 2012 um 6:30 Uhr


  • @binaryLV: Ich würde das wahrscheinlich dem ersten Ansatz vorziehen. Das Problem beim ersten Ansatz ist, dass dies nicht sofort offensichtlich ist foo und foo.counter = sind eng verwandt. Ich bevorzuge jedoch letztendlich den Decorator-Ansatz, da der Decorator auf keinen Fall nicht aufgerufen wird und es semantisch offensichtlicher ist, was er tut (@static_var("counter", 0) ist einfacher und macht für meine Augen mehr Sinn als if "counter" not in foo.__dict__: foo.counter = 0zumal in letzterem der Funktionsname (zweimal) verwendet werden muss, der sich ändern kann).

    – Claudius

    1. März 2013 um 19:31 Uhr


  • @lpapp: Es hängt davon ab, was der Sinn statischer Variablen ist. Ich dachte immer, es wäre derselbe Wert über mehrere Funktionsaufrufe hinweg, was dies erfüllt. Ich habe nie angenommen, dass es um das Verbergen von Variablen geht, was hier nicht der Fall ist, wie Sie sagten.

    – Claudius

    3. September 2014 um 14:25 Uhr

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

    – Erik Aronesty

    3. Januar 2017 um 17:41 Uhr


Benutzer-Avatar
Vincent

Sie können einer Funktion Attribute hinzufügen und sie als statische Variable verwenden.

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

# attribute must be initialized
myfunc.counter = 0

Wenn Sie die Variable nicht außerhalb der Funktion einrichten möchten, können Sie alternativ verwenden hasattr() zu vermeiden ein AttributeError Ausnahme:

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

Wie auch immer, statische Variablen sind eher selten, und Sie sollten einen besseren Platz für diese Variable finden, höchstwahrscheinlich innerhalb einer Klasse.

  • Warum versuchen Sie es nicht anstelle der if-Anweisung?

    – rav

    1. Dezember 2013 um 15:01 Uhr

  • try: myfunc.counter += 1; except AttributeError: myfunc.counter = 1 sollten dasselbe tun und stattdessen Ausnahmen verwenden.

    – Schlank

    10. Dezember 2013 um 4:31 Uhr

  • Ausnahmen sollten für Ausnahmesituationen verwendet werden, dh Situationen, von denen der Programmierer erwartet, dass sie nicht eintreten, wie beispielsweise eine erfolgreich geöffnete Eingabedatei, die plötzlich nicht mehr verfügbar ist. Dies ist eine erwartete Situation, eine if-Anweisung ist sinnvoller.

    – Bügelsäge

    15. Januar 2014 um 7:51 Uhr

  • @Hack_Saw: Nun, das ist Pythonic (besser um Vergebung als um Erlaubnis bitten). Dies wird tatsächlich in Python-Optimierungstechniken empfohlen, da es die Kosten für ein if spart (obwohl ich keine vorzeitige Optimierung empfehle). Ihre Regel zu Ausnahmefällen: 1. Scheitern IST hier gewissermaßen ein Ausnahmefall. Es passiert nur einmal. 2. Ich denke, dass es bei dieser Regel darum geht, Ausnahmen zu verwenden (dh auszulösen). Dies ist eine Ausnahme für etwas, von dem Sie erwarten, dass es funktioniert, für das Sie jedoch einen Backup-Plan haben, was in den meisten Sprachen üblich ist.

    – leewz

    17. Januar 2014 um 23:42 Uhr


  • @leewangzhong: Schließt einen Block ein, der keine Ausnahme auslöst try Kosten hinzufügen? Nur neugierig.

    – trss

    23. Juli 2014 um 20:04 Uhr


Benutzer-Avatar
rav

Man könnte auch überlegen:

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

Argumentation:

  • viel pythonisch (“bitte um Vergebung, nicht um Erlaubnis”)
  • benutze Ausnahme (wird nur einmal geworfen) statt if Filiale (denke StopIteration Ausnahme)

  • Ich mache Python noch nicht lange, aber dies erfüllt eines der impliziten Mietshäuser der Sprache: wenn es nicht (ziemlich) einfach ist, machst du es falsch.

    – ZX9

    21. September 2016 um 17:19 Uhr


  • Funktionierte nicht sofort mit Klassenmethoden, “self.foo.counter = 1” löst erneut AttributeError aus.

    – Villenv

    14. Oktober 2016 um 21:10 Uhr

  • Dies ist die richtige Lösung und sollte die akzeptierte Antwort sein, da der Initialisierungscode ausgeführt wird, wenn die Funktion aufgerufen wird und nicht, wenn das Modul ausgeführt oder etwas daraus importiert wird, was der Fall ist, wenn Sie den Decorator-Ansatz von verwenden die derzeit akzeptierte Antwort. Siehe Python-Decorator-Funktionsausführung. Wenn Sie ein riesiges Bibliotheksmodul haben, wird jeder Decorator ausgeführt, einschließlich der Funktionen, die Sie nicht importieren.

    – Nils Lindemann

    8. Mai 2017 um 9:48 Uhr


  • Ein einfacherer Ansatz: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c

    – MANU

    18. Dezember 2017 um 5:12 Uhr

  • @MANU Verwenden hasattr() denn das ist nicht einfacher und auch weniger effizient.

    – muuuuh

    8. Februar 2019 um 11:39 Uhr


Viele Leute haben bereits vorgeschlagen, ‘hasattr’ zu testen, aber es gibt eine einfachere Antwort:

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

Kein try/außer, kein Testen von hasattr, nur getattr mit einem Standardwert.

Benutzer-Avatar
Jeremy

Andere Antworten haben gezeigt, wie Sie dies tun sollten. Folgendes sollten Sie vermeiden:

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

Standardwerte werden nur initialisiert, wenn die Funktion zum ersten Mal ausgewertet wird, nicht jedes Mal, wenn sie ausgeführt wird, sodass Sie eine Liste oder ein beliebiges anderes änderbares Objekt verwenden können, um statische Werte zu speichern.

  • Ich habe das versucht, aber aus irgendeinem Grund initialisierte sich der Funktionsparameter auf 140, nicht auf 0. Warum sollte das so sein?

    – andrewdotnich

    10. November 2008 um 23:58 Uhr

  • @bouvard Für rekursive Funktionen, die eine statische Variable benötigen, ist dies die einzige, die wirklich gut liest.

    – Lebensbalance

    5. Juni 2017 um 16:57 Uhr


  • Ich habe mehrere Ansätze ausprobiert und ich wünschte, dieser würde als pythonisch akzeptiert. Mit einigen aussagekräftigen Namen wie def foo(arg1, arg2, _localstorage=DataClass(counter=0)) Ich finde es gut lesbar. Ein weiterer guter Punkt ist das einfache Umbenennen von Funktionen.

    – VPfB

    30. Januar 2018 um 12:40 Uhr


  • Warum sagst du, du sollst es nicht so machen? Sieht für mich absolut vernünftig aus!

    – Konstantin

    29. Juli 2018 um 8:01 Uhr

  • @VPfB: Für die allgemeine Speicherung könnten Sie verwenden types.SimpleNamespaceIch mach das def foo(arg1, arg2, _staticstorage=types.SimpleNamespace(counter=0)): ohne eine spezielle Klasse definieren zu müssen.

    – ShadowRanger

    28. Dezember 2019 um 2:25 Uhr


Benutzer-Avatar
Gemeinschaft

Python hat keine statischen Variablen, aber Sie können es vortäuschen, indem Sie ein aufrufbares Klassenobjekt definieren und es dann als Funktion verwenden. Siehe auch diese Antwort.

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

Beachten Sie, dass __call__ macht eine Instanz einer Klasse (Objekt) unter ihrem eigenen Namen aufrufbar. Deshalb anrufen foo() oben ruft die Klasse auf’ __call__ Methode. Aus der Dokumentation:

Instanzen beliebiger Klassen können aufrufbar gemacht werden, indem a definiert wird __call__() Methode in ihrer Klasse.

  • Ich habe das versucht, aber aus irgendeinem Grund initialisierte sich der Funktionsparameter auf 140, nicht auf 0. Warum sollte das so sein?

    – andrewdotnich

    10. November 2008 um 23:58 Uhr

  • @bouvard Für rekursive Funktionen, die eine statische Variable benötigen, ist dies die einzige, die wirklich gut liest.

    – Lebensbalance

    5. Juni 2017 um 16:57 Uhr


  • Ich habe mehrere Ansätze ausprobiert und ich wünschte, dieser würde als pythonisch akzeptiert. Mit einigen aussagekräftigen Namen wie def foo(arg1, arg2, _localstorage=DataClass(counter=0)) Ich finde es gut lesbar. Ein weiterer guter Punkt ist das einfache Umbenennen von Funktionen.

    – VPfB

    30. Januar 2018 um 12:40 Uhr


  • Warum sagst du, du sollst es nicht so machen? Sieht für mich absolut vernünftig aus!

    – Konstantin

    29. Juli 2018 um 8:01 Uhr

  • @VPfB: Für die allgemeine Speicherung könnten Sie verwenden types.SimpleNamespaceIch mach das def foo(arg1, arg2, _staticstorage=types.SimpleNamespace(counter=0)): ohne eine spezielle Klasse definieren zu müssen.

    – ShadowRanger

    28. Dezember 2019 um 2:25 Uhr


Hier ist eine vollständig gekapselte Version, die keinen externen Initialisierungsaufruf erfordert:

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

In Python sind Funktionen Objekte, und wir können ihnen über das spezielle Attribut einfach Member-Variablen hinzufügen oder sie patchen __dict__. Das eingebaute vars() gibt das spezielle Attribut zurück __dict__.

EDIT: Hinweis, im Gegensatz zur Alternative try:except AttributeError Antwort, mit diesem Ansatz ist die Variable nach der Initialisierung immer bereit für die Codelogik. Ich denke, die try:except AttributeError Alternativen zu den folgenden sind weniger TROCKEN und/oder haben einen unangenehmen Fluss:

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: Ich empfehle den obigen Ansatz nur, wenn die Funktion von mehreren Standorten aus aufgerufen wird. Wenn die Funktion stattdessen nur an einer Stelle aufgerufen wird, ist es besser, sie zu verwenden 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))
    ...

  • Das einzige Problem dabei ist, dass es überhaupt nicht ordentlich ist, und wann immer Sie dieses Muster verwenden möchten, müssen Sie den Code ausschneiden und einfügen … daher meine Verwendung eines Dekorateurs

    – Claudius

    30. Januar 2013 um 0:07 Uhr

  • wahrscheinlich sollte so etwas wie verwenden try: mystaticfun.counter+=10 except AttributeError: mystaticfun.counter=0

    – Endolith

    6. Dezember 2013 um 2:26 Uhr


  • Bitte verwende X not in Y statt not X in Y (oder raten Sie zu verwenden, wenn Sie es nur für einen ähnlich aussehenden Vergleich zwischen diesem und verwendet haben hasattr)

    – Nik T

    30. September 2014 um 19:09 Uhr

  • Wie wäre es damit: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c

    – MANU

    18. Dezember 2017 um 5:11 Uhr

  • es ist nicht ideal, weil die if-Klausel unnötige Verschachtelungen hinzufügt, in dieser Situation bevorzuge ich setdefault

    – Riaz Rizvi

    18. Dezember 2017 um 18:36 Uhr

1136800cookie-checkWas ist das Python-Äquivalent zu statischen Variablen innerhalb einer Funktion?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy