нелокальне ключове слово в Python 2.x


117

Я намагаюся реалізувати закриття в Python 2.6, і мені потрібно отримати доступ до нелокальної змінної, але здається, що це ключове слово недоступне в python 2.x. Як слід отримувати доступ до нелокальних змінних у закриттях у цих версіях python?

Відповіді:


125

Внутрішні функції можуть читати нелокальні змінні в 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

4
чому можна змінити значення зі словника?
coelhudo

9
@coelhudo Тому що ви можете змінювати нелокальні змінні. Але ви не можете виконувати призначення нелокальних змінних. Наприклад, це підніме UnboundLocalError: def inner(): print d; d = {'y': 1}. Тут print dчитається зовнішня, dстворюючи таким чином нелокальну змінну dу внутрішній області.
suzanshakya

21
Дякую за цю відповідь. Я думаю, ви могли б вдосконалити термінологію: замість "може читати, не можу змінити", можливо "може посилатися, не можу призначити". Ви можете змінити вміст об'єкта в нелокальній області, але ви не можете змінити, на який об'єкт йдеться.
метамат

3
Крім того, ви можете використовувати довільний клас та об'єкт інстанції замість словника. Я вважаю це набагато більш елегантним і читабельним, оскільки він не заливає код буквами (словниковими ключами), плюс ви можете використовувати методи.
Алоїз Магдал

6
Для Python я вважаю, що найкраще використовувати дієслово "прив'язувати" чи "відновлювати" та використовувати іменник "ім'я", а не "змінна". У Python 2.x ви можете закрити об'єкт, але не зможете відновити ім'я в початковій області. У такій мові, як C, оголошення змінної залишає деяке сховище (або статичне сховище, або тимчасове на стеку). У Python вираз X = 1просто пов'язує ім'я Xз певним об'єктом (а intзі значенням 1). X = 1; Y = Xприв’язує два імені до одного і того ж точного об’єкта. У всякому разі, деякі об'єкти є змінними і ви можете змінити їх значення.
steveha

37

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

Це не повинно бути негарним. Замість використання Internal.y використовуйте external.y. Ви можете визначити external.y = 0 перед початком def.
jgomo3

1
Гаразд, я бачу, що мій коментар невірний. Еліас Замарія також здійснив рішення external.y. Але як коментує Натаніел [1], ви повинні бути обачними. Я думаю, що цю відповідь слід пропагувати як рішення, але зауважте про рішення. [1] stackoverflow.com/questions/3190706 / ...
jgomo3

Це стає некрасивим, якщо потрібно більше однієї внутрішньої функції, яка ділиться нелокальним станом, що змінюється. Скажіть "a" inc()та " dec()return", що повернувся із зовнішнього збільшення та зменшення загального лічильника. Тоді ви повинні вирішити, до якої функції слід приєднати поточне значення лічильника та посилатись на цю функцію з інших (-ів). Що виглядає дещо дивно і несиметрично. Наприклад у dec()такій лінії, як inc.value -= 1.
BlackJack

33

Замість словника, до нелокального класу є менше скупчення . Зміна @ 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

Кожен зовнішній () виклик створює новий і чіткий клас, який називається контекстом (а не лише новим екземпляром). Таким чином, це уникає побоювання @ Nathaniel про спільний контекст.

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

assert f() == 5

Приємно! Це дійсно більш елегантно і читабельно, ніж словник.
Вінченцо

Я б рекомендував тут використовувати слоти взагалі. Це захищає вас від помилок.
DerWeh

@DerWeh цікаво, я ніколи про це не чув . Не могли б ви сказати те саме для будь-якого класу? Я ненавиджу, коли мене спалахують друкарські помилки.
Боб Штейн

@BobStein Вибачте, я не дуже розумію, що ви маєте на увазі. Але додаючи __slots__ = ()та створюючи об’єкт замість використання класу, наприклад context.z = 3, піднімемо AttributeError. Це можливо для всіх класів, якщо вони не успадковують клас, який не визначає слоти.
DerWeh

@DerWeh Я ніколи не чув про використання слотів для лову помилок. Ви маєте рацію, це допоможе лише в екземплярах класу, а не в змінних класу. Можливо, ви хочете створити робочий приклад на pyfiddle. Було б потрібно щонайменше дві додаткові лінії. Не можу уявити, як би ви це зробили, без більшої безладу.
Боб Штейн

14

Я думаю, тут ключовим є те, що ви маєте на увазі під "доступом". Не повинно виникнути проблем з читанням змінної за межами області закриття, наприклад,

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

Замість того, щоб створити клас та інстанціювати його, можна просто створити функцію: def ns(): passдалі ns.x = 3. Це не дуже, але це трохи менш потворно для мого ока.
davidchambers

