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개 부분을 슬라이스로 잘라 냅니다. 이 예에서는 ham과 beans가 모두 제거되고, 남은 부분(bacon)에 spam이 결합됩니다. 그 결과 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__용 특수 문자열 서브클래스

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

이는 지나치게 편법적인 방법으로 보여 거부되었습니다.

참고 문헌