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

628

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

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

Wie implementiert man das statische Element auf Funktionsebene im Gegensatz zur Klassenebene? Und ändert das Platzieren der Funktion in einer Klasse etwas?

andrewdotnich
quelle
22
Ich fürchte, es gibt KEINE Äquivalenz. Selbst wenn Sie den Decorator-Hack mit Funktionsattributen ausführen, können Sie auf die Variable außerhalb zugreifen, was den Punkt leider irgendwie zunichte macht. Außerdem müssen Sie den Funktionsnamen in der Funktion fest codieren, was sehr ärgerlich ist. Ich würde vorschlagen, stattdessen globale Klassen- oder Modulvariablen mit dem herkömmlichen _Präfix zu verwenden.
lpapp
8
Für Nicht-C-Programmierer ist die statische Variable [ stackoverflow.com/questions/5033627/… innerhalb einer Funktion nur innerhalb des Funktionsumfangs sichtbar, ihre Lebensdauer entspricht jedoch der gesamten Lebensdauer des Programms und wird nur einmal initialisiert. Grundsätzlich ein persistenter Zähler oder eine Speichervariable, die zwischen Funktionsaufrufen lebt.
smci
2
@lpapp: Es gibt eine Art Klassenmitglied . Sie haben Recht, dass wir nicht verhindern können, dass anderer Code ihn anzeigt oder ändert.
smci

Antworten:

681

Ein bisschen umgekehrt, aber das sollte funktionieren:

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

Wenn der Zählerinitialisierungscode oben statt unten angezeigt werden soll, können Sie einen Dekorator erstellen:

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

Verwenden Sie dann den folgenden Code:

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

foo.Leider müssen Sie weiterhin das Präfix verwenden.

( Bildnachweis : @ony )

Claudiu
quelle
23
Es gibt nur eine Instanz von foo - diese eine Funktion. Alle Aufrufe greifen auf dieselbe Variable zu.
Claudiu
120
Tut mir leid, dass ich das ausgegraben habe, aber ich würde es lieber if "counter" not in foo.__dict__: foo.counter = 0als erste Zeile von setzen foo(). Dies würde helfen, Code außerhalb der Funktion zu vermeiden. Ich bin mir nicht sicher, ob dies 2008 möglich war. PS Diese Antwort wurde gefunden, als nach der Möglichkeit gesucht wurde, statische Funktionsvariablen zu erstellen, sodass dieser Thread noch "lebendig" ist :)
binaryLV
8
@binaryLV: Ich würde das wahrscheinlich dem ersten Ansatz vorziehen. Das Problem beim ersten Ansatz ist, dass dies nicht sofort offensichtlich ist foound foo.counter = eng miteinander verbunden ist. Letztendlich bevorzuge ich jedoch den Dekorationsansatz, da der Dekorateur auf keinen Fall aufgerufen wird und es semantisch offensichtlicher ist, was er tut ( @static_var("counter", 0)ist einfacher und für meine Augen sinnvoller als if "counter" not in foo.__dict__: foo.counter = 0, insbesondere, wenn Sie letzteres verwenden müssen der Funktionsname (zweimal), der sich ändern könnte).
Claudiu
6
@lpapp: Es hängt davon ab, wozu statische Variablen gut sind. Ich dachte immer, es wäre der gleiche Wert über mehrere Funktionsaufrufe hinweg, was dies befriedigt. Ich habe nie angenommen, dass es um das Verstecken von Variablen geht, was dies nicht tut, wie Sie sagten.
Claudiu
3
def foo(): if not hasattr(foo,"counter"): foo.counter=0 foo.counter += 1
Erik Aronesty
221

Sie können einer Funktion Attribute hinzufügen und diese 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 hasattr()eine AttributeErrorAusnahme vermeiden :

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

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

Vincent
quelle
6
Warum nicht statt if-Anweisung versuchen?
Ravwojdyla
12
try: myfunc.counter += 1; except AttributeError: myfunc.counter = 1sollte dasselbe tun und stattdessen Ausnahmen verwenden.
Sleblanc
Ausnahmen sollten für Ausnahmesituationen verwendet werden, dh solche, von denen der Programmierer erwartet, dass sie nicht auftreten, z. B. eine erfolgreich geöffnete Eingabedatei, die plötzlich nicht mehr verfügbar ist. Dies ist eine erwartete Situation, eine if-Anweisung ist sinnvoller.
Hack Saw
11
@ Hack_Saw: Nun, das ist Pythonic (besser um Vergebung als um Erlaubnis zu bitten). Dies wird in Python-Optimierungstechniken tatsächlich empfohlen, da es die Kosten eines if spart (obwohl ich keine vorzeitige Optimierung empfehle). Ihre Regel zu Ausnahmefällen: 1. Fehler ist hier gewissermaßen ein Ausnahmefall. Es kommt nur einmal vor. 2. Ich denke, in dieser Regel geht es darum, Ausnahmen zu verwenden (dh zu erhöhen). Dies ist eine Ausnahme für etwas, von dem Sie erwarten, dass es funktioniert, für das jedoch ein Sicherungsplan vorhanden ist, was in den meisten Sprachen üblich ist.
Leez
@leewangzhong: Fügt das Einschließen eines Blocks, der keine Ausnahme auslöst, tryzusätzliche Kosten hinzu? Nur neugierig.
trss
201

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")
  • Verwenden Sie eine Ausnahme (nur einmal ausgelöst) anstelle einer ifVerzweigung (denken Sie an eine StopIteration- Ausnahme).
Ravwojdyla
quelle
10
Ich habe Python nicht lange gemacht, aber dies erfüllt eine der impliziten Anforderungen der Sprache: Wenn es nicht (ziemlich) einfach ist, machen Sie es falsch .
ZX9
Funktionierte nicht sofort mit Klassenmethoden. "Self.foo.counter = 1" löst AttributeError erneut aus.
Villasv
16
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 wird oder wenn etwas daraus importiert wird. Dies ist der Fall, wenn Sie den Decorator-Ansatz von verwenden die aktuell akzeptierte Antwort. Siehe Ausführung der Python Decorator-Funktion . Wenn Sie über ein großes Bibliotheksmodul verfügen, wird jeder Dekorateur ausgeführt, einschließlich der Funktionen, die Sie nicht importieren.
Nils Lindemann
3
Ein einfacherer Ansatz: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCuriousOne
5
@MANU Die Verwendung hasattr()hierfür ist nicht einfacher und auch weniger effizient.
Moooeeeep
48

Andere Antworten haben gezeigt, wie Sie dies tun sollten. Hier ist ein Weg, den Sie nicht sollten:

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

Standardwerte werden nur bei der ersten Auswertung der Funktion initialisiert, nicht bei jeder Ausführung. Sie können also eine Liste oder ein anderes veränderbares Objekt zum Speichern statischer Werte verwenden.

Jeremy Banks
quelle
Ich habe das versucht, aber aus irgendeinem Grund initialisierte sich der Funktionsparameter auf 140 und nicht auf 0. Warum sollte das so sein?
Andrewdotnich
1
@bouvard Für rekursive Funktionen, die eine statische Variable benötigen, ist dies die einzige, die wirklich gut liest.
Lebensbalance
1
Ich habe verschiedene Ansätze ausprobiert und ich wünschte, dieser würde als pythonisch akzeptiert. Mit einigen aussagekräftigen Namen def foo(arg1, arg2, _localstorage=DataClass(counter=0))finde ich es gut lesbar. Ein weiterer guter Punkt ist das einfache Umbenennen von Funktionen.
VPfB
2
Warum sagst du, du solltest es nicht so machen? Sieht für mich völlig vernünftig aus!
Konstantin
1
@VPfB: Für den allgemeinen Speicher können Sie ihn verwenden types.SimpleNamespace, def foo(arg1, arg2, _staticstorage=types.SimpleNamespace(counter=0)):ohne eine spezielle Klasse definieren zu müssen.
ShadowRanger
42

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

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

Kein Versuch / außer, kein Test hasattr, nur getattr mit einem Standard.

