Вложенные функции Python переменная область действия [дубликат]


этот вопрос уже есть ответ здесь:

  • UnboundLocalError c вложенными областями действия 4 ответы

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

Я думаю, что мне чего-то не хватает в области переменных 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?

10 71

10 ответов:

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

UnboundLocalError: local variable '_total' referenced before assignment

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

_total += PRICE_RANGES[key][0]

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

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

так с линия фактически говорит:

_total = _total + PRICE_RANGES[key][0]

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

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

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 в этой внутренней области, прежде чем он был назначен.

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

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

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

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

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

Это вариант решения Редмана, но с помощью правильного пространства имен вместо массива для инкапсуляции переменных:

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). Я также не уверен в эффективности этого решения во время выполнения.

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

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

Я бы вместо этого:

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

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

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

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

хотя я использовал основанный на списке подход @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(): ....
>>> 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
>>> 

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

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

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