Qual é o equivalente em Python de variáveis ​​estáticas dentro de uma função?

631

Qual é o equivalente idiomático do Python desse código C / C ++?

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

especificamente, como se implementa o membro estático no nível da função, em oposição ao nível da classe? E colocar a função em uma classe muda alguma coisa?

andrewdotnich
fonte
22
NO equivalência eu tenho medo. Mesmo se você fizer o hack do decorador com atributos de função, você poderá acessar a variável do lado de fora, o que meio que derrota o ponto, infelizmente. Além disso, você terá que codificar o nome da função na função, o que é muito irritante. Eu sugeriria usar variáveis ​​globais de classe ou módulo em vez do _prefixo convencional .
Lpapp # /
8
Para os não-C-programadores, [ stackoverflow.com/questions/5033627/... variável estática dentro de uma função só está dentro visível que o escopo da função, mas seu tempo de vida é toda a vida do programa, e ele só é inicializado uma vez). Basicamente, um contador persistente ou variável de armazenamento que vive entre chamadas de função.
187 smci
2
@ lpapp: existe um tipo de membro da classe . Você está certo de que não podemos impedir que outro código o visualize ou altere.
SMCI

Respostas:

681

Um pouco invertido, mas isso deve funcionar:

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

Se você deseja o código de inicialização do contador na parte superior e não na inferior, é possível criar um decorador:

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

Em seguida, use o código como este:

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

Ainda será necessário que você use o foo.prefixo, infelizmente.

(Crédito: @ony )

Claudiu
fonte
23
existe apenas uma instância de foo - essa única função. todas as invocações acessam a mesma variável.
Claudiu
121
Desculpe por desenterrar isso, mas eu prefiro colocar if "counter" not in foo.__dict__: foo.counter = 0como as primeiras linhas de foo(). Isso ajudaria a evitar código fora da função. Não tenho certeza se isso era possível em 2008. PS acharam esta resposta enquanto procura possibilidade de criar variáveis de função estáticos, por isso esta discussão ainda está "vivo" :)
binaryLV
8
@ binaryLV: Eu provavelmente preferiria isso à primeira abordagem. O problema com a primeira abordagem é que não é imediatamente óbvio isso fooe foo.counter = está intimamente relacionado. no entanto, prefiro a abordagem do decorador, já que não há como o decorador não ser chamado e é semanticamente mais óbvio o que ele faz ( @static_var("counter", 0)é mais fácil e faz mais sentido para os meus olhos do que if "counter" not in foo.__dict__: foo.counter = 0, especialmente porque no último você deve usar o nome da função (duas vezes) que pode mudar).
Claudiu
6
@ lpapp: Depende de qual é o objetivo das variáveis ​​estáticas. Eu sempre pensei que seria o mesmo valor em várias chamadas de função, o que isso satisfaz. Eu nunca imaginei que isso fosse ocultar variáveis, o que não acontece, como você disse.
Claudiu
3
def foo(): if not hasattr(foo,"counter"): foo.counter=0 foo.counter += 1
Erik Aronesty 03/01
222

Você pode adicionar atributos a uma função e usá-lo como uma variável estática.

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

# attribute must be initialized
myfunc.counter = 0

Como alternativa, se você não deseja configurar a variável fora da função, pode usar hasattr()para evitar uma AttributeErrorexceção:

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

De qualquer forma, as variáveis ​​estáticas são bastante raras e você deve encontrar um local melhor para essa variável, provavelmente dentro de uma classe.

vincent
fonte
6
Por que não tentar, em vez da instrução if?
Ravwojdyla
12
try: myfunc.counter += 1; except AttributeError: myfunc.counter = 1deve fazer o mesmo, usando exceções.
sleblanc
Exceções devem ser usadas para situações excepcionais, ou seja, aquelas que o programador espera que não aconteçam, como um arquivo de entrada que ele abriu com sucesso de repente e não está disponível. Esta é uma situação esperada, uma declaração if faz mais sentido.
precisa saber é o seguinte
11
@Hack_Saw: Bem, isso é Pythonic (é melhor pedir perdão do que permissão). Isso é realmente recomendado nas técnicas de otimização do Python, pois economiza o custo de um if (embora eu não esteja recomendando a otimização prematura). Sua regra sobre casos excepcionais: 1. Falha é um caso excepcional aqui, em certo sentido. Isso só acontece uma vez. 2. Eu acho que essa regra é sobre o uso (ou seja, aumento) de exceções. Isso está capturando uma exceção para algo que você espera que funcione, mas possui um plano de backup, o que é comum na maioria dos idiomas.
leewz
@leewangzhong: O fechamento de um bloco que não gera uma exceção no interior tryadiciona algum custo? Apenas curioso.
trss 23/07
202

