Какие ограничения имеют замыкания в Python по сравнению с замыканиями языка X? - PullRequest
46 голосов
/ 27 сентября 2008

Где X - это любой язык программирования (C #, Javascript, Lisp, Perl, Ruby, Scheme и т. Д.), Который поддерживает некоторую разновидность замыканий.

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

Было бы здорово увидеть пример кода для конкретного ограничения.

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

Ответы [ 7 ]

44 голосов
/ 27 сентября 2008

Наиболее важным ограничением в настоящее время является то, что вы не можете назначить переменную внешней области видимости. Другими словами, замыкания доступны только для чтения:

>>> def outer(x): 
...     def inner_reads():
...         # Will return outer's 'x'.
...         return x
...     def inner_writes(y):
...         # Will assign to a local 'x', not the outer 'x'
...         x = y
...     def inner_error(y):
...         # Will produce an error: 'x' is local because of the assignment,
...         # but we use it before it is assigned to.
...         tmp = x
...         x = y
...         return tmp
...     return inner_reads, inner_writes, inner_error
... 
>>> inner_reads, inner_writes, inner_error = outer(5)
>>> inner_reads()
5
>>> inner_writes(10)
>>> inner_reads()
5
>>> inner_error(10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in inner_error
UnboundLocalError: local variable 'x' referenced before assignment

Имя, которое присваивается в локальной области (функция), всегда локально, если не указано иное. Хотя существует «глобальное» объявление для объявления глобальной переменной, даже когда она назначена, для вложенных переменных такого объявления пока нет. В Python 3.0 есть (будет) нелокальное объявление, которое делает именно это.

В настоящее время вы можете обойти это ограничение, используя изменяемый тип контейнера:

>>> def outer(x):
...     x = [x]
...     def inner_reads():
...         # Will return outer's x's first (and only) element.
...         return x[0]
...     def inner_writes(y):
...         # Will look up outer's x, then mutate it.      
...         x[0] = y
...     def inner_error(y):
...         # Will now work, because 'x' is not assigned to, just referenced.
...         tmp = x[0]
...         x[0] = y
...         return tmp
...     return inner_reads, inner_writes, inner_error
... 
>>> inner_reads, inner_writes, inner_error = outer(5)
>>> inner_reads()
5
>>> inner_writes(10)
>>> inner_reads()
10
>>> inner_error(15)
10
>>> inner_reads()
15
6 голосов
/ 09 августа 2011

Ограничение (или «ограничение») замыканий Python по сравнению с замыканиями Javascript заключается в том, что его нельзя использовать для эффективного сокрытия данных

Javascript

var mksecretmaker = function(){
    var secrets = [];
    var mksecret = function() {
        secrets.push(Math.random())
    }
    return mksecret
}
var secretmaker = mksecretmaker();
secretmaker(); secretmaker()
// privately generated secret number list
// is practically inaccessible

Python

import random
def mksecretmaker():
    secrets = []
    def mksecret():
        secrets.append(random.random())
    return mksecret

secretmaker = mksecretmaker()
secretmaker(); secretmaker()
# "secrets" are easily accessible,
# it's difficult to hide something in Python:
secretmaker.__closure__[0].cell_contents # -> e.g. [0.680752847190161, 0.9068475951742101]
6 голосов
/ 27 сентября 2008

Единственная трудность, с которой я сталкивался, в частности, с теми, с кем сталкиваются пользователи Python, - это когда они пытаются смешать не функциональные функции, такие как переназначение переменных, с замыканиями, и удивляются, когда это не работает:

def outer ():
    x = 1
    def inner ():
        print x
        x = 2
    return inner
outer () ()

Обычно достаточно просто указать, что функция имеет свои локальные переменные, чтобы сдержать такую ​​глупость.

4 голосов
/ 27 сентября 2008

Исправлено в Python 3 с помощью оператора nonlocal:

Оператор nonlocal заставляет перечисленные идентификаторы ссылаться на ранее связанные переменные в ближайшей охватывающей области, за исключением глобальных. Это важно, потому что поведение привязки по умолчанию заключается в том, чтобы сначала выполнить поиск в локальном пространстве имен. Оператор позволяет инкапсулированному коду связывать переменные вне локальной области, кроме глобальной (модульной) области.

2 голосов
/ 27 сентября 2008

комментарий для @ ответа Кевина Литтла для включения примера кода

nonlocal не решает полностью эту проблему на python3.0:

x = 0 # global x
def outer():
    x = 1 # local to `outer`
    def inner():
        global x
        x = 2 # change global
        print(x) 
        x = 3 # change global
        return x
    def inner2():
##        nonlocal x # can't use `nonlocal` here
        print(x)     # prints global
##        x = 4      # can't change `x` here
        return x
    x = 5
    return (inner, inner2)

for inner in outer():
    print(inner())
# -> 2 3 3 3

С другой стороны:

x = 0
def outer():
    x = 1 # local to `outer`
    def inner():
##        global x
        x = 2
        print(x) # local to `inner` 
        x = 3 
        return x
    def inner2():
        nonlocal x
        print(x)
        x = 4  # local to `outer`
        return x
    x = 5
    return (inner, inner2)

for inner in outer():
    print(inner())
# -> 2 3 5 4

работает на python3.1-3.3

2 голосов
/ 27 сентября 2008

@ Джон Милликин

def outer():
    x = 1 # local to `outer()`

    def inner():
        x = 2     # local to `inner()`
        print(x)
        x = 3
        return x

    def inner2():
        nonlocal x
        print(x)  # local to `outer()`
        x = 4     # change `x`, it is not local to `inner2()`
        return x

    x = 5         # local to `outer()`
    return (inner, inner2)

for inner in outer():
    print(inner()) 

# -> 2 3 5 4
0 голосов
/ 13 июня 2009

Лучшим обходным решением до 3.0 является включение переменной в качестве параметра по умолчанию в определение вложенной функции:

def f()
    x = 5
    def g(y, z, x=x):
        x = x + 1
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...