Jonathan
quelle
2
Achten Sie auf den dritten Parameter von getattr, wenn Sie dort eine Funktion einfügen, zum Beispiel: def func (): def foo (): return 1112 func.counter = getattr (func, 'counter', foo ()) + 1, wenn Sie anrufen func, das foo wird immer aufgerufen!
Code für den
1
Nur ein Anruf bei getattr jedes Mal, wenn diese Funktion aufgerufen wird. Das ist in Ordnung, wenn die Leistung kein Problem darstellt.
Mark Lawrence
2
@MarkLawrence: Zumindest bei meiner Windows x64 3.8.0-Installation ist der Leistungsunterschied zwischen dieser Antwort und dem äquivalenten try/ exceptbasierten Ansatz von ravwojdyla ziemlich bedeutungslos. Ein einfaches ipython %%timeitMikrobenchmark ergab die Kosten für try/ exceptbei 255 ns pro Anruf gegenüber 263 ns für die getattrbasierte Lösung. Ja, das try/ exceptist schneller, aber es ist nicht gerade "zweifellos gewinnen"; Es ist eine winzige Mikrooptimierung. Schreiben Sie den Code, der klarer erscheint. Machen Sie sich keine Sorgen über geringfügige Leistungsunterschiede wie diesen.
ShadowRanger
@ShadowRanger danke für das Benchmarking. Ich habe mich 2 Jahre lang über MarkLawrence's Aussage gewundert und bin sehr froh, dass Sie die Nachforschungen angestellt haben. Ich stimme definitiv Ihrem letzten Satz zu - "Schreiben Sie, welcher Code klarer erscheint" - genau deshalb habe ich diese Antwort geschrieben.
Jonathan
28

Hier ist eine vollständig gekapselte Version, für die kein externer Initialisierungsaufruf erforderlich ist:

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 über das spezielle Attribut hinzufügen oder Affen-Patches hinzufügen __dict__. Das integrierte vars()Element gibt das spezielle Attribut zurück __dict__.

BEARBEITEN: Beachten Sie, dass try:except AttributeErrorbei diesem Ansatz die Variable im Gegensatz zur alternativen Antwort nach der Initialisierung immer für die Codelogik bereit ist. Ich denke, die try:except AttributeErrorAlternative zu den folgenden ist weniger trocken und / oder hat 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 Stellen aus aufgerufen wird. Wenn die Funktion stattdessen nur an einer Stelle aufgerufen wird, ist es besser, Folgendes 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))
    ...
Riaz Rizvi
quelle
2
Das einzige Problem dabei ist, dass es wirklich ü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
Claudiu
2
sollte wahrscheinlich so etwas wietry: mystaticfun.counter+=10 except AttributeError: mystaticfun.counter=0
Endolith
2
Bitte verwenden Sie X not in Yanstelle von not X in Y(oder empfehlen Sie die Verwendung, wenn Sie es nur für einen ähnlicheren Vergleich zwischen diesem und verwenden hasattr)
Nick T
Wie wäre es damit: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCuriousOne
Es ist nicht ideal, weil die if-Klausel unnötige Verschachtelung hinzufügt. In dieser Situation bevorzuge ich setdefault
Riaz Rizvi
27

Python hat keine statischen Variablen, aber Sie können es fälschen, 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, __call__dass eine Instanz einer Klasse (eines Objekts) unter ihrem eigenen Namen aufgerufen werden kann. Aus diesem Grund ruft der foo()obige Aufruf die __call__Methode der Klasse auf . Aus der Dokumentation :

Instanzen beliebiger Klassen können durch Definieren einer __call__()Methode in ihrer Klasse aufrufbar gemacht werden .

Daniels
quelle
15
Funktionen sind bereits Objekte, sodass nur eine unnötige Ebene hinzugefügt wird.
DasIch
In dieser SO-Antwort finden Sie eine lange Meinung, dass dies tatsächlich eine gute Idee ist. stackoverflow.com/questions/460586 . Ich bin damit einverstanden, dass es auch eine gute Idee wäre, eine solche Klasse zu einem Singleton zu machen, vielleicht wie diese stackoverflow.com/questions/6760685 . Ich weiß nicht, was @ S.Lott unter "... Zähler in Klassendefinition verschieben ..." versteht, da es für mich so aussieht, als ob es sich bereits in einer klassenvariablen Position befindet.
Reb.Cabin
1
Basierend auf meinen Recherchen scheint diese Klassentechnik die "pythonischste" der auf dieser Seite vorgestellten Ansätze zu sein und verwendet die wenigsten Tricks. Ich habe daher vor, es als neuen Python-Entwickler selbst als Ersatz für C-statische Variablen in Funktionen zu verwenden.
Gabriel Staples
1
Was passiert, wenn ich foo1 = Foo () und foo2 = Foo () möchte?
Mark Lawrence
@MarkLawrence Dann haben Sie zwei verschiedene Instanzen einer aufrufbaren Klasse, jede mit einem eigenen Zähler. Was genau Sie erwarten sollten, wenn Sie foodie als Singleton bereitgestellte Instanz nicht verwenden .
Aaron McMillin
14

