Замыкания в Python

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

def memoize(fn):
    def get(key):
        return (False,)

    def vset(key, value):
        global get
        oldget = get
        def newget(ky):
            if key==ky: return (True, value)
            return oldget(ky)
        get = newget

    def mfun(*args):
        cache = get(args)
        if (cache[0]): return cache[1]

        val = apply(fn, args)
        vset(args, val)
        return val

    return mfun

def fib(x):
    if x<2: return x
    return fib(x-1)+fib(x-2)

def fibm(x):
    if x<2: return x
    return fibm(x-1)+fibm(x-2)

fibm = memoize(fibm)

в основном, то, что это должно делать, это использовать замыкания для поддержания memoized состояния функции. Я понимаю, что, вероятно, есть много более быстрых, простых для чтения и в целом более "Питонических" способов реализовать это; однако моя цель - понять, как точно работают замыкания в Python и как они отличается от Лиспа, поэтому меня не интересуют альтернативные решения, просто почему мой код не работает и что я могу сделать (если что), чтобы это исправить.

проблема, с которой я сталкиваюсь, когда я пытаюсь использовать fibm - Python настаивает на том, что get не определен:

Python 2.6.1 (r261:67515, Feb  1 2009, 11:39:55) 
[GCC 4.0.1 (Apple Inc. build 5488)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import memoize
>>> memoize.fibm(35)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "memoize.py", line 14, in mfun
    cache = get(args)
NameError: global name 'get' is not defined
>>> 

видя, что я новичок в Python, я не знаю, сделал ли я что-то неправильно, или это просто ограничение языка. Надеюсь, первое. :-)

6 ответов


проблема в вашей области, а не в ваших закрытиях. Если вы готовы к тяжелому чтению, то вы можете попробоватьhttp://www.python.org/dev/peps/pep-3104/.

если это не так, вот простое объяснение:

проблема в отчете global get . global относится к внешней области, и поскольку нет никакой глобальной функции get, он бросает.

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

в python 3.0, Как я тестировал,nonlocal ключевое слово-это именно то, что нужно, на месте global.

nonlocal get
...

в python 2.x, я только что удалил global get и oldget ссылки и он работает правильно.


def memoize(fn):
  get = [lambda key: (False, None)]

  def vset(args):
    value = fn(*args)
    oldget = get[0]
    def newget(key):
      if args == key:
        return (True, value)
      return oldget(key)
    get[0] = newget
    return value

  def mfun(*args):
    found, value = get[0](args)
    if found:
      return value
    return vset(args)

  return mfun

CALLS = 0

def fib(x):
  global CALLS
  CALLS += 1
  if x<2: return x
  return fib(x-1)+fib(x-2)

@memoize
def fibm(x):
  global CALLS
  CALLS += 1
  if x<2: return x
  return fibm(x-1)+fibm(x-2)

CALLS = 0
print "fib(35) is", fib(35), "and took", CALLS, "calls"
CALLS = 0
print "fibm(35) is", fibm(35), "and took", CALLS, "calls"

вывод:

fib(35) is 9227465 and took 29860703 calls
fibm(35) is 9227465 and took 36 calls

подобно другим ответам, однако этот работает. :)

важным изменением из кода в вопросе является назначение нелокального нелокального (get); однако я также сделал некоторые улучшения, пытаясь сохранить ваш *кашель*сломанные*кашель* использовать закрытие. Обычно кэш является dict вместо связанного списка замыканий.


вы хотите поставить global get в начале ).

на def get поручение на имя get, поэтому вы хотите, чтобы вас объявили глобальным до этого.

положить global get в mfun и vset заставляет их работать. Я не могу указать на правила определения области, которые делают это необходимым, но это работает; -)

ваш Cons-ячейки слишком довольно lispy... :)


Get не является глобальным, но локальным для окружающей функции, поэтому global декларация не удается.

если убрать global, Он все еще терпит неудачу, потому что вы не можете назначить захваченному имени переменной. Чтобы обойти это, вы можете использовать объект в качестве переменной, захваченной вашими закрытиями, а не просто изменять свойства этого объекта:

class Memo(object):
    pass

def memoize(fn):
    def defaultget(key):
        return (False,)

    memo = Memo()
    memo.get = defaultget

    def vset(key, value):
        oldget = memo.get
        def newget(ky):
            if key==ky: return (True, value)
            return oldget(ky)
        memo.get = newget

    def mfun(*args):
        cache = memo.get(args)
        if cache[0]: return cache[1]

        val = apply(fn, args)
        vset(args, val)
        return val

    return mfun

таким образом, вам не нужно назначать захваченные имена переменных, но все равно получить то, что вы хотели.


наверное, потому, что вы хотите глобальные пока это не глобальная? Кстати, apply устарел, вместо этого используйте fn(*args).

def memoize(fn):
    def get(key):
        return (False,)

    def vset(key, value):
        def newget(ky):
            if key==ky: return (True, value)
            return get(ky)
        get = newget

    def mfun(*args):
        cache = get(args)
        if (cache[0]): return cache[1]

        val = fn(*args)
        vset(args, val)
        return val

    return mfun

def fib(x):
    if x<2: return x
    return fib(x-1)+fib(x-2)

def fibm(x):
    if x<2: return x
    return fibm(x-1)+fibm(x-2)

fibm = memoize(fibm)

Я думаю, что лучшим способом было бы:

class Memoized(object):
    def __init__(self,func):
        self.cache = {}
        self.func = func
    def __call__(self,*args):
        if args in self.cache: return cache[args]
        else:
            self.cache[args] = self.func(*args)
            return self.cache[args]