[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 부분을 잘라냅니다. 이 예에서는 hambeans가 제거되고 남은 부분(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”

이 문서는 공개 도메인에 있습니다.

⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.

Comments