클래스 정의의 목록 이해에서 클래스 변수에 액세스


174

클래스 정의 내 목록 이해에서 다른 클래스 변수에 어떻게 액세스합니까? 다음은 Python 2에서 작동하지만 Python 3에서는 실패합니다.

class Foo:
    x = 5
    y = [x for i in range(1)]

Python 3.2는 오류를 제공합니다.

NameError: global name 'x' is not defined

노력도 효과 Foo.x가 없습니다. Python 3 에서이 작업을 수행하는 방법에 대한 아이디어가 있습니까?

약간 더 복잡한 동기 부여 예제 :

from collections import namedtuple
class StateDatabase:
    State = namedtuple('State', ['name', 'capital'])
    db = [State(*args) for args in [
        ['Alabama', 'Montgomery'],
        ['Alaska', 'Juneau'],
        # ...
    ]]

이 예제에서는 apply()괜찮은 해결 방법 이었지만 Python 3에서 슬프게 제거되었습니다.


오류 메시지가 잘못되었습니다. 내가 얻을 NameError: global name 'x' is not defined파이썬 3.2에 내가 기대했던 것입니다 3.3.
Martijn Pieters

흥미로운 점 ... 한 가지 확실한 해결 방법은 클래스 정의를 종료 한 후 y를 할당하는 것입니다. Foo.y = [범위 (1)의 i에 대한 Foo.x]
gps

3
+ martijn-pieters 복제본에 대한 링크가 맞습니다. + matt-b의 설명과 함께 설명이 있습니다 .Python 2.7 목록 이해에는 고유 한 네임 스페이스가 없습니다 (설정 또는 dict 이해 또는 생성기 표현식과 달리 ... ]를 {}와 함께 사용하면 실제로 볼 수 있습니다). 그들은 모두 3 에 자신의 네임 스페이스를 가지고 있습니다.
gps

@gps : 또는 클래스 정의 제품군에 (임시) 함수를 삽입하여 중첩 범위를 사용하십시오.
Martijn Pieters

방금 2.7.11에서 테스트했습니다. 이름 오류가 있습니다
Junchao Gu

답변:


244

클래스 범위 및 목록, 세트 또는 사전 이해 및 생성기 표현식이 혼합되지 않습니다.

이유; 또는 이것에 대한 공식적인 단어

파이썬 3에서,리스트 변수에는 로컬 변수가 주변 범위로 흘러 들어가는 것을 막기 위해 적절한 범위 (로컬 네임 스페이스)가 주어졌습니다 ( 파이어리스트 이해는 범위를 이해 한 후에도 리 바인드 이름을 참조하십시오 . 맞습니까? ). 모듈이나 함수에서 이러한 목록 이해를 사용할 때 좋습니다. 그러나 클래스에서는 범위가 약간, 음, 이상 합니다.

이것은 pep 227에 설명되어 있습니다 :

클래스 범위의 이름은 액세스 할 수 없습니다. 가장 안쪽에있는 함수 범위에서 이름이 확인됩니다. 중첩 된 범위의 체인에서 클래스 정의가 발생하면 해결 프로세스는 클래스 정의를 건너 뜁니다.

그리고 class복합 명세서 문서에서 :

그런 다음 클래스 스위트는 새로 작성된 로컬 네임 스페이스 및 원래 글로벌 네임 스페이스를 사용하여 새 실행 프레임 ( 네이밍 및 바인딩 섹션 참조)에서 실행됩니다 . (일반적으로 스위트에는 함수 정의 만 포함됩니다.) 클래스 스위트가 실행을 마치면 해당 실행 프레임은 삭제되지만 로컬 네임 스페이스는 저장 됩니다. [4] 그런 다음 기본 클래스의 상속 목록과 속성 사전의 저장된 로컬 네임 스페이스를 사용하여 클래스 객체를 만듭니다.

강조 광산; 실행 프레임은 임시 범위입니다.

범위는 클래스 객체의 특성으로 용도가 변경되므로이를 비 로컬 범위로 사용하면 정의되지 않은 동작이 발생합니다. 예를 들어 x중첩 범위 변수 라고하는 클래스 메서드가 조작 Foo.x되면 어떻게됩니까? 더 중요한 것은 하위 클래스의 의미는 Foo무엇입니까? 파이썬 클래스 범위가 함수 범위와 매우 다르기 때문에 다르게 취급해야합니다.