Pode-se também considerar:

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

Raciocínio:

  • muito pitônico ("pedir perdão, não permissão")
  • use a exceção (lançada apenas uma vez) em vez da iframificação (pense na exceção StopIteration )
ravwojdyla
fonte
11
Eu não uso Python há muito tempo, mas isso satisfaz um dos princípios implícitos da linguagem: se não é (razoavelmente) fácil, você está fazendo errado .
ZX9 21/09/16
Não funcionou imediatamente com métodos de classe, "self.foo.counter = 1" gera AttributeError novamente.
villasv
16
Esta é a solução correta e deve ser a resposta aceita, porque o código de inicialização será executado quando a função for chamada e não quando o módulo for executado ou quando algo dele for importado, o que é o caso se você usar a abordagem do decorador de a resposta atualmente aceita. Consulte Execução da função do decorador Python . Se você tiver um módulo de biblioteca enorme, todos os decoradores serão executados, incluindo os de funções que você não importa.
Nils Lindemann
3
Uma abordagem mais simples: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCuriousOne
5
@ MANU A utilização hasattr()para isso não é mais simples e também menos eficiente.
moooeeeep
48

Outras respostas demonstraram a maneira como você deve fazer isso. Aqui está uma maneira que você não deve:

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

Os valores padrão são inicializados apenas quando a função é avaliada pela primeira vez, não sempre que é executada, para que você possa usar uma lista ou qualquer outro objeto mutável para armazenar valores estáticos.

Jeremy Banks
fonte
Eu tentei isso, mas por algum motivo, o parâmetro de função estava se inicializando em 140, não em 0. Por que isso seria?
andrewdotnich 10/11/2008
1
@bouvard Para funções recursivas que precisam de uma variável estática, esta é a única que realmente lê bem.
lifebalance
1
Eu tentei várias abordagens e gostaria que essa fosse aceita como pitônica. Com alguns nomes significativos como def foo(arg1, arg2, _localstorage=DataClass(counter=0))eu acho bem legível. Outro ponto positivo é a renomeação fácil da função.
VPfB
2
Por que você diz que não deve fazer dessa maneira? Parece perfeitamente razoável para mim!
Konstantin
1
@VPfB: Para armazenamento geral, você pode usá- types.SimpleNamespacelo, def foo(arg1, arg2, _staticstorage=types.SimpleNamespace(counter=0)):sem precisar definir uma classe especial.
ShadowRanger
43

Muitas pessoas já sugeriram testar o 'hasattr', mas há uma resposta mais simples:

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

Nenhuma tentativa / exceto, nenhum teste de hasattr, apenas getattr com um padrão.

Jonathan
fonte
2
preste atenção ao terceiro parm de getattr quando você coloca uma func lá, por exemplo: def func (): def foo (): retorna 1112 func.counter = getattr (func, 'counter', foo ()) + 1 quando você chama func, o foo sempre será chamado!
Codefor
1
Apenas uma chamada para getattr toda vez que a função é chamada. Tudo bem se o desempenho não for um problema, se for tentar / exceto ganhará as mãos.
Mark Lawrence
2
@ MarkLawrence: Na verdade, pelo menos na instalação do Windows x64 3.8.0, a diferença de desempenho entre esta resposta e a abordagem equivalente try/ exceptbaseada em ravwojdyla é praticamente sem sentido. Uma simples ipython %%timeitmarca de microbench deu o custo de try/ excepta 255 ns por chamada, contra 263 ns para a getattrsolução baseada. Sim, o try/ excepté mais rápido, mas não é exatamente "ganhar as mãos para baixo"; é uma pequena micro-otimização. Escreva o código que parecer mais claro, não se preocupe com diferenças triviais de desempenho como esta.
ShadowRanger
@ShadowRanger, obrigado por comparar isso. Eu estive pensando sobre a declaração de MarkLawrence por 2 anos, e estou muito feliz que você fez a pesquisa. Eu definitivamente concordo com sua sentença final - "escreva o código que parecer mais claro" - foi exatamente por isso que escrevi esta resposta.
Jonathan
28

