Jaki jest odpowiednik zmiennych statycznych w Pythonie wewnątrz funkcji?

630

Jaki jest idiomatyczny odpowiednik Pythona w tym kodzie C / C ++?

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

w szczególności, w jaki sposób implementuje się element statyczny na poziomie funkcji, a nie na poziomie klasy? I czy umieszczenie funkcji w klasie coś zmienia?

andrewdotnich
źródło
22
Obawiam się, że NIE ma odpowiednika. Nawet jeśli wykonasz hak dekoratora za pomocą atrybutów funkcji, będziesz w stanie uzyskać dostęp do zmiennej na zewnątrz, co trochę niestety pokonuje ten punkt. Co więcej, będziesz musiał zaszyfrować nazwę funkcji w funkcji, co jest bardzo denerwujące. Sugerowałbym użycie globalnych zmiennych klasy lub modułu zamiast konwencjonalnego _prefiksu.
lpapp,
8
Dla programistów spoza C [ statoverflow.com/questions/5033627/… zmienna statyczna wewnątrz funkcji jest widoczna tylko w zakresie tej funkcji, ale jej żywotność to całe życie programu i jest ona inicjowana tylko raz). Zasadniczo trwała zmienna licznika lub pamięci, która żyje między wywołaniami funkcji.
smci
2
@lpapp: istnieje rodzaj członka klasy . Masz rację, że nie możemy zapobiec wyświetlaniu go lub zmienianiu przez inny kod.
smci

Odpowiedzi:

681

Trochę odwrócone, ale to powinno działać:

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

Jeśli chcesz, aby kod inicjalizacji licznika był na górze zamiast na dole, możesz utworzyć dekorator:

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

Następnie użyj kodu w następujący sposób:

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

foo.Niestety nadal będzie wymagać użycia prefiksu.

( Źródło : @ony )

Claudiu
źródło
23
jest tylko jedna instancja foo - ta jedna funkcja. wszystkie wywołania mają dostęp do tej samej zmiennej.
Claudiu,
121
Przepraszam za wykopanie tego, ale wolę umieścić if "counter" not in foo.__dict__: foo.counter = 0jako pierwsze wiersze foo(). Pomogłoby to uniknąć kodu poza funkcją. Nie jestem jednak pewien, czy było to możliwe w 2008 roku. PS Znalazłem tę odpowiedź, szukając możliwości utworzenia statycznych zmiennych funkcyjnych, więc ten wątek jest nadal „żywy” :)
binaryLV
8
@binaryLV: Prawdopodobnie wolałbym to od pierwszego podejścia. Problem z pierwszym podejściem polega na tym, że nie jest to od razu oczywiste fooi foo.counter = są one ściśle powiązane. jednak ostatecznie wolę podejście do dekoratora, ponieważ nie ma możliwości, aby dekorator nie został powołany i jest semantycznie bardziej oczywiste, co robi ( @static_var("counter", 0)jest łatwiejsze i ma więcej sensu dla moich oczu niż if "counter" not in foo.__dict__: foo.counter = 0, szczególnie w tym ostatnim, którego musisz użyć nazwa funkcji (dwukrotnie), która może ulec zmianie).
Claudiu
6
@lpapp: Zależy to od tego, jaki jest punkt zmiennych statycznych. Zawsze myślałem, że będzie to ta sama wartość w wielu wywołaniach funkcji, co to spełnia. Nigdy nie myślałem, że chodzi o ukrywanie zmiennych, co nie jest, jak powiedziałeś.
Claudiu
3
def foo(): if not hasattr(foo,"counter"): foo.counter=0 foo.counter += 1
Erik Aronesty,
221

Możesz dodać atrybuty do funkcji i użyć jej jako zmiennej statycznej.

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

# attribute must be initialized
myfunc.counter = 0

Alternatywnie, jeśli nie chcesz ustawiać zmiennej poza funkcją, możesz użyć, hasattr()aby uniknąć AttributeErrorwyjątku:

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

W każdym razie zmienne statyczne są raczej rzadkie i powinieneś znaleźć lepsze miejsce dla tej zmiennej, najprawdopodobniej wewnątrz klasy.

