нелокальное ключевое слово в Python 2.x - PullRequest
109 голосов
/ 07 июля 2010

Я пытаюсь реализовать замыкание в Python 2.6, и мне нужно получить доступ к нелокальной переменной, но кажется, что это ключевое слово недоступно в Python 2.x. Как получить доступ к нелокальным переменным в замыканиях в этих версиях python?

Ответы [ 10 ]

116 голосов
/ 07 июля 2010

Внутренние функции могут читать нелокальные переменные в 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
37 голосов
/ 16 апреля 2013

Следующее решение основано на ответе Элиаса Замарии , но, вопреки этому ответу, правильно обрабатывает многочисленные вызовы внешней функции. «Переменная» 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)
29 голосов
/ 10 февраля 2015

Вместо словаря, есть меньше беспорядка для нелокального класса . Модификация @CrisB's пример :

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 (а не просто новый экземпляр). Таким образом, он избегает @ остерегаться Натаниэля об общем контексте.

g = outer()
assert g() == 1
assert g() == 2

assert f() == 5
14 голосов
/ 07 июля 2010

Я думаю, что ключом здесь является то, что вы подразумеваете под «доступом». Не должно быть проблем с чтением переменной вне области закрытия, например,

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
12 голосов
/ 10 декабря 2012

Существует еще один способ реализации нелокальных переменных в 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

Излишне использовать имя функции в присваиванииутверждение переменной, но для меня это выглядит проще и понятнее, чем помещение переменной в словарь.Значение запоминается от одного звонка к другому, как в ответе Криса Б.

10 голосов
/ 16 февраля 2014

Вот что-то вдохновленное предложением Алоиса Махдала, сделанным в комментарии относительно другого ответа :

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.

3 голосов
/ 10 января 2014

Еще один способ сделать это (хотя это слишком многословно):

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
3 голосов
/ 01 мая 2012

В правилах видимости 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.

0 голосов
/ 12 января 2015

Расширяя элегантное решение 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)
0 голосов
/ 10 ноября 2013

Использовать глобальную переменную

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

Лично мне не нравятся глобальные переменные. Но мое предложение основано на https://stackoverflow.com/a/19877437/1083704 ответе

def report():
        class Rank: 
            def __init__(self):
                report.ranks += 1
        rank = Rank()
report.ranks = 0
report()

, где пользователю необходимо объявить глобальную переменную ranks, каждый раз, когда вам нужно вызвать report. Мое улучшение устраняет необходимость инициализации переменных функции от пользователя.

...