Нелокальное ключевое слово в Python 2.x
Я пытаюсь реализовать замыкание в Python 2.6, и мне нужно получить доступ к нелокальной переменной, но кажется, что это ключевое слово не доступно в Python 2.x. Как получить доступ к нелокальным переменным в замыканиях в этих версиях Python?
10 ответов
Внутренние функции могут читать нелокальные переменные в 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
Следующее решение вдохновлено ответом Элиаса Замарии, но, вопреки этому ответу, правильно обрабатывает множественные вызовы внешней функции. "Переменная" 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)
Вместо словаря, есть меньше беспорядка для нелокального класса. Изменение примера @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 () создает новый и отличный класс под названием context (а не просто новый экземпляр). Таким образом, он избегает опасения @Nathaniel об общем контексте.
g = outer()
assert g() == 1
assert g() == 2
assert f() == 5
Я думаю, что ключом здесь является то, что вы подразумеваете под "доступом". Не должно быть проблем с чтением переменной вне области закрытия, например:
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
Существует еще один способ реализации нелокальных переменных в 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
Излишне использовать имя функции в операторе присваивания переменной, но для меня это выглядит проще и понятнее, чем помещать переменную в словарь. Значение запоминается от одного звонка к другому, как в ответе Криса Б.
Вот что-то, вдохновленное предложением Алоиса Махдала в комментарии относительно другого ответа:
class Nonlocals(object):
""" Helper class to implement nonlocal names in Python 2.x """
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def outer():
nonlocals = Nonlocals(y=0)
def inner():
nonlocals.y += 1
return nonlocals.y
return inner
f = outer()
print(f(), f(), f()) # -> (1 2 3)
Обновить
Оглядываясь на это недавно, я был поражен тем, насколько он похож на декоратор, а потом до меня дошло, что его формально можно превратить в тот, который сделает его более универсальным и полезным (хотя это несколько ухудшает его читабельность).
# Implemented as a decorator.
class Nonlocals(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
@Nonlocals(y=0)
def outer():
def inner():
outer.y += 1
return outer.y
return inner
f = outer()
print(f(), f(), f()) # -> (1 2 3)
Также обратите внимание, что код в обеих версиях все еще будет работать в Python 3.x.
Еще один способ сделать это (хотя это слишком многословно):
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
В правилах области действия 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 комбинатор.
Расширяя элегантное решение 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)
Используйте глобальную переменную
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
Лично мне не нравятся глобальные переменные. Но мое предложение основано на /questions/13800128/dostup-k-lokalnoj-funktsii-peremennoj/13800139#13800139 ответе.
def report():
class Rank:
def __init__(self):
report.ranks += 1
rank = Rank()
report.ranks = 0
report()
где пользователь должен объявить глобальную переменную ranks
каждый раз, когда вам нужно позвонить report
, Мое улучшение устраняет необходимость инициализации переменных функции от пользователя.