Нелокальное ключевое слово в 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, Мое улучшение устраняет необходимость инициализации переменных функции от пользователя.

Другие вопросы по тегам