variable python

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



python static typing (26)

¿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, a diferencia del nivel de clase? ¿Y la colocación de la función en una clase cambia algo?


Ante esta pregunta , puedo presentar otra alternativa que podría ser un poco más agradable de usar y se verá igual tanto para los métodos como para las 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


Aquí hay una versión completamente 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 podemos simplemente agregarles, o parches de mono, variables miembro a través del atributo especial __dict__ . El vars() incorporado vars() devuelve el atributo especial __dict__ .

EDITAR: Tenga en cuenta que, a diferencia del try:except AttributeError alternativo try:except AttributeError respuesta try:except AttributeError , con este enfoque, la variable siempre estará lista para la lógica del código después de la inicialización. Creo que el try:except AttributeError alternativa try:except AttributeError 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 el 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)) ...


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

Parece que el argumento de rendimiento es obsoleto. El mismo conjunto de pruebas parece dar resultados similares para siInt_try e 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. Cambié 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 el intento / captura produciría el código más seguro para el futuro y la prueba de la mayúscula, por lo que tal vez simplemente lo envuelva en función


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)


El uso de 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 desordenar el valor.

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

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

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

Lamentablemente, no conozco ninguna forma de encapsular esta solución en un decorador.


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

Ya 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 un operator() sobrecargado operator() . Las instancias de la clase se comportarán como funciones. Por ejemplo, puedes escribir int x = square(5); incluso si el square es un objeto (con operator() sobrecargado operator() ) y técnicamente no es una "función". Puede dar a una función-objeto 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 el operator() excepto que el método se llama __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 que se está utilizando:

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

La salida impresa a 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


Esta respuesta muestra que setdefault no satisface realmente la pregunta de los OP sobre cómo crear variables locales estáticas.

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

Funciona siempre y cuando fn. Se prefija a cada nombre de variable. Si los quitas así:

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

no hay errores, pero el contador siempre es 0 y eso me dice que vars (fn) NO está accediendo a las variables locales, sino más bien a un global, probablemente un decorador o atributo stash.

Si esto hubiera funcionado, habría sido mi solución preferida. Sin embargo, como no lo hace, me inclino a utilizar una definición de clase completamente encapsulada para crear este tipo de variables estáticas.

En mi humilde opinión que es el más sencillo. Por supuesto, depende de si está más familiarizado con los estilos de codificación funcional frente a OOP.


Esta respuesta se basa en la respuesta de @claudiu.

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

Es decir, en mi código de función preferiría escribir:

print(statics.foo)

en lugar de

print(my_function_name.foo)

Entonces, mi solución es:

  1. añadir un atributo statics a la función
  2. en el ámbito de la función, agregue una statics local variable como un alias a 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))

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 (consulte el artículo original sobre él, alrededor de 2000)

Se puede instalar via pip install bunch

También puede ser escrito a mano así:

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


La forma idiomática es usar una clase , que puede tener atributos. Si necesita instancias para no estar separados, use un singleton.

Hay varias formas en que puedes falsificar o munge las variables "estáticas" en Python (una no mencionada hasta ahora es tener un argumento predeterminado mutable), pero esta no es la forma idiomática y pitónica de hacerlo. Solo usa una clase.

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


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

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

No intente / excepto, no haya pruebas hasattr, solo getattr con un valor predeterminado.


Otras respuestas han demostrado la forma en que debe hacerlo. 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.


Otro giro (¡no recomendado!) En el objeto llamable como https://.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


Personalmente prefiero lo siguiente a los decoradores. A cada uno 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)


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 usar hasattr() para evitar una excepción AttributeError :

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, probablemente dentro de una clase.


Python no tiene variables estáticas, pero puede falsificar 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__ hace que una instancia de una clase (objeto) se pueda __call__ por su propio nombre. Es por eso que llamar a foo() anterior llama a la clase '' __call__ método. De la documentación :

Las instancias de clases arbitrarias pueden hacerse invocables definiendo un __call__() en su clase.


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 quise agregar esta parte es que las variables estáticas se usan no solo para incrementar un cierto 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 aún está protegida y se usa solo dentro del alcance de la función use_foo ()

En este ejemplo, la llamada a 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 de forma restrictiva como una clase singleton, sería ideal. Esto lo haría más pitónico.


Soulution n + = 1

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


También se podría considerar:

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

Razonamiento:

  • mucho pitónico ( ask for forgiveness not permission )
  • usar la excepción (lanzada solo una vez) en lugar de if ramifica (piense en la excepción StopIteration )

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

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

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

Ver PEP 3104 para la especificación de la declaración nonlocal .


Un poco invertido, pero esto debería funcionar:

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

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

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

Luego usa el código así:

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

Todavía requerirá que uses el foo. prefijo, por desgracia.

EDITAR (gracias a ony ): Esto se ve aún mejor:

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


Un poco más legible, pero más detallado:

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


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. Entonces, cada vez que la función se ejecuta, modifica el valor fuera de la función, conservándola 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"


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


Utilice una función de generador para generar un iterador.

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

Entonces utilízalo 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 finaliza (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 sobre la declaración de rendimiento .


_counter = 0 def foo(): global _counter _counter += 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, lo que no es realmente Python idiomático.


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 de Vincent anterior, este se usaría como un 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 puede ser lo suficientemente inteligente como para descifrarlo) es que puede tener múltiples variables estáticas e inicializarlas de una manera más convencional.