Відповіді:
Внутрішні функції можуть читати нелокальні змінні в 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
def inner(): print d; d = {'y': 1}
. Тут print d
читається зовнішня, d
створюючи таким чином нелокальну змінну d
у внутрішній області.
X = 1
просто пов'язує ім'я X
з певним об'єктом (а int
зі значенням 1
). X = 1; Y = X
прив’язує два імені до одного і того ж точного об’єкта. У всякому разі, деякі об'єкти є змінними і ви можете змінити їх значення.
Наступне рішення натхнене відповіддю Еліаса Замарії , але всупереч цій відповіді правильно обробляти кілька викликів зовнішньої функції. "Змінна" 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)
inc()
та " dec()
return", що повернувся із зовнішнього збільшення та зменшення загального лічильника. Тоді ви повинні вирішити, до якої функції слід приєднати поточне значення лічильника та посилатись на цю функцію з інших (-ів). Що виглядає дещо дивно і несиметрично. Наприклад у dec()
такій лінії, як inc.value -= 1
.
Замість словника, до нелокального класу є менше скупчення . Зміна @ 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
__slots__ = ()
та створюючи об’єкт замість використання класу, наприклад context.z = 3
, піднімемо AttributeError
. Це можливо для всіх класів, якщо вони не успадковують клас, який не визначає слоти.
Я думаю, тут ключовим є те, що ви маєте на увазі під "доступом". Не повинно виникнути проблем з читанням змінної за межами області закриття, наприклад,
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
. Це не дуже, але це трохи менш потворно для мого ока.
class Namespace: x = 3
?
ns
- це глобальний об'єкт, саме тому ви можете посилатися ns.x
на рівні модуля в print
заяві в самому кінці .
Є ще один спосіб реалізації нелокальних змінних в 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
Зайвим є використання назви функції в операторі присвоєння змінної, але для мене це виглядає більш простим і чистішим, ніж розміщення змінної у словнику. Значення запам'ятовується від одного дзвінка до іншого, як і у відповіді Кріса Б.
f = outer()
а потім пізніше g = outer()
, тоді f
лічильник буде скинутий. Це тому, що вони обидва ділять одну outer.y
змінну, а не кожну свою незалежну. Хоча цей код виглядає більш естетично, ніж відповідь Кріса Б., здається, його спосіб є єдиним способом наслідувати лексичне визначення, якщо ви хочете зателефонувати outer
не один раз.
outer.y
не передбачає нічого локального для виклику функції (екземпляра) outer()
, але призначає атрибут об'єкта функції, який пов'язаний з ім'ям outer
в його обкладинці . І тому можна було б однаково добре використовували, в письмовому вигляді outer.y
, будь-яке інше ім'я замість outer
, при умови , що , як відомо, пов'язані в цій області. Це правильно?
outer.y
використовувати ім’я inner.y
(оскільки inner
прив’язане всередині виклику outer()
, саме такий обсяг, який ми хочемо), але ставити ініціалізація inner.y = 0
після визначення внутрішнього (як об'єкт повинен існувати, коли створюється його атрибут), але звичайно раніше return inner
?
Ось щось, натхнене пропозицією Алоїза Магдала, зробленої в коментарі щодо іншої відповіді :
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.
Існує бородавка в правилах розміщення 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()
що збільшення і зменшення загального лічильника.
nonlocal
- функція python 3.
nonlocal
у Python 2 загалом . Ваші ідеї не стосуються загальної справи, а лише тієї, що має одну внутрішню функцію. Погляньте на приклад цю суть . Обидві внутрішні функції мають свій контейнер. Вам потрібен змінний об'єкт в області зовнішньої функції, як це вже запропоновано в інших відповідях.
nonlocal
ключового слова, введеного в Python 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
Розширюючи вишукане рішення 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
Особисто мені не подобаються глобальні змінні. Але моя пропозиція заснована на 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
, але не можете призначити його, але ви можете змінити його ключі та значення. Це дозволяє уникнути використання глобальних змінних.