Soru Python'da 'yerel statik' değişkenini simüle etme


Aşağıdaki kodu göz önünde bulundurun:

def CalcSomething(a):
    if CalcSomething._cache.has_key(a):
      return CalcSomething._cache[a]
    CalcSomething._cache[a] = ReallyCalc(a)
    return CalcSomething._cache[a] 

CalcSomething._cache = { }

Bu, python'da bir 'yerel statik' değişkeni taklit etmenin en kolay yoludur.
Beni rahatsız eden şey, CalcSomething._cache işlevinin tanımı dışında belirtildi, ancak alternatif şöyle bir şey olurdu:

if not hasattr(CalcSomething, "_cache"):  
    setattr(CalcSomething, "_cache", { } )  

Fonksiyonun içinde gerçekten hantal olan tanımı.

Daha zarif bir yolu var mı?

[DÜZENLE]
Sadece açıklığa kavuşturmak için, bu soru, yukarıdaki örnek önerebileceği gibi, yerel işlev önbellekleriyle ilgili değildir. Burada 'statik yerel' kullanışlı olabilecek başka bir kısa örnek:

def ParseString(s):
    return ParseString._parser.parse(s)  
# Create a Parser object once, which will be used for all parsings.
# Assuming a Parser object is heave on resources, for the sake of this example.
ParseString._parser = Parser() 

25
2018-01-20 09:32


Menşei


BTW, sorunuzu farklı şekilde ifade etmek isteyebilirsiniz. Yerel bir statik değişken aramıyorsunuz, ancak işlevinize not almanın bir yolu için. - Torsten Marek
ikinci örneğiniz ilkinden daha kötü. Strptime, yeni bir datetime nesnesi yaratan bir sınıf yöntemi olduğundan, datetime.datetime.strptime (dts, "% m-% d-% Y% H:% M:% S") döndürebilirsiniz. Gerçekten oluşturulacak bir datetime nesnesine gerek yoktur. - nosklo
İkinci örnek hakkında haklısınız. Bunu değiştirdi. - Paul Oyster


Cevaplar:


Kalıcı bir nesneye dönüştürün (çünkü gerçekte olduğu gibi).

class CalcSomething(object):
    def __init__(self):
        self._cache = {}
    def __call__(self, a):
        if a not in self._cache: 
            self._cache[a] = self.reallyCalc(a)
        return self._cache[a]
    def reallyCalc(self, a):
        return # a real answer
calcSomething = CalcSomething()

Şimdi kullanabilirsiniz calcSomething Bir işlevmiş gibi. Ama düzenli ve kendi kendine yeten kalır.


49
2018-01-20 10:57



+1 - önbelleğe alma uygulamasını gizleyen başka bir çözüm. Bu yaklaşım aklımdan geçmedi, ama benim basit dekoratörümden daha güçlü görünüyor. - Abgan
+1: sınıf kullanarak olduğu çözüm. - nosklo
Özel heceleme yöntemlerini yöntem olarak kullanmak isteyenler için uygulamak zorunda kalacaksınız. almak doğru yol (bu konuda daha fazla bilgi için "python tanımlayıcı protokolü" için google) - bruno desthuilliers
Güzel. Özel bir sınıfın kullanılıp kullanılmasının bu tür bir kombinasyon, aslında, farklı durumlar için bir çeşit 'işlevler fabrikası' sağlar. - Paul Oyster
İçin +1 aramakBunu python nesnelerle yapabileceğiniz hakkında hiçbir fikrim yoktu. - Wim Coenen


Bir dekoratöre dönüştürün.

def static_var(var_name, initial_value):
    def _set_var(obj):
        setattr(obj, var_name, initial_value)
        return obj
    return _set_var

@static_var("_cache", {})
def CalcSomething(a):
    ...

16
2018-01-20 09:39