마지막으로, 실행 모델 문서 의 링크 된 이름 지정 및 바인딩 섹션에서 클래스 범위를 명시 적으로 언급합니다.

클래스 블록에 정의 된 이름의 범위는 클래스 블록으로 제한됩니다. 코드는 메소드의 코드 블록으로 확장되지 않습니다. 여기에는 함수 범위를 사용하여 구현되므로 이해 및 생성기 표현식이 포함됩니다. 이는 다음이 실패 함을 의미합니다.

class A:
     a = 42
     b = list(a + i for i in range(10))

따라서 요약하면 함수에서 클래스 범위에 액세스 할 수없고 해당 범위에 포함 된 이해 또는 생성기 표현식을 나열 할 수 없습니다. 해당 범위가 존재하지 않는 것처럼 작동합니다. Python 2에서는 목록 이해가 바로 가기를 사용하여 구현되었지만 Python 3에서는 자체 기능 범위 (모두 있어야 했으므로)가 있으므로 예제가 중단됩니다. 다른 이해 유형은 Python 버전에 관계없이 자체 범위를 가지므로 set 또는 dict 이해를 가진 유사한 예제는 Python 2에서 중단됩니다.

# Same error, in Python 2 or 3
y = {x: x for i in range(1)}

(작은) 예외; 또는 한 부분 여전히 작동하는 이유

파이썬 버전에 관계없이 주변 범위에서 실행되는 이해 또는 생성기 표현의 한 부분이 있습니다. 그것은 가장 바깥 쪽의 반복 가능한 표현입니다. 귀하의 예에서 다음과 range(1)같습니다.

y = [x for i in range(1)]
#               ^^^^^^^^

따라서 x해당 표현식에서 사용 하면 오류가 발생하지 않습니다.

# Runs fine
y = [i for i in range(x)]

이것은 가장 바깥 쪽 반복 가능에만 적용됩니다. 이해에 여러 for절이 있는 경우 내부 for절의 반복 가능 항목은 이해 범위에서 평가됩니다.

# NameError
y = [i for i in range(1) for j in range(x)]

이 디자인 결정은 생성기 표현식의 가장 바깥 쪽 반복 가능을 만들 때 오류가 발생하거나 가장 바깥 쪽 반복 가능이 반복 가능하지 않을 때 반복 시간 대신 genexp 생성 시간에 오류를 발생시키기 위해 이루어졌습니다. 이해도는 일관성을 위해이 동작을 공유합니다.

후드를보고; 또는, 당신이 원했던 것보다 더 자세하게

dis모듈을 사용하여이 모든 것을 실제로 볼 수 있습니다 . 다음 예제에서는 Python 3.3을 사용합니다 . 검사 할 코드 객체를 깔끔하게 식별하는 정규화 된 이름 을 추가하기 때문 입니다. 생성 된 바이트 코드는 기능적으로 Python 3.2와 동일합니다.

클래스 를 만들기 위해 Python은 본질적으로 클래스 본문을 구성하는 전체 제품군 ( class <name>:라인 보다 한 수준 깊게 들여 쓰기 된 모든 항목)을 가져와 마치 함수 인 것처럼 실행합니다.

>>> import dis
>>> def foo():
...     class Foo:
...         x = 5
...         y = [x for i in range(1)]
...     return Foo
... 
>>> dis.dis(foo)
  2           0 LOAD_BUILD_CLASS     
              1 LOAD_CONST               1 (<code object Foo at 0x10a436030, file "<stdin>", line 2>) 
              4 LOAD_CONST               2 ('Foo') 
              7 MAKE_FUNCTION            0 
             10 LOAD_CONST               2 ('Foo') 
             13 CALL_FUNCTION            2 (2 positional, 0 keyword pair) 
             16 STORE_FAST               0 (Foo) 

  5          19 LOAD_FAST                0 (Foo) 
             22 RETURN_VALUE         