Aqui está uma versão totalmente encapsulada que não requer uma chamada de inicialização externa:

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

No Python, funções são objetos e podemos simplesmente adicionar, ou aplicar um patch de macaco, variáveis ​​de membro a elas através do atributo especial __dict__. O interno vars()retorna o atributo especial __dict__.

EDIT: Observe que, diferentemente da try:except AttributeErrorresposta alternativa , com essa abordagem a variável estará sempre pronta para a lógica do código após a inicialização. Eu acho que a try:except AttributeErroralternativa para o seguinte será menos SECA e / ou terá um fluxo estranho:

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: Eu apenas recomendo a abordagem acima quando a função será chamada de vários locais. Se, em vez disso, a função for chamada apenas em um lugar, é melhor 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
fonte
2
o único problema com isto é que não é realmente puro em tudo, e sempre que você quiser usar este padrão você tem que cortar e colar o código ... daí o meu uso de um decorador
Claudiu
2
provavelmente deve usar algo comotry: mystaticfun.counter+=10 except AttributeError: mystaticfun.counter=0
endolith 6/12/2013
2
Utilize X not in Yem vez de not X in Y(ou aconselhar usar se você foram apenas usá-lo por causa de uma comparação mais olhando semelhante-entre isso e hasattr)
Nick T
Que tal isso: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCuriousOne
não é ideal porque a cláusula if acrescenta nidificação desnecessário, nesta situação eu prefiro setdefault
Riaz Rizvi
27

O Python não possui variáveis ​​estáticas, mas você pode falsificá-lo definindo um objeto de classe que pode ser chamado e, em seguida, usando-o como uma função. Veja também esta resposta .

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

Observe que __call__faz com que uma instância de uma classe (objeto) possa ser chamada com seu próprio nome. É por isso que chamar foo()acima chama o __call__método da classe A partir da documentação :

É possível chamar instâncias de classes arbitrárias definindo um __call__()método em sua classe.

daniels
fonte
15
As funções já são objetos, então isso apenas adiciona uma camada desnecessária.
DasIch 30/01
Veja esta resposta para uma longa opinião de que essa é realmente uma boa ideia. stackoverflow.com/questions/460586 . Concordo que tornar uma classe como uma singleton, talvez como este stackoverflow.com/questions/6760685 , também seria uma boa ideia. Não sei o que @ S.Lott quer dizer com "... mover o contador para a definição de classe ..." porque parece que ele já está na posição de variável de classe para mim.
Reb.Cabin
1
Com base em minha pesquisa, essa técnica de classe parece ser a mais "pitônica" das abordagens apresentadas nesta página e usa o mínimo de truques. Portanto, pretendo adotá-lo como meu substituto obrigatório para variáveis ​​do tipo C-estático em funções, como um novo desenvolvedor de Python.
Gabriel Staples
1
O que acontece se eu quiser foo1 = Foo () e foo2 = Foo ()?
Mark Lawrence
@MarkLawrence Então você tem duas instâncias diferentes de uma classe que pode ser chamada, cada uma com seu próprio contador. Qual exatamente o que você deve esperar se não estiver usando a instância fooque é fornecida como um singleton.
Aaron McMillin
14

Use uma função de gerador para gerar um iterador.

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

Então use-o como

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

Se você deseja um limite superior:

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

Se o iterador terminar (como no exemplo acima), você também poderá fazer um loop direto sobre ele, como

for i in foo_gen(20):
    print i

Obviamente, nesses casos simples, é melhor usar o xrange :)

Aqui está a documentação na declaração de rendimento .

gnud
fonte
11

Outras soluções anexam um atributo de contador à função, geralmente com lógica complicada para lidar com a inicialização. Isso é inadequado para o novo código.

No Python 3, o caminho certo é usar uma nonlocaldeclaração:

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

Veja PEP 3104 para a especificação da nonlocaldeclaração.

Se o contador se destina a ser privado para o módulo, ele deve ser nomeado _counter.

cbarrick
fonte
Mesmo antes do Python 3, você sempre podia fazer isso com uma global counterinstrução em vez de nonlocal counter( nonlocalapenas permite escrever no estado de fechamento em uma função aninhada). O motivo pelo qual as pessoas estão anexando um atributo à função é evitar poluir o espaço de nomes global para o estado específico da função, para que você não precise fazer coisas ainda mais obscenas quando duas funções precisam de counters independentes . Essa solução não é escalável; atributos na função do. A resposta do kdb é como nonlocalpode ajudar, mas adiciona complexidade.
ShadowRanger
Acho que a complexidade de uma função ou decorador de fábrica é um exagero, a menos que você faça muito isso e, nesse caso, o design já é um pouco fedorento. Para um caso único, basta adicionar o contador não-local e pronto. Adicionei um pouco à resposta sobre convenções de nomenclatura. Além disso, a razão pela qual eu recomendo nonlocalmais globalé exatamente como você apontar - ele funciona em estritamente mais circunstâncias.
cbarrick
8

O uso de um atributo de uma função como variável estática tem algumas desvantagens em potencial:

  • Toda vez que você deseja acessar a variável, é necessário escrever o nome completo da função.
  • O código externo pode acessar a variável facilmente e mexer com o valor.

O python linguístico para o segundo problema provavelmente nomeará a variável com um sublinhado principal para sinalizar que ela não deve ser acessada, mantendo-a acessível após o fato.

Uma alternativa seria um padrão usando fechamentos lexicais, suportados pela nonlocalpalavra - chave no python 3.

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

Infelizmente, não sei como encapsular esta solução em um decorador.

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

Assim como o código de vincent acima, isso seria usado como decorador de função e as variáveis ​​estáticas devem ser acessadas com o nome da função como prefixo. A vantagem desse código (embora seja certo que alguém possa ser inteligente o suficiente para descobrir isso) é que você pode ter várias variáveis ​​estáticas e inicializá-las de uma maneira mais convencional.

Giorgian Borca-Tasciuc
fonte
7

Um pouco mais legível, mas mais detalhado (Zen of Python: explícito é melhor que implícito):

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

Veja aqui uma explicação de como isso funciona.

warvariuc
fonte
você pode explicar por que esse código funciona? O segundo foo()deve reinicializar o dicionário para o valor especificado na definição da função (portanto, com a tecla do contador com o valor 0). Por que não?
raffaem
3
@raffamaiden: argumentos padrão são avaliados apenas uma vez quando a função é definida e não sempre que a função é chamada.
Daniel K.
6
_counter = 0
def foo ():
   global _counter
   _counter + = 1
   print 'counter is', _counter

O Python costuma usar sublinhados para indicar variáveis ​​privadas. O único motivo em C para declarar a variável estática dentro da função é ocultá-la fora da função, o que não é realmente Python idiomático.

Dave
fonte
4

Depois de tentar várias abordagens, acabo usando uma versão aprimorada da resposta do @ warvariuc:

import types

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

A maneira idiomática é usar uma classe , que pode ter atributos. Se você precisar que as instâncias não sejam separadas, use um singleton.

Existem várias maneiras de falsificar ou mover variáveis ​​"estáticas" para o Python (uma que não foi mencionada até agora é ter um argumento padrão mutável), mas essa não é a maneira idiomática Pythonic de fazer isso. Basta usar uma classe.

Ou possivelmente um gerador, se o seu padrão de uso se encaixar.

Urso de pelúcia
fonte
Para funções recursivas independentes, o defaultargumento é o mais elegante.
lifebalance
3

Solicitado por esta pergunta , posso apresentar outra alternativa que pode ser um pouco mais agradável de usar e terá a mesma aparência para métodos e funções:

@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    

Se você gosta do uso, aqui está a implementação:

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

Outra reviravolta (não recomendada!) No objeto que pode ser chamado, como https://stackoverflow.com/a/279598/916373 , se você não se importa em usar uma assinatura de chamada descolada, seria

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
perdido
fonte
3

Em vez de criar uma função com uma variável local estática, você sempre pode criar o que é chamado de "objeto de função" e fornecer uma variável de membro padrão (não estática).

Como você deu um exemplo escrito em C ++, primeiro explicarei o que é um "objeto de função" em C ++. Um "objeto de função" é simplesmente qualquer classe com uma sobrecarga operator(). Instâncias da classe se comportarão como funções. Por exemplo, você pode escrever int x = square(5);mesmo que squareseja um objeto (com sobrecarga operator()) e tecnicamente não seja uma "função". Você pode dar a um objeto de função qualquer um dos recursos que você poderia dar a um objeto de classe.

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

