Я пытаюсь реализовать закрытие в Python 2.6, и мне нужно получить доступ к нелокальной переменной, но похоже, что это ключевое слово недоступно в python 2.x. Как получить доступ к нелокальным переменным в замыканиях в этих версиях python?
Ключевое слово nonlocal в Python 2.x
Ответ 1
Внутренние функции могут читать нелокальные переменные в 2.x, просто не перепроверяя их. Это раздражает, но вы можете обойти это. Просто создайте словарь и сохраните свои данные в качестве элементов. Внутренние функции не запрещают мутировать объекты, к которым относятся нелокальные переменные.
Чтобы использовать этот пример из Википедии:
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
Ответ 2
Следующее решение вдохновлено ответом Elias Zamaria, но вопреки этому ответу правильно обрабатывается несколько вызовов внешней функции. "Variable" inner.y
является локальным для текущего вызова outer
. Только это не переменная, так как это запрещено, но атрибут объекта (объект является самой функцией inner
). Это очень уродливо (обратите внимание, что атрибут может быть создан только после определения функции inner
), но кажется эффективным.
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)
Ответ 3
Вместо словаря там меньше помех для нелокального класса. Изменение примера @ChrisB:
def outer():
class context:
y = 0
def inner():
context.y += 1
return context.y
return inner
затем
f = outer()
assert f() == 1
assert f() == 2
assert f() == 3
assert f() == 4
Каждый вызов external() создает новый и отдельный класс, называемый контекстом (а не просто новым экземпляром). Поэтому он избегает @Nathaniel остерегаться общего контекста.
g = outer()
assert g() == 1
assert g() == 2
assert f() == 5
Ответ 4
Я думаю, что ключ здесь - это то, что вы подразумеваете под "доступом". Не должно быть проблем с чтением переменной вне области закрытия, например,
x = 3
def outer():
def inner():
print x
inner()
outer()
должен работать как ожидалось (печать 3). Однако переопределение значения x не работает, например,
x = 3
def outer():
def inner():
x = 5
inner()
outer()
print x
все равно будет печатать 3. Из моего понимания PEP-3104 это то, что должно охватывать нелокальное ключевое слово. Как упоминалось в PEP, вы можете использовать класс, чтобы выполнить одно и то же (вроде беспорядочно):
class Namespace(object): pass
ns = Namespace()
ns.x = 3
def outer():
def inner():
ns.x = 5
inner()
outer()
print ns.x
Ответ 5
Существует еще один способ реализовать нелокальные переменные в Python 2, если любой из ответов здесь по какой-либо причине нежелателен:
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
Слишком просто использовать имя функции в инструкции присваивания переменной, но для меня это выглядит проще и чище, чем перенос переменной в словарь. Значение запоминается от одного вызова другому, как и в ответе Криса Б.
Ответ 6
Вот что-то, вдохновленное предложением Алоиса Махдала в комментарии относительно другого ответа:
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)
Обновить
Оглядываясь на это недавно, я был поражен тем, насколько он похож на декоратор - когда до меня дошло, что реализация его как одного сделает его более универсальным и полезным (хотя это, возможно, в некоторой степени ухудшает его читабельность).
# 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)
Обратите внимание, что обе версии работают как в Python 2, так и в 3.
Ответ 7
В правилах видимости python существует бородавка - присваивание делает локальную переменную локалируемой в ее непосредственном окружении. Для глобальной переменной вы решили бы это с помощью ключевого слова global
.
Решение состоит в том, чтобы ввести объект, который разделяется между двумя областями, который содержит изменяемые переменные, но сам ссылается на переменную, которая не назначена.
def outer(v):
def inner(container = [v]):
container[0] += 1
return container[0]
return inner
Альтернативой может быть несколько хакеров областей:
def outer(v):
def inner(varname = 'v', scope = locals()):
scope[varname] += 1
return scope[varname]
return inner
Возможно, вам удастся выяснить какую-то обманку, чтобы получить имя параметра outer
, а затем передать его как varname, но, не полагаясь на имя outer
, вам нужно использовать Y combinator.
Ответ 8
Другой способ сделать это (хотя и слишком подробный):
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
Ответ 9
Расширение элегантного решения Martineau выше практического и несколько менее элегантного варианта использования:
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)
Ответ 10
Использовать глобальную переменную
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
Лично мне не нравятся глобальные переменные. Но мое предложение основано на fooobar.com/info/61949/... answer
def report():
class Rank:
def __init__(self):
report.ranks += 1
rank = Rank()
report.ranks = 0
report()
где пользователь должен объявить глобальную переменную ranks
, каждый раз, когда вам нужно вызвать report
. Мое улучшение устраняет необходимость инициализации функциональных переменных от пользователя.