Vincent
źródło
6
Dlaczego nie spróbować zamiast instrukcji if?
ravwojdyla
12
try: myfunc.counter += 1; except AttributeError: myfunc.counter = 1powinien zrobić to samo, używając zamiast tego wyjątków.
sleblanc
Wyjątków należy używać w wyjątkowych sytuacjach, tj. Takich, których programista się spodziewa, że ​​się nie wydarzy, takich jak plik wejściowy, który pomyślnie otworzył, nagle niedostępny. Jest to oczekiwana sytuacja, a wyrażenie if ma większy sens.
Hack Saw
11
@Hack_Saw: Cóż, to jest Python (lepiej poprosić o wybaczenie niż pozwolenie). Jest to faktycznie zalecane w technikach optymalizacji Pythona, ponieważ pozwala to zaoszczędzić na kosztach if (choć nie zalecam przedwczesnej optymalizacji). Twoja zasada dotycząca wyjątkowych przypadków: 1. Niepowodzenie JEST tutaj w pewnym sensie wyjątkowym przypadkiem. To się zdarza tylko raz. 2. Myślę, że ta reguła dotyczy używania (tj. Zgłaszania) wyjątków. Wyłapuje to wyjątek dla czegoś, co powinno działać, ale ma plan tworzenia kopii zapasowych, co jest powszechne w większości języków.
leewz
@leewangzhong: Czy dołączenie bloku, który nie podnosi wyjątku w ramach, powoduje trydodatkowe koszty? Po prostu ciekawy.
trss
201

Można również rozważyć:

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

Rozumowanie:

  • dużo pythonowych („prośba o wybaczenie, a nie pozwolenie”)
  • użyj wyjątku (wyrzuconego tylko raz) zamiast ifrozgałęzienia (pomyśl wyjątek StopIteration )
ravwojdyla
źródło
11
Nie pisałem długo w Pythonie, ale spełnia to jedną z ukrytych cech języka: jeśli nie jest to (dość) łatwe, robisz to źle .
ZX9,
Nie działał natychmiast z metodami klasowymi, „self.foo.counter = 1” ponownie podnosi AttributeError.
villasv
16
Jest to poprawne rozwiązanie i powinna to być akceptowana odpowiedź, ponieważ kod inicjujący zostanie uruchomiony, gdy funkcja zostanie wywołana, a nie podczas wykonywania modułu lub importowania czegoś z niego, co ma miejsce, jeśli używasz metody dekoratora z aktualnie akceptowana odpowiedź. Zobacz Wykonywanie funkcji dekoratora Python . Jeśli masz ogromny moduł biblioteki, zostanie uruchomiony każdy dekorator, łącznie z funkcjami, których nie importujesz.
Nils Lindemann,
3
Prostsze podejście: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCuriousOne
5
@MANU Używanie hasattr()do tego nie jest prostsze, a także mniej wydajne.
moooeeeep
48

Inne odpowiedzi pokazały, jak należy to zrobić. Oto sposób, w jaki nie powinieneś:

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

Wartości domyślne są inicjowane tylko podczas pierwszej oceny funkcji, a nie za każdym razem, gdy jest wykonywana, dzięki czemu do przechowywania wartości statycznych można użyć listy lub dowolnego obiektu zmiennego.

Jeremy Banks
źródło
Próbowałem tego, ale z jakiegoś powodu parametr funkcji zainicjował się na 140, a nie na 0. Dlaczego to miałoby być?
andrewdotnich
1
@ Bouvard W przypadku funkcji rekurencyjnych, które wymagają zmiennej statycznej, jest to jedyna funkcja, która naprawdę dobrze czyta.
lifebalance
1
Próbowałem kilku podejść i chciałbym, aby ten został zaakceptowany jako pytoniczny. Z pewnymi znaczącymi nazwami, takimi jak def foo(arg1, arg2, _localstorage=DataClass(counter=0))ja, uważam to za dobrze czytelne. Kolejną zaletą jest łatwa zmiana nazwy funkcji.
VPfB
2
Dlaczego mówisz, że nie powinieneś tego robić w ten sposób? Wygląda dla mnie całkowicie rozsądnie!
Konstantin,
1
@VPfB: Do ogólnego przechowywania można użyć types.SimpleNamespace, dzięki czemu def foo(arg1, arg2, _staticstorage=types.SimpleNamespace(counter=0)):nie trzeba definiować specjalnej klasy.
ShadowRanger
43

Wiele osób już sugerowało przetestowanie „hasattr”, ale istnieje prostsza odpowiedź:

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

