Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 3122 – 메인 모듈의 구분

Author:
Brett Cannon
Status:
Rejected
Type:
Standards Track
Created:
27-Apr-2007
Post-History:


Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

Attention

이 PEP는 거부되었습니다. Guido는 패키지 내부에서 스크립트를 실행하는 것을 안티 패턴으로 봅니다 [3].

초록

상대 임포트에서 이름 해석이 작동하는 방식으로 인해, PEP 328이 구현된 환경에서는 패키지 내 모듈을 실행하는 기능을 더 이상 사용할 수 없게 됩니다. 이러한 실패는 실행 중인 모듈이 “메인” 모듈로서 모듈의 절대 이름을 그대로 유지하는 대신 __name__ 특성을 "__main__"으로 바꾼다는 사실에서 비롯됩니다. 이로 인해 가져오기가 메인 모듈에서 시작된 상대 가져오기를 절대 이름으로 확인할 수 없게 됩니다.

이 문제를 해결하기 위해 이 PEP는 메인 모듈을 구분하는 방식을 변경할 것을 제안합니다. 모듈의 __name__ 특성을 그대로 두고 sys.main을 메인 모듈의 이름으로 설정하면 상대 가져오기를 사용하는 패키지 내부의 모듈을 실행하는 일부 경우를 허용할 수 있습니다.

이 PEP는 PEP 299에서 제안한 것처럼 자동으로 실행되는 모듈 수준 함수를 도입하는 아이디어는 다루지 않습니다.

문제

관련 PEP 328이 도입되면서 상대 임포트는 임포트를 수행하는 모듈의 __name__어트리뷰트에 의존하게 되었습니다. 이는 상대 가져오기에서 점을 사용하여 호출하는 모듈 이름의 일부를 제거하고 패키지 계층 구조에서 가져오기가 어느 위치에 속해야 하는지 계산하기 때문입니다(PEP 328 이전에는 상대 가져오기가 실패할 수 있었으며, 성공할 가능성이 있는 절대 가져오기로 대체되었습니다).

예를 들어 bacon.ham.beans 모듈에서 수행되는 from .. import spam 가져오기를 생각해 보십시오(bacon.ham.beans 자체는 패키지가 아니며, 즉 __path__를 정의하지 않습니다). 상대 가져오기의 이름 확인은 호출자의 이름인 bacon.ham.beans을 가져와 점을 기준으로 분할한 다음, 레벨(이 경우 2)에 따라 마지막 n개 부분을 잘라 냅니다. 이 예에서는 hambeans가 모두 제거되고, 남은 부분인 baconspam이 결합됩니다. 그러면 모듈 bacon.spam을 올바르게 가져오게 됩니다.

상대 가져오기를 처리할 때 모듈의 __name__ 특성에 의존하는 방식은 패키지 내부에서 스크립트를 실행할 때 문제가 됩니다. 실행 중인 스크립트의 이름이 '__main__'로 설정되므로 가져오기는 어떤 상대 가져오기도 확인할 수 없으며, 그 결과 ImportError가 발생합니다.

예를 들어 __init__.py 파일을 포함하는 bacon이라는 패키지가 있다고 가정해 보십시오.:

from . import spam

또한 bacon 패키지 내부에 spam이라는 모듈을 만드십시오(빈 파일이어도 됩니다). 이제 bacon 패키지를 실행하려고 하면(python bacon/__init__.py 또는 python -m bacon을 통해) 비패키지 내부에서 상대 가져오기를 시도했다는 내용의 ImportError가 발생합니다. 물론 가져오기는 유효하지만, __name__'__main__'로 설정했기 때문에 가져오기는 __name__에 점이 없으므로 bacon/__init__.py가 패키지에 속하지 않는다고 간주합니다. 알고리즘이 작동하는 방식을 더 자세히 알아보려면 샌드박스의 importlib.Import._resolve_name()를 참조하십시오 [2].

현재의 해결 방법은 실행 중인 모듈에서 모든 상대 가져오기를 제거하고 절대 가져오기로 만드는 것입니다. 그러나 패키지의 모듈을 실행할 수 있도록 하기 위해 특정 유형의 리소스를 사용하도록 요구해서는 안 되므로 이는 바람직하지 않습니다.

해결책

이 문제의 해결책은 모듈에서 __name__의 값을 변경하지 않는 것입니다. 그러나 실행 중인 코드가 스크립트로 실행되고 있음을 알 수 있는 방법은 여전히 필요합니다. 이는 sys 모듈의 main이라는 새 특성을 통해 처리됩니다.

모듈이 스크립트로 실행될 때 sys.main은 해당 모듈의 이름으로 설정됩니다. 이는 현재 다음과 같은 관용구를 변경합니다.:

if __name__ == '__main__':
    ...

수신:

import sys
if __name__ == sys.main:
    ...

새로 제안된 해결책은 모듈 임포트라는 보일러플레이트 한 줄을 추가합니다. 그러나 이 해결책은 Rejected Ideas에서 논의한 것처럼 새로운 내장 기능이나 모듈 속성을 도입하지 않으므로, 추가되는 한 줄은 감수할 만한 가치가 있다고 판단되었습니다.

제안된 해결책의 또 다른 문제는(모든 거부된 아이디어에도 동일하게 적용됩니다) 파일 이름을 알아내는 문제를 직접 해결하지 않는다는 점입니다. python bacon/spam.py를 고려하십시오. 파일 이름만으로는 bacon이 패키지인지 명확하지 않습니다. 이를 제대로 알아내려면 현재 디렉터리가 sys.path에 존재해야 할 뿐만 아니라 bacon/__init__.py도 존재해야 합니다.

