¿Cuál es el equivalente de Python a las variables estáticas dentro de una función?

631

¿Cuál es el equivalente idiomático de Python de este código C / C ++?

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

específicamente, ¿cómo se implementa el miembro estático en el nivel de función, en oposición al nivel de clase? ¿Y colocar la función en una clase cambia algo?

andrewdotnich
fuente
22
Hay NO equivalencia me temo. Incluso si realiza el truco del decorador con atributos de función, podrá acceder a la variable exterior, que lamentablemente frustra el punto. Además, tendrá que codificar el nombre de la función en la función, lo cual es muy molesto. Sugeriría usar una clase o módulo de variables globales en su lugar con el _prefijo convencional .
lpapp
8
Para los programadores que no son C, [ stackoverflow.com/questions/5033627/… variable estática dentro de una función solo es visible dentro del alcance de esa función, pero su vida útil es la vida completa del programa, y ​​solo se inicializa una vez). Básicamente, una variable de almacenamiento o contador persistente que vive entre llamadas de función.
smci
2
@lpapp: hay una especie de miembro de la clase . Tiene razón en que no podemos evitar que otro código lo vea o lo cambie.
smci

Respuestas:

681

Un poco invertido, pero esto debería funcionar:

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

Si desea el código de inicialización del contador en la parte superior en lugar de en la parte inferior, puede crear un decorador:

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

Luego usa el código como este:

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

Todavía requerirá que use el foo.prefijo, desafortunadamente.

(Crédito: @ony )

Claudiu
fuente
23
solo hay una instancia de foo: esta es una función. Todas las invocaciones acceden a la misma variable.
Claudiu
121
Perdón por desenterrar esto, pero prefiero poner if "counter" not in foo.__dict__: foo.counter = 0como primeras líneas de foo(). Esto ayudaría a evitar el código fuera de la función. Sin embargo, no estoy seguro de si esto fue posible en 2008. PD: Encontré esta respuesta mientras buscaba la posibilidad de crear variables de función estáticas, por lo que este hilo aún está "vivo" :)
binaryLV
8
@binaryLV: Probablemente prefiera eso al primer enfoque. El problema con el primer enfoque es que no es inmediatamente obvio fooy foo.counter = están íntimamente relacionados. sin embargo, en última instancia, prefiero el enfoque del decorador, ya que no hay forma de que no se llame al decorador y es semánticamente más obvio lo que hace ( @static_var("counter", 0)es más fácil y tiene más sentido para mis ojos que if "counter" not in foo.__dict__: foo.counter = 0, especialmente porque en este último tiene que usar el nombre de la función (dos veces) que puede cambiar).
Claudiu
66
@lpapp: depende de cuál sea el punto de las variables estáticas. Siempre pensé que sería el mismo valor en varias llamadas a funciones, lo que sí satisface. Nunca pensé que se trataba de ocultar variables, lo cual no es así, como dijiste.
Claudiu
3
def foo(): if not hasattr(foo,"counter"): foo.counter=0 foo.counter += 1
Erik Aronesty
222

Puede agregar atributos a una función y usarla como una variable estática.

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

# attribute must be initialized
myfunc.counter = 0

Alternativamente, si no desea configurar la variable fuera de la función, puede usarla hasattr()para evitar una AttributeErrorexcepción:

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

De todos modos, las variables estáticas son bastante raras, y debería encontrar un lugar mejor para esta variable, muy probablemente dentro de una clase.

vincent
fuente
66
¿Por qué no probar en lugar de la declaración if?
ravwojdyla
12
try: myfunc.counter += 1; except AttributeError: myfunc.counter = 1debería hacer lo mismo, usando excepciones en su lugar.
sleblanc
Deben usarse excepciones para situaciones excepcionales, es decir, aquellas que el programador espera que no sucedan, como un archivo de entrada que se abrió con éxito de repente y que no está disponible. Esta es una situación esperada, una declaración if tiene más sentido.
Hack Saw
11
@Hack_Saw: Bueno, esto es Pythonic (es mejor pedir perdón que permiso). En realidad, esto se recomienda en las técnicas de optimización de Python, ya que ahorra el costo de un if (aunque no recomiendo la optimización prematura). Su regla sobre casos excepcionales: 1. El fracaso ES un caso excepcional aquí, en cierto sentido. Solo sucede una vez. 2. Creo que esa regla se trata de usar (es decir, elevar) excepciones. Esto es una excepción para algo que espera que funcione pero para el que tiene un plan de respaldo, que es algo común en la mayoría de los idiomas.
leewz
@leewangzhong: ¿Adjuntar un bloque que no genera una excepción tryagrega algún costo? Sólo curioso.
trss
202