Bez prób / z wyjątkiem, bez testowania hasattr, po prostu getattr z domyślnym.

Jonathan
źródło
2
zwracaj uwagę na trzeci parm getattr, gdy umieszczasz tam func, na przykład: def func (): def foo (): return 1112 func.counter = getattr (func, 'counter', foo ()) + 1, gdy wywołujesz func, foo zawsze będzie nazywane!
Codefor dla
1
Tylko połączenie z getattr za każdym razem, gdy wywoływany jest func. To dobrze, jeśli wydajność nie jest problemem, jeśli spróbujesz / z wyjątkiem, wygrasz ręce.
Mark Lawrence
2
@MarkLawrence: W rzeczywistości, przynajmniej w mojej instalacji Windows x64 3.8.0, różnica w wydajności między tą odpowiedzią a równoważnym try/ exceptopartym na ravwojdyla podejściem jest dość bez znaczenia. Prosty ipython %%timeitmikrobenchmark dał koszt try/ exceptna 255 ns za połączenie, w porównaniu do 263 ns dla getattrrozwiązania bazowego. Tak, try/ exceptjest szybszy, ale nie do końca „wygrywa rozdanie”; to drobna mikrooptymalizacja. Napisz dowolny kod, który wydaje się wyraźniejszy, nie martw się o takie trywialne różnice wydajności.
ShadowRanger
@ShadowRanger dzięki za przeprowadzenie testu porównawczego. Zastanawiam się nad oświadczeniem MarkLawrence od 2 lat i cieszę się, że przeprowadziłeś badania. Zdecydowanie zgadzam się z twoim ostatnim zdaniem - „pisz kod, który wydaje się jaśniejszy” - właśnie dlatego napisałem tę odpowiedź.
Jonathan
28

Oto w pełni zamknięta wersja, która nie wymaga zewnętrznego wywołania inicjalizacji:

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

W Pythonie funkcje są obiektami i możemy po prostu dodawać do nich zmienne składowe lub małpować je za pomocą specjalnego atrybutu __dict__. Wbudowany vars()zwraca specjalny atrybut__dict__ .

EDYCJA: Uwaga, w przeciwieństwie do alternatywy try:except AttributeError odpowiedzi, przy takim podejściu zmienna zawsze będzie gotowa na logikę kodu po inicjalizacji. Myślę, że try:except AttributeErroralternatywą dla poniższych będzie mniej SUSZENIA i / lub niezręczny przepływ:

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

EDYCJA 2: Polecam powyższe podejście tylko wtedy, gdy funkcja będzie wywoływana z wielu lokalizacji. Jeśli zamiast tego funkcja jest wywoływana tylko w jednym miejscu, lepiej użyć 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
źródło
2
Jedynym problemem jest to, że tak naprawdę nie jest wcale schludne, a kiedy chcesz użyć tego wzoru, musisz wyciąć i wkleić kod ... stąd moje użycie dekoratora
Claudiu
2
prawdopodobnie powinien użyć czegoś takiegotry: mystaticfun.counter+=10 except AttributeError: mystaticfun.counter=0
endolith
2
Proszę używać X not in Yzamiast not X in Y(lub doradzać używanie, jeśli po prostu go używałeś w celu bardziej podobnego porównania między tym a hasattr)
Nick T
co powiesz na: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCuriousOne
nie jest to idealne, ponieważ klauzula if dodaje niepotrzebne zagnieżdżanie, w tej sytuacji wolę setdefault
Riaz Rizvi
27

Python nie ma zmiennych statycznych, ale można go sfałszować, definiując obiekt klasy na żądanie, a następnie używając go jako funkcji. Zobacz także tę odpowiedź .

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

Zauważ, że __call__umożliwia wywołanie klasy (obiektu) według własnej nazwy. Dlatego wywołanie foo()powyższego wywołuje __call__metodę klasy . Z dokumentacji :

Wystąpienia dowolnych klas można wywołać, definiując __call__()metodę w ich klasie.

