it-swarm.com.de

Was ist das Python-Äquivalent statischer Variablen in einer Funktion?

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

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

wie kann man das statische Member im Gegensatz zur Klassenebene auf der Funktionsebene implementieren? Und ändert das Platzieren der Funktion in einer Klasse etwas?

511
andrewdotnich

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 und nicht unten angezeigt werden soll, können Sie einen Dekorator erstellen:

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

Dann verwenden Sie den Code wie folgt:

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

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


EDIT (Dank an ony ): Das sieht noch schöner aus:

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
576
Claudiu

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() verwenden, um eine AttributeError-Ausnahme zu vermeiden:

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

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

189
vincent

Man könnte auch überlegen:

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

Argumentation:

  • viel Pythonic (ask for forgiveness not permission)
  • ausnahme (nur einmal geworfen) anstelle von if branch verwenden (think StopIteration exception)
165
ravwojdyla

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 beim ersten Auswerten der Funktion initialisiert, nicht bei jeder Ausführung. Daher können Sie eine Liste oder ein beliebiges anderes veränderliches Objekt zum Speichern statischer Werte verwenden.

37
Jeremy Banks

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/Ausnahme, kein Testatattr, nur Getattr mit einem Standard.

28
Jonathan

Python hat keine statischen Variablen, aber Sie können es vorgeben, 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__ eine Instanz einer Klasse (Objekt) unter ihrem eigenen Namen aufrufbar macht. Daher ruft der Aufruf von foo() oben 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.

23
daniels

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

BEARBEITEN: Beachten Sie, dass die Variable im Gegensatz zur alternativen Antwort try:except AttributeError nach dieser Initialisierung immer für die Codelogik bereit ist. Ich denke, dass die try:except AttributeError-Alternative zu Folgendem weniger DRY ist und/oder einen unangenehmen Fluss hat: 

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 Orten aus aufgerufen wird. Wenn die Funktion stattdessen nur an einer Stelle aufgerufen wird, ist es besser, nonlocal zu verwenden:

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))
    ...
21
Riaz Rizvi

Verwenden Sie eine Generatorfunktion, um einen Iterator zu generieren.

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

Dann benutze es gerne

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

for i in foo_gen(20):
    print i

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

Hier ist die Dokumentation zur Yield-Anweisung .

10
gnud
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 Code von vincent oben würde dies 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, um es herauszufinden) besteht darin, dass Sie mehrere statische Variablen haben und diese auf herkömmliche Weise initialisieren können.

_ counter = 0 
 def foo (): 
 globaler _counter 
 _counter + = 1 
 print 'counter is', _counter 

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

6
Dave

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

  • Jedes Mal, wenn Sie auf die Variable zugreifen möchten, müssen Sie den vollständigen Namen der Funktion ausschreiben. 
  • Fremdcode kann leicht auf die Variable zugreifen und den Wert durcheinander bringen.

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

Eine Alternative wäre ein Muster mit lexikalischen Schließungen, die mit dem Schlüsselwort nonlocal 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 einem Dekorateur zu verkapseln.

6
kdb

Bei allen bisherigen Lösungen wird der Funktion ein Zählerattribut hinzugefügt, normalerweise mit verschachtelter Logik, um die Initialisierung zu handhaben. Dies ist ungeeignet für neuen Code.

In Python 3 ist die richtige Methode die Verwendung einer nonlocal-Anweisung:

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

Siehe PEP 3104 für die Angabe der nonlocal-Anweisung.

4
cbarrick

Etwas lesbarer, aber ausführlicher:

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

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

import types

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

Die Methode idiomatic besteht darin, eine class zu verwenden, die Attribute haben kann. Wenn Sie Instanzen benötigen, um nicht getrennt zu sein, verwenden Sie ein 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änderliches Standardargument), aber dies ist nicht die Methode Pythonic, idiomatic . Benutze einfach eine Klasse.

Oder vielleicht ein Generator, wenn Ihr Nutzungsmuster passt.

2
Teddy

Daraufhin diese Frage , darf ich eine andere Alternative vorstellen, die vielleicht etwas schöner zu verwenden ist und sowohl für Methoden als auch für 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 dir die Verwendung gefällt, 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
2
Claudiu

Eine weitere (nicht empfohlene!) Variante des aufrufbaren Objekts wie https://stackoverflow.com/a/279598/916373 , wenn Sie nichts dagegen haben, eine funky Call-Signatur zu verwenden, wäre dies 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
1
lost

Diese Antwort zeigt, dass setdefault die Frage des OPs, wie statischelocal- Variablen erstellt werden, nicht wirklich erfüllt.

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

Es funktioniert so lange wie Fn. wird jedem Variablennamen vorangestellt. Wenn Sie sie wie folgt entfernen:

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

es gibt keine Fehler, aber Zähler ist immer 0 und sagt mir, dass Vars (Fn) NICHT auf lokale Variablen zugreift, sondern eher auf eine globale, wahrscheinlich eine Dekorations- oder Attributablage.

Wenn dies funktioniert hätte, wäre es meine bevorzugte Lösung gewesen. Da dies jedoch nicht der Fall ist, neige ich dazu, mit einer vollständig gekapselten Klassendefinition solche statischen Variablen zu erstellen. 

IMHO ist das am einfachsten. Natürlich hängt es davon ab, ob Sie mit funktionalen Codierungsstilen OOP besser vertraut sind.

1
user4830534

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

Anstatt eine Funktion mit einer statischen lokalen Variablen zu erstellen, können Sie immer ein so genanntes "Funktionsobjekt" erstellen und ihm eine Standardmitgliedsvariable (nicht statisch) zuweisen.

Da Sie ein Beispiel für C++ geschrieben haben, werde ich zuerst erklären, was ein "Funktionsobjekt" in C++ ist. Ein "Funktionsobjekt" ist einfach eine Klasse mit einer überladenen operator(). Instanzen der Klasse verhalten sich wie Funktionen. Beispielsweise können Sie int x = square(5); schreiben, auch wenn square ein Objekt ist (mit überladener operator()) und nicht technisch keine "Funktion" ist. Sie können einem Funktionsobjekt alle Funktionen zuweisen, die Sie einem Klassenobjekt zuweisen 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 operator() auch überladen, außer dass die Methode stattdessen __call__ heißt.

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 die Konsole gedruckte Ausgabe lautet:

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

Wenn Sie möchten, dass Ihre Funktion Eingabeargumente verwendet, können Sie diese auch zu __call__ hinzufügen:

# 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
1
IdleCustard

Eine globale Deklaration stellt diese Funktionalität bereit. Im folgenden Beispiel (Python 3.5 oder höher für die Verwendung des "f") wird die Variable counter außerhalb der Funktion definiert. Die Definition als global in der Funktion bedeutet, dass die "globale" Version außerhalb der Funktion für die Funktion verfügbar sein soll. Jedes Mal, wenn die Funktion ausgeführt wird, ändert sie den Wert außerhalb der Funktion und behält diesen über die Funktion hinaus.

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"
1
Richard Merren

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

Es scheint, dass das Leistungsargument veraltet ist. Die gleiche Testsuite scheint ähnliche Ergebnisse für siInt_try und isInt_re2 ..__ zu ergeben. 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 Regex in die Funktion static verschoben, aber der Leistungsunterschied ist vernachlässigbar. 

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

Bei Performanceproblemen scheint es so, als würde Try/Catch den zukunftssichersten und sichersten Code produzieren, also vielleicht einfach die Funktion einpacken

1
Keji Li

Ich persönlich bevorzuge 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)
1
spenthil

Seeleneinfluss n + = 1 

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

Diese Antwort baut auf der Antwort von @claudiu auf.

Ich stellte fest, dass mein Code weniger klar wurde, wenn ich immer mit .__ den Funktionsnamen voranstellen wollte, wenn ich auf eine statische Variable zugreifen möchte.

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

print(statics.foo)

anstatt

print(my_function_name.foo)

Meine Lösung ist also:

  1. fügen Sie der Funktion ein statics-Attribut hinzu
  2. fügen Sie im Funktionsbereich eine lokale Variable statics als Alias ​​zu my_function.statics hinzu.
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 mit dem Namen Bunch, ein Wörterbuch, das den Zugriff auf Attribute-Stil unterstützt, a la JavaScript (siehe Originalartikel darüber, um 2000).

Es kann über pip install bunch installiert werden.

Es kann auch so geschrieben werden:

class Bunch(dict):
    def __init__(self, **kw):
        dict.__init__(self,kw)
        self.__dict__ = self
0
Pascal T.

Dies ist jedoch ein ruhiger alter Beitrag, da ich ein anderes idiomatisches Ziel habe:

In einer Funktion möchte ich eine Variable nur einmal mit einem berechneten Wert initialisieren, der etwas kostspielig sein kann.

Wie ich es liebe, schön zu schreiben und ein alter Programmierer im C-Stil zu sein. Ich habe versucht, eine Makroschrift zu definieren:

def  Foo () :
    StaticVar( Foo, ‘Var’, CalculateStatic())
    StaticVar( Foo, ‘Step’, CalculateStep())
    Foo.Var += Foo.Step
    print(‘Value of Var : ‘, Foo.Var)

Dann schrieb ich "StaticVar" wie folgt:

def StaticVar(Cls, Var, StaticVal) :
    if not hasattr(Cls, Var) :
        setattr(Cls, Var, StaticVal)

Noch schöner in Python:

def StaticVars(Cls, **Vars) :
    for Var, StaticVal in Vars.items() :
        if not hasattr(Cls, Var) :
            setattr(Cls, Var, StaticVal)

def  Foo () :
    StaticVars( Foo, Var = CalculateStatic(),Step= CalculateStep()))
    Foo.Var += Foo. Step
    print(‘Value of Var : ‘, Foo.Var)

Dies ist eine nette Art zu schreiben, aber mein Ziel (nur ein Aufruf der Initialisierungsfunktionen) wird nicht erreicht (fügen Sie einfach einen Ausdruck in die Initialisierungsfunktion ein)! Fakt ist, dass bei einem Funktionsaufruf der Parameterwert ausgewertet wird, bevor die Funktion aufgerufen wird.

def CalculateStatic() :
    print("Costly Initialization")
    return 0

Um mein Ziel zu erreichen, schreibe ich lieber:

def  Foo () :
    if not hasattr(Foo, ‘Var’) :
        setattr ( Foo, ‘Var’, CalculateStatic())
        setattr ( Foo, ‘Step’, CalculateStep())

    Foo.Var += Foo. Step
    print(‘Value of Var : ‘, Foo.Var)

Wenn Python 'Marcro-Vorverarbeitung' gehabt hätte, wäre es schöner ..

0
Farazdak

Auf Daniels Antwort aufbauen (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, statische Variablen werden nicht nur für das Inkrementieren um einen bestimmten Wert verwendet, sondern auch überprüft, ob die statische Var einem bestimmten Wert entspricht, als ein reales Beispiel.

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 () genau 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 mehr Pythonic machen.

0
yash