첫 번째 LOAD_CONSTFoo클래스 본문에 대한 코드 객체를로드 한 다음이를 함수로 만들어 호출합니다. 그런 다음 해당 호출 의 결과 는 클래스의 네임 스페이스 인 its를 작성하는 데 사용됩니다 __dict__. 여태까지는 그런대로 잘됐다.

여기서 주목할 것은 바이트 코드는 중첩 된 코드 객체를 포함한다는 것입니다. 파이썬에서 클래스 정의, 함수, 이해 및 생성기는 모두 바이트 코드뿐만 아니라 지역 변수, 상수, 전역 변수, 중첩 범위 변수를 나타내는 구조를 포함하는 코드 객체로 표현됩니다. 컴파일 된 바이트 코드는 이러한 구조를 참조하며 파이썬 인터프리터는 제공된 바이트 코드가 주어진 구조에 액세스하는 방법을 알고 있습니다.

여기서 기억해야 할 중요한 것은 파이썬은 컴파일 타임에 이러한 구조를 생성한다는 것입니다. 이 class스위트는 <code object Foo at 0x10a436030, file "<stdin>", line 2>이미 컴파일 된 코드 객체 ( )입니다.

클래스 바디 자체를 생성하는 코드 객체를 검사하자. 코드 객체의 co_consts구조는 다음과 같습니다.

>>> foo.__code__.co_consts
(None, <code object Foo at 0x10a436030, file "<stdin>", line 2>, 'Foo')
>>> dis.dis(foo.__code__.co_consts[1])
  2           0 LOAD_FAST                0 (__locals__) 
              3 STORE_LOCALS         
              4 LOAD_NAME                0 (__name__) 
              7 STORE_NAME               1 (__module__) 
             10 LOAD_CONST               0 ('foo.<locals>.Foo') 
             13 STORE_NAME               2 (__qualname__) 

  3          16 LOAD_CONST               1 (5) 
             19 STORE_NAME               3 (x) 

  4          22 LOAD_CONST               2 (<code object <listcomp> at 0x10a385420, file "<stdin>", line 4>) 
             25 LOAD_CONST               3 ('foo.<locals>.Foo.<listcomp>') 
             28 MAKE_FUNCTION            0 
             31 LOAD_NAME                4 (range) 
             34 LOAD_CONST               4 (1) 
             37 CALL_FUNCTION            1 (1 positional, 0 keyword pair) 
             40 GET_ITER             
             41 CALL_FUNCTION            1 (1 positional, 0 keyword pair) 
             44 STORE_NAME               5 (y) 
             47 LOAD_CONST               5 (None) 
             50 RETURN_VALUE         

위의 바이트 코드는 클래스 본문을 만듭니다. 함수가 실행되고 결과 locals()네임 스페이스가 포함 x되어 y클래스를 작성하는 데 사용됩니다 ( x글로벌로 정의 되지 않아 작동하지 않는 경우 제외 ). 저장 한 후주의 5x, 또 다른 코드 객체를로드; 이것이 목록 이해력입니다. 클래스 본문과 마찬가지로 함수 객체로 래핑됩니다. 생성 된 함수는 range(1)반복 인수로 캐스트 되는 위치 코드, 반복 코드에 사용할 iterable을 사용합니다. 바이트 코드에 표시된 것처럼 range(1)클래스 범위에서 평가됩니다.

이것으로부터 함수 또는 생성기의 코드 객체와 이해를위한 코드 객체의 유일한 차이점 은 상위 코드 객체가 실행될 때 후자가 즉시 실행된다는 것 입니다. 바이트 코드는 단순히 함수를 즉시 생성하고 몇 가지 작은 단계로 실행합니다.

파이썬 2.x는 대신 인라인 바이트 코드를 사용합니다. 파이썬 2.7의 출력 결과는 다음과 같습니다.

  2           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)

  3           6 LOAD_CONST               0 (5)
              9 STORE_NAME               2 (x)

  4          12 BUILD_LIST               0
             15 LOAD_NAME                3 (range)
             18 LOAD_CONST               1 (1)
             21 CALL_FUNCTION            1
             24 GET_ITER            
        >>   25 FOR_ITER                12 (to 40)
             28 STORE_NAME               4 (i)
             31 LOAD_NAME                2 (x)
             34 LIST_APPEND              2
             37 JUMP_ABSOLUTE           25
        >>   40 STORE_NAME               5 (y)
             43 LOAD_LOCALS         
             44 RETURN_VALUE        