También se podría considerar:

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

Razonamiento:

  • mucho pitónico ("pedir perdón, no permiso")
  • use la excepción (lanzada solo una vez) en lugar de la iframa (piense en la excepción StopIteration )
ravwojdyla
fuente
11
No he estado haciendo Python por mucho tiempo, pero esto satisface una de las viviendas implícitas del lenguaje: si no es (bastante) fácil, lo estás haciendo mal .
ZX9
No funcionó inmediatamente con los métodos de clase, "self.foo.counter = 1" vuelve a generar AttributeError.
villasv
16
Esta es la solución correcta y debería ser la respuesta aceptada porque el código de inicialización se ejecutará cuando se invoque la función y no cuando se ejecute el módulo o cuando se importe algo de él, que es el caso si utiliza el enfoque de decorador de La respuesta actualmente aceptada. Ver ejecución de la función decoradora de Python . Si tiene un gran módulo de biblioteca, se ejecutarán todos los decoradores, incluidos los de las funciones que no importe.
Nils Lindemann
3
Un enfoque más simple: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCuriousOne
55
@MANU Usar hasattr()para esto no es más simple y también menos eficiente.
moooeeeep
48

Otras respuestas han demostrado la forma en que debe hacer esto. Aquí hay una forma en que no deberías:

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

Los valores predeterminados se inicializan solo cuando la función se evalúa por primera vez, no cada vez que se ejecuta, por lo que puede usar una lista o cualquier otro objeto mutable para almacenar valores estáticos.

Jeremy Banks
fuente
Lo intenté, pero por alguna razón, el parámetro de función se inicializaba a 140, no a 0. ¿Por qué sería esto?
andrewdotnich
1
@bouvard Para las funciones recursivas que necesitan una variable estática, esta es la única que realmente se lee bien.
Lifebalance
1
Intenté varios enfoques y desearía que este fuera aceptado como pitónico. Con algunos nombres significativos como def foo(arg1, arg2, _localstorage=DataClass(counter=0))me parece bien legible. Otro buen punto es el cambio de nombre de la función fácil.
VPfB
2
¿Por qué dices que no deberías hacerlo de esa manera? ¡Me parece perfectamente razonable!
Konstantin
1
@VPfB: para el almacenamiento general, puede usarlo types.SimpleNamespace, haciéndolo def foo(arg1, arg2, _staticstorage=types.SimpleNamespace(counter=0)):sin necesidad de definir una clase especial.
ShadowRanger
43

Muchas personas ya han sugerido probar 'hasattr', pero hay una respuesta más simple:

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

Sin prueba / excepto, sin prueba hasattr, solo getattr con un valor predeterminado.

Jonathan
fuente
2
preste atención al tercer parámetro de getattr cuando coloca un func allí, por ejemplo: def func (): def foo (): return 1112 func.counter = getattr (func, 'counter', foo ()) + 1 cuando llama func, el foo siempre será llamado!
Código para el
1
Solo una llamada a getattr cada vez que se llama esa función. Eso está bien si el rendimiento no es un problema, si es probar / excepto, ganará sin dudas.
Mark Lawrence
2
@ MarkLawrence: En realidad, al menos en mi instalación de Windows x64 3.8.0, la diferencia de rendimiento entre esta respuesta y el enfoque equivalente try/ exceptbasado de ravwojdyla no tiene sentido. Un ipython %%timeitmicrobenchmark simple dio el costo de try/ excepta 255 ns por llamada, frente a 263 ns para la getattrsolución basada. Sí, el try/ exceptes más rápido, pero no es exactamente "ganar sin dudas"; Es una pequeña micro-optimización. Escriba cualquier código que parezca más claro, no se preocupe por diferencias de rendimiento triviales como esta.
ShadowRanger
@ShadowRanger gracias por comparar eso. Me he estado preguntando acerca de la declaración de MarkLawrence durante 2 años, y estoy muy feliz de que hayas investigado. Definitivamente estoy de acuerdo con su oración final: "escriba el código que parezca más claro", es exactamente por eso que escribí esta respuesta.
Jonathan
28

Aquí hay una versión totalmente encapsulada que no requiere una llamada de inicialización externa:

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

En Python, las funciones son objetos y simplemente podemos agregarles, o un parche de mono, variables miembro a través del atributo especial __dict__. El incorporado vars()devuelve el atributo especial __dict__.

EDITAR: tenga en cuenta que, a diferencia de la try:except AttributeErrorrespuesta alternativa , con este enfoque la variable siempre estará lista para la lógica del código después de la inicialización. Creo que la try:except AttributeErroralternativa a lo siguiente será menos SECO y / o tendrá un flujo incómodo:

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: solo recomiendo el enfoque anterior cuando se llamará a la función desde múltiples ubicaciones. Si, en cambio, la función solo se llama en un lugar, es mejor usar 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
fuente
2
el único problema con esto es que realmente no está nada bien, y cada vez que quieras usar este patrón tienes que cortar y pegar el código ... de ahí mi uso de un decorador
Claudiu
2
probablemente debería usar algo comotry: mystaticfun.counter+=10 except AttributeError: mystaticfun.counter=0
endolito el
2
Úselo en X not in Ylugar de not X in Y(o aconseje usarlo si solo lo estaba usando en aras de una comparación de aspecto más similar entre eso y hasattr)
Nick T
¿qué tal esto: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCuriousOne
no es ideal porque la cláusula if agrega anidamiento innecesario, en esta situación prefiero setdefault
Riaz Rizvi
27

Python no tiene variables estáticas, pero puede simularlo definiendo un objeto de clase invocable y luego usándolo como una función. También vea esta respuesta .

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

Tenga en cuenta que __call__una instancia de una clase (objeto) se puede llamar por su propio nombre. Es por eso que llamar foo()arriba llama al __call__método de la clase . De la documentación :

Las instancias de clases arbitrarias se pueden llamar definiendo un __call__()método en su clase.

daniels
fuente
15
Las funciones ya son objetos, por lo que esto simplemente agrega una capa innecesaria.
DasIch
Vea esta respuesta SO para una larga opinión de que esta es realmente una buena idea. stackoverflow.com/questions/460586 . Estoy de acuerdo en que hacer que cualquier clase de este tipo sea un singleton, tal vez como esta stackoverflow.com/questions/6760685 , también sería una buena idea. No sé lo que @ S.Lott quiere decir con "... mover el contador a la definición de clase ..." porque parece que ya está en posición de variable de clase para mí.
Reb.Cabin
1
Basado en mi investigación, esta técnica de clase parece ser la más "Pitónica" de los enfoques presentados en esta página, y utiliza el menor truco. Por lo tanto, planeo adoptarlo como mi reemplazo para las variables similares a C-static en funciones, como un nuevo desarrollador de Python.
Gabriel Staples
1
¿Qué sucede si quiero foo1 = Foo () y foo2 = Foo ()?
Mark Lawrence
@ MarkLawrence Entonces tiene dos instancias diferentes de una clase invocable, cada una con su propio contador. Que es exactamente lo que debe esperar si no está utilizando la instancia fooque se proporciona como un singleton.
Aaron McMillin
14

Use una función generadora para generar un iterador.

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

Entonces úsalo como

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

Si quieres un límite superior:

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

Si el iterador termina (como en el ejemplo anterior), también puede recorrerlo directamente, como

for i in foo_gen(20):
    print i

Por supuesto, en estos casos simples es mejor usar xrange :)

Aquí está la documentación en la declaración de rendimiento .

GNUD
fuente
11

Otras soluciones adjuntan un atributo de contador a la función, generalmente con lógica enrevesada para manejar la inicialización. Esto es inapropiado para el nuevo código.

En Python 3, la forma correcta es usar una nonlocaldeclaración:

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

Ver PEP 3104 para la especificación de la nonlocaldeclaración.

Si el contador está destinado a ser privado para el módulo, se debe nombrar _counteren su lugar.