Daniels
źródło
15
Funkcje są już obiektami, więc dodaje to niepotrzebną warstwę.
Dasich
Zobacz tę SO odpowiedź na długą opinię, że to naprawdę dobry pomysł. stackoverflow.com/questions/460586 . Zgadzam się, że uczynienie każdej takiej klasy singletonem, być może takim jak stackoverflow.com/questions/6760685 , również byłoby dobrym pomysłem. Nie wiem, co @ S.Lott rozumie przez „... przenieś licznik do definicji klasy ...”, ponieważ wygląda na to, że jest już dla mnie zmienną klasą.
Reb.Cabin
1
Z moich badań wynika, że ​​ta technika klasowa wydaje się najbardziej „pytoniczna” spośród podejść przedstawionych na tej stronie i wykorzystuje najmniejszą sztuczkę. W związku z tym planuję przyjąć go jako zastępcę zmiennych typu C-static w funkcjach, jako nowego programistę Python.
Gabriel Staples
1
Co się stanie, jeśli chcę foo1 = Foo () i foo2 = Foo ()?
Mark Lawrence
@MarkLawrence Następnie masz dwa różne wystąpienia klasy na żądanie, każda z własnym licznikiem. Tego dokładnie należy się spodziewać, jeśli nie korzystasz z instancji fooudostępnianej jako singleton.
Aaron McMillin
14

Użyj funkcji generatora, aby wygenerować iterator.

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

Następnie użyj go jak

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

Jeśli chcesz górny limit:

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

Jeśli iterator zakończy się (jak w powyższym przykładzie), możesz również zapętlić go bezpośrednio, np

for i in foo_gen(20):
    print i

Oczywiście w tych prostych przypadkach lepiej użyć xrange :)

Oto dokumentacja dotycząca deklaracji dochodu .

gnud
źródło
11

Inne rozwiązania dołączają do funkcji atrybut licznika, zwykle ze skomplikowaną logiką do obsługi inicjalizacji. Jest to nieodpowiednie dla nowego kodu.

W Pythonie 3 właściwym sposobem jest użycie nonlocalinstrukcji:

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

Zobacz PEP 3104 do specyfikacji nonlocaloświadczeniu.

Jeśli licznik ma być prywatny dla modułu, należy go _counterzamiast tego nazwać .

cbarrick
źródło
Nawet przed Pythonem 3 zawsze można to zrobić za pomocą global counterinstrukcji zamiast nonlocal counter( nonlocalpozwala tylko pisać do stanu zamknięcia w funkcji zagnieżdżonej). Powodem, dla którego ludzie dołączają atrybut do funkcji jest unikanie zanieczyszczania globalnej przestrzeni nazw dla stanu, który jest specyficzny dla funkcji, więc nie musisz robić nawet trudniejszych rzeczy, gdy dwie funkcje potrzebują niezależnych counters. To rozwiązanie nie jest skalowane; atrybuty na funkcji do. Odpowiedź kdb brzmi, jak nonlocalmoże pomóc, ale dodaje złożoności.
ShadowRanger
Eh, myślę, że złożoność funkcji fabrycznej lub dekoratora jest nadmierna, chyba że często to robisz, a w takim przypadku projekt jest już trochę śmierdzący. Aby jednorazowo, wystarczy dodać licznik nielokalny i gotowe. Dodałem trochę do odpowiedzi na temat konwencji nazewnictwa. Również powodem polecam nonlocalciągu globaljest dokładnie tak, jak podkreślić - to działa w ściśle więcej okolicznościach.
cbarrick
8

Używanie atrybutu funkcji jako zmiennej statycznej ma pewne potencjalne wady:

  • Za każdym razem, gdy chcesz uzyskać dostęp do zmiennej, musisz wpisać pełną nazwę funkcji.
  • Kod zewnętrzny może łatwo uzyskać dostęp do zmiennej i zadzierać z wartością.

Idiomatyczny python dla drugiego problemu prawdopodobnie nazwałby zmienną wiodącym znakiem podkreślenia, aby zasygnalizować, że nie ma do niej dostępu, jednocześnie utrzymując ją dostępną po fakcie.

Alternatywą może być wzorzec wykorzystujący zamknięcia leksykalne, które są obsługiwane przez nonlocalsłowo kluczowe w python 3.

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

Niestety nie wiem, jak zawrzeć to rozwiązanie w dekoratorze.

kdb
źródło
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)

Podobnie jak powyższy kod Vincenta, byłby on używany jako dekorator funkcji, a do zmiennych statycznych należy uzyskać dostęp z nazwą funkcji jako prefiksem. Zaletą tego kodu (choć trzeba przyznać, że każdy może być na tyle inteligentny, by to rozgryźć) jest to, że można mieć wiele zmiennych statycznych i inicjować je w bardziej konwencjonalny sposób.

Giorgian Borca-Tasciuc
źródło
7

Trochę bardziej czytelny, ale bardziej szczegółowy (Zen of Python: wyraźne jest lepsze niż niejawne):

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

Zobacz tutaj wyjaśnienie, jak to działa.

warvariuc
źródło
czy możesz wyjaśnić, dlaczego ten kod działa? Drugi foo()powinien ponownie zainicjować słownik do wartości określonej w definicji funkcji (tak więc klawisz licznika ma wartość 0). Dlaczego tak nie jest?
raffaem,
3
@raffamaiden: Domyślne argumenty są oceniane tylko raz, gdy funkcja jest zdefiniowana, a nie za każdym razem, gdy funkcja jest wywoływana.
Daniel K.
6
_counter = 0
def foo ():
   globalny licznik
   _counter + = 1
   wypisz „counter is”, _counter

Python zwyczajowo używa podkreślników do wskazania zmiennych prywatnych. Jedynym powodem w C, aby zadeklarować zmienną statyczną wewnątrz funkcji, jest ukrycie jej poza funkcją, co nie jest tak naprawdę idiomatycznym Pythonem.

Dave
źródło
4

Po wypróbowaniu kilku podejść wykorzystuję ulepszoną wersję odpowiedzi @ warvariuc:

import types

def func(_static=types.SimpleNamespace(counter=0)):
    _static.counter += 1
    print(_static.counter)
VPfB
źródło
3

Idiomatyczne sposobem jest użycie klasy , które mogą mieć atrybuty. Jeśli potrzebujesz, aby instancje nie były oddzielne, użyj singletonu.

Istnieje wiele sposobów na fałszowanie lub modyfikowanie „statycznych” zmiennych w Pythonie (jednym z dotychczas nie wspomnianych jest modyfikowalny argument domyślny), ale nie jest to Pythoniczny, idiomatyczny sposób. Po prostu użyj klasy.

Lub ewentualnie generator, jeśli twój wzorzec użytkowania pasuje.

Miś
źródło
W przypadku autonomicznych funkcji rekurencyjnych defaultargument jest najbardziej elegancki.
lifebalance
3

Poddane temu pytaniu , czy mogę przedstawić inną alternatywę, która może być nieco przyjemniejsza w użyciu i będzie wyglądać tak samo dla metod i funkcji:

@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    

Jeśli podoba Ci się użycie, oto implementacja:

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
źródło
3

Kolejnym (niezalecanym!) Zwrotem na wywoływalnym obiekcie, takim jak https://stackoverflow.com/a/279598/916373 , jeśli nie masz nic przeciwko użyciu funky podpisu połączenia, byłoby zrobić

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
Stracony
źródło
3

Zamiast tworzyć funkcję posiadającą statyczną zmienną lokalną, zawsze możesz utworzyć tak zwany „obiekt funkcji” i nadać jej standardową (niestatyczną) zmienną składową.

Ponieważ podałeś przykład napisany w C ++, najpierw wyjaśnię, czym jest „obiekt funkcji” w C ++. „Obiekt funkcji” to po prostu dowolna klasa z przeciążeniem operator(). Instancje klasy będą zachowywać się jak funkcje. Na przykład możesz pisać, int x = square(5);nawet jeśli squarejest obiektem (z przeciążeniem operator()) i technicznie nie jest „funkcją”. Możesz nadać obiektowi funkcyjnemu dowolną funkcję, którą możesz nadać obiektowi klasy.

# 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;

W Pythonie możemy również przeciążać, operator()ale zamiast tego nazwa metody __call__:

Oto definicja klasy:

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

Oto przykład zastosowanej klasy:

from foo import foo

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

Dane wyjściowe drukowane na konsoli to:

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

Jeśli chcesz, aby twoja funkcja pobierała argumenty wejściowe, możesz również dodać je do __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
źródło
3

Soulution n + = 1

def foo():
  foo.__dict__.setdefault('count', 0)
  foo.count += 1
  return foo.count
Feca
źródło
3