코드 객체가로드되지 않고 FOR_ITER루프가 인라인으로 실행됩니다. 따라서 Python 3.x에서는 목록 생성기에 자체 코드 객체가 제공되었으므로 자체 범위가 있습니다.

그러나 모듈이나 스크립트가 인터프리터에 의해 처음로드 될 때 이해는 나머지 파이썬 소스 코드와 함께 컴파일되었으며 컴파일러는 클래스 스위트를 유효한 범위로 간주 하지 않습니다 . 목록 이해에서 참조 된 변수 는 클래스 정의를 둘러싼 범위 를 재귀 적으로 찾아야합니다 . 컴파일러가 변수를 찾지 못하면 전역 변수로 표시합니다. 목록 이해 코드 객체의 디스 어셈블리 x는 실제로 전역으로로드 되었음을 보여줍니다 .

>>> foo.__code__.co_consts[1].co_consts
('foo.<locals>.Foo', 5, <code object <listcomp> at 0x10a385420, file "<stdin>", line 4>, 'foo.<locals>.Foo.<listcomp>', 1, None)
>>> dis.dis(foo.__code__.co_consts[1].co_consts[2])
  4           0 BUILD_LIST               0 
              3 LOAD_FAST                0 (.0) 
        >>    6 FOR_ITER                12 (to 21) 
              9 STORE_FAST               1 (i) 
             12 LOAD_GLOBAL              0 (x) 
             15 LIST_APPEND              2 
             18 JUMP_ABSOLUTE            6 
        >>   21 RETURN_VALUE         

이 바이트 코드 덩어리는 전달 된 첫 번째 인수 ( range(1)반복자)를 로드하며 Python 2.x 버전 FOR_ITER이 루프 오버 및 출력을 만드는 데 사용 하는 것과 같습니다 .

우리가 정의했던 xfoo기능을 대신, x셀 변수 (세포가 중첩 된 범위 참조) 될 것이다 :

>>> def foo():
...     x = 2
...     class Foo:
...         x = 5
...         y = [x for i in range(1)]
...     return Foo
... 
>>> dis.dis(foo.__code__.co_consts[2].co_consts[2])
  5           0 BUILD_LIST               0 
              3 LOAD_FAST                0 (.0) 
        >>    6 FOR_ITER                12 (to 21) 
              9 STORE_FAST               1 (i) 
             12 LOAD_DEREF               0 (x) 
             15 LIST_APPEND              2 
             18 JUMP_ABSOLUTE            6 
        >>   21 RETURN_VALUE         

LOAD_DEREF간접적으로로드 x코드 객체 세포 개체에서 :

>>> foo.__code__.co_cellvars               # foo function `x`
('x',)
>>> foo.__code__.co_consts[2].co_cellvars  # Foo class, no cell variables
()
>>> foo.__code__.co_consts[2].co_consts[2].co_freevars  # Refers to `x` in foo
('x',)
>>> foo().y
[2]

실제 참조는 함수 객체의 .__closure__속성 에서 초기화 된 현재 프레임 데이터 구조에서 값을 찾습니다 . 이해 코드 객체를 위해 생성 된 함수는 다시 폐기되므로 해당 함수의 닫힘을 검사 할 수 없습니다. 클로저가 실제로 작동하는지 확인하려면 대신 중첩 함수를 검사해야합니다.

>>> def spam(x):
...     def eggs():
...         return x
...     return eggs
... 
>>> spam(1).__code__.co_freevars
('x',)
>>> spam(1)()
1
>>> spam(1).__closure__
>>> spam(1).__closure__[0].cell_contents
1
>>> spam(5).__closure__[0].cell_contents
5