cbarrick
fuente
Incluso antes de Python 3, siempre podría hacer esto con una global counterdeclaración en lugar de nonlocal counter( nonlocalsolo le permite escribir en estado de cierre en una función anidada). La razón por la que las personas están adjuntando un atributo a la función es para evitar contaminar el espacio de nombres global para el estado específico de la función, por lo que no tiene que hacer cosas aún más difíciles cuando dos funciones necesitan counters independientes . Esta solución no escala; atributos en la función do. La respuesta de kdb es cómo nonlocalpuede ayudar, pero agrega complejidad.
ShadowRanger
Eh, creo que la complejidad de una función de fábrica o un decorador es exagerada a menos que estés haciendo esto mucho, y en ese caso el diseño ya es un poco maloliente. Para una única vez, solo agregue el contador no local y termine con él. He agregado un poco a la respuesta sobre las convenciones de nomenclatura. Además, la razón por la que recomiendo nonlocalsobre globales exactamente como usted señala - que funciona en más estrictamente las circunstancias.
cbarrick
8

Usar un atributo de una función como variable estática tiene algunos inconvenientes potenciales:

  • Cada vez que desee acceder a la variable, debe escribir el nombre completo de la función.
  • El código externo puede acceder a la variable fácilmente y meterse con el valor.

Python idiomático para el segundo problema probablemente nombraría la variable con un guión bajo para indicar que no se debe acceder a ella, mientras se mantiene accesible después del hecho.

Una alternativa sería un patrón utilizando cierres léxicos, que son compatibles con la nonlocalpalabra clave en python 3.

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

Lamentablemente, no sé cómo encapsular esta solución en un decorador.

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

Al igual que el código anterior de vincent, esto se usaría como decorador de funciones y se debe acceder a las variables estáticas con el nombre de la función como prefijo. La ventaja de este código (aunque es cierto que cualquiera podría ser lo suficientemente inteligente como para descubrirlo) es que puede tener múltiples variables estáticas e inicializarlas de una manera más convencional.

Giorgian Borca-Tasciuc
fuente
7

Un poco más legible, pero más detallado (Zen de Python: explícito es mejor que implícito):

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

Vea aquí para una explicación de cómo funciona esto.

warvariuc
fuente
¿Puedes explicar por qué funciona este código? El segundo foo()debe reinicializar el diccionario al valor especificado en la definición de la función (por lo que la clave del contador tiene un valor de 0). ¿Por qué no?
raffaem
3
@raffamaiden: los argumentos predeterminados se evalúan solo una vez cuando se define la función y no cada vez que se llama a la función.
Daniel K.
6
_counter = 0
def foo ():
   _contador global
   _contador + = 1
   print 'counter is', _counter

Python usa habitualmente guiones bajos para indicar variables privadas. La única razón en C para declarar la variable estática dentro de la función es ocultarla fuera de la función, que no es Python realmente idiomática.

Dave
fuente
4

Después de probar varios enfoques, termino usando una versión mejorada de la respuesta de @ warvariuc:

import types

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

La forma idiomática es usar una clase , que puede tener atributos. Si necesita que las instancias no estén separadas, use un singleton.

Hay varias formas de falsificar o combinar variables "estáticas" en Python (una no mencionada hasta ahora es tener un argumento predeterminado mutable), pero esta no es la forma idiomática e idiomática de hacerlo. Solo usa una clase.

O posiblemente un generador, si su patrón de uso se ajusta.

Osito de peluche
fuente
Para funciones recursivas independientes, el defaultargumento es el más elegante.
Lifebalance
3

Impulsado por esta pregunta , ¿puedo presentar otra alternativa que podría ser un poco más agradable de usar y se verá igual para ambos métodos y funciones:

@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    

Si te gusta el uso, aquí está la implementación:

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

Otro giro (¡no recomendado!) En el objeto invocable como https://stackoverflow.com/a/279598/916373 , si no le importa usar una firma de llamada funky, sería hacer

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
perdió
fuente
3

En lugar de crear una función que tenga una variable local estática, siempre puede crear lo que se llama un "objeto de función" y asignarle una variable miembro estándar (no estática).

Dado que dio un ejemplo escrito en C ++, primero explicaré qué es un "objeto de función" en C ++. Un "objeto de función" es simplemente cualquier clase con una sobrecarga operator(). Las instancias de la clase se comportarán como funciones. Por ejemplo, puede escribir int x = square(5);incluso si squarees un objeto (con sobrecarga operator()) y técnicamente no es una "función". Puede dar a un objeto de función cualquiera de las características que podría darle a un objeto de clase.

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

