web-dev-qa-db-ja.com

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変数スコープについて何が欠けているのか説明できますか?

77

コードを実行すると、次のエラーが表示されます。

_UnboundLocalError: local variable '_total' referenced before assignment
_

この問題はこのラインによって引き起こされます:

__total += PRICE_RANGES[key][0]
_

スコープと名前空間に関するドキュメント はこう言っています:

Python)の特別な癖は、– globalステートメントが有効でない場合–名前への割り当ては常に最も内側のスコープに入ることです。割り当てはデータをコピーせず、名前をオブジェクトにバインドするだけです。

だから、行は事実上言っているので:

__total = _total + PRICE_RANGES[key][0]
_

recurse()の名前空間に__total_を作成します。 __total_は新しく、割り当てられていないため、追加で使用することはできません。

55
Dave Webb

以下は、デビッドの答えの本質に迫るイラストです。

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が何らかの形で消えてしまうのは不思議なようです。しかし、Davidが引用するテキストはその理由を説明しています:静的解析中に、インタープリターはbがinnerに割り当てられているため、それがinnerのローカル変数であると判断します。印刷行は、割り当てられる前に、その内部スコープでbを印刷しようとします。

132
moos

Python 3では、 nonlocalステートメント を使用して、非ローカル、非グローバルスコープにアクセスできます。

101
Michael Hoffman

特別なオブジェクト、マップ、または配列を宣言する代わりに、関数属性を使用することもできます。これにより、変数のスコープが本当に明確になります。

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

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

もちろん、この属性は関数呼び出しではなく、関数(定義)に属します。したがって、スレッド化と再帰に注意する必要があります。

29
Hans

これはredmanのソリューションのバリエーションですが、変数のカプセル化に配列ではなく適切な名前空間を使用します。

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

foo()
foo()

この方法でクラスオブジェクトを使用するのがugいハックまたはpythonコミュニティでの適切なコーディング手法と見なされるかどうかはわかりませんが、python = 2.xおよび3.x(2.7.3および3.2.3でテスト済み)このソリューションの実行時の効率についても不明です。

16
CliffordVienna

おそらくあなたはあなたの質問の答えを得ているでしょう。しかし、私は通常これを回避する方法を示したかったのですが、それはリストを使用することです。たとえば、これを行いたい場合:

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

代わりにこれを行います:

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

このように、Xは決してローカル変数ではありません

7
redman

以前は@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
_

したがって、実際には、それらを内部関数の属性にする方が良いようです。

また、読みやすさの観点からは賢明なようです。変数は概念的に内部関数に関連しており、この表記法は、変数が内部関数と外部関数のスコープ間で共有されることを読者に思い出させるためです。読みやすさのわずかな欠点は、def inner(): ...の後にのみ_inner.attribute_を構文的に設定できることです。

3
Evgeni Sergeev

哲学的な観点から見ると、1つの答えは「名前空間に問題がある場合は、独自の名前空間を付けてください!」かもしれません。

独自のクラスで提供することで、問題をカプセル化できるだけでなく、テストを簡単にし、厄介なグローバルを排除し、さまざまなトップレベル関数間で変数をシャベルで動かす必要性を減らします(疑いなく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()
2
tantrix
>>> 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のローカルスコープでのみ定義されているため

0
Ant