요약하면 다음과 같습니다.

  • 리스트 이해는 파이썬 3에서 자체 코드 객체를 얻습니다. 함수, 생성기 또는 이해를위한 코드 객체에는 차이가 없습니다. 이해 코드 객체는 임시 함수 객체로 래핑되어 즉시 호출됩니다.
  • 코드 객체는 컴파일 타임에 생성되며 로컬이 아닌 변수는 중첩 된 코드 범위에 따라 전역 변수 또는 자유 변수로 표시됩니다. 클래스 본문은 이러한 변수를 찾는 범위로 간주 되지 않습니다 .
  • 코드를 실행할 때 파이썬은 전역 또는 현재 실행중인 객체의 클로저 만 조사하면됩니다. 컴파일러는 클래스 본문을 범위로 포함하지 않았으므로 임시 함수 네임 스페이스는 고려되지 않습니다.

해결 방법; 또는, 어떻게해야합니까

x함수에서와 같이 변수에 대한 명시 적 범위를 작성하려는 경우 목록 이해에 클래스 범위 변수를 사용할 있습니다.

>>> class Foo:
...     x = 5
...     def y(x):
...         return [x for i in range(1)]
...     y = y(x)
... 
>>> Foo.y
[5]

'임시' y기능은 직접 호출 할 수 있습니다. 반환 값으로 할 때 대체합니다. 범위 는 다음 을 해결할 때 고려됩니다 x.

>>> foo.__code__.co_consts[1].co_consts[2]
<code object y at 0x10a5df5d0, file "<stdin>", line 4>
>>> foo.__code__.co_consts[1].co_consts[2].co_cellvars
('x',)

물론, 당신의 코드를 읽는 사람들은 이것에 대해 머리를 긁을 것입니다. 당신은 왜 당신이 이것을하고 있는지 설명하는 큰 뚱뚱한 의견을 넣을 수 있습니다.

가장 좋은 해결 방법은 __init__대신 인스턴스 변수를 만드는 데 사용하는 것입니다.

def __init__(self):
    self.y = [self.x for i in range(1)]

머리를 긁지 말고 자신을 설명하는 질문을 피하십시오. 당신 자신의 구체적인 예를 들어, 나는 namedtuple교실에 저장조차하지 않을 것입니다 . 출력을 직접 사용하거나 (생성 된 클래스를 전혀 저장하지 않음) 전역을 사용하십시오.

from collections import namedtuple
State = namedtuple('State', ['name', 'capital'])

class StateDatabase:
    db = [State(*args) for args in [
       ('Alabama', 'Montgomery'),
       ('Alaska', 'Juneau'),
       # ...
    ]]

21
람다를 사용하여 바인딩을 수정할 수도 있습니다.y = (lambda x=x: [x for i in range(1)])()
ecatmur

3
@ecatmur : 맞습니다 lambda. 결국 익명 함수일뿐입니다.
Martijn Pieters

2
레코드의 경우 람다 또는 함수에 대한 기본 인수를 사용하여 클래스 변수를 전달하는 해결 방법에는 문제가 있습니다. 즉, 변수 의 현재 값 을 전달합니다 . 따라서 변수가 나중에 변경되고 람다 또는 함수가 호출되면 람다 또는 함수는 이전 값을 사용합니다. 이 동작은 클로저 동작 (값이 아닌 변수에 대한 참조를 캡처하는 동작)과 다르므로 예상치 못한 결과가 발생할 수 있습니다.
닐 영

9
왜 직관적으로 작동하지 않는 이유를 설명하기 위해 기술 정보 페이지가 필요한 경우이를 버그라고합니다.
Jonathan

5
@JonathanLeaders : 버그 라고 부르지 말고 트레이드 오프 라고 부릅니다 . A와 B를 원하지만 그 중 하나만 얻을 수 있다면 어떻게 결정하든 어떤 상황에서는 결과를 싫어할 것입니다. 인생이 다 그렇지.
Lutz Prechelt

15

제 생각에는 파이썬 3의 결함입니다.

올드 웨이 (2.7에서 작동, NameError: name 'x' is not defined3+ 에서 던짐 ) :

class A:
    x = 4
    y = [x+i for i in range(1)]

참고 : 단순히 범위를 지정하면 A.x해결되지 않습니다.

새로운 방식 (3 이상에서 작동) :

class A:
    x = 4
    y = (lambda x=x: [x+i for i in range(1)])()

구문이 너무 추악하기 때문에 생성자에서 모든 클래스 변수를 일반적으로 초기화합니다.


