다른 모듈에서 모듈 변수를 변경하는 방법은 무엇입니까?


107

라는 패키지가 bar있고 다음이 포함되어 있다고 가정 합니다 bar.py.

a = None

def foobar():
    print a

__init__.py:

from bar import a, foobar

그런 다음이 스크립트를 실행합니다.

import bar

print bar.a
bar.a = 1
print bar.a
bar.foobar()

내가 기대하는 것은 다음과 같습니다.

None
1
1

내가 얻는 것은 다음과 같습니다.

None
1
None

아무도 내 오해를 설명 할 수 있습니까?

답변:


103

을 (를) 사용하고 from bar import a있습니다. a가져 오기 모듈의 전역 범위 (또는 가져 오기 문이 발생하는 모든 범위)에서 심볼이됩니다.

에 새 값을 할당 하면 실제 값이 아닌 aa포인트도 변경하는 것 입니다. 에서 bar.py직접 가져 와서을 설정하여 실험을 수행하십시오 . 이렇게하면 실제로이 컨텍스트에서 '실제'값을 수정 하게 됩니다.import bar__init__.pybar.a = 1bar.__dict__['a']a

세 개의 레이어로 약간 복잡하지만 실제로에서 파생 된 라는 모듈 bar.a = 1의 값을 변경합니다 . 의이 값을 변경하지 않는 그 때문에보고 실제 파일에서의 삶 . 변경하려면 설정할 수 있습니다.abar__init__.pyafoobarfoobarbar.pybar.bar.a

이것은 문장 의 from foo import bar형태 를 사용할 때의 위험 중 하나입니다 import. 그것은 bar두 개의 기호로 분할 됩니다. 하나 foo는 원래 값을 가리 키기 시작하는 전역에서 볼 수 있고 import문장이 실행되는 범위에서 볼 수있는 다른 기호 입니다. 심볼이 가리키는 위치를 변경해도 가리키는 값도 변경되지 않습니다.

이런 종류의 것들은 reload인터랙티브 인터프리터에서 모듈을 시도 할 때 킬러 입니다.


감사! 당신의 대답은 아마도 범인을 찾는 데 몇 시간을 절약 할 수 있었을 것입니다.
jnns

bar.bar.a접근 방식 조차도 대부분의 사용 사례에서 많은 도움이되지 않는 것 같습니다 . 컴파일 또는 모듈로드와 런타임 할당을 혼합하는 것은 자신 (또는 코드를 사용하는 다른 사용자)을 혼란스럽게 만들 수 있기 때문에 약한 생각 일 것입니다. 특히 파이썬처럼 다소 일관성이없는 언어에서 작동합니다.
matanster

26

이 질문에 어려움 중 하나는 소스라는 이름의 프로그램을 가지고있다 bar/bar.py: import bar수입하거나 bar/__init__.py또는 bar/bar.py이 조금 성가신를 추적 할 수있는 완료 위치에 따라 a입니다 bar.a.

작동 방식은 다음과 같습니다.

무슨 일이 일어나는지 이해하는 열쇠는 당신의 __init__.py,

from bar import a

사실상 다음과 같은 일을한다

a = bar.a
# … where bar = bar/bar.py (as if bar were imported locally from __init__.py)

새 변수를 정의합니다 ( bar/__init__.py:a원하는 경우). 따라서 from bar import ain __init__.py은 이름 bar/__init__.py:a을 원래 bar.py:a개체 ( None) 에 바인딩 합니다 . 당신이 할 수있는 이유입니다 from bar import a as a2에서 __init__.py:이 경우, 당신이 모두있는 것이 분명 bar/bar.py:a하고 뚜렷한 변수 이름 bar/__init__.py:a2귀하의 경우를 (두 변수의 이름은 둘 다 될 일이 a,하지만 여전히 라이브 서로 다른 네임 스페이스에서 그들은 :에 __init__.py, 그들은 bar.aa)입니다.

이제 할 때

import bar

print bar.a

