Закрытия являются невероятно полезной функцией языка. Они позволяют нам сделать умные вещи, которые иначе взяли бы много кода, и часто позволять нам написать код, который является более изящным и более четким. В Python 2.x имена переменной закрытий не могут быть восстановлением; то есть, функция, определяемая в другом лексическом контексте не может сделать чего-то как some_var = 'changed!'
для переменных за пределами его локального объема. Кто-то может объяснить, почему это? Были ситуации, в которых я хотел бы создать закрытие, которое снова переплетает переменные во внешнем объеме, но это не было возможно. Я понимаю, что почти во всех случаях (если не все они), это поведение может быть достигнуто с классами, но это часто не как чистое или как изящное. Почему я не могу сделать этого с закрытием?
Вот пример закрытия повторного переплетения:
def counter():
count = 0
def c():
count += 1
return count
return c
Это - текущее поведение при вызове его:
>>> c()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in c
UnboundLocalError: local variable 'count' referenced before assignment
Что я хотел бы, чтобы это сделало, вместо этого это:
>>> c()
1
>>> c()
2
>>> c()
3
] Расширить ответ Игнасио:[
] [def counter():
count = 0
def c():
nonlocal count
count += 1
return count
return c
x = counter()
print([x(),x(),x()])
]
[] дает [1,2,3] на питоне 3; вызовы [] counter()[
] дают независимые счетчики. Другие решения - особенно использование []итертуалов[
]/[]yield[
] - более идиоматичны.[
Функции также могут иметь атрибуты, так что это тоже сработает:
def counter():
def c():
while True:
yield c.count
c.count += 1
c.count = 0
return c
Однако в этом конкретном примере я бы использовал генератор, как предложил jbochi.
Что касается почему, то я не могу сказать наверняка, но думаю, что это не явный выбор дизайна, а скорее остаток иногда устаревших правил Python (и особенно несколько устаревших правил).
.Я бы использовал генератор:
>>> def counter():
count = 0
while True:
count += 1
yield(count)
>>> c = counter()
>>> c.next()
1
>>> c.next()
2
>>> c.next()
3
EDIT: Полагаю, что конечный ответ на Ваш вопрос - PEP-3104:
На большинстве языков, поддерживающих вложенность диапазоны, код может относиться или переоткрывать (назначить) любое имя в ближайшем Прилагаемый прицел. В настоящее время Питон код может относиться к имени в любом ограждающий прицел, но он может только перепривязывать имена в двух диапазонах: локальный сфера применения (простым заданием) или модуль-глобальный охват (с использованием глобального Заявление).
Это ограничение было затронуто во многих случаях. в списке рассылки Python-Dev. и в других местах, и это привело к тому, что обсуждение и множество предложений о путях чтобы снять это ограничение. Этот ПЭП обобщает различные альтернативы которые были предложены, вместе с преимуществами и недостатками, которые были упомянуты для каждого.
До версии 2.1, лечение Питона масштабы напоминают масштабы стандартного C: в файле было только два уровни охвата, глобальный и локальный. На сайте . C, это естественное следствие тот факт, что определения функций не может быть вложена. Но в Питоне, хотя функции обычно определяются на высшем уровне, функция определение может быть выполнено где угодно. Это дало Питону синтаксический внешний вид гнездового обзора без семантика и уступила несоответствия, которые удивили некоторым программистам - например, рекурсивная функция, которая работала на верхний уровень перестанет работать, когда перешел в другую функцию, потому что собственное имя рекурсивной функции больше не будет виден в его прицел для тела. Это нарушает интуиция, что функция должна вести себя последовательно при размещении различные контексты.
Вы могли бы сделать это, и это сработало бы более или менее одинаково:
class counter(object):
def __init__(self, count=0):
self.count = count
def __call__(self):
self.count += 1
return self.count
Или, немного взломать:
def counter():
count = [0]
def incr(n):
n[0] += 1
return n[0]
return lambda: incr(count)
Я бы выбрал первое решение.
EDIT: Вот что я получаю за то, что не читаю большой текстовый блог.
В любом случае, причина, по которой закрытие Python довольно ограничено, в том, что "потому что Guido чувствовал это". Python был разработан в начале 90-х, в период расцвета OO. Закрытия были довольно низкими в списке языковых особенностей, которые люди хотели. Так как функциональные идеи, такие как первоклассные функции, замыкания, и другие вещи прокладывают себе путь в мейнстрим популярности, таким языкам, как Python, приходилось решать их, так что их использование может быть немного неудобным, потому что это не то, для чего язык был разработан.
Кроме того, у Python (2.x) есть довольно странные (на мой взгляд) идеи о замыканиях, которые мешают разумной реализации замыканий, среди всего прочего. Меня всегда беспокоит, что это:
new = [x for x in old]
оставляет нам имя x
, определенное в области применения, в которой мы его использовали, так как это (на мой взгляд) концептуально меньшая область применения. (Хотя Python получает очки за последовательность, как и в цикле для
, имеет такое же поведение. Единственный способ избежать этого - использовать map
.)
В любом случае,
Дело не в том, что они доступны только для чтения, а в том, что сфера их применения более строгая, как вы понимаете. Если вы не можете использовать нелокальную
на Python 3+, то вы можете, по крайней мере, использовать эксплицитную область видимости. На Python 2.6.1, с явной областью действия на модульном уровне:
>>> def counter():
... sys.modules[__name__].count = 0
... def c():
... sys.modules[__name__].count += 1
... return sys.modules[__name__].count
... sys.modules[__name__].c = c
...
>>> counter()
>>> c()
1
>>> c()
2
>>> c()
3
Немного больше работы требуется для того, чтобы иметь более ограниченную область действия для счетной переменной, вместо использования псевдо-глобальной модульной переменной (все равно на Python 2.6. 1):
>>> def counter():
... class c():
... def __init__(self):
... self.count = 0
... cinstance = c()
... def iter():
... cinstance.count += 1
... return cinstance.count
... return iter
...
>>> c = counter()
>>> c()
1
>>> c()
2
>>> c()
3
>>> d = counter()
>>> d()
1
>>> c()
4
>>> d()
2
Такое поведение довольно подробно объяснено в официальном учебном пособии Python , а также в модели выполнения Python . В частности, из учебника:
Особая особенность Python заключается в том, что - если нет действующего глобального заявления - присвоения имен всегда входят в самая сокровенная сфера.
Однако это ничего не говорит о , почему он ведет себя таким образом.
Дополнительная информация взята из PEP 3104 , который пытается разрешить эту ситуацию для Python 3.0.
Здесь вы можете увидеть, что это так, потому что в определенный момент времени он считался лучшим решением вместо введения классических статических вложенных областей видимости (см. Re: Scoping (была ли решена привязка Re: Lambda?) ).
Тем не менее, у меня также есть собственная интерпретация.
Python реализует пространства имен как словари; когда поиск для переменной не удается во внутреннем, тогда он пытается во внешнем и так далее, пока не достигнет встроенных функций.
Однако привязка переменной - это совершенно другое дело, потому что вам нужно указать конкретное пространство имен - это всегда самое внутреннее (если вы не установите флаг «global», это означает, что это всегда глобальное пространство имен).
В конце концов, различные алгоритмы, используемые для поиска и привязки переменных, являются причиной того, что закрытие доступно только для чтения в Python.
Но, опять же, это всего лишь мои предположения: -)