En Python, también podemos sobrecargar, operator()excepto que el método se llama en su lugar __call__:

Aquí hay una definición de clase:

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

Aquí hay un ejemplo de la clase utilizada:

from foo import foo

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

El resultado impreso en la consola es:

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

Si desea que su función tome argumentos de entrada, también puede agregarlos a __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
fuente
3

Soulution n + = 1

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

Una declaración global proporciona esta funcionalidad. En el siguiente ejemplo (python 3.5 o superior para usar la "f"), la variable de contador se define fuera de la función. Definirlo como global en la función significa que la versión "global" fuera de la función debe estar disponible para la función. Por lo tanto, cada vez que se ejecuta la función, modifica el valor fuera de la función, manteniéndolo más allá de la función.

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
fuente
Esto funciona de la misma manera si se usa correctamente. La diferencia con el código c es que en el ejemplo c del OP, la variable de contador solo puede ser tocada por la función. Se puede usar o alterar una variable global en Python en cualquier parte del script
MortenSickel
2

Una variable estática dentro de un método 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
fuente
1

Personalmente prefiero lo siguiente a los decoradores. A cada cual lo suyo.

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)
gastado
fuente
3
No se ofenda, pero esta solución me recuerda un poco al "estilo de la gran empresa" :-) willa.me/2013/11/the-six-most-common-species-of-code.html
JJC
Sí, el uso no portátil (la manipulación de la pila en general es un detalle de implementación de CPython, no es algo en lo que pueda confiar en PyPy, Jython, IronPython, what-have-you), manipulación de pila frágil, con media docena de llamadas a funciones en cada uso es mucho mejor que un simple decorador ... </s>
ShadowRanger
1

Esta respuesta se basa en la respuesta de @claudiu.

Descubrí que mi código se volvía menos claro cuando siempre tenía que anteponer el nombre de la función, cada vez que tenía la intención de acceder a una variable estática.

A saber, en mi código de función preferiría escribir:

print(statics.foo)

en vez de

print(my_function_name.foo)

Entonces, mi solución es:

  1. agregar un staticsatributo a la función
  2. en el alcance de la función, agregue una variable local staticscomo un alias paramy_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))

Observación

Mi método utiliza una clase llamada Bunch, que es un diccionario que admite el acceso de estilo de atributo, a la JavaScript (vea el artículo original al respecto, alrededor de 2000)

Se puede instalar a través de pip install bunch

También se puede escribir a mano así:

class Bunch(dict):
    def __init__(self, **kw):
        dict.__init__(self,kw)
        self.__dict__ = self
Pascal T.
fuente
Nota: types.SimpleNamespace(disponible desde 3.3) admite este comportamiento fuera de la caja (y se implementa en C en CPython, por lo que es lo más rápido posible).
ShadowRanger
0

Sobre la base de la respuesta de Daniel (adiciones):

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)

La razón por la que quería agregar esta parte es que las variables estáticas se usan no solo para aumentar en algún valor, sino también para verificar si la var estática es igual a algún valor, como un ejemplo de la vida real.

La variable estática todavía está protegida y se usa solo dentro del alcance de la función use_foo ()

En este ejemplo, call to foo () funciona exactamente como (con respecto al equivalente de c ++ correspondiente):

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

si la clase Foo se define restrictivamente como una clase singleton, eso sería ideal. Esto lo haría más pitónico.

yash
fuente
-1

Claro que esta es una vieja pregunta, pero creo que podría proporcionar alguna actualización.

Parece que el argumento del rendimiento es obsoleto. El mismo conjunto de pruebas parece dar resultados similares para siInt_try y isInt_re2. Por supuesto, los resultados varían, pero esta es una sesión en mi computadora con Python 3.4.4 en el kernel 4.3.01 con Xeon W3550. Lo he ejecutado varias veces y los resultados parecen ser similares. Moví la expresión regular global a la función estática, pero la diferencia de rendimiento es insignificante.

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

Con el problema de rendimiento fuera del camino, parece que try / catch produciría el código más a prueba de futuro y de esquina, por lo que tal vez solo lo envuelva en función

Keji Li
fuente
1
¿Qué estás comparando aquí? Esto parece un comentario sobre otras respuestas, pero no está claro cuáles, y no responde la pregunta en sí.
ShadowRanger