변수에 액세스하고 있습니다 bar/__init__.py:a(을 import bar가져 오기 때문에 bar/__init__.py). 이것은 수정하는 변수입니다 (1로). 변수의 내용을 건드리지 않습니다 bar/bar.py:a. 그래서 나중에 할 때

bar.foobar()

당신이 전화를 bar/bar.py:foobar()변수에 액세스하는, a에서 bar/bar.py여전히있는이 None때 ( foobar()정의되는, 그래서 그것은, 한 번 모든 변수 이름을 결합 a에서이 bar.py있다 bar.py:a다른, 아니 a많은이있을 수 있습니다 또 다른 정의 변수 모듈이-로 a가져온 모든 모듈의 변수 ). 따라서 마지막 None출력입니다.

결론 : 그것은 어떤 모호성을 피하기 위해 최선 import bar에 의해, 아니 어떤 데 bar/bar.py(이후 모듈 bar.__init__.py차종 디렉토리를 bar/당신도 함께 가져올 수있는, 패키지 이미 import bar).


12

다른 말로하면 :이 오해는 매우 쉽게 만들 수 있습니다. 이것은 파이썬 언어 참조 : symbol 대신 object 사용에 몰래 정의되어 있습니다 . 나는 파이썬 언어 참조가 이것을 더 명확하고 덜 희박하게 만들 것을 제안합니다 ..

from양식은 모듈 이름을 바인딩하지 않습니다. 식별자 목록을 살펴보고 (1) 단계에서 찾은 모듈에서 각각을 조회하고 로컬 네임 스페이스의 이름 을 찾은 개체 에 바인딩합니다 .

하나:

가져올 때 가져온 심볼의 현재 값을 가져와 정의 된대로 네임 스페이스에 추가합니다. 참조를 가져 오는 것이 아니라 값을 효과적으로 가져 오는 것입니다.

따라서의 업데이트 된 값을 i가져 오려면 해당 기호에 대한 참조를 보유하는 변수를 가져와야합니다.

즉, 가져 오기는 importJAVA의 external선언, C / C ++의 선언 또는 usePERL 의 절과는 다릅니다.

오히려 Python의 다음 문 :

from some_other_module import a as x

같은 K & R C에 다음 코드 :

extern int a; /* import from the EXTERN file */

int x = a;

(주의 : Python의 경우 "a"와 "x"는 기본적으로 실제 값에 대한 참조입니다. INT를 복사하지 않고 참조 주소를 복사합니다.)


사실, import네임 스페이스 / 범위는 항상 적절하게 분리 된 상태로 유지되고 예기치 않은 방식으로 서로 간섭하지 않기 때문에 Java보다 훨씬 더 깔끔한 Python 방식을 발견했습니다 . 여기처럼 : 네임 스페이스의 이름에 대한 객체의 바인딩을 변경 (읽기 : 모듈의 전역 속성에 어싱)하는 것은 Python의 다른 네임 스페이스 (읽기 : 가져온 참조)에 영향을주지 않습니다. 그러나 Java 등에서는 그렇게합니다. Python에서는 가져온 항목 만 이해하면되고 Java에서는 나중에 가져온 값을 변경하는 경우에 대비하여 다른 모듈도 이해해야합니다.
Tino

2
나는 매우 격렬하게 동의하지 않습니다. 가져 오기 / 포함 / 사용은 다른 모든 언어의 값 형식이 아닌 참조 형식을 사용한 오랜 역사를 가지고 있습니다.
Mark Gerolimatos 2017

4
젠장, 나는 리턴을 쳤다… 참조에 의한 수입에 대한 기대가있다 (@OP에 따라). 사실, 새로운 파이썬 프로그래머는 아무리 경험이 있더라도 "내다보기"와 같은 방식으로 이것을 말해야합니다. 절대로 발생해서는 안됩니다. 일반적인 사용법에 따라 Python은 잘못된 경로를 택했습니다. 필요한 경우 "가져 오기 값"을 생성하되, 가져올 때 값과 기호를 결합하지 마십시오.
Mark Gerolimatos 2017
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.