2
Якась конкретна причина для голосування? Зізнаюся, моє не найвишуканіше рішення, але воно працює ...
ig0774

2
Про що class Namespace: x = 3?
Feuermurmel

3
Я не думаю, що цей спосіб імітує нелокальне, оскільки він створює глобальну посилання замість посилання в закритті.
CarmeloS

1
Я погоджуюся з @CarmeloS, ваш останній блок коду не виконувати те, що PEP-3104 пропонує як спосіб здійснити щось подібне в Python 2. ns- це глобальний об'єкт, саме тому ви можете посилатися ns.xна рівні модуля в printзаяві в самому кінці .
мартіно

13

Є ще один спосіб реалізації нелокальних змінних в 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

Зайвим є використання назви функції в операторі присвоєння змінної, але для мене це виглядає більш простим і чистішим, ніж розміщення змінної у словнику. Значення запам'ятовується від одного дзвінка до іншого, як і у відповіді Кріса Б.


19
Будь ласка, будьте уважні: якщо реалізовано таким чином, якщо ви зробите це, f = outer()а потім пізніше g = outer(), тоді fлічильник буде скинутий. Це тому, що вони обидва ділять одну outer.y змінну, а не кожну свою незалежну. Хоча цей код виглядає більш естетично, ніж відповідь Кріса Б., здається, його спосіб є єдиним способом наслідувати лексичне визначення, якщо ви хочете зателефонувати outerне один раз.
Натаніел

@Nathaniel: Дозвольте мені подивитися, чи правильно я це розумію. Призначення outer.yне передбачає нічого локального для виклику функції (екземпляра) outer(), але призначає атрибут об'єкта функції, який пов'язаний з ім'ям outerв його обкладинці . І тому можна було б однаково добре використовували, в письмовому вигляді outer.y, будь-яке інше ім'я замість outer, при умови , що , як відомо, пов'язані в цій області. Це правильно?
Марк ван Левен

1
Виправлення, я мав би сказати, після "пов'язаного в цій області": до об'єкта, тип якого дозволяє встановити атрибути (наприклад, функції або будь-якого екземпляра класу). Крім того, оскільки ця сфера дійсно виходить за рамки тієї, яку ми хочемо, чи не запропонувало б таке надзвичайно потворне рішення: замість того, щоб outer.yвикористовувати ім’я inner.y(оскільки innerприв’язане всередині виклику outer(), саме такий обсяг, який ми хочемо), але ставити ініціалізація inner.y = 0 після визначення внутрішнього (як об'єкт повинен існувати, коли створюється його атрибут), але звичайно раніше return inner?
Марк ван Левен

@MarcvanLeeuwen Схоже, ваш коментар надихнув рішення на I. Гарна думка.
Анкур Агарвал

Доступ та оновлення глобальних змінних - це, мабуть, не те, що намагаються зробити більшість людей під час вимкнення записів про закриття.
бінкі

12

Ось щось, натхнене пропозицією Алоїза Магдала, зробленої в коментарі щодо іншої відповіді :

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

Існує бородавка в правилах розміщення 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-комбінатор.


Обидві версії працюють у цьому конкретному випадку, але не є заміною nonlocal. locals()створює словник outer()s місцевих жителів на час inner()визначено, але зміна цього словника не змінює vв outer(). Це більше не працюватиме, якщо у вас більше внутрішніх функцій, які хочуть ділитися закритою над змінною. Замовте inc()і dec()що збільшення і зменшення загального лічильника.
BlackJack

@BlackJack nonlocal- функція python 3.
Марцін

Так, я знаю. Питання полягало в тому, як досягти ефекту Python 3nonlocal у Python 2 загалом . Ваші ідеї не стосуються загальної справи, а лише тієї, що має одну внутрішню функцію. Погляньте на приклад цю суть . Обидві внутрішні функції мають свій контейнер. Вам потрібен змінний об'єкт в області зовнішньої функції, як це вже запропоновано в інших відповідях.
BlackJack

@BlackJack Це не питання, але дякую за ваш внесок.
Марцін

Так, це питання. Погляньте вгорі сторінки. adinsa має Python 2.6 і потребує доступу до нелокальних змінних у закритому режимі без nonlocalключового слова, введеного в Python 3.
BlackJack

3

Ще один спосіб зробити це (хоча це занадто багатослівно):

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

1
насправді я віддаю перевагу рішенню за допомогою словника, але цей класний :)
Ezer Fernandes

0

Розширюючи вишукане рішення 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)

-3

Використовуйте глобальну змінну

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. Моє вдосконалення усуває необхідність ініціалізувати змінні функції у користувача.


Набагато краще використовувати словник. Ви можете посилатися на екземпляр inner, але не можете призначити його, але ви можете змінити його ключі та значення. Це дозволяє уникнути використання глобальних змінних.
johannestaas
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.