programing tip

Python 중첩 함수 변수 범위 지정

itbloger 2020. 10. 13. 07:30
반응형

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()부모 함수의 변수에 대한 액세스 권한 이 없어야합니까 ?

누군가 파이썬 변수 범위에 대해 내가 놓친 것을 설명 할 수 있습니까?


코드를 실행하면이 오류가 발생합니다.

UnboundLocalError: local variable '_total' referenced before assignment

이 문제는 다음 줄로 인해 발생합니다.

_total += PRICE_RANGES[key][0]

범위 및 네임 스페이스에 대한 문서 는 다음 같이 말합니다.

파이썬의 특별한 특징은 – 만약 어떤 global문장도 유효하지 않다면 이름에 대한 할당은 항상 가장 안쪽 범위에 들어간다는 것 입니다. 할당은 데이터를 복사하지 않고 개체에 이름을 바인딩 할뿐입니다.

따라서 라인이 효과적으로 다음과 같이 말하고 있기 때문에 :

_total = _total + PRICE_RANGES[key][0]

_total의 네임 스페이스에 생성 됩니다 recurse(). _total그러면 새롭고 할당되지 않았 으므로 추가로 사용할 수 없습니다.


다음은 David의 대답의 본질을 보여주는 그림입니다.

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가 in에 할당되어 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

물론이 속성은 함수 호출이 아니라 함수 (정의)에 속합니다. 따라서 스레딩과 재귀를 염두에 두어야합니다.


이것은 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에서 테스트 됨). ). 이 솔루션의 런타임 효율성에 대해서도 확신이 없습니다.


질문에 대한 답을 얻었을 것입니다. 그러나 나는 일반적으로 이것을 우회하는 방법을 나타내고 싶었고 그것은 목록을 사용하는 것입니다. 예를 들어 이렇게하려면 다음을 수행합니다.

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

대신 이렇게합니다.

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

이런 식으로 X는 결코 지역 변수가 아닙니다.


@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.attributedef inner(): ....


철학적 관점에서 보면 한 가지 대답은 "네임 스페이스 문제가있는 경우 고유 한 네임 스페이스를 제공하십시오!"일 수 있습니다.

자체 클래스에 제공하면 문제를 캡슐화 할 수있을뿐만 아니라 테스트가 더 쉬워지고, 성가신 전역을 제거하고, 다양한 최상위 함수 사이에 변수를 삽질 할 필요가 줄어 듭니다 (확실히 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()

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

참고 URL : https://stackoverflow.com/questions/5218895/python-nested-functions-variable-scoping

반응형