[Rejected] PEP 3122 - Delineation of the main module
원문 링크: PEP 3122 - Delineation of the main module
상태: Rejected 유형: Standards Track 작성일: 27-Apr-2007
PEP 3122 – 메인 모듈의 경계 설정 (Delineation of the main module)
작성자: Brett Cannon 상태: Rejected (거부됨) 유형: Standards Track 생성일: 2007년 4월 27일
주의 사항
이 PEP는 거부되었습니다. Guido van Rossum은 패키지 내에서 스크립트를 실행하는 것을 안티패턴으로 간주합니다.
요약 (Abstract)
PEP 328이 구현된 환경에서 상대 임포트(relative import)의 이름 해석(name resolution) 방식 때문에 패키지 내 모듈 실행이 불가능해지는 문제가 발생합니다. 이 문제는 “메인” 모듈로 실행되는 모듈이 __name__
속성을 해당 모듈의 절대 이름 대신 __main__
으로 대체하기 때문에 발생하며, 이는 메인 모듈에서 상대 임포트를 절대 이름으로 해석하는 기능을 방해합니다.
이 문제를 해결하기 위해, 이 PEP는 메인 모듈이 구분되는 방식을 변경할 것을 제안합니다. 모듈의 __name__
속성은 그대로 유지하고, sys.main
을 메인 모듈의 이름으로 설정함으로써 상대 임포트를 사용하는 패키지 내 모듈을 실행할 수 있는 일부 사례를 허용할 것입니다.
이 PEP는 PEP 299에서 제안하는 것처럼 모듈 수준의 함수를 자동으로 실행하는 아이디어는 다루지 않습니다.
문제점 (The Problem)
PEP 328의 도입으로 인해 상대 임포트는 임포트를 수행하는 모듈의 __name__
속성에 의존하게 되었습니다. 이는 상대 임포트에서 사용되는 점(dots)이 호출하는 모듈 이름의 일부를 제거하여 임포트가 패키지 계층 구조의 어느 위치에 속해야 하는지 계산하는 데 사용되기 때문입니다 (PEP 328 이전에는 상대 임포트가 실패하면 절대 임포트로 폴백(fallback)하여 성공할 가능성이 있었습니다).
예를 들어, bacon.ham.beans
모듈(bacon.ham.beans
는 패키지 자체가 아님, 즉 __path__
를 정의하지 않음)에서 from .. import spam
임포트를 고려해 봅시다. 상대 임포트의 이름 해석은 호출자의 이름(bacon.ham.beans
)을 가져와 점을 기준으로 분할한 다음, 수준(여기서는 2)에 따라 마지막 n
부분을 잘라냅니다. 이 예에서는 ham
과 beans
가 제거되고 남은 부분(bacon
)과 spam
이 결합됩니다. 이는 bacon.spam
모듈의 올바른 임포트로 이어집니다.
상대 임포트를 처리할 때 모듈의 __name__
속성에 대한 이러한 의존성은 패키지 내에서 스크립트를 실행할 때 문제가 됩니다. 실행되는 스크립트의 이름이 __main__
으로 설정되기 때문에 임포트 메커니즘은 상대 임포트를 해석할 수 없어 ImportError
를 발생시킵니다.
예를 들어, from . import spam
을 포함하는 __init__.py
파일이 있는 bacon
이라는 패키지가 있다고 가정해 봅시다. 또한 bacon
패키지 내에 spam
이라는 모듈을 생성합니다 (빈 파일일 수 있음). 이제 python bacon/__init__.py
또는 python -m bacon
을 통해 bacon
패키지를 실행하려고 하면, 패키지가 아닌 곳에서 상대 임포트를 시도하는 것에 대한 ImportError
가 발생합니다. 명백히 임포트는 유효하지만, __name__
이 __main__
으로 설정되기 때문에 임포트 메커니즘은 __name__
에 점이 없으므로 bacon/__init__.py
가 패키지 안에 없다고 생각합니다. 알고리즘이 어떻게 작동하는지 더 자세히 보려면 샌드박스의 importlib.Import._resolve_name()
을 참조하세요.
현재 해결책은 실행되는 모듈의 모든 상대 임포트를 제거하고 절대 임포트로 변경하는 것입니다. 그러나 이는 패키지 내 모듈을 실행 가능하게 만들기 위해 특정 유형의 리소스를 사용해야 한다는 점에서 바람직하지 않습니다.
해결책 (The Solution)
이 문제에 대한 해결책은 모듈에서 __name__
의 값을 변경하지 않는 것입니다. 그러나 실행 중인 코드가 스크립트로 실행되고 있음을 알리는 방법은 여전히 필요합니다. 이는 sys
모듈의 main
이라는 새 속성으로 처리됩니다.
모듈이 스크립트로 실행될 때, sys.main
은 모듈의 이름으로 설정됩니다. 이는 현재의 관용구인 if __name__ == '__main__': ...
를 다음과 같이 변경합니다.
import sys
if __name__ == sys.main:
# ...
새로 제안된 해결책은 모듈 임포트라는 한 줄의 상용구(boilerplate)를 추가합니다. 그러나 이 해결책은 새로운 내장(built-in)이나 모듈 속성을 도입하지 않으므로 (거부된 아이디어에서 논의됨) 추가 한 줄의 가치가 있다고 판단되었습니다.
제안된 해결책의 또 다른 문제점(거부된 모든 아이디어에도 적용됨)은 파일의 이름을 찾는 문제를 직접적으로 해결하지 못한다는 것입니다. 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__
으로 설정하는 폴백(fallback) 시맨틱이 발생할 것입니다. sys.main
은 __name__
이 무엇으로 설정되든 상관없이 올바른 값으로 설정될 것입니다.
구현 (Implementation)
-m
옵션이 사용되면, sys.main
은 전달된 인수로 설정됩니다. sys.argv
는 현재와 같이 조정됩니다. 그런 다음 __import__(self.main)
와 동등한 작업이 발생합니다. 이는 runpy
모듈이 __name__
및 기타 속성을 명시적으로 설정하기 위해 모듈 이름으로 지정된 파일에 대한 코드 객체를 가져오는 현재 시맨틱과 다릅니다. 임포트 메커니즘이 이 상황에서 정상적인 작업을 수행할 수 있으므로 더 이상 필요하지 않습니다.
파일 이름이 지정되면 sys.main
은 __main__
으로 설정됩니다. 지정된 파일은 읽혀지고 코드 객체가 생성된 다음 __name__
이 __main__
으로 설정된 채로 실행됩니다. 이는 현재 시맨틱을 반영합니다.
전환 계획 (Transition Plan)
Python 2.6이 현재 시맨틱과 제안된 시맨틱을 모두 지원할 수 있도록 sys.main
은 항상 __main__
으로 설정됩니다. 그렇지 않으면 Python 2.6에서는 변경 사항이 없습니다. 이는 유감스럽게도 Python 2.6에서는 이 변경으로 인한 이점이 없지만, 2.6과 3.0에서 최대한 작동해야 하는 코드의 호환성을 극대화합니다.
새로운 관용구로의 전환을 돕기 위해, 2to3 도구는 현재의 if __name__ == '__main__': ...
관용구를 새로운 것으로 변환하는 규칙을 얻게 될 것입니다. 그러나 이는 관용구 외부에서 __name__
을 확인하는 코드에는 도움이 되지 않습니다.
거부된 아이디어 (Rejected Ideas)
__main__
내장 (built-in)
__main__
이라는 내장을 도입하자는 역제안이 있었습니다. 내장의 값은 실행되는 모듈의 이름이 될 것입니다 (제안된 sys.main
과 동일). 이는 다음과 같은 새로운 관용구로 이어질 것입니다.
if __name__ == __main__:
# ...
한 가지 단점은 문법적 차이가 미묘하다는 것입니다. 즉, __main__
주변의 따옴표가 제거됩니다. 일부는 기존 Python 프로그래머들이 실수로 따옴표를 넣어서 버그가 발생할 것이라고 생각합니다. 그러나 매우 얕은 버그이기 때문에 테스트를 통해 빠르게 발견될 것이라고 주장할 수도 있습니다.
내장의 이름이 분명히 다를 수 있지만 (예: main
), 다른 단점은 새로운 내장을 도입한다는 것입니다. Python에 또 다른 내장을 추가하지 않고도 sys.main
과 같은 간단한 해결책이 가능했기 때문에 이 제안은 거부되었습니다.
__main__
모듈 속성
또 다른 제안은 모든 모듈에 __main__
속성을 추가하는 것이었습니다. 메인 모듈로 실행되는 모듈의 경우, 이 속성은 참 값을 가질 것이고 다른 모든 모듈은 거짓 값을 가질 것입니다. 이는 메인 모듈 관용구를 if __main__: ...
으로 단순화하는 좋은 결과를 가져옵니다.
단점은 새로운 모듈 속성을 도입한다는 것이었습니다. 또한 제안된 해결책보다 임포트 메커니즘과의 더 많은 통합이 필요했습니다.
__name__
대신 __file__
사용
현재 시맨틱을 포함하여 어떤 제안이든 모듈의 __name__
속성 대신 __file__
속성을 사용하도록 변경될 수 있었습니다. 이 문제점은 제안된 해결책에서 모듈에 __file__
속성이 정의되지 않았거나 다른 모듈과 동일한 값을 가질 수 있다는 문제가 발생한다는 것입니다.
현재 시맨틱에서 발생하는 문제는 임포트가 작동하려면 여전히 파일 경로를 모듈 이름으로 해결하려고 시도해야 한다는 것입니다.
__eq__
를 오버라이드하는 __name__
을 위한 특별한 문자열 서브클래스
한 가지 제안은 __eq__
메서드를 오버라이드하여 __main__
과 모듈의 실제 이름 모두와 같다고 비교되도록 str
의 서브클래스를 정의하는 것이었습니다. 다른 모든 면에서 서브클래스는 str
과 동일할 것입니다.
이것은 너무 해킹처럼 보였기 때문에 거부되었습니다.
참조 (References)
2to3 도구 importlib Python-Dev 이메일: “PEP to change how the main module is delineated”
저작권 (Copyright)
이 문서는 공개 도메인에 있습니다.
⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.
Comments