Verwenden Sie eine Generatorfunktion, um einen Iterator zu generieren.

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

Dann benutze es wie

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

Wenn Sie eine Obergrenze wünschen:

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

Wenn der Iterator beendet wird (wie im obigen Beispiel), können Sie ihn auch direkt durchlaufen, z

for i in foo_gen(20):
    print i

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

Hier ist die Dokumentation zur Renditeerklärung .

Gnud
quelle
11

Andere Lösungen fügen der Funktion ein Zählerattribut hinzu, normalerweise mit verschlungener Logik, um die Initialisierung zu handhaben. Dies ist für neuen Code ungeeignet.

In Python 3 ist der richtige Weg, eine nonlocalAnweisung zu verwenden:

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

Siehe PEP 3104 für die Spezifikation der nonlocalAnweisung.

Wenn der Zähler für das Modul privat sein soll, sollte er _counterstattdessen benannt werden.

Cbarrick
quelle
Noch vor Python 3 konnten Sie dies immer mit einer global counterAnweisung anstelle von tun nonlocal counter(Sie können nonlocalnur in einer verschachtelten Funktion in den Abschlusszustand schreiben). Der Grund, warum Benutzer der Funktion ein Attribut hinzufügen, besteht darin, zu vermeiden, dass der globale Namespace für den funktionsspezifischen Status verschmutzt wird, sodass Sie keine noch härteren Aufgaben ausführen müssen, wenn zwei Funktionen unabhängige counters benötigen . Diese Lösung lässt sich nicht skalieren. Attribute auf der Funktion tun. Die Antwort von kdb lautet, wie nonlocalman helfen kann, aber es erhöht die Komplexität.
ShadowRanger
Eh, ich denke, die Komplexität einer Fabrikfunktion oder eines Dekorateurs ist übertrieben, wenn Sie dies nicht oft tun, und in diesem Fall stinkt das Design bereits ein bisschen. Für eine einmalige Aktion fügen Sie einfach den nichtlokalen Zähler hinzu und fertig. Ich habe der Antwort zu Namenskonventionen etwas hinzugefügt. Der Grund, den ich nonlocalüber empfehle, globalist genau der, auf den Sie hinweisen - es funktioniert unter genau mehr Umständen.
Cbarrick
8

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

  • Jedes Mal, wenn Sie auf die Variable zugreifen möchten, müssen Sie den vollständigen Namen der Funktion ausschreiben.
  • Externer Code kann leicht auf die Variable zugreifen und mit dem Wert herumspielen.

Idiomatisches Python für das zweite Problem würde wahrscheinlich die Variable mit einem führenden Unterstrich benennen, um zu signalisieren, dass nicht darauf zugegriffen werden soll, während sie nachträglich zugänglich bleibt.

Eine Alternative wäre ein Muster mit lexikalischen Abschlüssen, die mit dem nonlocalSchlüsselwort in Python 3 unterstützt werden.

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 einen Dekorateur zu verkapseln.

kdb
quelle
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)

Ähnlich wie der obige Code von vincent wird dieser als Funktionsdekorator verwendet, und auf statische Variablen muss mit dem Funktionsnamen als Präfix zugegriffen werden. Der Vorteil dieses Codes (obwohl zugegebenermaßen jeder klug genug ist, es herauszufinden) besteht darin, dass Sie mehrere statische Variablen haben und diese auf konventionellere Weise initialisieren können.

Giorgian Borca-Tasciuc
quelle
7

Ein bisschen lesbarer, aber ausführlicher (Zen of Python: explizit ist besser als implizit):

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

Sehen Sie hier für eine Erklärung, wie das funktioniert.

