[python] 처음 사용한 후 재 할당 할 때 로컬 변수의 UnboundLocalError

다음 코드는 Python 2.5 및 3.0에서 예상대로 작동합니다.

a, b, c = (1, 2, 3)

print(a, b, c)

def test():
    print(a)
    print(b)
    print(c)    # (A)
    #c+=1       # (B)
test()

그러나 (B) 줄의 주석을 해제 하면 UnboundLocalError: 'c' not assignedat (A) 줄이 나타납니다 . a및 의 값이 b올바르게 인쇄됩니다. 이것은 두 가지 이유로 완전히 당황했습니다.

  1. (B) 라인의 이후 명령문으로 인해 (A) 라인에서 런타임 오류가 발생하는 이유는 무엇 입니까?

  2. 왜 변수 ab동안 예상대로 인쇄는 c오류를 발생시킵니다?

내가 설명 할 수있는 유일한 설명 은 할당에 의해 지역 변수 c가 생성 된다는 것입니다. 이것은 지역 변수 가 생성 되기 전에 c+=1“전역”변수보다 우선합니다 c. 물론 변수가 존재하기 전에 범위를 “훔치는”것은 의미가 없습니다.

누군가이 행동을 설명해 주시겠습니까?



답변

Python은 함수 내부 또는 외부에서 값을 할당하는지에 따라 함수의 변수를 다르게 처리합니다. 변수가 함수 내에 할당되면 기본적으로 로컬 변수로 처리됩니다. 따라서 줄의 주석을 해제하면 로컬 변수를 참조하려고합니다c 값이 할당되기 전에 합니다.

변수 cc = 3함수 앞에 할당 된 전역을 참조하도록하려면

global c

함수의 첫 번째 줄로.

파이썬 3은 지금 있습니다

nonlocal c

c변수 가있는 가장 가까운 둘러싸는 함수 범위를 참조하는 데 사용할 수 있습니다 .


답변

파이썬은 모든 것을 다양한 범위의 사전에 보관한다는 점에서 조금 이상합니다. 원래의 a, b, c는 최상위 범위에 있으므로 최상위 사전에 있습니다. 함수에는 자체 사전이 있습니다. 당신이 도달 할 때 print(a)print(b) 문에 사전에 해당 이름의 이름이 없으므로 Python은 목록을 찾아 전역 사전에서 찾습니다.

이제 우리 c+=1는 물론에 해당합니다 c=c+1. 파이썬이 그 라인을 스캔 할 때, “aha, c라는 변수가 있는데, 이것을 내 로컬 스코프 사전에 넣겠습니다”라고 말합니다. 그런 다음 할당의 오른쪽에서 c에 대한 c에 대한 값을 찾으면 아직 값이없는 c라는 로컬 변수를 찾아서 오류를 발생시킵니다.

global c위에서 언급 한 내용 은 파서에게 c전역 범위 의 from을 사용 하므로 새로운 구문이 필요하지 않다는 것을 파서에게 알려줍니다 .

그것이 라인에 문제가 있다고 말하는 이유는 코드를 생성하기 전에 효과적으로 이름을 찾고 있기 때문에 어떤 의미에서는 아직 그 라인을 실제로하고 있다고 생각하지 않기 때문입니다. 나는 이것이 유용성 버그라고 주장하지만 일반적으로 컴파일러의 메시지를 너무 심각하게 받아들이지 않는 법을 배우는 것이 좋습니다 .

그것이 위안이라면, Guido가 모든 것을 설명하는 사전에 대해 쓴 것을 발견하기 전에이 같은 문제를 파고 실험하는 데 하루를 보냈습니다.

업데이트, 의견 참조 :

코드를 두 번 스캔하지는 않지만 lexing과 parsing의 두 단계로 코드를 스캔합니다.

이 코드 줄의 구문 분석이 어떻게 작동하는지 고려하십시오. 어휘 분석기는 소스 텍스트를 읽고 문법의 “가장 작은 구성 요소”인 렉 세스로 나눕니다. 그래서 라인에 도달하면

