Переменные переменных переменных вложенных функций Python

Я прочитал почти все другие вопросы по теме, но мой код все еще не работает.

Я думаю, что я пропускаю что-то о области переменных python.

Вот мой код:

PRICE_RANGES = {
                64:(25, 0.35),
                32:(13, 0.40),
                16:(7, 0.45),
                8:(4, 0.5)
                }

def get_order_total(quantity):
    global PRICE_RANGES
    _total = 0
    _i = PRICE_RANGES.iterkeys()
    def recurse(_i):
        try:
            key = _i.next()
            if quantity % key != quantity:
                _total += PRICE_RANGES[key][0]
            return recurse(_i) 
        except StopIteration:
            return (key, quantity % key)

    res = recurse(_i)

И я получаю

"глобальное имя" _total "не определено"

Я знаю, что проблема связана с назначением _total, но я не могу понять, почему. Должен ли recurse() иметь доступ к родительским переменным функции?

Может кто-нибудь объяснить мне, что мне не хватает в области переменных python?

Ответ 1

Когда я запускаю свой код, я получаю эту ошибку:

UnboundLocalError: local variable '_total' referenced before assignment

Эта проблема вызвана этой строкой:

_total += PRICE_RANGES[key][0]

Документация о Области и пространстве имен говорит следующее:

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

Итак, поскольку строка эффективно говорит:

_total = _total + PRICE_RANGES[key][0]

он создает _total в пространстве имен recurse(). Так как _total является новым и неназначенным, вы не можете использовать его в добавлении.

Ответ 2

Вот иллюстрация, которая доходит до сути Дэвида.

def outer():
    a = 0
    b = 1

    def inner():
        print a
        print b
        #b = 4

    inner()

outer()

С выражением b = 4 закомментировано, этот код выводит 0 1, что вы ожидаете.

Но если вы раскомментируете эту строку, в строке print b вы получите сообщение об ошибке

UnboundLocalError: local variable 'b' referenced before assignment

Кажется загадочным, что присутствие b = 4 может каким-то образом сделать b исчезнуть на предшествующих строках. Но в тексте Дэвида объясняется, почему: во время статического анализа интерпретатор определяет, что b назначен в inner, и поэтому он является локальной переменной inner. Линия печати пытается напечатать b в этой внутренней области до того, как она будет назначена.

Ответ 3

В Python 3 вы можете использовать оператор nonlocal для доступа к нелокальным, неглобальным областям.

Ответ 4

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

def sumsquares(x,y):
  def addsquare(n):
    sumsquares.total += n*n

  sumsquares.total = 0
  addsquare(x)
  addsquare(y)
  return sumsquares.total

Конечно, этот атрибут относится к функции (defintion), а не к вызову функции. Поэтому нужно помнить о потоковом и рекурсии.

Ответ 5

Это вариант решения redman, но с использованием правильного пространства имен вместо массива для инкапсуляции переменной:

def foo():
    class local:
        counter = 0
    def bar():
        print(local.counter)
        local.counter += 1
    bar()
    bar()
    bar()

foo()
foo()

Я не уверен, что использование объекта класса таким образом считается уродливым взломом или правильной методикой кодирования в сообществе python, но отлично работает в python 2.x и 3.x(проверено с помощью 2.7.3 и 3.2.3). Я также не уверен в эффективности выполнения этого решения.

Ответ 6

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

X=0
While X<20:
    Do something. ..
    X+=1

Я бы сделал это:

X=[0]
While X<20:
   Do something....
   X[0]+=1

Таким образом, X никогда не является локальной переменной

Ответ 7

Больше с философской точки зрения один ответ может быть "если у вас проблемы с пространством имен, дайте им пространство имен самого самого!"

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

Сохранение кода OP для фокусировки на существенном изменении,

class Order(object):
  PRICE_RANGES = {
                  64:(25, 0.35),
                  32:(13, 0.40),
                  16:(7, 0.45),
                  8:(4, 0.5)
                  }


  def __init__(self):
    self._total = None

  def get_order_total(self, quantity):
      self._total = 0
      _i = self.PRICE_RANGES.iterkeys()
      def recurse(_i):
          try:
              key = _i.next()
              if quantity % key != quantity:
                  self._total += self.PRICE_RANGES[key][0]
              return recurse(_i) 
          except StopIteration:
              return (key, quantity % key)

      res = recurse(_i)

#order = Order()
#order.get_order_total(100)

Как PS, один взлом, который является вариантом в идее списка в другом ответе, но, возможно, более ясным,

