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.

3voto

Claudiu Punkte 215027

Angeregt durch この質問 Ich möchte Ihnen eine andere Alternative vorstellen, die vielleicht etwas angenehmer zu benutzen ist und für beide Methoden und Funktionen gleich aussehen wird:

@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 Sie die Verwendung mögen, hier ist die Umsetzung:

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

3voto

IdleCustard Punkte 307

Anstatt eine Funktion mit einer statischen lokalen Variablen zu erstellen, können Sie auch ein sogenanntes "Funktionsobjekt" erstellen und ihm eine (nicht statische) Standardvariable geben.

Da Sie ein Beispiel gegeben haben, das in C++ geschrieben wurde, werde ich zunächst erklären, was ein "Funktionsobjekt" in C++ ist. Ein "Funktionsobjekt" ist einfach jede Klasse mit einer überladenen operator() . Instanzen der Klasse verhalten sich wie Funktionen. Sie können zum Beispiel schreiben int x = square(5); même si square ist ein Objekt (mit überladenen operator() ) und ist technisch gesehen keine "Funktion". Sie können einem Funktionsobjekt alle Eigenschaften geben, die Sie auch einem Klassenobjekt geben können.

# 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() mit dem Unterschied, dass die Methode stattdessen den Namen __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 Verwendung der Klasse:

from foo import foo

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

Die Ausgabe auf der Konsole lautet:

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

Wenn Sie möchten, dass Ihre Funktion Eingabeargumente akzeptiert, können Sie diese zu __call__ auch:

# 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

2voto

Pascal T. Punkte 3572

Diese Antwort baut auf der Antwort von @claudiu auf.

Ich habe festgestellt, dass mein Code immer unübersichtlicher wurde, wenn ich immer immer den Funktionsnamen vorangestellt habe, wenn ich auf eine statische Variable zugreifen wollte.

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

print(statics.foo)

anstelle von

print(my_function_name.foo)

Meine Lösung lautet also: :

  1. einfügen statics Attribut an die Funktion
  2. fügen Sie im Funktionsbereich eine lokale Variable statics als Alias für 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))


Bemerkung

Meine Methode verwendet eine Klasse namens Bunch das ein Wörterbuch ist, das Attribut-Zugriff unterstützt, a la JavaScript (siehe die Originalartikel darüber, um 2000)

Es kann installiert werden über pip install bunch

Sie kann auch handschriftlich verfasst werden:

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

0 Stimmen

Nota: types.SimpleNamespace (verfügbar seit 3.3) unterstützt dieses Verhalten von Haus aus (und ist in C auf CPython implementiert, so dass es so schnell wie möglich ist).

1voto

spenthil Punkte 3207

Ich persönlich bevorzuge gegenüber Dekorateuren die folgenden. 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)

3 Stimmen

Bitte nehmen Sie es mir nicht übel, aber diese Lösung erinnert mich ein wenig an den "Großkonzern-Stil" :-) willa.me/2013/11/die-sechs-meist-verbreiteten-arten-von-code.html

0 Stimmen

Ja, mit nicht-portable (Stack-Manipulation im Allgemeinen ist ein CPython-Implementierung Detail, nicht etwas, das Sie auf in PyPy, Jython, IronPython, was-haben-Sie), fragile Stack-Manipulation, mit einem halben Dutzend Funktionsaufrufe bei jeder Verwendung verlassen können ist Weg besser als ein einfacher Dekorateur...</s>

0voto

yash Punkte 9

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 für die Inkrementierung um einen Wert verwendet werden, sondern auch prüfen, ob die statische Variable gleich einem Wert ist, wie ein Beispiel aus der Praxis zeigt.

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 das ideal. Das würde es pythonischer machen.

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