Вложенные функции 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 ответов:
когда я запускаю свой код, я получаю эту ошибку:
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