Чтение / запись закрытий на 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 

  • Неизменяемая матрица?
  • python - почему свойство read-only доступно для записи?
  • SQLAlchemy - способ сопоставления с единственным (или рассчитанным) свойством
  • Свойство Python для чтения
  • Оптимизация SqlAlchemy для объектных моделей только для чтения
  • 7 Solutions collect form web for “Чтение / запись закрытий на Python”

    Чтобы расширить ответ Игнасио:

     def counter(): count = 0 def c(): nonlocal count count += 1 return count return c x = counter() print([x(),x(),x()]) 

    дает [1,2,3] в Python 3; invoice of counter() дают независимые счетчики. Другие решения – особенно использование itertools / yield более идиоматичны.

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

     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 довольно ограничена: «потому что Гвидо почувствовал это». Python был разработан в начале 90-х, в период расцвета OO. Закрытие было довольно низким в списке желаемых языковых функций. Поскольку функциональные идеи, такие как функции первого класса, закрытие и другие вещи, попадают в популярность, такие языки, как Python, должны были их использовать, поэтому их использование может немного неудобно, потому что это не то, для чего был разработан язык.

    <rant on="Python scoping">

    Кроме того, у Python (2.x) есть довольно странные (на мой взгляд) идеи о области, которые мешают разумной реализации замыканий, между прочим. Меня всегда беспокоит, что это:

     new = [x for x in old] 

    Оставляет нас с именем x определенным в области, в которой мы его использовали, поскольку это (на мой взгляд) концептуально меньший объем. (Хотя Python получает точки для согласованности, поскольку одно и то же с циклом for имеет такое же поведение. Единственный способ избежать этого – использовать map .)

    Во всяком случае, </rant>

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

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

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

    Это ограничение неоднократно поднималось в списке рассылки Python-Dev и в других местах и ​​привело к расширенному обсуждению и множеству предложений о способах устранения этого ограничения. Этот PEP обобщает различные альтернативы, которые были предложены, вместе с преимуществами и недостатками, которые были упомянуты для каждого.

    До версии 2.1 обработка Python по областям напоминала работу стандарта C: внутри файла были только два уровня видимости, глобальные и локальные. В C это является естественным следствием того, что определения функций не могут быть вложенными. Но в Python, хотя функции обычно определяются на верхнем уровне, определение функции может выполняться в любом месте. Это дало Python синтаксический вид вложенного охвата без семантики и дало несогласованности, которые были удивительны для некоторых программистов – например, рекурсивная функция, работавшая на верхнем уровне, перестала бы работать, когда перемещалась внутри другой функции, потому что рекурсивная функция собственное имя больше не будет видно в области его тела. Это нарушает интуицию, что функция должна вести себя последовательно при размещении в разных контекстах.

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

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

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

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

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

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

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

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

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

    Дело не в том, что они доступны только для чтения, так как объем более строгий, чем вы понимаете. Если вы не можете nonlocal в 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 

    Требуется немного больше работы, чтобы иметь более ограниченную область для переменной count, вместо использования псевдоглобальной переменной модуля (все еще 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 - лучший язык программирования в мире.