No Python, também podemos sobrecarregar, operator()exceto que o método é chamado __call__:

Aqui está uma definição de classe:

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

Aqui está um exemplo da classe que está sendo usada:

from foo import foo

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

A saída impressa no console é:

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

Se você deseja que sua função receba argumentos de entrada, também pode adicioná-los __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
fonte
3

Soulution n + = 1

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

Uma declaração global fornece essa funcionalidade. No exemplo abaixo (python 3.5 ou superior para usar o "f"), a variável counter é definida fora da função. Definir como global na função significa que a versão "global" fora da função deve ser disponibilizada para a função. Portanto, cada vez que a função é executada, ela modifica o valor fora da função, preservando-o além da função.

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
fonte
Isso funciona da mesma maneira se usado corretamente. A diferença para o código c é que, no exemplo c do OP, a variável do contador só pode ser tocada pela função. Uma variável global em python pode ser usado ou alterado em qualquer lugar do roteiro
MortenSickel
2

Uma variável estática dentro de um 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
fonte
1

Pessoalmente, prefiro o seguinte aos decoradores. Cada um com sua mania.

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)
gastou
fonte
3
Por favor, não se ofenda, mas esta solução me lembra um pouco o "estilo da grande empresa" :-) willa.me/2013/11/the-six-most-common-species-of-code.html
JJC
Sim, usando não portátil (a manipulação de pilha em geral é um detalhe de implementação do CPython, não é algo em que você possa confiar em PyPy, Jython, IronPython, o que você tem), manipulação de pilha frágil, com meia dúzia de chamadas de função em todos os usos é muito melhor do que um simples decorador ... </s> #
ShadowRanger
1

Esta resposta baseia-se na resposta de @claudiu.

Eu descobri que meu código estava ficando menos claro quando eu sempre precisava acrescentar o nome da função, sempre que pretendia acessar uma variável estática.

Ou seja, no meu código de função eu preferiria escrever:

print(statics.foo)

ao invés de

print(my_function_name.foo)

Então, minha solução é:

  1. adicione um staticsatributo à função
  2. no escopo da função, adicione uma variável local staticscomo um 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))

Observação

Meu método usa uma classe chamada Bunch, que é um dicionário que suporta acesso ao estilo de atributo, à la JavaScript (consulte o artigo original sobre isso, por volta de 2000)

Pode ser instalado via pip install bunch

Também pode ser manuscrito da seguinte maneira:

class Bunch(dict):
    def __init__(self, **kw):
        dict.__init__(self,kw)
        self.__dict__ = self
Pascal T.
fonte
Nota: types.SimpleNamespace(disponível desde 3.3) suporta esse comportamento imediatamente (e é implementado em C no CPython, por isso é o mais rápido possível).
ShadowRanger
0

Com base na resposta de Daniel (adições):

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)

A razão pela qual desejei adicionar esta parte é que as variáveis ​​estáticas são usadas não apenas para incrementar por algum valor, mas também para verificar se o var estático é igual a algum valor, como um exemplo da vida real.

A variável estática ainda está protegida e usada apenas dentro do escopo da função use_foo ()

Neste exemplo, chame as funções foo () exatamente como (com relação ao equivalente correspondente em 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

se a classe Foo for definida restritamente como uma classe singleton, isso seria o ideal. Isso tornaria mais pitônico.

yash
fonte
-1

Claro que essa é uma pergunta antiga, mas acho que posso fornecer alguma atualização.

Parece que o argumento de desempenho é obsoleto. O mesmo conjunto de testes parece fornecer resultados semelhantes para siInt_try e isInt_re2. Obviamente, os resultados variam, mas esta é uma sessão no meu computador com o python 3.4.4 no kernel 4.3.01 com o Xeon W3550. Já o executei várias vezes e os resultados parecem semelhantes. Mudei o regex global para a função estática, mas a diferença de desempenho é insignificante.

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

Com o problema de desempenho fora do caminho, parece que o try / catch produziria o código mais à prova de futuro e de base de milho, portanto, talvez apenas o envolva na função

Keji Li
fonte
1
O que você está comparando aqui? Parece um comentário sobre outras respostas, mas não está claro quais e não responde à pergunta em si.
ShadowRanger