warvariuc
quelle
Können Sie erläutern, warum dieser Code funktioniert? Der zweite foo()sollte das Wörterbuch auf den in der Funktionsdefinition angegebenen Wert neu initialisieren (also mit der Zählertaste mit dem Wert 0). Warum nicht?
Raffaem
3
@raffamaiden: Standardargumente werden nur einmal ausgewertet, wenn die Funktion definiert ist, und nicht jedes Mal, wenn die Funktion aufgerufen wird.
Daniel K.
6
_counter = 0
def foo ():
   globaler _counter
   _counter + = 1
   print 'counter is', _counter

Python verwendet normalerweise Unterstriche, um private Variablen anzugeben. Der einzige Grund in C, die statische Variable innerhalb der Funktion zu deklarieren, besteht darin, sie außerhalb der Funktion auszublenden, was nicht wirklich idiomatisch für Python ist.

Dave
quelle
4

Nachdem ich mehrere Ansätze ausprobiert habe, verwende ich eine verbesserte Version von @ warvariucs Antwort:

import types

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

Die idiomatische Methode besteht darin, eine Klasse zu verwenden , die Attribute haben kann. Wenn Instanzen nicht getrennt sein sollen, verwenden Sie einen Singleton.

Es gibt eine Reihe von Möglichkeiten, wie Sie "statische" Variablen in Python fälschen oder mischen können (eine, die bisher nicht erwähnt wurde, ist ein veränderbares Standardargument), aber dies ist nicht die pythonische, idiomatische Methode, dies zu tun. Verwenden Sie einfach eine Klasse.

Oder möglicherweise ein Generator, wenn Ihr Nutzungsmuster passt.

Teddy
quelle
Für eigenständige rekursive Funktionen ist das defaultArgument das eleganteste.
Lebensbalance
3

Auf diese Frage hin möchte ich eine andere Alternative vorstellen, die möglicherweise etwas besser zu verwenden ist und für Methoden und Funktionen gleich aussieht:

@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    

Wenn Ihnen die Verwendung gefällt, finden Sie hier die Implementierung:

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
quelle
3

Eine andere (nicht empfohlene!) Änderung des aufrufbaren Objekts wie https://stackoverflow.com/a/279598/916373 , wenn Sie nichts dagegen haben, eine funky Anrufsignatur zu verwenden, wäre zu tun

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
hat verloren
quelle
3

Anstatt eine Funktion mit einer statischen lokalen Variablen zu erstellen, können Sie jederzeit ein sogenanntes "Funktionsobjekt" erstellen und ihm eine Standard-Mitgliedsvariable (nicht statisch) zuweisen.

Da Sie ein Beispiel für C ++ angegeben haben, werde ich zunächst erklären, was ein "Funktionsobjekt" in C ++ ist. Ein "Funktionsobjekt" ist einfach eine beliebige Klasse mit einer Überladung operator(). Instanzen der Klasse verhalten sich wie Funktionen. Sie können beispielsweise int x = square(5);auch dann schreiben , wenn squarees sich um ein Objekt (mit Überladung operator()) handelt und technisch gesehen keine "Funktion". Sie können einem Funktionsobjekt alle Funktionen zuweisen, die Sie einem Klassenobjekt geben könnten.

# 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 können wir auch überladen, operator()außer dass die Methode stattdessen den Namen hat __call__:

Hier ist eine Klassendefinition:

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

Hier ist ein Beispiel für die verwendete Klasse:

from foo import foo

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

Die auf der Konsole gedruckte Ausgabe lautet:

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

Wenn Ihre Funktion Eingabeargumente annehmen soll, können Sie diese auch hinzufügen __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
quelle
3

Soulution n + = 1

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

Eine globale Deklaration bietet diese Funktionalität. Im folgenden Beispiel (Python 3.5 oder höher, um das "f" zu verwenden) wird die Zählervariable außerhalb der Funktion definiert. Wenn Sie es in der Funktion als global definieren, bedeutet dies, dass die "globale" Version außerhalb der Funktion der Funktion zur Verfügung gestellt werden soll. Bei jeder Ausführung der Funktion wird der Wert außerhalb der Funktion geändert und über die Funktion hinaus beibehalten.

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
quelle
Dies funktioniert bei korrekter Verwendung genauso. Der Unterschied zum C-Code besteht darin, dass im c-Beispiel des OP die Zählervariable nur von der Funktion berührt werden konnte. Eine globale Variable in Python kann an einer beliebigen Stelle im Skript verwendet oder geändert werden
MortenSickel
2

