807 Stimmen

Was ist die Python-Entsprechung von statischen Variablen innerhalb einer Funktion?

Was ist die idiomatische Python-Entsprechung für diesen C/C++-Code?

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

Wie implementiert man das statische Mitglied auf der Funktionsebene, im Gegensatz zur Klassenebene? Und ändert sich etwas, wenn man die Funktion in eine Klasse einordnet?

33 Stimmen

Es gibt NO Gleichwertigkeit, fürchte ich. Selbst wenn man den Decorator-Hack mit Funktionsattributen durchführt, kann man auf die Variable von außen zugreifen, was den Sinn der Sache leider zunichte macht. Außerdem müssen Sie den Funktionsnamen in der Funktion hart codieren, was sehr ärgerlich ist. Ich würde vorschlagen, stattdessen globale Variablen einer Klasse oder eines Moduls zu verwenden, mit der konventionellen _ Vorwahl.

17 Stimmen

Für Nicht-C-Programmierer, [ [stackoverflow.com/questions/5033627/](https://stackoverflow.com/questions/5033627/static-variable-inside-of-a-function-in-c#5033656](a "static variable inside of a function in c%235033656%5d(a") statische Variable innerhalb einer Funktion ist nur innerhalb des Funktionsbereichs dieser Funktion sichtbar, aber ihre Lebensdauer ist die gesamte Lebensdauer des Programms, und sie wird nur einmal initialisiert). Im Grunde genommen handelt es sich um einen dauerhaften Zähler oder eine Speichervariable, die zwischen Funktionsaufrufen lebt.

2 Stimmen

@lpapp: Irgendwie schon, es ist ein Klassenmitglied . Sie haben Recht, dass wir nicht verhindern können, dass andere Codes sie anzeigen oder ändern.

33voto

daniels Punkte 17668

In Python gibt es keine statischen Variablen, aber man kann sie vortäuschen, indem man ein aufrufbares Klassenobjekt definiert und es dann als Funktion verwendet. 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 (eines Objekts) durch ihren eigenen Namen aufrufbar. Deshalb ist der Aufruf von foo() oben ruft die Klasse __call__ Methode. Aus der Dokumentation :

Instanzen beliebiger Klassen können aufrufbar gemacht werden, indem man eine __call__() Methode in ihrer Klasse.

18 Stimmen

Funktionen sind bereits Objekte, so dass dies nur eine unnötige Ebene darstellt.

0 Stimmen

Siehe diese SO-Antwort für eine lange Stellungnahme, dass dies tatsächlich eine gute Idee ist. stackoverflow.com/questions/460586 . Ich stimme zu, dass eine solche Klasse ein Singleton sein sollte, vielleicht wie folgt stackoverflow.com/questions/6760685 wäre auch eine gute Idee. Ich weiß nicht, was @S.Lott mit "... Zähler in die Klassendefinition verschieben ..." meint, denn für mich sieht es so aus, als ob er sich bereits in der Position der Klassenvariablen befindet.

1 Stimmen

Nach meinen Recherchen scheint diese Klassentechnik die "pythonischste" der auf dieser Seite vorgestellten Methoden zu sein und die wenigsten Tricks zu verwenden. Als neuer Python-Entwickler plane ich daher, sie als Ersatz für C-statik-ähnliche Variablen in Funktionen zu verwenden.

32voto

Riaz Rizvi Punkte 8825

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 einfach Mitgliedsvariablen hinzufügen oder sie mit dem speziellen Attribut __dict__ . Die eingebaute vars() gibt das besondere Attribut __dict__ .

EDIT: Hinweis: Im Gegensatz zur Alternative try:except AttributeError Antwort: Bei diesem Ansatz steht die Variable nach der Initialisierung immer für die Codelogik bereit. Ich denke, die try:except AttributeError Alternativen zu den folgenden sind weniger DRY und/oder haben einen ungünstigen 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 Stellen aus aufgerufen wird. Wenn die Funktion stattdessen nur an einer Stelle aufgerufen wird, ist es besser, die 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 Stimmen

Das einzige Problem dabei ist, dass es wirklich nicht ordentlich ist, und wann immer Sie dieses Muster verwenden wollen, müssen Sie den Code ausschneiden und einfügen... daher meine Verwendung eines Dekorators

2 Stimmen

Sollte wahrscheinlich etwas verwenden wie try: mystaticfun.counter+=10 except AttributeError: mystaticfun.counter=0

2 Stimmen

Bitte verwenden Sie X not in Y statt not X in Y (oder raten Sie zur Verwendung, wenn Sie es nur verwenden, um einen ähnlich aussehenden Vergleich zwischen diesem und hasattr )

18voto

gnud Punkte 75549

Verwenden Sie eine Generatorfunktion, um einen Iterator zu erzeugen.

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

Dann verwenden Sie es wie

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

Wenn Sie eine Obergrenze wollen:

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

Wenn der Iterator abbricht (wie im obigen Beispiel), können Sie auch direkt eine Schleife darüber laufen lassen, wie

for i in foo_gen(20):
    print i

In diesen einfachen Fällen ist es natürlich besser, xrange zu verwenden :)

Hier finden Sie die Dokumentation zum Ertragsanweisung .

18voto

cbarrick Punkte 1199

Andere Lösungen fügen der Funktion ein Zählerattribut hinzu, in der Regel mit einer komplizierten Logik zur Handhabung der Initialisierung. Dies ist für neuen Code unangemessen.

In Python 3 ist der richtige Weg die Verwendung einer nonlocal Erklärung:

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

Siehe PEP 3104 für die Angabe der nonlocal Erklärung.

Wenn der Zähler für das Modul privat sein soll, sollte er den Namen _counter stattdessen.

5 Stimmen

Auch vor Python 3 konnte man dies immer mit einer global counter Anweisung anstelle von nonlocal counter ( nonlocal lässt Sie einfach in einer verschachtelten Funktion in den Abschlusszustand schreiben). Der Grund, warum die Leute ein Attribut an die Funktion anhängen, ist, um zu vermeiden, dass der globale Namespace für den Zustand, der spezifisch für die Funktion ist, verschmutzt wird, so dass man nicht noch mehr umständliche Dinge tun muss, wenn zwei Funktionen unabhängige counter s. Diese Lösung ist nicht skalierbar, die Attribute der Funktion schon. kdb's Antwort ist wie nonlocal kann helfen, macht die Sache aber auch komplizierter.

0 Stimmen

Eh, ich denke, die Komplexität einer Fabrikfunktion oder eines Dekorators ist übertrieben, es sei denn, Sie tun dies eine Menge, und in diesem Fall ist das Design bereits ein bisschen stinkig. Für eine einmalige, fügen Sie einfach die nicht-lokale Zähler und mit ihm fertig sein. Ich habe der Antwort ein wenig über Namenskonventionen hinzugefügt. Auch der Grund, warum ich empfehle nonlocal über global ist genau das, was du sagst - es funktioniert unter viel mehr Umständen.

1 Stimmen

Ich habe das in PyCharm: Nichtlokale Variable '_counter' muss in einem äußeren Funktionsbereich gebunden werden

13voto

kdb Punkte 3673

Die Verwendung eines Attributs einer Funktion als statische Variable hat einige potenzielle Nachteile:

  • Jedes Mal, wenn Sie auf die Variable zugreifen wollen, müssen Sie den vollständigen Namen der Funktion ausschreiben.
  • Externer Code kann leicht auf die Variable zugreifen und den Wert verändern.

Idiomatisches Python für das zweite Problem wäre wahrscheinlich, die Variable mit einem führenden Unterstrich zu benennen, um zu signalisieren, dass auf sie nicht zugegriffen werden soll, während sie nach der Tat zugänglich bleibt.

Verwendung von Verschlüssen

Eine Alternative wäre ein Muster, das lexikalische Abschlüsse verwendet, die mit dem nonlocal Schlüsselwort in Python 3.

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

Leider kenne ich keine Möglichkeit, diese Lösung in einem Dekorator zu kapseln.

Verwendung eines internen Zustandsparameters

Eine weitere Option könnte ein undokumentierter Parameter sein, der als Container für veränderbare Werte dient.

def counter(*, _i=[0]):
    _i[0] += 1
    return _i[0]

Das funktioniert, weil die Standardargumente bei der Definition der Funktion ausgewertet werden und nicht erst beim Aufruf der Funktion.

Sauberer wäre es, wenn man statt der Liste einen Containertyp hätte, z. B.

def counter(*, _i = Mutable(0)):
    _i.value += 1
    return _i.value

aber mir ist kein eingebauter Typ bekannt, der den Zweck eindeutig angibt.

0 Stimmen

Dieser Schwebezustand des internen Zustandsparameters erinnert mich an das Idiom des versteckten Freundes in C++.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X