Globalna deklaracja zapewnia tę funkcjonalność. W poniższym przykładzie (python 3.5 lub nowszy, aby użyć „f”) zmienna licznika jest zdefiniowana poza funkcją. Zdefiniowanie go jako globalnego w funkcji oznacza, że ​​wersja „globalna” poza funkcją powinna zostać udostępniona tej funkcji. Dlatego przy każdym uruchomieniu funkcja modyfikuje wartość poza funkcją, zachowując ją poza funkcją.

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
źródło
Działa to w ten sam sposób, jeśli jest używane poprawnie. Różnica w stosunku do kodu c polega na tym, że w przykładzie c OP, zmienna licznika mogła zostać dotknięta tylko przez funkcję. Zmienna globalna w pythonie może być używana lub zmieniana w dowolnym miejscu skryptu
MortenSickel
2

Zmienna statyczna w metodzie Python

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
źródło
1

Osobiście wolę te niż dekoratorów. Do każdego własnego.

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)
spędziłam czas
źródło
3
Nie obrażaj się, ale to rozwiązanie przypomina mi trochę „styl dużej firmy” :-) willa.me/2013/11/the-six-most-common-species-of-code.html
JJC
Tak, używanie nieprzenośnych (ogólnie manipulacja stosami jest szczegółem implementacji CPython, a nie czymś, na czym można polegać w PyPy, Jython, IronPython, what-have-you), delikatna manipulacja stosem, z każdym użyciem pół tuzina wywołań funkcji jest znacznie lepszy niż zwykły dekorator ... </s>
ShadowRanger
1

Ta odpowiedź opiera się na odpowiedzi @claudiu.

Odkryłem, że mój kod jest coraz mniej wyraźny, gdy zawsze muszę wstawić nazwę funkcji, ilekroć zamierzam uzyskać dostęp do zmiennej statycznej.

Mianowicie w moim kodzie funkcji wolałbym pisać:

print(statics.foo)

zamiast

print(my_function_name.foo)

Więc moim rozwiązaniem jest:

  1. dodaj staticsatrybut do funkcji
  2. w zakresie funkcji dodaj zmienną lokalną staticsjako alias domy_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))

Uwaga

Moja metoda używa klasy o nazwie Bunch, która jest słownikiem obsługującym dostęp w stylu atrybutów, a la JavaScript (zobacz oryginalny artykuł na ten temat, około 2000 r.)

Można go zainstalować za pośrednictwem pip install bunch

Można go również napisać ręcznie:

class Bunch(dict):
    def __init__(self, **kw):
        dict.__init__(self,kw)
        self.__dict__ = self
Pascal T.
źródło
Uwaga: types.SimpleNamespace(dostępne od 3.3) obsługuje to zachowanie od razu (i jest zaimplementowane w C na CPython, więc jest tak szybkie, jak to tylko możliwe).
ShadowRanger
0

Opierając się na odpowiedzi Daniela (uzupełnienia):

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)

Powodem, dla którego chciałem dodać tę część, jest to, że zmienne statyczne są używane nie tylko do zwiększania o jakąś wartość, ale także sprawdzają, czy var statyczny jest równy jakiejś wartości, jako przykład z życia.

Zmienna statyczna jest nadal chroniona i używana tylko w zakresie funkcji use_foo ()

W tym przykładzie wywołanie funkcji foo () działa dokładnie tak, jak (w odniesieniu do odpowiedniego odpowiednika c ++):

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

jeśli klasa Foo jest zdefiniowana jako klasa singleton, byłoby to idealne. To by uczyniło go bardziej pytonicznym.

yash
źródło
-1

Jasne, że to stare pytanie, ale myślę, że mogę trochę zaktualizować.

Wygląda na to, że argument dotyczący wydajności jest przestarzały. Ten sam zestaw testów wydaje się dawać podobne wyniki dla siInt_try i isInt_re2. Oczywiście wyniki są różne, ale jest to jedna sesja na moim komputerze z Pythonem 3.4.4 na jądrze 4.3.01 z Xeon W3550. Uruchomiłem go kilka razy, a wyniki wydają się podobne. Przeniosłem globalny regex na funkcję static, ale różnica w wydajności jest znikoma.

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

Z problemem wydajnościowym wydaje się, że try / catch stworzy kod najbardziej odporny na przyszłe i narożniki, więc może po prostu zawiń go w funkcję

Keji Li
źródło
1
Co tu w ogóle porównujesz? To wydaje się być komentarzem do innych odpowiedzi, ale nie jest jasne, które z nich, i nie odpowiada na samo pytanie.
ShadowRanger