c+=1

그것은 그것을 다음과 같이 나눕니다.

SYMBOL(c) OPERATOR(+=) DIGIT(1)

파서는 결국 이것을 구문 분석 트리로 만들고 실행하려고하지만 할당이기 때문에 로컬 사전에서 c라는 이름을 찾고 그것을 보지 않고 사전에 삽입하여 표시합니다. 초기화되지 않은 것으로. 완전히 컴파일 된 언어에서는 기호 테이블로 이동하여 구문 분석을 기다릴 수 있지만 두 번째 패스의 사치가 없기 때문에 렉서 (Lexer)는 나중에 인생을 더 쉽게하기 위해 약간의 추가 작업을 수행합니다. 그런 다음 운영자 만 볼 수 있고 규칙에 “운영자 + =가있는 경우 왼쪽이 초기화되어야합니다”라고 말하고 “누가!”라고 말합니다.

여기서 요점은 아직 라인의 구문 분석을 시작하지 않았다는 것입니다. 입니다. 이것은 실제 구문 분석에 대한 모든 준비 과정이므로 행 카운터가 다음 행으로 진행되지 않았습니다. 따라서 오류를 알리더라도 여전히 이전 라인에서 오류를 생각합니다.

내가 말했듯이, 그것은 유용성 버그라고 주장 할 수 있지만 실제로는 매우 일반적인 것입니다. 일부 컴파일러는 그것에 대해 더 정직하고 “XXX 라인 또는 그 주변에 오류”라고 말하지만, 그렇지 않습니다.


답변

분해를 살펴보면 무슨 일이 일어나고 있는지 알 수 있습니다.

>>> def f():
...    print a
...    print b
...    a = 1

>>> import dis
>>> dis.dis(f)

  2           0 LOAD_FAST                0 (a)
              3 PRINT_ITEM
              4 PRINT_NEWLINE

  3           5 LOAD_GLOBAL              0 (b)
              8 PRINT_ITEM
              9 PRINT_NEWLINE

  4          10 LOAD_CONST               1 (1)
             13 STORE_FAST               0 (a)
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

당신이 볼 수있는 바와 같이, 액세스하기위한 바이트 코드는 LOAD_FAST,와 b에 대한 LOAD_GLOBAL. 컴파일러가 함수 내에서 a가 할당되었음을 식별하고이를 로컬 변수로 분류했기 때문입니다. 지역에 대한 액세스 메커니즘은 전역에 대해 근본적으로 다릅니다. 프레임의 변수 테이블에 오프셋이 정적으로 할당됩니다. 즉, 전역에 비해 값 비싼 딕트 조회가 아니라 조회가 빠른 색인임을 의미합니다. 이 때문에 Python은 print a“슬롯 0에 보유 된 로컬 변수 ‘a’의 값을 가져 와서 인쇄”로 해당 행을 읽 습니다.이 변수가 아직 초기화되지 않았 음을 감지하면 예외가 발생합니다.


답변

파이썬은 전통적인 전역 변수 의미를 시도 할 때 다소 흥미로운 동작을합니다. 세부 사항은 기억 나지 않지만 ‘전역’범위에서 선언 된 변수의 값을 잘 읽을 수는 있지만 수정하려면 global키워드 를 사용해야합니다 . test()이것으로 변경 하십시오 :

def test():
    global c
    print(a)
    print(b)
    print(c)    # (A)
    c+=1        # (B)

또한이 오류가 발생하는 이유는 해당 함수 내에서 ‘전역’과 동일한 이름으로 새 변수를 선언 할 수 있기 때문에 완전히 분리되어 있기 때문입니다. 인터프리터는이 범위에서 새로운 변수를 호출 c하고 한 번의 조작으로 변수를 모두 수정 하려고한다고 생각합니다.이 새로운 변수는 c초기화되지 않았기 때문에 파이썬에서는 허용되지 않습니다 .