def outer():
  order = {'total': 0}

  def inner():
    order['total'] += 42

  inner()

  return order['total']

print outer()

Ответ 8

>>> def get_order_total(quantity):
    global PRICE_RANGES

    total = 0
    _i = PRICE_RANGES.iterkeys()
    def recurse(_i):
    print locals()
    print globals()
        try:
            key = _i.next()
            if quantity % key != quantity:
                total += PRICE_RANGES[key][0]
            return recurse(_i)
        except StopIteration:
            return (key, quantity % key)
    print 'main function', locals(), globals()

    res = recurse(_i)


>>> get_order_total(20)
main function {'total': 0, 'recurse': <function recurse at 0xb76baed4>, '_i': <dictionary-keyiterator object at 0xb6473e64>, 'quantity': 20} {'__builtins__': <module '__builtin__' (built-in)>, 'PRICE_RANGES': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, '__package__': None, 's': <function s at 0xb646adf4>, 'get_order_total': <function get_order_total at 0xb646ae64>, '__name__': '__main__', '__doc__': None}
{'recurse': <function recurse at 0xb76baed4>, '_i': <dictionary-keyiterator object at 0xb6473e64>, 'quantity': 20}
{'__builtins__': <module '__builtin__' (built-in)>, 'PRICE_RANGES': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, '__package__': None, 's': <function s at 0xb646adf4>, 'get_order_total': <function get_order_total at 0xb646ae64>, '__name__': '__main__', '__doc__': None}
{'recurse': <function recurse at 0xb76baed4>, '_i': <dictionary-keyiterator object at 0xb6473e64>, 'quantity': 20}
{'__builtins__': <module '__builtin__' (built-in)>, 'PRICE_RANGES': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, '__package__': None, 's': <function s at 0xb646adf4>, 'get_order_total': <function get_order_total at 0xb646ae64>, '__name__': '__main__', '__doc__': None}
{'recurse': <function recurse at 0xb76baed4>, '_i': <dictionary-keyiterator object at 0xb6473e64>, 'quantity': 20}
{'__builtins__': <module '__builtin__' (built-in)>, 'PRICE_RANGES': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, '__package__': None, 's': <function s at 0xb646adf4>, 'get_order_total': <function get_order_total at 0xb646ae64>, '__name__': '__main__', '__doc__': None}

Traceback (most recent call last):
  File "<pyshell#32>", line 1, in <module>
    get_order_total(20)
  File "<pyshell#31>", line 18, in get_order_total
    res = recurse(_i)
  File "<pyshell#31>", line 13, in recurse
    return recurse(_i)
  File "<pyshell#31>", line 13, in recurse
    return recurse(_i)
  File "<pyshell#31>", line 12, in recurse
    total += PRICE_RANGES[key][0]
UnboundLocalError: local variable 'total' referenced before assignment
>>> 

как вы видите, общая сумма находится в локальной области основной функции, но она не находится в локальной области рекурсии (очевидно), но она не находится в глобальной области видимости, потому что она определяется только в локальной области get_order_total

Ответ 9

В то время как я использовал подход на основе списка @redman, он не является оптимальным с точки зрения удобочитаемости.

Вот модифицированный подход @Hans, за исключением того, что я использую атрибут внутренней функции, а не внешний. Это должно быть более совместимо с рекурсией и, возможно, даже многопоточным:

def outer(recurse=2):
    if 0 == recurse:
        return

    def inner():
        inner.attribute += 1

    inner.attribute = 0
    inner()
    inner()
    outer(recurse-1)
    inner()
    print "inner.attribute =", inner.attribute

outer()
outer()

Отпечатки:

inner.attribute = 3
inner.attribute = 3
inner.attribute = 3
inner.attribute = 3

Если я s/inner.attribute/outer.attribute/g, получим:

outer.attribute = 3
outer.attribute = 4
outer.attribute = 3
outer.attribute = 4

Так что, действительно, лучше сделать их внутренними атрибутами функции.

Кроме того, это кажется разумным с точки зрения удобочитаемости: поскольку тогда переменная концептуально относится к внутренней функции, и это обозначение напоминает читателю, что переменная распределяется между областями внутренней и внешней функций. Небольшой недостаток читаемости состоит в том, что inner.attribute можно установить синтаксически только после def inner(): ....

Ответ 10

Мой путь вокруг...

def outer():

class Cont(object):
    var1 = None
    @classmethod
    def inner(cls, arg):
        cls.var1 = arg


Cont.var1 = "Before"
print Cont.var1
Cont.inner("After")
print Cont.var1

outer()