Bunu 'daha zarif bir yol' olarak değerlendiriyor musunuz? Yok gerçekten ;-) - Paul Oyster
Statik değişkenleri Python'da kullanılacak şık bir desen olarak düşünmüyorum, ancak dekoratör en azından özellik ayarının teknik ayrıntılarını içine alır. - Torsten Marek
Python özelliklerinin soğuk kullanımı için +1. @Pau Oyster: neden bu kadar zarif düşünmediğiniz hakkında hiçbir fikriniz yok. Bunu yapmak için hacksüz bir yol istiyorsanız, sadece bir örnek değişkeni olan bir Calculator sınıfı kullanın. Doğal olarak çirkin olan yerel statik için sordun. - Wim Coenen
Eh, bir pürist olmak yerine pragmatist olmak (artı 1991'den beri C ++ kodu yazmak ..) Ben yerel statik bir çirkin şey düşünmüyorum. - Paul Oyster
Gerek return obj _set_var'ın sonunda. Ve _set_var'ın kamuya açık olmadığını belirtmeye çalıştığınızı bildiğim halde, yalnızca static_var içinde olduğu için isminin (eklenen alt çizgi ile) karıştırılmasına gerek yoktur.


Önbelleği koruyacak dekoratör yazmayı düşünün ve işleviniz önbelleğe alma koduyla kirlenmeyecektir:

def cacheResults(aFunc):
    '''This decorator funcion binds a map between the tuple of arguments 
       and results computed by aFunc for those arguments'''
    def cachedFunc(*args):
        if not hasattr(aFunc, '_cache'):
            aFunc._cache = {}
        if args in aFunc._cache:
            return aFunc._cache[args]
        newVal = aFunc(*args)
        aFunc._cache[args] = newVal
        return newVal
    return cachedFunc

@cacheResults
def ReallyCalc(a):
    '''This function does only actual computation'''
    return pow(a, 42)

Belki ilk başta harika görünmüyor, ama sen kullanabilirsin cacheResults() Anahtar kelime parametrelerine ihtiyacınız olmayan her yerde. Aynı zamanda anahtar kelime paramları için de çalışacak benzer bir dekoratör oluşturmak mümkündür, ancak bu sefer gerekli görünmüyordu.


11
2018-01-20 10:10



Bu sorumun amacı olmamasına rağmen (orada açıklığa bakınız), bu yerel önbelleklerin uygulanması için güzel bir şemadır. Bunun için teşekkürler. - Paul Oyster
Kurtulmak if not hasattr... şart koşarak _cache  cacheResultsyerel değişken. - GingerPlusPlus


S.Lott tarafından önerilen çözüm de teklif edeceğim çözüm.

Etrafında da yararlı "memoize" dekoratörler var:

Tüm bunlar göz önüne alındığında, bir işlevdeki ilk denemeniz için bir alternatif ve bağımsız bir "statik yerel" sağladım:

def calc_something(a):

    try:
        return calc_something._cache[a]
    except AttributeError: # _cache is not there
        calc_something._cache= {}
    except KeyError: # the result is not there
        pass

    # compute result here

    calc_something._cache[a]= result
    return result

4
2018-01-20 14:37





Bir seçenek, varsayılan parametreleri kötüye kullanmaktır. yani:

def CalcSomething(a, _cache={}):
    if _cache.has_key(a):

Bu, adı nitelendirmenize gerek kalmaması ve iki yavaş dict araması yapmak yerine değişkenlere hızlı yerel erişim elde etmeniz avantajına sahiptir. Bununla birlikte, hala işlevin dışında belirtilmesi problemine sahiptir (aslında, işlev imzasında olduğundan daha kötüsüdür.)

Bunu önlemek için, daha iyi bir çözüm işlevi, statiklerinizi içeren bir kapatma işleminde sarmak olacaktır:

@apply
def CalcSomething():
    cache = {}  # statics go here

    def CalcSomething(a):
        if cache.has_key(a):
            return cache[a]
        cache[a] = ReallyCalc(a)
        return cache[a]
    return CalcSomething

3
2018-01-20 11:34



Ne yazık ki, başvuru Python 3.0'dan kaldırılmıştır. Ben de kısa, basit ve yararlı olduğu benzer durumlarda bir avuç buldum.
Bunun iyi bir uygulama olduğunu sanmıyorum. Sadece fonksiyonu çağırmak çok daha belirgindir. - Benjamin Peterson