6
생성기 표현식을 사용할 때뿐만 아니라 세트 및 사전 이해와 함께 Python 2에서도 문제가 발생합니다. 버그가 아니며 클래스 네임 스페이스가 작동하는 방식의 결과입니다. 변경되지 않습니다.
Martijn Pieters

4
그리고 귀하의 해결 방법은 내 대답에 이미 나와있는 것과 정확히 같습니다. 새로운 범위를 만듭니다 (람다는 def함수를 만드는 데 사용 하는 것과 다르지 않습니다 ).
Martijn Pieters

1
네. 해결 방법으로 한 번에 대답하는 것이 좋지만 이것은 언어가 작동하는 방식의 부작용으로 버그를 행동으로 잘못 표시합니다 (따라서 변경되지 않음).
jsbueno

이것은 실제로 파이썬 3에서는 문제가되지 않는 다른 문제 python -c "import IPython;IPython.embed()"입니다. say를 사용하여 임베드 모드에서 호출 할 때만 IPython에서만 발생합니다 . say를 사용하여 IPython을 직접 실행 ipython하면 문제가 사라집니다.
Riaz Rizvi

6

대답은 훌륭한 정보를 제공하지만 여기에는 목록 이해와 생성기 표현의 차이점과 같은 다른 주름이 있습니다. 내가 함께 연주 한 데모 :

class Foo:

    # A class-level variable.
    X = 10

    # I can use that variable to define another class-level variable.
    Y = sum((X, X))

    # Works in Python 2, but not 3.
    # In Python 3, list comprehensions were given their own scope.
    try:
        Z1 = sum([X for _ in range(3)])
    except NameError:
        Z1 = None

    # Fails in both.
    # Apparently, generator expressions (that's what the entire argument
    # to sum() is) did have their own scope even in Python 2.
    try:
        Z2 = sum(X for _ in range(3))
    except NameError:
        Z2 = None

    # Workaround: put the computation in lambda or def.
    compute_z3 = lambda val: sum(val for _ in range(3))

    # Then use that function.
    Z3 = compute_z3(X)

    # Also worth noting: here I can refer to XS in the for-part of the
    # generator expression (Z4 works), but I cannot refer to XS in the
    # inner-part of the generator expression (Z5 fails).
    XS = [15, 15, 15, 15]
    Z4 = sum(val for val in XS)
    try:
        Z5 = sum(XS[i] for i in range(len(XS)))
    except NameError:
        Z5 = None

print(Foo.Z1, Foo.Z2, Foo.Z3, Foo.Z4, Foo.Z5)

2

이것은 파이썬의 버그입니다. 이해력은 for 루프와 동등한 것으로 광고되지만 클래스에서는 그렇지 않습니다. 클래스에서 사용되는 이해에서 최소한 Python 3.6.6까지는 이해 외부에서 하나의 변수 만 이해 내부에서 액세스 할 수 있으며 가장 바깥 쪽 반복자로 사용되어야합니다. 함수에서이 범위 제한은 적용되지 않습니다.

이것이 버그 인 이유를 설명하기 위해 원래 예제로 돌아가 보겠습니다. 이것은 실패합니다 :

class Foo:
    x = 5
    y = [x for i in range(1)]

그러나 이것은 작동합니다.

def Foo():
    x = 5
    y = [x for i in range(1)]

이 제한 사항은 참조 안내서 의이 섹션 끝에 설명되어 있습니다.


1

가장 바깥 쪽 반복자가 주변 범위에서 평가 되므로 종속성을 이해 범위로 전달하기 위해 zip함께 사용할 수 있습니다 itertools.repeat.

import itertools as it

class Foo:
    x = 5
    y = [j for i, j in zip(range(3), it.repeat(x))]

또한 for이해에 중첩 루프를 사용할 수 있으며 가장 바깥쪽에 iterable에 종속 항목을 포함 시킬 수 있습니다 .

class Foo:
    x = 5
    y = [j for j in (x,) for i in range(3)]

OP의 구체적인 예 :

from collections import namedtuple
import itertools as it

class StateDatabase:
    State = namedtuple('State', ['name', 'capital'])
    db = [State(*args) for State, args in zip(it.repeat(State), [
        ['Alabama', 'Montgomery'],
        ['Alaska', 'Juneau'],
        # ...
    ])]
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.