그러나 이것은 간단한 예입니다. python ../spam.py를 고려하십시오. 파일 이름만으로는 spam.py가 패키지 안에 있는지 전혀 명확하지 않습니다. 가능한 한 가지 해결책은 ..의 절대 이름을 알아내고, __init__.py라는 파일이 존재하는지 확인한 다음, 해당 디렉터리가 sys.path에 있는지 확인하는 것입니다. 그렇지 않다면 더 이상 __init__.py 파일이 발견되지 않거나 디렉터리가 sys.path에서 발견될 때까지 디렉터리 트리를 계속 상위로 거슬러 올라가야 합니다.

이는 잠재적으로 비용이 많이 드는 과정일 수 있습니다. 패키지 깊이가 깊다면 패키지가 sys.path에서 어디에 기반을 두는지, 혹은 아예 기반을 두고 있는지를 알아내기 위해 많은 디스크 액세스가 필요할 수 있습니다. 실행된 스크립트가 있는 파일 시스템이 NFS와 같은 것이라면 stat 호출만으로도 비용이 많이 들 수 있습니다.

이러한 문제 때문에 PEP 338에서 도입된 -m 명령줄 인자를 사용하는 경우에만 __name__이 설정됩니다. 그렇지 않으면 __name__"__main__"으로 설정하는 대체 의미 체계가 적용됩니다. __name__이 무엇으로 설정되었는지와 관계없이 sys.main은 올바른 값으로 설정됩니다.

구현

-m 옵션을 사용하면 sys.main은 전달된 인자로 설정됩니다. sys.argv는 현재와 같이 조정됩니다. 그런 다음 __import__(self.main)에 해당하는 작업이 수행됩니다. 이는 현재 의미 체계와 다릅니다. runpy 모듈은 __name__ 및 기타 속성을 명시적으로 설정하기 위해 모듈 이름으로 지정된 파일의 코드 객체를 가져오기 때문입니다. 이 상황에서는 임포트가 정상적인 작업을 수행할 수 있으므로 더 이상 이것이 필요하지 않습니다.

파일 이름이 지정되면 sys.main"__main__"으로 설정됩니다. 그런 다음 지정된 파일을 읽고 코드 객체를 생성한 뒤, __name__"__main__"으로 설정하여 실행합니다. 이는 현재 의미 체계를 그대로 따릅니다.

전환 계획

Python 2.6이 현재 의미 체계와 제안된 의미 체계를 모두 지원할 수 있도록 sys.main은 항상 "__main__"으로 설정됩니다. 그 외에는 Python 2.6에서 아무런 변경도 발생하지 않습니다. 안타깝게도 이는 Python 2.6에서 이 변경의 이점을 얻을 수 없다는 의미이지만, 2.6 및 3.0에서 최대한 작동해야 하는 코드의 호환성을 극대화합니다.

새로운 관용구로의 전환을 돕기 위해 2to3 [1]에는 현재 if __name__ == '__main__': ... 관용구를 새로운 관용구로 변환하는 규칙이 추가됩니다. 다만 관용구 외부에서 __name__을 확인하는 코드에는 도움이 되지 않습니다.

거부된 아이디어

__main__ 내장 기능

__main__이라는 내장 기능을 도입하자는 반대 제안입니다. 이 내장 기능의 값은 실행 중인 모듈의 이름이 됩니다(제안된 sys.main과 동일합니다). 이는 다음과 같은 새로운 관용구로 이어집니다.:

if __name__ == __main__:
    ...

단점은 구문상의 차이가 미묘하다는 점입니다. 즉, “__main__” 주위의 따옴표가 사라집니다. 일부 사람들은 기존 Python 프로그래머의 경우 실수로 따옴표를 넣는 버그가 발생할 것이라고 생각합니다. 그러나 매우 단순한 버그이므로 테스트를 통해 빠르게 발견될 것이라고 주장할 수도 있습니다.

내장 기능의 이름은 당연히 다를 수 있지만(예: main), 또 다른 단점은 새로운 내장 기능을 도입한다는 점입니다. Python에 또 다른 내장 기능을 추가하지 않고도 sys.main과 같은 간단한 해결책이 가능하므로 이 제안은 거부되었습니다.

__main__ 모듈 속성

또 다른 제안은 모든 모듈에 __main__속성을 추가하는 것이었습니다. 주 모듈로 실행되는 모듈에서는 이 속성이 참 값을 갖고, 다른 모든 모듈에서는 거짓 값을 갖도록 하는 방식입니다. 이는 다음과 같이 주 모듈 관용구를 간단하게 만드는 좋은 결과를 가져옵니다.:

if __main__:
    ...

단점은 새로운 모듈 속성을 도입한다는 점이었습니다. 또한 제안된 해결책보다 가져오기 메커니즘과의 통합이 더 많이 필요했습니다.

__name__대신 __file__사용

현재의 의미 체계를 포함하여, 모든 제안을 모듈의 __name__대신 __file__속성을 사용하도록 변경할 수 있습니다. 이 방식의 문제는 제안된 해결책을 사용할 경우 __file__속성이 정의되지 않은 모듈이 있거나 다른 모듈과 동일한 값을 갖는 모듈이 생긴다는 점입니다.

현재의 의미 체계에서 발생하는 문제는 가져오기가 작동하도록 파일 경로를 모듈 이름으로 확인해야 한다는 점입니다.

__eq__를 재정의하는 __name__용 특수 문자열 서브클래스

한 가지 제안은 str의 하위 클래스를 정의하여, 해당 클래스의 __eq__ 메서드가 실제 모듈 이름뿐 아니라 "__main__"과도 같은 것으로 비교되도록 재정의하는 것이었습니다. 그 밖의 모든 측면에서 이 서브클래스는 str과 동일합니다.

이는 지나치게 편법처럼 보였기 때문에 거부되었습니다.

참고 자료