답변

가장 분명한 예는 다음과 같습니다.

bar = 42
def foo():
    print bar
    if False:
        bar = 0

호출 할 때 foo(),이 또한 제기 UnboundLocalError 우리가 라인에 도달하지 않습니다 있지만,bar=0 그렇게 논리적으로 지역 변수가 생성해서는 안됩니다.

수수께끼는 ” Python is Interpreted Language “에 있으며 함수 선언은 foo단일 명령문 (예 : 복합 명령문)으로 해석되며,이를 어리석게 해석하고 로컬 및 글로벌 범위를 만듭니다. 그래서bar 실행 전에 로컬 범위에서 인식됩니다.

에 대한 더 많은 예제 이 읽기이 게시물 같은 : http://blog.amir.rachum.com/blog/2013/07/09/python-common-newbie-mistakes-part-2/

이 게시물은 변수의 파이썬 범위 지정에 대한 완전한 설명 및 분석을 제공합니다.


답변

도움이 될만한 두 가지 링크는 다음과 같습니다.

1 : docs.python.org/3.1/faq/programming.html?highlight=nonlocal#why-am-i-getting-an-unboundlocalerror-w-the-variable-has-a-value

2 : docs.python.org/3.1/faq/programming.html?highlight=nonlocal#how-do-i-write-a-function-with-output-parameters-call-by-reference

링크 1은 오류 UnboundLocalError를 설명합니다. 링크 2는 테스트 기능을 다시 작성하는 데 도움이 될 수 있습니다. 링크 2를 기반으로 원래 문제를 다음과 같이 다시 작성할 수 있습니다.

>>> a, b, c = (1, 2, 3)
>>> print (a, b, c)
(1, 2, 3)
>>> def test (a, b, c):
...     print (a)
...     print (b)
...     print (c)
...     c += 1
...     return a, b, c
...
>>> a, b, c = test (a, b, c)
1
2
3
>>> print (a, b ,c)
(1, 2, 4)


답변

이것은 귀하의 질문에 대한 직접적인 대답은 아니지만, 확장 할당과 기능 범위 사이의 관계로 인해 발생하는 또 다른 문제이므로 밀접한 관련이 있습니다.

대부분의 경우 증강 된 과제 ( a += b)는 단순한 과제 (a = a + b ) 있습니다. 그러나 한 모퉁이의 경우이 문제에 어려움을 겪을 수 있습니다. 설명하겠습니다 :

파이썬의 간단한 할당이 작동하는 방식은 a함수에 전달 되면 ( func(a)파이썬은 항상 참조 로 전달됨에 유의하십시오) 전달 된 것을 a = a + b수정하지 않습니다 a. 대신에 로컬 포인터를 수정합니다.a .

그러나를 사용하면 a += b때로는 다음과 같이 구현됩니다.

a = a + b

또는 때로는 (방법이 존재하는 경우) 다음과 같이 :

a.__iadd__(b)

첫 번째 경우 ( a글로벌로 선언되지 않은 한)에 대한 할당 a은 포인터 업데이트 일 뿐이 므로 로컬 범위 외부의 부작용은 없습니다 .

두 번째 경우 a실제로 실제로 수정되므로 모든 참조 a는 수정 된 버전을 가리 킵니다. 이것은 다음 코드로 설명됩니다.

def copy_on_write(a):
      a = a + a
def inplace_add(a):
      a += a
a = [1]
copy_on_write(a)
print a # [1]
inplace_add(a)
print a # [1, 1]
b = 1
copy_on_write(b)
print b # [1]
inplace_add(b)
print b # 1

따라서 트릭은 함수 인수에 대한 할당을 늘리지 않는 것입니다 (로컬 / 루프 변수에만 사용하려고합니다). 간단한 과제를 사용하면 모호한 행동으로부터 안전합니다.