Переменные переменных переменных вложенных функций 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()