Eine statische Variable innerhalb einer Python-Methode

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
quelle
1

Ich persönlich bevorzuge Folgendes gegenüber Dekorateuren. Jedem das Seine.

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)
verbraucht
quelle
3
Bitte seien Sie nicht beleidigt, aber diese Lösung erinnert mich ein wenig an den "großen Unternehmensstil" :-) willa.me/2013/11/the-six-most-common-species-of-code.html
JJC
Ja, nicht portable (Stapelmanipulation ist im Allgemeinen ein CPython-Implementierungsdetail, auf das Sie sich in PyPy, Jython, IronPython nicht verlassen können), fragile Stapelmanipulation mit einem halben Dutzend Funktionsaufrufen bei jeder Verwendung ist viel besser als ein einfacher Dekorateur ... </ s>
ShadowRanger
1

Diese Antwort baut auf der Antwort von @claudiu auf.

Ich stellte fest, dass mein Code weniger klar wurde, wenn ich immer den Funktionsnamen voranstellen musste, wenn ich auf eine statische Variable zugreifen wollte.

In meinem Funktionscode würde ich nämlich lieber schreiben:

print(statics.foo)

anstatt

print(my_function_name.foo)

Meine Lösung lautet also:

  1. Fügen Sie staticsder Funktion ein Attribut hinzu
  2. Fügen Sie im Funktionsumfang eine lokale Variable staticsals Alias ​​hinzumy_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))

Anmerkung

Meine Methode verwendet eine Klasse namens Bunch, ein Wörterbuch, das den Zugriff im Attributstil a la JavaScript unterstützt (siehe den Originalartikel darüber, um 2000).

Es kann über installiert werden pip install bunch

Es kann auch so handgeschrieben werden:

class Bunch(dict):
    def __init__(self, **kw):
        dict.__init__(self,kw)
        self.__dict__ = self
Pascal T.
quelle
Hinweis: types.SimpleNamespace(verfügbar seit 3.3) unterstützt dieses Verhalten sofort (und ist in C unter CPython implementiert, sodass es ungefähr so ​​schnell wie möglich ist).
ShadowRanger
0

Aufbauend auf Daniels Antwort (Ergänzungen):

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)

Der Grund, warum ich diesen Teil hinzufügen wollte, ist, dass statische Variablen nicht nur zum Inkrementieren um einen bestimmten Wert verwendet werden, sondern auch prüfen, ob die statische Variable einem Beispiel entspricht, als Beispiel aus dem wirklichen Leben.

Die statische Variable ist weiterhin geschützt und wird nur im Rahmen der Funktion use_foo () verwendet.

In diesem Beispiel funktioniert der Aufruf von foo () genauso wie (in Bezug auf das entsprechende c ++ - Äquivalent):

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

Wenn die Klasse Foo restriktiv als Singleton-Klasse definiert ist, wäre dies ideal. Dies würde es pythonischer machen.

yash
quelle
-1

Sicher, das ist eine alte Frage, aber ich denke, ich könnte ein Update bereitstellen.

Es scheint, dass das Leistungsargument überholt ist. Dieselbe Testsuite scheint ähnliche Ergebnisse für siInt_try und isInt_re2 zu liefern. Natürlich variieren die Ergebnisse, aber dies ist eine Sitzung auf meinem Computer mit Python 3.4.4 auf Kernel 4.3.01 mit Xeon W3550. Ich habe es mehrmals ausgeführt und die Ergebnisse scheinen ähnlich zu sein. Ich habe den globalen regulären Ausdruck in die statische Funktion verschoben, aber der Leistungsunterschied ist vernachlässigbar.

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

Wenn Leistungsprobleme aus dem Weg geräumt sind, scheint Try / Catch den zukunfts- und eckensichersten Code zu produzieren. Wickeln Sie ihn also einfach in die Funktion ein

Keji Li
quelle
1
Was vergleichst du hier überhaupt? Dies scheint ein Kommentar zu anderen Antworten zu sein, aber es ist nicht klar, welche, und es beantwortet die Frage selbst nicht.
ShadowRanger