Respostas:
As funções internas podem ler variáveis não locais em 2.x, mas não religá- las. Isso é irritante, mas você pode contornar isso. Basta criar um dicionário e armazenar seus dados como elementos nele. As funções internas não estão proibidas de alterar os objetos aos quais as variáveis não locais se referem.
Para usar o exemplo da Wikipedia:
def outer():
d = {'y' : 0}
def inner():
d['y'] += 1
return d['y']
return inner
f = outer()
print(f(), f(), f()) #prints 1 2 3
def inner(): print d; d = {'y': 1}
. Aqui, print d
lê externo d
, criando assim uma variável não local d
no escopo interno.
X = 1
simplesmente vincula o nome X
a um objeto específico ( int
e ao valor 1
). X = 1; Y = X
vincula dois nomes ao mesmo objeto exato. De qualquer forma, alguns objetos são mutáveis e você pode alterar seus valores.
A solução a seguir é inspirada na resposta de Elias Zamaria , mas, ao contrário dessa resposta, trata corretamente várias chamadas da função externa. A "variável" inner.y
é local para a chamada atual de outer
. Só que não é uma variável, já que isso é proibido, mas um atributo do objeto (sendo o objeto a inner
própria função ). Isso é muito feio (observe que o atributo só pode ser criado depois que a inner
função for definida), mas parece eficaz.
def outer():
def inner():
inner.y += 1
return inner.y
inner.y = 0
return inner
f = outer()
g = outer()
print(f(), f(), g(), f(), g()) #prints (1, 2, 1, 3, 2)
inc()
e um dec()
retornou de exterior que aumentar e diminuir um contador compartilhada. Em seguida, você precisa decidir a qual função anexar o valor do contador atual e fazer referência a essa função a partir das outras. O que parece um tanto estranho e assimétrico. Por exemplo, em dec()
uma linha semelhante inc.value -= 1
.
Em vez de um dicionário, há menos confusão em uma classe não local . Modificando o exemplo de @ChrisB :
def outer():
class context:
y = 0
def inner():
context.y += 1
return context.y
return inner
Então
f = outer()
assert f() == 1
assert f() == 2
assert f() == 3
assert f() == 4
Cada chamada outer () cria uma classe nova e distinta chamada contexto (não apenas uma nova instância). Portanto, evita o cuidado de @Nathaniel sobre o contexto compartilhado.
g = outer()
assert g() == 1
assert g() == 2
assert f() == 5
__slots__ = ()
e criar um objeto em vez de usar a classe, por exemplo context.z = 3
, levantaria um AttributeError
. É possível para todas as classes, a menos que herdem de uma classe que não define slots.
Acho que a chave aqui é o que você entende por "acesso". Não deve haver problema com a leitura de uma variável fora do escopo de fechamento, por exemplo,
x = 3
def outer():
def inner():
print x
inner()
outer()
deve funcionar como esperado (impressão 3). No entanto, substituir o valor de x não funciona, por exemplo,
x = 3
def outer():
def inner():
x = 5
inner()
outer()
print x
continuará a imprimir 3. Pelo que entendi do PEP-3104, é isso que a palavra-chave não local deve abranger. Conforme mencionado no PEP, você pode usar uma classe para realizar a mesma coisa (meio confuso):
class Namespace(object): pass
ns = Namespace()
ns.x = 3
def outer():
def inner():
ns.x = 5
inner()
outer()
print ns.x
def ns(): pass
seguida por ns.x = 3
. Não é bonito, mas é um pouco menos feio para os meus olhos.
class Namespace: x = 3
?
ns
é um objeto global que é o motivo pelo qual você pode fazer referência ns.x
no nível do módulo na print
instrução no final .
Há outra maneira de implementar variáveis não locais no Python 2, caso alguma das respostas aqui seja indesejável por qualquer motivo:
def outer():
outer.y = 0
def inner():
outer.y += 1
return outer.y
return inner
f = outer()
print(f(), f(), f()) #prints 1 2 3
É redundante usar o nome da função na instrução de atribuição da variável, mas parece mais simples e limpo para mim do que colocar a variável em um dicionário. O valor é lembrado de uma chamada para outra, assim como na resposta de Chris B..
f = outer()
e depois o fizer g = outer()
, f
o contador de será reiniciado. Isso ocorre porque ambos compartilham uma única outer.y
variável, em vez de cada um ter sua própria variável independente. Embora este código pareça mais esteticamente agradável do que a resposta de Chris B, sua maneira parece ser a única maneira de emular o escopo léxico se você quiser chamar outer
mais de uma vez.
outer.y
não envolve nada local para a chamada de função (instância) outer()
, mas atribui a um atributo do objeto de função que está vinculado ao nome outer
em seu escopo delimitador . E, portanto, alguém poderia igualmente ter usado, por escrito outer.y
, qualquer outro nome em vez de outer
, desde que seja conhecido por estar vinculado a esse escopo. Isso está correto?
outer.y
usar o nome inner.y
(uma vez que inner
está vinculado dentro da chamada outer()
, que é exatamente o escopo que queremos), mas colocando a inicialização inner.y = 0
após a definição de interno (já que o objeto deve existir quando seu atributo é criado), mas é claro antes return inner
?
Aqui está algo inspirado por uma sugestão de Alois Mahdal feita em um comentário sobre outra resposta :
class Nonlocal(object):
""" Helper to implement nonlocal names in Python 2.x """
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def outer():
nl = Nonlocal(y=0)
def inner():
nl.y += 1
return nl.y
return inner
f = outer()
print(f(), f(), f()) # -> (1 2 3)
Atualizar
Depois de olhar para trás recentemente, fiquei impressionado com o quão parecido com o decorador ele era - quando me dei conta de que implementá-lo como um iria torná-lo mais genérico e útil (embora isso possa degradar sua legibilidade em algum grau).
# Implemented as a decorator.
class Nonlocal(object):
""" Decorator class to help implement nonlocal names in Python 2.x """
def __init__(self, **kwargs):
self._vars = kwargs
def __call__(self, func):
for k, v in self._vars.items():
setattr(func, k, v)
return func
@Nonlocal(y=0)
def outer():
def inner():
outer.y += 1
return outer.y
return inner
f = outer()
print(f(), f(), f()) # -> (1 2 3)
Observe que ambas as versões funcionam em Python 2 e 3.
Há uma verruga nas regras de escopo do python - a atribuição torna uma variável local para seu escopo de função imediatamente envolvente. Para uma variável global, você resolveria isso com a global
palavra - chave.
A solução é introduzir um objeto que é compartilhado entre os dois escopos, que contém variáveis mutáveis, mas é referenciado por meio de uma variável que não é atribuída.
def outer(v):
def inner(container = [v]):
container[0] += 1
return container[0]
return inner
Uma alternativa é o hackeamento de escopos:
def outer(v):
def inner(varname = 'v', scope = locals()):
scope[varname] += 1
return scope[varname]
return inner
Você pode ser capaz de descobrir alguns truques para obter o nome do parâmetro e outer
, em seguida, passá-lo como varname, mas sem depender do nome, outer
você gostaria de usar um combinador Y.
nonlocal
. locals()
cria um dicionário de outer()
locais no momento em que inner()
é definido, mas alterar esse dicionário não altera o v
in outer()
. Isso não funcionará mais quando você tiver mais funções internas que desejam compartilhar uma variável fechada. Digamos que um inc()
e dec()
que aumentar e diminuir um contador compartilhada.
nonlocal
é um recurso do Python 3.
nonlocal
no Python 2 em geral . Suas ideias não cobrem o caso geral, mas apenas aquele com uma função interna. Dê uma olhada nesta essência para um exemplo. Ambas as funções internas têm seu próprio contêiner. Você precisa de um objeto mutável no escopo da função externa, como outras respostas já sugeridas.
nonlocal
palavra - chave introduzida no Python 3.
Outra maneira de fazer isso (embora seja muito prolixo):
import ctypes
def outer():
y = 0
def inner():
ctypes.pythonapi.PyCell_Set(id(inner.func_closure[0]), id(y + 1))
return y
return inner
x = outer()
x()
>> 1
x()
>> 2
y = outer()
y()
>> 1
x()
>> 3
Estendendo a elegante solução Martineau acima para um caso de uso prático e um pouco menos elegante, recebo:
class nonlocals(object):
""" Helper to implement nonlocal names in Python 2.x.
Usage example:
def outer():
nl = nonlocals( n=0, m=1 )
def inner():
nl.n += 1
inner() # will increment nl.n
or...
sums = nonlocals( { k:v for k,v in locals().iteritems() if k.startswith('tot_') } )
"""
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def __init__(self, a_dict):
self.__dict__.update(a_dict)
Use uma variável global
def outer():
global y # import1
y = 0
def inner():
global y # import2 - requires import1
y += 1
return y
return inner
f = outer()
print(f(), f(), f()) #prints 1 2 3
Pessoalmente, não gosto das variáveis globais. Mas, minha proposta é baseada em https://stackoverflow.com/a/19877437/1083704 resposta
def report():
class Rank:
def __init__(self):
report.ranks += 1
rank = Rank()
report.ranks = 0
report()
onde o usuário precisa declarar uma variável global ranks
, toda vez que você precisa chamar o report
. Meu aprimoramento elimina a necessidade de inicializar as variáveis de função do usuário.
inner
, mas não pode atribuir a ela, mas pode modificar suas chaves e valores. Isso evita o uso de variáveis globais.