diff --git a/book/docs/10_2_thread.md b/book/docs/10_2_thread.md
index ec61892..635c710 100644
--- a/book/docs/10_2_thread.md
+++ b/book/docs/10_2_thread.md
@@ -1 +1,251 @@
-# 스레드
+# 멀티 스레딩
+
+## 1. 파이썬에서의 단일 프로세스와 멀티 스레딩
+
+### 1.1 Process (remind)
+
+
+
+프로세스는 “데이터를 이용해서 명령어를 수행”하는 역할을 한다. 따라서, “데이터”와 “명령어”는 중요하다. 또한, 명령을 수행할 때, 외부 I/O (파일 시스템 or 소켓 통신)가 필요할 수도 있다. 추가로, 여러개의 프로세스가 동시에 동작할 수도 있으니 작업의 우선 순위를 위한 장치도 필요할 것이다. 이를 정리하면 다음과 같다.
+
+- 데이터 ⇒ 여러 메모리 공간에 나눠서 저장됨: stack, heap
+- 명령어 ⇒ Instructions (메모리 영역 관점에서는 data가 여기에 들어갈 것으로 보임. 정확하게는 모르겠음.)
+- 외부 I/O ⇒ Files, Sockets
+- 작업의 우선 순위를 위한 장치 ⇒ Locks
+
+추가로 알아야 할 것은, 레지스터다. 레지스터는 정말 빠른 메모리 장치인데, 그 크기가 매우 작다. 대신, 프로세스 수행에 매우 중요한 정보를 가지고 있다
+
+- instruction pointer (or program counter): 어디까지 instruction을 수행했는지에 대한 정보. 정확히는 현재 실행중인 instruction의 메모리 주소를 가리킴.
+
+여기서 우리가 알 수 있는 것은, 프로세스는 1개의 instruction pointer를 갖는다는 것이다. 즉, 프로세스는 하나의 명령만 수행한다는 것이다. CPython 맥락에서 얘기해보면, only one Python bytecode instruction can be executing at a given time 이라는 것이다.
+
+### 1.2 Multi Threading
+
+
+
+단일 프로세스의 단점(=program counter가 1개임)을 극복하기 위해, OS는 멀티스레딩을 지원하기 시작했다. 1개의 프로세스 내에 존재하는 여러개의 스레드들은, 각각 Program Counter와 Stack을 갖는다.
+
+- Program Counter (remind): 현재 실행중인 명령어의 주소
+- Stack: 명령어를 수행할 때 필요한 메모리 공간
+
+즉, 구조적으로 “여러개의 명령어를 수행”할 수 있게 된 것이다👍🏻
+
+다만, 조심해야 할 점이 있다. 바로, race condition이다. Race condition이란, 여러개의 작업자가 특정 메모리에 동시에 접근하는 상황을 의미하는데, 이렇게 되면 여러가지 예상하지 못한 일이 발생할 수 있다. 사칙연산이 의도대로 되지 않는다거나, 파일에 10개의 라인을 적었는데 8개만 적혔다거나 등등이다. 참고로, 멀티스레딩 환경에서의 race condition을 방지하는 것을 “thread safety를 보장한다” 라고 말한다. 주로 thread safety는 동일한 메모리 주소에 접근하는 것을 제한하는 방법인, Lock을 통해 해결한다.
+
+## 2. GIL (Global Interpreter Lock)
+
+> *The Python interpreter is not fully thread-safe. In order to support multi-threaded Python programs, there’s a global lock, called the global interpreter lock or GIL, that must be held by the current thread before it can safely access Python objects. Without the lock, even the simplest operations could cause problems in a multi-threaded program: for example, when two threads simultaneously increment the reference count of the same object, the reference count could end up being incremented only once instead of twice.
+(by [The Python/C API Reference](https://docs.python.org/3.9/c-api/index.html))*
+>
+
+CPython은 C API기반으로 만들어지긴 했지만(=C API를 이용해 멀티 스레딩을 구성함), 결국 파이썬 스레드는 evaluation loop(평가 루프) 위에서 파이썬 바이트 코드를 실행한다. 그런데, 이 evaluation loop는 not thread-safe하다.
+
+- 가비지 컬렉터
+- 인터프리터 상태를 구성하는 부분은 전역적이고 공유 상태임 (참고: 1개의 인터프리터가 여러개의 스레드를 갖는 구조임)
+- 따라서, not thread-safe
+
+이를 해결하기 위해 CPython 진영에서 나온 것이 GIL (Global Interpreter Lock)이다. 이 덕분에(?) global thread safety를 달성할 수 있었지만, 대신 많은 것을 포기하게 되었다. 특히, 속도를 많이 희생하게 되었다😥 특정 작업이 오래 걸리면, 다른 작업은 이전 작업이 끝날 때까지 대기해야 하기 때문이다.
+
+특정 명령어가 수행되는 동안 다른 명령어가 아예 수행되지 않는 것을 방지하기 위해, evaluation loop state(평가 루프 상태)는 `gil_drop_request` 플래그를 저장한다. 특정 프레임의 바이트코드 연산이 완료되면 GIL이 잠깐 해제된다. 그러면 그 사이에, 또 다른 프레임에서 GIL을 획득해서 바이트코드 연산을 수행하고 GIL을 반납하는 식이다. 이런 방식으로 여러개의 명령어를 동시에 수행할 수 있다. 여기서 동시에는 병렬성(parallelism)이 아닌 동시성(concurrency)을 의미한다! 코드로 본다면 다음과 같다.
+
+
+
+CPU-bound 작업에서의 GIL의 한계점은 명확하지만, I/O-bound 작업에서는 매우 안전하고 동시적으로 작업할 수 있다는 장점이 있다.
+
+### 2.1 macros for I/O
+
+GIL을 통한 성능 저하를 방지하기 위해, evaluation loop 레벨이 아닌 코드 레벨에서의 GIL 관리도 있다. 이는 `Py_BEGIN_ALLOW_THREADS`와 `Py_END_ALLOW_THREADS` macros로 구현된다. 대표적인 예시로, **/Modules/socketmodule.c, line 3245**를 참고하자.
+
+
+
+소켓 연결을 하기 전에 `Py_BEGIN_ALLOW_THREADS` 가 사용되었고, 소켓 연결 후에 `Py_END_ALLOW_THREADS` 가 사용된 것을 알 수 있다. 두 매크로는 **Include/ceval.h**에 정의되어있다.
+
+
+
+`Py_BEGIN_ALLOW_THREADS` 호출하면, `PyEval_SaveThread()`를 호출한다. 해당 함수는 **/Python/ceval.c, line 444**에 구현되어있다.
+
+
+
+어디서 익숙한 코드가 보인다. `drop_gil` 코드는 evaluation loop에서 GIL을 관리할 때 사용된 함수이다. 즉, GIL을 잠시 해제한다는 것을 알 수 있다! 그럼 이제 `Py_END_ALLOW_THREADS` 에서 호출하는 `PyEval_RestoreThread()`를 살펴보자. **/Python/ceval.c, line 458**에 있다.
+
+
+
+여기서도 익숙한 코드를 볼 수 있다. `take_gil`이다. 해당 함수는 GIL을 획득할 때 사용했던 함수이다.
+
+지금까지 살펴본 `Py_BEGIN_ALLOW_THREADS`와 `Py_END_ALLOW_THREADS` macros는 소켓 구현체 뿐만 아니라, 기본 라이브러리 곳곳에서 발견할 수 있다. 거의 300번 이상 사용된다고 한다. CPython에서 I/O-bound 작업이 GIL에 영향을 받지 않는 이유를 지금까지의 설명으로 정리할 수 있겠다!
+
+- Making HTTP requests
+- Interacting with local hardware
+- Encrypting data
+- Reading and writing files
+
+## 3. 프로세스, 런타임, 인터프리터, 스레드 상호 작용
+
+
+
+1. CPython 프로그램을 실행하면, 프로세스가 생성된다. 프로세스는 CPython 런타임을 하나 가지고 있다.
+2. CPython은 런타임이 하나 있고, 이 런타임은 런타임 상태(`Runtime State`)를 가지고 있다.
+3. 런타임에는 1개 이상의 인터프리터가 있다.
+4. 인터프리터는 인터프리터 상태(`Interpreter State`)를 가지고 있다.
+5. 인터프리터는 코드 객체(code object)를 일련의 프레임 객체(a series of frame objects)로 변환한다.
+6. 인터프리터는 스레드를 최소 하나 가진다. 스레드는 스레드 상태(`Thread State`)를 가진다.
+7. 프레임 객체는 프레임 스택(스택 종류 중 하나)에서 실행된다.
+8. CPython은 값 스택(스택 종류 중 하나)에서 변수를 참조한다.
+9. 인터프리터 상태는 스레드들을 연결 리스트로 가지고 있다.
+10. 스레드 상태는 다양한 properties를 갖는다. 목록은 위 그림을 참고하자.
+11. 멀티프로세싱의 preparation data 처럼, 스레드도 boot state가 필요하다. 하지만, 프로세스와 메모리 공간을 공유하기 때문에, 데이터를 직렬화해서 파일 스트림으로 주고 받는 과정은 필요 없음.
+12. (참고) 위 그림에서는 GIL이 인터프리터 레벨에 있는 것처럼 보이는데, 파이썬 3.9 버전에서는 런타임 레벨에 있는 것으로 보임. 왜냐면, 서브인터프리터 프로젝트의 목적이 각 인터프리터는 독립적인 GIL을 사용하는 것인데, 아직 구현이 완전하지 않다고 했기 때문임. 따라서, GIL이 “인터프리터에 속해있다” 혹은 “런타임에 속해있다”는 어떤 버전을 쓰냐에 따라 달라지는 말로 보임.
+
+## 4. 새로운 스레드가 생기는 과정
+
+
+
+1. bootstate를 생성한 후 args와 kwargs 인자와 함께 target에 연결된다. (target은 실행할 함수)
+ - (참고) target is the callable object
+2. bootstate를 인터프리터 상태에 연결한다.
+3. 새 PyThreadState를 생성하고, 현재 인터프리터에 연결한다.
+ - (참고) 인터프리터 상태는 스레드들을 연결리스트로 가지고 있다
+4. PyEval_InitThreads()를 호출해서 GIL이 활성화되지 않았을 경우 GIL을 활성화한다.
+5. 운영 체제에 맞는 PyThread_start_new_thread 구현을 사용해서 새 스레드를 시작한다. 아래 그림은 이 과정을 보다 자세하게 보여준다.
+
+
+
+> threading.Thread를 사용해 새로운 스레드를 생성한다고 생각해보자. threading. Thread는 PyThread 타입을 추상화하는 고수준 모듈이고, PyThread 인스턴스는 C 확장 모듈 _thread가 관리한다. _thread 모듈은 새 스레드를 실행하기 위한 진입점으로, `thread_PyThread_start_new_thread()`를 제공한다. start_new_thread()는 Thread타입 인스턴스의 메서드다.
+>
+1. threading.Thread.start_new_thread()
+2. PyThread
+3. _thread.thread_PyThread_start_new_thread()
+4. 운영체제에 맞는 PyThread_start_new_thread 구현체 사용
+ 1. POSIX 스레드 (리눅스, macOS)
+ 2. NT 스레드 (윈도우)
+5. PyThread_start_new_thread는 운영 체제 스레드를 생성하고, 어트리뷰트들을 설정한 후, 새 스레드에서 콜백 t_bootstrap()을 실행함.
+6. t_bootstrap()는 저수준 스레드와 파이썬 런타임 간의 인터페이스임. 부트스트랩 함수가 스레드를 생성하고, PyObject_Call()을 사용해 target을 호출함.
+
+## 5. Recap: Multi Threading
+
+1. 각 스레드는 각자의 Program Counter와 Stack을 갖기 때문에, 동시에 다른 작업을 처리할 수 있다.
+2. thread safety를 위해서 CPython 진영에서는 GIL을 사용한다.
+3. GIL로 인해 CPU-bound 작업에서는 멀티스레딩의 이점을 얻지 못하지만, I/O-bound 작업에서는 멀티스레딩을 적극적으로 활용할 수 있다.
+4. 이를 위해서 `Py_BEGIN_ALLOW_THREADS`와 `Py_END_ALLOW_THREADS` macros가 실제 구현에 사용된다. 내장 패키지 곳곳에서 해당 구현을 확인할 수 있다.
+5. 스레드는 프로세스와 메모리를 공유하기 때문에, 프로세스를 새로 생성하는 것보다는 오버헤드가 적다.
+
+## 6. Port Scanner: Multi Threading
+
+책에서는 적은 범위의 포트만 조회하고 있음. 성능 차이를 확실하게 보기 위해 조회 범위를 넓혔음.
+
+**싱글 프로세스 + 싱글 스레드**
+
+```python
+from queue import Queue
+import socket
+import time
+
+timeout = 1.0
+
+def check_port(host: str, port: int, results: Queue):
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.settimeout(timeout)
+ result = sock.connect_ex((host, port))
+ if result == 0:
+ results.put(port)
+ sock.close()
+
+def main():
+ start = time.time()
+ host = "localhost" # Replace with a host you own
+ results = Queue()
+ for port in range(30000, 65536):
+ check_port(host, port, results)
+ while not results.empty():
+ print("Port {0} is open".format(results.get()))
+ print("Completed scan in {0} seconds".format(time.time() - start))
+
+if __name__ == '__main__':
+ main()
+```
+
+- 3.7초 ~ 3.9초 사이
+
+**멀티스레딩**
+
+```python
+from threading import Thread
+from queue import Queue
+import socket
+import time
+
+timeout = 1.0
+
+def check_port(host: str, port: int, results: Queue):
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.settimeout(timeout)
+ result = sock.connect_ex((host, port))
+ if result == 0:
+ results.put(port)
+ sock.close()
+
+def main():
+ start = time.time()
+ host = "localhost" # Replace with a host you own
+ threads = []
+ results = Queue()
+ for port in range(30000, 65536):
+ t = Thread(target=check_port, args=(host, port, results))
+ t.start()
+ threads.append(t)
+ for t in threads:
+ t.join()
+ while not results.empty():
+ print("Port {0} is open".format(results.get()))
+ print("Completed scan in {0} seconds".format(time.time() - start))
+
+if __name__ == '__main__':
+ main()
+```
+
+- 6.5초 ~ 6.6초 사이
+- 훨씬 오래 걸림. I/O 작업인데도 멀티스레딩이 느린 것은 문제가 있음. 아마, 스레드 개수에 제한이 없어서 그런 것으로 추측됨.
+
+**멀티스레딩 + 스레드 개수 제한**
+
+```python
+import socket
+import time
+from concurrent.futures import ThreadPoolExecutor, as_completed
+from queue import Queue
+
+timeout = 1.0
+
+def check_port(host: str, port: int) -> int:
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
+ sock.settimeout(timeout)
+ result = sock.connect_ex((host, port))
+ if result == 0:
+ return port
+ return None
+
+def main():
+ start = time.time()
+ host = "localhost" # Replace with a host you own
+ open_ports = []
+
+ with ThreadPoolExecutor(max_workers=50) as executor:
+ futures = [executor.submit(check_port, host, port) for port in range(30000, 65536)]
+
+ for future in as_completed(futures):
+ port = future.result()
+ if port is not None:
+ open_ports.append(port)
+
+ for port in open_ports:
+ print(f"Port {port} is open")
+ print(f"Completed scan in {time.time() - start:.2f} seconds")
+
+if __name__ == '__main__':
+ main()
+
+```
+
+- 3초 ~ 3.1초 사이
+- 싱글 스레드보다 빨라짐. 참고로, 스레드 개수를 100개로 늘리면 3.4초 ~ 3.5초 정도 소요됨.
+- 멀티스레딩이 멀티프로세싱에 비해 오버헤드가 적다는 것이지, 오버헤드가 없다는 뜻은 아님! 따라서, 스레드 개수의 적절한 설정은 중요함.
diff --git a/book/docs/5_0_config_and_input.md b/book/docs/5_0_config_and_input.md
index 9b5e4ea..5417558 100644
--- a/book/docs/5_0_config_and_input.md
+++ b/book/docs/5_0_config_and_input.md
@@ -7,7 +7,6 @@
3. 인터프리터의 입력에서 모듈을 만드는 과정
## 5.0 CPython Interpreter Execution Overview
-
인터프리터가 실행되는 흐름은 다음과 같습니다.

이 때, 세 가지 요소가 중요합니다.
@@ -18,225 +17,207 @@
세 가지 요소로 넘어가기 전에, 인터프리터가 실행되는 과정에 대한 이해를 높여보겠습니다. 인터프리터의 생성부터 실행까지의 과정을 코드 레벨로 정리하면 다음과 같습니다.
-
-1. 인터프리터 생성 (= 3.컴파일하기)
-
-
-
-- 책에서는 CPython Source Code를 compile 해서 인터프리터를 생성한다고 표현하기도 하고, build한다고도 표현합니다. **Build** 과정에는 **Compile** 과정이 포함되어 있기 때문에, 굳이 구분하지 않고 혼용해서 사용하는 것으로 보입니다.
-- 또한, 빌드 결과물인 **CPython Binary**와 **executable interpreter**(작동하는 인터프리터)를 같은 용어로 사용하고 있습니다. **CPython Interpreter**로 이해해도 될 듯 합니다. **“CPython 소스 코드를 빌드해서 파이썬 인터프리터를 생성했구나”** 라고 생각하면 되겠습니다.
-- 인터프리터 파일은 윈도우, 맥 모두 **python.exe** 파일로 생성됩니다. 확장자는 **Makefile**의 `BUILDEXE` 변수로 변경할 수 있습니다.
-
-```makefile
-# Executable suffix (.exe on Windows and Mac OS X)
-EXE=
-BUILDEXE= .exe
-```
-
-- 인터프리터를 빌드할 때 사용되는 소스 코드는 **Programs/python.c** 입니다. 따라서, 인터프리터를 실행할 때 시작되는 entrypoint file은 **Programs/python.c 입니다.**
-
-```makefile
-# Default target
-all: build_all
-```
-
-```makefile
-build_all: check-clean-src $(BUILDPYTHON) oldsharedmods sharedmods gdbhooks \
- Programs/_testembed python-config
-```
-
-```makefile
-# Build the interpreter
-$(BUILDPYTHON): Programs/python.o $(LIBRARY) $(LDLIBRARY) $(PY3LIBRARY) $(EXPORTSYMS)
- $(LINKCC) $(PY_CORE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/python.o $(BLDLIBRARY) $(LIBS) $(MODLIBS) $(SYSLIBS)
-```
-
-```makefile
-Programs/python.o: $(srcdir)/Programs/python.c
- $(MAINCC) -c $(PY_CORE_CFLAGS) -o $@ $(srcdir)/Programs/python.c
-```
-
-
-
-
-2. 인터프리터 시작: 운영 체제에 따른 main 선택 (./python.exe)
-
-**Programs/python.c**
-
-```c
-/* Minimal main program -- everything is loaded from the library */
-
-#include "Python.h"
+1. 인터프리터 생성 (= `3.컴파일하기`)
-#ifdef MS_WINDOWS
-int
-wmain(int argc, wchar_t **argv)
-{
- return Py_Main(argc, argv);
-}
-#else
-int
-main(int argc, char **argv)
-{
- return Py_BytesMain(argc, argv); // macOS
-}
-#endif
-
-```
-
-- macOS는 `Py_BytesMain` 함수로 진입합니다.
-
-
-
-
-3. Py_BytesMain: argument 포함시키기
-
-**Modules/main.c: L723-L732**
-
-```c
-int
-Py_BytesMain(int argc, char **argv)
-{
- _PyArgv args = {
- .argc = argc,
- .use_bytes_argv = 1,
- .bytes_argv = argv,
- .wchar_argv = NULL};
- return pymain_main(&args);
-}
-```
-
-
-
-
-4. pymain_main: 인터프리터 전체 동작 흐름
-
-**Modules/main.c: L695-L708**
-
-```c
-static int
-pymain_main(_PyArgv *args)
-{
- PyStatus status = pymain_init(args);
- if (_PyStatus_IS_EXIT(status)) {
- pymain_free();
- return status.exitcode;
+ ```mermaid
+ graph LR
+ A("`CPython Source Code`") --> |Build|C("`CPython Interpreter`")
+ ```
+ - 책에서는 CPython Source Code를 compile 해서 인터프리터를 생성한다고 표현하기도 하고, build한다고도 표현합니다. **Build** 과정에는 **Compile** 과정이 포함되어 있기 때문에, 굳이 구분하지 않고 혼용해서 사용하는 것으로 보입니다.
+ - 또한, 빌드 결과물인 **CPython Binary**와 **executable interpreter**(작동하는 인터프리터)를 같은 용어로 사용하고 있습니다. **CPython Interpreter**로 이해해도 될 듯 합니다. **“CPython 소스 코드를 빌드해서 파이썬 인터프리터를 생성했구나”** 라고 생각하면 되겠습니다.
+ - 인터프리터 파일은 윈도우, 맥 모두 **python.exe** 파일로 생성됩니다. 확장자는 **Makefile**의 `BUILDEXE` 변수로 변경할 수 있습니다.
+
+ ```makefile
+ # Executable suffix (.exe on Windows and Mac OS X)
+ EXE=
+ BUILDEXE= .exe
+ ```
+
+ - 인터프리터를 빌드할 때 사용되는 소스 코드는 **Programs/python.c** 입니다. 따라서, 인터프리터를 실행할 때 시작되는 entrypoint file은 **Programs/python.c 입니다.**
+
+ ```makefile
+ # Default target
+ all: build_all
+ ```
+
+ ```makefile
+ build_all: check-clean-src $(BUILDPYTHON) oldsharedmods sharedmods gdbhooks \
+ Programs/_testembed python-config
+ ```
+
+ ```makefile
+ # Build the interpreter
+ $(BUILDPYTHON): Programs/python.o $(LIBRARY) $(LDLIBRARY) $(PY3LIBRARY) $(EXPORTSYMS)
+ $(LINKCC) $(PY_CORE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/python.o $(BLDLIBRARY) $(LIBS) $(MODLIBS) $(SYSLIBS)
+ ```
+
+ ```makefile
+ Programs/python.o: $(srcdir)/Programs/python.c
+ $(MAINCC) -c $(PY_CORE_CFLAGS) -o $@ $(srcdir)/Programs/python.c
+ ```
+
+2. 인터프리터 시작: 운영 체제에 따른 main 선택 (`./python.exe`)
+
+ **Programs/python.c**
+
+ ```c
+ ```c
+ /* Minimal main program -- everything is loaded from the library */
+
+ #include "Python.h"
+
+ #ifdef MS_WINDOWS
+ int
+ wmain(int argc, wchar_t **argv)
+ {
+ return Py_Main(argc, argv);
}
- if (_PyStatus_EXCEPTION(status)) {
- pymain_exit_error(status);
+ #else
+ int
+ main(int argc, char **argv)
+ {
+ return Py_BytesMain(argc, argv); // macOS
}
+ #endif
+
+ ```
+ - macOS는 `Py_BytesMain` 함수로 진입합니다.
- return Py_RunMain();
-}
-```
-
-- `pymain_init()`의 리턴인 `PyStatus`는 `OK`, `ERROR`, `EXIT` 3가지 타입을 갖습니다.
-
- **Include/cpython/initconfig.h: L10-L19**
-
+3. Py_BytesMain: argument 포함시키기
+
+ **Modules/main.c: L723-L732**
+
```c
- typedef struct {
- enum {
- _PyStatus_TYPE_OK=0,
- _PyStatus_TYPE_ERROR=1,
- _PyStatus_TYPE_EXIT=2
- } _type;
- const char *func;
- const char *err_msg;
- int exitcode;
- } PyStatus;
+ int
+ Py_BytesMain(int argc, char **argv)
+ {
+ _PyArgv args = {
+ .argc = argc,
+ .use_bytes_argv = 1,
+ .bytes_argv = argv,
+ .wchar_argv = NULL};
+ return pymain_main(&args);
+ }
```
-
-- 타입이 `OK`인 경우에만 `Py_RunMain()` 함수가 실행됩니다.
-
-
-
-
-5. pymain_init: runtime configuration 설정 (5.1 ~ 5.2)
-
-**Modules/main.c: L33-L75**
-
-```c
-static PyStatus
-pymain_init(const _PyArgv *args)
-{
- PyStatus status;
-
- status = _PyRuntime_Initialize();
- if (_PyStatus_EXCEPTION(status)) {
- return status;
+
+4. pymain_main: 인터프리터 전체 동작 흐름
+
+ **Modules/main.c: L695-L708**
+
+ ```c
+ static int
+ pymain_main(_PyArgv *args)
+ {
+ PyStatus status = pymain_init(args);
+ if (_PyStatus_IS_EXIT(status)) {
+ pymain_free();
+ return status.exitcode;
+ }
+ if (_PyStatus_EXCEPTION(status)) {
+ pymain_exit_error(status);
+ }
+
+ return Py_RunMain();
}
-
- PyPreConfig preconfig;
- PyPreConfig_InitPythonConfig(&preconfig);
-
- status = _Py_PreInitializeFromPyArgv(&preconfig, args);
- if (_PyStatus_EXCEPTION(status)) {
+ ```
+
+ - `pymain_init()`의 리턴인 `PyStatus`는 `OK`, `ERROR`, `EXIT` 3가지 타입을 갖습니다.
+
+ **Include/cpython/initconfig.h: L10-L19**
+
+ ```c
+ typedef struct {
+ enum {
+ _PyStatus_TYPE_OK=0,
+ _PyStatus_TYPE_ERROR=1,
+ _PyStatus_TYPE_EXIT=2
+ } _type;
+ const char *func;
+ const char *err_msg;
+ int exitcode;
+ } PyStatus;
+ ```
+
+ - 타입이 `OK`인 경우에만 `Py_RunMain()` 함수가 실행됩니다.
+5. pymain_init: runtime configuration 설정 (5.1 ~ 5.2)
+
+ **Modules/main.c: L33-L75**
+
+ ```c
+ static PyStatus
+ pymain_init(const _PyArgv *args)
+ {
+ PyStatus status;
+
+ status = _PyRuntime_Initialize();
+ if (_PyStatus_EXCEPTION(status)) {
+ return status;
+ }
+
+ PyPreConfig preconfig;
+ PyPreConfig_InitPythonConfig(&preconfig);
+
+ status = _Py_PreInitializeFromPyArgv(&preconfig, args);
+ if (_PyStatus_EXCEPTION(status)) {
+ return status;
+ }
+
+ PyConfig config;
+ PyConfig_InitPythonConfig(&config);
+
+ /* pass NULL as the config: config is read from command line arguments,
+ environment variables, configuration files */
+ if (args->use_bytes_argv) {
+ status = PyConfig_SetBytesArgv(&config, args->argc, args->bytes_argv);
+ }
+ else {
+ status = PyConfig_SetArgv(&config, args->argc, args->wchar_argv);
+ }
+ if (_PyStatus_EXCEPTION(status)) {
+ goto done;
+ }
+
+ status = Py_InitializeFromConfig(&config);
+ if (_PyStatus_EXCEPTION(status)) {
+ goto done;
+ }
+ status = _PyStatus_OK();
+
+ done:
+ PyConfig_Clear(&config);
return status;
}
-
- PyConfig config;
- PyConfig_InitPythonConfig(&config);
-
- /* pass NULL as the config: config is read from command line arguments,
- environment variables, configuration files */
- if (args->use_bytes_argv) {
- status = PyConfig_SetBytesArgv(&config, args->argc, args->bytes_argv);
- }
- else {
- status = PyConfig_SetArgv(&config, args->argc, args->wchar_argv);
- }
- if (_PyStatus_EXCEPTION(status)) {
- goto done;
- }
-
- status = Py_InitializeFromConfig(&config);
- if (_PyStatus_EXCEPTION(status)) {
- goto done;
- }
- status = _PyStatus_OK();
-
-done:
- PyConfig_Clear(&config);
- return status;
-}
-```
-
-
-
-
-6. Py_RunMain: 파이썬 코드 실행 (5.3)
-
-**Modules/main.c: L672:L692**
-
-```c
-int
-Py_RunMain(void)
-{
- int exitcode = 0;
-
- pymain_run_python(&exitcode);
-
- if (Py_FinalizeEx() < 0) {
- /* Value unlikely to be confused with a non-error exit status or
- other special meaning */
- exitcode = 120;
- }
-
- pymain_free();
-
- if (_Py_UnhandledKeyboardInterrupt) {
- exitcode = exit_sigint();
+ ```
+
+6. Py_RunMain: 파이썬 코드 실행 (5.3)
+
+ **Modules/main.c: L672:L692**
+
+ ```c
+ int
+ Py_RunMain(void)
+ {
+ int exitcode = 0;
+
+ pymain_run_python(&exitcode);
+
+ if (Py_FinalizeEx() < 0) {
+ /* Value unlikely to be confused with a non-error exit status or
+ other special meaning */
+ exitcode = 120;
+ }
+
+ pymain_free();
+
+ if (_Py_UnhandledKeyboardInterrupt) {
+ exitcode = exit_sigint();
+ }
+
+ return exitcode;
}
-
- return exitcode;
-}
-```
-
-
+ ```
## 5.1 Configuration State (구성 상태)
-
파이썬 코드를 실행하기 전에, CPython 런타임은 먼저 **configuration of the runtime**과 **user-provided options**을 설정합니다.
Configuration of the runtime은 세 부분으로 나뉘어 있습니다. ([PEP 587](https://peps.python.org/pep-0587/)).
@@ -246,7 +227,6 @@ Configuration of the runtime은 세 부분으로 나뉘어 있습니다. ([PEP 5
3. The **compiled(=build) configuration** of the CPython Interpreter
### Preinitialization Configuration
-
**preinitialization configuration**은 operating system 혹은 user environment와 관련된 설정이기 때문에, runtime configuration과 구분됩니다.
다음은 `PyPreConfig`의 세 가지 주요 기능입니다.
@@ -255,7 +235,8 @@ Configuration of the runtime은 세 부분으로 나뉘어 있습니다. ([PEP 5
- [2] LC_CTYPE 로캘(locale)을 system 또는 user-preferred 로캘로 설정하기
> ⭐ **로캘(Locale)이란?**
> - 특정 지역, 국가, 또는 문화권에서 사용하는 언어, 통화, 날짜 및 시간 형식과 같은 **지역 설정**을 의미함.
- > - 로캘의 정보는 일반적으로 **언어 코드**와 **국가/지역 코드의 조합**으로 표현된다. (ex> `en-US`는 미국 영어, `ko-KR`은 대한민국 한국어를 나타냄)
+
+ > - 로캘의 정보는 일반적으로 **언어 코드**와 **국가/지역 코드의 조합**으로 표현된다. (ex> **`en-US`**는 미국 영어, **`ko-KR`**은 대한민국 한국어를 나타냄)
- [3] UTF-8 모드 설정하기 ([PEP 540](https://peps.python.org/pep-0540/))
> ⭐ **인코딩**
> - 파이썬은 3.7부터 로캘 설정과 상관 없이 UTF-8을 기본 인코딩으로 사용함
@@ -272,11 +253,11 @@ Configuration of the runtime은 세 부분으로 나뉘어 있습니다. ([PEP 5
- `parse_argv`: 0이 아니면 명령줄 인자(command-line arguments)를 사용합니다.
- `use_environment`: 0보다 큰 값이면 환경 변수를 사용합니다.
- `utf8_mode`: 0이 아니면 UTF-8 모드를 활성화합니다.
--
+-
(참고) PyPreConfig 구조체
-
- **Include/cpython/initconfig.h: L45-L125**
+
+ **Include/cpython/initconfig.h: L45-L125**
```c
/* --- PyPreConfig ----------------------------------------------- */
@@ -375,7 +356,7 @@ Configuration의 두 번째 단계는 **runtime configuration**이다. `PyConfig
- **Runtime flags** for modes like `debug` and `optimized`
- The **mode of execution**, such as a `script file`, `stdin`, or `module`
-
+-
-X option 으로 설정 가능한 확장 옵션(extended options)
@@ -415,7 +396,6 @@ Configuration의 두 번째 단계는 **runtime configuration**이다. `PyConfig
```
-
- Environment variables for runtime settings
### Setting Runtime Configuration with the Command Line
@@ -448,7 +428,6 @@ import zipimport # builtin
CPython 인터프리터는 많은 runtime flags가 있습니다. 플래그는 CPython의 특정 동작을 제어하는데 사용되는 고급 기능이다. 파이썬 세션 중에, `sys.flags` 네임드 튜플을 통해 runtime flags에 접근할 수 있습니다.
- 플래그 사용 X
-
```bash
./python.exe
@@ -460,7 +439,7 @@ CPython 인터프리터는 많은 runtime flags가 있습니다. 플래그는 CP
```
- -X dev 플래그 사용
-
+
```bash
./python.exe -X dev
@@ -470,8 +449,8 @@ CPython 인터프리터는 많은 runtime flags가 있습니다. 플래그는 CP
>>> import sys; sys.flags
sys.flags(debug=0, inspect=0, interactive=0, optimize=0, dont_write_bytecode=0, no_user_site=0, no_site=0, ignore_environment=0, verbose=0, bytes_warning=0, quiet=0, hash_randomization=1, isolated=0, dev_mode=True, utf8_mode=0, int_max_str_digits=-1)
```
-
- - `dev_mode`가 `True`로 변경됨.
+
+ - `dev_mode`가 `True`로 변경됨.
- -X dev -X utf8 플래그 사용
```bash
@@ -484,8 +463,10 @@ CPython 인터프리터는 많은 runtime flags가 있습니다. 플래그는 CP
sys.flags(debug=0, inspect=0, interactive=0, optimize=0, dont_write_bytecode=0, no_user_site=0, no_site=0, ignore_environment=0, verbose=0, bytes_warning=0, quiet=0, hash_randomization=1, isolated=0, dev_mode=True, utf8_mode=1, int_max_str_digits=-1)
```
- - `utf8_mode`가 `1`로 변경됨.
+
+ - `utf8_mode`가 `1`로 변경됨.
- -X dev -q 플래그 사용
+
```bash
./python.exe -X dev -q
@@ -494,8 +475,10 @@ CPython 인터프리터는 많은 runtime flags가 있습니다. 플래그는 CP
sys.flags(debug=0, inspect=0, interactive=0, optimize=0, dont_write_bytecode=0, no_user_site=0, no_site=0, ignore_environment=0, verbose=0, bytes_warning=0, quiet=1, hash_randomization=1, isolated=0, dev_mode=True, utf8_mode=0, int_max_str_digits=-1)
```
- - `-q` (=quiet) 모드라서 파이썬 버전이 출력되지 않음.
- - `quiet`이 `1`로 변경됨.
+
+ - `-q` (=quiet) 모드라서 파이썬 버전이 출력되지 않음.
+ - `quiet`이 `1`로 변경됨.
+
## 5.2 Build Configuration (빌드 구성)
@@ -511,22 +494,24 @@ CPython 인터프리터는 많은 runtime flags가 있습니다. 플래그는 CP
Current installation scheme: "posix_prefix"
Paths:
- data = "/usr/local"
- include = "/Users/donghee/Projects/cpython-internals/cpython/Include"
- platinclude = "/Users/donghee/Projects/cpython-internals/cpython"
- platlib = "/usr/local/lib/python3.9/site-packages"
- platstdlib = "/usr/local/lib/python3.9"
- purelib = "/usr/local/lib/python3.9/site-packages"
- scripts = "/usr/local/bin"
- stdlib = "/usr/local/lib/python3.9"
+ data = "/usr/local"
+ include = "/Users/donghee/Projects/cpython-internals/cpython/Include"
+ platinclude = "/Users/donghee/Projects/cpython-internals/cpython"
+ platlib = "/usr/local/lib/python3.9/site-packages"
+ platstdlib = "/usr/local/lib/python3.9"
+ purelib = "/usr/local/lib/python3.9/site-packages"
+ scripts = "/usr/local/bin"
+ stdlib = "/usr/local/lib/python3.9"
Variables:
- ABIFLAGS = "d"
- AC_APPLE_UNIVERSAL_BUILD = "0"
- AIX_BUILDDATE = "0"
- AIX_GENUINE_CPLUSPLUS = "0"
- ...
+ ABIFLAGS = "d"
+ AC_APPLE_UNIVERSAL_BUILD = "0"
+ AIX_BUILDDATE = "0"
+ AIX_GENUINE_CPLUSPLUS = "0"
+ ...
```
+
+
build configuration은 5가지의 key로 구성되어 있습니다.
@@ -546,18 +531,20 @@ build configuration은 5가지의 key로 구성되어 있습니다.
...
```
- - (참고) `error: 'lzma.h' file not found` 에러로 인해 `./configure` 스크립트를 실행할 때, `xz` 라이브러리를 추가하고 빌드했기 때문에 `xz` 라이브러리에 대한 경로가 포함되어 있습니다.
- - 사용했던 스크립트
-
- ```bash
- # xz 경로 추가
- CPPFLAGS="-I$(brew --prefix zlib)/include -I$(brew --prefix xz)/include" \
- LDFLAGS="-L$(brew --prefix zlib)/lib -L$(brew --prefix xz)/lib" \
- ./configure --with-openssl=$(brew --prefix openssl) --with-pydebug
- # 빌드
- make -j2 -s
- ```
+ - (참고) `error: 'lzma.h' file not found` 에러로 인해 `./configure` 스크립트를 실행할 때, `xz` 라이브러리를 추가하고 빌드했기 때문에 `xz` 라이브러리에 대한 경로가 포함되어 있습니다.
+ - 사용했던 스크립트
+
+ ```bash
+ # xz 경로 추가
+ CPPFLAGS="-I$(brew --prefix zlib)/include -I$(brew --prefix xz)/include" \
+ LDFLAGS="-L$(brew --prefix zlib)/lib -L$(brew --prefix xz)/lib" \
+ ./configure --with-openssl=$(brew --prefix openssl) --with-pydebug
+
+ # 빌드
+ make -j2 -s
+ ```
+
Build configuration 항목들은 컴파일 시에(=CPython Interpreter 생성할 때) 결정되는 값으로, 바이너리에 링크할 추가 모듈 선택에 사용됩니다. 예를 들어 디버거와 계측(instrumentation) 라이브러리, 메모리 할당자는 모두 컴파일 시 결정됩니다.
@@ -570,14 +557,17 @@ Build configuration 항목들은 컴파일 시에(=CPython Interpreter 생성할
코드를 실행하려면 먼저 입력을 모듈로 컴파일해야 합니다.
- 입력방식
- - 로컬 파일과 패키지
- - 메모리 파이프나 stdin 같은 I/O 스트림
- - 문자열
+
+ - 로컬 파일과 패키지
+ - 메모리 파이프나 stdin 같은 I/O 스트림
+ - 문자열
+
이렇게 읽어 들인 입력은 파서를 거쳐 컴파일러로 전달 됩니다. 이렇게 유연한 입력 방식을 제공하기 위해 CPython은 CPython 소스 코드의 상당 분량을 파서의 입력 처리에 사용합니다.
### 연관된 소스 파일 목록
+
- Lib > runpy.py : 파이썬 모듈을 임포트 하고 실행하는 표준 라이브러리 모듈
- Modules > main.c : 파일이나 모듈, 입력 스트림 같은 외부 코드 실행을 감싸는 함수
- Programs > python.c : 윈도우나, 리눅스, macOS에서 Python의 진입점. 위의 `main.c` 를 감싸는 역할만 맡음.
@@ -594,158 +584,148 @@ Build configuration 항목들은 컴파일 시에(=CPython Interpreter 생성할
### 명령줄 문자열 입력
> ⭐ **-c 옵션을 사용해 명령줄에서 파이썬 애플리케이션을 실행하는 경우**
->
-> - python -c “print(2 ** 2)”
->
-
-
- Modules > main.c > pymain_run_command()
- ```c
-static int
-pymain_run_command(wchar_t *command, PyCompilerFlags *cf)
-{
- PyObject *unicode, *bytes;
- int ret;
-
- unicode = PyUnicode_FromWideChar(command, -1);
- if (unicode == NULL) {
- goto error;
- }
-
- if (PySys_Audit("cpython.run_command", "O", unicode) < 0) {
+> - python -c “print(2 ** 2)”
+- Modules > main.c > pymain_run_command()
+
+ ```c
+ static int
+ pymain_run_command(wchar_t *command, PyCompilerFlags *cf)
+ {
+ PyObject *unicode, *bytes;
+ int ret;
+
+ unicode = PyUnicode_FromWideChar(command, -1);
+ if (unicode == NULL) {
+ goto error;
+ }
+
+ if (PySys_Audit("cpython.run_command", "O", unicode) < 0) {
+ return pymain_exit_err_print();
+ }
+
+ bytes = PyUnicode_AsUTF8String(unicode);
+ Py_DECREF(unicode);
+ if (bytes == NULL) {
+ goto error;
+ }
+
+ ret = PyRun_SimpleStringFlags(PyBytes_AsString(bytes), cf);
+ Py_DECREF(bytes);
+ return (ret != 0);
+
+ error:
+ PySys_WriteStderr("Unable to decode the command from the command line:\n");
return pymain_exit_err_print();
}
-
- bytes = PyUnicode_AsUTF8String(unicode);
- Py_DECREF(unicode);
- if (bytes == NULL) {
- goto error;
- }
-
- ret = PyRun_SimpleStringFlags(PyBytes_AsString(bytes), cf);
- Py_DECREF(bytes);
- return (ret != 0);
-
-error:
- PySys_WriteStderr("Unable to decode the command from the command line:\n");
- return pymain_exit_err_print();
-}
-
-```
-
-
+
+ ```
+
1. Modules > main.c에서 pymain_run_command()가 실행되며 -c로 전달된 명령은 C의 wchar_t* 타입 인자로 함수에 전달됩니다.
- wchar_t* 타입은 UTF-8 문자를 저장할 수 있기 때문에 CPython에서 저수준 유니코드 데이터를 저장하는 타입으로 사용됩니다.
2. PyUnicode_FromWideChar()를 이용해 wchar_t*를 파이썬 유니코드 문자열 객체로 변환할 수 있습니다.
3. pymain_run_command()는 파이썬 바이트열 객체를 PyRun_SimpleStringFlags()로 넘겨서 실행합니다.
4. PyRun_SimpleStringFlags()는 문자열을 **파이썬 모듈**로 변환하고 실행합니다.
-5. 파이썬 모듈을 독립된 모듈로 실행하려면 **main** 진입점이 필요하기 때문에 PyRun_SimpleStringFlags()가 진입점을 자동으로 추가합니다.
-6. 또한 PyRun_SimpleStringFlags()는 가짜 파일 이름을 만들고 파이썬 파서를 실행해서, 문자열에서 추상 구문 트리(abstract syntax tree, AST)를 생성해 모듈로 반환한다.
+
+5. 파이썬 모듈을 독립된 모듈로 실행하려면 __main__ 진입점이 필요하기 때문에 PyRun_SimpleStringFlags()가 진입점을 자동으로 추가합니다.
+6. 또한 PyRun_SimpleStringFlags()는 가짜 파일 이름을 만들고 파이썬 파서를 실행해서, 문자열에서 추상 구문 트리(abstract syntax tree, AST)를 생성해 모듈로 반환한다.
### 로컬 모듈 입력
->
> ⭐ **-m 옵션을 사용해 모듈을 실행하는 경우**
->
> - python -m unittest
->
-1. -m 플래그는 모듈 패키지의 진입점(**main**) 을 실행합니다.
+1. -m 플래그는 모듈 패키지의 진입점(__main__) 을 실행합니다.
- 임포트 라이브러리(importlib)의 검색 매커니즘 덕분에 특정 모듈의 파일 시스템 위치를 기억할 필요는 없습니다.
- Lib > runpy.py > _get_module_details()
-
- ```python
- def _get_module_details(mod_name, error=ImportError):
- if mod_name.startswith("."):
- raise error("Relative module names not supported")
- pkg_name, _, _ = mod_name.rpartition(".")
- if pkg_name:
- # Try importing the parent to avoid catching initialization errors
+ - Lib > runpy.py > _get_module_details()
+
+ ```python
+ def _get_module_details(mod_name, error=ImportError):
+ if mod_name.startswith("."):
+ raise error("Relative module names not supported")
+ pkg_name, _, _ = mod_name.rpartition(".")
+ if pkg_name:
+ # Try importing the parent to avoid catching initialization errors
+ try:
+ __import__(pkg_name)
+ except ImportError as e:
+ # If the parent or higher ancestor package is missing, let the
+ # error be raised by find_spec() below and then be caught. But do
+ # not allow other errors to be caught.
+ if e.name is None or (e.name != pkg_name and
+ not pkg_name.startswith(e.name + ".")):
+ raise
+ # Warn if the module has already been imported under its normal name
+ existing = sys.modules.get(mod_name)
+ if existing is not None and not hasattr(existing, "__path__"):
+ from warnings import warn
+ msg = "{mod_name!r} found in sys.modules after import of " \
+ "package {pkg_name!r}, but prior to execution of " \
+ "{mod_name!r}; this may result in unpredictable " \
+ "behaviour".format(mod_name=mod_name, pkg_name=pkg_name)
+ warn(RuntimeWarning(msg))
+
try:
- __import__(pkg_name)
- except ImportError as e:
- # If the parent or higher ancestor package is missing, let the
- # error be raised by find_spec() below and then be caught. But do
- # not allow other errors to be caught.
- if e.name is None or (e.name != pkg_name and
- not pkg_name.startswith(e.name + ".")):
- raise
- # Warn if the module has already been imported under its normal name
- existing = sys.modules.get(mod_name)
- if existing is not None and not hasattr(existing, "__path__"):
- from warnings import warn
- msg = "{mod_name!r} found in sys.modules after import of " \
- "package {pkg_name!r}, but prior to execution of " \
- "{mod_name!r}; this may result in unpredictable " \
- "behaviour".format(mod_name=mod_name, pkg_name=pkg_name)
- warn(RuntimeWarning(msg))
-
- try:
- spec = importlib.util.find_spec(mod_name)
- except (ImportError, AttributeError, TypeError, ValueError) as ex:
- # This hack fixes an impedance mismatch between pkgutil and
- # importlib, where the latter raises other errors for cases where
- # pkgutil previously raised ImportError
- msg = "Error while finding module specification for {!r} ({}: {})"
- if mod_name.endswith(".py"):
- msg += (f". Try using '{mod_name[:-3]}' instead of "
- f"'{mod_name}' as the module name.")
- raise error(msg.format(mod_name, type(ex).__name__, ex)) from ex
- if spec is None:
- raise error("No module named %s" % mod_name)
- if spec.submodule_search_locations is not None:
- if mod_name == "__main__" or mod_name.endswith(".__main__"):
- raise error("Cannot use package as __main__ module")
+ spec = importlib.util.find_spec(mod_name)
+ except (ImportError, AttributeError, TypeError, ValueError) as ex:
+ # This hack fixes an impedance mismatch between pkgutil and
+ # importlib, where the latter raises other errors for cases where
+ # pkgutil previously raised ImportError
+ msg = "Error while finding module specification for {!r} ({}: {})"
+ if mod_name.endswith(".py"):
+ msg += (f". Try using '{mod_name[:-3]}' instead of "
+ f"'{mod_name}' as the module name.")
+ raise error(msg.format(mod_name, type(ex).__name__, ex)) from ex
+ if spec is None:
+ raise error("No module named %s" % mod_name)
+ if spec.submodule_search_locations is not None:
+ if mod_name == "__main__" or mod_name.endswith(".__main__"):
+ raise error("Cannot use package as __main__ module")
+ try:
+ pkg_main_name = mod_name + ".__main__"
+ return _get_module_details(pkg_main_name, error)
+ except error as e:
+ if mod_name not in sys.modules:
+ raise # No module loaded; being a package is irrelevant
+ raise error(("%s; %r is a package and cannot " +
+ "be directly executed") %(e, mod_name))
+ loader = spec.loader
+ if loader is None:
+ raise error("%r is a namespace package and cannot be executed"
+ % mod_name)
try:
- pkg_main_name = mod_name + ".__main__"
- return _get_module_details(pkg_main_name, error)
- except error as e:
- if mod_name not in sys.modules:
- raise # No module loaded; being a package is irrelevant
- raise error(("%s; %r is a package and cannot " +
- "be directly executed") %(e, mod_name))
- loader = spec.loader
- if loader is None:
- raise error("%r is a namespace package and cannot be executed"
- % mod_name)
- try:
- code = loader.get_code(mod_name)
- except ImportError as e:
- raise error(format(e)) from e
- if code is None:
- raise error("No code object available for %s" % mod_name)
- return mod_name, spec, code
-
- ```
-
-
-
-
- importlib.util.find_spec(mod_name)
-
- ```python
- import importlib.util
- importlib.util.find_spec("unittest")
-
- # Output
- # ModuleSpec(
- # name='unittest',
- # loader=<_frozen_importlib_external.SourceFileLoader object at 0x7fe01263bd30>,
- # origin='/home/patrick/miniconda3/envs/py3.10/lib/python3.10/unittest/__init__.py',
- # submodule_search_locations=['/home/patrick/miniconda3/envs/py3.10/lib/python3.10/unittest'],
- # )
-
- importlib.util.find_spec("itertools")
-
- # Output
- # ModuleSpec(
- # name='itertools',
- # loader=,
- # origin='built-in'
- # )
- ```
-
-
+ code = loader.get_code(mod_name)
+ except ImportError as e:
+ raise error(format(e)) from e
+ if code is None:
+ raise error("No code object available for %s" % mod_name)
+ return mod_name, spec, code
+
+ ```
+
+ - importlib.util.find_spec(mod_name)
+
+ ```python
+ import importlib.util
+ importlib.util.find_spec("unittest")
+
+ # Output
+ # ModuleSpec(
+ # name='unittest',
+ # loader=<_frozen_importlib_external.SourceFileLoader object at 0x7fe01263bd30>,
+ # origin='/home/patrick/miniconda3/envs/py3.10/lib/python3.10/unittest/__init__.py',
+ # submodule_search_locations=['/home/patrick/miniconda3/envs/py3.10/lib/python3.10/unittest'],
+ # )
+
+ importlib.util.find_spec("itertools")
+
+ # Output
+ # ModuleSpec(
+ # name='itertools',
+ # loader=,
+ # origin='built-in'
+ # )
+ ```
+
2. runpy를 임포트하고 PyObject_Call()로 해당 모듈을 실행합니다. runpy 모듈은 Lib > runpy.py에 위치한 순수한 파이썬 모듈입니다.
- `python -m `을 실행하는 것은 `python -m runpy `을 실행하는 것과 같습니다.
@@ -763,201 +743,194 @@ error:
```
3. runpy는 세 단계로 모듈을 실행합니다.
- 1. 제공된 모듈 이름을 **import**()로 임포트 합니다.
- - **import**() 함수는 파이썬의 내장 함수 중 하나입니다. 함수는 모듈을 동적으로 로드하고, 모듈 객체를 반환합니다.
+ 1. 제공된 모듈 이름을 __import__()로 임포트 합니다.
+ - __import__() 함수는 파이썬의 내장 함수 중 하나입니다. 함수는 모듈을 동적으로 로드하고, 모듈 객체를 반환합니다.
```python
mod_name = 'math'
mod = __import__(mod_name)
print(mod.sqrt(16))
```
-
- 2. **name** (모듈 이름)을 **main** 이름 공간에 설정합니다.
- 3. **main** 이름 공간에서 모듈을 실행합니다.
+
+ 2. __name__ (모듈 이름)을 __main__ 이름 공간에 설정합니다.
+ 3. __main__ 이름 공간에서 모듈을 실행합니다.
### 표준 입력 또는 스크립트 파일 입력
->
> ⭐ **python test.py**
- 관련 코드
-
- PyRun_SimpleFileExFlags()
-
- ```c
- int
- PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
- PyCompilerFlags *flags)
- {
- PyObject *filename_obj = PyUnicode_DecodeFSDefault(filename);
- if (filename_obj == NULL)
+ - PyRun_SimpleFileExFlags()
+
+ ```c
+ int
+ PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
+ PyCompilerFlags *flags)
{
- return -1;
+ PyObject *filename_obj = PyUnicode_DecodeFSDefault(filename);
+ if (filename_obj == NULL)
+ {
+ return -1;
+ }
+ int res = pyrun_simple_file(fp, filename_obj, closeit, flags);
+ Py_DECREF(filename_obj);
+ return res;
}
- int res = pyrun_simple_file(fp, filename_obj, closeit, flags);
- Py_DECREF(filename_obj);
- return res;
- }
- ```
-
-
-
-
- pyrun_simple_file()
-
- ```c
- static int
- pyrun_simple_file(FILE *fp, PyObject *filename, int closeit,
- PyCompilerFlags *flags)
- {
- PyObject *m, *d, *v;
- int set_file_name = 0, ret = -1;
-
- m = PyImport_AddModule("__main__");
- if (m == NULL)
- return -1;
- Py_INCREF(m);
- d = PyModule_GetDict(m);
- if (PyDict_GetItemString(d, "__file__") == NULL)
+ ```
+
+ - pyrun_simple_file()
+
+ ```c
+ static int
+ pyrun_simple_file(FILE *fp, PyObject *filename, int closeit,
+ PyCompilerFlags *flags)
{
- if (PyDict_SetItemString(d, "__file__", filename) < 0)
+ PyObject *m, *d, *v;
+ int set_file_name = 0, ret = -1;
+
+ m = PyImport_AddModule("__main__");
+ if (m == NULL)
+ return -1;
+ Py_INCREF(m);
+ d = PyModule_GetDict(m);
+ if (PyDict_GetItemString(d, "__file__") == NULL)
{
- goto done;
+ if (PyDict_SetItemString(d, "__file__", filename) < 0)
+ {
+ goto done;
+ }
+ if (PyDict_SetItemString(d, "__cached__", Py_None) < 0)
+ {
+ goto done;
+ }
+ set_file_name = 1;
}
- if (PyDict_SetItemString(d, "__cached__", Py_None) < 0)
+
+ int pyc = maybe_pyc_file(fp, filename, closeit);
+ if (pyc < 0)
{
goto done;
}
- set_file_name = 1;
- }
-
- int pyc = maybe_pyc_file(fp, filename, closeit);
- if (pyc < 0)
- {
- goto done;
- }
-
- if (pyc)
- {
- FILE *pyc_fp;
- /* Try to run a pyc file. First, re-open in binary */
- if (closeit)
+
+ if (pyc)
{
- fclose(fp);
+ FILE *pyc_fp;
+ /* Try to run a pyc file. First, re-open in binary */
+ if (closeit)
+ {
+ fclose(fp);
+ }
+
+ pyc_fp = _Py_fopen_obj(filename, "rb");
+ if (pyc_fp == NULL)
+ {
+ fprintf(stderr, "python: Can't reopen .pyc file\n");
+ goto done;
+ }
+
+ if (set_main_loader(d, filename, "SourcelessFileLoader") < 0)
+ {
+ fprintf(stderr, "python: failed to set __main__.__loader__\n");
+ ret = -1;
+ fclose(pyc_fp);
+ goto done;
+ }
+ v = run_pyc_file(pyc_fp, d, d, flags);
}
-
- pyc_fp = _Py_fopen_obj(filename, "rb");
- if (pyc_fp == NULL)
+ else
{
- fprintf(stderr, "python: Can't reopen .pyc file\n");
- goto done;
+ /* When running from stdin, leave __main__.__loader__ alone */
+ if (PyUnicode_CompareWithASCIIString(filename, "") != 0 &&
+ set_main_loader(d, filename, "SourceFileLoader") < 0)
+ {
+ fprintf(stderr, "python: failed to set __main__.__loader__\n");
+ ret = -1;
+ goto done;
+ }
+ v = pyrun_file(fp, filename, Py_file_input, d, d,
+ closeit, flags);
}
-
- if (set_main_loader(d, filename, "SourcelessFileLoader") < 0)
+ flush_io();
+ if (v == NULL)
{
- fprintf(stderr, "python: failed to set __main__.__loader__\n");
- ret = -1;
- fclose(pyc_fp);
+ Py_CLEAR(m);
+ PyErr_Print();
goto done;
}
- v = run_pyc_file(pyc_fp, d, d, flags);
- }
- else
- {
- /* When running from stdin, leave __main__.__loader__ alone */
- if (PyUnicode_CompareWithASCIIString(filename, "") != 0 &&
- set_main_loader(d, filename, "SourceFileLoader") < 0)
+ Py_DECREF(v);
+ ret = 0;
+ done:
+ if (set_file_name)
{
- fprintf(stderr, "python: failed to set __main__.__loader__\n");
- ret = -1;
- goto done;
+ if (PyDict_DelItemString(d, "__file__"))
+ {
+ PyErr_Clear();
+ }
+ if (PyDict_DelItemString(d, "__cached__"))
+ {
+ PyErr_Clear();
+ }
}
- v = pyrun_file(fp, filename, Py_file_input, d, d,
- closeit, flags);
- }
- flush_io();
- if (v == NULL)
- {
- Py_CLEAR(m);
- PyErr_Print();
- goto done;
+ Py_XDECREF(m);
+ return ret;
}
- Py_DECREF(v);
- ret = 0;
- done:
- if (set_file_name)
+
+ ```
+
+ - pyrun_file()
+
+ ```c
+ static PyObject *
+ pyrun_file(FILE *fp, PyObject *filename, int start, PyObject *globals,
+ PyObject *locals, int closeit, PyCompilerFlags *flags)
{
- if (PyDict_DelItemString(d, "__file__"))
+ PyArena *arena = PyArena_New();
+ if (arena == NULL)
{
- PyErr_Clear();
+ return NULL;
}
- if (PyDict_DelItemString(d, "__cached__"))
+
+ mod_ty mod;
+ int use_peg = _PyInterpreterState_GET()->config._use_peg_parser;
+ if (use_peg)
{
- PyErr_Clear();
- }
- }
- Py_XDECREF(m);
- return ret;
- }
-
- ```
-
-
-
-
- pyrun_file()
-
- ```c
- static PyObject *
- pyrun_file(FILE *fp, PyObject *filename, int start, PyObject *globals,
- PyObject *locals, int closeit, PyCompilerFlags *flags)
- {
- PyArena *arena = PyArena_New();
- if (arena == NULL)
- {
- return NULL;
- }
-
- mod_ty mod;
- int use_peg = _PyInterpreterState_GET()->config._use_peg_parser;
- if (use_peg)
- {
- mod = PyPegen_ASTFromFileObject(fp, filename, start, NULL, NULL, NULL,
- flags, NULL, arena);
- }
- else
- {
- mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,
+ mod = PyPegen_ASTFromFileObject(fp, filename, start, NULL, NULL, NULL,
flags, NULL, arena);
+ }
+ else
+ {
+ mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,
+ flags, NULL, arena);
+ }
+
+ if (closeit)
+ {
+ fclose(fp);
+ }
+
+ PyObject *ret;
+ if (mod != NULL)
+ {
+ ret = run_mod(mod, filename, globals, locals, flags, arena);
+ }
+ else
+ {
+ ret = NULL;
+ }
+ PyArena_Free(arena);
+
+ return ret;
}
-
- if (closeit)
- {
- fclose(fp);
- }
-
- PyObject *ret;
- if (mod != NULL)
- {
- ret = run_mod(mod, filename, globals, locals, flags, arena);
- }
- else
- {
- ret = NULL;
- }
- PyArena_Free(arena);
-
- return ret;
- }
-
- ```
-
-
+
+ ```
+
1. Python > pythonrun.c의 PyRun_SimpleFileExFlags()를 호출합니다.
2. PyRun_SimpleFileExFlags()
- .pyc 파일 경로면 run_pyc_file()을 호출합니다.
- 스크립트 파일(.py) 경로면 PyRun_FileExFlags()를 호출합니다.
- - python처럼 파일 경로가 stdin이면 stdin을 파일 핸들로 취급하고 PyRun_FileExFlags()를 호출합니다.
+
+ - | python처럼 파일 경로가 stdin이면 stdin을 파일 핸들로 취급하고 PyRun_FileExFlags()를 호출합니다.
+
3. PyRun_FileExFlags()는 파일에서 파이썬 모듈을 생성하고 run_mod()로 보내 실행합니다.
### 컴파일된 바이트 코드 입력
@@ -971,7 +944,8 @@ error:
## References
- CPython 파헤치기 내용 정리 블로그
- - [https://velog.io/@1eejuhwany/나도-CPython-파헤치기3](https://velog.io/@1eejuhwany/%EB%82%98%EB%8F%84-CPython-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B03)
+
+ - [https://velog.io/@1eejuhwany/나도-CPython-파헤치기3](https://velog.io/@1eejuhwany/%EB%82%98%EB%8F%84-CPython-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B03)
- 파이썬 모듈
- - [https://docs.python.org/3.9/tutorial/modules.html](https://docs.python.org/3.9/tutorial/modules.html)
- - [https://realpython.com/python-modules-packages](https://realpython.com/python-modules-packages/)
+ - [https://docs.python.org/3.9/tutorial/modules.html](https://docs.python.org/3.9/tutorial/modules.html)
+ - [https://realpython.com/python-modules-packages](https://realpython.com/python-modules-packages/)
diff --git a/book/images/10_2_thread/untitled.png b/book/images/10_2_thread/untitled.png
new file mode 100644
index 0000000..f443282
Binary files /dev/null and b/book/images/10_2_thread/untitled.png differ
diff --git a/book/images/10_2_thread/untitled_1.png b/book/images/10_2_thread/untitled_1.png
new file mode 100644
index 0000000..42a2f9d
Binary files /dev/null and b/book/images/10_2_thread/untitled_1.png differ
diff --git a/book/images/10_2_thread/untitled_2.png b/book/images/10_2_thread/untitled_2.png
new file mode 100644
index 0000000..35a60d3
Binary files /dev/null and b/book/images/10_2_thread/untitled_2.png differ
diff --git a/book/images/10_2_thread/untitled_3.png b/book/images/10_2_thread/untitled_3.png
new file mode 100644
index 0000000..e38ac7a
Binary files /dev/null and b/book/images/10_2_thread/untitled_3.png differ
diff --git a/book/images/10_2_thread/untitled_4.png b/book/images/10_2_thread/untitled_4.png
new file mode 100644
index 0000000..1003c72
Binary files /dev/null and b/book/images/10_2_thread/untitled_4.png differ
diff --git a/book/images/10_2_thread/untitled_5.png b/book/images/10_2_thread/untitled_5.png
new file mode 100644
index 0000000..de3d3af
Binary files /dev/null and b/book/images/10_2_thread/untitled_5.png differ
diff --git a/book/images/10_2_thread/untitled_6.png b/book/images/10_2_thread/untitled_6.png
new file mode 100644
index 0000000..0367001
Binary files /dev/null and b/book/images/10_2_thread/untitled_6.png differ
diff --git a/book/images/10_2_thread/untitled_7.png b/book/images/10_2_thread/untitled_7.png
new file mode 100644
index 0000000..e58d162
Binary files /dev/null and b/book/images/10_2_thread/untitled_7.png differ
diff --git a/book/images/10_2_thread/untitled_8.png b/book/images/10_2_thread/untitled_8.png
new file mode 100644
index 0000000..999a38b
Binary files /dev/null and b/book/images/10_2_thread/untitled_8.png differ
diff --git a/book/images/10_2_thread/untitled_9.png b/book/images/10_2_thread/untitled_9.png
new file mode 100644
index 0000000..ba54f1a
Binary files /dev/null and b/book/images/10_2_thread/untitled_9.png differ