Чтение-запись Закрытия Python

Закрытия являются невероятно полезной функцией языка. Они позволяют нам сделать умные вещи, которые иначе взяли бы много кода, и часто позволять нам написать код, который является более изящным и более четким. В 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
34
задан Ethan Furman 24 September 2011 в 05:14
поделиться

7 ответов

[

] Расширить ответ Игнасио:[

] [
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[] - более идиоматичны.[

].
29
ответ дан 27 November 2019 в 16:12
поделиться

нелокального в 3.x должен это исправить.

.
17
ответ дан 27 November 2019 в 16:12
поделиться

Функции также могут иметь атрибуты, так что это тоже сработает:

def counter():
    def c():
        while True:
            yield c.count
            c.count += 1
    c.count = 0
    return c

Однако в этом конкретном примере я бы использовал генератор, как предложил jbochi.

Что касается почему, то я не могу сказать наверняка, но думаю, что это не явный выбор дизайна, а скорее остаток иногда устаревших правил Python (и особенно несколько устаревших правил).

.
7
ответ дан 27 November 2019 в 16:12
поделиться

Я бы использовал генератор:

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

13
ответ дан 27 November 2019 в 16:12
поделиться

Вы могли бы сделать это, и это сработало бы более или менее одинаково:

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.)

В любом случае,

22
ответ дан 27 November 2019 в 16:12
поделиться

Дело не в том, что они доступны только для чтения, а в том, что сфера их применения более строгая, как вы понимаете. Если вы не можете использовать нелокальную на 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
1
ответ дан 27 November 2019 в 16:12
поделиться

Такое поведение довольно подробно объяснено в официальном учебном пособии Python , а также в модели выполнения Python . В частности, из учебника:

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

Однако это ничего не говорит о , почему он ведет себя таким образом.

Дополнительная информация взята из PEP 3104 , который пытается разрешить эту ситуацию для Python 3.0.
Здесь вы можете увидеть, что это так, потому что в определенный момент времени он считался лучшим решением вместо введения классических статических вложенных областей видимости (см. Re: Scoping (была ли решена привязка Re: Lambda?) ).

Тем не менее, у меня также есть собственная интерпретация.
Python реализует пространства имен как словари; когда поиск для переменной не удается во внутреннем, тогда он пытается во внешнем и так далее, пока не достигнет встроенных функций.
Однако привязка переменной - это совершенно другое дело, потому что вам нужно указать конкретное пространство имен - это всегда самое внутреннее (если вы не установите флаг «global», это означает, что это всегда глобальное пространство имен).
В конце концов, различные алгоритмы, используемые для поиска и привязки переменных, являются причиной того, что закрытие доступно только для чтения в Python.
Но, опять же, это всего лишь мои предположения: -)

6
ответ дан 27 November 2019 в 16:12
поделиться
Другие вопросы по тегам:

Похожие вопросы: