diff --git a/.gitignore b/.gitignore
index 1b593d6..6af2088 100644
--- a/.gitignore
+++ b/.gitignore
@@ -132,4 +132,6 @@ fabric.properties
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
.idea/**/azureSettings.xml
-# End of https://www.toptal.com/developers/gitignore/api/pycharm,jupyternotebooks
\ No newline at end of file
+# End of https://www.toptal.com/developers/gitignore/api/pycharm,jupyternotebooks
+
+!8th_members/*
diff --git a/8th_members/.gitkeep b/8th_members/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/10\354\243\274\354\260\250.md" "b/8th_members/\352\263\240\353\217\231\355\235\254/10\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..6bff43e
--- /dev/null
+++ "b/8th_members/\352\263\240\353\217\231\355\235\254/10\354\243\274\354\260\250.md"
@@ -0,0 +1,10 @@
+# 10주차
+
+## 싱글 프로세스 + 싱글 스레드
+
+
+## 싱글 프로세스 + 멀티 스레딩
+
+
+## 싱글 프로세스 + 멀티 스레딩 (개수 제한)
+
\ No newline at end of file
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/11\354\243\274\354\260\250.md" "b/8th_members/\352\263\240\353\217\231\355\235\254/11\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..ae232c3
--- /dev/null
+++ "b/8th_members/\352\263\240\353\217\231\355\235\254/11\354\243\274\354\260\250.md"
@@ -0,0 +1,79 @@
+# 11주차
+
+## Set 내부 구조 살펴보기
+```
+PyTypeObject PySet_Type = {
+ PyVarObject_HEAD_INIT(&PyType_Type, 0)
+ "set", /* tp_name */
+ sizeof(PySetObject), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ /* methods */
+ (destructor)set_dealloc, /* tp_dealloc */
+ 0, /* tp_vectorcall_offset */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_as_async */
+ (reprfunc)set_repr, /* tp_repr */
+ &set_as_number, /* tp_as_number */
+ &set_as_sequence, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ PyObject_HashNotImplemented, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ PyObject_GenericGetAttr, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
+ Py_TPFLAGS_BASETYPE, /* tp_flags */
+ set_doc, /* tp_doc */
+ (traverseproc)set_traverse, /* tp_traverse */
+ (inquiry)set_clear_internal, /* tp_clear */
+ (richcmpfunc)set_richcompare, /* tp_richcompare */
+ offsetof(PySetObject, weakreflist), /* tp_weaklistoffset */
+ (getiterfunc)set_iter, /* tp_iter */
+ 0, /* tp_iternext */
+ set_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)set_init, /* tp_init */
+ PyType_GenericAlloc, /* tp_alloc */
+ set_new, /* tp_new */
+ PyObject_GC_Del, /* tp_free */
+ .tp_vectorcall = set_vectorcall,
+};
+```
+
+```
+static void
+set_dealloc(PySetObject *so)
+{
+ setentry *entry;
+ Py_ssize_t used = so->used;
+
+ /* bpo-31095: UnTrack is needed before calling any callbacks */
+ PyObject_GC_UnTrack(so);
+ Py_TRASHCAN_BEGIN(so, set_dealloc)
+ if (so->weakreflist != NULL)
+ PyObject_ClearWeakRefs((PyObject *) so);
+
+ for (entry = so->table; used > 0; entry++) {
+ if (entry->key && entry->key != dummy) {
+ used--;
+ Py_DECREF(entry->key);
+ }
+ }
+ if (so->table != so->smalltable)
+ PyMem_DEL(so->table);
+ Py_TYPE(so)->tp_free(so);
+ Py_TRASHCAN_END
+}
+```
+1. set 객체는 삭제될 때(destruction), set_dealloc 함수를 호출한다.
+2. set_dealloc 함수는 가비지 컬렉터의 추적 대상에서 제외하고, 약한 참조 리스트를 클리어 하는 등의 작업을 한다.
+3. 약한 참조 리스트를 클리어한다고 해서, 약한 참조 리스트에 포함되어 있는 다른 객체들의 메모리가 해제되는 것은 아니다.
+
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/12\354\243\274\354\260\250.md" "b/8th_members/\352\263\240\353\217\231\355\235\254/12\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..3aacba5
--- /dev/null
+++ "b/8th_members/\352\263\240\353\217\231\355\235\254/12\354\243\274\354\260\250.md"
@@ -0,0 +1,4 @@
+# 12주차
+
+## unittest 사용하기
+
\ No newline at end of file
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/13\354\243\274\354\260\250.md" "b/8th_members/\352\263\240\353\217\231\355\235\254/13\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..5be9469
--- /dev/null
+++ "b/8th_members/\352\263\240\353\217\231\355\235\254/13\354\243\274\354\260\250.md"
@@ -0,0 +1,7 @@
+# 13주차
+
+## pdb 실행해보기
+
+
+## CProfile 실행해보기
+
\ No newline at end of file
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/1\354\243\274\354\260\250.md" "b/8th_members/\352\263\240\353\217\231\355\235\254/1\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..ea89599
--- /dev/null
+++ "b/8th_members/\352\263\240\353\217\231\355\235\254/1\354\243\274\354\260\250.md"
@@ -0,0 +1,9 @@
+# 1주차
+
+## CPython 소스코드 다운로드 및 환경 구성
+
+
+## 스터디에서 이루고 싶은 목표, 함께 하게 된 소감
+1. CPython internal 뿌셔뿌셔~!
+2. 운영체제, 자료구조는 덤~!
+3. 열심히 하겠습니다!!
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/2\354\243\274\354\260\250.md" "b/8th_members/\352\263\240\353\217\231\355\235\254/2\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..a6bf541
--- /dev/null
+++ "b/8th_members/\352\263\240\353\217\231\355\235\254/2\354\243\274\354\260\250.md"
@@ -0,0 +1,5 @@
+# 2주차
+
+## 문법 다시 생성하기
+`break` 대신 `bye`도 사용할 수 있도록 문법을 변경함.
+
\ No newline at end of file
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/3\354\243\274\354\260\250.md" "b/8th_members/\352\263\240\353\217\231\355\235\254/3\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..684ea77
--- /dev/null
+++ "b/8th_members/\352\263\240\353\217\231\355\235\254/3\354\243\274\354\260\250.md"
@@ -0,0 +1,19 @@
+# 3주차
+
+## [1] CPython 인터프리터 실행 과정 코드 레벨로 이해하기
+
+
+
+
+
+## [2] 파이썬 세션에서 runtime flag 확인하기
+
+
+
+
+
+## [3] `importlib.util.find_spec` 함수를 사용해서 원하는 모듈의 위치를 찾아보기
+
+
+
+
\ No newline at end of file
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/4\354\243\274\354\260\250.md" "b/8th_members/\352\263\240\353\217\231\355\235\254/4\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..6e46299
--- /dev/null
+++ "b/8th_members/\352\263\240\353\217\231\355\235\254/4\354\243\274\354\260\250.md"
@@ -0,0 +1,7 @@
+# 4주차
+
+## instaviz로 AST 시각화 해보기
+
+
+## ‘거의 같음’ 연산자를 문법에 추가하고 AlE타입의 객체를 출력 해보기
+
\ No newline at end of file
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/6\354\243\274\354\260\250.md" "b/8th_members/\352\263\240\353\217\231\355\235\254/6\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..43c61ac
--- /dev/null
+++ "b/8th_members/\352\263\240\353\217\231\355\235\254/6\354\243\274\354\260\250.md"
@@ -0,0 +1,4 @@
+# 6주차
+
+## 프레임 객체 생성하기
+
\ No newline at end of file
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/7\354\243\274\354\260\250.md" "b/8th_members/\352\263\240\353\217\231\355\235\254/7\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..5e0f693
--- /dev/null
+++ "b/8th_members/\352\263\240\353\217\231\355\235\254/7\354\243\274\354\260\250.md"
@@ -0,0 +1,4 @@
+# 7주차
+
+## tracemalloc 모듈 사용해서 직접 메모리 확인해보기
+
\ No newline at end of file
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/8\354\243\274\354\260\250.md" "b/8th_members/\352\263\240\353\217\231\355\235\254/8\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..e1820a3
--- /dev/null
+++ "b/8th_members/\352\263\240\353\217\231\355\235\254/8\354\243\274\354\260\250.md"
@@ -0,0 +1,4 @@
+# 8주차
+
+## sys.getrefcount 사용해보기
+
\ No newline at end of file
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/9\354\243\274\354\260\250.md" "b/8th_members/\352\263\240\353\217\231\355\235\254/9\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..ce074d8
--- /dev/null
+++ "b/8th_members/\352\263\240\353\217\231\355\235\254/9\354\243\274\354\260\250.md"
@@ -0,0 +1,11 @@
+# 9주차
+
+## 싱글 프로세스
+
+
+## 멀티 프로세스
+
+
+## 결론
+- 싱글: 1초
+- 멀티: 0.48초
\ No newline at end of file
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/main.py" "b/8th_members/\352\263\240\353\217\231\355\235\254/main.py"
new file mode 100644
index 0000000..ec319ac
--- /dev/null
+++ "b/8th_members/\352\263\240\353\217\231\355\235\254/main.py"
@@ -0,0 +1,8 @@
+import instaviz
+
+def hello():
+ name = 'donghee'
+ text = 'hello ' + name
+ return name
+
+instaviz.show(hello)
\ No newline at end of file
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/week_1.png" "b/8th_members/\352\263\240\353\217\231\355\235\254/week_1.png"
new file mode 100644
index 0000000..7621bb1
Binary files /dev/null and "b/8th_members/\352\263\240\353\217\231\355\235\254/week_1.png" differ
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/week_10_multi_threading.png" "b/8th_members/\352\263\240\353\217\231\355\235\254/week_10_multi_threading.png"
new file mode 100644
index 0000000..af3eac4
Binary files /dev/null and "b/8th_members/\352\263\240\353\217\231\355\235\254/week_10_multi_threading.png" differ
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/week_10_multi_threading2.png" "b/8th_members/\352\263\240\353\217\231\355\235\254/week_10_multi_threading2.png"
new file mode 100644
index 0000000..2aa7894
Binary files /dev/null and "b/8th_members/\352\263\240\353\217\231\355\235\254/week_10_multi_threading2.png" differ
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/week_10_single.png" "b/8th_members/\352\263\240\353\217\231\355\235\254/week_10_single.png"
new file mode 100644
index 0000000..10451c7
Binary files /dev/null and "b/8th_members/\352\263\240\353\217\231\355\235\254/week_10_single.png" differ
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/week_12.png" "b/8th_members/\352\263\240\353\217\231\355\235\254/week_12.png"
new file mode 100644
index 0000000..ff41905
Binary files /dev/null and "b/8th_members/\352\263\240\353\217\231\355\235\254/week_12.png" differ
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/week_13_debug.png" "b/8th_members/\352\263\240\353\217\231\355\235\254/week_13_debug.png"
new file mode 100644
index 0000000..5d87576
Binary files /dev/null and "b/8th_members/\352\263\240\353\217\231\355\235\254/week_13_debug.png" differ
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/week_13_profile.png" "b/8th_members/\352\263\240\353\217\231\355\235\254/week_13_profile.png"
new file mode 100644
index 0000000..9fcad16
Binary files /dev/null and "b/8th_members/\352\263\240\353\217\231\355\235\254/week_13_profile.png" differ
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/week_2.png" "b/8th_members/\352\263\240\353\217\231\355\235\254/week_2.png"
new file mode 100644
index 0000000..237381e
Binary files /dev/null and "b/8th_members/\352\263\240\353\217\231\355\235\254/week_2.png" differ
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/week_3_1.png" "b/8th_members/\352\263\240\353\217\231\355\235\254/week_3_1.png"
new file mode 100644
index 0000000..120b6b4
Binary files /dev/null and "b/8th_members/\352\263\240\353\217\231\355\235\254/week_3_1.png" differ
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/week_3_2.png" "b/8th_members/\352\263\240\353\217\231\355\235\254/week_3_2.png"
new file mode 100644
index 0000000..bb41135
Binary files /dev/null and "b/8th_members/\352\263\240\353\217\231\355\235\254/week_3_2.png" differ
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/week_3_3.png" "b/8th_members/\352\263\240\353\217\231\355\235\254/week_3_3.png"
new file mode 100644
index 0000000..18d20c3
Binary files /dev/null and "b/8th_members/\352\263\240\353\217\231\355\235\254/week_3_3.png" differ
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/week_4_1.png" "b/8th_members/\352\263\240\353\217\231\355\235\254/week_4_1.png"
new file mode 100644
index 0000000..febb846
Binary files /dev/null and "b/8th_members/\352\263\240\353\217\231\355\235\254/week_4_1.png" differ
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/week_4_2.png" "b/8th_members/\352\263\240\353\217\231\355\235\254/week_4_2.png"
new file mode 100644
index 0000000..f96920c
Binary files /dev/null and "b/8th_members/\352\263\240\353\217\231\355\235\254/week_4_2.png" differ
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/week_6.png" "b/8th_members/\352\263\240\353\217\231\355\235\254/week_6.png"
new file mode 100644
index 0000000..d494c76
Binary files /dev/null and "b/8th_members/\352\263\240\353\217\231\355\235\254/week_6.png" differ
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/week_7.png" "b/8th_members/\352\263\240\353\217\231\355\235\254/week_7.png"
new file mode 100644
index 0000000..68be964
Binary files /dev/null and "b/8th_members/\352\263\240\353\217\231\355\235\254/week_7.png" differ
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/week_8.png" "b/8th_members/\352\263\240\353\217\231\355\235\254/week_8.png"
new file mode 100644
index 0000000..025f80e
Binary files /dev/null and "b/8th_members/\352\263\240\353\217\231\355\235\254/week_8.png" differ
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/week_9_multi.png" "b/8th_members/\352\263\240\353\217\231\355\235\254/week_9_multi.png"
new file mode 100644
index 0000000..63d5b4b
Binary files /dev/null and "b/8th_members/\352\263\240\353\217\231\355\235\254/week_9_multi.png" differ
diff --git "a/8th_members/\352\263\240\353\217\231\355\235\254/week_9_single.png" "b/8th_members/\352\263\240\353\217\231\355\235\254/week_9_single.png"
new file mode 100644
index 0000000..1ca06e4
Binary files /dev/null and "b/8th_members/\352\263\240\353\217\231\355\235\254/week_9_single.png" differ
diff --git "a/8th_members/\352\271\200\354\204\234\355\230\204/1\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\204\234\355\230\204/1\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..b5930e7
--- /dev/null
+++ "b/8th_members/\352\271\200\354\204\234\355\230\204/1\354\243\274\354\260\250.md"
@@ -0,0 +1,8 @@
+### CPython 소스코드 다운로드 및 환경 구성 (화면 캡쳐)
+
+
+
+### 스터디에서 이루고 싶은 목표, 함께 하게 된 소감 등
+
+ - 잠시 놓았던 개발과 다시 친해지고 싶어요...
+ - 화이팅!
diff --git "a/8th_members/\352\271\200\354\204\234\355\230\204/mission1.png" "b/8th_members/\352\271\200\354\204\234\355\230\204/mission1.png"
new file mode 100644
index 0000000..7842f10
Binary files /dev/null and "b/8th_members/\352\271\200\354\204\234\355\230\204/mission1.png" differ
diff --git "a/8th_members/\352\271\200\354\233\220\352\262\275/10\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\233\220\352\262\275/10\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..26e6439
--- /dev/null
+++ "b/8th_members/\352\271\200\354\233\220\352\262\275/10\354\243\274\354\260\250.md"
@@ -0,0 +1,12 @@
+# Port Scanner: Multi Threading 코드 실행해보기
+## 싱글 프로세스 + 싱글 스레드
+
+
+
+## 멀티 스레드
+
+
+
+## 멀티스레딩 + 스레드 개수 제한
+
+근소하게 개선됐다.
\ No newline at end of file
diff --git "a/8th_members/\352\271\200\354\233\220\352\262\275/11\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\233\220\352\262\275/11\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..61eca46
--- /dev/null
+++ "b/8th_members/\352\271\200\354\233\220\352\262\275/11\354\243\274\354\260\250.md"
@@ -0,0 +1,30 @@
+# 원하는 Type 분석해보기: List 타입
+### 1. data structure of python list
+
+
+ ### Python list data structure
+
+ - PyListObject: python list의 C 구현체.
+ - PyObject_VAR_HEAD: 인스턴스마다 길이가 변하는 객체를 선언할 때 사용되는 macro.
+ - **ob_item: 이중 포인터 (리스트 원소들의 포인터 배열에 대한 포인터). 객체 주소들을 저장한다.
+ - allocated: 리스트에 할당된 크기
+ - **Py__Object: 모든 클래스들의 조상인 PyObject를 상속받은 뒤 __에 type명(ex: int, var, list, array etc.)을 붙여 각 타입에 대한 파이썬 클래스를 구현.
+
+이에 의해 파생되는 특징들?
+ #### 1) 동적 타입 지원
+
+한 연산 수행 중간중간에 type checking이 많아 정적 배열보다 동작 느림
+
+#### 2) 크기가 가변적(동적)
+처음에 크기를 작게 잡아뒀다 꽉 차면 list_resize 메서드를 작동시켜 크기를 늘려준다. (용어: 더블링)
+→ 해당 작업 발생 시 정적 배열보다 동작 속도 느려짐
+
+#### 3) 포인터를 이중으로 거침
+ob_items**의 특징에 의해 value를 바로 얻을 수 없어 속도 느림
+
+#### 4) 캐시의 메모리 지역성 문제
+
+
+
+list의 0, 1번 객체 즉 PyObject를 가리키는 포인터 주소는 메모리 상에서 인접하지만 value는 인접하지 않음
+→ nparray와 비교하면, 캐싱 시 메모리 지역성 문제로 cache hit rate가 더 낮음
diff --git "a/8th_members/\352\271\200\354\233\220\352\262\275/12\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\233\220\352\262\275/12\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..21d7c61
--- /dev/null
+++ "b/8th_members/\352\271\200\354\233\220\352\262\275/12\354\243\274\354\260\250.md"
@@ -0,0 +1,206 @@
+# unittest를 이용해 나만의 Test Case 만들기
+디렉토리 구조는 아래와 같이 구성했습니다.
+
+
+
+각 코드는 아래와 같습니다.
+
+### game.py
+```python
+from abc import ABC, abstractmethod
+
+
+class Unit(ABC):
+ def __init__(self, _type, _name, _price):
+ self.type = _type
+ self.name = _name
+ self.price = _price
+
+ @abstractmethod
+ def action(self):
+ pass
+
+ @abstractmethod
+ def print(self):
+ pass
+
+ def get_type(self):
+ return self.type
+
+ def get_name(self):
+ return self.name
+
+ def get_price(self):
+ return self.price
+
+
+class Attacker(Unit):
+ def __init__(self, type, name, price, _type, _name, _price, _attack_point):
+ super().__init__(type, name, price)
+ self.at_ty = _type
+ self.at_name = _name
+ self.at_price = _price
+ self.attack_point = _attack_point
+
+ def action(self):
+ return self.attack_point
+
+ def print(self):
+ print(f"[Attacker] Name: {self.at_name} (Attack point: {self.attack_point}, Price: {self.at_price})")
+
+
+class Miner(Unit):
+ def __init__(self, type, name, price, _type, _name, _price, _mining_point):
+ super().__init__(type, name, price)
+ self.mi_ty = _type
+ self.mi_name = _name
+ self.mi_price = _price
+ self.mining_point = _mining_point
+
+ def action(self):
+ return self.mining_point
+
+ def print(self):
+ print(f"[Miner] Name: {self.mi_name} (Mining point: {self.mining_point}, Price: {self.mi_price})")
+
+
+class ApplicationType:
+ def __init__(self):
+ self.unit_list_in_barracks = []
+ self.unit_list = []
+ self.curr_turn = 1
+ self.MAX_turn = 50
+ self.gold = 1000
+ self.enemy_hp = 500
+ self.flag_status = 0
+
+ def __del__(self):
+ for elem in self.unit_list_in_barracks:
+ del elem
+ for elem in self.unit_list:
+ del elem
+
+ def increase_turn(self):
+ self.curr_turn += 1
+ if self.curr_turn == self.MAX_turn:
+ self.flag_status = 1
+ print("You lose")
+
+ def print_unit_list_in_barracks(self):
+ for elem in self.unit_list_in_barracks:
+ elem.print()
+
+ def print_unit_list(self):
+ for elem in self.unit_list:
+ elem.print()
+
+ def run(self):
+ while self.flag_status == 0:
+ sOption = self.get_command()
+ if sOption == "1":
+ self.hire_unit()
+ elif sOption == "2":
+ self.attack_to_enemy()
+ elif sOption == "3":
+ self.gather_money()
+ elif sOption == "4":
+ self.print_unit_list()
+ elif sOption == "5":
+ break
+ print("Exit the program...")
+
+ def get_command(self):
+ print(f"Turn: {self.curr_turn}")
+ print(f"Gold: {self.gold}")
+ print(f"Enemy HP: {self.enemy_hp}\n")
+ print("1. Hire Unit\n2. Attack to Enemy\n3. Gather Money\n4. Print Units\nInput>", end='')
+ cmd = input()
+ return cmd
+
+ def hire_unit(self):
+ pass
+
+ def attack_to_enemy(self):
+ pass
+
+ def gather_money(self):
+ pass
+
+```
+
+
+### test.py
+```python
+import unittest
+from unittest.mock import patch
+from io import StringIO
+
+from game import Attacker, Miner, ApplicationType
+
+
+class TestAttacker(unittest.TestCase):
+ def test_attacker_action(self):
+ attacker = Attacker(1, "Attacker1", 100, 1, "Attacker1", 100, 50)
+ self.assertEqual(attacker.action(), 50)
+
+ def test_attacker_print(self):
+ attacker = Attacker(1, "Attacker1", 100, 1, "Attacker1", 100, 50)
+ with patch('sys.stdout', new=StringIO()) as fake_out:
+ attacker.print()
+ self.assertEqual(fake_out.getvalue().strip(), "[Attacker] Name: Attacker1 (Attack point: 50, Price: 100)")
+
+
+class TestMiner(unittest.TestCase):
+ def test_miner_action(self):
+ miner = Miner(1, "Miner1", 100, 1, "Miner1", 100, 30)
+ self.assertEqual(miner.action(), 30)
+
+ def test_miner_print(self):
+ miner = Miner(1, "Miner1", 100, 1, "Miner1", 100, 30)
+ with patch('sys.stdout', new=StringIO()) as fake_out:
+ miner.print()
+ self.assertEqual(fake_out.getvalue().strip(), "[Miner] Name: Miner1 (Mining point: 30, Price: 100)")
+
+
+class TestApplicationType(unittest.TestCase):
+ def setUp(self):
+ self.app = ApplicationType()
+
+ def test_initial_values(self):
+ self.assertEqual(self.app.curr_turn, 1)
+ self.assertEqual(self.app.MAX_turn, 50)
+ self.assertEqual(self.app.gold, 1000)
+ self.assertEqual(self.app.enemy_hp, 500)
+ self.assertEqual(self.app.flag_status, 0)
+
+ @patch('builtins.input', side_effect=['5'])
+ def test_run(self):
+ with patch('sys.stdout', new=StringIO()) as fake_out:
+ self.app.run()
+ self.assertIn("Exit the program...", fake_out.getvalue())
+
+ def test_increase_turn(self):
+ self.app.increase_turn()
+ self.assertEqual(self.app.curr_turn, 2)
+ self.app.curr_turn = 49
+ self.app.increase_turn()
+ self.assertEqual(self.app.flag_status, 1)
+
+ @patch('sys.stdout', new_callable=StringIO)
+ def test_print_unit_list_in_barracks(self, mock_stdout):
+ miner = Miner(1, "Miner1", 100, 1, "Miner1", 100, 30)
+ self.app.unit_list_in_barracks.append(miner)
+ self.app.print_unit_list_in_barracks()
+ self.assertIn("[Miner] Name: Miner1 (Mining point: 30, Price: 100)", mock_stdout.getvalue().strip())
+
+ @patch('sys.stdout', new_callable=StringIO)
+ def test_print_unit_list(self, mock_stdout):
+ attacker = Attacker(1, "Attacker1", 100, 1, "Attacker1", 100, 50)
+ self.app.unit_list.append(attacker)
+ self.app.print_unit_list()
+ self.assertIn("[Attacker] Name: Attacker1 (Attack point: 50, Price: 100)", mock_stdout.getvalue().strip())
+```
+
+
+실행 결과는 아래와 같습니다.
+
\ No newline at end of file
diff --git "a/8th_members/\352\271\200\354\233\220\352\262\275/13\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\233\220\352\262\275/13\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..e2d8852
--- /dev/null
+++ "b/8th_members/\352\271\200\354\233\220\352\262\275/13\354\243\274\354\260\250.md"
@@ -0,0 +1,8 @@
+# pdb 실행 해보기
+
+
+
+# CProfile 예제 파일 직접 생성해서 실행해보기
+12주차 미션으로 제출한 python game.py 코드를 그대로 사용했습니다. 실행 결과는 아래와 같습니다.
+
+
\ No newline at end of file
diff --git "a/8th_members/\352\271\200\354\233\220\352\262\275/1\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\233\220\352\262\275/1\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..301175b
--- /dev/null
+++ "b/8th_members/\352\271\200\354\233\220\352\262\275/1\354\243\274\354\260\250.md"
@@ -0,0 +1,17 @@
+## CPython 소스코드 다운로드 및 환경 구성 (화면 캡쳐)
+
+
+
+### 특이 사항
+스터디 노션 환경 구성에 나온 정보대로
+git clone --branch 3.9 https://github.com/python/cpython
+
+명령어만 사용하면 사이즈가 커서 RPC fail 에러 발생.
+그래서 뒤에 "--depth 1" 을 추가로 붙여 얕은 클론만 해둔 상태.
+필요한 경우 "git fetch --unshallow"로 다른 레포들도 갖고 와야 한다.
+*참고: https://eunjinii.tistory.com/128
+
+
+
+## 스터디에서 이루고 싶은 목표, 함께 하게 된 소감 등
+그동안 파이썬을 사용하면서 궁금했던 점들을 알아 가며 호기심을 해소할 수 있는 기회가 됐음 합니다!
\ No newline at end of file
diff --git "a/8th_members/\352\271\200\354\233\220\352\262\275/2\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\233\220\352\262\275/2\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..59421c0
--- /dev/null
+++ "b/8th_members/\352\271\200\354\233\220\352\262\275/2\354\243\274\354\260\250.md"
@@ -0,0 +1,8 @@
+# 2주차
+
+## 컴파일 완료된 화면 캡쳐 (./python.exe 실행 화면)
+
+
+
+## 문법 다시 생성하기 (pass 키워드 변경해보기) 부분 원하는 키워드 다른걸로 바꿔보기
+
\ No newline at end of file
diff --git "a/8th_members/\352\271\200\354\233\220\352\262\275/3\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\233\220\352\262\275/3\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..91877f1
--- /dev/null
+++ "b/8th_members/\352\271\200\354\233\220\352\262\275/3\354\243\274\354\260\250.md"
@@ -0,0 +1,5 @@
+## 파이썬 세션에서 runtime flag 확인하기
+
+
+## importlib.util.find_spec 함수를 사용해서 원하는 모듈의 위치를 찾아보기
+
\ No newline at end of file
diff --git "a/8th_members/\352\271\200\354\233\220\352\262\275/4\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\233\220\352\262\275/4\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..0005a0b
--- /dev/null
+++ "b/8th_members/\352\271\200\354\233\220\352\262\275/4\354\243\274\354\260\250.md"
@@ -0,0 +1,6 @@
+# 1. instaviz로 AST 시각화
+
+
+
+# 2. ‘거의 같음’ 연산자를 문법에 추가하고 AlE타입의 객체를 출력
+
\ No newline at end of file
diff --git "a/8th_members/\352\271\200\354\233\220\352\262\275/5\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\233\220\352\262\275/5\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..04bdee8
--- /dev/null
+++ "b/8th_members/\352\271\200\354\233\220\352\262\275/5\354\243\274\354\260\250.md"
@@ -0,0 +1,2 @@
+# ‘거의 같음’ 연산자 구현하기
+
\ No newline at end of file
diff --git "a/8th_members/\352\271\200\354\233\220\352\262\275/6\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\233\220\352\262\275/6\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..674475c
--- /dev/null
+++ "b/8th_members/\352\271\200\354\233\220\352\262\275/6\354\243\274\354\260\250.md"
@@ -0,0 +1,45 @@
+## 1. 사용한 코드
+```python
+import inspect
+
+def minBitFlips3(start: int, goal: int) -> int:
+ flips, xor = 0, start ^ goal
+ while xor != 0:
+ flips += 1
+ xor &= (xor - 1)
+ print_frame_info()
+ return flips
+
+
+
+def minBitFlips4(start: int, goal: int) -> int:
+ x = start ^ goal
+ count = 0
+ while x:
+ x = x & (x - 1)
+ count += 1
+ print_frame_info()
+ return count
+
+
+def print_frame_info():
+ frame = inspect.currentframe().f_back # 현재 함수의 호출자 프레임
+ try:
+ print("Function Name:", frame.f_code.co_name)
+ print("File Name:", frame.f_code.co_filename)
+ print("Line No:", frame.f_back.f_lineno)
+ print("Local Variables:", list(frame.f_locals.keys()))
+ print("Global Variables:", list(frame.f_globals.keys()))
+ # dis.dis(frame.f_code) # 역 디스어셈블러
+ print("\\n\\n")
+ finally:
+ del frame
+
+
+minBitFlips3(1, 10)
+minBitFlips4(1, 10)
+```
+
+
+## 2. 실행 결과
+
\ No newline at end of file
diff --git "a/8th_members/\352\271\200\354\233\220\352\262\275/7\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\233\220\352\262\275/7\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..a542b1b
--- /dev/null
+++ "b/8th_members/\352\271\200\354\233\220\352\262\275/7\354\243\274\354\260\250.md"
@@ -0,0 +1,9 @@
+- tracemalloc 통해 직접 메모리 확인해보기
+
+아래 레포지토리의 코드(프로그램)를 통해 메모리를 확인해보았습니다.
+https://github.com/secureWKkim/web-crawling-on-container
+
+
+실행 결과는 아래와 같습니다.
+
+
\ No newline at end of file
diff --git "a/8th_members/\352\271\200\354\233\220\352\262\275/8\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\233\220\352\262\275/8\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..291631e
--- /dev/null
+++ "b/8th_members/\352\271\200\354\233\220\352\262\275/8\354\243\274\354\260\250.md"
@@ -0,0 +1,2 @@
+### 원하는 라이브러리 import 해보고, 참조 카운트가 어떻게 바뀌는지 출력해보기
+
\ No newline at end of file
diff --git "a/8th_members/\352\271\200\354\233\220\352\262\275/9\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\233\220\352\262\275/9\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..0dac85b
--- /dev/null
+++ "b/8th_members/\352\271\200\354\233\220\352\262\275/9\354\243\274\354\260\250.md"
@@ -0,0 +1,5 @@
+### 10.3.8 애플리케이션 예제 두 코드 실행해보기
+1개 이상의 port가 출력되도록 만들기 위해 검사하는 port 번호 range를 0~99로 변경했습니다.
+
+
+
diff --git "a/8th_members/\352\271\200\354\233\220\352\262\275/image.png" "b/8th_members/\352\271\200\354\233\220\352\262\275/image.png"
new file mode 100644
index 0000000..94002c9
Binary files /dev/null and "b/8th_members/\352\271\200\354\233\220\352\262\275/image.png" differ
diff --git "a/8th_members/\352\271\200\354\247\204\354\225\204/10\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\247\204\354\225\204/10\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..00648e1
--- /dev/null
+++ "b/8th_members/\352\271\200\354\247\204\354\225\204/10\354\243\274\354\260\250.md"
@@ -0,0 +1,4 @@
+# 10주차
+
+## Port Scanner: Multi Threading
+
diff --git "a/8th_members/\352\271\200\354\247\204\354\225\204/11\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\247\204\354\225\204/11\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..636ced4
--- /dev/null
+++ "b/8th_members/\352\271\200\354\247\204\354\225\204/11\354\243\274\354\260\250.md"
@@ -0,0 +1,391 @@
+# 11주차
+
+## 원하는 type 정해서 구조체 및 type 코드 분석해보기
+
+### String type
+
+```c
+// Include/unicodeobject.h
+
+PyAPI_DATA(PyTypeObject) PyUnicode_Type;
+PyAPI_DATA(PyTypeObject) PyUnicodeIter_Type;
+```
+- `PyUnicode_Type` : 기본 문자열 타입, Python 문자열 객체의 모든 메타데이터와 메서드를 포함
+- `PyUnicodeIter_Type` : 문자열 이터레이터 타입, 문자열을 순회할 때 사용
+
+### `PyUnicode_Type`
+
+```c
+// Objects/unicodeobject.c
+
+PyTypeObject PyUnicode_Type = {
+ PyVarObject_HEAD_INIT(&PyType_Type, 0)
+ "str", /* tp_name */
+ sizeof(PyUnicodeObject), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ /* Slots */
+ (destructor)unicode_dealloc, /* tp_dealloc */
+ 0, /* tp_vectorcall_offset */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_as_async */
+ unicode_repr, /* tp_repr */
+ &unicode_as_number, /* tp_as_number */
+ &unicode_as_sequence, /* tp_as_sequence */
+ &unicode_as_mapping, /* tp_as_mapping */
+ (hashfunc) unicode_hash, /* tp_hash*/
+ 0, /* tp_call*/
+ (reprfunc) unicode_str, /* tp_str */
+ PyObject_GenericGetAttr, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
+ Py_TPFLAGS_UNICODE_SUBCLASS, /* tp_flags */
+ unicode_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ PyUnicode_RichCompare, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ unicode_iter, /* tp_iter */
+ 0, /* tp_iternext */
+ unicode_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ &PyBaseObject_Type, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ unicode_new, /* tp_new */
+ PyObject_Del, /* tp_free */
+};
+```
+
+### `PyUnicodeIter_Type`
+
+```c
+// Objects/unicodeobject.c
+
+PyTypeObject PyUnicodeIter_Type = {
+ PyVarObject_HEAD_INIT(&PyType_Type, 0)
+ "str_iterator", /* tp_name */
+ sizeof(unicodeiterobject), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ /* methods */
+ (destructor)unicodeiter_dealloc, /* tp_dealloc */
+ 0, /* tp_vectorcall_offset */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_as_async */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ PyObject_GenericGetAttr, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
+ 0, /* tp_doc */
+ (traverseproc)unicodeiter_traverse, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ PyObject_SelfIter, /* tp_iter */
+ (iternextfunc)unicodeiter_next, /* tp_iternext */
+ unicodeiter_methods, /* tp_methods */
+ 0,
+};
+```
+
+### `unicode_new`
+
+```c
+// Objects/unicodeobject.c
+
+static PyObject *
+unicode_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ PyObject *x = NULL;
+ static char *kwlist[] = {"object", "encoding", "errors", 0};
+ char *encoding = NULL;
+ char *errors = NULL;
+
+ if (type != &PyUnicode_Type)
+ return unicode_subtype_new(type, args, kwds);
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oss:str",
+ kwlist, &x, &encoding, &errors))
+ return NULL;
+ if (x == NULL)
+ _Py_RETURN_UNICODE_EMPTY();
+ if (encoding == NULL && errors == NULL)
+ return PyObject_Str(x);
+ else
+ return PyUnicode_FromEncodedObject(x, encoding, errors);
+}
+```
+
+1. 타입 확인
+2. `PyArg_ParseTupleAndKeywords`를 통해 인자 파싱
+3. 객체 생성
+
+### `PyUnicode_New`
+
+```c
+// Objects/unicodeobject.c
+
+PyObject *
+PyUnicode_New(Py_ssize_t size, Py_UCS4 maxchar)
+{
+ PyObject *obj;
+ PyCompactUnicodeObject *unicode;
+ void *data;
+ enum PyUnicode_Kind kind;
+ int is_sharing, is_ascii;
+ Py_ssize_t char_size;
+ Py_ssize_t struct_size;
+
+ /* Optimization for empty strings */
+ if (size == 0 && unicode_empty != NULL) {
+ Py_INCREF(unicode_empty);
+ return unicode_empty;
+ }
+
+ is_ascii = 0;
+ is_sharing = 0;
+ struct_size = sizeof(PyCompactUnicodeObject);
+ if (maxchar < 128) {
+ kind = PyUnicode_1BYTE_KIND;
+ char_size = 1;
+ is_ascii = 1;
+ struct_size = sizeof(PyASCIIObject);
+ }
+ else if (maxchar < 256) {
+ kind = PyUnicode_1BYTE_KIND;
+ char_size = 1;
+ }
+ else if (maxchar < 65536) {
+ kind = PyUnicode_2BYTE_KIND;
+ char_size = 2;
+ if (sizeof(wchar_t) == 2)
+ is_sharing = 1;
+ }
+ else {
+ if (maxchar > MAX_UNICODE) {
+ PyErr_SetString(PyExc_SystemError,
+ "invalid maximum character passed to PyUnicode_New");
+ return NULL;
+ }
+ kind = PyUnicode_4BYTE_KIND;
+ char_size = 4;
+ if (sizeof(wchar_t) == 4)
+ is_sharing = 1;
+ }
+
+ /* Ensure we won't overflow the size. */
+ if (size < 0) {
+ PyErr_SetString(PyExc_SystemError,
+ "Negative size passed to PyUnicode_New");
+ return NULL;
+ }
+ if (size > ((PY_SSIZE_T_MAX - struct_size) / char_size - 1))
+ return PyErr_NoMemory();
+
+ /* Duplicated allocation code from _PyObject_New() instead of a call to
+ * PyObject_New() so we are able to allocate space for the object and
+ * it's data buffer.
+ */
+ obj = (PyObject *) PyObject_MALLOC(struct_size + (size + 1) * char_size);
+ if (obj == NULL)
+ return PyErr_NoMemory();
+ obj = PyObject_INIT(obj, &PyUnicode_Type);
+ if (obj == NULL)
+ return NULL;
+
+ unicode = (PyCompactUnicodeObject *)obj;
+ if (is_ascii)
+ data = ((PyASCIIObject*)obj) + 1;
+ else
+ data = unicode + 1;
+ _PyUnicode_LENGTH(unicode) = size;
+ _PyUnicode_HASH(unicode) = -1;
+ _PyUnicode_STATE(unicode).interned = 0;
+ _PyUnicode_STATE(unicode).kind = kind;
+ _PyUnicode_STATE(unicode).compact = 1;
+ _PyUnicode_STATE(unicode).ready = 1;
+ _PyUnicode_STATE(unicode).ascii = is_ascii;
+ if (is_ascii) {
+ ((char*)data)[size] = 0;
+ _PyUnicode_WSTR(unicode) = NULL;
+ }
+ else if (kind == PyUnicode_1BYTE_KIND) {
+ ((char*)data)[size] = 0;
+ _PyUnicode_WSTR(unicode) = NULL;
+ _PyUnicode_WSTR_LENGTH(unicode) = 0;
+ unicode->utf8 = NULL;
+ unicode->utf8_length = 0;
+ }
+ else {
+ unicode->utf8 = NULL;
+ unicode->utf8_length = 0;
+ if (kind == PyUnicode_2BYTE_KIND)
+ ((Py_UCS2*)data)[size] = 0;
+ else /* kind == PyUnicode_4BYTE_KIND */
+ ((Py_UCS4*)data)[size] = 0;
+ if (is_sharing) {
+ _PyUnicode_WSTR_LENGTH(unicode) = size;
+ _PyUnicode_WSTR(unicode) = (wchar_t *)data;
+ }
+ else {
+ _PyUnicode_WSTR_LENGTH(unicode) = 0;
+ _PyUnicode_WSTR(unicode) = NULL;
+ }
+ }
+#ifdef Py_DEBUG
+ unicode_fill_invalid((PyObject*)unicode, 0);
+#endif
+ assert(_PyUnicode_CheckConsistency((PyObject*)unicode, 0));
+ return obj;
+}
+```
+
+1. empty string 처리
+2. 유니코드 종류 결정
+3. 메모리 할당
+4. `PyObject_INIT`을 통해 객체 초기화
+5. 객체 상태 초기화
+6. 데이터 초기화
+7. 디버깅 모드일 경우 `unicode_fill_invalid`를 통해 객체 검사
+
+### `_PyUnicode_New`
+
+```c
+// Objects/unicodeobject.c
+
+static PyUnicodeObject *
+_PyUnicode_New(Py_ssize_t length)
+{
+ PyUnicodeObject *unicode;
+ size_t new_size;
+
+ /* Optimization for empty strings */
+ if (length == 0 && unicode_empty != NULL) {
+ Py_INCREF(unicode_empty);
+ return (PyUnicodeObject*)unicode_empty;
+ }
+
+ /* Ensure we won't overflow the size. */
+ if (length > ((PY_SSIZE_T_MAX / (Py_ssize_t)sizeof(Py_UNICODE)) - 1)) {
+ return (PyUnicodeObject *)PyErr_NoMemory();
+ }
+ if (length < 0) {
+ PyErr_SetString(PyExc_SystemError,
+ "Negative size passed to _PyUnicode_New");
+ return NULL;
+ }
+
+ unicode = PyObject_New(PyUnicodeObject, &PyUnicode_Type);
+ if (unicode == NULL)
+ return NULL;
+ new_size = sizeof(Py_UNICODE) * ((size_t)length + 1);
+
+ _PyUnicode_WSTR_LENGTH(unicode) = length;
+ _PyUnicode_HASH(unicode) = -1;
+ _PyUnicode_STATE(unicode).interned = 0;
+ _PyUnicode_STATE(unicode).kind = 0;
+ _PyUnicode_STATE(unicode).compact = 0;
+ _PyUnicode_STATE(unicode).ready = 0;
+ _PyUnicode_STATE(unicode).ascii = 0;
+ _PyUnicode_DATA_ANY(unicode) = NULL;
+ _PyUnicode_LENGTH(unicode) = 0;
+ _PyUnicode_UTF8(unicode) = NULL;
+ _PyUnicode_UTF8_LENGTH(unicode) = 0;
+
+ _PyUnicode_WSTR(unicode) = (Py_UNICODE*) PyObject_MALLOC(new_size);
+ if (!_PyUnicode_WSTR(unicode)) {
+ Py_DECREF(unicode);
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ /* Initialize the first element to guard against cases where
+ * the caller fails before initializing str -- unicode_resize()
+ * reads str[0], and the Keep-Alive optimization can keep memory
+ * allocated for str alive across a call to unicode_dealloc(unicode).
+ * We don't want unicode_resize to read uninitialized memory in
+ * that case.
+ */
+ _PyUnicode_WSTR(unicode)[0] = 0;
+ _PyUnicode_WSTR(unicode)[length] = 0;
+
+ assert(_PyUnicode_CheckConsistency((PyObject *)unicode, 0));
+ return unicode;
+}
+```
+
+1. empty string 처리
+2. size 유효성 체크
+3. `PyObject_New`를 통해 객체 생성
+4. 메모리 할당 및 초기화
+5. 아래 코드를 통해 메모리 초기화
+ ```c
+ _PyUnicode_WSTR(unicode)[0] = 0;
+ _PyUnicode_WSTR(unicode)[length] = 0;
+ ```
+6. `_PyUnicode_CheckConsistency`를 통해 객체 검증
+
+### `unicode_fill`
+
+문자열 데이터를 특정 값으로 채우는 기능 수행, 메모리 초기화 및 데이터 채우기 작업에 사용
+
+```c
+// Objects/unicodeobject.c
+
+static inline void
+unicode_fill(enum PyUnicode_Kind kind, void *data, Py_UCS4 value,
+ Py_ssize_t start, Py_ssize_t length)
+{
+ assert(0 <= start);
+ assert(kind != PyUnicode_WCHAR_KIND);
+ switch (kind) {
+ case PyUnicode_1BYTE_KIND: {
+ assert(value <= 0xff);
+ Py_UCS1 ch = (unsigned char)value;
+ Py_UCS1 *to = (Py_UCS1 *)data + start;
+ memset(to, ch, length);
+ break;
+ }
+ case PyUnicode_2BYTE_KIND: {
+ assert(value <= 0xffff);
+ Py_UCS2 ch = (Py_UCS2)value;
+ Py_UCS2 *to = (Py_UCS2 *)data + start;
+ const Py_UCS2 *end = to + length;
+ for (; to < end; ++to) *to = ch;
+ break;
+ }
+ case PyUnicode_4BYTE_KIND: {
+ assert(value <= MAX_UNICODE);
+ Py_UCS4 ch = value;
+ Py_UCS4 * to = (Py_UCS4 *)data + start;
+ const Py_UCS4 *end = to + length;
+ for (; to < end; ++to) *to = ch;
+ break;
+ }
+ default: Py_UNREACHABLE();
+ }
+}
+```
+
+- Parameters
+ - `enum PyUnicode_Kind kind` : 문자열 유니코드 인코딩 종류
+ - `void *data` : 문자열 데이터
+ - `Py_UCS4 value` : 채울 유니코드 값
+ - `Py_ssize_t start` : 문자열 데이터를 채우기 시작할 위치
+ - `Py_ssize_t length` : 채울 길이
+
+1. 시작 위치 유효성 체크
+2. 유니코드 종류에 따른 방식으로 데이터 채우기
diff --git "a/8th_members/\352\271\200\354\247\204\354\225\204/12\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\247\204\354\225\204/12\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..ef995c8
--- /dev/null
+++ "b/8th_members/\352\271\200\354\247\204\354\225\204/12\354\243\274\354\260\250.md"
@@ -0,0 +1,77 @@
+# 12주차
+
+## 나만의 Test Case 만들기
+
+### 구조
+
+```
+.
+├── tests
+│ └── test_normalize.py
+└── text_processing.py
+```
+
+### `text_processing.py`
+
+```python
+def normalize(input_str):
+ out = input_str.lower()
+ out = out.strip()
+ while ' ' in out:
+ out = out.replace(' ', ' ')
+ return out
+```
+
+### `test_normalize.py`
+
+```python
+import unittest
+from text_processing import normalize
+
+
+class TestTextNormalize(unittest.TestCase):
+ def test_normalize(self):
+ test_str = "This is an example."
+ pred = normalize(test_str)
+ self.assertEqual(pred, "this is an example.")
+
+ test_str = " EXTRA SPACE "
+ pred = normalize(test_str)
+ self.assertEqual(pred, "extra space")
+
+ test_str = "THIS IS ALL CAPS!!"
+ pred = normalize(test_str)
+ self.assertEqual(pred, "this is all caps!!")
+
+ test_str = " "
+ pred = normalize(test_str)
+ self.assertEqual(pred, "")
+
+ test_str = "this is all lower space..."
+ pred = normalize(test_str)
+ self.assertEqual(pred, "this is all lower space...")
+
+ test_str = " H e L l O !"
+ pred = normalize(test_str)
+ self.assertEqual(pred, "h e l l o !")
+
+ test_str = ""
+ pred = normalize(test_str)
+ self.assertEqual(pred, "")
+
+ test_str = "........"
+ pred = normalize(test_str)
+ self.assertEqual(pred, "........")
+
+ test_str = "EX A M P LE"
+ pred = normalize(test_str)
+ self.assertEqual(pred, "ex a m p le")
+
+ test_str = "AbCd EfGh IjKl MnOp"
+ pred = normalize(test_str)
+ self.assertEqual(pred, "abcd efgh ijkl mnop")
+```
+
+### 결과
+
+
diff --git "a/8th_members/\352\271\200\354\247\204\354\225\204/13\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\247\204\354\225\204/13\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..220c5b8
--- /dev/null
+++ "b/8th_members/\352\271\200\354\247\204\354\225\204/13\354\243\274\354\260\250.md"
@@ -0,0 +1,43 @@
+# 13주차
+
+## pbd 실행해보기
+
+```python
+def calculate_performance_improvement_rate(before, after):
+ return (before - after) / after * 100
+
+
+if __name__ == "__main__":
+ breakpoint()
+
+ before_performance = 150
+ after_performance = 57
+
+ performance_improvement_rate = calculate_performance_improvement_rate(before_performance, after_performance)
+
+ print(f"{performance_improvement_rate:.2f}%")
+
+```
+
+
+
+
+## CProfile 예제 파일 직접 생성해서 실행해보기
+
+```python
+def calculate_performance_improvement_rate(before, after):
+ return (before - after) / after * 100
+
+
+if __name__ == "__main__":
+ before_performance = 150
+ after_performance = 57
+
+ performance_improvement_rate = calculate_performance_improvement_rate(before_performance, after_performance)
+
+ print(f"{performance_improvement_rate:.2f}%")
+```
+
+
+
+
diff --git "a/8th_members/\352\271\200\354\247\204\354\225\204/1\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\247\204\354\225\204/1\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..fb156ea
--- /dev/null
+++ "b/8th_members/\352\271\200\354\247\204\354\225\204/1\354\243\274\354\260\250.md"
@@ -0,0 +1,9 @@
+# 1주차
+
+## CPython 소스코드 다운로드 및 환경 구성 (화면 캡쳐)
+
+
+## 스터디에서 이루고 싶은 목표, 함께 하게 된 소감 등
+- Python을 깊이 이해하여 Python을 잘 활용하는 개발자가 되고 싶습니다.
+- Python의 내부 구조와 코드를 파악하며 Python의 철학을 이해하고 좋은 코드에 대해서도 배워가고 싶습니다.
+- 같이 스터디하면서 많이 배우겠습니다! 재밌고 보람찬 시간이 될 수 있도록 열심히 참여하겠습니다.
diff --git "a/8th_members/\352\271\200\354\247\204\354\225\204/2\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\247\204\354\225\204/2\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..032d9a5
--- /dev/null
+++ "b/8th_members/\352\271\200\354\247\204\354\225\204/2\354\243\274\354\260\250.md"
@@ -0,0 +1,7 @@
+# 2주차
+
+## 컴파일 완료된 화면 캡쳐 (./python.exe 실행 화면)
+
+
+## 문법 다시 생성하기 (pass 키워드 변경해보기) 부분 원하는 키워드 다른걸로 바꿔보기 (결과 화면 캡쳐)
+
diff --git "a/8th_members/\352\271\200\354\247\204\354\225\204/3\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\247\204\354\225\204/3\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..e3c1e49
--- /dev/null
+++ "b/8th_members/\352\271\200\354\247\204\354\225\204/3\354\243\274\354\260\250.md"
@@ -0,0 +1,12 @@
+# 3주차
+
+## CPython 인터프리터 실행 과정 코드 레벨로 이해하기
+
+
+## 파이썬 세션에서 runtime flag 확인하기
+
+
+
+## importlib.util.find_spec 함수를 사용해서 원하는 모듈의 위치를 찾아보기
+
+
diff --git "a/8th_members/\352\271\200\354\247\204\354\225\204/4\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\247\204\354\225\204/4\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..ca86875
--- /dev/null
+++ "b/8th_members/\352\271\200\354\247\204\354\225\204/4\354\243\274\354\260\250.md"
@@ -0,0 +1,17 @@
+# 4주차
+
+## instaviz로 AST 시각화 해보기
+```python
+def pow(x, y):
+ result = 1
+ for i in range(0, y):
+ result *= x
+ return result
+
+import instaviz
+instaviz.show(pow)
+```
+
+
+## ‘거의 같음’ 연산자를 문법에 추가하고 AlE타입의 객체를 출력 해보기
+
diff --git "a/8th_members/\352\271\200\354\247\204\354\225\204/5\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\247\204\354\225\204/5\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..75ed16a
--- /dev/null
+++ "b/8th_members/\352\271\200\354\247\204\354\225\204/5\354\243\274\354\260\250.md"
@@ -0,0 +1,4 @@
+# 4주차
+
+## ‘거의 같음’ 연산자 구현
+
diff --git "a/8th_members/\352\271\200\354\247\204\354\225\204/6\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\247\204\354\225\204/6\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..984d33b
--- /dev/null
+++ "b/8th_members/\352\271\200\354\247\204\354\225\204/6\354\243\274\354\260\250.md"
@@ -0,0 +1,35 @@
+# 6주차
+
+## 파이썬에서 프레임 객체 접근해보기
+```python
+import inspect
+
+
+def print_frame_info():
+ frame = inspect.currentframe().f_back
+ try:
+ print("Function Name:", frame.f_code.co_name)
+ print("File Name:", frame.f_code.co_filename)
+ print("Line No:", frame.f_back.f_lineno)
+ print("Local Variables:", list(frame.f_locals.keys()))
+ print("Global Variables:", list(frame.f_globals.keys()))
+ finally:
+ del frame
+
+
+def print_sum(n1, n2):
+ sum = n1 + n2
+ print(sum)
+ print_frame_info()
+
+
+def print_upper(word):
+ upper_word = word.upper()
+ print(upper_word)
+ print_frame_info()
+
+
+print_sum(10, 20)
+print_upper("Python")
+```
+
diff --git "a/8th_members/\352\271\200\354\247\204\354\225\204/7\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\247\204\354\225\204/7\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..30c2c9b
--- /dev/null
+++ "b/8th_members/\352\271\200\354\247\204\354\225\204/7\354\243\274\354\260\250.md"
@@ -0,0 +1,35 @@
+# 7주차
+
+## tracemalloc 모듈 사용해서 직접 메모리 확인해보기
+```python
+import tracemalloc
+
+
+def fac_with_recursion(n):
+ if n <= 1:
+ return n
+ return n * fac_with_recursion(n - 1)
+
+
+def fac_with_loop(n):
+ result = 1
+ for i in range(1, n):
+ result *= i
+ return result
+
+
+tracemalloc.start()
+
+fac_10_with_recursion = fac_with_recursion(10)
+fac_10_with_loop = fac_with_loop(10)
+
+snapshot = tracemalloc.take_snapshot()
+snapshot = snapshot.filter_traces([tracemalloc.Filter(True, "**/tracemalloc_example.py")])
+stats = snapshot.statistics("lineno")
+
+for stat in stats:
+ print(stat, stat.traceback.format())
+
+tracemalloc.stop()
+```
+
diff --git "a/8th_members/\352\271\200\354\247\204\354\225\204/8\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\247\204\354\225\204/8\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..184fff0
--- /dev/null
+++ "b/8th_members/\352\271\200\354\247\204\354\225\204/8\354\243\274\354\260\250.md"
@@ -0,0 +1,4 @@
+# 8주차
+
+## 원하는 라이브러리 import 해보고, 참조 카운트가 어떻게 바뀌는지 출력해보기
+
diff --git "a/8th_members/\352\271\200\354\247\204\354\225\204/9\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\354\247\204\354\225\204/9\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..c9cdb12
--- /dev/null
+++ "b/8th_members/\352\271\200\354\247\204\354\225\204/9\354\243\274\354\260\250.md"
@@ -0,0 +1,7 @@
+# 9주차
+
+## Port Scanner: Queue
+
+
+## Port Scanner: multiprocessing.Queue
+
diff --git "a/8th_members/\352\271\200\355\225\204\353\252\250/10\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\355\225\204\353\252\250/10\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..b48399e
--- /dev/null
+++ "b/8th_members/\352\271\200\355\225\204\353\252\250/10\354\243\274\354\260\250.md"
@@ -0,0 +1,120 @@
+# Port Scanner 예제 실행해보기
+
+### 싱글 프로세스 + 싱글 스레드
+```python3
+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()
+```
+
+
+
+### 멀티스레딩
+
+
+```python3
+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()
+```
+
+
+
+### 멀티스레딩 + 스레드 개수 제한
+```python3
+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()
+
+```
+
+
+
+
diff --git "a/8th_members/\352\271\200\355\225\204\353\252\250/11\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\355\225\204\353\252\250/11\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..213ffc9
--- /dev/null
+++ "b/8th_members/\352\271\200\355\225\204\353\252\250/11\354\243\274\354\260\250.md"
@@ -0,0 +1,102 @@
+
+# dictionay 타입에 대해서 알아보자
+### dict get
+
+```c
+static inline Py_ssize_t
+dictkeys_get_index(const PyDictKeysObject *keys, Py_ssize_t i)
+{
+ Py_ssize_t s = DK_SIZE(keys);
+ Py_ssize_t ix;
+
+ if (s <= 0xff) {
+ const int8_t *indices = (const int8_t*)(keys->dk_indices);
+ ix = indices[i];
+ }
+ else if (s <= 0xffff) {
+ const int16_t *indices = (const int16_t*)(keys->dk_indices);
+ ix = indices[i];
+ }
+#if SIZEOF_VOID_P > 4
+ else if (s > 0xffffffff) {
+ const int64_t *indices = (const int64_t*)(keys->dk_indices);
+ ix = indices[i];
+ }
+#endif
+ else {
+ const int32_t *indices = (const int32_t*)(keys->dk_indices);
+ ix = indices[i];
+ }
+ assert(ix >= DKIX_DUMMY);
+ return ix;
+}
+```
+
+### dict set
+```c
+static inline void
+dictkeys_set_index(PyDictKeysObject *keys, Py_ssize_t i, Py_ssize_t ix)
+{
+ Py_ssize_t s = DK_SIZE(keys);
+
+ assert(ix >= DKIX_DUMMY);
+
+ if (s <= 0xff) {
+ int8_t *indices = (int8_t*)(keys->dk_indices);
+ assert(ix <= 0x7f);
+ indices[i] = (char)ix;
+ }
+ else if (s <= 0xffff) {
+ int16_t *indices = (int16_t*)(keys->dk_indices);
+ assert(ix <= 0x7fff);
+ indices[i] = (int16_t)ix;
+ }
+#if SIZEOF_VOID_P > 4
+ else if (s > 0xffffffff) {
+ int64_t *indices = (int64_t*)(keys->dk_indices);
+ indices[i] = ix;
+ }
+#endif
+ else {
+ int32_t *indices = (int32_t*)(keys->dk_indices);
+ assert(ix <= 0x7fffffff);
+ indices[i] = (int32_t)ix;
+ }
+}
+```
+
+### dict new
+```c
+static PyObject *
+new_dict(PyDictKeysObject *keys, PyObject **values)
+{
+ PyDictObject *mp;
+ assert(keys != NULL);
+#if PyDict_MAXFREELIST > 0
+ if (numfree) {
+ mp = free_list[--numfree];
+ assert (mp != NULL);
+ assert (Py_IS_TYPE(mp, &PyDict_Type));
+ _Py_NewReference((PyObject *)mp);
+ }
+ else
+#endif
+ {
+ mp = PyObject_GC_New(PyDictObject, &PyDict_Type);
+ if (mp == NULL) {
+ dictkeys_decref(keys);
+ if (values != empty_values) {
+ free_values(values);
+ }
+ return NULL;
+ }
+ }
+ mp->ma_keys = keys;
+ mp->ma_values = values;
+ mp->ma_used = 0;
+ mp->ma_version_tag = DICT_NEXT_VERSION();
+ ASSERT_CONSISTENT(mp);
+ return (PyObject *)mp;
+}
+```
+
diff --git "a/8th_members/\352\271\200\355\225\204\353\252\250/12\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\355\225\204\353\252\250/12\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..673839b
--- /dev/null
+++ "b/8th_members/\352\271\200\355\225\204\353\252\250/12\354\243\274\354\260\250.md"
@@ -0,0 +1,53 @@
+
+# 나만의 Test Case 만들기
+
+```python3
+def is_prime(n):
+ """
+ Check if a number is a prime number.
+
+ Parameters:
+ n (int): The number to check.
+
+ Returns:
+ bool: True if n is a prime number, False otherwise.
+ """
+ if n <= 1:
+ return False
+ if n <= 3:
+ return True
+ if n % 2 == 0 or n % 3 == 0:
+ return False
+ i = 5
+ while i * i <= n:
+ if n % i == 0 or n % (i + 2) == 0:
+ return False
+ i += 6
+ return True
+
+
+# Example usage:
+if __name__ == "__main__":
+ print(is_prime(29)) # Expected output: True
+ print(is_prime(15)) # Expected output: False
+```
+
+
+```
+import unittest
+
+from src import example
+
+
+class TestIsPrime(unittest.TestCase):
+ def test_is_prime(self):
+ self.assertEqual(example.is_prime(2), True)
+ self.assertEqual(example.is_prime(3), True)
+ self.assertEqual(example.is_prime(4), False)
+ self.assertEqual(example.is_prime(5), True)
+ self.assertEqual(example.is_prime(7), True)
+
+```
+
+
+
diff --git "a/8th_members/\352\271\200\355\225\204\353\252\250/13\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\355\225\204\353\252\250/13\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..fb327aa
--- /dev/null
+++ "b/8th_members/\352\271\200\355\225\204\353\252\250/13\354\243\274\354\260\250.md"
@@ -0,0 +1,43 @@
+# pdb 실행 해보기
+
+
+
+
+
+# CProfile 예제 직접 실행해보기
+
+```python3
+# example.py
+
+import cProfile
+import pstats
+import io
+
+def slow_function():
+ total = 0
+ for i in range(10000):
+ for j in range(10000):
+ total += i * j
+ return total
+
+def fast_function():
+ return sum(i * j for i in range(1000) for j in range(1000))
+
+def main():
+ slow_function()
+ fast_function()
+
+if __name__ == '__main__':
+ pr = cProfile.Profile()
+ pr.enable()
+ main()
+ pr.disable()
+
+ s = io.StringIO()
+ sortby = 'cumulative'
+ ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
+ ps.print_stats()
+ print(s.getvalue())
+
+```
+
diff --git "a/8th_members/\352\271\200\355\225\204\353\252\250/1\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\355\225\204\353\252\250/1\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..b2fec39
--- /dev/null
+++ "b/8th_members/\352\271\200\355\225\204\353\252\250/1\354\243\274\354\260\250.md"
@@ -0,0 +1,11 @@
+# 1주차
+
+## CPython 소스코드 다운로드 및 환경 구성
+
+
+
+## 스터디에서 이루고 싶은 목표, 함께 하게 된 소감
+
+- 한국어로 된 문서 중 가장 좋은 Cpython 문서 만들기!!
+- 이제 백준 문제 풀이 시간 순위 1등은 내꺼!
+- 어디가서 파이썬 좀 안다 자신있게 말하기
diff --git "a/8th_members/\352\271\200\355\225\204\353\252\250/2\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\355\225\204\353\252\250/2\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..a66732a
--- /dev/null
+++ "b/8th_members/\352\271\200\355\225\204\353\252\250/2\354\243\274\354\260\250.md"
@@ -0,0 +1,11 @@
+## 문법 다시 생성하기 (pass 키워드 변경해보기) 부분 원하는 키워드 다른걸로 바꿔보기 (결과 화면 캡쳐)
+### 1. pass -> do_nothing
+
+
+### 2. break -> stop
+
+
+### lzma.h 을 찾을 수 없다는 에러
+
+
+- 맥 기준 brew install xz 하면 해결된다는데 저는 안되네요.. ㅠㅠ
diff --git "a/8th_members/\352\271\200\355\225\204\353\252\250/3\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\355\225\204\353\252\250/3\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..2b55ba0
--- /dev/null
+++ "b/8th_members/\352\271\200\355\225\204\353\252\250/3\354\243\274\354\260\250.md"
@@ -0,0 +1,8 @@
+# 3주차
+
+## 파이썬 세션에서 runtime flag 확인하기
+
+
+
+## importlib.util.find_spec 함수를 사용해서 원하는 모듈의 위치를 찾아보기
+
diff --git "a/8th_members/\352\271\200\355\225\204\353\252\250/4\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\355\225\204\353\252\250/4\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..3ebde9c
--- /dev/null
+++ "b/8th_members/\352\271\200\355\225\204\353\252\250/4\354\243\274\354\260\250.md"
@@ -0,0 +1,25 @@
+- instaviz로 AST 시각화 해보기
+
+```python3
+def scramble_word(word):
+ word_list = list(word)
+ random.shuffle(word_list)
+ return ''.join(word_list)
+```
+
+
+
+
+
+
+
+- ‘거의 같음’ 연산자를 문법에 추가하고 ALE 타입의 객체를 출력 해보기
+
+
+
+```shell
+make regen-token regen-pegen
+make regen-ast
+make -j2 -s
+```
+
diff --git "a/8th_members/\352\271\200\355\225\204\353\252\250/5\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\355\225\204\353\252\250/5\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..2202150
--- /dev/null
+++ "b/8th_members/\352\271\200\355\225\204\353\252\250/5\354\243\274\354\260\250.md"
@@ -0,0 +1,3 @@
+# 거의 같음 연산자 구현
+
+
diff --git "a/8th_members/\352\271\200\355\225\204\353\252\250/6\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\355\225\204\353\252\250/6\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..caeade8
--- /dev/null
+++ "b/8th_members/\352\271\200\355\225\204\353\252\250/6\354\243\274\354\260\250.md"
@@ -0,0 +1,47 @@
+```python3
+import inspect
+import re
+
+
+def print_frame_info():
+ frame = inspect.currentframe().f_back # 현재 함수의 호출자 프레임
+ try:
+ print("Function Name:", frame.f_code.co_name)
+ print("File Name:", frame.f_code.co_filename)
+ print("Line No:", frame.f_back.f_lineno)
+ print("Local Variables:", list(frame.f_locals.keys()))
+ print("Global Variables:", list(frame.f_globals.keys()))
+ # dis.dis(frame.f_code) # 역 디스어셈블러
+ print("\\n\\n")
+ finally:
+ del frame
+
+
+def validate_email(email):
+ pattern = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
+ if re.match(pattern, email):
+ print_frame_info()
+
+ return True
+ else:
+ print_frame_info()
+
+ return False
+
+
+def check_password_complexity(password):
+ if (len(password) >= 8 and
+ any(c.isupper() for c in password) and
+ any(c.islower() for c in password) and
+ any(c.isdigit() for c in password) and
+ any(c in '!@#$%^&*()-_=+[]{}|;:",.<>?`~' for c in password)):
+ print_frame_info()
+ return True
+ else:
+ print_frame_info()
+ return False
+
+validate_email("pilmokim99@gmail.com")
+check_password_complexity("1234")
+```
+
diff --git "a/8th_members/\352\271\200\355\225\204\353\252\250/7\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\355\225\204\353\252\250/7\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..cfcf62b
--- /dev/null
+++ "b/8th_members/\352\271\200\355\225\204\353\252\250/7\354\243\274\354\260\250.md"
@@ -0,0 +1,50 @@
+
+## 코드
+```python
+import tracemalloc
+import linecache
+import random
+import numpy as np
+
+def memory_intensive_function(size):
+ data = [random.randint(1, 1000000) for _ in range(size)]
+ sorted_data = sorted(data)
+ mid_index = len(sorted_data) // 2
+ return sorted_data[mid_index]
+
+
+def matrix_multiply(size):
+ # size x size 크기의 무작위 행렬 두 개 생성
+ matrix1 = np.random.rand(size, size)
+ matrix2 = np.random.rand(size, size)
+ # 두 행렬의 곱 계산
+ product = np.dot(matrix1, matrix2)
+ return product
+
+
+def main():
+ tracemalloc.start()
+ x = memory_intensive_function(100000)
+ y = matrix_multiply(100)
+ snapshot = tracemalloc.take_snapshot()
+ snapshot = snapshot.filter_traces((
+ tracemalloc.Filter(True, "**/tracedemo.py"),
+ ))
+ stats = snapshot.statistics("lineno")
+
+ for stat in stats:
+ frame = stat.traceback[0]
+ print(stat)
+ print(linecache.getline(frame.filename, frame.lineno).strip())
+
+ tracemalloc.stop()
+
+
+if __name__ == "__main__":
+ main()
+```
+
+
+## 결과 스크린샷
+
+
diff --git "a/8th_members/\352\271\200\355\225\204\353\252\250/8\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\355\225\204\353\252\250/8\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..10e08fb
--- /dev/null
+++ "b/8th_members/\352\271\200\355\225\204\353\252\250/8\354\243\274\354\260\250.md"
@@ -0,0 +1,3 @@
+# 원하는 라이브러리 import 해보고, 참조 카운트가 어떻게 바뀌는지 출력해보기
+
+
diff --git "a/8th_members/\352\271\200\355\225\204\353\252\250/9\354\243\274\354\260\250.md" "b/8th_members/\352\271\200\355\225\204\353\252\250/9\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..a638239
--- /dev/null
+++ "b/8th_members/\352\271\200\355\225\204\353\252\250/9\354\243\274\354\260\250.md"
@@ -0,0 +1,44 @@
+```python3
+import time
+from multiprocessing import Pool
+
+# Define the CPU-bound task
+
+
+def cpu_bound_task(n):
+ result = 1
+ for i in range(1, n + 1):
+ result *= i
+ return result
+
+# Single-process version
+
+
+def single_process(tasks):
+ results = []
+ for task in tasks:
+ results.append(cpu_bound_task(task))
+ return results
+
+# Multiprocessing version
+
+
+def multi_process(tasks):
+ with Pool() as pool:
+ results = pool.map(cpu_bound_task, tasks)
+ return results
+
+
+if __name__ == "__main__":
+ tasks = [100000 + i for i in range(20)] # Define tasks
+
+ start = time.time()
+ single_process(tasks)
+ print("Single-process time:", time.time() - start)
+
+ start = time.time()
+ multi_process(tasks)
+ print("Multiprocessing time:", time.time() - start)
+```
+
+
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/10\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\204\261\353\262\224/10\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..93e100a
--- /dev/null
+++ "b/8th_members/\354\235\264\354\204\261\353\262\224/10\354\243\274\354\260\250.md"
@@ -0,0 +1,39 @@
+# 10주차
+
+## Port Scanner: Multi Threading - 멀티스레딩 + 스레드 개수 제한
+```python
+import time
+import socket
+from concurrent.futures import ThreadPoolExecutor, as_completed
+
+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=10) as executor:
+ futures = [executor.submit(check_port, host, port) for port in range(100, 200)]
+
+ 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()
+```
+
\ No newline at end of file
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/11\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\204\261\353\262\224/11\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..5a23a2b
--- /dev/null
+++ "b/8th_members/\354\235\264\354\204\261\353\262\224/11\354\243\274\354\260\250.md"
@@ -0,0 +1,74 @@
+# 11주차
+## str type 분석
+```c
+Objects/object.c
+
+INIT_TYPE(&PyUnicode_Type, "str");
+```
+- python의 str은 PyUnicode_Type 을 서용함
+
+```c
+Objects\unicodeobject.c
+
+PyTypeObject PyUnicode_Type = {
+ PyVarObject_HEAD_INIT(&PyType_Type, 0)
+ "str", /* tp_name */
+ sizeof(PyUnicodeObject), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ /* Slots */
+ (destructor)unicode_dealloc, /* tp_dealloc */
+ 0, /* tp_vectorcall_offset */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_as_async */
+ unicode_repr, /* tp_repr */
+ &unicode_as_number, /* tp_as_number */
+ &unicode_as_sequence, /* tp_as_sequence */
+ &unicode_as_mapping, /* tp_as_mapping */
+ (hashfunc) unicode_hash, /* tp_hash*/
+ 0, /* tp_call*/
+ (reprfunc) unicode_str, /* tp_str */
+ PyObject_GenericGetAttr, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
+ Py_TPFLAGS_UNICODE_SUBCLASS, /* tp_flags */
+ unicode_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ PyUnicode_RichCompare, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ unicode_iter, /* tp_iter */
+ 0, /* tp_iternext */
+ unicode_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ &PyBaseObject_Type, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ unicode_new, /* tp_new */
+ PyObject_Del, /* tp_free */
+};
+```
+- type 정의
+
+```c
+/* Strings allocated through PyUnicode_FromUnicode(NULL, len) use the
+ PyUnicodeObject structure. The actual string data is initially in the wstr
+ block, and copied into the data block using _PyUnicode_Ready. */
+typedef struct {
+ PyCompactUnicodeObject _base;
+ union {
+ void *any;
+ Py_UCS1 *latin1;
+ Py_UCS2 *ucs2;
+ Py_UCS4 *ucs4;
+ } data; /* Canonical, smallest-form Unicode buffer */
+} PyUnicodeObject;
+
+```
+- 구조체
\ No newline at end of file
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/12\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\204\261\353\262\224/12\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..c9ec885
--- /dev/null
+++ "b/8th_members/\354\235\264\354\204\261\353\262\224/12\354\243\274\354\260\250.md"
@@ -0,0 +1,73 @@
+# 12주차
+
+## unittest 사용해보기
+```python
+import unittest
+
+def normalize(input_str):
+ """
+ 인풋으로 받는 스트링에서 아래의 규칙으로 정규화된 스트링을 반환하는 함수입니다.
+ * 모든 단어들은 소문자로 변환됨
+ * 띄어쓰기는 한칸으로 되도록 함
+ * 앞뒤 필요없는 띄어쓰기는 제거함
+ Parameters:
+ input_str (string): 영어로 된 대문자, 소문자, 띄어쓰기, 문장부호, 숫자로 이루어진 string
+ ex - " EXTRA SPACE "
+ Returns:
+ normalized_string (string): 정규회된 string
+ ex - 'extra space'
+ Examples:
+ >>> import text_processing as tp
+ >>> example = " EXTRA SPACE "
+ >>> tp.normalize(example)
+ 'extra space'
+ """
+ out = input_str.lower()
+ out = out.strip()
+ while ' ' in out:
+ out = out.replace(' ', ' ')
+ return out
+
+class TestTextNormalize(unittest.TestCase):
+ def test_normalize(self):
+ test_str = "This is an example."
+ pred = normalize(test_str)
+ self.assertEqual(pred, "this is an example.")
+
+ test_str = " EXTRA SPACE "
+ pred = normalize(test_str)
+ self.assertEqual(pred, "extra space")
+
+ test_str = "THIS IS ALL CAPS!!"
+ pred = normalize(test_str)
+ self.assertEqual(pred, "this is all caps!!")
+
+ test_str = " "
+ pred = normalize(test_str)
+ self.assertEqual(pred, "")
+
+ test_str = "this is all lower space..."
+ pred = normalize(test_str)
+ self.assertEqual(pred, "this is all lower space...")
+
+ test_str = " H e L l O !"
+ pred = normalize(test_str)
+ self.assertEqual(pred, "h e l l o !")
+
+ test_str = ""
+ pred = normalize(test_str)
+ self.assertEqual(pred, "")
+
+ test_str = "........"
+ pred = normalize(test_str)
+ self.assertEqual(pred, "........")
+
+ test_str = "EX A M P LE"
+ pred = normalize(test_str)
+ self.assertEqual(pred, "ex a m p le")
+
+ test_str = "AbCd EfGh IjKl MnOp"
+ pred = normalize(test_str)
+ self.assertEqual(pred, "abcd efgh ijkl mnop")
+```
+
\ No newline at end of file
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/1\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\204\261\353\262\224/1\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..8310493
--- /dev/null
+++ "b/8th_members/\354\235\264\354\204\261\353\262\224/1\354\243\274\354\260\250.md"
@@ -0,0 +1,8 @@
+# 1주차
+
+## CPython 소스코드 다운로드 및 환경 구성
+
+
+## 스터디에서 이루고 싶은 목표, 함께 하게 된 소감
+- 이번 스터디를 통해서 Python 동작 원리를 조금 더 깊게 이해해서, 사용하고 있는 코드들의 성능을 최적화 하고 싶습니다.
+- CPython을 공부하려는 사람들을 위한 좋은 가이드북을 만들면 좋겠습니다.
\ No newline at end of file
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/2\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\204\261\353\262\224/2\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..ac12377
--- /dev/null
+++ "b/8th_members/\354\235\264\354\204\261\353\262\224/2\354\243\274\354\260\250.md"
@@ -0,0 +1,8 @@
+# 2주차
+
+## 컴파일
+
+
+## 새로운 문법 생성
+- pass 키워드에 LGTM을 추가
+
\ No newline at end of file
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/3\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\204\261\353\262\224/3\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..69c7bdd
--- /dev/null
+++ "b/8th_members/\354\235\264\354\204\261\353\262\224/3\354\243\274\354\260\250.md"
@@ -0,0 +1,10 @@
+# 3주차
+
+## CPython 인터프리터 실행 과정 코드 레벨로 이해하기
+
+
+## 파이썬 세션에서 runtime flag 확인하기
+
+
+## importlib.util.find_spec 함수를 사용해서 원하는 모듈의 위치를 찾아보기
+
\ No newline at end of file
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/4\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\204\261\353\262\224/4\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..9628d79
--- /dev/null
+++ "b/8th_members/\354\235\264\354\204\261\353\262\224/4\354\243\274\354\260\250.md"
@@ -0,0 +1,8 @@
+# 4주차
+
+## instaviz로 AST 시각화 해보기
+
+
+
+## ‘거의 같음’ 연산자를 문법에 추가하고 AlE타입의 객체를 출력 해보기
+
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/5\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\204\261\353\262\224/5\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..900e900
--- /dev/null
+++ "b/8th_members/\354\235\264\354\204\261\353\262\224/5\354\243\274\354\260\250.md"
@@ -0,0 +1,4 @@
+# 5주차
+
+## ‘거의 같음’ 연산자 구현
+
\ No newline at end of file
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/6\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\204\261\353\262\224/6\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..598fa53
--- /dev/null
+++ "b/8th_members/\354\235\264\354\204\261\353\262\224/6\354\243\274\354\260\250.md"
@@ -0,0 +1,4 @@
+# 6주차
+
+## 파이썬에서 프레임 객체 접근해보기
+
\ No newline at end of file
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/7\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\204\261\353\262\224/7\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..d936efc
--- /dev/null
+++ "b/8th_members/\354\235\264\354\204\261\353\262\224/7\354\243\274\354\260\250.md"
@@ -0,0 +1,4 @@
+# 7주차
+
+## tracemalloc 모듈 사용해서 직접 메모리 확인해보기
+
\ No newline at end of file
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/8\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\204\261\353\262\224/8\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..029fcec
--- /dev/null
+++ "b/8th_members/\354\235\264\354\204\261\353\262\224/8\354\243\274\354\260\250.md"
@@ -0,0 +1,4 @@
+# 8주차
+
+## 원하는 라이브러리 import 해보고, 참조 카운트가 어떻게 바뀌는지 출력해보기
+
\ No newline at end of file
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/9\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\204\261\353\262\224/9\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..db1b902
--- /dev/null
+++ "b/8th_members/\354\235\264\354\204\261\353\262\224/9\354\243\274\354\260\250.md"
@@ -0,0 +1,31 @@
+# 9주차
+
+## 간단한 CPU-Bound Job을 만들고 멀티프로세싱을 적용한 코드와 아닌 코드를 작성해보기
+```python
+import time
+import multiprocessing as mp
+
+pool_size = 10
+
+def cpu_task(a=1):
+ time.sleep(1)
+
+def no_multiprocessing_run():
+ start_time = time.time()
+ for _ in range(pool_size):
+ cpu_task()
+ end_time = time.time()
+ print(f"Duration without multiprocessing: {end_time - start_time} seconds")
+
+def multiprocessing_run():
+ start_time = time.time()
+ with mp.Pool(pool_size) as p:
+ p.map(cpu_task, range(pool_size))
+ end_time = time.time()
+ print(f"Duration with multiprocessing: {end_time - start_time} seconds")
+
+if __name__ == '__main__':
+ no_multiprocessing_run()
+ multiprocessing_run()
+```
+
\ No newline at end of file
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/image/10_week.png" "b/8th_members/\354\235\264\354\204\261\353\262\224/image/10_week.png"
new file mode 100644
index 0000000..f7d405c
Binary files /dev/null and "b/8th_members/\354\235\264\354\204\261\353\262\224/image/10_week.png" differ
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/image/12_week.png" "b/8th_members/\354\235\264\354\204\261\353\262\224/image/12_week.png"
new file mode 100644
index 0000000..356ebab
Binary files /dev/null and "b/8th_members/\354\235\264\354\204\261\353\262\224/image/12_week.png" differ
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/image/1_week.png" "b/8th_members/\354\235\264\354\204\261\353\262\224/image/1_week.png"
new file mode 100644
index 0000000..244e4d7
Binary files /dev/null and "b/8th_members/\354\235\264\354\204\261\353\262\224/image/1_week.png" differ
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/image/2_week_build.png" "b/8th_members/\354\235\264\354\204\261\353\262\224/image/2_week_build.png"
new file mode 100644
index 0000000..5c8edc1
Binary files /dev/null and "b/8th_members/\354\235\264\354\204\261\353\262\224/image/2_week_build.png" differ
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/image/2_week_pass.png" "b/8th_members/\354\235\264\354\204\261\353\262\224/image/2_week_pass.png"
new file mode 100644
index 0000000..62b2413
Binary files /dev/null and "b/8th_members/\354\235\264\354\204\261\353\262\224/image/2_week_pass.png" differ
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/image/3_week_find_spec.png" "b/8th_members/\354\235\264\354\204\261\353\262\224/image/3_week_find_spec.png"
new file mode 100644
index 0000000..514acd5
Binary files /dev/null and "b/8th_members/\354\235\264\354\204\261\353\262\224/image/3_week_find_spec.png" differ
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/image/3_week_interpreter.png" "b/8th_members/\354\235\264\354\204\261\353\262\224/image/3_week_interpreter.png"
new file mode 100644
index 0000000..d8fea35
Binary files /dev/null and "b/8th_members/\354\235\264\354\204\261\353\262\224/image/3_week_interpreter.png" differ
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/image/3_week_runtime_flag.png" "b/8th_members/\354\235\264\354\204\261\353\262\224/image/3_week_runtime_flag.png"
new file mode 100644
index 0000000..9c9fcbc
Binary files /dev/null and "b/8th_members/\354\235\264\354\204\261\353\262\224/image/3_week_runtime_flag.png" differ
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/image/4_week_1-1.png" "b/8th_members/\354\235\264\354\204\261\353\262\224/image/4_week_1-1.png"
new file mode 100644
index 0000000..5145259
Binary files /dev/null and "b/8th_members/\354\235\264\354\204\261\353\262\224/image/4_week_1-1.png" differ
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/image/4_week_1-2.png" "b/8th_members/\354\235\264\354\204\261\353\262\224/image/4_week_1-2.png"
new file mode 100644
index 0000000..366b74d
Binary files /dev/null and "b/8th_members/\354\235\264\354\204\261\353\262\224/image/4_week_1-2.png" differ
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/image/4_week_2.png" "b/8th_members/\354\235\264\354\204\261\353\262\224/image/4_week_2.png"
new file mode 100644
index 0000000..8e8b41d
Binary files /dev/null and "b/8th_members/\354\235\264\354\204\261\353\262\224/image/4_week_2.png" differ
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/image/5_week.png" "b/8th_members/\354\235\264\354\204\261\353\262\224/image/5_week.png"
new file mode 100644
index 0000000..6172603
Binary files /dev/null and "b/8th_members/\354\235\264\354\204\261\353\262\224/image/5_week.png" differ
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/image/6_week.png" "b/8th_members/\354\235\264\354\204\261\353\262\224/image/6_week.png"
new file mode 100644
index 0000000..804524b
Binary files /dev/null and "b/8th_members/\354\235\264\354\204\261\353\262\224/image/6_week.png" differ
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/image/7_week.png" "b/8th_members/\354\235\264\354\204\261\353\262\224/image/7_week.png"
new file mode 100644
index 0000000..c5a72da
Binary files /dev/null and "b/8th_members/\354\235\264\354\204\261\353\262\224/image/7_week.png" differ
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/image/8_week.png" "b/8th_members/\354\235\264\354\204\261\353\262\224/image/8_week.png"
new file mode 100644
index 0000000..a87a470
Binary files /dev/null and "b/8th_members/\354\235\264\354\204\261\353\262\224/image/8_week.png" differ
diff --git "a/8th_members/\354\235\264\354\204\261\353\262\224/image/9_week.png" "b/8th_members/\354\235\264\354\204\261\353\262\224/image/9_week.png"
new file mode 100644
index 0000000..a889d10
Binary files /dev/null and "b/8th_members/\354\235\264\354\204\261\353\262\224/image/9_week.png" differ
diff --git "a/8th_members/\354\235\264\354\232\251\354\232\260/10\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\232\251\354\232\260/10\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..76fecde
--- /dev/null
+++ "b/8th_members/\354\235\264\354\232\251\354\232\260/10\354\243\274\354\260\250.md"
@@ -0,0 +1,47 @@
+### 멀티 쓰레드 요약
+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`
+ 매크로가 실제 구현에 사용된다. 내장 패키지 구석구석에서 해당 구현을 확인할 수 있다.
+5. Thread는 프로세스와 메모리를 공유하기 때문에
+ 프로세스를 새로 생성하는 것 보다는 오버헤드가 적다.
+
+
+
+
+#### 코루틴
+→ 실행을 중단하고 재개할 수 있는 함수, 특정 시점에서 작업을 중단하고 다른 작업을 수행할 수 있다.
+→ 호출자와 호출된 함수 사이의 제어 흐름이 상호작용
+→ 비동기 프로그래밍에서 사용
+
+
+
+#### 네이티브 코루틴
+- 함수 앞에 `async` 키워드를 명시하여 코루틴을 반환함을 명시
+- `asyncio.run()`을 통해 코루틴 객체 생성
+- 실행 순서
+ - 1) 새 이벤트 루프를 시작
+ - 2) 코루틴 객체를 Task로 감싸기
+ - 3) Task가 완료될 때 까지 실행할 CallBack을 설정
+ - 4) Task가 완료될 때 까지 루프를 반복
+ - 5) 결과 반환
+
+장점
+- 여러 코루틴 동시 실행 가능
+- 코루틴 객체를 다른 함수의 인자로 사용 가능
+ - 코루틴 객체를 서로 연결하여 연쇄적으로 처리하거나 순차적으로 생성 가능
+
+※ 이벤트 루프
+- 비동기 코드를 연결하는 접착제 역할
+
+
+
+
+### Port Scanner 예제 실행
+
+좌: 멀티 쓰레드, 우: 멀티 쓰레드(쓰레드 수 제한)
+
+
\ No newline at end of file
diff --git "a/8th_members/\354\235\264\354\232\251\354\232\260/12\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\232\251\354\232\260/12\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..a4d5cda
--- /dev/null
+++ "b/8th_members/\354\235\264\354\232\251\354\232\260/12\354\243\274\354\260\250.md"
@@ -0,0 +1,96 @@
+### Test Case 만들기
+```
+Project
+ |- tests
+ |- __init__.py
+ |- test_normalize.py
+ |- text_processing.py
+```
+
+
+
+
+``` python
+def normalize(input_str):
+ out = input_str.lower()
+ out = out.strip()
+ while ' ' in out:
+ out = out.replace(' ', ' ')
+ return out
+```
+
+
+
+``` python
+import unittest
+from text_processing import normalize
+
+
+class TestTextNormalize(unittest.TestCase):
+ def test_normalize(self):
+ test_str = "This is an example."
+ pred = normalize(test_str)
+ self.assertEqual(pred, "this is an example.")
+
+ test_str = " EXTRA SPACE "
+ pred = normalize(test_str)
+ self.assertEqual(pred, "extra space")
+
+ test_str = "THIS IS ALL CAPS!!"
+ pred = normalize(test_str)
+ self.assertEqual(pred, "this is all caps!!")
+
+ test_str = " "
+ pred = normalize(test_str)
+ self.assertEqual(pred, "")
+
+ test_str = "this is all lower space..."
+ pred = normalize(test_str)
+ self.assertEqual(pred, "this is all lower space...")
+
+ test_str = " H e L l O !"
+ pred = normalize(test_str)
+ self.assertEqual(pred, "h e l l o !")
+
+ test_str = ""
+ pred = normalize(test_str)
+ self.assertEqual(pred, "")
+
+ test_str = "........"
+ pred = normalize(test_str)
+ self.assertEqual(pred, "........")
+
+ test_str = "EX A M P LE"
+ pred = normalize(test_str)
+ self.assertEqual(pred, "ex a m p le")
+
+ test_str = "AbCd EfGh IjKl MnOp"
+ pred = normalize(test_str)
+ self.assertEqual(pred, "abcd efgh ijkl mnop")
+```
+
+
+
+
+
+```
+# tests 디렉토리 내 모든 테스트 파일 실행
+$ python -m unittest discover tests -v
+```
+
+```
+# 특정 테스트 파일 실행
+$ python -m unittest tests.test_normalize -v
+```
+
+```
+# 특정 테스트 스위트 실행
+$ python -m unittest tests.test_normalize.TestTextNormalize
+```
+
+```
+# 특정 테스트 케이스 실행
+$ python -m unittest tests.test_normalize.TestTextNormalize.test_normalize
+```
+
+
diff --git "a/8th_members/\354\235\264\354\232\251\354\232\260/13\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\232\251\354\232\260/13\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..ae52e37
--- /dev/null
+++ "b/8th_members/\354\235\264\354\232\251\354\232\260/13\354\243\274\354\260\250.md"
@@ -0,0 +1,31 @@
+### 1. pdb 실행
+
+|shortcut|설명|
+|---|---|
+|`l`|주변 소스코드를 출력하며 현재 라인이 화살표로 나옴|
+|`n`|다음 문장으로 이동|
+|`s`|Step Into|
+|`w`|함수의 call stack 출력|
+|`s`|현재 함수의 argument들을 출력|
+
+
+
+
+
+### 2. CProfile 예제 파일 직접 생성해서 실행해보기
+
+``` bash
+$ python -m cProfile -o output.pstat portscanner_multithread.py
+```
+
+
+
+
+
+
+``` bash
+$ pip install snakeviz
+$ python -m snakeviz output.pstat
+```
+
+
\ No newline at end of file
diff --git "a/8th_members/\354\235\264\354\232\251\354\232\260/1\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\232\251\354\232\260/1\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..52c4461
--- /dev/null
+++ "b/8th_members/\354\235\264\354\232\251\354\232\260/1\354\243\274\354\260\250.md"
@@ -0,0 +1,83 @@
+# 1주차
+### 목차
+1. [CPython 소스코드 다운로드 및 환경 구성](#cpython-소스코드-다운로드-및-환경-구성)
+ 1-1. [VSCode 코드 구성하기](#vscode-코드-구성하기)
+ 1-2. [작업과 실행 파일 설정](#작업과-실행-파일-설정)
+ 1-3. [환경 구성 켑처](#환경-구성-결과)
+2. [스터디에서 이루고 싶은 목표 & 함께 하게 된 소감](#스터디에서-이루고-싶은-목표--함께-하게-된-소감)
+
+
+
+## CPython 소스코드 다운로드 및 환경 구성
+### VSCode 코드 구성하기
+VSCode는 온라인 플러그인 마켓 플레이스를 제공하는 확장 가능한 코드 편집기이다.
+통합 git 인터페이스를 제공하며, C와 파이썬 모두를 지원하기 때문에 CPython을 위한 좋은 선택이 될 수 있다.
+
+Extensions 패널에 접근하여 `ms-vscode.cpptools`와 같은 고유 식별자로 확장을 검색할 수 있다.
+C/C++ for Visual Studio Code 를 클릭하여 다운로드하도록 하자.
+
+
+또한 권장되는 확장은 아래와 같다.
+|Extension|Description|
+|--|--|
+|C/C++ (`ms-vscode.cpptools`)|인텔리센스, 디버깅, 코드 강조 등의 C/C++ 지원을 제공|
+|Python (`ms-python.python`)|파이썬 코드 편집, 디버깅, 탐색 등의 파이썬 지원을 제공|
+|reStructuredText (`lextudio.restructuredtext`)|CPython 문서에 사용되는 reStructuredtext에 대한 지원을 제공|
+|Task Explorer (`spmeesseman.vscode-taskexplorer`)|make 작업을 편리하게 실행할 수 있는 Task Explorer 패널을 Explorer 탭 안에 추가|
+
+
+
+
+### 작업과 실행 파일 설정
+VSCode는 작업 디렉터리 내부의 `.vscode` 폴더를 작업 공간 설정에 사용한다.
+폴더가 없으면 새로 만들고 다음 파일들을 추가해서 작업 공간을 설정할 수 있다.
+
+- 프로젝트 실행 명령에 대한 `tasks.json`
+- 디버거를 위한 `launch.json`
+- 기타 플러그인을 위한 파일
+
+`tasks.json` 파일에 작업을 등록할 수 있다.
+
+```json
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build",
+ "type": "shell",
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ },
+ "windows": {
+ "command": "PCBuild/build.bat",
+ "args": ["-p", "x64", "-c", "Debug"]
+ },
+ "linux": {
+ "command": "make -j2 -s"
+ },
+ "osx": {
+ "command": "make -j2 -s"
+ }
+ }
+ ]
+}
+```
+
+
+
+### 환경 구성 결과
+
+
+
+
+
+
+## 스터디에서 이루고 싶은 목표 & 함께 하게 된 소감
+이번 스터디를 통해 Python 언어를 더 깊이 있게 이해하고,
+이 스터디를 마친 후에는 Python을 원하는 기능 및 스펙에 맞게
+입맛대로 커스텀 할 수 있는 엔지니어로 성장하기 위한 초석을 다지고 싶습니다.
+
+이번 CPython 스터디에 참여하여 관심사가 비슷한 분들과 함께 할 수 있게 되어 기쁘게 생각하고 있습니다.
+모든 분들이 이번 스터디를 통해 각자 성취, 혹은 원하는 바를 이룰 수 있었으면 좋겠습니다.
+앞으로 스터디 기간 동안 잘 부탁드립니다. 감사합니다🙂
\ No newline at end of file
diff --git "a/8th_members/\354\235\264\354\232\251\354\232\260/2\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\232\251\354\232\260/2\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..5e49d62
--- /dev/null
+++ "b/8th_members/\354\235\264\354\232\251\354\232\260/2\354\243\274\354\260\250.md"
@@ -0,0 +1,98 @@
+# 2주차
+### 목차
+1. [macOS에서 CPython 컴파일하기](#macos에서-cpython-컴파일하기)
+2. [문법 다시 생성하기 (pass 키워드 변경해보기) 부분 원하는 키워드 다른걸로 바꿔보기 (결과 화면 캡쳐)](#문법-다시-생성하기-pass-키워드-변경해보기-부분-원하는-키워드-다른걸로-바꿔보기-결과-화면-캡쳐)
+
+
+
+### macOS에서 CPython 컴파일하기
+macOS에서 CPython을 컴파일하려면 어플리케이션과 라이브러리가 추가로 필요하다.
+그 중 Command Line Tools는 필수적인 C 컴파일러 툴킷으로
+터미널로 설치하고 앱스토어에서 업데이트 할 수 있다.
+
+다음 명령을 실행해 C 컴파일러 툴킷을 설치하면
+git, make, GNU C 컴파일러 등의 도구들이 설치된다.
+
+```
+$ xcode-select --install
+```
+
+
+
+
+
+PyPI에서 패키지를 받아 설치하려면 OpenSSL도 필요하다.
+HomeBrew로 macOS에 간단하게 OpenSSL을 설치할 수 있다.
+
+brew install 명령으로 CPython에 필요한 의존성을 설치하자.
+
+```
+$ brew install openssl xz zlib gdbm sqlite
+```
+
+
+
+zlib이 설치된 위치를 지정하고 구성 작업을 실행하자.
+이 작업은 한 번만 실행하면 된다.
+
+```
+# cd ~/your/cpython/path
+
+$ CPPFLAGS="-I$(brew --prefix zlib)/include" \
+LDFLAGS="-L$(brew --prefix zlib)/lib" \
+./configure --with-openssl=$(brew --prefix openssl) \
+--with-pydebug
+```
+
+
+
+./configure가 저장소 최상단에 위치한 Makefile이 빌드 과정을 자동화한다.
+다음 명령어로 CPython 바이너리를 빌드하자.
+
+```
+$ make -j2 -s
+```
+
+
+
+### 문법 다시 생성하기 (pass 키워드 변경해보기) 부분 원하는 키워드 다른걸로 바꿔보기 (결과 화면 캡쳐)
+CPython3.9 부터 도입된 새로운 PEG 생성기인 pegen을 테스트해 보기 위해 문법 일부를 변경해보자.
+Grammar → python.gram에서 간단한 정의인 small_stmt를 찾아 수정해보자.
+※ small_stmt: 단일 독립 실행 가능한 구성요소
+
+```
+small_stmt[stmt_ty] (memo):
+ | assignment
+ | e=star_expressions { _Py_Expr(e, EXTRA) }
+ | &'return' return_stmt
+ | &('import' | 'from') import_stmt
+ | &'raise' raise_stmt
+ | 'pass' { _Py_Pass(EXTRA) }
+ | &'del' del_stmt
+ | &'yield' yield_stmt
+ | &'assert' assert_stmt
+ | ('break' | 'heartbreaker') { _Py_Break(EXTRA) }
+ | 'continue' { _Py_Continue(EXTRA) }
+ | &'global' global_stmt
+ | &'nonlocal' nonlocal_stmt
+```
+
+
+
+CPython의 자동 문법 재생성 스크립트를 사용해 문법 파일을 다시 빌드하자.
+```
+$ make regen-pegen
+```
+
+
+
+CPython을 새 파서 테이블과 같이 컴파일하면 새 문법이 추가된다.
+이전 장에서 소개한 운영 체제 별 컴파일 방법을 그대로 실행하면 된다.
+
+```
+$ make -j2 -s
+```
+
+
+
+
\ No newline at end of file
diff --git "a/8th_members/\354\235\264\354\232\251\354\232\260/3\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\232\251\354\232\260/3\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..fe95c4a
--- /dev/null
+++ "b/8th_members/\354\235\264\354\232\251\354\232\260/3\354\243\274\354\260\250.md"
@@ -0,0 +1,207 @@
+# 3주차
+### 목차
+1. [구성 상태](#구성-상태)
+ - [딕셔너리 초기화 구성](#딕셔너리-초기화-구성)
+ - [연관된 소스 파일 목록](#연관된-소스-파일-목록)
+ - [런타임 구성 구조체](#런타임-구성-구조체)
+ - [명령줄로 런타임 구성 설정하기](#명령줄로-런타임-구성-설정하기)
+2. [빌드 구성](#빌드-구성)
+3. [입력에서 모듈 만들기](#입력에서-모듈-만들기)
+ - [연관된 소스 파일 목록](#연관된-소스-파일-목록)
+4. [3주차 미션](#3주차-미션)
+ - [CPython 인터프리터 실행 과정 코드 레벨로 이해하기](#cpython-인터프리터-실행-과정-코드-레벨로-이해하기)
+ - [파이썬 세션에서 Runtime Flag 확인하기](#파이썬-세션에서-runtime-flag-확인하기)
+ - [importlib.util.find_spec 함수를 사용해서 원하는 모듈의 위치를 찾아보기](#importlibutilfind_spec-함수를-사용해서-원하는-모듈의-위치를-찾아보기)
+
+
+
+
+### 구성 상태
+CPython 런타임은 사용자 옵션 과 구성(Configuration) 을 설정한다.
+특히 **Configuration**은 세 부분으로 나뉘어 있다.
+
+- `PyPreConfig` 딕셔너리 초기화 Configuration
+- `PyConfig` 런타임 Configuration
+- CPython 인터프리터에 같이 컴파일된 Configuration
+
+여기서 `PyPreconfig`, `PyConfig` 구조체는
+`Include/cpython/initconfig.h`에서 정의한다.
+
+
+
+#### 딕셔너리 초기화 구성
+**딕셔너리 초기화 Configuration**은 사용자 환경 또는 운영체제와 관련된 Configuration 이기에
+런타임 Configuration과는 구분된다.
+
+다음은 `PyPreConfig`의 세 가지 주요 기능이다.
+1. 파이썬 메모리 할당자 설정하기
+2. `LC_CTYPE` 로켈(locale)을 시스템 또는 사용자 선호 로켈로 구성
+3. UTF-8 모드로 설정
+
+> Locale : 특정 지역, 국가, 또는 문화권에서 사용하는 언어 등과 같은 지역설정을 의미함. (ex. `ko-KR`)
+
+
+`PyPreConfig` 구조체는 다음과 같은 `int` 타입 필드들을 포함한다.
+
+| 필드 | 설명 |
+| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `allocator` | `PYMEM_ALLOCATOR_MALLOC`와 같은 값을 사용해서 메모리 할당자를 선택한다. `./configure --help`를 실행해 메모리 할당자에 대한 추가 정보를 얻을 수 있다. |
+| `configure_locate` | `LC_CTYPE` 로켈을 사용자 선호 로캘로 설정한다. `0`으로 설정하면 `coerce_c_locale`과 `coerce_c_locale_warn`을 `0`으로 설정한다. |
+| `coerce_c_locale` | `2`로 설정하면 C 로켈을 강제로 적용한다. `1`로 설정하면 `LC_CTYPE`을 읽은 후 강제로 적용할지 결정한다. |
+| `coerce_c_locale_warn` | `0`이 아니면 C 로켈이 강제로 적용될 때 경고가 발생한다. |
+| `dev_mode` | 개발 모드를 활성화한다. |
+| `isolated` | 격리 모드를 활성화한다. `sys.path`에 스크립트 디렉터리와 사용자의 사이트 패키지 디렉터리가 포함되지 않는다. |
+| `legacy_windows_fs_encoding` | `0`이 아니면 UTF-8 모드를 비활성화하고 파이썬 파일 시스템 인코딩을 `mbcs`로 설정한다. |
+| `parse_argv` | `0`이 아니면 명령줄 인자를 사용한다. |
+| `use_environment` | `0`보다 큰 값이면 환경 변수를 사용한다. |
+| `utf8_mode` | `0`이 아니면 UTF-8 모드를 활성화한다. |
+
+
+
+`Include/cpython/initconfig.h`에서 `PyPreConfig` 구조체를 찾아볼 수 있다.
+
+
+
+#### 연관된 소스 파일 목록
+다음은 `PyPreConfig`와 연관된 소스 파일 목록이다.
+
+| 파일 | 용도 |
+| ---------------------- | ------------------------------------------------------- |
+|`Python/initconfig.c` | 시스템 환경에서 불러온 구성을 명령줄 플래그와 결합한다. |
+|`Include/cpython/initconfig.h` | 초기화 구성 구조체를 정의한다. |
+
+
+
+#### 런타임 구성 구조체
+`PyConfig`이라 불리는 런타임 Configuration 구조체는 다음과 같은 값들을 포함한다.
+- '디버그'나 '최적화'와 같은 실행 모드 플래그
+- 스크립트 파일이나 `stdin`, 모듈 등 실행 모드
+- `-X `으로 설정 가능한 확장 옵션
+- 런타임 설정을 위한 환경 변수
+
+런타임 구성 데이터는 CPython 런타임 기능의 활성화 여부를 결정한다.
+
+#### 명령줄로 런타임 구성 설정하기
+파이썬은 다양한 명령줄 인터페이스 옵션을 제공한다.
+예를 들어 파이썬은 상세(verbose) 모드 를 제공한다.
+CPython 디버깅용 기능인 상세 모드는 주로 개발자 대상이다.
+
+`-v` 플래그로 verbose 모드를 활성화하면
+파이썬은 모듈을 로딩할 때 마다 화면에 아래와 같은 메시지를 출력한다.
+
+```
+$ ./python -v -c "print('hello world')"
+...
+# initiallizing zipimport hook
+import zipimport # built in
+# installed zipimport hook
+```
+
+
+
+
+런타임 구성을 설정하는 방법은 다양하기 때문에 우선순위가 정해져있다.
+다음은 verbose 모드에 대한 우선 순위이다.
+1. `config → verbose`의 기본값은 -1 이며 소스 코드에 하드코딩되어 있다.
+2. `PYTHONVERBOSE` 환경 변수를 `config → verbose`를 설정하는 데 사용한다.
+3. 환경 변수가 없으면 기본값인 -1을 사용한다.
+4. `Python/initconfig.c`의 `config_parse_cmdline()`은
+ 명시된 플래그를 사용해 모드를 설정한다.
+5. `_Py_GetGlobalVariablesAsDict()`가 값을 전역 변수 `Py_VerboseFlag`로 복사한다.
+
+
+
+런타임 시, 모든 `PyConfig`(런타임 구성) 값에는 아래와 같은 순서의 우선순위가 적용된다.
+
+
+
+
+
+
+### 빌드 구성
+런타임 구성을 `include/cpython/initconfig.h`에서 정의하듯이
+빌드 구성은 최상위 폴더의 `pyconfig.h`에서 정의한다.
+
+`pyconfig.h`는 macOS나 리눅스용 빌드 과정 중 `./configure` 단계나
+윈도우의 `build.bat` 실행 중에 자동으로 생성된다.
+
+다음 명령으로 빌드 구성을 확인해 볼 수 있다.
+
+```
+./python.exe -m sysconfig
+Platform: "macosx-14.2-arm64"
+Python version: "3.9"
+Current installation scheme: "posix_prefix"
+
+Paths:
+ data = "/usr/local"
+ include = "/Users/wooy0ng/Desktop/playground/practice-cpython/cpython/Include"
+ platinclude = "/Users/wooy0ng/Desktop/playground/practice-cpython/cpython"
+...
+```
+
+빌드 구성 항목들은 컴파일 시에 결정되는 값으로, 바이너리에 링크할 추가 모듈 선택에 사용된다.
+예를 들어 디버거와 계측(instrumentation) 라이브러리, 메모리 할당자는 모두 컴파일 시 결정된다.
+
+세 단계의 구성을 모두 완료하면 CPython 인터프리터는 입력된 텍스트를 코드로 실행할 수 있다.
+
+
+
+
+### 입력에서 모듈 만들기
+코드를 실행하려면 먼저 입력을 모듈로 컴파일해야 한다.
+입력 방식에는 여러 가지가 있다.
+
+- 로컬 파일과 패키지
+- 메모리 파이프나 `stdin`과 같은 I/O 스트림
+- 문자열
+
+읽어 들인 입력은 Parser를 거쳐 컴파일러로 전달된다.
+
+
+
+
+
+
+유연한 입력 방식을 제공하기 위해
+CPython은 CPython 소스 코드의 상당 부분이 Parser의 입력 처리에 사용한다.
+
+
+
+#### 연관된 소스 파일 목록
+다음은 명령줄 인터페이스 처리와 관련된 주요 파일들이다.
+
+| 파일 | 용도 |
+| -------------------- | --------------------------------------------------------------------------------------------- |
+| `Lib/runpy.py` | 파이썬 모듈을 임포트하고 실행하는 표준 라이브러리 모듈 |
+| `Modules/main.c` | 파일이나 모듈, 입력 스트림과 같은 외부 코드 실행을 감싸는 함수 |
+| `Programs/python.c` | 윈도우나 리눅스, macOS에서 python의 진입점이다. `Modules/main.c`를 감싸는 역할만 맡는다. |
+| `Python/pythonrun.c` | 명령줄 입력을 처리하는 내부 C API를 감싸는 함수 |
+
+
+
+### 3주차 미션
+#### CPython 인터프리터 실행 과정 코드 레벨로 이해하기
+1. CPython 소스코드에 적절한 프린트문을 추가해서 인터프리터 실행과정을 출력하기
+a. main
+b. Py_BytesMain
+c. pymain_main
+d. pymain_init
+e. Py_RunMain
+
+
+
+
+
+#### 파이썬 세션에서 Runtime Flag 확인하기
+
+
+
+
+
+
+#### importlib.util.find_spec 함수를 사용해서 원하는 모듈의 위치를 찾아보기
+
\ No newline at end of file
diff --git "a/8th_members/\354\235\264\354\232\251\354\232\260/4\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\232\251\354\232\260/4\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..db891de
--- /dev/null
+++ "b/8th_members/\354\235\264\354\232\251\354\232\260/4\354\243\274\354\260\250.md"
@@ -0,0 +1,58 @@
+# 4주차
+### 목차
+1. [Lexer](#lexer)
+2. [중요한 용어들](#중요한-용어들)
+3. [미션](#미션)
+ - [instaviz로 AST 시각화 해보기](#instaviz로-ast-시각화-해보기)
+ - ["거의 같음" 연산자를 문법에 추가하고 AlE 타입의 객체를 출력해보기](#거의-같음-연산자를-문법에-추가하고-ale타입의-객체를-출력-해보기)
+
+
+
+
+
+### Lexer
+Lexer는 Tokenizer로 쪼개진 토큰들의 의미를 분석하는 역할을 한다.
+Tokenizer를 거치며 의미있는 단위로 쪼개지고,
+Lexer를 거치며 그 의미를 분석하는 과정을 통틀어서 Lexical Analyze라고 한다.
+
+> 예를 들어 return 명령어를 분석해보자.
+> - return이라는 단어에서 r, e, t, u, r, n은 각각 따로 놓고 보면 아무 의미도 가지지 않는다.
+> - Tokenizer를 거치며 return이 의미 있는 단어가 된다. (Tokenizing)
+> - Lexer를 거치며 이 토큰은 무언가를 반환하는 명령어임을 인지하게 된다.
+>
+> 위 과정을 거치며 생성된 오브젝트 {type: 명령어, value: "return", child: []}가 Parser에 전달된다.
+
+
+
+- Tokenizer : 구문에서 의미 있는 요소들을 토큰으로 쪼깸
+- Lexer : 토큰의 의미를 분석
+- Parser : Lexical analyze된 후의 리턴값인 CST를 AST로 바꿔줌
+
+
+
+
+### 중요한 용어들
+
+| 용어 | 설명 |
+|----------| -------------------------------------------------|
+|AST | 파이썬 문법과 문장들에 대한 문맥(Context) 있는 트리 표현 |
+|CST | 토큰과 심볼에 대한 문맥(Context)이 없는 트리 표현 |
+|Parse Tree | CST의 다른 이름 |
+|Token | 심볼의 종류 중 하나 |
+|Tokenizing | 텍스트를 토큰들로 변환하는 과정 |
+|Parsing | 텍스트를 CST나 AST로 변환하는 과정 |
+
+
+
+### 미션
+#### instaviz로 AST 시각화 해보기
+
+
+
+
+
+
+#### ‘거의 같음’ 연산자를 문법에 추가하고 AlE타입의 객체를 출력 해보기
+
+
+
diff --git "a/8th_members/\354\235\264\354\232\251\354\232\260/5\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\232\251\354\232\260/5\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..eb5a559
--- /dev/null
+++ "b/8th_members/\354\235\264\354\232\251\354\232\260/5\354\243\274\354\260\250.md"
@@ -0,0 +1,19 @@
+### 요약
+이번 챕터에서는 Parsing된 파이썬 모듈을
+심볼 테이블과 컴파일러 상태로 변환 하고
+마지막으로 일련의 바이트코드 명령으로 변환 하는 과정에 대해 알아보았다.
+
+
+
+
+바이트코드 명령으로 변환된 모듈을 실행하는 것이
+CPython 인터프리터의 코어 평가 루프가 하는 역할이다.
+
+
+
+### 5주차 미션
+이번 장에서는 컴파일러, 바이트코드 명령, 어셈블러를 다뤘다.
+여기서 06. 렉싱과 파싱에서 문법에 추가한 '거의 같음' 연산자를 지원하도록 CPython을 수정해보자.
+
+
+
diff --git "a/8th_members/\354\235\264\354\232\251\354\232\260/6\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\232\251\354\232\260/6\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..930a967
--- /dev/null
+++ "b/8th_members/\354\235\264\354\232\251\354\232\260/6\354\243\274\354\260\250.md"
@@ -0,0 +1,63 @@
+### 요약
+
+코드 객체는 프레임 객체에 삽입된다.
+파이썬 타입인 프레임 객체는 C와 파이썬 코드 양쪽에서 참조할 수 있다.
+
+
+프레임 객체는 코드 객체의 명령을 실행하는 데 필요한 런타임 데이터를 포함한다.
+런타임 데이터에는 지역, 전역 변수 및 내장 모듈 등이 포함된다.
+
+
+
+
+
+코드 객체에서 PyEval_EvalCode → _PyFrame_New_NoTrack를 거쳐서
+프레임 객체를 반환받게 된다. 그리고 프레임 객체는 다음과 같이 구성된다.
+
+
+
+
+
+
+### TODO: 파이썬에서 프레임 객체 접근해보기
+프레임 객체는 함수가 호출될 때마다 만들어집니다.
+이를 파이썬의 inspect.currentframe() 메소드를 사용하여 접근할 수 있습니다.
+
+다음은 현재 함수의 프레임 객체 상태를 확인할 수 있는 예제 코드이다.
+
+
+
+``` python
+import dis
+import inspect
+
+global_variable01 = 'gvalue01'
+
+def print_frame_info():
+ frame = inspect.currentframe().f_back # 현재 함수의 호출자 프레임
+ try:
+ print("Function Name:", frame.f_code.co_name)
+ print("File Name:", frame.f_code.co_filename)
+ print("Line No:", frame.f_back.f_lineno)
+ print("Local Variables:", list(frame.f_locals.keys()))
+ print("Global Variables:", list(frame.f_globals.keys()))
+ # dis.dis(frame.f_code) # 역 디스어셈블러
+ print("\n\n")
+ finally:
+ del frame
+
+def sample01():
+ sample01_variable = 1
+ print_frame_info()
+
+def sample02():
+ sample02_variable = 2
+ print_frame_info()
+
+sample01()
+sample02()
+```
+
+
+
+
diff --git "a/8th_members/\354\235\264\354\232\251\354\232\260/7\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\232\251\354\232\260/7\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..78e7460
--- /dev/null
+++ "b/8th_members/\354\235\264\354\232\251\354\232\260/7\354\243\274\354\260\250.md"
@@ -0,0 +1,36 @@
+### 미션 - tracemalloc 모듈을 사용해서 직접 메모리 확인해보기
+- tracemalloc 모듈을 통해 객체 할당자의 메모리 할당 동작 디버깅이 가능하다.
+- tracemalloc 모듈은 객체 할당 위치, 할당된 메모리 블록 개수 등을 제공해준다.
+- 코드 실행 시 사용되는 메모리 양을 예상, 메모리 누수를 감지할 수 있다.
+
+python 실행 시 `-X tracemalloc=[추적할 프레임 수]` 옵션 추가하여 실행시킬 수 있다.
+- `take_snapshot()`: 스냅샷 인스턴스 생성
+- `compare_to()`: 호출 → 여러 스냅샷 비교
+
+
+
+
+``` python
+import tracemalloc
+
+
+tracemalloc.start()
+value = "1"
+string_value = [value for _ in range(1000)]
+float_value = [float(value) for _ in range(1000)]
+int_value = [int(value) for _ in range(1000)]
+
+# 스냅샷 시작
+snapshot1 = tracemalloc.take_snapshot()
+snapshot1 = snapshot1.filter_traces([tracemalloc.Filter(True, "**/stat_*.py")])
+stats = snapshot1.statistics("lineno")
+
+for stat in stats:
+ print(stat)
+ print(stat.traceback.format(), end='\n\n')
+
+# 스냅샷 종료
+tracemalloc.stop()
+```
+
+
\ No newline at end of file
diff --git "a/8th_members/\354\235\264\354\232\251\354\232\260/9\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\232\251\354\232\260/9\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..864a09e
--- /dev/null
+++ "b/8th_members/\354\235\264\354\232\251\354\232\260/9\354\243\274\354\260\250.md"
@@ -0,0 +1,88 @@
+### 멀티 프로세싱 요약
+**장점**
+`multiprocessing`은 확장 가능한 파이썬용 병렬 실행 API를 제공한다.
+프로세스 간 데이터 공유가 가능하며, CPU 집약적인 작업을 병렬로 쪼개서
+멀티코어 또는 멀티 CPU 컴퓨터의 장점을 활용할 수도 있다.
+
+**단점**
+단, I/O 집약적인 작업의 경우에는 멀티 프로세싱이 적합하지 않다.
+예를 들어 Worker 프로세스 4개를 Spawn 하고 같은 파일을 읽고 쓰는 경우,
+한 프로세스가 작업하는 동안 나머지 프로세스 3개는 Lock 해제될 때 까지 기다려야 한다.
+
+멀티프로세싱은 새로운 파이썬 인터프리터를 시작하는 데 필요한 시간과 처리 오버헤드로 인해
+시간 소요가 얼마 되지 않는 작업에는 적합하지 않다.
+
+
+
+→ I/O 작업과 시간 소요가 얼마되지 않는 작업에 대해서는 멀티 쓰레딩 방식이 더 적합하다.
+
+
+
+
+### CPU Bound, Unbound 예제 실행
+#### CPU Bound
+
+``` python
+import time
+from typing import List
+
+def compute_squares(numbers: List[int]) -> List[int]:
+ return [x**2 for x in numbers]
+
+
+def no_multiprocessing():
+ numbers = range(1, 10000000)
+ start_time = time.time()
+ results = compute_squares(numbers)
+ end_time = time.time()
+
+ print(f"Duration without multiprocessing: {end_time - start_time} seconds")
+
+if __name__ == '__main__':
+ no_multiprocessing()
+```
+
+
+
+
+
+
+#### CPU Unbound
+
+``` python
+import time
+import multiprocessing as mp
+from typing import List
+
+def compute_squares(numbers: List[int]) -> List[int]:
+ return [x**2 for x in numbers]
+
+
+def multiprocessing_compute():
+ numbers = range(1, 10000000)
+ process_cnt = mp.cpu_count()
+ print(f"cpu count: {process_cnt}")
+
+ pool = mp.Pool(process_cnt)
+
+ # 데이터 분할
+ size = len(numbers) // process_cnt
+ numbers_split = [
+ numbers[i * size:(i + 1) * size]
+ for i in range(process_cnt)
+ ]
+
+ start_time = time.time()
+ results = pool.map(compute_squares, numbers_split)
+ end_time = time.time()
+
+ pool.close()
+ pool.join()
+ print(f"Duration with multiprocessing: {end_time - start_time} seconds")
+
+
+if __name__ == '__main__':
+ multiprocessing_compute()
+```
+
+
\ No newline at end of file
diff --git "a/8th_members/\354\235\264\354\232\251\354\244\200/1\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\232\251\354\244\200/1\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..36d6d51
--- /dev/null
+++ "b/8th_members/\354\235\264\354\232\251\354\244\200/1\354\243\274\354\260\250.md"
@@ -0,0 +1,11 @@
+# CPython 소스코드 다운로드 및 환경 구성 (화면 캡쳐)
+
+
+
+# 스터디에서 이루고 싶은 목표, 함께 하게 된 소감 등
+- 목표
+ - Python 동작 원리 맛보기
+ - 스터디 참여자분들과 소통하며 다른 분들의 생각 본받기
+ - 회사 일이 습격하더라도, 중간에 내용 이해가 어렵더라도 끝까지 완주하기
+- 소감
+ - 개인적으로 진지한 스터디 참여(?)는 처음이고 Cpython을 공부해볼 수 있다니 기대가 됩니다. 하지만 내 실력에 너무 난이도 높은 주제를 선택한 게 아닐까 하는 두려움이 있습니다. 그리고 당장 도움이 안 될 것 같은 내용 가지고 씨름하며 동기부여를 잃어버릴까 걱정도 앞섭니다. 그래서 스터디원분들과 소통하며 서로의 학습과 동기부여의 도우미가 된다면 좋겠습니다.
\ No newline at end of file
diff --git "a/8th_members/\354\235\264\354\232\251\354\244\200/2\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\232\251\354\244\200/2\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..ff2f07f
--- /dev/null
+++ "b/8th_members/\354\235\264\354\232\251\354\244\200/2\354\243\274\354\260\250.md"
@@ -0,0 +1,7 @@
+# 컴파일 완료된 화면 캡쳐 (./python.exe 실행 화면)
+
+
+
+# 문법 다시 생성하기 (pass 키워드 변경해보기) (결과 화면 캡쳐)
+
+
diff --git "a/8th_members/\354\235\264\354\232\251\354\244\200/3\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\232\251\354\244\200/3\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..e9c7c67
--- /dev/null
+++ "b/8th_members/\354\235\264\354\232\251\354\244\200/3\354\243\274\354\260\250.md"
@@ -0,0 +1,7 @@
+# 파이썬 세션에서 runtime flag 확인하기
+
+
+
+# importlib.util.find_spec 함수를 사용해서 원하는 모듈의 위치를 찾아보기 (결과 화면 캡쳐)
+
+
diff --git "a/8th_members/\354\235\264\354\232\251\354\244\200/4\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\232\251\354\244\200/4\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..350d60a
--- /dev/null
+++ "b/8th_members/\354\235\264\354\232\251\354\244\200/4\354\243\274\354\260\250.md"
@@ -0,0 +1,7 @@
+# instaviz로 AST 시각화 해보기
+
+
+
+# ‘거의 같음’ 연산자를 문법에 추가하고 AlE타입의 객체를 출력 해보기
+
+
diff --git "a/8th_members/\354\235\264\354\232\251\354\244\200/5\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\232\251\354\244\200/5\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..a547266
--- /dev/null
+++ "b/8th_members/\354\235\264\354\232\251\354\244\200/5\354\243\274\354\260\250.md"
@@ -0,0 +1,3 @@
+# 거의 같음 연산자 구현
+
+
diff --git "a/8th_members/\354\235\264\354\232\251\354\244\200/6\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\232\251\354\244\200/6\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..fe7da0c
--- /dev/null
+++ "b/8th_members/\354\235\264\354\232\251\354\244\200/6\354\243\274\354\260\250.md"
@@ -0,0 +1,3 @@
+# 함수를 자유롭게 2개 이상 만들어서 프레임 객체의 생성을 눈으로 확인
+
+
diff --git "a/8th_members/\354\235\264\354\232\251\354\244\200/7\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\232\251\354\244\200/7\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..6cfdc3a
--- /dev/null
+++ "b/8th_members/\354\235\264\354\232\251\354\244\200/7\354\243\274\354\260\250.md"
@@ -0,0 +1,3 @@
+## tracemalloc 모듈 사용해서 직접 메모리 확인해보기
+
+
diff --git "a/8th_members/\354\235\264\354\232\251\354\244\200/8\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\232\251\354\244\200/8\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..f7e289b
--- /dev/null
+++ "b/8th_members/\354\235\264\354\232\251\354\244\200/8\354\243\274\354\260\250.md"
@@ -0,0 +1,3 @@
+# 원하는 라이브러리 import 해보고, 참조 카운트가 어떻게 바뀌는지 출력해보기
+
+
diff --git "a/8th_members/\354\235\264\354\232\251\354\244\200/9\354\243\274\354\260\250.md" "b/8th_members/\354\235\264\354\232\251\354\244\200/9\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..b9ad660
--- /dev/null
+++ "b/8th_members/\354\235\264\354\232\251\354\244\200/9\354\243\274\354\260\250.md"
@@ -0,0 +1,5 @@
+## 10.3.8 애플리케이션 예제의 두 코드를 실행
+
+
+
+
diff --git "a/8th_members/\354\240\204\355\235\254\354\204\240/10\354\243\274\354\260\250.md" "b/8th_members/\354\240\204\355\235\254\354\204\240/10\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..af93555
--- /dev/null
+++ "b/8th_members/\354\240\204\355\235\254\354\204\240/10\354\243\274\354\260\250.md"
@@ -0,0 +1,44 @@
+### 멀티스레딩 + 스레드 개수 제한 통한 port scanner 실행
+
+- 실행 코드
+ ```python
+ import socket
+ import time
+ from concurrent.futures import ThreadPoolExecutor, as_completed
+ from queue import Queue
+ import sys
+
+ 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():
+ MAX_WORKERS = int(sys.argv[1]) # worker 수 입력으로 받아 다르게 테스트
+
+ start = time.time()
+ host = "localhost" # Replace with a host you own
+ open_ports = []
+
+ with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
+ futures = [executor.submit(check_port, host, port) for port in range(10000, 65536)] # port 범위 확장
+
+ 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()
+ ```
+- 결과: worker 수를 30으로 했을 때 가장 적게 걸리는 점을 통해, 멀티스레딩도 오버헤드가 존재하며 (멀티프로세스에 비해 상대적으로 적을 뿐) worker 개수가 많다고 항상 좋은 것은 아니라는 점을 확인할 수 있었습니다 :)
+
diff --git "a/8th_members/\354\240\204\355\235\254\354\204\240/12\354\243\274\354\260\250.md" "b/8th_members/\354\240\204\355\235\254\354\204\240/12\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..735ca80
--- /dev/null
+++ "b/8th_members/\354\240\204\355\235\254\354\204\240/12\354\243\274\354\260\250.md"
@@ -0,0 +1,67 @@
+```
+✗ tree tests
+tests
+├── __init__.py
+├── test_normalize.py
+└── text_processing.py
+```
+- text_processing.py
+ ```python
+ def normalize(input_str):
+ """
+ 인풋으로 받는 스트링에서 아래의 규칙으로 정규화된 스트링을 반환하는 함수입니다.
+ * 모든 단어들은 소문자로 변환됨
+ * 띄어쓰기는 한칸으로 되도록 함
+ * 앞뒤 필요없는 띄어쓰기는 제거함
+ Parameters:
+ input_str (string): 영어로 된 대문자, 소문자, 띄어쓰기, 문장부호, 숫자로 이루어진 string
+ ex - " EXTRA SPACE "
+ Returns:
+ normalized_string (string): 정규회된 string
+ ex - 'extra space'
+ Examples:
+ >>> import text_processing as tp
+ >>> example = " EXTRA SPACE "
+ >>> tp.normalize(example)
+ 'extra space'
+ """
+ out = input_str.lower()
+ out = out.strip()
+ while ' ' in out:
+ out = out.replace(' ', ' ')
+ return out
+ ```
+- test_normalize.py
+ ```python
+ import unittest
+ from text_processing import normalize
+
+
+ class TestTextNormalize(unittest.TestCase):
+ def test_normalize(self):
+ test_str = "This is an example."
+ pred = normalize(test_str)
+ self.assertEqual(pred, "this is an example.")
+
+ def test_extra_space(self):
+ test_str = " EXTRA SPACE "
+ pred = normalize(test_str)
+ self.assertEqual(pred, "extra space")
+
+ def test_uppercase(self):
+ test_str = "THIS IS ALL UPPERCASE!!"
+ pred = normalize(test_str)
+ self.assertEqual(pred, "this is all uppercase!!")
+
+ def test_lowercase(self):
+ test_str = "this is all lowercase..."
+ pred = normalize(test_str)
+ self.assertEqual(pred, "this is all lowercase...")
+
+ def test_whitespace(self):
+ test_str = " "
+ pred = normalize(test_str)
+ self.assertEqual(pred, "")
+ ```
+### Result
+
diff --git "a/8th_members/\354\240\204\355\235\254\354\204\240/13\354\243\274\354\260\250.md" "b/8th_members/\354\240\204\355\235\254\354\204\240/13\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..c4e6bb6
--- /dev/null
+++ "b/8th_members/\354\240\204\355\235\254\354\204\240/13\354\243\274\354\260\250.md"
@@ -0,0 +1,145 @@
+## (Chapter 14) pdb 사용해보기
+#### (참고) 주요 PDB 명령어
+- n (next): 다음 라인으로 이동
+- s (step): 함수 내부로 들어감
+- c (continue): 다음 중단점까지 실행
+- p (print): 변수 값 출력
+- l (list): 현재 위치의 코드 표시
+- q (quit): 디버거 종료
+
+#### test.py
+```python
+import pdb
+
+def add(n1, n2):
+ result = n1 + n2
+ pdb.set_trace()
+ return result
+
+def subtract(n1, n2):
+ result = n2 - n1
+ pdb.set_trace()
+ return result
+
+a = 1
+b = 2
+c = 3
+d = add(a, b)
+e = subtract(c, b)
+```
+
+#### Result
+```
+✗ python3 -m pdb test.py
+> /Users/user/Documents/CPython-Guide/ch14/test.py(1)()
+-> import pdb
+(Pdb) n
+> /Users/user/Documents/CPython-Guide/ch14/test.py(3)()
+-> def add(n1, n2):
+(Pdb) n
+> /Users/user/Documents/CPython-Guide/ch14/test.py(8)()
+-> def subtract(n1, n2):
+(Pdb) n
+> /Users/user/Documents/CPython-Guide/ch14/test.py(13)()
+-> a = 1
+(Pdb) n
+> /Users/user/Documents/CPython-Guide/ch14/test.py(14)()
+-> b = 2
+(Pdb) l
+ 9 result = n2 - n1
+ 10 pdb.set_trace()
+ 11 return result
+ 12
+ 13 a = 1
+ 14 -> b = 2
+ 15 c = 3
+ 16 d = add(a, b)
+ 17 e = subtract(c, b)
+ 18
+[EOF]
+(Pdb) n
+> /Users/user/Documents/CPython-Guide/ch14/test.py(15)()
+-> c = 3
+(Pdb) n
+> /Users/user/Documents/CPython-Guide/ch14/test.py(16)()
+-> d = add(a, b)
+(Pdb) s
+--Call--
+> /Users/user/Documents/CPython-Guide/ch14/test.py(3)add()
+-> def add(n1, n2):
+(Pdb) n
+> /Users/user/Documents/CPython-Guide/ch14/test.py(4)add()
+-> result = n1 + n2
+(Pdb) n
+> /Users/user/Documents/CPython-Guide/ch14/test.py(5)add()
+-> pdb.set_trace()
+(Pdb) n
+> /Users/user/Documents/CPython-Guide/ch14/test.py(6)add()
+-> return result
+(Pdb) n
+--Return--
+> /Users/user/Documents/CPython-Guide/ch14/test.py(6)add()->3
+-> return result
+(Pdb) n
+> /Users/user/Documents/CPython-Guide/ch14/test.py(17)()
+-> e = subtract(c, b)
+(Pdb) s
+--Call--
+> /Users/user/Documents/CPython-Guide/ch14/test.py(8)subtract()
+-> def subtract(n1, n2):
+(Pdb) c
+> /Users/user/Documents/CPython-Guide/ch14/test.py(11)subtract()
+-> return result
+(Pdb) l
+ 6 return result
+ 7
+ 8 def subtract(n1, n2):
+ 9 result = n2 - n1
+ 10 pdb.set_trace()
+ 11 -> return result
+ 12
+ 13 a = 1
+ 14 b = 2
+ 15 c = 3
+ 16 d = add(a, b)
+(Pdb) n
+--Return--
+> /Users/user/Documents/CPython-Guide/ch14/test.py(11)subtract()->-1
+-> return result
+(Pdb) n
+--Return--
+> /Users/user/Documents/CPython-Guide/ch14/test.py(17)()->None
+-> e = subtract(c, b)
+(Pdb) n
+--Return--
+> (1)()->None
+(Pdb) n
+> /Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/bdb.py(584)run()
+-> self.quitting = True
+(Pdb) n
+> /Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/bdb.py(585)run()
+-> sys.settrace(None)
+(Pdb) n
+The program finished and will be restarted
+> /Users/user/Documents/CPython-Guide/ch14/test.py(1)()
+-> import pdb
+(Pdb) q
+```
+
+## (Chapter 15) cProfile 사용해보기
+
+(위 test.py에서 pdb 관련 부분만 주석 처리)
+```
+✗ python3 -m cProfile test.py
+ 5 function calls in 0.000 seconds
+
+ Ordered by: standard name
+
+ ncalls tottime percall cumtime percall filename:lineno(function)
+ 1 0.000 0.000 0.000 0.000 test.py:3()
+ 1 0.000 0.000 0.000 0.000 test.py:3(add)
+ 1 0.000 0.000 0.000 0.000 test.py:8(subtract)
+ 1 0.000 0.000 0.000 0.000 {built-in method builtins.exec}
+ 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
+```
+
diff --git "a/8th_members/\354\240\204\355\235\254\354\204\240/1\354\243\274\354\260\250.md" "b/8th_members/\354\240\204\355\235\254\354\204\240/1\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..26dead9
--- /dev/null
+++ "b/8th_members/\354\240\204\355\235\254\354\204\240/1\354\243\274\354\260\250.md"
@@ -0,0 +1,5 @@
+- CPython 소스코드 다운로드 및 환경 구성 (화면 캡쳐)
+
+- 스터디에서 이루고 싶은 목표, 함께 하게 된 소감 등
+ - CPython 튜토리얼 페이지를 팀원 분들과 함께 완성해나가는 것이 목표입니다!
+ - 함께 Python 내부 구조를 깊이 있게 알아가서 고급 Python 개발자가 되었으면 좋겠습니다 :)
diff --git "a/8th_members/\354\240\204\355\235\254\354\204\240/2\354\243\274\354\260\250.md" "b/8th_members/\354\240\204\355\235\254\354\204\240/2\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..67ab640
--- /dev/null
+++ "b/8th_members/\354\240\204\355\235\254\354\204\240/2\354\243\274\354\260\250.md"
@@ -0,0 +1,5 @@
+- 컴파일 진행
+
+
+- Grammar/python.gram 에서 'break'문 키워드에 'end_this_loop' 추가
+
diff --git "a/8th_members/\354\240\204\355\235\254\354\204\240/3\354\243\274\354\260\250.md" "b/8th_members/\354\240\204\355\235\254\354\204\240/3\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..a725e83
--- /dev/null
+++ "b/8th_members/\354\240\204\355\235\254\354\204\240/3\354\243\274\354\260\250.md"
@@ -0,0 +1,18 @@
+1. CPython 인터프리터 실행 과정 코드 레벨로 이해하기
+
+
+
+
+
+
+2. 파이썬 세션에서 runtime flag 확인하기
+ - 플래그 미사용
+
+ - `-X dev` 플래그 사용 (`dev_mode=True`)
+
+ - `-X dev -X utf8` 플래그 사용 (`dev_mode=True, utf8_mode=1`)
+
+ - `-X dev -q` 플래그 사용 (`dev_mode=True, quiet=1` + python version 미출력)
+
+3. importlib.util.find_spec 함수를 사용해서 원하는 모듈의 위치를 찾아보기
+
diff --git "a/8th_members/\354\240\204\355\235\254\354\204\240/4\354\243\274\354\260\250.md" "b/8th_members/\354\240\204\355\235\254\354\204\240/4\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..3cdede0
--- /dev/null
+++ "b/8th_members/\354\240\204\355\235\254\354\204\240/4\354\243\274\354\260\250.md"
@@ -0,0 +1,21 @@
+- instaviz로 AST 시각화 해보기
+ ```python
+ def add_and_subtract(a, b):
+ add_result = a + b
+ subtract_result = a - b
+ return add_result, subtract_result
+
+ import instaviz
+ instaviz.show(add_and_subtract)
+ ```
+ 
+- ‘거의 같음’ 연산자를 문법에 추가하고 ALE 타입의 객체를 출력 해보기
+
+
+
+
+ ```
+ make regen-token regen-pegen
+ make regen-ast
+ make -j2 -s
+ ```
diff --git "a/8th_members/\354\240\204\355\235\254\354\204\240/5\354\243\274\354\260\250.md" "b/8th_members/\354\240\204\355\235\254\354\204\240/5\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..7c9bac7
--- /dev/null
+++ "b/8th_members/\354\240\204\355\235\254\354\204\240/5\354\243\274\354\260\250.md"
@@ -0,0 +1,2 @@
+- ‘거의 같음’ 연산자 구현
+
diff --git "a/8th_members/\354\240\204\355\235\254\354\204\240/6\354\243\274\354\260\250.md" "b/8th_members/\354\240\204\355\235\254\354\204\240/6\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..2f79f50
--- /dev/null
+++ "b/8th_members/\354\240\204\355\235\254\354\204\240/6\354\243\274\354\260\250.md"
@@ -0,0 +1,31 @@
+### 파이썬에서 프레임 객체 접근해보기
+```python
+import inspect
+import requests
+import dis
+
+def print_frame_info():
+ frame = inspect.currentframe().f_back # 현재 함수의 호출자 프레임
+ try:
+ print("Function Name:", frame.f_code.co_name)
+ print("File Name:", frame.f_code.co_filename)
+ print("Line No:", frame.f_back.f_lineno)
+ print("Local Variables:", list(frame.f_locals.keys()))
+ print("Global Variables:", list(frame.f_globals.keys()))
+ dis.dis(frame.f_code) # 역 디스어셈블러
+ print()
+ finally:
+ del frame
+
+def request_get(url):
+ requests.get(url)
+ print_frame_info()
+
+def request_post(url):
+ requests.post(url)
+ print_frame_info()
+
+request_get("https://naver.com")
+request_post("https://naver.com")
+```
+
diff --git "a/8th_members/\354\240\204\355\235\254\354\204\240/7\354\243\274\354\260\250.md" "b/8th_members/\354\240\204\355\235\254\354\204\240/7\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..7b46fa0
--- /dev/null
+++ "b/8th_members/\354\240\204\355\235\254\354\204\240/7\354\243\274\354\260\250.md"
@@ -0,0 +1,35 @@
+- tracemalloc 통해 직접 메모리 확인해보기
+```python
+import tracemalloc
+import linecache
+import requests
+
+def request_get(url):
+ result = requests.get(url)
+ return result
+
+def request_post(url):
+ result = requests.post(url)
+ return result
+
+def main():
+ tracemalloc.start()
+ result_get = request_get("https://naver.com")
+ result_post = request_post("https://naver.com")
+
+ snapshot = tracemalloc.take_snapshot()
+ snapshot = snapshot.filter_traces((
+ tracemalloc.Filter(True, "*/requests/*"),
+ ))
+ stats = snapshot.statistics("lineno")
+
+ for stat in stats[:10]:
+ frame = stat.traceback[0]
+ print(f"{frame.filename}:{frame.lineno} - {stat.size} bytes")
+ print("\t" + linecache.getline(frame.filename, frame.lineno).strip())
+
+ tracemalloc.stop()
+
+main()
+```
+
diff --git "a/8th_members/\354\240\204\355\235\254\354\204\240/8\354\243\274\354\260\250.md" "b/8th_members/\354\240\204\355\235\254\354\204\240/8\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..9ffd816
--- /dev/null
+++ "b/8th_members/\354\240\204\355\235\254\354\204\240/8\354\243\274\354\260\250.md"
@@ -0,0 +1,18 @@
+- 라이브러리(requests) import 후 참조 카운트 변화 확인
+ ```python
+ >>> import sys
+ >>> for i in range(1, 4):
+ ... print(f"{i}: {sys.getrefcount(i)}")
+ ...
+ 1: 146
+ 2: 110
+ 3: 50
+ >>> import requests
+ >>> for i in range(1, 4):
+ ... print(f"{i}: {sys.getrefcount(i)}")
+ ...
+ 1: 1063
+ 2: 444
+ 3: 244
+ ```
+
diff --git "a/8th_members/\354\240\204\355\235\254\354\204\240/9\354\243\274\354\260\250.md" "b/8th_members/\354\240\204\355\235\254\354\204\240/9\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..1f92268
--- /dev/null
+++ "b/8th_members/\354\240\204\355\235\254\354\204\240/9\354\243\274\354\260\250.md"
@@ -0,0 +1,47 @@
+간단한 CPU-Bound Job을 만들고 멀티프로세싱을 적용한 코드와 아닌 코드를 작성해보기
+
+```python
+from math import sqrt
+import time
+import multiprocessing as mp
+import sys
+
+NUMBERS_LIMIT = 100000000
+
+def compute_sqrt(numbers):
+ return [sqrt(n) for n in numbers]
+
+def multiprocessing_compute():
+ numbers = range(1, NUMBERS_LIMIT)
+ process_count = mp.cpu_count() # 사용 가능한 CPU 코어 수
+ pool = mp.Pool(process_count)
+
+ # 데이터 분할
+ size = len(numbers) // process_count
+ numbers_split = [numbers[i * size:(i + 1) * size] for i in range(process_count)]
+
+ start_time = time.time()
+ results = pool.map(compute_sqrt, numbers_split)
+ end_time = time.time()
+ pool.close()
+ pool.join()
+ print(f"Duration with multiprocessing: {end_time - start_time} seconds")
+
+
+def no_multiprocessing():
+ numbers = range(1, NUMBERS_LIMIT)
+ start_time = time.time()
+ results = compute_sqrt(numbers)
+ end_time = time.time()
+ print(f"Duration without multiprocessing: {end_time - start_time} seconds")
+
+
+if __name__ == '__main__':
+ option = sys.argv[1]
+ if option == "multiprocessing":
+ multiprocessing_compute()
+ else:
+ no_multiprocessing()
+```
+
+
diff --git "a/8th_members/\355\225\230\354\203\201\354\262\234/10\354\243\274\354\260\250.md" "b/8th_members/\355\225\230\354\203\201\354\262\234/10\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..1eb800b
--- /dev/null
+++ "b/8th_members/\355\225\230\354\203\201\354\262\234/10\354\243\274\354\260\250.md"
@@ -0,0 +1,123 @@
+## 10주차 미션
+
+### 싱글 프로세스 + 싱글 스레드
+
+```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()
+```
+
+
+
+### 멀티스레딩
+
+```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()
+```
+
+
+
+### 멀티스레딩 + 스레드 개수 제한
+
+```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()
+```
+
+
diff --git "a/8th_members/\355\225\230\354\203\201\354\262\234/11\354\243\274\354\260\250.md" "b/8th_members/\355\225\230\354\203\201\354\262\234/11\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..cae4c78
--- /dev/null
+++ "b/8th_members/\355\225\230\354\203\201\354\262\234/11\354\243\274\354\260\250.md"
@@ -0,0 +1,108 @@
+## 타입별 내부 구조 살펴보기
+
+### List 타입
+
+- PyList_Type
+
+```c
+PyTypeObject PyList_Type = {
+ PyVarObject_HEAD_INIT(&PyType_Type, 0)
+ "list",
+ sizeof(PyListObject),
+ 0,
+ (destructor)list_dealloc, /* tp_dealloc */
+ 0, /* tp_vectorcall_offset */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_as_async */
+ (reprfunc)list_repr, /* tp_repr */
+ 0, /* tp_as_number */
+ &list_as_sequence, /* tp_as_sequence */
+ &list_as_mapping, /* tp_as_mapping */
+ PyObject_HashNotImplemented, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ PyObject_GenericGetAttr, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
+ Py_TPFLAGS_BASETYPE | Py_TPFLAGS_LIST_SUBCLASS, /* tp_flags */
+ list___init____doc__, /* tp_doc */
+ (traverseproc)list_traverse, /* tp_traverse */
+ (inquiry)_list_clear, /* tp_clear */
+ list_richcompare, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ list_iter, /* tp_iter */
+ 0, /* tp_iternext */
+ list_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)list___init__, /* tp_init */
+ PyType_GenericAlloc, /* tp_alloc */
+ PyType_GenericNew, /* tp_new */
+ PyObject_GC_Del, /* tp_free */
+ .tp_vectorcall = list_vectorcall,
+};
+```
+
+- tp_new (PyType_GenericNew)
+
+```c
+PyObject *
+PyType_GenericNew(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ return type->tp_alloc(type, 0);
+}
+```
+
+- tp_alloc (PyType_GenericAlloc)
+
+```c
+// PyObject_GC_Del: 가비지 컬렉션 대상 객체를 삭제하는 함수
+void PyObject_GC_Del(void *op)
+{
+ // op를 PyGC_Head 포인터로 변환
+ PyGC_Head *g = AS_GC(op);
+
+ // 객체가 가비지 컬렉션 추적 대상이면 리스트에서 제거
+ if (_PyObject_GC_IS_TRACKED(op))
+ {
+ gc_list_remove(g);
+ }
+
+ // 현재 스레드 상태 가져오기
+ PyThreadState *tstate = _PyThreadState_GET();
+ // 가비지 컬렉션 상태 가져오기
+ GCState *gcstate = &tstate->interp->gc;
+
+ // 첫 번째 세대(young generation)의 객체 수가 0보다 크면 감소
+ if (gcstate->generations[0].count > 0)
+ {
+ gcstate->generations[0].count--;
+ }
+
+ // 메모리 해제
+ PyObject_FREE(g);
+}
+```
+
+### 코드 보면서 궁금한 점
+
+- CPython에서 스레드마다 가비지 컬렉션을 가지고 있는가?
+ - No!
+ - 각 스레드마다 완전히 독립적인 가비지 컬렉션을 가지고 있지는 않지만, 스레드별로 일부 상태를 관리
+ - 전역 가비지 컬렉션
+ - CPython은 기본적으로 하나의 전역 가비지 컬렉션 시스템을 가지고 있다. 이는 모든 스레드에서 공유한다.
+ - 스레드별 상태
+ - 각 스레드는 자체적인 `PyThreadState` 구조체를 가지고 있으며, 이 구조체 안에 `GCState`라는 필드가 있다. 이 `GCState`는 해당 스레드의 가비지 컬렉션 관련 상태 정보를 포함한다.
+ - 스레드별로 가비지 컬렉션 카운터를 관리한다.
+ - 가비지 컬렉션 세대(generations)에 대한 정보를 저장한다.
+ - 스레드별 가비지 컬렉션 임계값을 설정할 수 있게 한다.
+ - 동기화
+ - 전역 가비지 컬렉션 작업은 GIL(Global Interpreter Lock)에 의해 보호
+ - 정리하면 가비지 컬렉션은 하나이고, 스레드별로 가비지 컬렉션의 상태 정보, 임계값을 가지고 있어서 개별로 실행할 수 있다. 실행한 이후에는 전역 가비지 컬렉션에 반영하는데 GIL이 있기 때문에 동기화가 보호된다.
diff --git "a/8th_members/\355\225\230\354\203\201\354\262\234/12\354\243\274\354\260\250.md" "b/8th_members/\355\225\230\354\203\201\354\262\234/12\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..d2779b5
--- /dev/null
+++ "b/8th_members/\355\225\230\354\203\201\354\262\234/12\354\243\274\354\260\250.md"
@@ -0,0 +1,26 @@
+## 나만의 Test Case 만들기
+
+```python
+import unittest
+import re
+
+korean = re.compile(r"[가-힣]")
+
+
+class TestPreprocess(unittest.TestCase):
+ def test_korean(self):
+ self.assertTrue(korean.match("한글"))
+ self.assertFalse(korean.match("english"))
+ self.assertFalse(korean.match("1234"))
+ self.assertFalse(korean.match("!@#$"))
+ self.assertTrue(korean.match("한글english1234!@#$"))
+```
+
+```bash
+python -m unittest discover tests -v
+python -m unittest tests.test_preprocess -v
+python -m unittest tests.test_preprocess.TestPreprocess -v
+python -m unittest tests.test_preprocess.TestPreprocess.test_korean -v
+```
+
+
diff --git "a/8th_members/\355\225\230\354\203\201\354\262\234/1\354\243\274\354\260\250.md" "b/8th_members/\355\225\230\354\203\201\354\262\234/1\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..7ec1bee
--- /dev/null
+++ "b/8th_members/\355\225\230\354\203\201\354\262\234/1\354\243\274\354\260\250.md"
@@ -0,0 +1,5 @@
+- CPython 소스코드 다운로드 및 환경 구성
+
+- 스터디에서 이루고 싶은 목표, 함께 하게 된 소감
+ - Python의 내부 구조를 깊이 있게 이해하고, CPython 소스코드를 분석해보고 싶습니다.
+ - CPython 소스코드를 분석하면서, Python의 내부 동작 원리를 이해하고, 이를 통해 Python의 성능을 최적화하는 방법을 배우고 싶습니다.
diff --git "a/8th_members/\355\225\230\354\203\201\354\262\234/2\354\243\274\354\260\250.md" "b/8th_members/\355\225\230\354\203\201\354\262\234/2\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..57d0b54
--- /dev/null
+++ "b/8th_members/\355\225\230\354\203\201\354\262\234/2\354\243\274\354\260\250.md"
@@ -0,0 +1,4 @@
+- 컴파일 완료된 화면 캡쳐
+- 키워드 다른걸로 바꾸기
+
+
diff --git "a/8th_members/\355\225\230\354\203\201\354\262\234/3\354\243\274\354\260\250.md" "b/8th_members/\355\225\230\354\203\201\354\262\234/3\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..d9b4ad1
--- /dev/null
+++ "b/8th_members/\355\225\230\354\203\201\354\262\234/3\354\243\274\354\260\250.md"
@@ -0,0 +1,12 @@
+
+1. CPython 인터프리터 실행 과정 코드 레벨로 이해하기
+
+
+
+2. 파이썬 세션에서 runtime flag 확인하기
+
+
+
+3. importlib.util.find_spec 함수를 사용해서 원하는 모듈의 위치를 찾아보기
+
+
diff --git "a/8th_members/\355\225\230\354\203\201\354\262\234/4\354\243\274\354\260\250.md" "b/8th_members/\355\225\230\354\203\201\354\262\234/4\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..9064235
--- /dev/null
+++ "b/8th_members/\355\225\230\354\203\201\354\262\234/4\354\243\274\354\260\250.md"
@@ -0,0 +1,9 @@
+- instaviz로 AST 시각화 해보기
+
+
+
+
+
+- ‘거의 같음’ 연산자를 문법에 추가하고 AlE타입의 객체를 출력 해보기
+
+
diff --git "a/8th_members/\355\225\230\354\203\201\354\262\234/5\354\243\274\354\260\250.md" "b/8th_members/\355\225\230\354\203\201\354\262\234/5\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..a334fbe
--- /dev/null
+++ "b/8th_members/\355\225\230\354\203\201\354\262\234/5\354\243\274\354\260\250.md"
@@ -0,0 +1,3 @@
+# ‘거의 같음’ 연산자 구현하기
+
+
diff --git "a/8th_members/\355\225\230\354\203\201\354\262\234/6\354\243\274\354\260\250.md" "b/8th_members/\355\225\230\354\203\201\354\262\234/6\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..599ab71
--- /dev/null
+++ "b/8th_members/\355\225\230\354\203\201\354\262\234/6\354\243\274\354\260\250.md"
@@ -0,0 +1,3 @@
+# 파이썬에서 프레임 객체 접근해보기
+
+
diff --git "a/8th_members/\355\225\230\354\203\201\354\262\234/7\354\243\274\354\260\250.md" "b/8th_members/\355\225\230\354\203\201\354\262\234/7\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..be83442
--- /dev/null
+++ "b/8th_members/\355\225\230\354\203\201\354\262\234/7\354\243\274\354\260\250.md"
@@ -0,0 +1,3 @@
+## tracemalloc 모듈 사용해서 직접 메모리 확인해보기
+
+
diff --git "a/8th_members/\355\225\230\354\203\201\354\262\234/8\354\243\274\354\260\250.md" "b/8th_members/\355\225\230\354\203\201\354\262\234/8\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..f8e95d3
--- /dev/null
+++ "b/8th_members/\355\225\230\354\203\201\354\262\234/8\354\243\274\354\260\250.md"
@@ -0,0 +1,3 @@
+## 8주차 미션
+
+
diff --git "a/8th_members/\355\225\230\354\203\201\354\262\234/9\354\243\274\354\260\250.md" "b/8th_members/\355\225\230\354\203\201\354\262\234/9\354\243\274\354\260\250.md"
new file mode 100644
index 0000000..162f95a
--- /dev/null
+++ "b/8th_members/\355\225\230\354\203\201\354\262\234/9\354\243\274\354\260\250.md"
@@ -0,0 +1,44 @@
+## 9주차 미션
+
+### Queue 사용
+
+
+
+### multiprocessing.Queue 사용
+
+```python
+import multiprocessing as mp
+import socket
+import time
+
+timeout = 1.0
+
+
+def check_port(host: str, port: int, results: mp.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()
+
+
+if __name__ == '__main__':
+ start = time.time()
+ processes = []
+ scan_range = range(80, 100)
+ host = "localhost"
+ mp.set_start_method('spawn')
+ pool_manager = mp.Manager()
+ with mp.Pool(len(scan_range)) as pool:
+ outputs = pool_manager.Queue()
+ for port in scan_range:
+ processes.append(pool.apply_async(check_port, (host, port, outputs)))
+ for process in processes:
+ process.get()
+ while not outputs.empty():
+ print("Port {0} is open.".format(outputs.get()))
+ print("Completed scan in {0} seconds".format(time.time() - start))
+```
+
+
diff --git a/book/_config.yml b/book/_config.yml
index 6ea7d97..e31f655 100644
--- a/book/_config.yml
+++ b/book/_config.yml
@@ -41,4 +41,4 @@ html:
sphinx:
config:
html_js_files:
- - https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js
\ No newline at end of file
+ - https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js
diff --git a/book/_toc.yml b/book/_toc.yml
index 3a42a78..72a0d5a 100644
--- a/book/_toc.yml
+++ b/book/_toc.yml
@@ -3,10 +3,30 @@ root: intro
parts:
- caption: ◽ CPython 가이드
chapters:
- - file: docs/0_0_dev_env_setup
- sections:
- - file: docs/0_1_directory_structure
+ # - file: docs/0_0_dev_env_setup
+ # sections:
+ # - file: docs/0_1_directory_structure
+ - file: docs/1_0_welcome_to_cpython
+ - file: docs/2_0_settings
+ - file: docs/3_0_compile
- file: docs/4_0_python_grammar
+ - file: docs/5_0_config_and_input
+ - file: docs/6_0_rexing_and_parsing
+ - file: docs/7_0_compiler
+ - file: docs/8_0_eval_loop
+ - file: docs/9_0_memory
+ sections:
+ - file: docs/9_1_memory_pool
+ - file: docs/9_2_garbage_collection
+ - file: docs/10_0_parallel_and_concurrent
+ sections:
+ - file: docs/10_1_process
+ - file: docs/10_2_thread
+ - file: docs/10_3_async
+ - file: docs/11_0_object_and_type
+ - file: docs/12_0_standard_library
+ - file: docs/13_0_test_suite
+ - file: docs/14_0_c_extension
#- caption: ◽ 샘플
# - file: docs/sample/markdown-example
diff --git a/book/c_extension_sample/fputsmodule.c b/book/c_extension_sample/fputsmodule.c
new file mode 100644
index 0000000..849a952
--- /dev/null
+++ b/book/c_extension_sample/fputsmodule.c
@@ -0,0 +1,33 @@
+#include
+
+static PyObject* method_fputs(PyObject* self, PyObject* args) {
+ char *str, *filename = NULL;
+ int bytes_copied = -1;
+
+ if(!PyArg_ParseTuple(args, "ss", &str, &filename)) {
+ return NULL;
+ }
+
+ FILE *fp = fopen(filename, "w");
+ bytes_copied = fputs(str, fp);
+ fclose(fp);
+
+ return PyLong_FromLong(bytes_copied);
+}
+
+static PyMethodDef FPutsMethods[] = {
+ {"fputs", method_fputs, METH_VARARGS, "Python interface for fputs C library function"},
+ {NULL, NULL, 0, NULL}
+};
+
+static struct PyModuleDef fputsmodule = {
+ PyModuleDef_HEAD_INIT,
+ "fputs",
+ "Python interface for the fputs C library function",
+ -1,
+ FPutsMethods
+};
+
+PyMODINIT_FUNC PyInit_fputs(void) {
+ return PyModule_Create(&fputsmodule);
+}
diff --git a/book/c_extension_sample/setup.py b/book/c_extension_sample/setup.py
new file mode 100644
index 0000000..ddbabed
--- /dev/null
+++ b/book/c_extension_sample/setup.py
@@ -0,0 +1,13 @@
+from distutils.core import setup, Extension
+
+def main():
+ setup(name="fputs",
+ version="1.0.0",
+ description="Python interface for the fputs C library funtion",
+ author="kwang1",
+ author_email="4roring@naver.com",
+ ext_modules=[Extension("fputs", ["fputsmodule.c"])])
+
+
+if __name__ == "__main__":
+ main()
diff --git a/book/docs/10_0_parallel_and_concurrent.md b/book/docs/10_0_parallel_and_concurrent.md
new file mode 100644
index 0000000..bd7fcfd
--- /dev/null
+++ b/book/docs/10_0_parallel_and_concurrent.md
@@ -0,0 +1,37 @@
+# 10. 병렬성과 동시성
+
+## 개요
+
+모든 운영체제의 핵심 기능은 실행 중인 프로세스를 관리하는 것이다.
+모든 프로세스는 메모리와 CPU 같은 자원을 요청할 수 있다.
+
+**프로세스는 작업 단위로 실행**되기 때문에 CPU에 자신이 사용할 시간을 요청한다.
+이 요쳥을 받은 운영체제는 시간을 프로세스에 할당해주고 우선순위에 따라 스케줄링하며
+어떤 프로세스가 CPU를 사용할지를 컨트롤한다.
+
+
+
+동시성은 멀티태스킹 구현에 알맞은 해결책이긴 하지만
+CPU를 나눠쓰기 때문에 한계가 있다.
+
+
+
+때문에 여러 개의 코어 또는 CPU를 사용해 작업을 분산하는 방식으로
+멀티태스킹을 수행하는 방법도 OS가 제공해준다.
+
+즉, 컴퓨터는 병렬성과 동시성을 활용해 멀티태스킹을 처리한다.
+
+- 병렬성 → CPU와 같은 연산 유닛들이 여러 개 필요
+- 동시성 → 유휴 상태의 프로세스가 하나의 자원을 계속해서 차지하지 않도록 스케줄링 필요
+
+## 병렬성과 동시성 모델
+
+CPython은 병렬성과 동시성에 대해 다양한 접근 방식을 제공한다.
+기본적으로 아래의 4가지 모델을 제공한다.
+
+| 접근 방식 | 모델명 | 동시 실행 | 병렬 실행 |
+| -------------- | --------------- | --------- | --------- |
+| 쓰레딩 | threading | O | X |
+| 멀티프로세싱 | multiprocessing | O | O |
+| 비동기 | asyncio | O | X |
+| 서브인터프리터 | subinterpreters | O | O |
diff --git a/book/docs/10_1_process.md b/book/docs/10_1_process.md
new file mode 100644
index 0000000..04619ff
--- /dev/null
+++ b/book/docs/10_1_process.md
@@ -0,0 +1,740 @@
+# 10.1 멀티 프로세스
+
+## 프로세스 구조
+
+CPython에서 프로세스에 대해 깊게 들어가보자.
+CPython에서 프로세스는 컴파일된 CPython 인터프리터와 모듈로 구성된다.
+
+즉, CPython에서 프로세스는 인터프리터이다 (프로세스 = 인터프리터)
+
+
+
+| 컴포넌트 | 설명 |
+| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| 스택(Stack) | 함수 호출, 지역 변수 및 제어 정보가 저장되는 메모리 영역 |
+| 힙(Heap) | 동적 메모리 할당에 사용되는 메모리 영역 |
+| 명령(Instructions) | 운영체제가 파일 및 I/O 리소스에 접근하기 위해 사용하는 핸들, 프로세스가 파일을 읽거나 쓰기 위해 운영체제로부터 제공받아 사용 |
+| 잠금(Lock 또는 Mutex) | 다중 쓰레드가 공유 자원에 동시에 접근하는 것을 제어하기 위해 사용되는 동기화 초기 값, Race Condition을 방지하기 위해 하나의 쓰레드만 자원에 접근 가능하도록 만들어줌 |
+| 소켓(Socket) | 네트워크 상 두 컴퓨터 간 통신 끝 점을 의미한다. TCP/IP 또는 UDP 통신과 같은 다양한 유형의 네트워크 통신에 사용 |
+
+위 컴포넌트들은 모두 인터프리터 상태의 구성요소이다.
+
+프로세스는 시작되면 다음과 같은 항목들이 부여된다.
+
+- 서브루틴 호출을 위한 메모리 스택(Stack)
+- 힙(Heap)
+- 파일이나 잠금(Lock) 또는 소켓(Socket) 접근 권한
+
+그리고 컴퓨터의 CPU는 프로세스를 실행할 때 아래와 같은 추가 데이터가 필요하다.
+
+- 프로그램 카운터(명령 포인터):
+ - 프로그램 시퀸스의 어떤 명령을 실행 중인지 저장
+- 프로그램 레지스터:
+ - 실행 중인 명령이나, 명령을 실행하는 데 필요한 데이터를 보관
+
+> 프로그램 카운터 : 현재 실행 중인 명령의 주소를 저장
+> 프로그램 레지스터 : 현재 실행 중인 명령어와 관련된 데이터를 저장하고 처리
+
+프로그램 레지스터와 프로그램 카운터는 프로세스에 저장된 명령 중 한 명령만 가리킨다.
+즉, 한 번에 하나의 명령만 사용 가능하다.
+
+이 방식은 CPython에서 아래와 같은 영향을 미친다.
+
+- GIL(Global Interpreter Lock)
+ - 한 번에 하나의 쓰레드만 파이썬의 바이트코드로 실행하도록 제한
+ - 이는 멀티 쓰레딩 환경에서 동시 실행을 제한함
+ 단, I/O 바운드 작업은 GIL의 영향을 받지 않음 (파일 읽기/쓰기, 네트워크 통신 등…)
+- 멀티코어 활용 부족
+ - 멀티코어 환경에서 CPython의 GIL로 인해 효율적으로 코어를 사용하기는 어려움
+
+때문에 파이썬에서 프로세스의 명령을 병렬로 실행하려면 아래의 2가지 방법을 사용할 수 있다.
+
+1. 인터프리터 Fork (POSIX)
+
+2. 새 인터프리터 프로세스 Spawn (POSIX/윈도우)
+
+## 멀티프로세스를 활용한 병렬 실행
+
+POSIX 시스템은 Fork API를 기본으로 제공한다.
+때문에 어떤 프로세스든 이 API를 통해 자식 프로세스를 Fork 할 수 있다.
+
+Fork 호출이 일어나면
+현재 실행 중인 프로세스의 모든 Attribute를 복제해 새 프로세스를 생성한다.
+
+이 때 부모의 힙과 레지스터, 카운터 위치도 새 프로세스로 복제된다.
+즉, Fork로 생성한 프로세스는 복제 시점에서 부모 프로세스가 가지고 있는 모든 변수를 읽을 수 있다.
+
+### POSIX에 프로세스 Fork 하기
+
+화씨-섭씨 변환 프로그램을
+순차적으로 값을 변환하는 방식에서
+`fork()`를 이용해 자식 프로세스를 spawn하게 바꿔보자.
+
+각 자식 프로세스는 fork 호출 뒤에서부터 실행을 이어나간다.
+
+```c
+#include
+#include
+#include
+
+static const double five_ninths = 5.0/9.0;
+
+double cesius(double fahrenheit) {
+ return (fahrenheit - 32) * five_ninths;
+}
+
+int main(int argc, char** argv) {
+ if (argc != 2)
+ return -1;
+ int number = atoi(argv[1]);
+ for (int i = 1; i <= number; i++) {
+ double f_value = 100 + (i * 10);
+
+ pid_t child = fork();
+ if (child == 0) { // 자식 프로세스에서는 0이다.
+ double c_value = celsius(f_value);
+ printf("%f F is %f C (pid %d)\n", f_value, c_value, getpid());
+ }
+ }
+ printf("Spawned %d processes from %d\n", number, getpid());
+ return 0;
+}
+```
+
+위 프로그램을 명령줄에서 실행하면 아래와 같은 결과가 출력된다.
+
+```bash
+$ ./thread_celsius 4
+110.000000 F is 43.333333 C (pid 57179)
+120.000000 F is 48.888889 C (pid 57180)
+Spawned 4 processes from 57178
+130.000000 F is 54.444444 C (pid 57181)
+140.000000 F is 60.000000 C (pid 57182)
+
+```
+
+부모 프로세스(57178)가 자식 프로세스 4개를 spawn했다.
+각 자식 프로세스는 `child = fork()` 이후부터 프로그램 실행을 이어 간다.
+이때 `child`에는 `0`이 할당된다.
+
+이후 계산을 완료하면 값을 출력한 후 프로세스를 종료한다.
+마지막으로 부모 프로세스는 spawn한 프로세스 수와 자신의 PID를 출력한다.
+
+Fork를 활용한 병렬 실행의 큰 단점은
+자식 프로세스가 부모 프로세스의 완벽한 복제본이라는 점이다.
+
+CPython의 경우 프로세스를 Fork 하면 2개 이상의 CPython 인터프리터가 실행되는 것과 같고
+각 인터프리터가 모듈과 라이브러리를 따로 불러들이면서 상당한 오버헤드가 발생할 수 있다.
+
+때문에 처리 중인 작업의 크기가
+프로세스를 Fork할 때의 오버헤드보다 클 때 사용하는 것이 좋다
+
+> 작업의 크기나 시간이 충분히 커서,
+> 프로세스를 Fork 하는 데 드는 시간 비용보다
+> 작업을 병렬로 처리하는 것이 더 효율적이라 판단될 때
+
+
+연관된 소스 파일 목록
+다음은 멀티프로세싱과 관련된 소스 파일 목록이다.
+
+| 파일 | 목적 |
+| --------------------------- | --------------------------------------------------------------- |
+| Lib/multiprocessing | multiprocessing 패키지의 파이썬 소스 파일 |
+| Modules/\_posixsubprocess.c | POSIX fork() 시스템 콜을 래핑하는 C 확장 모듈 |
+| Modules/\_winapi.c | 윈도우 커널 API를 제공하는 C 확장 모듈 |
+| PC/msvcrtmodule.c | 마이크로소프트 비주얼 C 런타임 라이브러리의 파이썬용 인터페이스 |
+
+
+
+### Spawn과 Fork
+
+파이썬의 `multiprocessing` 패키지는 아래의 3가지 병렬 프로세싱 방법을 제공한다.
+
+1. 인터프리터 Fork (POSIX 전용)
+2. 새 인터프리터 프로세스 Spawn (POSIX / 윈도우)
+3. fork 서버를 실행한 후에 원하는 수 만큼의 프로세스를 fork (POSIX 전용)
+
+> windows/macOS는 Spawn을 기본으로 사용하고 리눅스에서는 Fork를 사용한다.
+> `multiprocessing.set_start_method()`를 사용해 변경할 수 있다.
+
+**인터프리터 Fork (POSIX)**
+
+기존 프로세스를 Fork 하여 새로운 프로세스를 생성
+새 프로세스는 복제 시점에서 부모 프로세스와 완전히 동일함
+
+> 부모 프로세스의 상태를 그대로 복제 → 동일한 인터프리터 상태로 시작
+
+**새 인터프리터 프로세스 Spawn (POSIX/윈도우)**
+
+새로운 인터프리터 프로세스를 생성하여 독립적인 메모리 공간에서 프로세스를 실행한다.
+때문에 부모 프로세스와 독립적인 메모리 공간을 사용한다.
+
+> 새로운 프로세스를 생성 → 초기화된 인터프리터로 시작
+
+**Fork 서버를 실행한 후에 원하는 수 만큼의 프로세스를 Fork (POSIX)**
+
+별도의 프로세스를 Fork 서버로 만들어 둔 후,
+필요한 만큼의 프로세스를 이 서버를 통해 fork한다.
+
+부모 프로세스가 아닌 별도의 서버 프로세스를 통해 Fork하는 방식이다.
+
+Fork 서버를 통해 프로세스를 생성하기 때문에 메모리 효율성이 높아진다.
+또한 여러 프로세스를 빠르게 생성할 수 있다.
+
+```python
+import multiprocessing as mp
+import os
+
+def to_celsius(f):
+ c = (f - 32) * (5/9)
+ pid = os.getpid()
+ print(f"{f}F is {c}C (pid {pid})")
+
+if __name__ == '__main__':
+ mp.set_start_method('spawn')
+ p = mp.Process(target=to_celsius, args=(110,))
+ p.start()
+```
+
+`multiprocessing` API는 한 프로세스뿐 아니라 여러 개의 프로세스를 시작하고
+프로세스에 데이터를 제공할 수 있는 편리한 방법들을 제공한다.
+`Pool` 클래스도 그 중 하나이다.
+
+아래는 spawn 방식으로 프로세스 4개를 생성하여 계산하는 예제이다.
+
+```python
+import multiprocessing as mp
+import os
+
+def to_celsius(f):
+ c = (f - 32) * (5/9)
+ pid = os.getpid()
+ print(f"{f}F is {c}C (pid {pid})")
+
+if __name__ == '__main__':
+ mp.set_start_method('spawn')
+ with mp.Pool(4) as pool:
+ pool.map(to_celsius, range(110, 150, 10))
+```
+
+
+
+CPython 인터프리터는 상당한 오버헤드가 있기 때문에
+`Pool` 은 각각의 프로세스를 Worker로 사용해서
+작업이 완료된 프로세스를 재사용한다.
+
+아래와 같이 Worker가 한 가지 작업만 수행하도록 아래와 같이 제한할 수도 있다.
+
+```python
+import multiprocessing as mp
+import os
+
+def to_celsius(f):
+ c = (f - 32) * (5/9)
+ pid = os.getpid()
+ print(f"{f}F is {c}C (pid {pid})")
+
+if __name__ == '__main__':
+ mp.set_start_method('spawn')
+ with mp.Pool(4, maxtasksperchild=1) as pool:
+ pool.map(to_celsius, range(110, 150, 10))
+```
+
+
+
+→ 재사용 기준
+
+### 자식 프로세스의 생성 과정
+
+```python
+import multiprocessing as mp
+import os
+
+def to_celsius(f):
+ c = (f - 32) * (5/9)
+ pid = os.getpid()
+ print(f"{f}F is {c}C (pid {pid})")
+
+if __name__ == '__main__':
+ mp.set_start_method('spawn')
+ with mp.Pool(4, maxtasksperchild=1) as pool:
+ # 이 부분이 pickle을 통한 데이터 전송이 이루어지는 부분
+ pool.map(to_celsius, range(110, 150, 10))
+```
+
+위 예제에서는 부모 프로세스에서 존재하는 데이터를 `pool.map` 을 통해
+자식에게 `pickle` 형태로 전달한다.
+
+즉, `map` 메소드 호출 시 자동으로 `pickle` 을 통한 자식 프로세스로의 데이터 전송이 된다는 것이다.
+
+
+
+### Pipe로 자식 프로세스에 데이터 전송하기
+
+OS가 자식 프로세스를 생성하면 생성된 프로세스는
+부모 프로세스의 초기화 데이터를 먼저 기다린다.
+
+자식 프로세스가 가장 먼저 기다리게 될 초기화 데이터는 아래와 같다.
+
+1. 준비 데이터(Preparation Data) 객체
+
+- 실행 디렉터리, 시작 방법, 명령줄 인자 등과 같은 부모 프로세스의 정보를 일부 담고 있는 딕셔너리
+ ```python
+ >>> import multiprocessing.spawn
+ >>> import pprint
+ >>> pprint.pprint(multiprocessing.spawn.get_preparation_data("example"))
+ {'authkey': b'\x1b\x7f\x80\xa8\xc5\x03\xed# \xeb;\xb6\x15~\xae\xd0\xf4\x97:\xb5'
+ b'w\x1a\x02\xce\xb29\xdd\x18\x0bx\xc8\xbe',
+ 'dir': '/Users/wooy0ng/Desktop/playground/practice-cpython/practice',
+ 'log_to_stderr': False,
+ 'name': 'example',
+ 'orig_dir': '/Users/wooy0ng/Desktop/playground/practice-cpython/practice',
+ 'start_method': 'spawn',
+ 'sys_argv': [''],
+ ```
+
+2. `BaseProcess` 의 자식 클래스 인스턴스
+
+- 호출 방식과 운영 체제에 따라 아래의 `BaseProcess` 의 자식 클래스 중 하나를 인스턴스화 한다
+ - POSIX: `ForkProcess` 클래스를 인스턴스화한다.
+ - 윈도우: `SpawnProcess` 클래스를 인스턴스화한다.
+
+위 2개의 초기화 데이터는 모두 `pickle` 로 직렬화되어
+부모 프로세스의 파이프 스트림으로 전송된다.
+
+
+
+여기까지 정리하자면, 초기화 과정은 다음과 같이 정리할 수 있다.
+
+1. 자식 프로세스를 생성한다. (Spawn 또는 Fork 방식으로)
+2. 자식 프로세스는 부모 프로세스로부터 준비 데이터 객체, BaseProcess의 자식 클래스 인스턴스를 받는다.
+
+ → 자식 프로세스는 부모로부터 초기화 데이터를 `pickle` 형태로 Pipe를 통해 전달받는다.
+
+3. 초기화된 자식 프로세스는 준비 상태가 되며,
+ 언제든지 부모로부터 작업 인자(함수의 인자)를 받을 준비를 한다.
+
+---
+
+## 자식 프로세스 실행하기
+
+자식 프로세스의 실행은 시작점인 `multiprocessing.spawn.spawn_main()` 로부터 시작된다.
+
+(인자로 **pipe_handle** 과 **parent_pid**(window 일 경우), **tracked_fd**(posix 일 경우)를 받는다.)
+
+**multiprocessing/spawn.py**
+
+```python
+def spawn_main(pipe_handle, parent_pid=None, tracker_fd=None):
+ '''
+ Run code specified by data received over pipe
+ '''
+ assert is_forking(sys.argv), "Not forking"
+ if sys.platform == 'win32':
+ import msvcrt
+ import _winapi
+
+ if parent_pid is not None:
+ source_process = _winapi.OpenProcess(
+ _winapi.SYNCHRONIZE | _winapi.PROCESS_DUP_HANDLE,
+ False, parent_pid)
+ else:
+ source_process = None
+ new_handle = reduction.duplicate(pipe_handle,
+ source_process=source_process)
+
+
+ fd = msvcrt.open_osfhandle(new_handle, os.O_RDONLY)
+ parent_sentinel = source_process
+
+
+ else:
+ from . import resource_tracker
+ resource_tracker._resource_tracker._fd = tracker_fd
+
+ fd = pipe_handle
+ parent_sentinel = os.dup(pipe_handle)
+
+
+ exitcode = _main(fd, parent_sentinel)
+ sys.exit(exitcode) # 반환 값을 프로세스의 종료 코드로 사용하고 인터프리터를 종료
+```
+
+이 함수는 fd, parent_sentinel 를 인자로 넘겨 `_main`을 호출하고 반환 값을 프로세스의 종료 코드로 사용한 후 인터프리터를 종료하는 함수이다.
+
+`_main`은 fd 바이트 스트림의 이진 데이터를 역직렬화 하는 함수이다.
+
+**fd:** 직렬화된 이진 데이터를 읽기 위한 `파일 디스크립터`
+
+**parent_sentinel:** 자식 프로세스가 실행되는 도중 부모 프로세스가 종료됐는 지 감시하는 부모 프로세스 감시자 역할, `프로세스 핸들` 혹은 `파일 디스크립터`
+
+두 시스템에서 **parent_sentinel 구현이** **system platform**에 따라 구현이 다른 것을 알 수 있다. 이는 각 시스템의 프로세스를 핸들링 하는 방식이 다르기 때문이다.
+
+이번엔 `_main` 함수에 대해 자세히 알아보자.
+
+
+
+가장 먼저 들어오는 데이터는 준비 데이터를 담고 있는 `dict`다. 그 다음으로 들어오는 건 `SpawnProcess` 인스턴스인데 이후 이 인스턴스의 `_bootstrap()`을 호출한다.
+
+**multiprocessing/spawn.py**
+
+```python
+def _main(fd, parent_sentinel):
+ with os.fdopen(fd, 'rb', closefd=True) as from_parent:
+ process.current_process()._inheriting = True
+ try:
+ preparation_data = reduction.pickle.load(from_parent)
+ prepare(preparation_data)
+ self = reduction.pickle.load(from_parent)
+ finally:
+ del process.current_process()._inheriting
+ return self._bootstrap(parent_sentinel)
+```
+
+`_bootstrap()`은 역직렬화된 데이터로부터 `BaseProcess` 인스턴스를 생성하고 마지막으로 `BaseProcess.run()`으로 실행 대상 함수를 주어진 인자와 함께 호출한다.
+
+```python
+def run(self):
+ '''
+ Method to be run in sub-process; can be overridden in sub-class
+ '''
+ if self._target:
+ self._target(*self._args, **self._kwargs)
+```
+
+(표준 run() 메소드는 객체의 생성자에 target 인자로 전달된 콜러블 객체를 호출하는데 *args* 와 *kwargs* 인자를 각각 위치 인자와 키워드 인자로 사용합니다.)
+
+이후 `self._bootstrap()`이 반환한 종료 코드가 종료 코드로 설정되고 자식 프로세스는 종료된다.
+
+부모 프로세스는 이러한 과정을 통해 모듈과 실행할 함수를 직렬화하고 자식 프로세스도 이 과정을 통해 전송된 인스턴스를 역직렬화하고 함수를 인자와 함께 호출하고 반환한다.
+
+자식 프로세스가 시작되면 더 이상 이렇게 데이터를 교환할 수 없다. 자식 프로세스가 시작된 이후에는 `Queue`와 `Pipe`를 확장한 객체를 이용해 데이터를 교체한다.
+
+프로세스를 풀에서 생성한 경우라면 생성된 첫번째 프로세스는 준비가 완료된 후 대기 상태로 들어가고 부모 프로세스는 이러한 과정을 반복하며 다음 워커에게 데이터를 전송한다.
+
+다음 워커는 데이터를 전달 받은 후 자신의 상태를 초기화하고 실행 대상 함수를 실행한다.
+
+(초기화 후에는 어떤 데이터든 큐와 파이프로만 교환할 수 있다.)
+
+## 큐와 파이프를 사용해 데이터 교환하기
+
+지금까지 자식 프로세스를 스폰하는 방법과 파이프를 직렬화 스트림으로 사용하여 자식 프로세스에 실행할 함수와 그 인자를 전달하는 방법에 대해 알아보았다.
+
+프로세스 간 통신에는 작업 특성에 따라 `큐와 파이프` 두 가지 방법을 사용할 수 있다. 큐와 파이프에 대해 자세히 알아보기 전에 운영체제가 `세마포어`라는 변수를 사용하여 자원을 적절하지 못한 접근으로부터 보호하는 방법에 대해 알아보자.
+
+### 세마포어
+
+다양한 멀티프로세싱 메커니즘들이 자원이 잠겼거나 대기 중이거나 잠기지 않았다는 신호를 보내는 방법으로 세마포어를 사용한다. 운영 체제는 파일과 소켓 등 자원을 잠그기 위한 단순한 가변 타입으로 `이진 세마포어`를 사용한다.
+
+한 프로세스가 파일이나 네트워크 소켓에 쓰고 있을 때, 다른 프로세스가 같은 파일에 쓰기 시작하면 데이터가 바로 손상된다. 이를 방지하기 위해 세마포어를 사용해 잠근다.
+
+### **세마포어의 정의와 작동 방식**
+
+- **P 연산 (acquire 또는 wait)**: 세마포어의 값을 감소시키는 연산이다. 만약 세마포어의 값이 0이면 (즉, 자원이 모두 사용 중이면), 프로세스는 세마포어가 0보다 커질 때까지 (자원이 해제될 때까지) 대기한다.
+ ```c
+ p(sem) {
+ while sem=0 do wait;
+ sem--;
+ }
+ ```
+- **V 연산 (release 또는 signal)**: 세마포어의 값을 증가시키는 연산이다. 이 연산은 다른 프로세스들이 자원을 사용할 수 있도록 세마포어 값을 증가시킨다.
+ ```c
+ v(sem) {
+ sem++;
+ if (대기중인 프로세스가 있다면)
+ 대기 중인 첫 번째 프로세스 동작시킴
+ }
+ ```
+
+세마포어는 **스레드 안정성과 프로세스 안정성을 모두 보장**하기 때문에 Cpython은 멀티프로세싱에 세마포어를 사용한다. 동일한 세마포어에 대한 잠재적인 읽기 또는 쓰기 데드락은 운영체제가 처리한다.
+
+이러한 세마포어 API 구현은 C 확장 모듈 `Module/_multiprocessing/semaphore.c`에서 찾을 수 있다.
+
+이 확장 모듈은 세마포어를 생성, 잠금, 해제할 수 있는 통일된 인터페이스를 제공한다.
+
+운영체제 세마포어 API 호출은 운영 체제에 따라 다른 구현으로 컴파일되는 일련의 매크로를 통해 이루어진다.
+
+윈도우에서는 API 함수들이 매크로에 사용된다.
+
+```c
+#define SEM_CREATE(name, val, max) CreateSemaphore(NULL, val, max, NULL)
+#define SEM_CLOSE(sem) (CloseHandle(sem) ? 0 : -1)
+#define SEM_GETVALUE(sem, pval) _GetSemaphoreValue(sem, pval)
+#define SEM_UNLINK(name) 0
+```
+
+posix에서는 API들이 사용된다.
+
+```c
+#define SEM_CREATE(name, val, max) sem_open(name, O_CREAT | O_EXCL, 0600, val)
+#define SEM_CLOSE(sem) sem_close(sem)
+#define SEM_GETVALUE(sem, pval) sem_getvalue(sem, pval)
+#define SEM_UNLINK(name) sem_unlink(name)
+```
+
+## Queue
+
+큐는 여러 프로세스 간에 작은 데이터를 주고받기 좋은 방법이다.
+
+이전에도 사용한 멀티프로세싱 예제에 `multiprocessing.Manager()` 인스턴스와 두 개의 큐를 적용해 보자.
+
+1. `inputs`로 화씨 데이터를 입력
+2. `outputs`로 변환된 섭씨 데이터를 출력
+
+두 개의 워커를 사용할 수 있도록 풀 크기를 2로 변경하자.
+
+```python
+import multiprocessing as mp
+def to_celsius(input: mp.Queue, output: mp.Queue):
+ f = input.get()
+ # Time-consuming task ...
+ c = (f - 32) * (5/9)
+ output.put(c)
+
+if __name__ == '__main__':
+ mp.set_start_method('spawn')
+ pool_manager = mp.Manager()
+ with mp.Pool(2) as pool:
+ inputs = pool_manager.Queue()
+ outputs = pool_manager.Queue()
+ input_values = list(range(110, 150, 10)) # [110, 120, 130, 140]
+ for i in input_values:
+ inputs.put(i)
+ pool.apply(to_celsius, (inputs, outputs))
+ for f in input_values:
+ print(outputs.get(block=False))
+```
+
+실행하면 `outputs` 큐에서 반환된 튜플의 리스트를 출력한다.
+
+```python
+# output
+43.333333333333336
+48.88888888888889
+54.44444444444445
+60.0
+```
+
+먼저 부모 프로세스가 `inputs` 큐에 입력값을 삽입하면 첫 번째 워커가 큐에서 객체를 꺼낸다. `.get`을 사용해 큐에서 객체를 꺼낼 때 큐 객체는 세마포어 잠금을 사용한다.
+
+
+
+첫번째 워커가 작업 중이라면 두번째 워커가 큐에서 다음 값을 꺼낸다
+
+
+
+첫번째 워커는 계산을 완료하고 결과를 outputs 큐에 삽입한다.
+
+
+
+두 큐는 각각 출력과 입력을 위해 사용된다. 최종적으로는 모든 입력값이 처리되고 `outputs` 큐가 가득채워진다. 이후 부모 프로세스가 결괏값들을 출력한다.
+
+
+
+이 예제를 통해 워커 풀이 큐에서 불연속적으로 수신한 작은 값들을 `병렬로 처리한 후` 그 결과를 호스트 프로세스로 전송하는 방법에 대해 알아보았다.
+
+화씨-섭씨 변환은 사실 병렬 실행에 적합하다고 볼 수 없는 작고 사소한 연산 작업이다. 워커 프로세스가 cpu 집약적인 연산을 수행하는 경우라면 멀티 cpu 또는 멀티코어 컴퓨터에서 큰 성능 향상을 얻을 수 있을 것이다.
+
+### 파이프
+
+`multiprocessing` 패키지는 `Pipe` 타입을 제공한다. 파이프를 인스턴스화하면 부모 쪽 연결과 자식 쪽 연결, 두 개의 연결이 반환된다. 두 연결 모두 데이터를 보낼 수도, 받을 수도 있다.
+
+
+
+예제에 파이프를 적용하려면 `pool.apply()`를 `pool.apply_async()`로 변경해야 한다. 변경하면 다음 프로세스가 논블로킹으로 실행된다.
+
+```python
+import multiprocessing as mp
+
+def to_celsius(child_pipe):
+ f = child_pipe.recv()
+ # Time-consuming task ...
+ c = (f - 32) * (5 / 9)
+ child_pipe.send(c)
+
+if __name__ == '__main__':
+ mp.set_start_method('spawn')
+ pool_manager = mp.Manager()
+ with mp.Pool(2) as pool:
+ parent_pipe, child_pipe = mp.Pipe()
+ results = []
+ for input in range(110, 150, 10):
+ parent_pipe.send(input)
+ results.append(pool.apply_async(to_celsius,
+ args=(child_pipe,)))
+ print(results)
+ print("Got {0:.2f}".format(parent_pipe.recv()))
+ parent_pipe.close()
+ child_pipe.close()
+```
+
+이 줄에서는 여러 프로세스가 동시에 부모 파이프에서 값을 읽어 버릴 수도 있다.
+
+`f = child_pipe.recv()`
+
+또한 이 줄에서는 여러 프로세스가 동시에 자식 파이프에 값을 써 버릴 수도 있다.
+
+`child_pipe.send(c)`
+
+이런 위험상황이 실제로 발생하면 데이터 교환 중에 데이터가 손상될 것이다.
+
+
+
+이런 상황을 막기 위해 운영 체제의 세마포어 잠금을 적용하면 모든 자식 프로세스는 똑같은 파이프에 쓰거나 읽을 때 잠금을 확인한다.
+
+잠금이 필요한 부분은 두 곳이다. 하나는 부모 파이프에서 값을 읽는 부분이고 다른 하나는 자식 파이프에 값을 쓰는 부분이다.
+
+```python
+import multiprocessing as mp
+
+def to_celsius(child_pipe: mp.Pipe, child_lock: mp.Lock):
+ child_lock.acquire(blocking=False)
+ try:
+ f = child_pipe.recv()
+ finally:
+ child_lock.release()
+
+ # Time-consuming task ... release lock before processing
+ c = (f - 32) * (5/9)
+
+ # Reacquire lock when done
+ child_lock.acquire(blocking=False)
+ try:
+ child_pipe.send(c)
+ finally:
+ child_lock.release()
+
+if __name__ == '__main__':
+ mp.set_start_method('spawn')
+ pool_manager = mp.Manager()
+ with mp.Pool(2) as pool:
+ parent_pipe, child_pipe = mp.Pipe()
+ child_lock = pool_manager.Lock()
+ results = []
+ for i in range(110, 150, 10):
+ parent_pipe.send(i)
+ results.append(pool.apply_async(to_celsius, args=(child_pipe, child_lock)))
+
+ for result in results:
+ print(parent_pipe.recv())
+
+ parent_pipe.close()
+ child_pipe.close()
+```
+
+이제 자식 프로세스는 데이터를 읽기 전과 쓰기 전에 `잠금을 기다린다`.
+
+
+
+큰 데이터를 전송할수록 충돌 가능성이 높아지기 때문에 이 예제는 큰 데이터를 파이프로 전송하는 상황에 적합하다.
+
+### 프로세스간 공유 상태
+
+지금까지는 부모와 자식 프로세스 간에 데이터를 주고받는 방법에 대해 알아봤지만 자식 프로세스끼리 데이터를 주고받아야 하는 경우도 분명 있다. 이런 경우에 `multiprocessing` 패키지는 두가지 방법을 제공한다.
+
+1. 고성능 공유 메모리 API: 공유 메모리 맵과 공유 C타입을 사용
+2. 유연한 서버 프로세스 API: Manager 클래스를 통해 복잡한 타입들을 지원한다.
+
+### 애플리케이션 예제
+
+지금부터 이번 장이 끝날 때까지 TCP 포트 스캐너 예제를 리팩터링해가며 다양한 `동시성 및 병렬성` 기법을 사용해 볼 것이다.
+
+네트워크에서는 호스트는 포트에 연결되며 포트번호는 1부터 65535번 까지 제공된다. 표준적인 서비스에는 표준포트가 제공되는데, 예를들어 HTTP는 80번포트에서 작동하고 HTTPS는 443번에서 실행된다. TCP 포트 스캐너는 네트워크로 패킷을 전송할 수 있는지 확인하는 일반적인 네트워크 테스트 도구이다.
+
+이 코드 예제는 멀티프로세싱 예제에서 사용했던 것과 비슷한 스레드 안전한 큐인 `Queue` 인터페이스를 사용하고, `socket` 패키지를 사용해 원격 포트에 **1초 타임아웃**으로 연결을 시도한다.
+
+`check_port()`는 host가 주어진 port에서 응답하는 지 확인한다. 응답할 경우 check_port()는 포트번호를 results 큐에 추가한다.
+
+스크립트는 실행되면 80번 포트부터 100번 포트까지 `check_port()`를 호출하고, 작업이 완료되면 result 큐를 비운 후 결과를 명령줄에 출력한다. 또한 방식별 차이를 확인할 수 있도록 실행에 걸린 시간을 출력한다.
+
+```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()
+
+
+if __name__ == '__main__':
+ start = time.time()
+ host = "localhost" # Replace with a host you own
+ results = Queue()
+ for port in range(80, 100):
+ 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))
+```
+
+실행해 보면 열려있는 포트와 실행된 시간이 출력된다.
+
+```python
+Completed scan in 0.008177042007446289 seconds
+```
+
+멀티 프로세싱을 사용하여 Queue 인터페이스를 `multiprocessing.Queue`로 바꾸고 풀 실행기로 포트 스캔을 실행하도록 리팩터링해보자.
+
+```python
+import multiprocessing as mp
+import socket
+import time
+timeout = 1.0
+def check_port(host: str, port: int, results: mp.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)
+ #time.sleep(1)
+ sock.close()
+
+
+if __name__ == '__main__':
+ start = time.time()
+ processes = []
+ scan_range = range(80, 100)
+ host = "localhost"
+ mp.set_start_method('spawn')
+ pool_manager = mp.Manager()
+ with mp.Pool(len(scan_range)) as pool:
+ outputs = pool_manager.Queue()
+ for port in scan_range:
+ processes.append(pool.apply_async(check_port, (host,
+port, outputs)))
+ for process in processes:
+ process.get()
+ while not outputs.empty():
+ print("Port {0} is open.".format(outputs.get()))
+ print("Completed scan in {0} seconds".format(time.time() -
+start))
+```
+
+기대했던 것처럼 여러 포트를 병렬로 테스트해 속도가 빨라진다.
+
+```python
+Completed scan in 19.171173786407470703 seconds
+```
+
+## 멀티프로세싱 요약
+
+멀티프로세싱은 확장 가능한 파이썬용 `병렬 실행` API를 제공한다. 프로세스 간에 데이터를 공유할 수도 있고 CPU 집약적인 작업을 병렬작업으로 쪼개서 멀티 코어 또는 멀티 CPU 컴퓨터의 장점을 활용할 수도 있다.
+
+`CPU 집약적인 작업`이 아닌 `I/O 집약적인 작업`의 경우에는 멀티프로세싱이 적합하지 않다.
+
+예를들어 워커 프로세스 4개를 스폰하고 같은 파일을 읽고 쓰는 경우(IO Bound Job), 한 프로세스가 작업하고 있는 동안 나머지 프로세스 3개는 잠금이 해제 되기를 기다려야 한다.
+
+또한 멀티프로세싱은 새로운 파이썬 인터프리터를 시작하는 데 필요한 시간과 처리 오버헤드로 인해 짧은 작업에는 그다지 적합하지 않다.
+
+I/O 작업과 짧은 작업의 시나리오에 대해서는 다음 장에 알아볼 멀티 쓰레딩(`thread 모듈`) 방법이 적합하다.
+
+(GIL: 한 시점에 단 하나의 스레드만이 파이썬 객체에 접근하도록 허용하는 메커니즘)
+
+(한 스레드가 I/O 작업 중에는 GIL이 해제되고 다른 스레드가 CPU시간을 사용하므로 효율적)
+
+
diff --git a/book/docs/10_2_thread.md b/book/docs/10_2_thread.md
new file mode 100644
index 0000000..635c710
--- /dev/null
+++ b/book/docs/10_2_thread.md
@@ -0,0 +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/10_3_async.md b/book/docs/10_3_async.md
new file mode 100644
index 0000000..ddf65ae
--- /dev/null
+++ b/book/docs/10_3_async.md
@@ -0,0 +1,899 @@
+# 비동기 프로그래밍
+
+## 1. 비동기 프로그래밍
+
+- 파이썬은 스레드와 멀티프로세싱을 사용하지 않는 동시성 프로그래밍 방법 또한 제공함
+- 발전하며 버전에 따라 추가, 개선, 폐지된 사항들이 존재
+ - Python 3.9
+ - `@coroutine` 폐지
+ - `async` 키워드로 퓨처 생성 가능
+ - `yield from` 키워드로 코루틴 실행 가능
+- 제너레이터
+- 코루틴
+- 비동기 제너레이터
+- 서브 인터프리터
+
+## 2. 제너레이터
+
+- 제너레이터
+ - `yield` 문으로 값을 반환하고 재개되어 값을 추가로 반환할 수 있는 함수
+ - `yield` 문을 통해 값을 하나씩 생성하여 반환하고, 호출될 때마다 이전 상태를 유지하며 실행을 재개하는 함수
+ - `yield` : 현재 값 반환 + 함수 실행 중단
+- 예시
+
+ ```python
+ def letters():
+ i = 97
+ end = 97 + 26
+ while i < end:
+ yield chr(i)
+ i += 1
+
+ for letter in letters():
+ print(letter)
+ ```
+
+- 파일, DB, 네트워크 같은 큰 데이터 블록의 값을 순회할 때 메모리 효율 측면에서 유리함
+ - `return` 문의 경우 반환된 값이 그때그때 메모리에 올라감
+ - `yield` 문의 경우 우선 제너레이터 객체가 생성되어 반환되고 제너레이터 객체를 통해 실제로 반환 값에 접근할 때, 즉 반환 값이 실제로 필요한 순간에 해당 값이 메모리에 올라감
+ → 지연 평가 + 순차적 접근
+- 함수 실행이 중단될 때 함수의 상태가 저장되고 함수 실행이 재개될 때 앞의 상태로부터 실행이 시작됨
+ - 제너레이터 객체 구조체가 마지막으로 실행한 `yield` 문의 프레임 객체를 유지
+- 제너레이터에는 파이썬의 컨테이너 타입(리스트, 튜플 등)과 같이 이터레이터 프로토콜이 구현되어 있음
+ - `__next__()`가 실행되면 실제로 함수가 실행되고 `yield` 키워드를 만나면 실행 중단
+
+### 2.1 제너레이터의 구조
+
+- 제너레이터 객체는 템플릿 매크로 `_PyGenObjec_HEAD(prefix)`에 의해 생성됨
+ - 아래 접두사를 가진 타입들에서 이 매크로가 사용됨
+ - 제너레이터 객체 : `PyGenObject(gi_)`
+ - 코루틴 객체 : `PyCoroObject(cr_)`
+ - 비동기 제너레이터 객체 : `PyAsyncGenObject(ag_)`
+- `Include/genobject.h` > `_PyGenObject_HEAD(prefix)`
+
+ ```c
+ /* _PyGenObject_HEAD defines the initial segment of generator
+ and coroutine objects. */
+ #define _PyGenObject_HEAD(prefix)
+ PyObject_HEAD
+ /* Note: gi_frame can be NULL if the generator is "finished" */
+ PyFrameObject *prefix##_frame;
+ /* True if generator is being executed. */
+ char prefix##_running;
+ /* The code object backing the generator */
+ PyObject *prefix##_code;
+ /* List of weak reference. */
+ PyObject *prefix##_weakreflist;
+ /* Name of the generator. */
+ PyObject *prefix##_name;
+ /* Qualified name of the generator. */
+ PyObject *prefix##_qualname;
+ _PyErr_StackItem prefix##_exc_state;
+ ```
+
+ | 필드명 | 용도 |
+ | --- | --- |
+ | [x]_code | 제너레이터를 제공하는 컴파일된 함수 |
+ | [x]_exc_state | 제너레이터 호출에서 예외가 발생했을 때 예외 데이터 |
+ | [x]_frame | 제너레이터의 현재 프레임 객체 |
+ | [x]_name | 제너레이터의 이름 |
+ | [x]_qualnmae | 제너레이터의 정규화된 이름 |
+ | [x]_running | 제너레이터가 실행중인지 여부 (0/1) |
+ | [x]_weakreflist | 제너레이터 함수 안의 객체들에 대한 약한 참조 리스트 |
+- `Include/genobject.h` > `PyGenObject`
+
+ ```c
+ typedef struct {
+ /* The gi_ prefix is intended to remind of generator-iterator. */
+ _PyGenObject_HEAD(gi)
+ } PyGenObject;
+ ```
+
+- `Include/genobject.h` > `PyCoroObject`
+
+ ```c
+ typedef struct {
+ _PyGenObject_HEAD(cr)
+ PyObject *cr_origin;
+ } PyCoroObject;
+ ```
+
+ | 필드명 | 용도 |
+ | --- | --- |
+ | cr_origin | 코루틴을 호출한 프레임과 함수를 담는 튜플 |
+- `Include/genobject.h` > `PyAsyncGenObject`
+
+ ```c
+ typedef struct {
+ _PyGenObject_HEAD(ag)
+ PyObject *ag_finalizer;
+
+ /* Flag is set to 1 when hooks set up by sys.set_asyncgen_hooks
+ were called on the generator, to avoid calling them more
+ than once. */
+ int ag_hooks_inited;
+
+ /* Flag is set to 1 when aclose() is called for the first time, or
+ when a StopAsyncIteration exception is raised. */
+ int ag_closed;
+
+ int ag_running_async;
+ } PyAsyncGenObject;
+ ```
+
+ | 필드명 | 용도 |
+ | --- | --- |
+ | ag_closed | 제너레이터가 종료됐음을 표시하는 플래그 |
+ | ag_finalizer | 파이널라이저 메서드로의 연결 |
+ | ag_hooks_initied | 훅이 초기화됐음을 표시하는 플래그 |
+ | ag_running_async | 제너레이터가 실행 중임을 표시하는 플래그 |
+- 연관된 소스 파일 목록
+ - `Include/genobject.h`
+ - `Objects/genobject.c`
+
+### 2.2 제너레이터 생성하기
+
+- `yield` 문이 포함된 함수는 컴파일되면 컴파일된 코드 객체에 추가 플래그 `CO_GENERATOR`가 설정됨
+
+ ```python
+ import dis
+
+ def letters():
+ i = 97
+ end = 97 + 26
+ while i < end:
+ yield chr(i)
+ i += 1
+
+ code = letters.__code__
+ flags = code.co_flags
+
+ print(flags)
+ print(dis.COMPILER_FLAG_NAMES[32])
+ print(flags & 32 > 0)
+
+ """
+ 99
+ GENERATOR
+ True
+ """
+ ```
+
+- 제너레이터, 코루틴, 비동기 제너레이터는 컴파일된 코드 객체를 프레임 객체로 변환할 때 특별하게 처리됨
+ - `_PyEval_EvalCode()`는 코드 객체에서 위 플래그를 확인함
+ - 플래그가 설정되어 있다면 평가 함수는 코드 객체를 바로 평가하는 대신 프레임을 만든 후 각각 아래 함수를 사용하여 변환함
+ - 코드
+
+ ```c
+ /* Handle generator/coroutine/asynchronous generator */
+ if (co->co_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR)) {
+ PyObject *gen;
+ int is_coro = co->co_flags & CO_COROUTINE;
+
+ /* Don't need to keep the reference to f_back, it will be set
+ * when the generator is resumed. */
+ Py_CLEAR(f->f_back);
+
+ /* Create a new generator that owns the ready to run frame
+ * and return that as the value. */
+ if (is_coro) {
+ gen = PyCoro_New(f, name, qualname);
+ } else if (co->co_flags & CO_ASYNC_GENERATOR) {
+ gen = PyAsyncGen_New(f, name, qualname);
+ } else {
+ gen = PyGen_NewWithQualName(f, name, qualname);
+ }
+ if (gen == NULL) {
+ return NULL;
+ }
+
+ _PyObject_GC_TRACK(f);
+
+ return gen;
+ }
+ ```
+
+ - `PyGen_NewWithQualName()` → 제너레이터로 변환
+ - `PyCoro_New()` → 코루틴으로 변환
+ - `PyAsyncGen_New()` → 비동기 제너레이터로 변환
+- `PyGen_NewWithQualName()`이 프레임을 통해 제너레이터 객체 필드를 채움
+ - `gi_code` 프로퍼티에 컴파일된 코드 객체 설정
+ - 제너레이터가 실행 중이 아님을 표시 (`gi_running = 0`)
+ - 예외와 약한 참조 리스트를 NULL로 설정
+- 역어셈블로 `gi_code`가 컴파일된 코드 객체임을 확인
+
+ ```python
+ import dis
+
+ def letters():
+ i = 97
+ end = 97 + 26
+ while i < end:
+ yield chr(i)
+ i += 1
+
+ generator = letters()
+ dis.disco(generator.gi_code)
+
+ """
+ 5 0 LOAD_CONST 1 (97)
+ 2 STORE_FAST 0 (i)
+
+ 6 4 LOAD_CONST 2 (123)
+ ...
+ """
+ ```
+
+
+### 2.3 제너레이터 실행하기
+
+- 제너레이터 객체의 `__next__()` 호출
+- `gen_iternext()`가 제너레이터 인스턴스와 함께 호출됨
+- `Objects/genobject.c`의 `gen_send_ex()` 호출
+ - `gen_send_ex()` : 제너레이터 객체를 다음 `yield` 값으로 바꾸는 함수
+ - 코드 객체로부터 프레임을 구성하는 로직과 상당히 비슷함
+ - 제너레이터, 코루틴, 비동기 제너레이터 모두 해당 함수 사용
+- `gen_send_ex()` 실행 순서
+ - 전체 코드
+
+ ```c
+ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
+ {
+ PyThreadState *tstate = _PyThreadState_GET();
+ PyFrameObject *f = gen->gi_frame;
+ PyObject *result;
+
+ if (gen->gi_running) {
+ const char *msg = "generator already executing";
+ if (PyCoro_CheckExact(gen)) {
+ msg = "coroutine already executing";
+ }
+ else if (PyAsyncGen_CheckExact(gen)) {
+ msg = "async generator already executing";
+ }
+ PyErr_SetString(PyExc_ValueError, msg);
+ return NULL;
+ }
+ if (f == NULL || f->f_stacktop == NULL) {
+ if (PyCoro_CheckExact(gen) && !closing) {
+ /* `gen` is an exhausted coroutine: raise an error,
+ except when called from gen_close(), which should
+ always be a silent method. */
+ PyErr_SetString(
+ PyExc_RuntimeError,
+ "cannot reuse already awaited coroutine");
+ }
+ else if (arg && !exc) {
+ /* `gen` is an exhausted generator:
+ only set exception if called from send(). */
+ if (PyAsyncGen_CheckExact(gen)) {
+ PyErr_SetNone(PyExc_StopAsyncIteration);
+ }
+ else {
+ PyErr_SetNone(PyExc_StopIteration);
+ }
+ }
+ return NULL;
+ }
+
+ if (f->f_lasti == -1) {
+ if (arg && arg != Py_None) {
+ const char *msg = "can't send non-None value to a "
+ "just-started generator";
+ if (PyCoro_CheckExact(gen)) {
+ msg = NON_INIT_CORO_MSG;
+ }
+ else if (PyAsyncGen_CheckExact(gen)) {
+ msg = "can't send non-None value to a "
+ "just-started async generator";
+ }
+ PyErr_SetString(PyExc_TypeError, msg);
+ return NULL;
+ }
+ } else {
+ /* Push arg onto the frame's value stack */
+ result = arg ? arg : Py_None;
+ Py_INCREF(result);
+ *(f->f_stacktop++) = result;
+ }
+
+ /* Generators always return to their most recent caller, not
+ * necessarily their creator. */
+ Py_XINCREF(tstate->frame);
+ assert(f->f_back == NULL);
+ f->f_back = tstate->frame;
+
+ gen->gi_running = 1;
+ gen->gi_exc_state.previous_item = tstate->exc_info;
+ tstate->exc_info = &gen->gi_exc_state;
+
+ if (exc) {
+ assert(_PyErr_Occurred(tstate));
+ _PyErr_ChainStackItem(NULL);
+ }
+
+ result = _PyEval_EvalFrame(tstate, f, exc);
+ tstate->exc_info = gen->gi_exc_state.previous_item;
+ gen->gi_exc_state.previous_item = NULL;
+ gen->gi_running = 0;
+
+ /* Don't keep the reference to f_back any longer than necessary. It
+ * may keep a chain of frames alive or it could create a reference
+ * cycle. */
+ assert(f->f_back == tstate->frame);
+ Py_CLEAR(f->f_back);
+
+ /* If the generator just returned (as opposed to yielding), signal
+ * that the generator is exhausted. */
+ if (result && f->f_stacktop == NULL) {
+ if (result == Py_None) {
+ /* Delay exception instantiation if we can */
+ if (PyAsyncGen_CheckExact(gen)) {
+ PyErr_SetNone(PyExc_StopAsyncIteration);
+ }
+ else {
+ PyErr_SetNone(PyExc_StopIteration);
+ }
+ }
+ else {
+ /* Async generators cannot return anything but None */
+ assert(!PyAsyncGen_CheckExact(gen));
+ _PyGen_SetStopIterationValue(result);
+ }
+ Py_CLEAR(result);
+ }
+ else if (!result && PyErr_ExceptionMatches(PyExc_StopIteration)) {
+ const char *msg = "generator raised StopIteration";
+ if (PyCoro_CheckExact(gen)) {
+ msg = "coroutine raised StopIteration";
+ }
+ else if (PyAsyncGen_CheckExact(gen)) {
+ msg = "async generator raised StopIteration";
+ }
+ _PyErr_FormatFromCause(PyExc_RuntimeError, "%s", msg);
+
+ }
+ else if (!result && PyAsyncGen_CheckExact(gen) &&
+ PyErr_ExceptionMatches(PyExc_StopAsyncIteration))
+ {
+ /* code in `gen` raised a StopAsyncIteration error:
+ raise a RuntimeError.
+ */
+ const char *msg = "async generator raised StopAsyncIteration";
+ _PyErr_FormatFromCause(PyExc_RuntimeError, "%s", msg);
+ }
+
+ if (!result || f->f_stacktop == NULL) {
+ /* generator can't be rerun, so release the frame */
+ /* first clean reference cycle through stored exception traceback */
+ _PyErr_ClearExcState(&gen->gi_exc_state);
+ gen->gi_frame->f_gen = NULL;
+ gen->gi_frame = NULL;
+ Py_DECREF(f);
+ }
+
+ return result;
+ }
+ ```
+
+ 1. 현재 스레드 상태를 가져옴
+ 2. 제너레이터 객체로부터 프레임 객체를 가져옴
+
+ ```c
+ PyThreadState *tstate = _PyThreadState_GET();
+ PyFrameObject *f = gen->gi_frame;
+ ```
+
+ 3. 제너레이터가 실행 중이면 `ValueError`를 발생시킴
+
+ ```c
+ if (gen->gi_running) {
+ const char *msg = "generator already executing";
+ if (PyCoro_CheckExact(gen)) {
+ msg = "coroutine already executing";
+ }
+ else if (PyAsyncGen_CheckExact(gen)) {
+ msg = "async generator already executing";
+ }
+ PyErr_SetString(PyExc_ValueError, msg);
+ return NULL;
+ }
+ ```
+
+ 4. 제너레이터 안의 프레임이 스택의 최상위에 위치해 있을 경우 처리
+
+ ```c
+ if (f == NULL || f->f_stacktop == NULL) {
+ if (PyCoro_CheckExact(gen) && !closing) {
+ /* `gen` is an exhausted coroutine: raise an error,
+ except when called from gen_close(), which should
+ always be a silent method. */
+ PyErr_SetString(
+ PyExc_RuntimeError,
+ "cannot reuse already awaited coroutine");
+ }
+ else if (arg && !exc) {
+ /* `gen` is an exhausted generator:
+ only set exception if called from send(). */
+ if (PyAsyncGen_CheckExact(gen)) {
+ PyErr_SetNone(PyExc_StopAsyncIteration);
+ }
+ else {
+ PyErr_SetNone(PyExc_StopIteration);
+ }
+ }
+ return NULL;
+ }
+ ```
+
+ 5. 프레임이 막 실행되기 시작해서 마지막 명령이 아직 -1이고 프레임이 코루틴이나 비동기 제너레이터인 경우 None 이외의 값을 인자로 넘기면 예외 발생
+ 6. 인자를 프레임의 값 스택에 추가
+
+ ```c
+ if (f->f_lasti == -1) {
+ if (arg && arg != Py_None) {
+ const char *msg = "can't send non-None value to a "
+ "just-started generator";
+ if (PyCoro_CheckExact(gen)) {
+ msg = NON_INIT_CORO_MSG;
+ }
+ else if (PyAsyncGen_CheckExact(gen)) {
+ msg = "can't send non-None value to a "
+ "just-started async generator";
+ }
+ PyErr_SetString(PyExc_TypeError, msg);
+ return NULL;
+ }
+ } else {
+ /* Push arg onto the frame's value stack */
+ result = arg ? arg : Py_None;
+ Py_INCREF(result);
+ *(f->f_stacktop++) = result;
+ }
+ ```
+
+ 7. 프레임의 `f_back` 필드는 반환값을 전송할 호출자이기 때문에 이 필드에는 스레드의 현재 프레임이 설정됨 → 제너레이터를 생성한 곳이 아닌 제너레이터를 호출한 곳에 값이 반환됨
+
+ ```c
+ /* Generators always return to their most recent caller, not
+ * necessarily their creator. */
+ Py_XINCREF(tstate->frame);
+ assert(f->f_back == NULL);
+ f->f_back = tstate->frame;
+ ```
+
+ 8. 제너레이터가 실행 중임을 표시
+ 9. 제너레이터의 마지막 예외가 스레드 상태의 마지막 예외로 복사됨
+ 10. 스레드 상태의 예외 정보가 제너레이터의 예외 정보를 가리키도록 설정됨 → 호출자가 제너레이터 실행부 주변에 중단점을 추가할 때 스택트레이스가 제너레이터를 통과해 문제가 되는 코드가 명확해짐
+
+ ```c
+ gen->gi_running = 1;
+ gen->gi_exc_state.previous_item = tstate->exc_info;
+ tstate->exc_info = &gen->gi_exc_state;
+ ```
+
+ 11. `Python/ceval.c`의 평가 루프에서 제너레이터 안의 프레임을 실행하고 값을 반환
+ 12. 스레드 상태의 마지막 예외 정보가 프레임 호출 전의 값으로 복구됨
+ 13. 제너레이터가 실행 중이 아니라고 표시됨
+
+ ```c
+ result = _PyEval_EvalFrame(tstate, f, exc);
+ tstate->exc_info = gen->gi_exc_state.previous_item;
+ gen->gi_exc_state.previous_item = NULL;
+ gen->gi_running = 0;
+ ```
+
+ 14. 반환값에 따라 예외를 발생시킴
+ 15. 결과가 `__next__()` 호출자에 반환됨
+
+ ```c
+ /* If the generator just returned (as opposed to yielding), signal
+ * that the generator is exhausted. */
+ if (result && f->f_stacktop == NULL) {
+ if (result == Py_None) {
+ /* Delay exception instantiation if we can */
+ if (PyAsyncGen_CheckExact(gen)) {
+ PyErr_SetNone(PyExc_StopAsyncIteration);
+ }
+ else {
+ PyErr_SetNone(PyExc_StopIteration);
+ }
+ }
+ else {
+ /* Async generators cannot return anything but None */
+ assert(!PyAsyncGen_CheckExact(gen));
+ _PyGen_SetStopIterationValue(result);
+ }
+ Py_CLEAR(result);
+ }
+ else if (!result && PyErr_ExceptionMatches(PyExc_StopIteration)) {
+ const char *msg = "generator raised StopIteration";
+ if (PyCoro_CheckExact(gen)) {
+ msg = "coroutine raised StopIteration";
+ }
+ else if (PyAsyncGen_CheckExact(gen)) {
+ msg = "async generator raised StopIteration";
+ }
+ _PyErr_FormatFromCause(PyExc_RuntimeError, "%s", msg);
+
+ }
+ else if (!result && PyAsyncGen_CheckExact(gen) &&
+ PyErr_ExceptionMatches(PyExc_StopAsyncIteration))
+ {
+ /* code in `gen` raised a StopAsyncIteration error:
+ raise a RuntimeError.
+ */
+ const char *msg = "async generator raised StopAsyncIteration";
+ _PyErr_FormatFromCause(PyExc_RuntimeError, "%s", msg);
+ }
+
+ if (!result || f->f_stacktop == NULL) {
+ /* generator can't be rerun, so release the frame */
+ /* first clean reference cycle through stored exception traceback */
+ _PyErr_ClearExcState(&gen->gi_exc_state);
+ gen->gi_frame->f_gen = NULL;
+ gen->gi_frame = NULL;
+ Py_DECREF(f);
+ }
+
+ return result;
+ ```
+
+
+## 3. 코루틴
+
+- 코루틴
+ - 실행을 중단하고 재개할 수 있는 함수, 특정 시점에서 작업을 중단하고 다른 작업을 수행할 수 있음
+ - 호출자와 호출된 함수 사이의 제어 흐름이 상호작용
+ - 비동기 프로그래밍에서 사용
+
+### 3.1 제너레이터 기반 코루틴
+
+- 제너레이터는 함수 실행을 중지하고 재개할 수 있음 → 이러한 동작에 기반을 두고 제너레이터 기반 코루틴이 만들어짐
+- 값을 생성하여 호출자에게 전달하기만 하는 제너레이터와 달리 코루틴은 호출자와 양방향 통신 가능 (`send()` 메서드 사용)
+- 제너레이터의 한계는 직접적인 호출자에게만 값을 제공할 수 있다는 것 → `yield from`을 통해 극복
+ - 제너레이터 함수를 유틸리티 함수로 리팩토링 가능
+ - 다른 제너레이터로부터 값을 받아온 뒤 현재 제너레이터의 호출자에게 반환
+ - 예시
+
+ ```python
+ def gen_letters():
+ i = 97
+ end = 97 + 26
+ while i < end:
+ yield chr(i)
+ i += 1
+
+ def letters(upper):
+ if upper:
+ yield from gen_letters(65, 26)
+ else:
+ yield from gen_letters(97, 26)
+
+ for letter in letters(True):
+ print(letter)
+
+ for letter in letters(False):
+ print(letter)
+ ```
+
+- `yield from`을 통해 다른 코루틴에 값을 전달할 수 있으므로 복잡한 비동기 코드 관리에 유용
+- 데코레이터를 사용하여 구현 → 네이티브 코루틴이 선호되어 더이상 쓰지 않음
+
+### 3.2 네이티브 코루틴
+
+- 함수 앞에 `async` 키워드를 명시하여 코루틴을 반환함을 표시
+- `asyncio.run()`을 통해 코루틴 객체 실행
+ - 코드 (`Lib/asyncio/runners.py`)
+
+ ```python
+ def run(main, *, debug=None):
+ """Execute the coroutine and return the result.
+
+ This function runs the passed coroutine, taking care of
+ managing the asyncio event loop and finalizing asynchronous
+ generators.
+
+ This function cannot be called when another asyncio event loop is
+ running in the same thread.
+
+ If debug is True, the event loop will be run in debug mode.
+
+ This function always creates a new event loop and closes it at the end.
+ It should be used as a main entry point for asyncio programs, and should
+ ideally only be called once.
+
+ Example:
+
+ async def main():
+ await asyncio.sleep(1)
+ print('hello')
+
+ asyncio.run(main())
+ """
+ if events._get_running_loop() is not None:
+ raise RuntimeError(
+ "asyncio.run() cannot be called from a running event loop")
+
+ if not coroutines.iscoroutine(main):
+ raise ValueError("a coroutine was expected, got {!r}".format(main))
+
+ loop = events.new_event_loop()
+ try:
+ events.set_event_loop(loop)
+ if debug is not None:
+ loop.set_debug(debug)
+ return loop.run_until_complete(main)
+ finally:
+ try:
+ _cancel_all_tasks(loop)
+ loop.run_until_complete(loop.shutdown_asyncgens())
+ loop.run_until_complete(loop.shutdown_default_executor())
+ finally:
+ events.set_event_loop(None)
+ loop.close()
+ ```
+
+ - 실행 순서
+ 1. 새 이벤트 루프를 시작
+ 2. 코루틴 객체를 태스크로 감싸기
+ 3. 태스크가 완료될 때 실행할 콜백을 설정
+ 4. 태스크가 완료될 때까지 루프를 반복
+ 5. 결과 반환
+- 예시
+
+ ```python
+ import asyncio
+
+ async def sleepy_alarm(time):
+ await asyncio.sleep(time)
+ return "wake up!"
+
+ alarm = asyncio.run(sleepy_alarm(5))
+ print(alarm)
+ ```
+
+- 장점
+ - 여러 코루틴 동시 실행 가능
+ - 코루틴 객체를 다른 함수의 인자로 사용 가능 → 코루틴 객체를 서로 연결하여 연쇄적으로 처리하거나 순차적으로 생성 가능
+- 연관된 소스 파일 목록
+ - `Lib/asyncio`
+
+### 3.3 이벤트 루프
+
+- 비동기 코드를 연결하는 접착제 역할
+- 순수한 파이썬으로 작성된 이벤트 루프는 태스크를 보관하는 객체
+ - `asyncio.Task` 타입으로 표현되는 일련의 태스크로 이루어짐
+ - 태스크를 루프에 예약 → 루프가 한 번 실행되면 모든 태스크가 완료될 때까지 태스크 순회
+ - 태스크에 콜백을 등록하여 작업이 완료되거나 실패하면 이벤트 루프가 등록된 콜백을 실행시킴
+- `yield` 키워드가 같은 프레임에서 여러 값을 반환할 수 있는 것처럼 `await` 키워드도 여러 상태 반환 가능
+- 예시
+
+ ```python
+ import asyncio
+
+ async def sleepy_alarm(person, time):
+ await asyncio.sleep(time)
+ print(f"{person} -- wake up!")
+
+ async def wake_up_gang():
+ tasks = [
+ asyncio.create_task(sleepy_alarm("Bob", 3), name="wake up Bob"),
+ asyncio.create_task(sleepy_alarm("Andy", 4), name="wake up Andy"),
+ asyncio.create_task(sleepy_alarm("Sam", 2), name="wake up Sam")
+ ]
+ await asyncio.gather(*tasks)
+
+ asyncio.run(wake_up_gang())
+
+ """
+ Sam -- wake up!
+ Bob -- wake up!
+ Andy -- wake up!
+ """
+ ```
+
+
+## 4. 비동기 제너레이터
+
+- 제너레이터와 네이티브 코루틴의 개념을 하나로 합친 것
+- 함수가 `async` 키워드를 사용하여 선언되었고 `yield` 문을 포함한다면 호출 시 비동기 제너레이터로 변환
+- 제너레이터처럼 이터레이터 프로토콜을 사용하여 실행됨
+ - 제너레이터 : `for` / `__next__()`
+ - 비동기 제너레이터 : `async for` / `__anext__()`
+
+## 5. 서브인터프리터
+
+- 멀티프로세싱을 활용한 병렬 실행
+ - 파이프와 큐를 사용하여 공유 메모리 대비 느린 프로세스 간 통신 방식 사용
+ - 새 프로세스 시작 시 오버헤드
+- 스레드와 비동기를 활용한 동시 실행
+ - 오버헤드가 적지만 스레드 안전성을 제공하는 GIL로 인해 진정한 병렬 실행이 아님
+- 서브인터프리터 : 여러 개의 독립적인 인터프리터 인스턴스를 생성하여 사용
+ - 진정한 병렬 실행인 동시에 멀티프로세싱에 비해 오버헤드가 적음
+- `Py_NewInterpreter()`와 같은 저수준 인터프리터 생성 C API 제공
+- 모든 파이썬 객체의 포인터가 보관되는 메모리 할당 아레나를 인터프리터 상태에서 관리하므로 각 인터프리터는 다른 인터프리터의 변수에 접근 불가
+ - 인터프리터 간 객체 공유를 위해서는 직렬화하거나 `ctypes`를 사용하고 IPC 형태로 공유해야 함
+- 연관된 소스 파일 목록
+ - `Lib/_xxsubinterpreters.c`
+ - `Python/pylifecycle.c`
+
+## 6. Port Scanner: `asyncio`, Async Generator, Subinterpreter
+
+### 6.1 멀티 스레딩
+
+```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.5초
+
+### 6.2 `asyncio`
+
+```python
+import time
+import asyncio
+
+timeout = 1.0
+
+async def check_port(host: str, port: int, results: list):
+ try:
+ future = asyncio.open_connection(host=host, port=port)
+ r, w = await asyncio.wait_for(future, timeout=timeout)
+ results.append(port)
+ w.close()
+ except OSError: # do not throw an exception when port is not open
+ pass
+ except asyncio.TimeoutError:
+ pass # closed
+
+async def scan(start, end, host):
+ tasks = []
+ results = []
+ for port in range(start, end):
+ tasks.append(check_port(host, port, results))
+ await asyncio.gather(*tasks)
+ return results
+
+if __name__ == '__main__':
+ start = time.time()
+ host = "localhost"
+ results = asyncio.run(scan(30000, 65536, host))
+ for result in results:
+ print("Port {0} is open".format(result))
+ print("Completed scan in {0} seconds".format(time.time() - start))
+```
+
+- 4 ~4.8초
+- 멀티 스레딩 코드와 다른 결과 출력
+
+### 6.3 비동기 제너레이터
+
+```python
+import time
+import asyncio
+
+timeout = 1.0
+
+async def check_ports(host: str, start: int, end: int, max=10):
+ found = 0
+ for port in range(start, end):
+ try:
+ future = asyncio.open_connection(host=host, port=port)
+ r, w = await asyncio.wait_for(future, timeout=timeout)
+ yield port
+ found += 1
+ w.close()
+ if found >= max:
+ return
+ except OSError:
+ pass
+ except ConnectionRefusedError:
+ pass
+ except asyncio.TimeoutError:
+ pass # closed
+
+async def scan(start, end, host):
+ results = []
+ async for port in check_ports(host, start, end, max=1):
+ results.append(port)
+ return results
+
+if __name__ == '__main__':
+ start = time.time()
+ host = "localhost"
+ scan_range = 30000, 65536
+ results = asyncio.run(scan(*scan_range, host))
+ for result in results:
+ print("Port {0} is open".format(result))
+ print("Completed scan in {0} seconds".format(time.time() - start))
+```
+
+- 1.5 ~ 1.9초
+- 멀티 스레딩 코드와 다른 결과 출력
+
+### 6.4 서브 인터프리터
+
+```python
+import time
+import _xxsubinterpreters as subinterpreters
+from threading import Thread
+import textwrap as tw
+from queue import Queue
+
+timeout = 1 # in seconds..
+
+def run(host: str, port: int, results: Queue):
+ # Create a communication channel
+ channel_id = subinterpreters.channel_create()
+ interpid = subinterpreters.create()
+ subinterpreters.run_string(
+ interpid,
+ tw.dedent(
+ """
+ import socket; import _xxsubinterpreters as subinterpreters
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.settimeout(timeout)
+ result = sock.connect_ex((host, port))
+ subinterpreters.channel_send(channel_id, result)
+ sock.close()
+ """),
+ shared=dict(
+ channel_id=channel_id,
+ host=host,
+ port=port,
+ timeout=timeout
+ ))
+ output = subinterpreters.channel_recv(channel_id)
+ subinterpreters.channel_release(channel_id)
+ if output == 0:
+ results.put(port)
+
+if __name__ == '__main__':
+ start = time.time()
+ host = "localhost" # pick a friend
+ threads = []
+ results = Queue()
+ for port in range(30000, 65536):
+ t = Thread(target=run, 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))
+```
diff --git a/book/docs/11_0_object_and_type.md b/book/docs/11_0_object_and_type.md
new file mode 100644
index 0000000..01dce68
--- /dev/null
+++ b/book/docs/11_0_object_and_type.md
@@ -0,0 +1,887 @@
+# 11. 객체와 타입
+
+## 11.1 Python에서의 객체란?
+
+(참고: https://docs.python.org/ko/3.9/reference/datamodel.html)
+
+객체(Objects)는 데이터를 추상화한 것으로, Python 내에서 모든 데이터는 객체와 객체 간의 관계로 표현합니다.
+(폰 노이만의 “프로그램 내장식 컴퓨터(stored program computer)” 모델을 따릅니다.)
+
+> “프로그램 내장식 컴퓨터(stored program computer)” 모델
+> : 컴퓨터 내부에 프로그램과 데이터를 저장 → 컴퓨터가 필요한 내용을 순서에 따라 인출하고 해독 → 프로그램 자체와 데이터를 동일한 형태로 메모리에 저장할 수 있는 구조
+
+모든 객체는 identity, type, value를 가집니다.
+- identity
+ : 메모리 상에서의 객체 주소로, `id()` 통해 확인 가능합니다.
+- type
+ : 각 타입별 소스 파일은 `Objects`에 저장되어 있으며, 소스 파일에 대한 헤더 파일은 `Include`에 포함되어 있습니다.
+- value
+ : 객체에 대한 값으로, 변경 가능 여부에 따라 가변(mutable) / 불변(immutable) 으로 분류됩니다.
+
+내장된 대표 객체 목록은 다음과 같습니다.
+- None
+- Ellipsis (…)
+- numbers.Number
+ - numbers.Integral: int, bool
+ - numbers.Real
+ - numbers.Complex
+- Sequences
+ - 불변: string, tuple, byte
+ - 가변: list, byte array
+- Set types
+ - set
+ - frozen set
+- Mappings
+ - dictionary
+
+## 11.2 객체 구조체 (기본 & 가변 객체 타입)
+
+(참고: https://docs.python.org/3/c-api/structures.html)
+
+
+### PyObject
+
+모든 파이썬 객체의 기본 구조체로, 크기가 고정된 immutable 객체에 사용합니다.
+ (ex: `int`, `float`)
+
+```c
+// Include/object.h
+
+/* Nothing is actually declared to be a PyObject,
+ but every pointer to a Python object can be cast to a PyObject*.
+ This is inheritance built by hand.
+ Similarly every pointer to a variable-size Python object can,
+ in addition, be cast to PyVarObject*.
+ */
+typedef struct _object {
+ _PyObject_HEAD_EXTRA
+ Py_ssize_t ob_refcnt; // 인스턴스 레퍼런스 카운터
+ [PyTypeObject](https://www.notion.so/Chapter-11-d7671cb0ccd049a780f58d2191efee53?pvs=21) *ob_type; // 객체 타입
+} PyObject;
+```
+
+`_PyObject_HEAD_EXTRA`는 모든 파이썬 객체를 doubly linked list로 만들어서 → 메모리 누수 또는 객체 생성/소멸 문제 디버깅 시 사용합니다.
+ (디버깅 모드에서만 사용)
+
+```c
+/* Define pointers to support a doubly-linked list of all live heap objects. */
+#define _PyObject_HEAD_EXTRA \
+ struct _object *_ob_next; \
+ struct _object *_ob_prev;
+```
+
+타입 정의 시에는 `PyObject_HEAD`를 사용합니다.
+
+```c
+/* PyObject_HEAD defines the initial segment of every PyObject. */
+#define PyObject_HEAD PyObject ob_base;
+```
+
+- ex) PyFloatObject
+ ```c
+ // Include/floatobject.h
+ typedef struct {
+ **PyObject_HEAD**
+ double ob_fval; // 실제 float 값 정의
+ } PyFloatObject;
+ ```
+
+### PyVarObject
+
+가변 크기 객체의 기본 구조체로, 길이가 런타임에 변할 수 있는 mutable 객체에 사용합니다.
+ (ex: `list`, `str`, `tuple`)
+
+```c
+// Include/object.h
+typedef struct {
+ PyObject ob_base; // 기반 타입
+ Py_ssize_t ob_size; // 객체 크기
+} PyVarObject;
+```
+
+타입 정의 시에는 `PyObject_VAR_HEAD`를 사용합니다.
+
+```c
+/* PyObject_VAR_HEAD defines the initial segment of all variable-size container objects.
+ These end with a declaration of an array with 1 element,
+ but enough space is malloc'ed so that the array actually has room for ob_size elements.
+ Note that ob_size is an element count, not necessarily a byte count. */
+#define PyObject_VAR_HEAD PyVarObject ob_base;
+```
+
+- ex) PyLongObject
+ ```c
+ // Include/longobject.h
+ typedef struct _longobject PyLongObject; /* Revealed in longintrepr.h */
+
+ // Include/longintrepr.h
+ struct _longobject {
+ **PyObject_VAR_HEAD**
+ digit ob_digit[1];
+ };
+ ```
+
+
+## 11.3 PyTypeObject: 객체 type 표현
+
+(참고: https://docs.python.org/3.9/c-api/typeobj.html)
+
+PyObject 정의에서 해당 객체가 어떤 type을 나타내는지 표현하기 위해 사용하며, 객체의 모든 행동(메소드, 속성 접근, 생성, 소멸 등)을 정의합니다.
+
+```c
+typedef struct _object {
+ _PyObject_HEAD_EXTRA
+ Py_ssize_t ob_refcnt; // 인스턴스 레퍼런스 카운터
+ **PyTypeObject *ob_type;** // 객체 타입
+} PyObject;
+```
+
+파이썬의 "모든 것이 객체"라는 철학의 핵심 하에, `PyTypeObject` 자체도 `PyObject` 형식으로 볼 수 있습니다.
+
+```c
+/* PyTypeObject structure is defined in cpython/object.h.
+ In Py_LIMITED_API, PyTypeObject is an opaque structure. */
+typedef struct _typeobject PyTypeObject;
+```
+
+
+### type slot (PyTypeObject 구조체 필드)
+
+PyTypeObject 구조체 내 필드를 나타내며, 각 필드는 특정 기능을 구현하는 함수에 대한 포인터를 가리킵니다.
+`type()`을 통해 PyTypeObject 인스턴스를 반환하며, `__init__`, `__repr__` 등 특수 메소드 등에 대응합니다.
+
+```c
+// Include/cpython/object.h
+struct _typeobject {
+ PyObject_VAR_HEAD
+ const char *tp_name; /* type명 ("." 형태) */
+ Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */
+
+ /* Methods to implement standard operations */
+
+ destructor tp_dealloc;
+ Py_ssize_t tp_vectorcall_offset;
+ getattrfunc tp_getattr;
+ setattrfunc tp_setattr;
+ PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
+ or tp_reserved (Python 3) */
+ reprfunc tp_repr;
+
+ /* Method suites for standard classes */
+
+ PyNumberMethods *tp_as_number;
+ PySequenceMethods *tp_as_sequence;
+ PyMappingMethods *tp_as_mapping;
+
+ /* More standard operations (here for binary compatibility) */
+
+ hashfunc tp_hash;
+ ternaryfunc tp_call;
+ reprfunc tp_str;
+ getattrofunc tp_getattro;
+ setattrofunc tp_setattro;
+
+ /* Functions to access object as input/output buffer */
+ PyBufferProcs *tp_as_buffer;
+
+ /* Flags to define presence of optional/expanded features */
+ unsigned long tp_flags;
+
+ const char *tp_doc; /* type 설명 문서 문자열 */
+
+ /* Assigned meaning in release 2.0 */
+ /* call function for all accessible objects */
+ traverseproc tp_traverse;
+
+ /* delete references to contained objects */
+ inquiry tp_clear;
+
+ /* Assigned meaning in release 2.1 */
+ /* rich comparisons */
+ richcmpfunc tp_richcompare;
+
+ /* weak reference enabler */
+ Py_ssize_t tp_weaklistoffset;
+
+ /* Iterators */
+ getiterfunc tp_iter;
+ iternextfunc tp_iternext;
+
+ /* Attribute descriptor and subclassing stuff */
+ struct PyMethodDef *tp_methods;
+ struct PyMemberDef *tp_members;
+ struct PyGetSetDef *tp_getset;
+ struct _typeobject *tp_base;
+ PyObject *tp_dict;
+ descrgetfunc tp_descr_get;
+ descrsetfunc tp_descr_set;
+ Py_ssize_t tp_dictoffset;
+ initproc tp_init;
+ allocfunc tp_alloc;
+ newfunc tp_new;
+ freefunc tp_free; /* Low-level free-memory routine */
+ inquiry tp_is_gc; /* For PyObject_IS_GC */
+ PyObject *tp_bases;
+ PyObject *tp_mro; /* method resolution order */
+ PyObject *tp_cache;
+ PyObject *tp_subclasses;
+ PyObject *tp_weaklist;
+ destructor tp_del;
+
+ /* Type attribute cache version tag. Added in version 2.6 */
+ unsigned int tp_version_tag;
+
+ destructor tp_finalize;
+ vectorcallfunc tp_vectorcall;
+};
+```
+
+#### type slot 목록
+- 기본 정보
+ - `const char *tp_name`: 타입의 이름 (예: `"collections.defaultdict"`)
+ - `const char *tp_doc`: 타입의 문서 문자열 (`help(type)`으로 확인 가능)
+- 객체 생명주기
+ - `newfunc tp_new`: 객체 생성 (`__new__`)
+ - `initproc tp_init`: 객체 초기화 (`__init__`)
+ - `destructor tp_dealloc`: 메모리 해제 (객체가 소멸될 때 호출)
+- 문자열 표현
+ - `reprfunc tp_repr`: 개발자용 문자열 표현 (`__repr__`)
+ - `reprfunc tp_str`: 사용자 친화적 문자열 표현 (`__str__`)
+- 속성 및 메서드 접근
+ - `getattrofunc tp_getattro`: 속성 접근 (`__getattribute__`)
+ - `setattrofunc tp_setattro`: 속성 설정 (`__setattr__`)
+ - `struct PyMethodDef *tp_methods`: method 정의 테이블
+- 연산자 및 특수 method
+ - `PyNumberMethods *tp_as_number`: 숫자 연산 (+, - 등)
+ - `PySequenceMethods *tp_as_sequence`: 시퀀스 연산 (`len()`, 인덱싱 등)
+ - `hashfunc tp_hash`: 해시 값 계산 (`__hash__`)
+ - `ternaryfunc tp_call`: 객체를 함수처럼 호출 (`__call__`)
+- 상속 및 타입 관계
+ - `struct _typeobject *tp_base`: 기본 클래스
+ - `PyObject *tp_bases`: 기본 클래스들의 튜플
+ - `PyObject *tp_mro`: method 해결 순서 (Method Resolution Order)
+
+#### type slot 종류
+- `tp_`: 기본 type slot
+- `nb_`: 숫자 연산 관련 method
+ - nb_add: +
+ - nb_subtract: -
+ - nb_multiply: *
+ - nb_true_divide: /
+ - nb_power: **
+- `sq_`: sequence 연산 관련 method
+ - sq_length: len()
+ - sq_concat: +로 sequence 연결
+ - sq_repeat: *로 sequence 반복
+ - sq_item: [] 통한 인덱싱
+- `mp_`: mapping(dictionary) 연산 관련 method
+ - mp_length: len()
+ - mp_subscript: []로 key 접근
+- `am_`: async 관련 method
+ - am_await: __await__
+ - am_aiter: __aiter__
+ - am_anext: __anext__
+- `bf_`: buffer protocol 관련 method (bytes, bytearray 등에서 사용)
+ - bf_getbuffer: __buffer__
+ - bf_releasebuffer: buffer 해제
+
+## 11.4 타입별 내부 구조 살펴보기
+
+### bool
+
+- long을 상속해서 만들어집니다. (구조체가 따로 정의되어 있지 않고, PyLongObject를 사용하여 정의됩니다.)
+- 불변 인스턴스 상수 Py_True / Py_False 로 표시합니다.
+- type 정의 (PyBool_Type)
+
+ ```c
+ // Objects/boolobject.c
+ PyTypeObject PyBool_Type = {
+ PyVarObject_HEAD_INIT(&PyType_Type, 0)
+ "bool",
+ sizeof(struct _longobject),
+ 0,
+ 0, /* tp_dealloc */
+ 0, /* tp_vectorcall_offset */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_as_async */
+ bool_repr, /* tp_repr */
+ &bool_as_number, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT, /* tp_flags */
+ bool_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ **&PyLong_Type, /* tp_base */**
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ **bool_new, /* tp_new */**
+ };
+ ```
+
+ - bool_new (tp_new)
+
+ ```c
+ // Objects/boolobject.c
+ static PyObject *
+ bool_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+ {
+ PyObject *x = Py_False;
+ long ok;
+
+ if (!_PyArg_NoKeywords("bool", kwds))
+ return NULL;
+ if (!PyArg_UnpackTuple(args, "bool", 0, 1, &x))
+ return NULL;
+ **ok = PyObject_IsTrue(x);**
+ if (ok < 0)
+ return NULL;
+ **return PyBool_FromLong(ok);**
+ }
+ ```
+
+ - PyObject_IsTrue
+
+ ```c
+ // Objects/object.c
+ int
+ PyObject_IsTrue(PyObject *v)
+ {
+ Py_ssize_t res;
+ if (v == Py_True)
+ return 1;
+ if (v == Py_False)
+ return 0;
+ if (v == Py_None)
+ return 0;
+
+ else if (Py_TYPE(v)->tp_as_number != NULL &&
+ Py_TYPE(v)->tp_as_number->nb_bool != NULL)
+ // 숫자 타입이고 nb_bool이 정의되어 있으면 -> nb_bool 결과 저장
+ res = (*Py_TYPE(v)->tp_as_number->nb_bool)(v);
+
+ else if (Py_TYPE(v)->tp_as_mapping != NULL &&
+ Py_TYPE(v)->tp_as_mapping->mp_length != NULL)
+ // 매핑 타입이고 mp_length가 정의되어 있으면 -> mp_length 결과 저장
+ res = (*Py_TYPE(v)->tp_as_mapping->mp_length)(v);
+
+ else if (Py_TYPE(v)->tp_as_sequence != NULL &&
+ Py_TYPE(v)->tp_as_sequence->sq_length != NULL)
+ // 시퀀스 타입이고 sq_length가 정의되어 있으면 -> sq_length 결과 저장
+ res = (*Py_TYPE(v)->tp_as_sequence->sq_length)(v);
+
+ else
+ // 위에 다 해당하지 않으면 1 반환
+ return 1;
+
+ /* if it is negative, it should be either -1 or -2 */
+ // 0보다 크면 1을 반환 / 0또는 음수이면 int로 변환해서 반환
+ return (res > 0) ? 1 : Py_SAFE_DOWNCAST(res, Py_ssize_t, int);
+ }
+ ```
+
+ - PyBool_FromLong
+
+ ```c
+ // Objects/boolobject.c
+
+ /* Function to return a bool from a C long
+ (숫자로부터 bool 인스턴스를 생성하는 헬퍼 함수) */
+ PyObject *PyBool_FromLong(long ok)
+ {
+ PyObject *result;
+
+ if (ok)
+ result = Py_True;
+ else
+ result = Py_False;
+ Py_INCREF(result);
+ return result;
+ }
+ ```
+
+ - Py_True / Py_False
+
+ ```c
+ // Include/boolobject.h
+ /* Don't use these directly */
+ PyAPI_DATA(struct _longobject) _Py_FalseStruct, _Py_TrueStruct; // PyAPI_DATA: Python 인터프리터의 내부 데이터에 대한 접근을 정의
+
+ /* Use these macros */
+ #define Py_False ((PyObject *) &_Py_FalseStruct)
+ #define Py_True ((PyObject *) &_Py_TrueStruct)
+ ```
+
+ ```c
+ // Objects/boolobject.c
+ struct _longobject _Py_FalseStruct = {
+ PyVarObject_HEAD_INIT(&PyBool_Type, 0)
+ { 0 }
+ };
+ /*
+ struct _longobject _Py_FalseStruct = {
+ {
+ 1, // 참조 횟수
+ &PyBool_Type, // 타입
+ 0 // 크기
+ },
+ { 0 } // 정수 값 (False)
+ };
+ */
+
+ struct _longobject _Py_TrueStruct = {
+ PyVarObject_HEAD_INIT(&PyBool_Type, 1)
+ { 1 }
+ };
+ /*
+ struct _longobject _Py_TrueStruct = {
+ {
+ 1, // 참조 횟수
+ &PyBool_Type, // 타입
+ 1 // 크기
+ },
+ { 1 } // 정수 값 (True)
+ };
+ */
+ ```
+
+
+### long
+
+- python2 → python3 로 전환되면서 int 타입 지원을 버리고 long 정수 타입을 사용하고 있습니다.
+
+ ```c
+ // Objects/object.c
+ INIT_TYPE(&PyLong_Type, "int");
+ ```
+
+- 가변 길이 숫자 저장이 가능합니다. (정수 최대 길이는 컴파일된 바이너리에 설정되어 있습니다.)
+- 구조체
+ ```c
+ // Include/longobject.h
+ typedef struct _longobject PyLongObject; /* Revealed in longintrepr.h */
+
+ // Include/longintrepr.h
+ struct _longobject {
+ **PyObject_VAR_HEAD**
+ digit ob_digit[1];
+ };
+ ```
+
+ - ex) 1 → ob_digit: [1]
+ 24601 → ob_digit: [2,4,6,0,1]
+- type 정의 (PyLong_Type)
+
+ ```c
+ // Objects/longobject.c
+ PyTypeObject PyLong_Type = {
+ PyVarObject_HEAD_INIT(&PyType_Type, 0)
+ "int", /* tp_name */
+ offsetof(PyLongObject, ob_digit), /* tp_basicsize */
+ sizeof(digit), /* tp_itemsize */
+ 0, /* tp_dealloc */
+ 0, /* tp_vectorcall_offset */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_as_async */
+ long_to_decimal_string, /* tp_repr */
+ &long_as_number, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ (hashfunc)long_hash, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ PyObject_GenericGetAttr, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
+ Py_TPFLAGS_LONG_SUBCLASS, /* tp_flags */
+ long_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ long_richcompare, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ long_methods, /* tp_methods */
+ 0, /* tp_members */
+ long_getset, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ **long_new, /* tp_new */**
+ PyObject_Del, /* tp_free */
+ };
+ ```
+
+ - long_new
+
+ ```c
+ static PyObject *
+ long_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+ {
+ PyObject *return_value = NULL;
+ static const char * const _keywords[] = {"", "base", NULL}; // 허용되는 키워드 인자
+ static _PyArg_Parser _parser = {NULL, _keywords, "int", 0}; // 인자 파서
+ PyObject *argsbuf[2]; // 임시 버퍼
+ PyObject * const *fastargs; // 빠르게 접근할 수 있는 인자 배열
+ Py_ssize_t nargs = PyTuple_GET_SIZE(args); // 위치 인자 개수
+ Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0; // 위치 인자와 키워드 인자의 총 개수
+ PyObject *x = NULL; // 실제 값
+ PyObject *obase = NULL; // base 키워드 인자
+
+ // args 인자 파싱 진행
+ fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 0, 2, 0, argsbuf);
+ if (!fastargs) {
+ goto exit;
+ }
+ if (nargs < 1) {
+ goto skip_optional_posonly;
+ }
+ noptargs--;
+ x = fastargs[0];
+ skip_optional_posonly:
+ if (!noptargs) {
+ goto skip_optional_pos;
+ }
+ obase = fastargs[1];
+ skip_optional_pos:
+ **return_value = long_new_impl(type, x, obase);**
+
+ exit:
+ return return_value;
+ }
+ ```
+
+ - long_new_impl
+
+ ```c
+ static PyObject *
+ long_new_impl(PyTypeObject *type, PyObject *x, PyObject *obase)
+ /*[clinic end generated code: output=e47cfe777ab0f24c input=81c98f418af9eb6f]*/
+ {
+ Py_ssize_t base;
+
+ if (type != &PyLong_Type)
+ return long_subtype_new(type, x, obase); /* Wimp out */
+ if (x == NULL) {
+ if (obase != NULL) {
+ PyErr_SetString(PyExc_TypeError,
+ "int() missing string argument");
+ return NULL;
+ }
+ return PyLong_FromLong(0L);
+ **** }
+ /* default base and limit, forward to standard implementation */
+ if (obase == NULL)
+ return PyNumber_Long(x);
+
+ base = PyNumber_AsSsize_t(obase, NULL);
+ if (base == -1 && PyErr_Occurred())
+ return NULL;
+ if ((base != 0 && base < 2) || base > 36) {
+ PyErr_SetString(PyExc_ValueError,
+ "int() base must be >= 2 and <= 36, or 0");
+ return NULL;
+ }
+
+ if (PyUnicode_Check(x))
+ return PyLong_FromUnicodeObject(x, (int)base);
+ else if (PyByteArray_Check(x) || PyBytes_Check(x)) {
+ const char *string;
+ if (PyByteArray_Check(x))
+ string = PyByteArray_AS_STRING(x);
+ else
+ string = PyBytes_AS_STRING(x);
+ return _PyLong_FromBytes(string, Py_SIZE(x), (int)base);
+ }
+ else {
+ PyErr_SetString(PyExc_TypeError,
+ "int() can't convert non-string with explicit base");
+ return NULL;
+ }
+ }
+ ```
+
+ - PyLong_FromLong
+
+ ```c
+ PyObject *
+ PyLong_FromLong(long ival)
+ {
+ PyLongObject *v;
+ unsigned long abs_ival;
+ unsigned long t; /* unsigned so >> doesn't propagate sign bit */
+ int ndigits = 0;
+ int sign;
+
+ if (IS_SMALL_INT(ival)) {
+ return get_small_int((sdigit)ival);
+ }
+
+ if (ival < 0) {
+ /* negate: can't write this as abs_ival = -ival since that
+ invokes undefined behaviour when ival is LONG_MIN */
+ abs_ival = 0U-(unsigned long)ival;
+ sign = -1;
+ }
+ else {
+ abs_ival = (unsigned long)ival;
+ sign = ival == 0 ? 0 : 1;
+ }
+
+ /* Fast path for single-digit ints */
+ if (!(abs_ival >> PyLong_SHIFT)) {
+ v = _PyLong_New(1);
+ if (v) {
+ Py_SET_SIZE(v, sign);
+ v->ob_digit[0] = Py_SAFE_DOWNCAST(
+ abs_ival, unsigned long, digit);
+ }
+ return (PyObject*)v;
+ }
+
+ #if PyLong_SHIFT==15
+ /* 2 digits */
+ if (!(abs_ival >> 2*PyLong_SHIFT)) {
+ v = _PyLong_New(2);
+ if (v) {
+ Py_SET_SIZE(v, 2 * sign);
+ v->ob_digit[0] = Py_SAFE_DOWNCAST(
+ abs_ival & PyLong_MASK, unsigned long, digit);
+ v->ob_digit[1] = Py_SAFE_DOWNCAST(
+ abs_ival >> PyLong_SHIFT, unsigned long, digit);
+ }
+ return (PyObject*)v;
+ }
+ #endif
+
+ /* Larger numbers: loop to determine number of digits */
+ t = abs_ival;
+ while (t) {
+ ++ndigits;
+ t >>= PyLong_SHIFT;
+ }
+ **v = _PyLong_New(ndigits);**
+ if (v != NULL) {
+ digit *p = v->ob_digit;
+ Py_SET_SIZE(v, ndigits * sign);
+ t = abs_ival;
+ while (t) {
+ *p++ = Py_SAFE_DOWNCAST(
+ t & PyLong_MASK, unsigned long, digit);
+ t >>= PyLong_SHIFT;
+ }
+ }
+ return (PyObject *)v;
+ }
+ ```
+
+ - _PyLong_New
+
+ ```c
+ // Objects/longobject.c
+ PyLongObject *
+ _PyLong_New(Py_ssize_t size)
+ {
+ PyLongObject *result;
+ /* Number of bytes needed is: offsetof(PyLongObject, ob_digit) + sizeof(digit)*size.
+ Previous incarnations of this code used sizeof(PyVarObject) instead of the offsetof,
+ but this risks being incorrect in the presence of padding between the PyVarObject header and the digits. */
+
+ // 고정된 길이가 MAX_LONG_DIGITS 보다 작은지 확인
+ if (size > (Py_ssize_t)MAX_LONG_DIGITS) {
+ PyErr_SetString(PyExc_OverflowError,
+ "too many digits in integer");
+ return NULL;
+ }
+
+ // ob_digit 길이에 맞춰 메모리 재할당
+ result = PyObject_MALLOC(offsetof(PyLongObject, ob_digit) +
+ size*sizeof(digit));
+ if (!result) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ **return (PyLongObject*)PyObject_INIT_VAR(result, &PyLong_Type, size);**
+ }
+ ```
+
+
+## 11.5 유니코드 문자열 타입
+
+### 11.5.1 사전 지식
+
+[https://velog.io/@viiviii/1-ASCII-ISO-8859-Unicode의-탄생](https://velog.io/@viiviii/1-ASCII-ISO-8859-Unicode%EC%9D%98-%ED%83%84%EC%83%9D)
+
+### 11.5.2 파이썬 문자 인코딩
+
+- char 타입에서 사용하는 ASCII의 문제
+ - 1byte로, 전세계에서 사용하는 모든 문자, 이모지 등을 담기엔 용량 및 크기가 부족합니다.
+- 유니코드 문자 데이터베이스(UCD, Unicode Character Database) 도입
+ - 128개의 문자만 포함하는 ASCII와 달리 143,859 개의 문자를 포함하고 있습니다. (version 13 기준)
+ - 국제 문자 세트(UCS, Universal Character Set)란 문자 테이블로 모든 문자를 정의하며, 테이블의 각 문자는 **코드 포인트**라는 16진법 고유 식별자를 가집니다.
+ - 코드 포인트를 **이진값으로 변환**하기 위해 다양한 인코딩을 지원합니다.
+- CPython 유니코드 문자열이 처리하는 것은 인코딩 뿐
+ - 코드 포인트를 올바른 스크립트로 나타내는 것은 운영 체제의 책임입니다.
+ - CPython은 UCD 사본을 포함하고 있지 않기 때문에 유니코드 표준이 변경될 때마다 CPython을 업데이트하지 않아도 됩니다.
+
+파이썬 유니코드 문자열은 다양한 플랫폼을 지원하기 위해 1byte, 2byte, 4byte 3가지 길이의 인코딩을 지원합니다.
+인코딩 방법으로는 **UTF-8을 가장 일반적으로 사용**합니다. ASCII 호환성은 인코딩 메커니즘을 결정할 때 중요한 요소인데, UTF-8은 **ASCII와 호환**되기 때문입니다.
+
+
+### 11.5.3. 확장 문자 타입
+
+CPython 소스 코드에서 인코딩 방식을 알 수 없는 유니코드 문자열을 처리할 경우, C 타입 wchar_t 가 사용됩니다. 유니코드 문자열 객체는 wchar_t 타입의 상수를 문자열 객체로 변환하는 PyUnicode_FromWideChar() 함수를 제공합니다.
+
+> wchar_t: 확장 문자를 사용하는 문자열을 위한 C 표준
+
+예를 들어, python -c 를 실행할 때 사용되는 pymain_run_command()가 PyUnicode_FromWideChar()를 사용해 -c 인수를 유니코드 문자열로 변환합니다.
+
+```c
+// Modules ▶︎ main.c 225행
+static int
+pymain_run_command(wchar_t *command, PyCompilerFlags *cf)
+{
+ PyObject *unicode, *bytes;
+ int ret;
+
+ unicode = PyUnicode_FromWideChar(command, -1);
+...
+```
+
+### 11.5.4. 바이트 순서 표식
+
+파일 등의 입력을 디코딩할 때, CPython은 바이트 순서 표식(byte order mark, BOM)을 보고 바이트 순서를 인식합니다.
+
+- BOM은 유니코드 바이트 스트림의 시작 부분에 나타나는 특수 문자로, 수신자에게 이 스트림에 어떤 바이트 순서로 데이터가 저장됐는지 알려줍니다.
+ - 컴퓨터 시스템에 따라 인코딩 시 바이트 순서도 다를 수 있습니다.
+ - 올바른 인코딩을 사용하더라도 잘못된 바이트 순서를 사용하면 데이터가 망가집니다.
+ - CPython의 기본 바이트 순서는 전역 변수 sys.byteorder 에 설정됩니다. (little, big 등)
+
+### 11.5.5 encodings 패키지
+
+- Lib ▶︎ encodings의 encodings 패키지는 CPython을 위해 100개 이상의 인코딩을 기본으로 지원합니다.
+ - 문자열 또는 바이트 문자열의 .encode() / .decode() 메서드는 호출될 때마다 이 패키지에서 인코딩을 검색합니다.
+- 각 인코딩은 별도 모듈로 정의되어 있습니다.
+- 모든 인코딩 모듈은 getregentry()라는 함수를 정의하고 다음 특성들을 구현합니다.
+ - 인코딩의 고유한 이름
+ - 코덱 모듈로부터 가져온 해당 인코딩의 인코딩/디코딩 함수
+ - 증분 인코더와 디코더 클래스
+ - 스트림 방식의 reader/writer 클래스
+- 대부분의 인코딩 모듈은 codecs나 _multibytecode 모듈의 동일한 코덱을 공유합니다. 일부 인코딩 모듈은 Modules ▶︎ cjkcodes의 별도 C 코덱 모듈을 사용하기도 합니다.
+ - 유니코드 이외의 인코딩들은 유니코드 문자열 객체 구현과 분리된 Modules ▶︎ _codecs에 구현돼 있습니다.
+
+### 11.5.6 코덱 모듈과 구현, 내부 코덱
+
+- codecs 모듈은 데이터를 특정 인코딩으로 변환합니다.
+- 유니코드 객체 구현 (Objects/unicodeobject.c)에 포함된 인코딩 method 목록
+ - ascii: `PyUnicode_EncodeASCII()`
+ - latin1: `PyUnicode_EncodeLatin1()`
+ - UTF7: `PyUnicode_EncodeUTF7()`
+ - UTF8: `PyUnicode_EncodeUTF8()`
+ - UTF16: `PyUnicode_EncodeUTF16()`
+ - UTF32: `PyUnicode_EncodeUTF32()`
+ - unicode_escape: `PyUnicode_EncodeUnicodeEscape()`
+ - raw_unicode_escape: `PyUnicode_EncodeRawUnicodeEscape()`
+- 내부 인코딩들은 CPython의 고유한 특성으로 일부 표준 라이브러리 함수와 소스 코드 작성에 유용하게 사용됩니다.
+- 텍스트 인코딩 목록
+ - idna: RFC 3490 구현
+ - mbcs: ANSI 코드페이지에 따라 인코딩 (Windows 전용)
+ - raw_unicode_escape: python 소스 코드 저수준 리터럴을 문자열로 변환
+ - undefined: 기본 시스템 인코딩 시도
+ - unicode_escape: 데이터를 python 소스 코드에 적합한 유니코드 리터럴로 변환
+- 이진 데이터용 인코딩 목록
+ - base64_codec (base64, base-64): 데이터를 MIME base64 형식으로 변환
+ - bz2_codec (bz2): 문자열을 bz2로 압축
+ - hex_codec (hex): byte 당 2자리 숫자를 사용 -> 데이터를 16진법으로 표현
+ - quopri_codec (quoted-printable): 데이터를 MIME QP(Quoted-Printable) 형식으로 변환
+ - rot_13 (rot13): Ceaser-cypher 적용한 결과물 반환
+ - uu_codec (uu): uuencode 사용하여 변환
+ - zlib_codec (zip, zlib): gzip 사용하여 데이터 압축
+
+
+## 11.6 Dictionary 타입
+
+- 지역•전역 변수 저장이나 키워드 인자 전달 등 많은 곳에서 사용됩니다.
+- 매핑된 값만 저장하는 해시 테이블 덕에 매우 컴팩트 합니다.
+- 내장 불변 타입들의 일부인 해싱 알고리즘이 매우 빠른 덕에 빠른 속도를 제공합니다.
+
+### 11.6.1 해싱
+
+- 모든 internal immutable type(내장 불변 타입)은 해시 함수를 제공합니다.
+ - tp_hash 타입 슬롯에 정의되어 있습니다.
+ - 사용자 지정 타입의 경우 매직 메서드 __hash__()를 사용해 해시 함수를 정의합니다.
+ - 해시값은 포인터와 크기가 동일합니다. (64비트 시스템에선 64비트, 32비트 시스템에선 32비트, ...)
+ - 해시값이 해당 값의 메모리 주소를 의미하진 않습니다.
+- 해시 충돌이 있으면 안됩니다.
+ - 파이썬 객체의 해시값은 객체의 수명이 지속되는 동안 변하는 일이 없어야 합니다.
+ - 불변 인스턴스의 경우 값이 동일한 두 불변 인스턴스의 해시는 같아야 합니다.
+- 유니코드 문자열은 문자열의 바이트 데이터를 Python▶︎pyhash.c 모듈의 _Py_HashBytes()로 해시합니다.
+
+
+### 11.6.2 딕셔너리의 구조
+
+
+
+- 딕셔너리의 크기, 버전 태그, 키와 값을 담고 있는 property들로 구성되어 있습니다.
+- PyDictKeysObject: 키와 각 키 엔트리의 해시값을 담고 있는 딕셔너리 키 테이블 객체
+- PyDictKeysObject property 목록
+ - dk_entries (PyDictKeyEntry[]): 동적 할당된 dictionary key 엔트리 배열
+ - dk_indices (char[]): 해시 테이블 - dk_entries 맵핑
+ - dk_lookup (dict_lookup_func): 검색 함수
+ - dk_nentries (Py_ssize_t): 엔트리 테이블의 엔트리를 사용한 개수
+ - dk_refcnt (Py_ssize_t): reference counter
+ - dk_size (Py_ssize_t): 해시 테이블 크기
+ - dk_usable (Py_ssize_t): 엔트리 테이블에서 사용 가능한 엔트리 개수 (남은 엔트리 0개일 때 딕셔너리 크기 조정됨)
+- PyDictObject property 목록
+ - ma_keys (PyDictKeysObject*): 딕셔너리 키 테이블 객체
+ - ma_used (Py_ssize_t): 딕셔너리 내 항목 개수
+ - ma_values (PyObject**): 추가된 값 배열
+ - ma_version_tag (uint64_t): 딕셔너리 버전
+
+
+### 11.6.3 검색
+
+- 주어진 키 객체로 딕셔너리 항목을 찾을 때 범용 검색 함수 lookdict()를 사용합니다.
+- 딕셔너리 검색이 처리해야 하는 3가지 시나리오
+ 1. 주어진 key 의 memory address 를 key table 에서 찾을 수 있는 경우
+ 2. 주어진 key object 의 hash value 를 key table 에서 찾을 수 있는 경우
+ 3. dictionary 에 해당 key 가 없는 경우
+- 검색 함수의 항목 탐색 순서
+ 1. ob의 hash value를 구합니다.
+ 2. dictionary key 중 ob의 hash value 와 일치하는 값을 찾아 인덱스(ix)를 구합니다.
+ 3. ix 값이 비어있을 경우, DKIX_EMPTY를 반환합니다.
+ 4. 주어진 index로 key entry ep를 찾습니다.
+ 5. ob와 key의 값이 일치하면 ob는 key 와 동일한 값을 가리키는 pointer이기 때문에 찾은 값을 반환합니다.
+ 6. ob의 hash와 key의 hash가 일치할 때도 찾은 값을 반환합니다.
+
+
+## References
+
+- [https://docs.python.org/3/c-api/structures.html](https://docs.python.org/3/c-api/structures.html)
+- [https://docs.python.org/3.9/reference/datamodel.html](https://docs.python.org/3.9/reference/datamodel.html)
+(한국어 버전: [https://docs.python.org/ko/3.9/reference/datamodel.html](https://docs.python.org/ko/3.9/reference/datamodel.html))
+- [https://docs.python.org/3.9/c-api/typeobj.html](https://docs.python.org/3.9/c-api/typeobj.html)
diff --git a/book/docs/12_0_standard_library.md b/book/docs/12_0_standard_library.md
new file mode 100644
index 0000000..c77a9d2
--- /dev/null
+++ b/book/docs/12_0_standard_library.md
@@ -0,0 +1,126 @@
+# 12. 표준 라이브러리
+
+CPython 표준 라이브러리 모듈은 두 가지 종류로 나뉜다.
+
+1. 유틸리티를 제공하는 순수한 파이썬 모듈
+2. C로 작성됐고 파이썬 래퍼를 제공하는 모듈
+
+## 12.1 파이썬 모듈
+
+순수한 파이썬 모듈들은 `Lib` 디렉토리에 위치한다.
+
+아래의 email 모듈과 같은 큰 모듈 중 일부는 하위 모듈이 있는 경우도 있다.
+
+
+
+파이썬 배포판을 설치하면 표준 라이브러리 모듈은 배포판 폴더로 복사된다. 해당 폴더는 파이썬을 시작할 때 항상 경로에 포함되어 모듈을 임포트할 때 모듈 위치를 신경 쓰지 않아도 된다.
+
+→ `sys.path`에 포함된다.
+
+아래는 파이썬 모듈 중 하나인 `colorsys`의 함수이다.
+
+```python
+def rgb_to_hls(r, g, b):
+ maxc = max(r, g, b)
+ minc = min(r, g, b)
+ # XXX Can optimize (maxc+minc) and (maxc-minc)
+ l = (minc+maxc)/2.0
+ if minc == maxc:
+ return 0.0, l, 0.0
+ if l <= 0.5:
+ s = (maxc-minc) / (maxc+minc)
+ else:
+ s = (maxc-minc) / (2.0-maxc-minc)
+ rc = (maxc-r) / (maxc-minc)
+ gc = (maxc-g) / (maxc-minc)
+ bc = (maxc-b) / (maxc-minc)
+ if r == maxc:
+ h = bc-gc
+ elif g == maxc:
+ h = 2.0+rc-bc
+ else:
+ h = 4.0+gc-rc
+ h = (h/6.0) % 1.0
+ return h, l, s
+```
+
+위 함수를 사용하는 방법은 아래와 같다.
+
+```python
+import colorsys
+
+colorsys.rgb_to_hls(255, 0, 0)
+```
+
+모듈의 위치를 신경 쓰지 않고 바로 임포트할 수 있다.
+
+모든 파이썬 모듈은 위 함수와 같이 단순한 파이썬 코드로 이루어져 있으며 이해하기 어렵지 않다. 따라서 표준 라이브러리 코드에서 버그나 개선 사항을 찾아 파이썬 배포판에 기여할 수도 있다.
+
+## 12.2 파이썬과 C가 혼용된 모듈
+
+순수한 파이썬 모듈을 제외한 나머지 모듈은 모두 C로 작성됐거나 C와 파이썬이 혼용되어 있다.
+
+C로 작성된 부분은 `Modules` 디렉토리에 위치하며 파이썬으로 작성된 부분은 `Lib` 디렉토리에 위치한다.
+
+아래 사항은 예외이다.
+
+1. `sys` 모듈은 `Python/sysmodule.c`에 위치한다.
+ - CPython의 내부와 강하게 연관되어 있기 때문에 `Python` 디렉토리에 존재한다.
+2. `__builtins__` 모듈은 `Python/bltinmodule.c`에 위치한다.
+ - 인터프리터를 인스턴스화할 때 내장 함수들을 __builtins__로부터 임포트하므로 `Python` 디렉토리에 존재한다.
+ - `print()`, `chr()`, `format()`과 같은 모든 내장 함수를 해당 파일에서 찾을 수 있다.
+
+C로 작성된 일부 모듈의 내용은 운영 체제 기능을 외부에 노출한다. 따라서 운영 체제에 따라 모듈이 다르게 동작하는 특수한 케이스가 존재한다.
+
+예를 들어 `time` 모듈은 윈도우가 시간을 유지하고 저장하는 방법이 리눅스, macOS와 전혀 다르기 때문에 운영 체제별로 시간 함수의 정확도가 다르다.
+
+스레딩 모듈, 파일 시스템 모듈, 네트워킹 모듈도 동일한 API를 운영 체제별로 여러 번 구현한다.
+
+운영 체제별로 동작이 다르기 때문에 CPython 소스 코드는 최대한 같은 방식으로 동작을 구현한 다음 일관성 있고 추상화된 API만 제공한다.
+
+이러한 구현 방식을 `time` 모듈에서도 확인할 수 있다.
+
+아래는 `time` 모듈의 함수로 프로세스의 CPU 사용 시간을 나노초 단위로 반환하는 함수이다.
+
+```c
+static PyObject *
+time_process_time_ns(PyObject *self, PyObject *unused)
+{
+ _PyTime_t t;
+ if (_PyTime_GetProcessTimeWithInfo(&t, NULL) < 0) {
+ return NULL;
+ }
+ return _PyTime_AsNanosecondsObject(t);
+}
+```
+
+아래 함수는 `time_process_time_ns()`에서 호출하는 함수로 운영 체제에 따라 다른 방식으로 시스템 프로세스 시간을 가져오는 기능을 수행한다.
+
+```c
+static int
+_PyTime_GetProcessTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
+{
+#if defined(MS_WINDOWS)
+ HANDLE process;
+ FILETIME creation_time, exit_time, kernel_time, user_time;
+
+...
+
+ t = _PyTime_FromNanoseconds((ktime + utime) * 100);
+ *tp = t;
+ return 0;
+#else
+
+ /* clock_gettime */
+#if defined(HAVE_CLOCK_GETTIME) \
+ && (defined(CLOCK_PROCESS_CPUTIME_ID) || defined(CLOCK_PROF))
+ struct timespec ts;
+
+...
+
+ /* clock */
+ /* Currently, Python 3 requires clock() to build: see issue #22624 */
+ return _PyTime_GetClockWithInfo(tp, info);
+#endif
+}
+```
diff --git a/book/docs/13_0_test_suite.md b/book/docs/13_0_test_suite.md
new file mode 100644
index 0000000..4a0212f
--- /dev/null
+++ b/book/docs/13_0_test_suite.md
@@ -0,0 +1,336 @@
+# 13. 테스트 스위트
+
+CPython은 코어 인터프리터, 표준 라이브러리, 툴링을 비롯해
+리눅스, macOS용 배포까지 포함하는 강력한 테스트 스위트를 가지고 있다.
+
+테스트 스위트는 `Lib/tests`에 위치해 있고 대부분 파이썬으로 작성되어있다.
+
+전체 테스트 스위트가 파이썬 패키지이기 때문에
+컴파일한 파이썬 인터프리터로 테스트를 실행해 볼 수 있다.
+
+---
+
+## 용어
+
+테스트를 처음 접하는 경우 아래의 용어를 알고 있다면 이해하는데 도움이 될 것 같다.
+
+### 테스트 하네스
+
+테스트 수행 도구를 의미한다.
+Postman, unittest, Pytest, JUnit5, Robot Framework 등이 여기에 해당한다.
+
+
+
+### 테스트 스위트, 테스트 케이스
+
+테스트 케이스의 모음을 테스트 스위트라고 부른다.
+
+### 확인 테스트, 회귀 테스트
+
+**확인 테스트**는 발생한 이슈에 대한 수정 검증 테스트를 의미한다.
+만약 확인 테스트가 이후 릴리즈 할 버전에도 영향을 미칠 것 같다고 판단된다면
+수행한 확인 테스트 케이스를 회귀 테스트 스위트에 포함시킨다.
+
+**회귀 테스트**는 테스트 버전에 대해
+릴리즈 버전에서 수행한 테스트 케이스들을 다시 수행하는 것을 의미한다.
+
+## 일반적인 테스트 코드 구조
+
+일반적으로는 테스트 코드는 다음과 같이 구성되어 있다. (프로젝트 별로 관리 방식이 상이할 수 있다)
+
+pytest를 사용할 경우의 디렉토리 구조이다
+
+```
+Project
+ |- src
+ |- __init__.py
+ |- main.py
+ |- data_loader
+ |- ...
+ |- models
+ |- ...
+ |- tests
+ |- __init__.py
+ |- test_{suites1}
+ |- ...
+ |- test_{suites2}
+ |- __init__.py
+ |- conftest.py
+ |- test_{suite1}.py
+ |- test_{suite2}.py
+```
+
+### Mock
+
+Mock은 '가짜 데이터'를 의미한다.
+예를 들어 테스트 코드를 작성할 때, 실제 모듈과 유사하게 동작하는 가짜 데이터를 만들어 사용할 수 있다.
+
+mock를 사용하면 함수의 실제 실행 없이, 미리 정해진 값을 반환하도록 할 수 있다.
+
+성능 저하, 비용 등 불필요한 리소스 발생이 예상될 때, 일반적으로 mock 사용을 권장한다.
+
+```python
+# src/example.py
+
+def func():
+ return "fail"
+```
+
+위와 같은 함수가 있다고 해보자.
+원래라면 “fail”만 리턴하는 함수이다.
+
+그리고 테스트를 위한 `test_example.py`를 만들어보자.
+
+```python
+# tests/test_example.py
+
+import unittest
+from unittest import mock
+from src import example
+
+class TestExample(unittest.TestCase):
+
+ @mock.patch("src.example.func", return_value="success")
+ def test_func(self, mock_func):
+ actual = example.func()
+ expected = "success"
+
+ self.assertEqual(actual, expected)
+
+if __name__ == "__main__":
+ unittest.main()
+```
+
+mock를 사용하기 위해서는 test 함수 위에 다음과 같은 데코레이터를 추가해야 한다.
+
+```python
+@mock.patch("src.example.func", return_value="success")
+```
+
+`patch`메소드의 첫 번째 인자는 target 함수의 경로이다.
+`return_value` 인자에는 target 함수의 return 값을 지정할 수 있다.
+
+`return_value`를 통해 함수의 return 값을 강제할 수 있다.
+
+`func()` 는 "fail"을 return 하지만,
+`return_value` 파라미터를 통해 "success"로 강제할 수 있다.
+
+### Hook
+
+각 테스트 케이스마다 반복되는 작업이 있을 경우 Hook을 수행할 수 있다.
+
+`unittest`에서는 테스트 케이스의 설정 및 정리를 위해
+`setUp`, `tearDown`, `setUpClass`, `tearDownClass` 메소드를 사용한다.
+
+- `setUp`: 각 테스트 케이스가 호출되기 전 먼저 호출되는 메소드
+- `setUpClass`: 테스트 스위트가 실행된 직후 가장 먼저 호출되는 메소드
+- `tearDown`: 각 테스트 케이스의 종료 후 호출되는 메소드
+- `tearDownClass`: 테스트 스위트의 종료 후 호출되는 메소드
+
+```python
+# tests/test_example.py
+
+import unittest
+from unittest import mock
+from src import example
+
+class TestExample(unittest.TestCase):
+ def setUp(self):
+ self.mock_func = mock.patch("src.example.func", return_value="success").start()
+
+ def tearDown(self):
+ mock.patch.stopall()
+
+ def test_func(self):
+ actual = example.func()
+ expected = "success"
+ self.assertEqual(actual, expected)
+
+if __name__ == "__main__":
+ unittest.main()
+```
+
+---
+
+## **리눅스와 macOS에서 테스트 스위트 실행하기**
+
+리눅스와 macOS에서 `make`로 `test` 타겟을 실행하면 컴파일 후 테스트가 실행된다.
+
+```python
+$ make test
+== CPython 3.9
+== macOS-14.4.1-arm64-arm-64bit little-endian
+== cwd: /Users/wooy0ng/Desktop/cpython/build/test_python_3006æ
+== CPU count: 8
+== encodings: locale=UTF-8, FS=utf-8
+0:00:00 load avg: 5.40 [ 1/425] test_wave passed
+0:00:00 load avg: 5.40 [ 2/425] test_richcmp passed
+0:00:00 load avg: 7.93 [ 3/425] test_future5 passed
+```
+
+또는 컴파일된 바이너리인 `python`이나 `python.exe`로 `test` 패키지를 실행할 수도 있다.
+
+```python
+$ ./python -m test
+== CPython 3.9
+== macOS-14.4.1-arm64-arm-64bit little-endian
+== cwd: /Users/wooy0ng/Desktop/cpython/build/test_python_3006æ
+== CPU count: 8
+== encodings: locale=UTF-8, FS=utf-8
+0:00:00 load avg: 5.20 [ 1/425] test_wave passed
+0:00:00 load avg: 5.20 [ 2/425] test_richcmp passed
+0:00:00 load avg: 7.68 [ 3/425] test_future5 passed
+```
+
+다음은 cpython에서 테스트를 위한 `make` 타겟 목록이다.
+
+| 타깃 | 용도 |
+| --- | --- |
+| test | 기본적인 회귀 테스트를 실행한다. |
+| quicktest | 오래 걸리는 테스트를 제외하고 빠른 회귀 테스트만 실행한다. |
+| testall | .pyc 파일이 없는 상태로 한 번, 있는 상태로 한 번 전체 테스트 스위트를 실행한다. |
+| testuniversal | macOS 유니버셜 빌드에서 여러 아키텍처에 대한 테스트 스위트를 실행한다. |
+| coverage | 컴파일 후 gcov로 테스트를 실행한다. |
+| coverage-lcov | HTML 커버리지 보고를 생성한다. |
+
+---
+
+## 테스트 플래그
+
+GUI가 필요한 IDLE에 대한 테스트처럼
+일부 테스트는 특정한 플래그가 없으면 자동으로 건너뛴다.
+
+- `-list-tests` 플래그로 구성에서 테스트 스위트 목록을 볼 수 있다.
+
+```python
+$ ./python -m test --list-tests
+
+test_grammer
+test_opcodes
+test_dict
+test_builtin
+test_exceptions
+...
+```
+
+---
+
+## 특정 테스트만 실행하기
+
+테스트를 실행할 때 첫 번째 인자에 실행할 테스트 스위트를 명시해서 특정 테스트만 실행할 수 있다.
+
+아래는 리눅스와 macOS에서 실행할 테스트 스위트를 명시하는 방법이다.
+
+```python
+$ ./python -m test test_webbrowser
+Raised RLIMIT_NOFILE: 256 -> 1024
+
+0:00:00 load avg: 1.70 Run tests sequentially
+0:00:00 load avg: 1.70 [1/1] test_webbrowser
+
+== Tests result: SUCCESS ==
+1 test OK.
+
+Total duration: 37 ms
+Tests result: SUCCESS
+```
+
+CPython을 변경하려면 테스트 스위트를 사용하는 방법과
+직접 컴파일한 바이너리 상태를 확인하는 방법을 이해하는 것이 매우 중요하다.
+
+소스 코드를 변경하기 전에 전체 테스트 세트를 실행하고 모두 통과되는지 확인해야 한다.
+
+---
+
+## 테스트 모듈
+
+C 확장과 파이썬 모듈은 `unittest` 모듈로 임포트하고 테스트한다.
+테스트는 모듈이나 패키지 단위로 구성된다.
+
+예를 들어 파이썬 유니코드 문자열 타입의 테스트는 `Lib/test/test_unicode.py`에서,
+`asyncio` 패키지의 테스트 패키지는 `Lib/test/test_asyncio`에서 찾을 수 있다.
+
+아래는 UnicodeTest 클래스 중 일부이다.
+
+```python
+
+class UnicodeTest(string_tests.CommonTest,
+ string_tests.MixinStrUnicodeUserStringTest,
+ string_tets.MixinStrUnicodeTest,
+ unittest.TestCase
+):
+...
+ def test_casefold(self):
+ self.assertEqual('hello'.casefold(), 'hello')
+ self.assertEqual('hELlo'.casefold(), 'hello')
+ self.assertEqual('ß'.casefold(), 'ss')
+ self.assertEqual('fi'.casefold(), 'fi')
+```
+
+이전 장에서 유니코드 문자열에 대해 구현한 '거의 같음' 연산자에 대한 테스트를
+UnicodeTest 클래스의 새 메소드로 추가해보자.
+
+```python
+def test_almost_equals(self):
+ self.assertTrue('hello' ~= 'hello')
+ self.assertTrue('heLlo' ~= 'hello)
+ self.assertFalse('hELlo!' ~= 'hello')
+```
+
+새로 추가한 테스트를 아래의 커멘드로 실행해보자
+
+- Windows
+
+ ```python
+ > rc.bat -q -d -x64 test_unicode
+ ```
+
+
+- MacOS, 리눅스
+
+ ```python
+ $ ./python -m test test_unicode -v
+ ```
+
+
+---
+
+## 테스트 유틸리티
+
+`test.support.script_helper` 모듈은
+파이썬 런타임 테스트를 사용할 수 있는 helper 함수를 제공한다.
+
+- `assert_python_ok(*args, **env_vars)`
+
+ > 지정된 인수와 함께 파이썬 프로세스를 실행하고 반환 코드와
+ stdout, stderr를 담은 튜플을 반환한다.
+ >
+- `assert_python_failure(*args, **env_vars)`
+
+ > assert_python_ok()와 비슷하지만 실패를 가정하는 경우에 사용한다.
+ >
+- `make_script(script_dir, script_basename, source)`
+
+ > script_basename과 source를 사용해 script_dir에 스크립트를 생성하고
+ 스크립트에 대한 경로를 반환한다.
+ >
+
+
+모듈이 빌드되지 않았을 경우 테스트도 건너뛰게 하고 싶다면
+유틸리티 함수 `test.support.import_module()`을 사용할 수 있다.
+
+이 유틸리티는 테스트할 모듈이 빌드되지 않았다면 `SkipTest`를 발생시켜
+이 테스트 패키지를 건너뛰라는 신호를 Test Runner에 보낸다.
+
+`import_module()`을 사용하는 방법은 아래와 같다.
+
+```python
+import test.support
+
+_multiprocessing = test.support.import_module('_multiprocessing')
+
+# test 작성
+...
+```
+
+---
diff --git a/book/docs/14_0_c_extension.md b/book/docs/14_0_c_extension.md
new file mode 100644
index 0000000..0c6599c
--- /dev/null
+++ b/book/docs/14_0_c_extension.md
@@ -0,0 +1,141 @@
+# Python C 확장 개발 가이드
+
+## 참고 페이지
+[Real Python Building a Python C Extension Module](https://realpython.com/build-python-c-extension-module/)
+[MSDN Python용 C++ 확장 만들기](https://learn.microsoft.com/ko-kr/visualstudio/python/working-with-c-cpp-python-in-visual-studio?view=vs-2022)
+
+
+## 샘플 코드
+저장소 book/c_extension_sample 폴더
+
+
+## Python C 확장 따라 만들기
+원하는 경로, 원하는 IDE에서 작업을 시작할 수 있습니다
+VSCode 기준으로 작업을 진행합니다
+C/C++ 확장이 설치되어 있어야 편합니다
+
+### VSCode 세팅하기
+
+Ctrl + Shift + P 를 눌러 C/C++ Configurations 창을 열어줍니다
+
+
+Include path 에 Python이 설치된 폴더를 보면 include 폴더가 있습니다
+해당 경로를 추가해줍니다
+
+### c 확장 모듈 기능 작성하기
+이제 간단하게 C API 인 fputs를 python에서 쓸 수 있는 모듈로 개발합니다
+```c
+#include
+
+static PyObject* method_fputs(PyObject* self, PyObject* args) {
+ char *str, *filename = NULL;
+ int bytes_copied = -1;
+
+ if(!PyArg_ParseTuple(args, "ss", &str, &filename)) {
+ return NULL;
+ }
+
+ FILE *fp = fopen(filename, "w");
+ bytes_copied = fputs(str, fp);
+ fclose(fp);
+
+ return PyLong_FromLong(bytes_copied);
+}
+```
+PyObject* 를 반환하는 fputs static 함수를 작성하였습니다
+PyObject 타입은 Python 의 객체 타입으로 **11장 객체와 타입**에서도 설명을 하고 있습니다
+
+```c
+...
+ char *str, *filename = NULL;
+ int bytes_copied = -1;
+ if(!PyArg_ParseTuple(args, "ss", &str, &filename)) {
+ return NULL;
+ }
+...
+```
+PyArg_ParseTuple 함수는 Python 에서 받은 인수 args를 로컬 변수로 대입합니다
+2번째 인자 "ss"는 어떤 타입으로 인수를 파싱할지 설정합니다 (예제에서는 문자열, 문자열로 파싱)
+[[c-api arg 관련 공식 문서](https://docs.python.org/3/c-api/arg.html)]
+3번째 인자부터는 가변길이 인자로 인수를 대입받을 변수들의 주소값을 차례대로 넘겨줍니다
+
+```c
+ FILE *fp = fopen(filename, "w");
+ bytes_copied = fputs(str, fp);
+ fclose(fp);
+
+ return PyLong_FromLong(bytes_copied);
+```
+받은 인수값으로 파일을 열어 fputs를 수행합니다
+그리고 반환값으로 PyLong_FromLong 함수를 통하여 fputs로 쓴 파일 크기를 PyObject로 반환합니다
+(PyLong_FromLong에 대한 부분도 11장 객체와 타입 참조)
+
+### c 확장 모듈 초기화 함수 작성하기
+위 코드에 이어서 확장 모듈 초기화 부분을 작성합니다
+```c
+static PyMethodDef FPutsMethods[] = {
+ {"fputs", method_fputs, METH_VARARGS, "Python interface for fputs C library function"},
+ {NULL, NULL, 0, NULL}
+};
+```
+모듈에서 정의된 메서드를 인터프리터에 알려주기위한 PyMethodDef 설정
+**"fputs"** - Python에서 호출되는 이름
+**method_fputs** - 호출되는 C 함수
+**METH_VARARGS** - self와 args를 인자로 받는다는 플래그
+([c-api sstructures](https://docs.python.org/3/c-api/structures.html))
+**마지막 문자열** - 메서드에 대한 docstring
+
+```c
+static struct PyModuleDef fputsmodule = {
+ PyModuleDef_HEAD_INIT,
+ "fputs",
+ "Python interface for the fputs C library function",
+ -1,
+ FPutsMethods
+};
+```
+PyModuleDef 로 C 확장 모듈 자체에 대한 정보를 저장 ([PyModuleDef 문서](https://docs.python.org/3/c-api/module.html#c.PyModuleDef))
+총 9개의 멤버가 있지만 여기서는 필요한 5개만 초기화합니다
+
+**PyModuleDef_HEAD_INIT** - PyModuleDef_Base 유형의 멤버, 하나만 갖도록 권장
+**"fputs"** - Python C 확장 모듈 이름 (import fputs)
+**3번째 문자열** - 모듈에 대한 docstring
+**-1** - 프로그램 상태 저장에 필요한 메모리 크기, 모듈이 여러 서브 인터프리터에서 사용될 때 유용, 음수 값은 서브 인터프리터를 지원하지 않음을 나타냄
+**FPutsMethods** - 메서드 테이블에 대한 참조
+
+```c
+PyMODINIT_FUNC PyInit_fputs(void) {
+ return PyModule_Create(&fputsmodule);
+}
+```
+Python에서 모듈을 처음 임포트 할 때 PyInit_fputs를 호출하게 합니다
+PyMODINIT_FUNC은 암시적으로 세 가지 작업을 수행
+1. 함수 반환형을 PyObject*로 암시적 설정
+2. 특별한 연결을 선언
+3. 함수를 External "C" 로 선언 (C++의 경우 C++ 컴파일러에 심볼에 대한 이름이 섞이지 않도록 지시)
+
+PyModule_Create는 PyObject* 형의 새로운 모듈 객체를 반환
+인자로는 이전에 선언해둔 fputsmodule 객체의 주소를 전달
+
+### setup.py 작성
+```python
+from distutils.core import setup, Extension
+
+def main():
+ setup(name="fputs",
+ version="1.0.0",
+ description="Python interface for the fputs C library function",
+ author="<개발자 이름>",
+ author_email="your_email@gmail.com",
+ ext_modules=[Extension("fputs", ["fputsmodule.c"])])
+
+if __name__ == "__main__":
+ main()
+```
+Python 패키지를 distutils를 사용하여 모듈을 빌드 할수 있습니다
+내부에서 clang을 사용하여 빌드합니다
+
+```shell
+python setup.py install
+```
+setup.py 작성 후 위 명령어로 실행하면 내가 만든 모듈이 빌드되고 설치됩니다
diff --git a/book/docs/1_0_welcome_to_cpython.md b/book/docs/1_0_welcome_to_cpython.md
new file mode 100644
index 0000000..f4c7b60
--- /dev/null
+++ b/book/docs/1_0_welcome_to_cpython.md
@@ -0,0 +1,79 @@
+# 1. CPython 살펴보기
+
+> 앞으로 저희는 CPython에 대해 아래 내용을 다루게 됩니다 🚀
+> - **CPython 소스 코드 읽고 탐색 / 컴파일**하기
+> - Python 문법 수정하고 컴파일해서 자신만의 CPython 버전 만들기
+> - list, dictionary, generator 등의 **내부 작동 방식 이해**하기
+> - CPython **메모리 관리 기능** 이해하기
+> - **병렬성과 동시성**을 통해 Python 코드 확장하기
+> - 코어 타입에 새로운 기능 추가하기
+> - **테스트 스위트** 실행하기
+> - Python **코드와 런타임 성능을 프로파일하고 벤치마크**하기
+> - C 코드와 Python 코드를 전문가처럼 **디버깅**하기
+> - CPython 라이브러리의 구성 요소를 수정하거나 개선해서 **향후 CPython에 기여**하기
+
+## 1.1 CPython이란?
+
+- Python 구현체 중 하나로, 일반적으로 이용하는 [python.org](http://python.org) 에서 제공하는 공식 파이썬 구현체입니다.
+- 이름에서 알 수 있듯이, CPython은 C로 구현되어 있습니다.
+- 다른 python 구현체
+ - Jython
+ - Java로 작성된 파이썬 구현체
+ - JVM(Java Virtual Machine)에서 실행
+ - IronPython
+ - C#로 작성된 파이썬 구현체
+ - .NET 프레임워크 사용
+ - PyPy
+ - Python 정적 타입으로 작성된 파이썬 구현체
+ (정적 타입: 실행하기 전에 변수의 type을 미리 결정하고, 그 이후에는 type을 변경하지 않는 방식)
+ - JIT(Just-In-Time: 프로그램을 실행하는 동안 실시간으로 기계어로 변환) 컴파일러 방식으로 구현되어 기존 interpreter 방식보다 빠르고 효율적
+
+## 1.2 CPython 배포판 구성 요소
+
+- **언어 사양(Language Specification)**: 파이썬 언어의 문법, 구문, 의미론
+ - ex) `[]`: 인덱싱, 슬라이싱, 빈 리스트 생성을 위해 사용
+- **컴파일러(Interpreter)**: C 언어로 작성된 컴파일러
+ - 파이썬 소스 코드 → 실행 가능한 기계어로 변환하는 역할
+- **표준 라이브러리 모듈(Standard Library Modules)**: 기본적으로 포함되어 있는 패키지
+ - ex) 파일 입출력, 네트워킹, 문자열 처리, 데이터 구조, GUI 프로그래밍 등
+- **코어 타입(Core Types)**: 내장 데이터 type
+ - ex) 숫자, 문자열, list, tuple, dictionary
+- **테스트 스위트(Test Suite)**: 개발 및 유지 보수에 사용되는 테스트 모음
+ - ex) 유닛 테스트, 통합 테스트, 성능 테스트
+
+## 1.3 소스 코드 들여다보기
+
+- 소스 코드 다운로드 (3.9 버전을 기준으로 살펴볼 예정입니다.)
+ ```bash
+ git clone --branch 3.9 https://github.com/python/cpython
+ ```
+- 소스 코드 구성
+ ```bash
+ cpython
+ ├── CODE_OF_CONDUCT.md
+ ├── Doc # 문서 소스 파일
+ ├── Grammar # 컴퓨터가 읽을 수 있는 언어 정의
+ ├── Include # C 헤더 파일
+ ├── LICENSE
+ ├── Lib # 파이썬으로 작성된 표준 라이브러리 모듈
+ ├── Mac # macOS를 위한 파일
+ ├── Makefile.pre.in
+ ├── Misc # 기타 파일
+ ├── Modules # C로 작성된 표준 라이브러리 모듈
+ ├── Objects # 코어 타입과 객체 모델
+ ├── PC # 이전 버전의 윈도우를 위한 윈도우 빌드 지원 파일
+ ├── PCbuild # 윈도우 빌드 지원 파일
+ ├── Parser # 파이썬 파서 소스 코드
+ ├── Programs # python 실행 파일과 기타 바이너리를 위한 소스 코드
+ ├── Python # CPython 인터프리터 소스 코드
+ ├── README.rst
+ ├── Tools # CPython 빌드하거나 확장하는 데 유용한 독립 실행형 도구
+ ├── aclocal.m4
+ ├── config.guess
+ ├── config.sub
+ ├── configure
+ ├── configure.ac
+ ├── install-sh
+ ├── pyconfig.h.in
+ └── setup.py
+ ```
diff --git a/book/docs/2_0_settings.md b/book/docs/2_0_settings.md
new file mode 100644
index 0000000..c6ca533
--- /dev/null
+++ b/book/docs/2_0_settings.md
@@ -0,0 +1,91 @@
+# 2. 개발 환경 세팅
+
+CPython-Guide는 CPython 파헤치기 책과 동일한 Python 3.9 버전을 기반으로 진행됩니다.
+
+## Visual Studio Code 개발 환경 세팅
+
+### 플러그인 설치
+아래와 같은 플러그인들을 설치해줍니다.
+
+
+### .vscode/task.json 작성
+프로젝트 실행 명령에 대한 json 파일 `.vscode/task.json`을 아래와 같이 생성해줍니다.
+
+```json
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build",
+ "type": "shell",
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ },
+ "windows": {
+ "command": "PCBuild/build.bat",
+ "args": ["-p", "x64", "-c", "Debug"]
+ },
+ "linux": {
+ "command": "make -j2 -s"
+ },
+ "osx": {
+ "command": "make -j2 -s"
+ }
+ }
+ ]
+}
+```
+
+위와 같이 파일 작성을 완료하면 TASK EXPLORER의 vscode 하위에 작성한 build task가 추가된 것을 볼 수 있습니다.
+
+
+
+### .vscode/launch.json 작성
+디버거를 위한 json 파일 `.vscode/launch.json`을 아래와 같이 생성해줍니다.
+
+- macOS
+ ```json
+ {
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Debug C Code",
+ "type": "cppdbg",
+ "request": "launch",
+ "program": "${workspaceFolder}/python.exe",
+ "args": [],
+ "stopAtEntry": false,
+ "cwd": "${workspaceFolder}",
+ "environment": [],
+ "externalConsole": true,
+ "MIMode": "lldb"
+ }
+ ]
+ }
+ ```
+- Windows
+ ```json
+ {
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "msvc cl.exe debug cpython",
+ "type": "cppvsdbg",
+ "request": "launch",
+ "program": "PCBuild/amd64/python_d.exe",
+ "args": [],
+ "stopAtEntry": false,
+ "cwd": "${workspaceFolder}",
+ "environment": [],
+ "externalConsole": true,
+ "preLaunchTask": "build"
+ }
+ ]
+ }
+ ```
+
+이제 F5를 누르면 CPython 빌드 진행 및 디버깅을 할 수 있습니다.
+CPython의 진입점이 되는 Programs/python.c의 9번 라인에 디버그 브레이크를 걸고 실행해보면,
+아래와 같이 디버깅이 잡힌 것을 확인할 수 있습니다.
+
diff --git a/book/docs/3_0_compile.md b/book/docs/3_0_compile.md
new file mode 100644
index 0000000..8b94c57
--- /dev/null
+++ b/book/docs/3_0_compile.md
@@ -0,0 +1,225 @@
+# 3. 컴파일하기
+
+> 지난 챕터까지는 CPython 개발 환경을 구성했다면,
+ 이번 챕터에서는 CPython 소스 코드를 실제로 작동시키는 interpreter로 컴파일하고자 합니다 🚀
+
+
+💡 컴파일(compile)이란?
+
+- 프로그래밍 언어로 작성된 소스 코드를 컴퓨터가 직접 이해하고 실행할 수 있는 기계어 코드로 변환하는 과정입니다.
+ (**소스 코드 → 실행 가능한 프로그램**)
+- 컴파일러는 번역기처럼 한 언어(출발어: source language)를 다른 언어(도착어: target language)로 변환하는 것이 목표입니다.
+
+
+### macOS
+
+```bash
+# C 컴파일러 툴킷(Xcode Command Line Tools) 설치
+xcode-select --install ## make, GNU Compiler Collection(gcc) 등 설치
+
+# 외부 라이브러리 설치
+brew install openssl xz zlib gdbm sqlite
+
+# Makefile 생성 (configure 스크립트 실행)
+CPPFLAGS="-I$(brew --prefix zlib)/include" \
+LDFLAGS="-L$(brew --prefix zlib)/lib" \
+./configure --with-openssl=$(brew --prefix openssl) --with-pydebug
+
+## 애플칩(M1, M2, ...)은 아래와 같이 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
+
+# CPython 바이러니 빌드
+make -j2 -s # -j2: 동시 작업 2개 / -s: 실행된 명령어 출력 X
+
+# 바이너리 파일 실행
+./python.exe
+```
+
+
+make란?
+
+- 소프트웨어 빌드 자동화 도구로, C/C++ 프로젝트 빌드 자동화에서 사용합니다.
+- Makefile을 기반으로 실행되며, `make [옵션] [target]` 을 통해 해당하는 명령어(command)가 실행됩니다.
+ ```makefile
+ # Makefile 형식
+ 목표(target): 의존성(dependencies)
+ 명령어(commands)
+
+ # 예시 (make docclean / make pythoninfo)
+ docclean:
+ -rm -rf Doc/build
+ -rm -rf Doc/tools/sphinx Doc/tools/pygments Doc/tools/docutils
+
+ pythoninfo: build_all
+ $(RUNSHARED) ./$(BUILDPYTHON) -m test.pythoninfo
+ ```
+
+- make 옵션
+ - `-d`(`--debug`): 디버깅 정보 출력
+ - `-e` (`—environment-overrides`): Makefile보다 우선할 환경 변수 지정
+ - `-i`(`—-ignore-errors`): 에러 무시하기
+ - `-j [N]`(`—-jobs`): N개 작업 동시 실행
+ - `-k`(`—-keep-going`): 특정 타깃 실패하더라도 계속 진행
+ - `-l [N]` (`--load-average`, `--max-load`): 평균 부하가 N 미만일 때 동시 작업 시작
+ - `-n`(`-dry-run`): 명령 실행하지 않고 출력하기
+ - `-s`(`--silent`): 실행 명령어 출력 X
+ - `-S`(`--stop`): 타깃 실패하면 중지하기
+
+- make를 사용하는 이유
+ - C/C++ 같은 컴파일 언어 사용 시,
+ 애플리케이션을 소스에서 컴파일 할 때 시스템의 외부 라이브러리 링크해야 하는데,
+ 코드를 올바르게 로드하고 링크 및 컴파일하기 위해 많은 명령어를 실행해야 할 수도 있습니다.
+ - CPython에서처럼 `./configure` 실행 시 autoconf를 통해 필요한 라이브러리 위치 찾아서 Makefile에 붙여 넣어주고,
+ make 실행을 통해 위와 같은 문제를 쉽게 실행할 수 있도록 해결해줍니다.
+
+- CPython make 타깃
+ - 빌드 타깃
+ - all(기본): 컴파일러, 라이브러리, 모듈 빌드
+ - clinic: 모든 소스 파일에 대해 인자 클리닉 실행
+ - profile-opt: 프로파일 기반 최적화를 통해 파이썬 바이너리 빌드
+ - regen-all: 생성된 파일을 전부 다시 생성
+ - sharedmods: 공유 모듈 빌드
+ - 테스트 타깃
+ - coverage: 컴파일 후 gcov로 테스트 실행
+ - coverage-lcov: html 커버리지 보고 생성
+ - quicktest: 빠른 회귀 테스트만 실행 (오래 걸리는 테스트 제외)
+ - test: 기본적인 회귀 테스트 실행
+ - testall: .pyc 파일 없는/있는 상태로 한 번씩 전체 테스트 스위트 실행
+ - testuniversal: macOS 유니버셜 빌드에서 여러 아키텍처에 대한 테스트 스위트 실행
+ - 정리 타깃
+ - check-clean-src: 빌드 시 소스 검사
+ - clean: .pyc 파일과 컴파일된 라이브러리, 프로파일 삭제
+ - cleantest: 직전에 실패한 테스트의 test_python_* 경로 삭제
+ - clobber: 라이브러리, 태그, 구성, 빌드 삭제
+ - distclean: Makefile을 비롯한 소스에서 생성된 모든 파일 삭제
+ - docclean: Doc/ 경로에 생성된 문서 삭제
+ - profile-removal: 모든 최적화 프로파일 삭제
+ - pycremoval: .pyc 파일 삭제
+ - 설치 타깃
+ - altbininstall: 버전 명시한 python 인터프리터 설치 (ex: python3.9)
+ - altinstall: 공유 라이브러리, 바이너리, 문서를 버전 접미사와 함께 설치
+ - altmaninstall: 버전 붙은 매뉴얼 설치
+ - bininstall: python, idle, 2to3 등 모든 바이너리 설치
+ - commoninstall: 공유 라이브러리 및 모듈 설치
+ - libinstall: 공유 라이브러리 설치
+ - maninstall: 문서 설치
+ - install: 공유 라이브러리, 바이너리, 문서 설치
+ (commoninstall + bininstall + maninstall 실행)
+ - sharedinstall: 동적으로 모듈 로드
+ - 기타 타깃
+ - autoconf: configure / pyconfig.h.in 다시 생성
+ - python-config: python-config 스크립트 생성
+ - recheck: 이전과 같은 옵션으로 configure 다시 실행
+ - smelly: 내보내진 심벌이 Py 또는 _Py로 시작하는지 확인
+ - tags: vi용 태그 파일 생성
+ - TAGS: 이맥스용 태그 파일 생성
+
+
+### Windows
+
+여기에서는 “명령 프롬프트 사용하기” 방식만 다룰 예정이며,
+ ”Visual Studio에서 PCbuild/pcbuild.sln 열어서 빌드하기” 방식도 있습니다.
+ (CPython 파헤치기 도서 32페이지 참고)
+
+```powershell
+# 의존성 설치 (외부 도구, 라이브러리, C 헤더 등 설치)
+PCbuild/get_externals.bat
+
+# PCbuild/amd64/python_d.exe 바이너리 파일 생성
+build.bat -p x64 -c Debug # 디버그 버전
+# 디버그 버전 바이너리 파일 실행
+amd64\python_d.exe
+
+# PCbuild/amd64/python.exe 바이너리 파일 생성
+build.bat -p x64 -c Release # 릴리즈 버전 (프로파일 기반 최적화 구성 사용됨)
+# 릴리즈 버전 바이너리 파일 실행
+amd64\python.exe
+```
+
+### 프로파일 기반 최적화
+
+- 프로파일 기반 최적화(PGO: Profile-Guided Optimization)는 컴파일러에서 제공하는 기능으로,
+최초 컴파일 후 → 일련의 테스트 실행 → 애플리케이션을 프로파일링하는 최적화 방식입니다.
+- `python -m test --pgo`로 실행합니다. (Lib/test/libregrtest/pgo.py 회귀 테스트 실행)
+ 
+- 출력 내용
+ ```python
+ Raised RLIMIT_NOFILE: 256 -> 1024
+ 0:00:00 load avg: 2.78 Run tests sequentially
+ 0:00:00 load avg: 2.78 [ 1/43] test_array
+ 0:00:01 load avg: 4.64 [ 2/43] test_base64
+ 0:00:01 load avg: 4.64 [ 3/43] test_binascii
+ 0:00:01 load avg: 4.64 [ 4/43] test_binop
+ 0:00:01 load avg: 4.64 [ 5/43] test_bisect
+ 0:00:02 load avg: 4.64 [ 6/43] test_bytes
+ 0:00:05 load avg: 4.42 [ 7/43] test_bz2
+ 0:00:06 load avg: 4.42 [ 8/43] test_cmath
+ 0:00:06 load avg: 4.42 [ 9/43] test_codecs
+ 0:00:09 load avg: 4.42 [10/43] test_collections
+ /Users/user/Documents/cpython/Lib/test/test_collections.py:1509: DeprecationWarning: Please use assertEqual instead.
+ self.assertEquals(len(s), len(items) - 1)
+ 0:00:10 load avg: 4.63 [11/43] test_complex
+ 0:00:11 load avg: 4.63 [12/43] test_dataclasses
+ 0:00:11 load avg: 4.63 [13/43] test_datetime
+ 0:00:18 load avg: 4.66 [14/43] test_decimal
+ ------------------------------------------------ NOTICE ------------------------------------------------
+ test_decimal may generate "malloc can't allocate region"
+ warnings on macOS systems. This behavior is known. Do not
+ report a bug unless tests are also failing. See bpo-40928.
+ --------------------------------------------------------------------------------------------------------
+ 0:00:26 load avg: 5.08 [15/43] test_difflib
+ 0:00:28 load avg: 5.08 [16/43] test_embed
+ 0:00:36 load avg: 5.62 [17/43] test_float
+ 0:00:36 load avg: 5.62 [18/43] test_fstring
+ 0:00:37 load avg: 5.62 [19/43] test_functools
+ 0:00:37 load avg: 5.62 [20/43] test_generators
+ 0:00:38 load avg: 5.62 [21/43] test_hashlib
+ 0:00:39 load avg: 5.62 [22/43] test_heapq
+ 0:00:40 load avg: 5.33 [23/43] test_int
+ 0:00:41 load avg: 5.33 [24/43] test_itertools
+ 0:00:49 load avg: 5.30 [25/43] test_json
+ 0:00:54 load avg: 5.04 [26/43] test_long
+ 0:01:00 load avg: 4.64 [27/43] test_lzma
+ 0:01:00 load avg: 4.64 [28/43] test_math -- test_lzma skipped
+ 0:01:05 load avg: 4.75 [29/43] test_memoryview
+ 0:01:06 load avg: 4.75 [30/43] test_operator
+ 0:01:06 load avg: 4.75 [31/43] test_ordered_dict
+ 0:01:07 load avg: 4.75 [32/43] test_pickle
+ 0:01:18 load avg: 5.11 [33/43] test_pprint
+ 0:01:19 load avg: 5.11 [34/43] test_re
+ 0:01:21 load avg: 5.10 [35/43] test_set
+ 0:01:30 load avg: 4.63 [36/43] test_sqlite
+ 0:01:31 load avg: 4.63 [37/43] test_statistics
+ 0:01:34 load avg: 4.63 [38/43] test_struct
+ 0:01:35 load avg: 4.58 [39/43] test_tabnanny
+ 0:01:36 load avg: 4.58 [40/43] test_time
+ 0:01:38 load avg: 4.58 [41/43] test_unicode
+ 0:01:40 load avg: 4.37 [42/43] test_xml_etree
+ 0:01:41 load avg: 4.37 [43/43] test_xml_etree_c
+
+ Total duration: 1 min 43 sec
+ Tests result: SUCCESS
+ ```
+- 최적화 된 파이썬 배포
+ - macOS: `./configure --enable-optimization`
+ - Windows: `build.bat --pgo`
+- 최적화에 포함되는 기능
+ - **함수 inline 처리**
+ : 다른 함수에서 자주 호출될 경우, 스택 크기 줄이기 위해 inline으로 변경되거나 호출자 함수에 복사됩니다.
+ - **가상 호출 추론**
+ : 특정 함수에 대한 가상 함수 호출이 빈번하게 일어나면, 조건부로 실행되는 직접 호출을 대상 함수에 추가합니다.
+ - **레지스터 할당 최적화**
+ : 프로파일 데이터를 기반으로 최적화를 하면 레지스터 할당이 향상됩니다.
+ - **기본 블록 최적화**
+ : 지정된 프레임 내 일시적으로 자주 실행되는 기본 블록을 동일한 페이지 집합에 배치
+ → 사용되는 페이지 수 최소화 → 메모리 오버헤드도 최소화 시킵니다.
+ - **핫스팟 최적화**
+ : 가장 많이 실행되는 함수의 실행 시간을 최적화시킵니다.
+ - **함수 레이아웃 최적화**
+ : 호출 그래프에 따라 동일한 실행 경로를 따르는 함수를 컴파일된 애플리케이션의 동일한 섹션에 배치합니다.
+ - **조건부 분기 최적화**
+ : 같은 분기에서 가장 자주 사용되는 분기를 찾아 바로 실행될 수 있도록 해당 분기를 먼저 배치합니다.
+ - **미사용 코드 분리**
+ : 호출되지 않은 코드를 애플리케이션의 별도 섹션으로 옮깁니다.
diff --git a/book/docs/4_0_python_grammar.md b/book/docs/4_0_python_grammar.md
index 59073c4..145dd40 100644
--- a/book/docs/4_0_python_grammar.md
+++ b/book/docs/4_0_python_grammar.md
@@ -1,51 +1,299 @@
-# 파이썬 언어와 문법
+# 4. 언어와 문법
+
+- 파이썬 애플리케이션은 보통 소스 코드 형태로 배포됩니다.
+- 소스 코드를 바이트코드라는 중간 언어로 컴파일하고 → `.pyc` 파일에 저장 + 실행 위해 캐싱을 진행합니다.
+- 파이썬 인터프리터가 해당 바이트코드(`.pyc`)를 한 줄씩 읽고 실행합니다.
+ - CPython 런타임이 첫 번째 실행될 때 코드를 컴파일하지만, 일반 사용자에게 노출되지는 않습니다.
+ - 코드 변경 없이 같은 파이썬 애플리케이션 다시 실행하면 → 컴파일된 바이트코드를 불러와서 더 빠르게 실행합니다.
+
+
+💡 이식성(portability)을 기준으로 컴파일러를 선택한다면..
+
+> - 저수준 기계어
+> - 시스템에서 바로 실행할 수 있는 기계어로 컴파일
+> - 바이러니 실행 파일로 컴파일 → 컴파일한 플랫폼과 동일한 플랫폼에서 사용 가능
+> - ex) C, Go, C++, Pascal
+> - 중간 언어
+> - 가상 머신에서 실행하기 위한 언어로 컴파일
+> - 여러 시스템 아키텍처에서 사용 가능한 중간 언어로 컴파일
+> - ex) 닷넷 CLR, JAVA, **Python**
+
+
+
## 4.1 CPython이 파이썬이 아니라 C로 작성된 이유
-CPython은 C로 만들어진 컴파일러를 사용한다.
-- 파이썬에서 이용하는 많은 라이브러리가 C로 되어있기 때문
-## 4.2 파이썬 언어 사양
+
+결론 먼저 보기
-### 언어 레퍼넌스
-Doc/reference에 언어의 구조, 키워드를 정의해둔다
+> - CPython은 파이썬에서 이용하는 많은 라이브러리가 C로 되어있기 때문에 C로 만들어진 컴파일러를 사용합니다.
+> - 안정적인 언어로 다양한 표준 라이브러리 모듈을 이용하기 위해서 C 컴파일러를 사용하고 있습니다.
-### 문법
-PEG 표현식을 이용
+
+
+
-- \* 반복을 의미
-- \+ 최소 한번의 반복
-- \[\] 선택적
-- | 대안
-- () 그룹
+- 새로운 프로그래밍 언어를 만들려면 한 언어(source language)를 다른 만들고자 하는 언어(target language)로 바꿔줄 컴파일러가 필요합니다.
+- 새로운 언어 개발 시 어떤 프로그램이든 실행할 수 있어야 하기 때문에 보통 **더 오래되고 안정적인 언어**로 컴파일러를 작성합니다.
-eg)
-coffee: 'cup' ('espresso') + \['water'\] \[milk\]
+> 💡 컴파일러 유형
+> - 셀프 호스팅 컴파일러
+> - 자기 자신으로 작성한 컴파일러 (부트스트래핑 단계를 통해 만들어짐)
+> - ex) Go(C로 작성된 첫번째 Go 컴파일러가 Go를 컴파일할 수 있게 되자 → 컴파일러를 Go로 재작성), PyPy(파이썬으로 작성된 파이썬 컴파일러)
+> - 소스 대 소스(source-to-source) 컴파일러
+> - 이미 갖고 있는 다른 언어로 작성한 컴파일러
+> - ex) CPython (C → Python)
-milk: 'full-fat' | 'skimmed' | 'soy'
+- 여러 표준 라이브러리 모듈(ssl, sockets 등)도 저수준 운영체제 API에 접근하기 위해서 C로 작성되어 있고,
+ 네트워크 소켓 만들기, 파일 시스템 조작, 디스플레이와 상호작용하는 윈도우/리눅스 커널 API도 모두 C로 작성되어 있기 때문에 **파이썬 또한 확장성을 고려하여 C로 작성되었다**고 볼 수 있습니다.
-철도 다이어그램
-
-### Parser
-Grammar/python.gram에 파이썬 문법이 PEG로 정의가 되어있다
-
+## 4.2 파이썬 언어 사양
+- 컴파일러가 언어를 실행하기 위해서는 문법 구조에 대한 엄격한 규칙인 **언어 사양**이 필요합니다.
+- 언어 사양은 모든 파이썬 인터프리터 구현이 사용하는 레퍼런스 사양으로,
+ - 사람이 읽을 수 있는 형식 + 기계가 읽을 수 있는 형식으로 제공합니다.
+ - 문법 형식 + 각 문법 요소가 실행되는 방식을 자세히 설명하고 있습니다.
+### 언어 레퍼런스
+사람이 읽을 수 있는 형식으로, Doc/reference에 언어의 구조, 키워드를 정의해두고 있습니다.
+```bash
+Doc/reference
+├── index.rst # 언어 레퍼런스 목차
+├── introduction.rst # 레퍼런스 문서 개요
+├── compound_stmts.rst # 복합문 (if, while, for, 함수 정의 등)
+├── datamodel.rst # 객체, 값, 타입
+├── executionmodel.rst # 프로그램 구조
+├── expressions.rst # 표현식 구성 요소
+├── grammar.rst # 문법 규격(Grammar/Grammar 참조)
+├── import.rst # import 시스템
+├── lexical_analysis.rst # 어휘 구조 (줄, 들여쓰기, 토큰, 키워드 등)
+├── simple_stmts.rst # 단순문 (assert, import, return, yield 등)
+└── toplevel_components.rst # 스크립트 및 모듈 실행 방법 설명
```
-'pass' { _Py_Pass(EXTRA) }
+
+ex1) if (compound_stmts.rst)
+
+```markdown
+.. _if:
+.. _elif:
+.. _else:
+
+The :keyword:`!if` statement
+============================
+
+.. index::
+ ! statement: if
+ keyword: elif
+ keyword: else
+ single: : (colon); compound statement
+
+The :keyword:`if` statement is used for conditional execution:
+
+.. productionlist:: python-grammar
+ if_stmt: "if" `assignment_expression` ":" `suite`
+ : ("elif" `assignment_expression` ":" `suite`)*
+ : ["else" ":" `suite`]
+
+It selects exactly one of the suites by evaluating the expressions one by one
+until one is found to be true (see section :ref:`booleans` for the definition of
+true and false); then that suite is executed (and no other part of the
+:keyword:`if` statement is executed or evaluated). If all expressions are
+false, the suite of the :keyword:`else` clause, if present, is executed.
```
-줄을
+
+
+
+ex2) Class instance (datamodel.rst)
+
+```markdown
+Class instances
+ .. index::
+ object: class instance
+ object: instance
+ pair: class; instance
+ pair: class instance; attribute
+
+ A class instance is created by calling a class object (see above). A class
+ instance has a namespace implemented as a dictionary which is the first place
+ in which attribute references are searched. When an attribute is not found
+ there, and the instance's class has an attribute by that name, the search
+ continues with the class attributes. If a class attribute is found that is a
+ user-defined function object, it is transformed into an instance method
+ object whose :attr:`__self__` attribute is the instance. Static method and
+ class method objects are also transformed; see above under "Classes". See
+ section :ref:`descriptors` for another way in which attributes of a class
+ retrieved via its instances may differ from the objects actually stored in
+ the class's :attr:`~object.__dict__`. If no class attribute is found, and the
+ object's class has a :meth:`~object.__getattr__` method, that is called to satisfy
+ the lookup.
+
+ .. index:: triple: class instance; attribute; assignment
+ Attribute assignments and deletions update the instance's dictionary, never a
+ class's dictionary. If the class has a :meth:`~object.__setattr__` or
+ :meth:`~object.__delattr__` method, this is called instead of updating the instance
+ dictionary directly.
+ .. index::
+ object: numeric
+ object: sequence
+ object: mapping
+
+ Class instances can pretend to be numbers, sequences, or mappings if they have
+ methods with certain special names. See section :ref:`specialnames`.
+
+ .. index::
+ single: __dict__ (instance attribute)
+ single: __class__ (instance attribute)
+
+ Special attributes: :attr:`~object.__dict__` is the attribute dictionary;
+ :attr:`~instance.__class__` is the instance's class.
```
-('pass'|'proceed') { _Py_Pass(EXTRA) }
+
+
+
+
+### 문법
+기계가 읽을 수 있는 형식으로, Grammar/python.gram에 PEG 표현식을 통해 정의하고 있습니다.
+
+- 파서 표현식 문법(parsing expression grammar, PEG) 사양
+ - `*`: 반복
+ - `+`: 최소 1번 반복
+ - `[]`: 선택적인 부분
+ - `|`: 대안
+ - `()`: 그룹
+- 철도 다이어그램
+ 
+
+
+
+ex0) 커피 레시피
+
+- 예시
+ - 컵 필요: `'cup'`
+ - 최소 에스프레소 한 샷 이상: `('espresso')+`
+ - 물 사용 (옵션): `['water']`
+ - 우유 사용 (옵션): `[milk]`
+ - 우유 사용했다면, 탈지우유나 두유 등 여러 종류의 우유 선택 가능: `milk: 'full-fat' | 'skimmed' | 'soy'`
+- 정의
+
+ ```makefile
+ coffee: 'cup' ('espresso')+ ['water'] [milk]
+ milk: 'full-fat' | 'skimmed' | 'soy'
+ ```
+- 철도 다이어그램
+ 
+
+
+
+
+ex1) while문
+
+- 예시
+ 1. 표현식 & `:` 단말 기호 & 코드 블록으로 구성
+
+ ```python
+ while finished:
+ do_things()
+ ```
+
+ 2. named_expression 대입 표현식 사용 (값 할당하는 동시에 그 값을 평가하는 표현식)
+
+ ```python
+ while letters := read(document, 10):
+ print(letters)
+ ```
+
+ 3. while문 다음에 else 블록 사용
+
+ ```python
+ while item := next(iterable):
+ print(item)
+ else:
+ print("Iterable is empty")
+ ```
+
+- 정의 (while_stmt 문법 파일)
+
+ ```python
+ # Grammar/python.gram#L165
+
+ while_stmt[stmt_ty]:
+ | 'while' a=named_expression ':' b=block c=[else_block] ...
+ ```
+
+- 철도 다이어그램
+
+ 
+
+
+
+
+ex2) try문
+
+- 정의
+
+ ```python
+ # Grammar/python.gram#L189
+
+ try_stmt[stmt_ty]:
+ | 'try' ':' b=block f=finally_block { _Py_Try(b, NULL, NULL, f, EXTRA) }
+ | 'try' ':' b=block ex=except_block+ el=[else_block] f=[finally_block] { _Py_Try(b, ex, el, f, EXTRA) }
+
+ finally_block[asdl_seq*]: 'finally' ':' a=block { a }
+
+ except_block[excepthandler_ty]:
+ | 'except' e=expression t=['as' z=NAME { z }] ':' b=block {
+ _Py_ExceptHandler(e, (t) ? ((expr_ty) t)->v.Name.id : NULL, b, EXTRA) }
+ | 'except' ':' b=block { _Py_ExceptHandler(NULL, NULL, b, EXTRA) }
+
+ else_block[asdl_seq*]: 'else' ':' b=block { b }
+ ```
+
+- 철도 다이어그램
+
+ 
+
+
+
+
+##### 문법 다시 생성해보기
+
+Grammar/python.gram 에서 간단문 정의(small_stmt)에서 pass 문이 정의된 것을 볼 수 있습니다.
+
+```python
+small_stmt[stmt_ty] (memo):
+ | assignment
+ | e=star_expressions { _Py_Expr(e, EXTRA) }
+ | &'return' return_stmt
+ | &('import' | 'from') import_stmt
+ | &'raise' raise_stmt
+ **| 'pass' { _Py_Pass(EXTRA) }**
+ | &'del' del_stmt
+ | &'yield' yield_stmt
+ | &'assert' assert_stmt
+ | 'break' { _Py_Break(EXTRA) }
+ | 'continue' { _Py_Continue(EXTRA) }
+ | &'global' global_stmt
+ | &'nonlocal' nonlocal_stmt
```
-로 바꾸고 컴파일 하면,
+pass 정의 `'pass' { _Py_Pass(EXTRA) }`를
+ `('pass'|'proceed') { _Py_Pass(EXTRA) }`로 바꾸고 아래와 같이 컴파일 하면,
+```
+# macOS
+make regen-pegen
+make -j2 -s
-proceed라는 함수가 pass랑 동일한 action을 하게 된다
+# Windows
+build.bat --regen
+build.bat -t CleanAll
+build.bat -t Build
+```
+proceed라는 함수가 pass랑 동일한 action을 하게 됩니다.
```
def test1():
pass
@@ -54,15 +302,1006 @@ def test2():
proceed
```
-위에서 test1()이랑 test2()의 action은 동일하다.
+위에서 test1()이랑 test2()의 action은 동일하게 됩니다.
+ 이처럼, python.gram 파일 수정을 통해서 파이썬 문법을 변경할 수 있습니다.
-python.gram파일 수정을 통해서 파이썬 문법을 변경할 수 있다
+
-## 4.4 토큰
-Grammar/Tokens 파일은 파스 트리의 Leaf node에서 사용되는 고유 토큰들을 정의한다. 코드 tokenization은 추후에 compiling할때 이용된다.
+💡 **파서 생성기**
+- 파이썬 컴파일러는 문법 파일을 직접 사용하지 않고, 파서 생성기가 문법 파일에서 생성한 파서를 사용합니다.
+- 문법 파일을 수정하면 → 파서 재생성 후 → CPython을 다시 컴파일해야 합니다.
+- Python 3.9부터는 파서 테이블 생성기(pgen 모듈) 대신 문맥 의존 문법 파서(pegen)를 사용합니다.
-
+
-python코드가 tokenizer를 통해서 토큰으로 파싱이 된다. 위 코드를 tokenize를 하면:
+### 토큰
+Grammar/Tokens 파일은 파스 트리의 Leaf node에서 사용되는 고유 토큰들을 정의합니다.
+코드 tokenization은 추후 컴파일링 할 때 이용되며,
+각 토큰별로 이름, 자동으로 생성된 고유 ID를 지니고 있습니다.
+```python
+LPAR '('
+RPAR ')'
+LSQB '['
+RSQB ']'
+COLON ':'
+COMMA ','
+```
+
+tokenizer를 통해서 토큰으로 파싱이 되며, 아래 예시를 통해 tokenize 되는 결과를 확인할 수 있습니다.
+
+- 예시 스크립트 파일 (test_tokens.py)
+ ```python
+ def my_function():
+ pass
+ ```
+- `./python.exe -m tokenize -e test_tokens.py` (토큰 목록 출력)
+ ```python
+ 0,0-0,0: ENCODING 'utf-8'
+ 1,0-1,3: NAME 'def'
+ 1,4-1,15: NAME 'my_function'
+ 1,15-1,16: LPAR '('
+ 1,16-1,17: RPAR ')'
+ 1,17-1,18: COLON ':'
+ 1,18-1,19: NEWLINE '\n'
+ 2,0-2,4: INDENT ' '
+ 2,4-2,8: NAME 'pass'
+ 2,8-2,9: NEWLINE '\n'
+ 3,0-3,0: DEDENT ''
+ 3,0-3,0: ENDMARKER ''
+ ```
+
+
+`./python.exe -d test_tokens.py` (디버그 빌드)
+
+```python
+ > file[0-0]: statements? $
+ > statements[0-0]: statement+
+ > _loop1_11[0-0]: statement
+ > statement[0-0]: compound_stmt
+ > compound_stmt[0-0]: &('def' | '@' | ASYNC) function_def
+ > _tmp_15[0-0]: 'def'
+ + _tmp_15[0-1]: 'def' succeeded!
+ > function_def[0-0]: decorators function_def_raw
+ > decorators[0-0]: (('@' named_expression NEWLINE))+
+ > _loop1_68[0-0]: ('@' named_expression NEWLINE)
+ > _tmp_140[0-0]: '@' named_expression NEWLINE
+ - _tmp_140[0-0]: '@' named_expression NEWLINE failed!
+ - _loop1_68[0-0]: ('@' named_expression NEWLINE) failed!
+ - decorators[0-0]: (('@' named_expression NEWLINE))+ failed!
+ - function_def[0-0]: decorators function_def_raw failed!
+ > function_def[0-0]: function_def_raw
+ > function_def_raw[0-0]: 'def' NAME '(' params? ')' ['->' expression] ':' func_type_comment? block
+ > params[3-3]: parameters
+ > parameters[3-3]: slash_no_default param_no_default* param_with_default* star_etc?
+ > slash_no_default[3-3]: param_no_default+ '/' ','
+ > _loop1_60[3-3]: param_no_default
+ > param_no_default[3-3]: param ',' TYPE_COMMENT?
+ > param[3-3]: NAME annotation?
+ - param[3-3]: NAME annotation? failed!
+ - param_no_default[3-3]: param ',' TYPE_COMMENT? failed!
+ > param_no_default[3-3]: param TYPE_COMMENT? &')'
+ > param[3-3]: NAME annotation?
+ - param[3-3]: NAME annotation? failed!
+ - param_no_default[3-3]: param TYPE_COMMENT? &')' failed!
+ - _loop1_60[3-3]: param_no_default failed!
+ - slash_no_default[3-3]: param_no_default+ '/' ',' failed!
+ > slash_no_default[3-3]: param_no_default+ '/' &')'
+ > _loop1_61[3-3]: param_no_default
+ > param_no_default[3-3]: param ',' TYPE_COMMENT?
+ > param[3-3]: NAME annotation?
+ - param[3-3]: NAME annotation? failed!
+ - param_no_default[3-3]: param ',' TYPE_COMMENT? failed!
+ > param_no_default[3-3]: param TYPE_COMMENT? &')'
+ > param[3-3]: NAME annotation?
+ - param[3-3]: NAME annotation? failed!
+ - param_no_default[3-3]: param TYPE_COMMENT? &')' failed!
+ - _loop1_61[3-3]: param_no_default failed!
+ - slash_no_default[3-3]: param_no_default+ '/' &')' failed!
+ - parameters[3-3]: slash_no_default param_no_default* param_with_default* star_etc? failed!
+ > parameters[3-3]: slash_with_default param_with_default* star_etc?
+ > slash_with_default[3-3]: param_no_default* param_with_default+ '/' ','
+ > _loop0_62[3-3]: param_no_default
+ > param_no_default[3-3]: param ',' TYPE_COMMENT?
+ > param[3-3]: NAME annotation?
+ - param[3-3]: NAME annotation? failed!
+ - param_no_default[3-3]: param ',' TYPE_COMMENT? failed!
+ > param_no_default[3-3]: param TYPE_COMMENT? &')'
+ > param[3-3]: NAME annotation?
+ - param[3-3]: NAME annotation? failed!
+ - param_no_default[3-3]: param TYPE_COMMENT? &')' failed!
+ - _loop0_62[3-3]: param_no_default failed!
+ > _loop1_63[3-3]: param_with_default
+ > param_with_default[3-3]: param default ',' TYPE_COMMENT?
+ > param[3-3]: NAME annotation?
+ - param[3-3]: NAME annotation? failed!
+ - param_with_default[3-3]: param default ',' TYPE_COMMENT? failed!
+ > param_with_default[3-3]: param default TYPE_COMMENT? &')'
+ > param[3-3]: NAME annotation?
+ - param[3-3]: NAME annotation? failed!
+ - param_with_default[3-3]: param default TYPE_COMMENT? &')' failed!
+ - _loop1_63[3-3]: param_with_default failed!
+ - slash_with_default[3-3]: param_no_default* param_with_default+ '/' ',' failed!
+ > slash_with_default[3-3]: param_no_default* param_with_default+ '/' &')'
+ > _loop0_64[3-3]: param_no_default
+ > param_no_default[3-3]: param ',' TYPE_COMMENT?
+ > param[3-3]: NAME annotation?
+ - param[3-3]: NAME annotation? failed!
+ - param_no_default[3-3]: param ',' TYPE_COMMENT? failed!
+ > param_no_default[3-3]: param TYPE_COMMENT? &')'
+ > param[3-3]: NAME annotation?
+ - param[3-3]: NAME annotation? failed!
+ - param_no_default[3-3]: param TYPE_COMMENT? &')' failed!
+ - _loop0_64[3-3]: param_no_default failed!
+ > _loop1_65[3-3]: param_with_default
+ > param_with_default[3-3]: param default ',' TYPE_COMMENT?
+ > param[3-3]: NAME annotation?
+ - param[3-3]: NAME annotation? failed!
+ - param_with_default[3-3]: param default ',' TYPE_COMMENT? failed!
+ > param_with_default[3-3]: param default TYPE_COMMENT? &')'
+ > param[3-3]: NAME annotation?
+ - param[3-3]: NAME annotation? failed!
+ - param_with_default[3-3]: param default TYPE_COMMENT? &')' failed!
+ - _loop1_65[3-3]: param_with_default failed!
+ - slash_with_default[3-3]: param_no_default* param_with_default+ '/' &')' failed!
+ - parameters[3-3]: slash_with_default param_with_default* star_etc? failed!
+ > parameters[3-3]: param_no_default+ param_with_default* star_etc?
+ > _loop1_57[3-3]: param_no_default
+ > param_no_default[3-3]: param ',' TYPE_COMMENT?
+ > param[3-3]: NAME annotation?
+ - param[3-3]: NAME annotation? failed!
+ - param_no_default[3-3]: param ',' TYPE_COMMENT? failed!
+ > param_no_default[3-3]: param TYPE_COMMENT? &')'
+ > param[3-3]: NAME annotation?
+ - param[3-3]: NAME annotation? failed!
+ - param_no_default[3-3]: param TYPE_COMMENT? &')' failed!
+ - _loop1_57[3-3]: param_no_default failed!
+ - parameters[3-3]: param_no_default+ param_with_default* star_etc? failed!
+ > parameters[3-3]: param_with_default+ star_etc?
+ > _loop1_59[3-3]: param_with_default
+ > param_with_default[3-3]: param default ',' TYPE_COMMENT?
+ > param[3-3]: NAME annotation?
+ - param[3-3]: NAME annotation? failed!
+ - param_with_default[3-3]: param default ',' TYPE_COMMENT? failed!
+ > param_with_default[3-3]: param default TYPE_COMMENT? &')'
+ > param[3-3]: NAME annotation?
+ - param[3-3]: NAME annotation? failed!
+ - param_with_default[3-3]: param default TYPE_COMMENT? &')' failed!
+ - _loop1_59[3-3]: param_with_default failed!
+ - parameters[3-3]: param_with_default+ star_etc? failed!
+ > parameters[3-3]: star_etc
+ > star_etc[3-3]: '*' param_no_default param_maybe_default* kwds?
+ - star_etc[3-3]: '*' param_no_default param_maybe_default* kwds? failed!
+ > star_etc[3-3]: '*' ',' param_maybe_default+ kwds?
+ - star_etc[3-3]: '*' ',' param_maybe_default+ kwds? failed!
+ > star_etc[3-3]: kwds
+ > kwds[3-3]: '**' param_no_default
+ - kwds[3-3]: '**' param_no_default failed!
+ - star_etc[3-3]: kwds failed!
+ - parameters[3-3]: star_etc failed!
+ - params[3-3]: parameters failed!
+ > _tmp_51[4-4]: '->' expression
+ - _tmp_51[4-4]: '->' expression failed!
+ > func_type_comment[5-5]: NEWLINE TYPE_COMMENT &(NEWLINE INDENT)
+ - func_type_comment[5-5]: NEWLINE TYPE_COMMENT &(NEWLINE INDENT) failed!
+ > func_type_comment[5-5]: TYPE_COMMENT
+ - func_type_comment[5-5]: TYPE_COMMENT failed!
+ > block[5-5]: NEWLINE INDENT statements DEDENT
+ > statements[7-7]: statement+
+ > _loop1_11[7-7]: statement
+ > statement[7-7]: compound_stmt
+ > compound_stmt[7-7]: &('def' | '@' | ASYNC) function_def
+ > _tmp_15[7-7]: 'def'
+ - _tmp_15[7-7]: 'def' failed!
+ > _tmp_15[7-7]: '@'
+ - _tmp_15[7-7]: '@' failed!
+ > _tmp_15[7-7]: ASYNC
+ - _tmp_15[7-7]: ASYNC failed!
+ - compound_stmt[7-7]: &('def' | '@' | ASYNC) function_def failed!
+ > compound_stmt[7-7]: &'if' if_stmt
+ - compound_stmt[7-7]: &'if' if_stmt failed!
+ > compound_stmt[7-7]: &('class' | '@') class_def
+ > _tmp_16[7-7]: 'class'
+ - _tmp_16[7-7]: 'class' failed!
+ > _tmp_16[7-7]: '@'
+ - _tmp_16[7-7]: '@' failed!
+ - compound_stmt[7-7]: &('class' | '@') class_def failed!
+ > compound_stmt[7-7]: &('with' | ASYNC) with_stmt
+ > _tmp_17[7-7]: 'with'
+ - _tmp_17[7-7]: 'with' failed!
+ > _tmp_17[7-7]: ASYNC
+ - _tmp_17[7-7]: ASYNC failed!
+ - compound_stmt[7-7]: &('with' | ASYNC) with_stmt failed!
+ > compound_stmt[7-7]: &('for' | ASYNC) for_stmt
+ > _tmp_18[7-7]: 'for'
+ - _tmp_18[7-7]: 'for' failed!
+ > _tmp_18[7-7]: ASYNC
+ - _tmp_18[7-7]: ASYNC failed!
+ - compound_stmt[7-7]: &('for' | ASYNC) for_stmt failed!
+ > compound_stmt[7-7]: &'try' try_stmt
+ - compound_stmt[7-7]: &'try' try_stmt failed!
+ > compound_stmt[7-7]: &'while' while_stmt
+ - compound_stmt[7-7]: &'while' while_stmt failed!
+ - statement[7-7]: compound_stmt failed!
+ > statement[7-7]: simple_stmt
+ > simple_stmt[7-7]: small_stmt !';' NEWLINE
+ > small_stmt[7-7]: assignment
+ > assignment[7-7]: NAME ':' expression ['=' annotated_rhs]
+ - assignment[7-7]: NAME ':' expression ['=' annotated_rhs] failed!
+ > assignment[7-7]: ('(' single_target ')' | single_subscript_attribute_target) ':' expression ['=' annotated_rhs]
+ > _tmp_20[7-7]: '(' single_target ')'
+ - _tmp_20[7-7]: '(' single_target ')' failed!
+ > _tmp_20[7-7]: single_subscript_attribute_target
+ > single_subscript_attribute_target[7-7]: t_primary '.' NAME !t_lookahead
+ > t_primary[7-7]: t_primary '.' NAME &t_lookahead
+ - t_primary[7-7]: t_primary '.' NAME &t_lookahead failed!
+ > t_primary[7-7]: t_primary '[' slices ']' &t_lookahead
+ - t_primary[7-7]: t_primary '[' slices ']' &t_lookahead failed!
+ > t_primary[7-7]: t_primary genexp &t_lookahead
+ - t_primary[7-7]: t_primary genexp &t_lookahead failed!
+ > t_primary[7-7]: t_primary '(' arguments? ')' &t_lookahead
+ - t_primary[7-7]: t_primary '(' arguments? ')' &t_lookahead failed!
+ > t_primary[7-7]: atom &t_lookahead
+ > atom[7-7]: NAME
+ - atom[7-7]: NAME failed!
+ > atom[7-7]: 'True'
+ - atom[7-7]: 'True' failed!
+ > atom[7-7]: 'False'
+ - atom[7-7]: 'False' failed!
+ > atom[7-7]: 'None'
+ - atom[7-7]: 'None' failed!
+ > atom[7-7]: '__peg_parser__'
+ - atom[7-7]: '__peg_parser__' failed!
+ > atom[7-7]: &STRING strings
+ - atom[7-7]: &STRING strings failed!
+ > atom[7-7]: NUMBER
+ - atom[7-7]: NUMBER failed!
+ > atom[7-7]: &'(' (tuple | group | genexp)
+ - atom[7-7]: &'(' (tuple | group | genexp) failed!
+ > atom[7-7]: &'[' (list | listcomp)
+ - atom[7-7]: &'[' (list | listcomp) failed!
+ > atom[7-7]: &'{' (dict | set | dictcomp | setcomp)
+ - atom[7-7]: &'{' (dict | set | dictcomp | setcomp) failed!
+ > atom[7-7]: '...'
+ - atom[7-7]: '...' failed!
+ - t_primary[7-7]: atom &t_lookahead failed!
+ - single_subscript_attribute_target[7-7]: t_primary '.' NAME !t_lookahead failed!
+ > single_subscript_attribute_target[7-7]: t_primary '[' slices ']' !t_lookahead
+ - single_subscript_attribute_target[7-7]: t_primary '[' slices ']' !t_lookahead failed!
+ - _tmp_20[7-7]: single_subscript_attribute_target failed!
+ - assignment[7-7]: ('(' single_target ')' | single_subscript_attribute_target) ':' expression ['=' annotated_rhs] failed!
+ > assignment[7-7]: ((star_targets '='))+ (yield_expr | star_expressions) !'=' TYPE_COMMENT?
+ > _loop1_22[7-7]: (star_targets '=')
+ > _tmp_137[7-7]: star_targets '='
+ > star_targets[7-7]: star_target !','
+ > star_target[7-7]: '*' (!'*' star_target)
+ - star_target[7-7]: '*' (!'*' star_target) failed!
+ > star_target[7-7]: target_with_star_atom
+ > target_with_star_atom[7-7]: t_primary '.' NAME !t_lookahead
+ - target_with_star_atom[7-7]: t_primary '.' NAME !t_lookahead failed!
+ > target_with_star_atom[7-7]: t_primary '[' slices ']' !t_lookahead
+ - target_with_star_atom[7-7]: t_primary '[' slices ']' !t_lookahead failed!
+ > target_with_star_atom[7-7]: star_atom
+ > star_atom[7-7]: NAME
+ - star_atom[7-7]: NAME failed!
+ > star_atom[7-7]: '(' target_with_star_atom ')'
+ - star_atom[7-7]: '(' target_with_star_atom ')' failed!
+ > star_atom[7-7]: '(' star_targets_tuple_seq? ')'
+ - star_atom[7-7]: '(' star_targets_tuple_seq? ')' failed!
+ > star_atom[7-7]: '[' star_targets_list_seq? ']'
+ - star_atom[7-7]: '[' star_targets_list_seq? ']' failed!
+ - target_with_star_atom[7-7]: star_atom failed!
+ - star_target[7-7]: target_with_star_atom failed!
+ - star_targets[7-7]: star_target !',' failed!
+ > star_targets[7-7]: star_target ((',' star_target))* ','?
+ - star_targets[7-7]: star_target ((',' star_target))* ','? failed!
+ - _tmp_137[7-7]: star_targets '=' failed!
+ - _loop1_22[7-7]: (star_targets '=') failed!
+ - assignment[7-7]: ((star_targets '='))+ (yield_expr | star_expressions) !'=' TYPE_COMMENT? failed!
+ > assignment[7-7]: single_target augassign ~ (yield_expr | star_expressions)
+ > single_target[7-7]: single_subscript_attribute_target
+ > single_subscript_attribute_target[7-7]: t_primary '.' NAME !t_lookahead
+ - single_subscript_attribute_target[7-7]: t_primary '.' NAME !t_lookahead failed!
+ > single_subscript_attribute_target[7-7]: t_primary '[' slices ']' !t_lookahead
+ - single_subscript_attribute_target[7-7]: t_primary '[' slices ']' !t_lookahead failed!
+ - single_target[7-7]: single_subscript_attribute_target failed!
+ > single_target[7-7]: NAME
+ - single_target[7-7]: NAME failed!
+ > single_target[7-7]: '(' single_target ')'
+ - single_target[7-7]: '(' single_target ')' failed!
+ - assignment[7-7]: single_target augassign ~ (yield_expr | star_expressions) failed!
+ - small_stmt[7-7]: assignment failed!
+ > small_stmt[7-7]: star_expressions
+ > star_expressions[7-7]: star_expression ((',' star_expression))+ ','?
+ > star_expression[7-7]: '*' bitwise_or
+ - star_expression[7-7]: '*' bitwise_or failed!
+ > star_expression[7-7]: expression
+ > expression[7-7]: disjunction 'if' disjunction 'else' expression
+ > disjunction[7-7]: conjunction (('or' conjunction))+
+ > conjunction[7-7]: inversion (('and' inversion))+
+ > inversion[7-7]: 'not' inversion
+ - inversion[7-7]: 'not' inversion failed!
+ > inversion[7-7]: comparison
+ > comparison[7-7]: bitwise_or compare_op_bitwise_or_pair+
+ > bitwise_or[7-7]: bitwise_or '|' bitwise_xor
+ - bitwise_or[7-7]: bitwise_or '|' bitwise_xor failed!
+ > bitwise_or[7-7]: bitwise_xor
+ > bitwise_xor[7-7]: bitwise_xor '^' bitwise_and
+ - bitwise_xor[7-7]: bitwise_xor '^' bitwise_and failed!
+ > bitwise_xor[7-7]: bitwise_and
+ > bitwise_and[7-7]: bitwise_and '&' shift_expr
+ - bitwise_and[7-7]: bitwise_and '&' shift_expr failed!
+ > bitwise_and[7-7]: shift_expr
+ > shift_expr[7-7]: shift_expr '<<' sum
+ - shift_expr[7-7]: shift_expr '<<' sum failed!
+ > shift_expr[7-7]: shift_expr '>>' sum
+ - shift_expr[7-7]: shift_expr '>>' sum failed!
+ > shift_expr[7-7]: sum
+ > sum[7-7]: sum '+' term
+ - sum[7-7]: sum '+' term failed!
+ > sum[7-7]: sum '-' term
+ - sum[7-7]: sum '-' term failed!
+ > sum[7-7]: term
+ > term[7-7]: term '*' factor
+ - term[7-7]: term '*' factor failed!
+ > term[7-7]: term '/' factor
+ - term[7-7]: term '/' factor failed!
+ > term[7-7]: term '//' factor
+ - term[7-7]: term '//' factor failed!
+ > term[7-7]: term '%' factor
+ - term[7-7]: term '%' factor failed!
+ > term[7-7]: term '@' factor
+ - term[7-7]: term '@' factor failed!
+ > term[7-7]: factor
+ > factor[7-7]: '+' factor
+ - factor[7-7]: '+' factor failed!
+ > factor[7-7]: '-' factor
+ - factor[7-7]: '-' factor failed!
+ > factor[7-7]: '~' factor
+ - factor[7-7]: '~' factor failed!
+ > factor[7-7]: power
+ > power[7-7]: await_primary '**' factor
+ > await_primary[7-7]: AWAIT primary
+ - await_primary[7-7]: AWAIT primary failed!
+ > await_primary[7-7]: primary
+ > primary[7-7]: primary '.' NAME
+ - primary[7-7]: primary '.' NAME failed!
+ > primary[7-7]: primary genexp
+ - primary[7-7]: primary genexp failed!
+ > primary[7-7]: primary '(' arguments? ')'
+ - primary[7-7]: primary '(' arguments? ')' failed!
+ > primary[7-7]: primary '[' slices ']'
+ - primary[7-7]: primary '[' slices ']' failed!
+ > primary[7-7]: atom
+ > atom[7-7]: NAME
+ - atom[7-7]: NAME failed!
+ > atom[7-7]: 'True'
+ - atom[7-7]: 'True' failed!
+ > atom[7-7]: 'False'
+ - atom[7-7]: 'False' failed!
+ > atom[7-7]: 'None'
+ - atom[7-7]: 'None' failed!
+ > atom[7-7]: '__peg_parser__'
+ - atom[7-7]: '__peg_parser__' failed!
+ > atom[7-7]: &STRING strings
+ - atom[7-7]: &STRING strings failed!
+ > atom[7-7]: NUMBER
+ - atom[7-7]: NUMBER failed!
+ > atom[7-7]: &'(' (tuple | group | genexp)
+ - atom[7-7]: &'(' (tuple | group | genexp) failed!
+ > atom[7-7]: &'[' (list | listcomp)
+ - atom[7-7]: &'[' (list | listcomp) failed!
+ > atom[7-7]: &'{' (dict | set | dictcomp | setcomp)
+ - atom[7-7]: &'{' (dict | set | dictcomp | setcomp) failed!
+ > atom[7-7]: '...'
+ - atom[7-7]: '...' failed!
+ - primary[7-7]: atom failed!
+ - await_primary[7-7]: primary failed!
+ - power[7-7]: await_primary '**' factor failed!
+ > power[7-7]: await_primary
+ - power[7-7]: await_primary failed!
+ - factor[7-7]: power failed!
+ - term[7-7]: factor failed!
+ - sum[7-7]: term failed!
+ - shift_expr[7-7]: sum failed!
+ - bitwise_and[7-7]: shift_expr failed!
+ - bitwise_xor[7-7]: bitwise_and failed!
+ - bitwise_or[7-7]: bitwise_xor failed!
+ - comparison[7-7]: bitwise_or compare_op_bitwise_or_pair+ failed!
+ > comparison[7-7]: bitwise_or
+ - comparison[7-7]: bitwise_or failed!
+ - inversion[7-7]: comparison failed!
+ - conjunction[7-7]: inversion (('and' inversion))+ failed!
+ > conjunction[7-7]: inversion
+ - conjunction[7-7]: inversion failed!
+ - disjunction[7-7]: conjunction (('or' conjunction))+ failed!
+ > disjunction[7-7]: conjunction
+ - disjunction[7-7]: conjunction failed!
+ - expression[7-7]: disjunction 'if' disjunction 'else' expression failed!
+ > expression[7-7]: disjunction
+ - expression[7-7]: disjunction failed!
+ > expression[7-7]: lambdef
+ > lambdef[7-7]: 'lambda' lambda_params? ':' expression
+ - lambdef[7-7]: 'lambda' lambda_params? ':' expression failed!
+ - expression[7-7]: lambdef failed!
+ - star_expression[7-7]: expression failed!
+ - star_expressions[7-7]: star_expression ((',' star_expression))+ ','? failed!
+ > star_expressions[7-7]: star_expression ','
+ - star_expressions[7-7]: star_expression ',' failed!
+ > star_expressions[7-7]: star_expression
+ - star_expressions[7-7]: star_expression failed!
+ - small_stmt[7-7]: star_expressions failed!
+ > small_stmt[7-7]: &'return' return_stmt
+ - small_stmt[7-7]: &'return' return_stmt failed!
+ > small_stmt[7-7]: &('import' | 'from') import_stmt
+ > _tmp_14[7-7]: 'import'
+ - _tmp_14[7-7]: 'import' failed!
+ > _tmp_14[7-7]: 'from'
+ - _tmp_14[7-7]: 'from' failed!
+ - small_stmt[7-7]: &('import' | 'from') import_stmt failed!
+ > small_stmt[7-7]: &'raise' raise_stmt
+ - small_stmt[7-7]: &'raise' raise_stmt failed!
+ > small_stmt[7-7]: 'pass'
+ + small_stmt[7-8]: 'pass' succeeded!
+ + simple_stmt[7-9]: small_stmt !';' NEWLINE succeeded!
+ + statement[7-9]: simple_stmt succeeded!
+ > statement[9-9]: compound_stmt
+ > compound_stmt[9-9]: &('def' | '@' | ASYNC) function_def
+ > _tmp_15[9-9]: 'def'
+ - _tmp_15[9-9]: 'def' failed!
+ > _tmp_15[9-9]: '@'
+ - _tmp_15[9-9]: '@' failed!
+ > _tmp_15[9-9]: ASYNC
+ - _tmp_15[9-9]: ASYNC failed!
+ - compound_stmt[9-9]: &('def' | '@' | ASYNC) function_def failed!
+ > compound_stmt[9-9]: &'if' if_stmt
+ - compound_stmt[9-9]: &'if' if_stmt failed!
+ > compound_stmt[9-9]: &('class' | '@') class_def
+ > _tmp_16[9-9]: 'class'
+ - _tmp_16[9-9]: 'class' failed!
+ > _tmp_16[9-9]: '@'
+ - _tmp_16[9-9]: '@' failed!
+ - compound_stmt[9-9]: &('class' | '@') class_def failed!
+ > compound_stmt[9-9]: &('with' | ASYNC) with_stmt
+ > _tmp_17[9-9]: 'with'
+ - _tmp_17[9-9]: 'with' failed!
+ > _tmp_17[9-9]: ASYNC
+ - _tmp_17[9-9]: ASYNC failed!
+ - compound_stmt[9-9]: &('with' | ASYNC) with_stmt failed!
+ > compound_stmt[9-9]: &('for' | ASYNC) for_stmt
+ > _tmp_18[9-9]: 'for'
+ - _tmp_18[9-9]: 'for' failed!
+ > _tmp_18[9-9]: ASYNC
+ - _tmp_18[9-9]: ASYNC failed!
+ - compound_stmt[9-9]: &('for' | ASYNC) for_stmt failed!
+ > compound_stmt[9-9]: &'try' try_stmt
+ - compound_stmt[9-9]: &'try' try_stmt failed!
+ > compound_stmt[9-9]: &'while' while_stmt
+ - compound_stmt[9-9]: &'while' while_stmt failed!
+ - statement[9-9]: compound_stmt failed!
+ > statement[9-9]: simple_stmt
+ > simple_stmt[9-9]: small_stmt !';' NEWLINE
+ > small_stmt[9-9]: assignment
+ > assignment[9-9]: NAME ':' expression ['=' annotated_rhs]
+ - assignment[9-9]: NAME ':' expression ['=' annotated_rhs] failed!
+ > assignment[9-9]: ('(' single_target ')' | single_subscript_attribute_target) ':' expression ['=' annotated_rhs]
+ > _tmp_20[9-9]: '(' single_target ')'
+ - _tmp_20[9-9]: '(' single_target ')' failed!
+ > _tmp_20[9-9]: single_subscript_attribute_target
+ > single_subscript_attribute_target[9-9]: t_primary '.' NAME !t_lookahead
+ > t_primary[9-9]: t_primary '.' NAME &t_lookahead
+ - t_primary[9-9]: t_primary '.' NAME &t_lookahead failed!
+ > t_primary[9-9]: t_primary '[' slices ']' &t_lookahead
+ - t_primary[9-9]: t_primary '[' slices ']' &t_lookahead failed!
+ > t_primary[9-9]: t_primary genexp &t_lookahead
+ - t_primary[9-9]: t_primary genexp &t_lookahead failed!
+ > t_primary[9-9]: t_primary '(' arguments? ')' &t_lookahead
+ - t_primary[9-9]: t_primary '(' arguments? ')' &t_lookahead failed!
+ > t_primary[9-9]: atom &t_lookahead
+ > atom[9-9]: NAME
+ - atom[9-9]: NAME failed!
+ > atom[9-9]: 'True'
+ - atom[9-9]: 'True' failed!
+ > atom[9-9]: 'False'
+ - atom[9-9]: 'False' failed!
+ > atom[9-9]: 'None'
+ - atom[9-9]: 'None' failed!
+ > atom[9-9]: '__peg_parser__'
+ - atom[9-9]: '__peg_parser__' failed!
+ > atom[9-9]: &STRING strings
+ - atom[9-9]: &STRING strings failed!
+ > atom[9-9]: NUMBER
+ - atom[9-9]: NUMBER failed!
+ > atom[9-9]: &'(' (tuple | group | genexp)
+ - atom[9-9]: &'(' (tuple | group | genexp) failed!
+ > atom[9-9]: &'[' (list | listcomp)
+ - atom[9-9]: &'[' (list | listcomp) failed!
+ > atom[9-9]: &'{' (dict | set | dictcomp | setcomp)
+ - atom[9-9]: &'{' (dict | set | dictcomp | setcomp) failed!
+ > atom[9-9]: '...'
+ - atom[9-9]: '...' failed!
+ - t_primary[9-9]: atom &t_lookahead failed!
+ - single_subscript_attribute_target[9-9]: t_primary '.' NAME !t_lookahead failed!
+ > single_subscript_attribute_target[9-9]: t_primary '[' slices ']' !t_lookahead
+ - single_subscript_attribute_target[9-9]: t_primary '[' slices ']' !t_lookahead failed!
+ - _tmp_20[9-9]: single_subscript_attribute_target failed!
+ - assignment[9-9]: ('(' single_target ')' | single_subscript_attribute_target) ':' expression ['=' annotated_rhs] failed!
+ > assignment[9-9]: ((star_targets '='))+ (yield_expr | star_expressions) !'=' TYPE_COMMENT?
+ > _loop1_22[9-9]: (star_targets '=')
+ > _tmp_137[9-9]: star_targets '='
+ > star_targets[9-9]: star_target !','
+ > star_target[9-9]: '*' (!'*' star_target)
+ - star_target[9-9]: '*' (!'*' star_target) failed!
+ > star_target[9-9]: target_with_star_atom
+ > target_with_star_atom[9-9]: t_primary '.' NAME !t_lookahead
+ - target_with_star_atom[9-9]: t_primary '.' NAME !t_lookahead failed!
+ > target_with_star_atom[9-9]: t_primary '[' slices ']' !t_lookahead
+ - target_with_star_atom[9-9]: t_primary '[' slices ']' !t_lookahead failed!
+ > target_with_star_atom[9-9]: star_atom
+ > star_atom[9-9]: NAME
+ - star_atom[9-9]: NAME failed!
+ > star_atom[9-9]: '(' target_with_star_atom ')'
+ - star_atom[9-9]: '(' target_with_star_atom ')' failed!
+ > star_atom[9-9]: '(' star_targets_tuple_seq? ')'
+ - star_atom[9-9]: '(' star_targets_tuple_seq? ')' failed!
+ > star_atom[9-9]: '[' star_targets_list_seq? ']'
+ - star_atom[9-9]: '[' star_targets_list_seq? ']' failed!
+ - target_with_star_atom[9-9]: star_atom failed!
+ - star_target[9-9]: target_with_star_atom failed!
+ - star_targets[9-9]: star_target !',' failed!
+ > star_targets[9-9]: star_target ((',' star_target))* ','?
+ - star_targets[9-9]: star_target ((',' star_target))* ','? failed!
+ - _tmp_137[9-9]: star_targets '=' failed!
+ - _loop1_22[9-9]: (star_targets '=') failed!
+ - assignment[9-9]: ((star_targets '='))+ (yield_expr | star_expressions) !'=' TYPE_COMMENT? failed!
+ > assignment[9-9]: single_target augassign ~ (yield_expr | star_expressions)
+ > single_target[9-9]: single_subscript_attribute_target
+ > single_subscript_attribute_target[9-9]: t_primary '.' NAME !t_lookahead
+ - single_subscript_attribute_target[9-9]: t_primary '.' NAME !t_lookahead failed!
+ > single_subscript_attribute_target[9-9]: t_primary '[' slices ']' !t_lookahead
+ - single_subscript_attribute_target[9-9]: t_primary '[' slices ']' !t_lookahead failed!
+ - single_target[9-9]: single_subscript_attribute_target failed!
+ > single_target[9-9]: NAME
+ - single_target[9-9]: NAME failed!
+ > single_target[9-9]: '(' single_target ')'
+ - single_target[9-9]: '(' single_target ')' failed!
+ - assignment[9-9]: single_target augassign ~ (yield_expr | star_expressions) failed!
+ - small_stmt[9-9]: assignment failed!
+ > small_stmt[9-9]: star_expressions
+ > star_expressions[9-9]: star_expression ((',' star_expression))+ ','?
+ > star_expression[9-9]: '*' bitwise_or
+ - star_expression[9-9]: '*' bitwise_or failed!
+ > star_expression[9-9]: expression
+ > expression[9-9]: disjunction 'if' disjunction 'else' expression
+ > disjunction[9-9]: conjunction (('or' conjunction))+
+ > conjunction[9-9]: inversion (('and' inversion))+
+ > inversion[9-9]: 'not' inversion
+ - inversion[9-9]: 'not' inversion failed!
+ > inversion[9-9]: comparison
+ > comparison[9-9]: bitwise_or compare_op_bitwise_or_pair+
+ > bitwise_or[9-9]: bitwise_or '|' bitwise_xor
+ - bitwise_or[9-9]: bitwise_or '|' bitwise_xor failed!
+ > bitwise_or[9-9]: bitwise_xor
+ > bitwise_xor[9-9]: bitwise_xor '^' bitwise_and
+ - bitwise_xor[9-9]: bitwise_xor '^' bitwise_and failed!
+ > bitwise_xor[9-9]: bitwise_and
+ > bitwise_and[9-9]: bitwise_and '&' shift_expr
+ - bitwise_and[9-9]: bitwise_and '&' shift_expr failed!
+ > bitwise_and[9-9]: shift_expr
+ > shift_expr[9-9]: shift_expr '<<' sum
+ - shift_expr[9-9]: shift_expr '<<' sum failed!
+ > shift_expr[9-9]: shift_expr '>>' sum
+ - shift_expr[9-9]: shift_expr '>>' sum failed!
+ > shift_expr[9-9]: sum
+ > sum[9-9]: sum '+' term
+ - sum[9-9]: sum '+' term failed!
+ > sum[9-9]: sum '-' term
+ - sum[9-9]: sum '-' term failed!
+ > sum[9-9]: term
+ > term[9-9]: term '*' factor
+ - term[9-9]: term '*' factor failed!
+ > term[9-9]: term '/' factor
+ - term[9-9]: term '/' factor failed!
+ > term[9-9]: term '//' factor
+ - term[9-9]: term '//' factor failed!
+ > term[9-9]: term '%' factor
+ - term[9-9]: term '%' factor failed!
+ > term[9-9]: term '@' factor
+ - term[9-9]: term '@' factor failed!
+ > term[9-9]: factor
+ > factor[9-9]: '+' factor
+ - factor[9-9]: '+' factor failed!
+ > factor[9-9]: '-' factor
+ - factor[9-9]: '-' factor failed!
+ > factor[9-9]: '~' factor
+ - factor[9-9]: '~' factor failed!
+ > factor[9-9]: power
+ > power[9-9]: await_primary '**' factor
+ > await_primary[9-9]: AWAIT primary
+ - await_primary[9-9]: AWAIT primary failed!
+ > await_primary[9-9]: primary
+ > primary[9-9]: primary '.' NAME
+ - primary[9-9]: primary '.' NAME failed!
+ > primary[9-9]: primary genexp
+ - primary[9-9]: primary genexp failed!
+ > primary[9-9]: primary '(' arguments? ')'
+ - primary[9-9]: primary '(' arguments? ')' failed!
+ > primary[9-9]: primary '[' slices ']'
+ - primary[9-9]: primary '[' slices ']' failed!
+ > primary[9-9]: atom
+ > atom[9-9]: NAME
+ - atom[9-9]: NAME failed!
+ > atom[9-9]: 'True'
+ - atom[9-9]: 'True' failed!
+ > atom[9-9]: 'False'
+ - atom[9-9]: 'False' failed!
+ > atom[9-9]: 'None'
+ - atom[9-9]: 'None' failed!
+ > atom[9-9]: '__peg_parser__'
+ - atom[9-9]: '__peg_parser__' failed!
+ > atom[9-9]: &STRING strings
+ - atom[9-9]: &STRING strings failed!
+ > atom[9-9]: NUMBER
+ - atom[9-9]: NUMBER failed!
+ > atom[9-9]: &'(' (tuple | group | genexp)
+ - atom[9-9]: &'(' (tuple | group | genexp) failed!
+ > atom[9-9]: &'[' (list | listcomp)
+ - atom[9-9]: &'[' (list | listcomp) failed!
+ > atom[9-9]: &'{' (dict | set | dictcomp | setcomp)
+ - atom[9-9]: &'{' (dict | set | dictcomp | setcomp) failed!
+ > atom[9-9]: '...'
+ - atom[9-9]: '...' failed!
+ - primary[9-9]: atom failed!
+ - await_primary[9-9]: primary failed!
+ - power[9-9]: await_primary '**' factor failed!
+ > power[9-9]: await_primary
+ - power[9-9]: await_primary failed!
+ - factor[9-9]: power failed!
+ - term[9-9]: factor failed!
+ - sum[9-9]: term failed!
+ - shift_expr[9-9]: sum failed!
+ - bitwise_and[9-9]: shift_expr failed!
+ - bitwise_xor[9-9]: bitwise_and failed!
+ - bitwise_or[9-9]: bitwise_xor failed!
+ - comparison[9-9]: bitwise_or compare_op_bitwise_or_pair+ failed!
+ > comparison[9-9]: bitwise_or
+ - comparison[9-9]: bitwise_or failed!
+ - inversion[9-9]: comparison failed!
+ - conjunction[9-9]: inversion (('and' inversion))+ failed!
+ > conjunction[9-9]: inversion
+ - conjunction[9-9]: inversion failed!
+ - disjunction[9-9]: conjunction (('or' conjunction))+ failed!
+ > disjunction[9-9]: conjunction
+ - disjunction[9-9]: conjunction failed!
+ - expression[9-9]: disjunction 'if' disjunction 'else' expression failed!
+ > expression[9-9]: disjunction
+ - expression[9-9]: disjunction failed!
+ > expression[9-9]: lambdef
+ > lambdef[9-9]: 'lambda' lambda_params? ':' expression
+ - lambdef[9-9]: 'lambda' lambda_params? ':' expression failed!
+ - expression[9-9]: lambdef failed!
+ - star_expression[9-9]: expression failed!
+ - star_expressions[9-9]: star_expression ((',' star_expression))+ ','? failed!
+ > star_expressions[9-9]: star_expression ','
+ - star_expressions[9-9]: star_expression ',' failed!
+ > star_expressions[9-9]: star_expression
+ - star_expressions[9-9]: star_expression failed!
+ - small_stmt[9-9]: star_expressions failed!
+ > small_stmt[9-9]: &'return' return_stmt
+ - small_stmt[9-9]: &'return' return_stmt failed!
+ > small_stmt[9-9]: &('import' | 'from') import_stmt
+ > _tmp_14[9-9]: 'import'
+ - _tmp_14[9-9]: 'import' failed!
+ > _tmp_14[9-9]: 'from'
+ - _tmp_14[9-9]: 'from' failed!
+ - small_stmt[9-9]: &('import' | 'from') import_stmt failed!
+ > small_stmt[9-9]: &'raise' raise_stmt
+ - small_stmt[9-9]: &'raise' raise_stmt failed!
+ > small_stmt[9-9]: 'pass'
+ - small_stmt[9-9]: 'pass' failed!
+ > small_stmt[9-9]: &'del' del_stmt
+ - small_stmt[9-9]: &'del' del_stmt failed!
+ > small_stmt[9-9]: &'yield' yield_stmt
+ - small_stmt[9-9]: &'yield' yield_stmt failed!
+ > small_stmt[9-9]: &'assert' assert_stmt
+ - small_stmt[9-9]: &'assert' assert_stmt failed!
+ > small_stmt[9-9]: 'break'
+ - small_stmt[9-9]: 'break' failed!
+ > small_stmt[9-9]: 'continue'
+ - small_stmt[9-9]: 'continue' failed!
+ > small_stmt[9-9]: &'global' global_stmt
+ - small_stmt[9-9]: &'global' global_stmt failed!
+ > small_stmt[9-9]: &'nonlocal' nonlocal_stmt
+ - small_stmt[9-9]: &'nonlocal' nonlocal_stmt failed!
+ - simple_stmt[9-9]: small_stmt !';' NEWLINE failed!
+ > simple_stmt[9-9]: ';'.small_stmt+ ';'? NEWLINE
+ > _gather_12[9-9]: small_stmt _loop0_13
+ - _gather_12[9-9]: small_stmt _loop0_13 failed!
+ - simple_stmt[9-9]: ';'.small_stmt+ ';'? NEWLINE failed!
+ - statement[9-9]: simple_stmt failed!
+ - _loop1_11[9-9]: statement failed!
+ + statements[7-9]: statement+ succeeded!
+ + block[5-10]: NEWLINE INDENT statements DEDENT succeeded!
+ + function_def_raw[0-10]: 'def' NAME '(' params? ')' ['->' expression] ':' func_type_comment? block succeeded!
+ + function_def[0-10]: function_def_raw succeeded!
+ + compound_stmt[0-10]: &('def' | '@' | ASYNC) function_def succeeded!
+ + statement[0-10]: compound_stmt succeeded!
+ > statement[10-10]: compound_stmt
+ > compound_stmt[10-10]: &('def' | '@' | ASYNC) function_def
+ > _tmp_15[10-10]: 'def'
+ - _tmp_15[10-10]: 'def' failed!
+ > _tmp_15[10-10]: '@'
+ - _tmp_15[10-10]: '@' failed!
+ > _tmp_15[10-10]: ASYNC
+ - _tmp_15[10-10]: ASYNC failed!
+ - compound_stmt[10-10]: &('def' | '@' | ASYNC) function_def failed!
+ > compound_stmt[10-10]: &'if' if_stmt
+ - compound_stmt[10-10]: &'if' if_stmt failed!
+ > compound_stmt[10-10]: &('class' | '@') class_def
+ > _tmp_16[10-10]: 'class'
+ - _tmp_16[10-10]: 'class' failed!
+ > _tmp_16[10-10]: '@'
+ - _tmp_16[10-10]: '@' failed!
+ - compound_stmt[10-10]: &('class' | '@') class_def failed!
+ > compound_stmt[10-10]: &('with' | ASYNC) with_stmt
+ > _tmp_17[10-10]: 'with'
+ - _tmp_17[10-10]: 'with' failed!
+ > _tmp_17[10-10]: ASYNC
+ - _tmp_17[10-10]: ASYNC failed!
+ - compound_stmt[10-10]: &('with' | ASYNC) with_stmt failed!
+ > compound_stmt[10-10]: &('for' | ASYNC) for_stmt
+ > _tmp_18[10-10]: 'for'
+ - _tmp_18[10-10]: 'for' failed!
+ > _tmp_18[10-10]: ASYNC
+ - _tmp_18[10-10]: ASYNC failed!
+ - compound_stmt[10-10]: &('for' | ASYNC) for_stmt failed!
+ > compound_stmt[10-10]: &'try' try_stmt
+ - compound_stmt[10-10]: &'try' try_stmt failed!
+ > compound_stmt[10-10]: &'while' while_stmt
+ - compound_stmt[10-10]: &'while' while_stmt failed!
+ - statement[10-10]: compound_stmt failed!
+ > statement[10-10]: simple_stmt
+ > simple_stmt[10-10]: small_stmt !';' NEWLINE
+ > small_stmt[10-10]: assignment
+ > assignment[10-10]: NAME ':' expression ['=' annotated_rhs]
+ - assignment[10-10]: NAME ':' expression ['=' annotated_rhs] failed!
+ > assignment[10-10]: ('(' single_target ')' | single_subscript_attribute_target) ':' expression ['=' annotated_rhs]
+ > _tmp_20[10-10]: '(' single_target ')'
+ - _tmp_20[10-10]: '(' single_target ')' failed!
+ > _tmp_20[10-10]: single_subscript_attribute_target
+ > single_subscript_attribute_target[10-10]: t_primary '.' NAME !t_lookahead
+ > t_primary[10-10]: t_primary '.' NAME &t_lookahead
+ - t_primary[10-10]: t_primary '.' NAME &t_lookahead failed!
+ > t_primary[10-10]: t_primary '[' slices ']' &t_lookahead
+ - t_primary[10-10]: t_primary '[' slices ']' &t_lookahead failed!
+ > t_primary[10-10]: t_primary genexp &t_lookahead
+ - t_primary[10-10]: t_primary genexp &t_lookahead failed!
+ > t_primary[10-10]: t_primary '(' arguments? ')' &t_lookahead
+ - t_primary[10-10]: t_primary '(' arguments? ')' &t_lookahead failed!
+ > t_primary[10-10]: atom &t_lookahead
+ > atom[10-10]: NAME
+ - atom[10-10]: NAME failed!
+ > atom[10-10]: 'True'
+ - atom[10-10]: 'True' failed!
+ > atom[10-10]: 'False'
+ - atom[10-10]: 'False' failed!
+ > atom[10-10]: 'None'
+ - atom[10-10]: 'None' failed!
+ > atom[10-10]: '__peg_parser__'
+ - atom[10-10]: '__peg_parser__' failed!
+ > atom[10-10]: &STRING strings
+ - atom[10-10]: &STRING strings failed!
+ > atom[10-10]: NUMBER
+ - atom[10-10]: NUMBER failed!
+ > atom[10-10]: &'(' (tuple | group | genexp)
+ - atom[10-10]: &'(' (tuple | group | genexp) failed!
+ > atom[10-10]: &'[' (list | listcomp)
+ - atom[10-10]: &'[' (list | listcomp) failed!
+ > atom[10-10]: &'{' (dict | set | dictcomp | setcomp)
+ - atom[10-10]: &'{' (dict | set | dictcomp | setcomp) failed!
+ > atom[10-10]: '...'
+ - atom[10-10]: '...' failed!
+ - t_primary[10-10]: atom &t_lookahead failed!
+ - single_subscript_attribute_target[10-10]: t_primary '.' NAME !t_lookahead failed!
+ > single_subscript_attribute_target[10-10]: t_primary '[' slices ']' !t_lookahead
+ - single_subscript_attribute_target[10-10]: t_primary '[' slices ']' !t_lookahead failed!
+ - _tmp_20[10-10]: single_subscript_attribute_target failed!
+ - assignment[10-10]: ('(' single_target ')' | single_subscript_attribute_target) ':' expression ['=' annotated_rhs] failed!
+ > assignment[10-10]: ((star_targets '='))+ (yield_expr | star_expressions) !'=' TYPE_COMMENT?
+ > _loop1_22[10-10]: (star_targets '=')
+ > _tmp_137[10-10]: star_targets '='
+ > star_targets[10-10]: star_target !','
+ > star_target[10-10]: '*' (!'*' star_target)
+ - star_target[10-10]: '*' (!'*' star_target) failed!
+ > star_target[10-10]: target_with_star_atom
+ > target_with_star_atom[10-10]: t_primary '.' NAME !t_lookahead
+ - target_with_star_atom[10-10]: t_primary '.' NAME !t_lookahead failed!
+ > target_with_star_atom[10-10]: t_primary '[' slices ']' !t_lookahead
+ - target_with_star_atom[10-10]: t_primary '[' slices ']' !t_lookahead failed!
+ > target_with_star_atom[10-10]: star_atom
+ > star_atom[10-10]: NAME
+ - star_atom[10-10]: NAME failed!
+ > star_atom[10-10]: '(' target_with_star_atom ')'
+ - star_atom[10-10]: '(' target_with_star_atom ')' failed!
+ > star_atom[10-10]: '(' star_targets_tuple_seq? ')'
+ - star_atom[10-10]: '(' star_targets_tuple_seq? ')' failed!
+ > star_atom[10-10]: '[' star_targets_list_seq? ']'
+ - star_atom[10-10]: '[' star_targets_list_seq? ']' failed!
+ - target_with_star_atom[10-10]: star_atom failed!
+ - star_target[10-10]: target_with_star_atom failed!
+ - star_targets[10-10]: star_target !',' failed!
+ > star_targets[10-10]: star_target ((',' star_target))* ','?
+ - star_targets[10-10]: star_target ((',' star_target))* ','? failed!
+ - _tmp_137[10-10]: star_targets '=' failed!
+ - _loop1_22[10-10]: (star_targets '=') failed!
+ - assignment[10-10]: ((star_targets '='))+ (yield_expr | star_expressions) !'=' TYPE_COMMENT? failed!
+ > assignment[10-10]: single_target augassign ~ (yield_expr | star_expressions)
+ > single_target[10-10]: single_subscript_attribute_target
+ > single_subscript_attribute_target[10-10]: t_primary '.' NAME !t_lookahead
+ - single_subscript_attribute_target[10-10]: t_primary '.' NAME !t_lookahead failed!
+ > single_subscript_attribute_target[10-10]: t_primary '[' slices ']' !t_lookahead
+ - single_subscript_attribute_target[10-10]: t_primary '[' slices ']' !t_lookahead failed!
+ - single_target[10-10]: single_subscript_attribute_target failed!
+ > single_target[10-10]: NAME
+ - single_target[10-10]: NAME failed!
+ > single_target[10-10]: '(' single_target ')'
+ - single_target[10-10]: '(' single_target ')' failed!
+ - assignment[10-10]: single_target augassign ~ (yield_expr | star_expressions) failed!
+ - small_stmt[10-10]: assignment failed!
+ > small_stmt[10-10]: star_expressions
+ > star_expressions[10-10]: star_expression ((',' star_expression))+ ','?
+ > star_expression[10-10]: '*' bitwise_or
+ - star_expression[10-10]: '*' bitwise_or failed!
+ > star_expression[10-10]: expression
+ > expression[10-10]: disjunction 'if' disjunction 'else' expression
+ > disjunction[10-10]: conjunction (('or' conjunction))+
+ > conjunction[10-10]: inversion (('and' inversion))+
+ > inversion[10-10]: 'not' inversion
+ - inversion[10-10]: 'not' inversion failed!
+ > inversion[10-10]: comparison
+ > comparison[10-10]: bitwise_or compare_op_bitwise_or_pair+
+ > bitwise_or[10-10]: bitwise_or '|' bitwise_xor
+ - bitwise_or[10-10]: bitwise_or '|' bitwise_xor failed!
+ > bitwise_or[10-10]: bitwise_xor
+ > bitwise_xor[10-10]: bitwise_xor '^' bitwise_and
+ - bitwise_xor[10-10]: bitwise_xor '^' bitwise_and failed!
+ > bitwise_xor[10-10]: bitwise_and
+ > bitwise_and[10-10]: bitwise_and '&' shift_expr
+ - bitwise_and[10-10]: bitwise_and '&' shift_expr failed!
+ > bitwise_and[10-10]: shift_expr
+ > shift_expr[10-10]: shift_expr '<<' sum
+ - shift_expr[10-10]: shift_expr '<<' sum failed!
+ > shift_expr[10-10]: shift_expr '>>' sum
+ - shift_expr[10-10]: shift_expr '>>' sum failed!
+ > shift_expr[10-10]: sum
+ > sum[10-10]: sum '+' term
+ - sum[10-10]: sum '+' term failed!
+ > sum[10-10]: sum '-' term
+ - sum[10-10]: sum '-' term failed!
+ > sum[10-10]: term
+ > term[10-10]: term '*' factor
+ - term[10-10]: term '*' factor failed!
+ > term[10-10]: term '/' factor
+ - term[10-10]: term '/' factor failed!
+ > term[10-10]: term '//' factor
+ - term[10-10]: term '//' factor failed!
+ > term[10-10]: term '%' factor
+ - term[10-10]: term '%' factor failed!
+ > term[10-10]: term '@' factor
+ - term[10-10]: term '@' factor failed!
+ > term[10-10]: factor
+ > factor[10-10]: '+' factor
+ - factor[10-10]: '+' factor failed!
+ > factor[10-10]: '-' factor
+ - factor[10-10]: '-' factor failed!
+ > factor[10-10]: '~' factor
+ - factor[10-10]: '~' factor failed!
+ > factor[10-10]: power
+ > power[10-10]: await_primary '**' factor
+ > await_primary[10-10]: AWAIT primary
+ - await_primary[10-10]: AWAIT primary failed!
+ > await_primary[10-10]: primary
+ > primary[10-10]: primary '.' NAME
+ - primary[10-10]: primary '.' NAME failed!
+ > primary[10-10]: primary genexp
+ - primary[10-10]: primary genexp failed!
+ > primary[10-10]: primary '(' arguments? ')'
+ - primary[10-10]: primary '(' arguments? ')' failed!
+ > primary[10-10]: primary '[' slices ']'
+ - primary[10-10]: primary '[' slices ']' failed!
+ > primary[10-10]: atom
+ > atom[10-10]: NAME
+ - atom[10-10]: NAME failed!
+ > atom[10-10]: 'True'
+ - atom[10-10]: 'True' failed!
+ > atom[10-10]: 'False'
+ - atom[10-10]: 'False' failed!
+ > atom[10-10]: 'None'
+ - atom[10-10]: 'None' failed!
+ > atom[10-10]: '__peg_parser__'
+ - atom[10-10]: '__peg_parser__' failed!
+ > atom[10-10]: &STRING strings
+ - atom[10-10]: &STRING strings failed!
+ > atom[10-10]: NUMBER
+ - atom[10-10]: NUMBER failed!
+ > atom[10-10]: &'(' (tuple | group | genexp)
+ - atom[10-10]: &'(' (tuple | group | genexp) failed!
+ > atom[10-10]: &'[' (list | listcomp)
+ - atom[10-10]: &'[' (list | listcomp) failed!
+ > atom[10-10]: &'{' (dict | set | dictcomp | setcomp)
+ - atom[10-10]: &'{' (dict | set | dictcomp | setcomp) failed!
+ > atom[10-10]: '...'
+ - atom[10-10]: '...' failed!
+ - primary[10-10]: atom failed!
+ - await_primary[10-10]: primary failed!
+ - power[10-10]: await_primary '**' factor failed!
+ > power[10-10]: await_primary
+ - power[10-10]: await_primary failed!
+ - factor[10-10]: power failed!
+ - term[10-10]: factor failed!
+ - sum[10-10]: term failed!
+ - shift_expr[10-10]: sum failed!
+ - bitwise_and[10-10]: shift_expr failed!
+ - bitwise_xor[10-10]: bitwise_and failed!
+ - bitwise_or[10-10]: bitwise_xor failed!
+ - comparison[10-10]: bitwise_or compare_op_bitwise_or_pair+ failed!
+ > comparison[10-10]: bitwise_or
+ - comparison[10-10]: bitwise_or failed!
+ - inversion[10-10]: comparison failed!
+ - conjunction[10-10]: inversion (('and' inversion))+ failed!
+ > conjunction[10-10]: inversion
+ - conjunction[10-10]: inversion failed!
+ - disjunction[10-10]: conjunction (('or' conjunction))+ failed!
+ > disjunction[10-10]: conjunction
+ - disjunction[10-10]: conjunction failed!
+ - expression[10-10]: disjunction 'if' disjunction 'else' expression failed!
+ > expression[10-10]: disjunction
+ - expression[10-10]: disjunction failed!
+ > expression[10-10]: lambdef
+ > lambdef[10-10]: 'lambda' lambda_params? ':' expression
+ - lambdef[10-10]: 'lambda' lambda_params? ':' expression failed!
+ - expression[10-10]: lambdef failed!
+ - star_expression[10-10]: expression failed!
+ - star_expressions[10-10]: star_expression ((',' star_expression))+ ','? failed!
+ > star_expressions[10-10]: star_expression ','
+ - star_expressions[10-10]: star_expression ',' failed!
+ > star_expressions[10-10]: star_expression
+ - star_expressions[10-10]: star_expression failed!
+ - small_stmt[10-10]: star_expressions failed!
+ > small_stmt[10-10]: &'return' return_stmt
+ - small_stmt[10-10]: &'return' return_stmt failed!
+ > small_stmt[10-10]: &('import' | 'from') import_stmt
+ > _tmp_14[10-10]: 'import'
+ - _tmp_14[10-10]: 'import' failed!
+ > _tmp_14[10-10]: 'from'
+ - _tmp_14[10-10]: 'from' failed!
+ - small_stmt[10-10]: &('import' | 'from') import_stmt failed!
+ > small_stmt[10-10]: &'raise' raise_stmt
+ - small_stmt[10-10]: &'raise' raise_stmt failed!
+ > small_stmt[10-10]: 'pass'
+ - small_stmt[10-10]: 'pass' failed!
+ > small_stmt[10-10]: &'del' del_stmt
+ - small_stmt[10-10]: &'del' del_stmt failed!
+ > small_stmt[10-10]: &'yield' yield_stmt
+ - small_stmt[10-10]: &'yield' yield_stmt failed!
+ > small_stmt[10-10]: &'assert' assert_stmt
+ - small_stmt[10-10]: &'assert' assert_stmt failed!
+ > small_stmt[10-10]: 'break'
+ - small_stmt[10-10]: 'break' failed!
+ > small_stmt[10-10]: 'continue'
+ - small_stmt[10-10]: 'continue' failed!
+ > small_stmt[10-10]: &'global' global_stmt
+ - small_stmt[10-10]: &'global' global_stmt failed!
+ > small_stmt[10-10]: &'nonlocal' nonlocal_stmt
+ - small_stmt[10-10]: &'nonlocal' nonlocal_stmt failed!
+ - simple_stmt[10-10]: small_stmt !';' NEWLINE failed!
+ > simple_stmt[10-10]: ';'.small_stmt+ ';'? NEWLINE
+ > _gather_12[10-10]: small_stmt _loop0_13
+ - _gather_12[10-10]: small_stmt _loop0_13 failed!
+ - simple_stmt[10-10]: ';'.small_stmt+ ';'? NEWLINE failed!
+ - statement[10-10]: simple_stmt failed!
+ - _loop1_11[10-10]: statement failed!
+ + statements[0-10]: statement+ succeeded!
+ + file[0-11]: statements? $ succeeded!
+```
-
+
diff --git a/book/docs/5_0_config_and_input.md b/book/docs/5_0_config_and_input.md
new file mode 100644
index 0000000..9b5e4ea
--- /dev/null
+++ b/book/docs/5_0_config_and_input.md
@@ -0,0 +1,977 @@
+# 5. 구성과 입력
+
+이번 챕터를 통해 다음 내용을 배울 수 있습니다.
+
+1. CPython 인터프리터의 빌드부터 파이썬 코드 실행까지의 과정에 대한 코드 레벨의 이해
+2. 인터프리터의 configuration 과정
+3. 인터프리터의 입력에서 모듈을 만드는 과정
+
+## 5.0 CPython Interpreter Execution Overview
+
+인터프리터가 실행되는 흐름은 다음과 같습니다.
+
+이 때, 세 가지 요소가 중요합니다.
+
+1. 실행할 모듈 (**A module** to execute)
+2. 변수 등을 저장할 상태 (**A state** to hold information such as variables)
+3. 활성화된 옵션 등의 구성 (**A configuration**, such as which options are enabled)
+
+세 가지 요소로 넘어가기 전에, 인터프리터가 실행되는 과정에 대한 이해를 높여보겠습니다. 인터프리터의 생성부터 실행까지의 과정을 코드 레벨로 정리하면 다음과 같습니다.
+
+
+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"
+
+#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;
+ }
+ if (_PyStatus_EXCEPTION(status)) {
+ pymain_exit_error(status);
+ }
+
+ return Py_RunMain();
+}
+```
+
+- `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;
+}
+```
+
+
+
+
+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;
+}
+```
+
+
+
+## 5.1 Configuration State (구성 상태)
+
+파이썬 코드를 실행하기 전에, CPython 런타임은 먼저 **configuration of the runtime**과 **user-provided options**을 설정합니다.
+
+Configuration of the runtime은 세 부분으로 나뉘어 있습니다. ([PEP 587](https://peps.python.org/pep-0587/)).
+
+1. `PyPreConfig`, used for preinitialization configuration
+2. `PyConfig`, user for the runtime configuration
+3. The **compiled(=build) configuration** of the CPython Interpreter
+
+### Preinitialization Configuration
+
+**preinitialization configuration**은 operating system 혹은 user environment와 관련된 설정이기 때문에, runtime configuration과 구분됩니다.
+
+다음은 `PyPreConfig`의 세 가지 주요 기능입니다.
+
+- [1] 파이썬 메모리 할당자(memory allocator) 설정하기
+- [2] LC_CTYPE 로캘(locale)을 system 또는 user-preferred 로캘로 설정하기
+ > ⭐ **로캘(Locale)이란?**
+ > - 특정 지역, 국가, 또는 문화권에서 사용하는 언어, 통화, 날짜 및 시간 형식과 같은 **지역 설정**을 의미함.
+ > - 로캘의 정보는 일반적으로 **언어 코드**와 **국가/지역 코드의 조합**으로 표현된다. (ex> `en-US`는 미국 영어, `ko-KR`은 대한민국 한국어를 나타냄)
+- [3] UTF-8 모드 설정하기 ([PEP 540](https://peps.python.org/pep-0540/))
+ > ⭐ **인코딩**
+ > - 파이썬은 3.7부터 로캘 설정과 상관 없이 UTF-8을 기본 인코딩으로 사용함
+
+`PyPreConfig` 구조체는 다음과 같은 int 타입 필드들을 포함합니다.
+
+- `allocator`: `PYMEM_ALLOCATOR_MALLOC` 같은 값을 사용해서 메모리 할당자를 선택합니다. `./configure —help`를 실행해 메모리 할당자에 대한 추가 정보를 얻을 수 있습니다.
+- `configure_locale`: LC_CTYPE 로캘을 user-preferred 로캘로 설정합니다. 0으로 설정하면 `coerce_c_locale`과 `coerce_c_locale_warn`을 0으로 설정합니다.
+- `coerce_c_locale`: 2로 설정하면 C 로캘을 강제로 적용합니다. 1로 설정하면 LC_CTYPE을 읽은 후 강제로 적용할지 결정합니다.
+- `coerce_c_locale_warn`: 0이 아니면 C 로캘이 강제로 적용될 때 경고가 발생합니다.
+- `dev_mode`: 개발 모드를 활성화합니다.
+- `isolated`: 격리 모드를 활성화합니다. sys.path에 스크립트 디렉토리와 사용자의 사이트 패키지 디렉토리가 포함되지 않습니다.
+- `legacy_window_fs_encoding`(윈도우 전용): 0이 아니면 UTF-8 모드를 비활성화하고, 파이썬 파일 시스템 인코딩을 mbcs로 설정합니다.
+- `parse_argv`: 0이 아니면 명령줄 인자(command-line arguments)를 사용합니다.
+- `use_environment`: 0보다 큰 값이면 환경 변수를 사용합니다.
+- `utf8_mode`: 0이 아니면 UTF-8 모드를 활성화합니다.
+-
+
+ (참고) PyPreConfig 구조체
+
+ **Include/cpython/initconfig.h: L45-L125**
+
+ ```c
+ /* --- PyPreConfig ----------------------------------------------- */
+
+ typedef struct {
+ int _config_init; /* _PyConfigInitEnum value */
+
+ /* Parse Py_PreInitializeFromBytesArgs() arguments?
+ See PyConfig.parse_argv */
+ int parse_argv;
+
+ /* If greater than 0, enable isolated mode: sys.path contains
+ neither the script's directory nor the user's site-packages directory.
+
+ Set to 1 by the -I command line option. If set to -1 (default), inherit
+ Py_IsolatedFlag value. */
+ int isolated;
+
+ /* If greater than 0: use environment variables.
+ Set to 0 by -E command line option. If set to -1 (default), it is
+ set to !Py_IgnoreEnvironmentFlag. */
+ int use_environment;
+
+ /* Set the LC_CTYPE locale to the user preferred locale? If equals to 0,
+ set coerce_c_locale and coerce_c_locale_warn to 0. */
+ int configure_locale;
+
+ /* Coerce the LC_CTYPE locale if it's equal to "C"? (PEP 538)
+
+ Set to 0 by PYTHONCOERCECLOCALE=0. Set to 1 by PYTHONCOERCECLOCALE=1.
+ Set to 2 if the user preferred LC_CTYPE locale is "C".
+
+ If it is equal to 1, LC_CTYPE locale is read to decide if it should be
+ coerced or not (ex: PYTHONCOERCECLOCALE=1). Internally, it is set to 2
+ if the LC_CTYPE locale must be coerced.
+
+ Disable by default (set to 0). Set it to -1 to let Python decide if it
+ should be enabled or not. */
+ int coerce_c_locale;
+
+ /* Emit a warning if the LC_CTYPE locale is coerced?
+
+ Set to 1 by PYTHONCOERCECLOCALE=warn.
+
+ Disable by default (set to 0). Set it to -1 to let Python decide if it
+ should be enabled or not. */
+ int coerce_c_locale_warn;
+
+ #ifdef MS_WINDOWS
+ /* If greater than 1, use the "mbcs" encoding instead of the UTF-8
+ encoding for the filesystem encoding.
+
+ Set to 1 if the PYTHONLEGACYWINDOWSFSENCODING environment variable is
+ set to a non-empty string. If set to -1 (default), inherit
+ Py_LegacyWindowsFSEncodingFlag value.
+
+ See PEP 529 for more details. */
+ int legacy_windows_fs_encoding;
+ #endif
+
+ /* Enable UTF-8 mode? (PEP 540)
+
+ Disabled by default (equals to 0).
+
+ Set to 1 by "-X utf8" and "-X utf8=1" command line options.
+ Set to 1 by PYTHONUTF8=1 environment variable.
+
+ Set to 0 by "-X utf8=0" and PYTHONUTF8=0.
+
+ If equals to -1, it is set to 1 if the LC_CTYPE locale is "C" or
+ "POSIX", otherwise it is set to 0. Inherit Py_UTF8Mode value value. */
+ int utf8_mode;
+
+ /* If non-zero, enable the Python Development Mode.
+
+ Set to 1 by the -X dev command line option. Set by the PYTHONDEVMODE
+ environment variable. */
+ int dev_mode;
+
+ /* Memory allocator: PYTHONMALLOC env var.
+ See PyMemAllocatorName for valid values. */
+ int allocator;
+ } PyPreConfig;
+ ```
+
+
+
+### Related Source Files
+
+- `Python/initconfig.c`: 시스템 환경에서 불러온 configuration을 명령줄 플래그(command-line flags)와 결합합니다.
+- `Include/cpython/initconfig.h`: `PyPreConfig`, `PyConfig`의 구조체가 정의되어 있습니다.
+
+### Runtime Configuration Data Structure
+
+Configuration의 두 번째 단계는 **runtime configuration**이다. `PyConfig`(runtime configuration 구조체)는 다음과 같은 값들을 포함합니다.
+
+- **Runtime flags** for modes like `debug` and `optimized`
+- The **mode of execution**, such as a `script file`, `stdin`, or `module`
+
+
+ -X option 으로 설정 가능한 확장 옵션(extended options)
+
+ ```
+ -X opt : set implementation-specific option. The following options are available:
+
+ -X faulthandler: enable faulthandler
+ -X oldparser: enable the traditional LL(1) parser; also PYTHONOLDPARSER
+ -X showrefcount: output the total reference count and number of used
+ memory blocks when the program finishes or after each statement in the
+ interactive interpreter. This only works on debug builds
+ -X tracemalloc: start tracing Python memory allocations using the
+ tracemalloc module. By default, only the most recent frame is stored in a
+ traceback of a trace. Use -X tracemalloc=NFRAME to start tracing with a
+ traceback limit of NFRAME frames
+ -X importtime: show how long each import takes. It shows module name,
+ cumulative time (including nested imports) and self time (excluding
+ nested imports). Note that its output may be broken in multi-threaded
+ application. Typical usage is python3 -X importtime -c 'import asyncio'
+ -X dev: enable CPython's "development mode", introducing additional runtime
+ checks which are too expensive to be enabled by default. Effect of the
+ developer mode:
+ * Add default warning filter, as -W default
+ * Install debug hooks on memory allocators: see the PyMem_SetupDebugHooks() C function
+ * Enable the faulthandler module to dump the Python traceback on a crash
+ * Enable asyncio debug mode
+ * Set the dev_mode attribute of sys.flags to True
+ * io.IOBase destructor logs close() exceptions
+ -X utf8: enable UTF-8 mode for operating system interfaces, overriding the default
+ locale-aware mode. -X utf8=0 explicitly disables UTF-8 mode (even when it would
+ otherwise activate automatically)
+ -X pycache_prefix=PATH: enable writing .pyc files to a parallel tree rooted at the
+ given directory instead of to the code tree
+ -X int_max_str_digits=number: limit the size of int<->str conversions.
+ This helps avoid denial of service attacks when parsing untrusted data.
+ The default is sys.int_info.default_max_str_digits. 0 disables.
+ ```
+
+
+
+- Environment variables for runtime settings
+
+### Setting Runtime Configuration with the Command Line
+
+파이썬은 다양한 명령줄 인터페이스 옵션(command-line interface options)을 제공합니다. 대표적인 예시로 verbose mode가 있습니다. 주로 CPython을 디버깅하려는 개발자들을 위한 모드입니다.
+
+`-v` 플래그로 verbose mode를 활성화하면, 파이썬은 모듈을 로딩할 때마다 화면에 메시지를 출력합니다.
+
+```bash
+$ ./python -v -c "print('hello world')"
+# installing zipimport hook
+import zipimport # builtin
+# installed zipimport hook
+...
+```
+
+**Runtime Configuration**을 설정하는 방법은 다양하기 때문에, 우선순위가 지정되어 있습니다. 다음은 verbose mode 설정에 대한 우선순위입니다.
+
+1. config→verbose의 기본값은 -1로 소스 코드에 하드코딩되어 있습니다.
+2. `PYTHONVERBOSE` 환경 변수를 config→verbose를 설정하는데 사용합니다.
+3. 환경 변수가 없으면 기본값인 -1을 사용합니다.
+4. **Python/initconfig.c**의 `config_parse_cmdline()`은 명시된 명령줄 플래그를 사용해 모드를 설정합니다.
+5. `_Py_GetGlobalVariableAsDict()`가 값을 전역 변수 `Py_VerboseFlag`로 복사합니다.
+
+모든 `PyConfig` 값에는 같은 순서와 우선순위가 적용됩니다.
+
+
+### Viewing Runtime Flags
+
+CPython 인터프리터는 많은 runtime flags가 있습니다. 플래그는 CPython의 특정 동작을 제어하는데 사용되는 고급 기능이다. 파이썬 세션 중에, `sys.flags` 네임드 튜플을 통해 runtime flags에 접근할 수 있습니다.
+
+- 플래그 사용 X
+
+ ```bash
+ ./python.exe
+
+ Python 3.9.19+ (heads/3.9:a04a0f6585, Mar 25 2024, 08:21:31)
+ [Clang 15.0.0 (clang-1500.3.9.4)] on darwin
+ Type "help", "copyright", "credits" or "license" for more information.
+ >>> 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=False, utf8_mode=0, int_max_str_digits=-1)
+ ```
+
+- -X dev 플래그 사용
+
+ ```bash
+ ./python.exe -X dev
+
+ Python 3.9.19+ (heads/3.9:a04a0f6585, Mar 25 2024, 08:21:31)
+ [Clang 15.0.0 (clang-1500.3.9.4)] on darwin
+ Type "help", "copyright", "credits" or "license" for more information.
+ >>> 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`로 변경됨.
+- -X dev -X utf8 플래그 사용
+
+ ```bash
+ ./python.exe -X dev -X utf8
+
+ Python 3.9.19+ (heads/3.9:a04a0f6585, Mar 25 2024, 08:21:31)
+ [Clang 15.0.0 (clang-1500.3.9.4)] on darwin
+ Type "help", "copyright", "credits" or "license" for more information.
+ >>> 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=1, int_max_str_digits=-1)
+ ```
+
+ - `utf8_mode`가 `1`로 변경됨.
+- -X dev -q 플래그 사용
+
+ ```bash
+ ./python.exe -X dev -q
+
+ >>> 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=1, hash_randomization=1, isolated=0, dev_mode=True, utf8_mode=0, int_max_str_digits=-1)
+ ```
+
+ - `-q` (=quiet) 모드라서 파이썬 버전이 출력되지 않음.
+ - `quiet`이 `1`로 변경됨.
+
+## 5.2 Build Configuration (빌드 구성)
+
+**Runtime configuration**을 `Include/cpython/initconfig.h`에 정의하듯이, **build configuration**은 root 디렉토리의 `pyconfig.h`에서 정의합니다. 이 파일은 `./configure` 단계 (macOS or Linux)나, `build.bat` 실행 (Windows) 중에 자동으로 생성됩니다.
+
+다음 명령어로 build configuration을 확인할 수 있습니다.
+
+- `./python.exe -m sysconfig`
+
+ ```bash
+ Platform: "macosx-14.4-arm64"
+ Python version: "3.9"
+ 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"
+
+ Variables:
+ ABIFLAGS = "d"
+ AC_APPLE_UNIVERSAL_BUILD = "0"
+ AIX_BUILDDATE = "0"
+ AIX_GENUINE_CPLUSPLUS = "0"
+ ...
+ ```
+
+build configuration은 5가지의 key로 구성되어 있습니다.
+
+- Platform
+- Python version
+- Current installation scheme
+- Paths
+- Variables
+
+- (참고) CPython을 컴파일 하는 과정 중, `./configure` 스크립트를 실행할 때 포함시켰던 옵션들을 확인할 수 있습니다. `CPPFLAGS`, `LDFLAGS`는 build configuration의 Variables에 포함되어 있습니다.
+
+ ```bash
+ ...
+ CONFIGURE_CPPFLAGS = "-I/opt/homebrew/opt/zlib/include -I/opt/homebrew/opt/xz/include"
+ ...
+ CONFIGURE_LDFLAGS = "-L/opt/homebrew/opt/zlib/lib -L/opt/homebrew/opt/xz/lib"
+ ...
+ ```
+
+ - (참고) `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) 라이브러리, 메모리 할당자는 모두 컴파일 시 결정됩니다.
+
+세 단계의 구성(Build Configuration, PyPreConfig, PyConfig)을 모두 완료하면, CPython Interpreter는 입력된 텍스트를 코드로 실행할 수 있게 됩니다.
+
+## 5.3 입력에서 모듈 만들기
+
+
+
+코드를 실행하려면 먼저 입력을 모듈로 컴파일해야 합니다.
+
+- 입력방식
+ - 로컬 파일과 패키지
+ - 메모리 파이프나 stdin 같은 I/O 스트림
+ - 문자열
+
+이렇게 읽어 들인 입력은 파서를 거쳐 컴파일러로 전달 됩니다. 이렇게 유연한 입력 방식을 제공하기 위해 CPython은 CPython 소스 코드의 상당 분량을 파서의 입력 처리에 사용합니다.
+
+### 연관된 소스 파일 목록
+
+- Lib > runpy.py : 파이썬 모듈을 임포트 하고 실행하는 표준 라이브러리 모듈
+- Modules > main.c : 파일이나 모듈, 입력 스트림 같은 외부 코드 실행을 감싸는 함수
+- Programs > python.c : 윈도우나, 리눅스, macOS에서 Python의 진입점. 위의 `main.c` 를 감싸는 역할만 맡음.
+- Python > pythonrun.c : 명령줄 입력을 처리하는 내부 C API를 감싸는 함수
+
+### 입력과 파일 읽기
+
+1. CPython은 런타임 구성과 명령줄 인자가 준비되면 실행할 코드를 불러옵니다.
+ - 이 작업은 `Modules > main.c > pymain_main()` 에서 실행됩니다.
+ - CPython은 어떤 파이썬 코드를 실행할지 결정하고 이 코드를 메모리에 로드합니다.
+2. 다음으로 CPython은 불러온 코드를 새로 생성된 `PyConfig` 인스턴스에 설정된 옵션들과 함께 실행됩니다.
+ - `PyConfig` 인스턴스는 CPython 인터프리터가 실행될 때 사용되는 설정들을 담고 있으며, 이 설정들은 파이썬 실행 환경을 조정하는데 사용됩니다.
+
+### 명령줄 문자열 입력
+
+> ⭐ **-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) {
+ 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)를 생성해 모듈로 반환한다.
+
+### 로컬 모듈 입력
+>
+> ⭐ **-m 옵션을 사용해 모듈을 실행하는 경우**
+>
+> - python -m unittest
+>
+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
+ 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")
+ 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'
+ # )
+ ```
+
+
+
+2. runpy를 임포트하고 PyObject_Call()로 해당 모듈을 실행합니다. runpy 모듈은 Lib > runpy.py에 위치한 순수한 파이썬 모듈입니다.
+ - `python -m `을 실행하는 것은 `python -m runpy `을 실행하는 것과 같습니다.
+ - Lib > runpy.py
+ - sys.argv[0]은 스크립트의 이름, sys.argv[1]은 모듈 이름이 들어갑니다.
+
+ ```python
+ if __name__ == "__main__":
+ # Run the module specified as the next command line argument
+ if len(sys.argv) < 2:
+ print("No module specified for execution", file=sys.stderr)
+ else:
+ del sys.argv[0] # Make the requested module sys.argv[0]
+ _run_module_as_main(sys.argv[0])
+ ```
+
+3. runpy는 세 단계로 모듈을 실행합니다.
+ 1. 제공된 모듈 이름을 **import**()로 임포트 합니다.
+ - **import**() 함수는 파이썬의 내장 함수 중 하나입니다. 함수는 모듈을 동적으로 로드하고, 모듈 객체를 반환합니다.
+
+ ```python
+ mod_name = 'math'
+ mod = __import__(mod_name)
+ print(mod.sqrt(16))
+ ```
+
+ 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)
+ {
+ return -1;
+ }
+ 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)
+ {
+ if (PyDict_SetItemString(d, "__file__", filename) < 0)
+ {
+ goto done;
+ }
+ if (PyDict_SetItemString(d, "__cached__", Py_None) < 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)
+ {
+ 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);
+ }
+ else
+ {
+ /* 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);
+ }
+ flush_io();
+ if (v == NULL)
+ {
+ Py_CLEAR(m);
+ PyErr_Print();
+ goto done;
+ }
+ Py_DECREF(v);
+ ret = 0;
+ done:
+ if (set_file_name)
+ {
+ if (PyDict_DelItemString(d, "__file__"))
+ {
+ PyErr_Clear();
+ }
+ if (PyDict_DelItemString(d, "__cached__"))
+ {
+ 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,
+ 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;
+ }
+
+ ```
+
+
+
+1. Python > pythonrun.c의 PyRun_SimpleFileExFlags()를 호출합니다.
+2. PyRun_SimpleFileExFlags()
+ - .pyc 파일 경로면 run_pyc_file()을 호출합니다.
+ - 스크립트 파일(.py) 경로면 PyRun_FileExFlags()를 호출합니다.
+ - python처럼 파일 경로가 stdin이면 stdin을 파일 핸들로 취급하고 PyRun_FileExFlags()를 호출합니다.
+3. PyRun_FileExFlags()는 파일에서 파이썬 모듈을 생성하고 run_mod()로 보내 실행합니다.
+
+### 컴파일된 바이트 코드 입력
+
+1. python을 .pyc 파일 경로와 함께 실행하면 CPython은 파일을 텍스트 파일로 불러와 파싱하는 대신 .pyc 파일에서 디스크에 기록된 코드 객체를 찾습니다.
+2. Python > pythonrun.c의 run_pyc_file()은 파일 핸들을 사용해 .pyc 파일에서 코드 객체를 마셜링합니다.
+ - 마셜링은 파일 내용을 메모리를 복사하여 특정 데이터 구조로 변환하는 것을 의미합니다.
+3. Cpython 컴파일러는 스크립트가 호출될 때마다 파싱하는 대신 디스크의 코드 객체 구조체에 컴파일한 코드를 캐시합니다.
+4. 메모리로 마셜링된 코드 객체는 Python > ceval.c > PyEval_EvalCode()를 호출하는 run_eval_code_obj()로 전달되어 실행됩니다.
+
+## 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://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/docs/6_0_rexing_and_parsing.md b/book/docs/6_0_rexing_and_parsing.md
new file mode 100644
index 0000000..541fb33
--- /dev/null
+++ b/book/docs/6_0_rexing_and_parsing.md
@@ -0,0 +1,766 @@
+# 6. 렉싱과 파싱
+
+- [렉싱과 파싱](#렉싱과-파싱)
+ - [6.0 개요](#60-개요)
+ - [6.1 CST 생성](#61-cst-생성)
+ - [6.2 파서 \& 토크나이저](#62-파서--토크나이저)
+ - [6.3 추상 구문 트리](#63-추상-구문-트리)
+ - [6.4 정리](#64-정리)
+ - [6.5 ‘거의 같음’ 비교 연산자 추가하기](#65-거의-같음-비교-연산자-추가하기)
+
+## 6.0 개요
+
+> _저번 시간에는 CPython 런타임 구성과 명령줄 문자열, 로컬 모듈, 스크립트 파일, 컴파일된 바이트 코드가 입력으로 들어왔을 때 CPython에서 어떻게 처리하는지 살펴보았습니다.
+> 이번 시간에는 입력 받은 코드를 컴파일 가능한 논리적 구조로 만들어 주는 렉싱과 파싱에 대해 알아봅니다._
+
+파이썬은 문법을 해석하기 위해서 CST(Concrete syntax tree) 와 AST(Abstract syntax tree) 두 가지 구조를 사용하며, 각각 렉서와 파서에 의해 만들어집니다.
+
+
+
+
+
+
+##### \*_출처: https://medium.com/@codeinseoul/python-lexer-and-parser-d0dbe676a6e_
+
+
+
+
+
+- CST : 토큰과 심벌에 대한 **문맥(context)이 없는** 트리 집합
+- AST : 파이썬 문법과 문장들에 대한 **문맥(context)이 있는** 트리 집합
+
+잠깐! 책에서 '렉서'와 '파서+토크나이저' 두 용어를 혼용하고 있습니다. 이를테면 CST를 생성하는 건 렉서 혹은 파서+토크나이저라고 하는 식입니다. 그렇다면 두 용어는 동일한 뜻을 가진 단어일까요?
+
+#### 🤔 파서+토크나이저 = 렉서?
+
+```text
+세 용어 모두 "컴파일러 이론 용어"입니다.
+
+•Tokenizer: 구문에서 의미있는 요소들을 토큰으로 쪼갬
+•Lexer: 토큰의 의미를 분석
+•Parser: Lexical analyze 된 후 토큰화된 데이터를 CST라는 구조로 표현
+
+*Lexical analyze?
+•Tokenizer를 거치며 의미있는 단위로 쪼개지고, Lexer를 거치며 그 결과의 의미를 분석하는 과정
+
+ex) return 명령어 분석
+- return 이라는 단어에서 r, e, t, u, r, n 은 각각 따로놓고 보면 아무 의미도 가지지 않음
+- Tokenizer 를 거치며 return 이라는 의미있는 단어가 됨 -> 토큰화
+- Lexer 를 거치며 이 토큰은 무언가를 반환하라는 명령어구나! 라고 의미를 분석함
+- 해당 토큰은 {type: 명령어, value: "return", child: []} 와 같은 식으로 의미가 분석되어 Parser 에게 전달됨
+```
+
+[References]
+👉🏻[용어 정의, 사진](https://velog.io/@mu1616/%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC-%EC%9D%B4%EB%A1%A0%EC%97%90%EC%84%9C-%ED%86%A0%ED%81%AC%EB%82%98%EC%9D%B4%EC%A0%80Tokenizer-%EB%A0%89%EC%84%9CLexer-%ED%8C%8C%EC%84%9CParse-%EC%9D%98-%EC%97%AD%ED%95%A0)
+👉🏻[return 명령어 예시](https://trumanfromkorea.tistory.com/79)
+
+- 결론: **원칙적으로 파서+토크나이저가 곧 렉서는 아닙니다.** 다만 **렉서, 즉 토큰의 의미를 분석하는 기능의 구현은 언어마다 다르며,** CPython의 렉서는 파서와 토크나이저를 합친 형태로 되어 있다고 이해하면 될 것 같습니다.
+
+
+
+
+
+## 6.1 CST 생성
+
+파스 트리라고도 부르는 CST 는 작성한 코드를 루트와 순서가 있는 트리로 변환한 결과물입니다. 다음은 a + 1라는 산술식이 CST로 표현된 모습입니다.
+
+
+
+파서는 입력 스트림으로 들어오는 토큰들이 파이썬 문법에 맞는 토큰인지 검증하는 동시에 CST를 생성합니다. 검증에는 Grammar 폴더 내 Grammar 파일에 정의돼있는 심볼들을 사용하며, 심볼은 CST 를 구성하는 노드가 됩니다.
+
+아래 사진을 통해 Grammar 파일에 정의된 arith_expr, term, factor, power 등의 심볼이 CST 노드에 대응한다는 사실을 확인할 수 있습니다.
+
+
+
+또한 <4. 파이썬 언어와 문법>(p.48)에서 나왔던 것처럼, 토큰은 Grammar 폴더의 Tokens 에서 정의하고 있습니다. 이번 시간에는 NAME과 NUMBER 토큰을 예시로 들겠습니다.
+
+
+- NAME 토큰은 변수나 함수, 클래스 모듈의 이름을 표현합니다.
+- NUMBER 토큰은 다양한 숫자 형식 값을 표현합니다. 이를테면 '0b10000'로 표현된 숫자를 이진수라고 이해하는데 쓰일 수 있습니다.
+
+
+
+
+이 때 python의 symbol과 token 모듈로 컴파일된 심벌과 토큰을 확인할 수 있습니다.
+
+
+
+
+## 6.2 파서-토크나이저
+
+### 6.2.1 연관된 소스 파일 목록
+
+| 파일 | 설명 |
+| ------------------ | --------------------------------------------------------------- |
+| Python/pythonrun.c | 파서와 컴파일러 실행 |
+| Parser/parsetok.c | 파서와 토크나이저 구현 |
+| Parser/tokenizer.c | 토크나이저 구현 |
+| Parser/tokenizer.h | 토큰 상태 등의 데이터 모델을 정의하는 토크나이저 구현 헤더 파일 |
+| Include/token.h | Tools▶︎scripts▶︎generate_token.py에 의해 생성되는 토큰 정의 |
+| Include/node.h | 토크나이저를 위한 CST 노드 인터페이스와 매크로 |
+
+
+
+
+### 6.2.2. 파일 데이터를 파서에 입력하기
+
+파서-토크나이저는 PyParser_ASTFromFileObject() 를 통해 파서의 진입점을 찾을 수 있습니다. PyParser_ASTFromFileObject() 은 다음 두 단계를 거쳐 CST -> AST 파싱을 진행합니다.
+
+1. PyParser_ParseFileObject() 를 통해 CST 로 변환
+2. PyAST_FromNodeObject() 를 사용해 CST 를 AST 로 변환
+
+PyParser_ParseFileObject() 함수는 2가지 중요한 작업을 수행합니다.
+
+1. PyTokenizer_FromFile()을 사용해 토크나이저 상태 tok_state를 초기화
+2. parsetok()를 사용해 토큰들을 CST(노드 리스트)로 변환 (Parser/parsetok.c L456:463)
+
+```c
+done:
+ PyTokenizer_Free(tok);
+
+ if (n != NULL) {
+ _PyNode_FinalizeEndPos(n);
+ }
+ return n;
+}
+...
+```
+
+
+
+### 6.2.3 파서-토크나이저 흐름
+
+기본적으로 커서가 텍스트 입력 끝에 도달하거나 문법 오류가 발견될 때까지 파서와 토크나이저를 실행합니다 (Parser/parsetok.c L242:253).
+
+```c
+#endif
+
+ for (;;) {
+ const char *a, *b;
+ int type;
+ size_t len;
+ char *str;
+ col_offset = -1;
+ int lineno;
+ const char *line_start;
+
+ type = PyTokenizer_Get(tok, &a, &b);
+ ...
+```
+
+
+
+1. 본격적인 파서-토크나이저 실행 전, 토크나이저에서 사용하는 모든 상태를 저장하는 임시 데이터 구조인 tok_state를 초기화합니다 (Parser/parsetok.c L165:189).
+
+```c
+node *
+PyParser_ParseFileObject(FILE *fp, PyObject *filename,
+ const char *enc, grammar *g, int start,
+ const char *ps1, const char *ps2,
+ perrdetail *err_ret, int *flags)
+ {
+ struct tok_state *tok;
+ ...
+ if ((tok = PyTokenizer_FromFile(fp, enc, ps1, ps2)) == NULL) {
+ err_ret->error = E_NOMEM;
+ return NULL;
+ }
+ if (*flags & PyPARSE_TYPE_COMMENTS) {
+ tok->type_comments = 1;
+ }
+ Py_INCREF(err_ret->filename);
+ tok->filename = err_ret->filename;
+ return parsetok(tok, g, start, err_ret, flags);
+```
+
+코드에서 볼 수 있듯, tok_state는 커서의 현재 위치와 같은 정보를 저장합니다.
+
+2. tok_get()으로 다음 토큰을 얻고, 해당 토큰의 고유 ID를 파서로 전달합니다 (Parser/tokenizer.c L1171:1185).
+
+```c
+/* Get next token, after space stripping etc. */
+
+static int
+tok_get(struct tok_state *tok, const char **p_start, const char **p_end)
+{
+ int c;
+ int blankline, nonascii;
+
+ *p_start = *p_end = NULL;
+ nextline:
+ tok->start = NULL;
+ blankline = 0;
+
+ /* Get indentation level */
+ if (tok->atbol) {
+ ...
+ return PyToken_OneChar(c);
+}
+```
+
+3. 파서는 파서 생성기 오토마타(DFA)로 CST에 노드를 추가합니다 (Parser/parser.c L232:316).
+
+```c
+PyParser_AddToken(parser_state *ps, int type, char *str,
+ int lineno, int col_offset,
+ int end_lineno, int end_col_offset,
+ int *expected_ret)
+{
+ ...
+ const dfa *d1 = PyGrammar_FindDFA(ps->p_grammar, nt);
+ if ((err = push(&ps->p_stack, nt, d1,
+ arrow, lineno, col_offset,
+ end_lineno, end_col_offset)) > 0) {
+ D(printf(" MemError: push\n"));
+ return err;
+ }
+ D(printf(" Push '%s'\n", d1->d_name));
+ continue;
+ ...
+ return E_OK;
+}
+
+```
+
+전 과정을 도식화하면 아래와 같습니다.
+
+
+
+## 6.3 추상 구문 트리
+
+*추상 구문 트리 단계*는 CST를 실행 가능한 형태이면서 좀더 논리적인 구조로 변환하는 단계입니다. CST는 코드 파일의 텍스트를 있는 그대로 표현한 구조로, 텍스트로부터 토큰을 추출하여 토큰 종류만 구분해 둔 상태에 불과합니다. 때문에 기본적인 문법 구조는 알 수 있지만 함수, 스코프, 루프 등 상세 정보는 파악이 불가능합니다.
+따라서 코드를 컴파일하기 전에 파이썬 언어 구조와 의미 요소를 표현하는 AST 로 변환해야 합니다.
+
+AST는 CPython 파싱 과정 중 생성하지만 표준 라이브러리의 ast 모듈을 사용해 생성할 수도 있습니다.
+
+AST의 상세 구현을 보기 전에 파이썬 코드의 기본 요소들이 AST로 어떻게 표현되는지 알아봅시다.
+
+### 6.3.1 AST 관련 소스 파일 목록
+
+| 파일 | 용도 |
+| --------------------- | ------------------------------------------------------------------------------------------------------- |
+| Include▶︎python-ast.h | Parser▶︎asdl_c.py로 생성한 AST 노드 타입 선언 |
+| Parser▶︎Python.asdl | 도메인 특화 언어인 ASDL(Abstract Syntax Description Language)5로 작성된 AST 노드 타입들과 프로퍼티 목록 |
+| Python▶︎ast.c | AST 구현 |
+
+> python-ast.h 파일은 Parser▶︎Python.asdl 에서 재생성되며, python ast 모듈이 문법을 재생성할 때 불러와 사용합니다. 때문에 Include▶︎python-ast.h의 파라미터와 이름은 Parser▶︎Python.asdl의 정의를 따릅니다. 그 후 AST 의 진입점인 PyAST_FromNodeObject( ) 는 TYPE(n) 에 대한 switch 문을 실행하고, 결과로 심벌 또는 토큰을 반환합니다.
+
+
+
+
+
+### 6.3.2 인스타비즈로 AST 시각화하기
+
+인스타비즈는 AST와 CPython code objects를 웹 인터페이스에서 빠르게 시각화해서 볼 수 있는 파이썬 패키지입니다.
+
+pip로 인스타비즈를 설치할 수 있습니다.
+
+```bash
+pip install instaviz
+```
+
+`instaviz.show()` 함수는 code object 타입의 인자 한개를 받습니다. [code object](https://docs.python.org/3/c-api/code.html)에 대해서는 다음 장(7장 컴파일러)에서 알아볼 것입니다.
+
+일단 간단한 함수를 하나 정의하고 함수 이름을 인자로 넘겨보겠습니다.
+
+```bash
+$ python
+>>> import instaviz
+>>> def foo():
+ a = 1
+ b = a + 1
+ return b
+>>> instavize.show(foo)
+# 내부적인 구현은 foo.__code__를 통해 컴파일된 함수 바디의 code object가 입력으로 들어갑니다.
+```
+
+명령줄에서 웹서버가 포트 8080에서 시작되었다는 출력을 볼 수 있습니다.
+
+해당 포트를 사용 중이라면 instaviz.show(foo, **port=9090**)처럼 비어 있는 포트 번호를 직접 지정하고 실행하면 됩니다.
+
+웹 브라우저에서 함수에 대한 자세한 분석을 확인할 수 있습니다.
+
+
+
+다음 그래프는 REPL에서 선언한 함수를 AST로 표현한 모습입니다.
+
+트리의 각 노드 타입은 AST 노드 클래스입니다. ast 모듈에서 찾을 수 있는 노드 클래스들은 모두 `_ast.AST`를 상속합니다. CST와 달리 AST의 노드들은 특정 프로퍼티들을 통해 자식노드와 연결됩니다. `b = a + 1`이 선언된 줄과 연결된 Assign 노드를 클릭해보세요!
+
+
+
+Assign 노드는 두 개의 프로퍼티를 가집니다.
+
+1. `targets` 는 값이 할당될 이름의 list입니다.
+ - **a, b = 1, 2** 처럼 언패킹을 통해 한 번에 여러 이름에 값을 할당하기 위해 array를 사용합니다.
+2. `value`는 이름에 할당할 값입니다. 이 경우에는 BinOp 표현식 a + 1가 할당됩니다.
+
+BinOp 노드는 세 개의 프로퍼티를 가집니다.
+
+- left: 왼쪽 항
+- op: 연산자. 이 경우에는 더하기를 뜻하는 Add 노드(+)
+- right: 오른쪽 항
+
+
+
+## 6.3.3 AST 컴파일
+
+C에서 AST를 컴파일하는 것은 매우 복잡한 작업입니다. `Python/ast.c` 모듈은 5000줄이 넘는 코드로 이루어져 있습니다..
+
+AST의 공개 API는 CST와 파일 이름, 컴파일러 플래그, 메모리 저장 영역을 인자로 받습니다.
+
+반환 타입은 파이썬 모듈을 표현하는 mod_ty 타입입니다. 해당 타입은 `Include/Python-ast.h`에서 정의합니다.
+
+
+ Include/Python-ast.h
+
+```c
+typedef struct _mod *mod_ty;
+struct _mod {
+ enum _mod_kind kind;
+ union {
+ struct {
+ asdl_seq *body;
+ asdl_seq *type_ignores;
+ } **Module**;
+
+ struct {
+ asdl_seq *body;
+ } **Interactive**;
+
+ struct {
+ expr_ty body;
+ } **Expression**;
+
+ struct {
+ asdl_seq *argtypes;
+ expr_ty returns;
+ } **FunctionType**;
+
+ } v;
+};
+```
+
+
+
+
+
+mod_ty는 다음 모듈 타입 중 하나를 담는 컨테이너 구조체입니다.
+
+1. Module
+2. Interactive
+3. Expression
+4. FunctionType
+
+모듈 타입들은 `Parser/Python.asdl (AST 노드 타입들과 프로퍼티 목록)` 에서 정의합니다. 해당 파일에서 문장, 표현식, 연산자, 컴프리헨션(comprehension) 타입들도 찾을 수 있습니다.
+
+AST가 생성하는 클래스들과 표준 라이브러리 ast 모듈의 클래스들은 `Parser/Python.asdl`에서 정의하는 타입입니다.
+
+```bash
+-- ASDL's 4 builtin types are:
+ -- identifier, int, string, constant
+
+module Python
+{
+ mod = Module(stmt* body, type_ignore* type_ignores)
+ | Interactive(stmt* body)
+ | Expression(expr body)
+ | FunctionType(expr* argtypes, expr returns)
+
+ ...
+
+ expr = BoolOp(boolop op, expr* values)
+ | NamedExpr(expr target, expr value)
+ | BinOp(expr left, operator op, expr right)
+```
+
+ast 모듈은 문법을 다시 생성할 때 Include/Python-ast.h 를 import하는데요. 이 파일은 Parser/Python.asdl 에서 자동으로 생성됩니다. 이때, Include/Python-ast.h 의 파라미터와 이름은 Parser/Python.asdl 의 정의를 따릅니다. 또, Include/Python-ast.h 의 mod_ty 타입은 Parser/Python.asdl 의 Module 정의로부터 생성됩니다.
+
+`Python/ast.c`는 이 헤더 파일에서 제공하는 구조체들을 사용해 필요한 데이터를 가리키는 포인터를 담은 구조체들을 생성합니다.
+
+AST의 진입점인 PyAST_FromNodeObject()는 TYPE(n)에 대한 switch문을 실행합니다. **TYPE()**은 CST 노드의 타입을 결정하는 매크로입니다.
+
+**#define TYPE(n) ((n)->n_type)**
+
+결과로는 심벌 또는 토큰 타입을 반환합니다.
+
+```c
+mod_ty
+PyAST_FromNodeObject(const node *n, PyCompilerFlags *flags,
+ PyObject *filename, PyArena *arena)
+{
+
+ switch (TYPE(n)) {
+ case file_input:
+ stmts = _Py_asdl_seq_new(num_stmts(n), arena);
+ ...
+ case eval_input: {
+ ...
+```
+
+루트 노드의 타입은 항상 Module, Interactive, Expression, FunctionType 중 하나입니다.
+
+- `file_input` 일 경우에는 **Module** 타입입니다.
+- REPL 등으로 들어오는 `eval_input`일 경우에는 **Expression** 타입입니다.
+
+`Python/ast.c`에는 각 타입에 대응되는 **ast_for_xxx** C 함수들이 구현되어 있습니다. 이 함수들은 CST의 노드 중에서 해당 statement에 대한 프로퍼티를 갖습니다.
+
+간단한 예시로 2의 4제곱을 뜻하는 2 \*\* 4 같은 제곱에 대한 expression을 살펴보겠습니다. ast_for_power()는 연산자가 Pow(제곱), 좌측은 e(2), 우측은f(4)인 BinOp를 반환합니다.
+
+**Python/ast.c 2716행**
+
+```c
+static expr_ty
+ast_for_power(struct compiling *c, const node *n)
+{
+ /* power: atom trailer* ('**' factor)*
+ */
+ expr_ty e;
+ REQ(n, power);
+ e = ast_for_atom_expr(c, CHILD(n, 0));
+ if (!e)
+ return NULL;
+ if (NCH(n) == 1)
+ return e;
+ if (TYPE(CHILD(n, NCH(n) - 1)) == factor) {
+ expr_ty f = ast_for_expr(c, CHILD(n, NCH(n) - 1));
+ if (!f)
+ return NULL;
+ e = BinOp(e, Pow, f, LINENO(n), n->n_col_offset,
+ n->n_end_lineno, n->n_end_col_offset, c->c_arena);
+ }
+ return e;
+}
+```
+
+이번엔 **instaviz**로 제곱에 대한 AST를 확인해 보겠습니다.
+
+```c
+>>> def foo():
+ 2**4
+>>> import instaviz
+>>> instaviz.show(foo)
+```
+
+
+
+AST의 프로퍼티들도 확인해 봅시다.
+
+
+
+요약하자면 모든 statement type과 expression에는 ast*for*\*() 생성자 함수가 있습니다.(AST 노드로 변환하는 역할을 담당하는 함수) arguments는 `Parser/Python.asdl`에서 정의하며 표준 라이브러리의 [ast 모듈](https://docs.python.org/3/library/ast.html)을 통해 외부에 제공됩니다.
+
+statement 과 expression이 자식 노드를 가지고 있으면 dfs를 통해 자식 노드에 대한 ast*for*\*() 함수를 먼저 호출합니다.
+
+## 6.4 정리
+
+- AST : 파이썬 문법과 문장들에 대한 _문맥_(context) 있는\* 트리 집합.
+- CST : 토큰과 심벌에 대한 _문맥(context)이 없는_ 트리 집합
+- 토큰 : 심벌의 종류 중 하나.
+- 토큰화 : 텍스트를 토큰들로 변환하는 과정이다.
+- 파싱 : 텍스트를 CST 나 AST 로 변환하는 과정이다.
+
+
+
+
+
+# 6.5 ‘거의 같음’ 비교 연산자 추가하기
+
+파이썬에 새 연산자를 추가해보겠습니다.
+
+파이썬 문법 정의는 Grammar/python.gram에 정의되고 python 토큰은 Grammar/Tokens에 저장되어 있는데요. 모두 `pegen`이라 불리는 파서 생성기의 input이 됩니다. **pegen**은 자동적으로 파이썬 렉서와 파서를 이러한 정의로부터 생성합니다.
+
+~= 심벌을 사용하는 ‘**거의 같음**’ 연산자를 비교 연산자로 새로 추가 해보겠습니다. ‘거의 같음’ 연산자는 다음과 같이 동작합니다.
+
+- 정수와 정수를 비교할 때는 일반 동등 연산자를 사용.
+
+```python
+>>> 1 ~= 1
+True
+>>> 1 ~= 2
+False
+```
+
+- 정수와 부동 소수점을 비교할 때 부동 소수점은 정수로 변환해 비교.
+
+```python
+>>> 1 ~= 1.000000021
+True
+>>> 1 ~= 1.9
+False
+```
+
+새 연산자를 추가하려면 먼저 CPython 문법을 변경해야 합니다. 비교 연산자들은 Grammar/python.gram 파일에 `comp_op` 심벌로 정의되어 있습니다.
+
+### Grammar/python.gram
+
+(notation이 [EBNF](https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form) 과 [PEG](https://en.wikipedia.org/wiki/Parsing_expression_grammar) 이 섞여 들어있습니다!)
+
+```
+comparison[expr_ty]:
+ | a=bitwise_or b=compare_op_bitwise_or_pair+ {
+ _Py_Compare(a, CHECK(_PyPegen_get_cmpops(p, b)), CHECK(_PyPegen_get_exprs(p, b)), EXTRA) }
+ | bitwise_or
+compare_op_bitwise_or_pair[CmpopExprPair*]:
+ | eq_bitwise_or
+ | noteq_bitwise_or
+ | lte_bitwise_or
+ | lt_bitwise_or
+ | gte_bitwise_or
+ | gt_bitwise_or
+ | notin_bitwise_or
+ | in_bitwise_or
+ | isnot_bitwise_or
+ | is_bitwise_or
+ | ale_bitwise_or <-- 이곳에 추가
+eq_bitwise_or[CmpopExprPair*]: '==' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, Eq, a) }
+noteq_bitwise_or[CmpopExprPair*]:
+ | (tok='!=' { _PyPegen_check_barry_as_flufl(p, tok) ? NULL : tok}) a=bitwise_or {_PyPegen_cmpop_expr_pair(p, NotEq, a) }
+lte_bitwise_or[CmpopExprPair*]: '<=' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, LtE, a) }
+lt_bitwise_or[CmpopExprPair*]: '<' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, Lt, a) }
+gte_bitwise_or[CmpopExprPair*]: '>=' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, GtE, a) }
+gt_bitwise_or[CmpopExprPair*]: '>' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, Gt, a) }
+notin_bitwise_or[CmpopExprPair*]: 'not' 'in' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, NotIn, a) }
+in_bitwise_or[CmpopExprPair*]: 'in' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, In, a) }
+isnot_bitwise_or[CmpopExprPair*]: 'is' 'not' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, IsNot, a) }
+is_bitwise_or[CmpopExprPair*]: 'is' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, Is, a) }
+ale_bitwise_or[CmpopExprPair*]: '~=' a=bitwise_or {_PyPegen_cmpop_expr_pair(p, ALE, a) }
+```
+
+compare_op_bitwise_or_pair 식에 `ale_bitwise_or` 를 허용하겠습니다.
+
+이제 ‘~=’ 단말 기호를 포함하는 `ale_bitwise_or` 식을 정의했습니다.
+
+`_PyPegen_cmpop_expr_pair(p, ALE, a)` 함수 호출은 AST에서 ‘거의 같음’ 연산자를 뜻하는 AlE(Almost Equal) 타입 cmpop 노드를 가져옵니다.
+
+다음으로 Grammar/Tokens에 토큰을 추가하겠습니다.
+
+```ABAP
+ATEQUAL '@='
+RARROW '->'
+ELLIPSIS '...'
+COLONEQUAL ':='
+ALMOSTEQUAL '~=' # 이곳에 추가
+```
+
+변경한 문법과 토큰을 c 코드에 반영하려면 헤더를 다시 생성해야 합니다.
+
+```bash
+# mac os / lunux
+make regen-token regen-pegen
+
+# window (at root dir)
+./PCbuild/build.bat --regen
+
+# makefile이 루트 디렉토리에 없다면 ./configure 후 진행할 것
+```
+
+
+각각의 타겟은 어떤 작업을 할까?
+
+**regen-pegen**
+
+```makefile
+regen-pegen:
+ @$(MKDIR_P) $(srcdir)/Parser/pegen
+ PYTHONPATH=$(srcdir)/Tools/peg_generator $(PYTHON_FOR_REGEN) -m pegen -q c \
+ $(srcdir)/Grammar/python.gram \
+ $(srcdir)/Grammar/Tokens \
+ -o $(srcdir)/Parser/pegen/parse.new.c
+ $(UPDATE_FILE) $(srcdir)/Parser/pegen/parse.c $(srcdir)/Parser/pegen/parse.new.c
+```
+
+- 문법 파일 변경 후 파서를 재생성
+- Grammar/Tokens, Grammar/python.gram 을 `Tools/peg_generator` 입력으로 줘서 `Parser/parser.c` 를 생성합니다.
+ **regen-token**
+
+```makefile
+regen-token:
+ # Regenerate Doc/library/token-list.inc from Grammar/Tokens
+ # using Tools/scripts/generate_token.py
+ $(PYTHON_FOR_REGEN) $(srcdir)/Tools/scripts/generate_token.py rst \
+ $(srcdir)/Grammar/Tokens \
+ $(srcdir)/Doc/library/token-list.inc
+ # Regenerate Include/token.h from Grammar/Tokens
+ # using Tools/scripts/generate_token.py
+ $(PYTHON_FOR_REGEN) $(srcdir)/Tools/scripts/generate_token.py h \
+ $(srcdir)/Grammar/Tokens \
+ $(srcdir)/Include/token.h
+ # Regenerate Parser/token.c from Grammar/Tokens
+ # using Tools/scripts/generate_token.py
+ $(PYTHON_FOR_REGEN) $(srcdir)/Tools/scripts/generate_token.py c \
+ $(srcdir)/Grammar/Tokens \
+ $(srcdir)/Parser/token.c
+ # Regenerate Lib/token.py from Grammar/Tokens
+ # using Tools/scripts/generate_token.py
+ $(PYTHON_FOR_REGEN) $(srcdir)/Tools/scripts/generate_token.py py \
+ $(srcdir)/Grammar/Tokens \
+ $(srcdir)/Lib/token.py
+```
+
+- 토큰 추가 후 토큰 관련 파일 재생성
+- (참고) 보통의 PEG 파서들은 파서가 parsing 과 tokenization 을 모두 하지만 Pegen의 경우는 대화형 모드 지원등의 이유로 커스텀 토크나이저를 사용합니다.
+
+
+
+
+
+헤드를 다시 생성하면 토크나이저도 자동으로 변경됩니다. `Parser/token.c` 에 **PyToken_TwoChars()** 함수의 case가 변경된 것을 확인할 수 있습니다.
+
+```python
+int
+PyToken_TwoChars(int c1, int c2)
+{
+
+...
+
+case '~':
+ switch (c2) {
+ case '=': return ALMOSTEQUAL; <- 추가됨
+ }
+ break;
+}
+
+...
+
+}
+```
+
+이제 토크나이저가 새 토큰을 처리할 수 있게 되었습니다. 하지만 아직 AST는 처리하지 못합니다.
+
+현 상태에서 CPython 컴파일을 하려 하면 다음과 같은 메시지와 함께 실패 합니다.
+
+
+문제발생
+
+```c
+CmpopExprPair *
+_PyPegen_cmpop_expr_pair(Parser *p, cmpop_ty cmpop, expr_ty expr)
+{
+ assert(expr != NULL);
+ CmpopExprPair *a = PyArena_Malloc(p->arena, sizeof(CmpopExprPair));
+ if (!a) {
+ return NULL;
+ }
+ a->cmpop = cmpop;
+ a->expr = expr;
+ return a;
+}
+```
+
+
+
+
+```bash
+$ make -j2 -s
+
+Parser/pegen/**parse.c**:9313:51: error: use of undeclared identifier 'AlE'
+ _res = _PyPegen_cmpop_expr_pair ( p , AlE, a );
+```
+
+- 이는 `make j2 -s` 커맨드가 타이핑될 때, `Parser/parse.c` 가 컴파일 되는데 이때, ‘AlE’를 인식하지 못해 발생합니다.
+- parse.c는 이전에 `make regen-pegen` 때 업데이트 되었습니다.
+
+또, 아직 `Python/ast.c`의 **ast_for_comp_op()**는 **ALMOSTEQUAL**을 올바른 비교 연산자로 인식할 수 없습니다.
+
+`Parser/Python.asdl`에서 정의하는 Compare 표현식은 좌측 표현식 left, 연산자 목록인 ops, 비교할 표현식 목록인 comparators로 이루어져있습니다.
+
+```python
+| Compare(expr left, cmpop* ops, expr* comparators)
+```
+
+Compare 정의는 cmpop 열거형을 참조합니다.
+
+```python
+cmpop = Eq | NotEq | Lt | LtE | Gt | GtE | Is | IsNot | In | NotIn
+```
+
+이 열거형은 비교 연산자로 사용할 수 있는 AST 리프 노드의 목록입니다. ‘거의 같음’ 연산자를 비교 연산자로 사용하기 위해 AlE를 추가하겠습니다.
+
+```python
+cmpop = Eq | NotEq | Lt | LtE | Gt | GtE | Is | IsNot | In | NotIn | AlE
+```
+
+이제 AST를 다시 생성해서 AST 헤더 파일에 변경된 AST를 반영하겠습니다.
+
+```bash
+make regen-ast
+```
+
+
+regen-ast
+
+```makefile
+.PHONY=regen-ast
+regen-ast:
+ # Regenerate Include/Python-ast.h and Python/Python-ast.c using Parser/asdl_c.py
+ $(MKDIR_P) $(srcdir)/Include
+ $(MKDIR_P) $(srcdir)/Python
+ $(PYTHON_FOR_REGEN) $(srcdir)/Parser/asdl_c.py \
+ $(srcdir)/Parser/Python.asdl \
+ -H $(srcdir)/Include/Python-ast.h.new \
+ -C $(srcdir)/Python/Python-ast.c.new
+
+ $(UPDATE_FILE) $(srcdir)/Include/Python-ast.h $(srcdir)/Include/Python-ast.h.new
+ $(UPDATE_FILE) $(srcdir)/Python/Python-ast.c $(srcdir)/Python/Python-ast.c.new
+```
+
+- Python-ast.h , Python-ast.c 재생성
+- (참고)
+
+```
+UPDATE_FILE=python3.9 $(srcdir)/Tools/scripts/update_file.py
+```
+
+
+
+
+
+`Include/Python-ast.h`에서 비교 연산자를 정의하는 열거형인 **\_cmpop**에 AlE가 추가된 것을 확인할 수 있습니다.
+
+```python
+typedef enum _cmpop { Eq=1, NotEq=2, Lt=3, LtE=4, Gt=5, GtE=6, Is=7, IsNot=8,
+ In=9, NotIn=10, AlE=11 } cmpop_ty;
+```
+
+AST는 `ALMOSTEQAUL` 토큰이 비교 연산자 **AlE**라는 것을 아직은 알 수 없습니다. 토큰을 연산자로 인식할 수 있게 AST C 코드를 수정하겠습니다.
+
+`Python/ast.c` 의 **ast_for_comp_op()**로 이동해서 연산자 토큰에 대한 switch 문을 찾아보겠습니다.
+
+해당 switch문은 \_cmpop 열거형 값 중 하나를 반환하는데요. 여기에 ALMOSTEQUAL 토큰일 경우 AlE 비교 연산자를 반환하는 case를 추가합니다.
+
+### Python/ast.c
+
+```c
+static cmpop_ty
+ast_for_comp_op(struct compiling *c, const node *n)
+{
+ /* comp_op: '<'|'>'|'=='|'>='|'<='|'!='|'in'|'not' 'in'|'is'
+ |'is' 'not'
+ */
+ REQ(n, comp_op);
+ if (NCH(n) == 1) {
+ n = CHILD(n, 0);
+ switch (TYPE(n)) {
+ case LESS:
+ return Lt;
+ case GREATER:
+ return Gt;
+ case ALMOSTEQUAL: // ALMOSTEQUAL 토큰을 인식
+ return AlE; // AST 노드 AlE를 반환
+ ...
+```
+
+이제 토크나이저와 AST 모두 코드를 파싱할 수 있지만 아직 저희는 연산자에 대한 동작을 구현하지 않았기 때문에 컴파일러는 이 연산자를 실행하는 방법을 모르는 상태입니다.
+
+AST로 나타낸 ‘거의 같음’ 연산자를 확인해 보려면 `ast.parse()`가 반환한 값에서 첫 번째 연산자를 출력해봅시다.
+
+```python
+>>> import ast
+>>> m = ast.parse('1 ~= 2')
+>>> m.body[0].value.ops[0]
+<_ast.AlE object at ~~~>
+```
+
+AST가 코드를 올바르게 파싱했다면 비교 연산자 AlE타입의 객체가 출력될 것입니다.
+
+다음 장에서는 Cpython 컴파일러가 동작하는 방식을 알아보고 ‘거의 같음’ 연산자의 동작을 구현할 예정입니다.
diff --git a/book/docs/7_0_compiler.md b/book/docs/7_0_compiler.md
new file mode 100644
index 0000000..22f4699
--- /dev/null
+++ b/book/docs/7_0_compiler.md
@@ -0,0 +1,857 @@
+# 7. 컴파일러
+
+파이썬 코드를 파싱하면 연산자, 함수, 클래스, 이름 공간을 포함하는 AST가 생성
+AST를 CPU 명령으로 바꾸는 것이 **컴파일러**
+
+## 컴파일 과정
+
+컴파일 과정을 그림으로 표현
+앞장에서 본 렉싱과 파서를 통하여 생성한 AST를 컴파일러를 통하여 CFG(Control Flow Graph)로 변환
+어셈블러를 통하여 CFG의 노드를 순서대로 **바이트 코드로 변환** 후 **실행**
+
+### 컴파일과 관련된 소스
+
+| 파일 | 목적 |
+|-------------------|-----------------|
+| Python/compile.c | 컴파일러의 구현 |
+| Include/compile.h | 컴파일러 API와 타입 정의 |
+
+PyAST_CompileObject() CPython 컴파일러의 주 진입점
+
+
+**컴파일러 상태**는 심벌 테이블을 담는 타입
+**심벌 테이블**은 변수 이름을 포함하고 추가로 하위 심벌 테이블을 포함할 수 있음
+**컴파일러 타입**에는 컴파일러 유닛도 포함
+각 **컴파일러 유닛**은 이름, 변수 이름, 상수, 셀(cell) 변수들을 포함
+컴파일러 유닛은 **기본 프레임 블록**을 포함
+기본 프레임 블록은 **바이트코드 명령**을 포함
+
+## 컴파일러 인스턴스 생성
+컴파일러를 실행하기 앞서 전역 컴파일러 상태가 생성
+compiler 타입은 컴파일러 플래그, 스택, PyArena 등 컴파일러를 위한 다양한 프로퍼티를 포함
+컴파일러 상태는 심벌 테이블 등의 다른 구조체도 포함
+```cpp
+struct compiler {
+ PyObject *c_filename;
+ struct symtable *c_st;
+ PyFutureFeatures *c_future; /* pointer to module's __future__ */
+ PyCompilerFlags *c_flags;
+
+ int c_optimize; /* optimization level */
+ int c_interactive; /* true if in interactive mode */
+ int c_nestlevel;
+ int c_do_not_emit_bytecode; /* The compiler won't emit any bytecode
+ if this value is different from zero.
+ This can be used to temporarily visit
+ nodes without emitting bytecode to
+ check only errors. */
+
+ PyObject *c_const_cache; /* Python dict holding all constants,
+ including names tuple */
+ struct compiler_unit *u; /* compiler state for current block */
+ PyObject *c_stack; /* Python list holding compiler_unit ptrs */
+ PyArena *c_arena; /* pointer to memory allocation arena */
+};
+```
+PyAST_CompileObject()가 다음과 같이 컴파일러 상태 초기화
+
+모듈에 문서화 문자열(\_\_doc\_\_)이 없다면 빈 문서화 문자열 생성
+\_\_annotations\_\_ 프로퍼티도 동일 작업 수행
+스택 트레이스 및 예외 처리에 필요한 파일 이름을 컴파일러 상태에 설정
+인터프리터가 사용한 메모리 할당 아레나(arena)를 컴파일러의 메모리 할당 아레나로 설정 (메모리 할당자는 9장 메모리 관리 참고)
+코드 컴파일 전 퓨처 플래그들을 설정
+
+## 퓨처 플래그와 컴파일러 플래그
+컴파일러 기능 설정
+1. 환경 변수 명령줄 플래그를 담는 구성 상태
+2. 모듈 소스 코드의 \_\_future\_\_ 문
+
+**퓨처 플래그**는 Python 2와 3 간 이식 지원을 위해 사용
+
+**컴파일러 플래그**는 실행 환경에 의존적, 실행 방식을 변경 할 수 있음
+예시로 -O 플래그는 디버그 용도의 assert 문을 비활성화 하는 최적화를 진행
+PYTHONOPTIMIZE=1 환경 변수로도 활성화 가능
+
+## 심벌 테이블
+코드 컴파일 전 PySymtable_BuildObject() API로 심벌 테이블 생성
+전역, 지역 등 이름 공간 목록을 컴파일러에 제공
+컴파일러는 심벌 테이블에서 얻은 이름 공간에서 스코프를 결정, 참조를 실행
+
+```cpp
+struct symtable {
+ PyObject *st_filename; /* name of file being compiled,
+ decoded from the filesystem encoding */
+ struct _symtable_entry *st_cur; /* current symbol table entry */
+ struct _symtable_entry *st_top; /* symbol table entry for module */
+ PyObject *st_blocks; /* dict: map AST node addresses
+ * to symbol table entries */
+ PyObject *st_stack; /* list: stack of namespace info */
+ PyObject *st_global; /* borrowed ref to st_top->ste_symbols */
+ int st_nblocks; /* number of blocks used. kept for
+ consistency with the corresponding
+ compiler structure */
+ PyObject *st_private; /* name of current class or NULL */
+ PyFutureFeatures *st_future; /* module's future features that affect
+ the symbol table */
+ int recursion_depth; /* current recursion depth */
+ int recursion_limit; /* recursion limit */
+};
+```
+컴파일러당 하나의 symtable 인스턴스만 사용, 공간 관리 중요
+두 클래스가 동일한 이름의 메서드를 가지고 있을 경우 모듈에서 어떤 메서드를 호출할지 정해주는 것
+하위 스코프의 변수를 상위 스코프에서 사용하지 못하게 하는 것
+위 두 가지가 symtable의 역할
+
+### 심벌 테이블 구현
+symtable.c 에서 찾을 수 있다
+주 인터페이스는 PySymtable_BuildObject()
+mod_ty 타입(Module, Interactive, Expression, FunctionType)에 따라 모듈 내의 문장을 순회
+mod_ty 타입인 AST의 노드의 분기를 재귀적으로 탐색하며 symtable의 엔트리로 추가
+```cpp
+struct symtable *
+PySymtable_BuildObject(mod_ty mod, PyObject *filename, PyFutureFeatures *future)
+{
+ struct symtable *st = symtable_new();
+ asdl_seq *seq;
+ int i;
+ PyThreadState *tstate;
+ int recursion_limit = Py_GetRecursionLimit();
+ int starting_recursion_depth;
+...
+ st->st_top = st->st_cur;
+ switch (mod->kind) {
+ case Module_kind:
+ seq = mod->v.Module.body;
+ for (i = 0; i < asdl_seq_LEN(seq); i++)
+ if (!symtable_visit_stmt(st,
+ (stmt_ty)asdl_seq_GET(seq, i)))
+ goto error;
+ break;
+ case Expression_kind:
+ if (!symtable_visit_expr(st, mod->v.Expression.body))
+ goto error;
+ break;
+ case Interactive_kind:
+ seq = mod->v.Interactive.body;
+ for (i = 0; i < asdl_seq_LEN(seq); i++)
+ if (!symtable_visit_stmt(st,
+ (stmt_ty)asdl_seq_GET(seq, i)))
+ goto error;
+ break;
+ case FunctionType_kind:
+ PyErr_SetString(PyExc_RuntimeError,
+ "this compiler does not handle FunctionTypes");
+ goto error;
+ }
+...
+```
+모듈의 각 문을 순회, symtable_visit_stmt() 를 호출
+Parser → Python.asdl에서 정의한 모든문 타입에 대한 case를 가지고 있는 거대한 swich 문
+
+각 문 타입마다 심벌을 처리하는 함수 존재
+함수 정의문 타입을 처리하는 함수는 다음 처리를 위한 로직이 있다
+- 현재 재귀 깊이가 제귀 제한을 넘지 않았는지 검사
+- 함수가 함수 객체로 넘겨지거나 호출될 수 있도록 함수 이름 심벌 테이블에 추가
+- 기본 인자 중 리터럴이 아닌 인자는 심벌 테이블에서 찾음
+- 타입 힌트 처리
+- 함수 데코레이터 처리
+
+마지막으로 symtable_enter_block()이 함수 블록을 방문해 인자와 함수 본문 처리
+
+이렇게 생성된 심벌 테이블은 컴파일러로 넘김
+
+## 핵심 컴파일 과정
+
+```c
+ // Python/compile.c
+
+ * This file compiles an abstract syntax tree (AST) into Python bytecode.
+ *
+ * The primary entry point is PyAST_Compile(), which returns a
+ * PyCodeObject. The compiler makes several passes to build the code
+ * object:
+ * 1. Checks for future statements. See future.c
+ * 2. Builds a symbol table. See symtable.c.
+ * 3. Generate code for basic blocks. See compiler_mod() in this file.
+ * 4. Assemble the basic blocks into final code. See assemble() in
+ * this file.
+ * 5. Optimize the byte code (peephole optimizations). See peephole.c
+```
+
+- PyAST_CompileObject()에 컴파일러 상태와 symtable, AST로 파싱된 모듈이 준비되면 컴파일이 시작됨
+ - 컴파일러 상태(future.c), 심벌 테이블(symtable.c), AST(parser.c)를 Control Flow Graph로 변환
+ - 논리 오류나 코드 오류를 탐지해 실행 단계를 런타임 예외로부터 보호함
+
+### python에서 컴파일러 사용하기
+
+- 내장 함수인 compile()로 컴파일러를 직접 호출할 수 있음
+
+```python
+>>> co = compile("b + 1", "test.py", "eval") # compile는 code object를 반환함
+>>> co
+ at 0x7f6f8e8e2040, file "test.py", line 1>
+>>> co.co_code # 컴파일된 코드는 co_code 속성에 담김
+b'e\x00d\x00\x17\x00S\x00'
+```
+
+- 컴파일된 코드를 바이트코드 명령어 형태로 보기 위해서는 바이트코드 역어셈블러 모듈 dis를 사용하면 됨
+
+```python
+>>> import dis
+>>> dis.dis(co.co_code) # 컴파일된 코드는 바이트코드 명령어 순서로 실행됨
+ 0 LOAD_NAME 0 (0)
+ 2 LOAD_CONST 0 (0)
+ 4 BINARY_ADD
+ 6 RETURN_VALUE
+```
+
+- instaviz 모듈을 이용해서도, code object와 bytecode를 확인할 수 있음
+
+
+
+### 컴파일러 C API 간단 설명
+
+- compiler_mod 는 compiler와 mod를 입력으로 받아서 최종적으로 코드 객체를 반환함
+- compiler_mod 내의 compiler_body를 통해서 일련의 명령을 담고 있는 블록 리스트를 얻음
+- compiler_mod 내의 assemble는 일련의 명령을 담고 있는 블록 리스트를 입력으로 받아서 코드 객체를 반환함
+- assemble 내의 dfs를 통해서 일련의 명령을 담고 있는 블록 리스트를 입력으로 받아서 Control Flow Graph 형태로 변환함
+- assemble 내의 makecode를 통해서 Control Flow Graph 형태를 입력으로 받아서 코드 객체를 반환함
+- makecode 내의 PyCode_Optimize를 통해서 핍홀 최적화된 바이트코드를 반환함
+- makecode 내의 PyCode_NewWithPosOnlyArgs를 통해서 최적화된 바이트 코드를 코드 객체를 반환함
+
+### 컴파일러 C API
+
+- AST 모듈 컴파일의 진입점인 compiler_mod(struct compiler *c, mod_ty mod)는 모듈 타입에 따라 다른 컴파일러 함수를 사용함
+- mod가 Module일 경우 모듈은 컴파일러 유닛으로 컴파일되어 c_stack 프로퍼티에 저장됨
+- 컴파일러 유닛 스택에서 PyCodeObject를 생성하기 위해 assemble() 을 실행함
+
+```c
+// Python/compile.c
+
+static PyCodeObject *
+compiler_mod(**struct compiler *c**, **mod_ty mod**)
+{
+ **PyCodeObject *co;
+ int addNone = 1;
+ static PyObject *module;**
+ if (!module) {
+ module = PyUnicode_InternFromString("");
+ if (!module)
+ return NULL;
+ }
+ /* Use 0 for firstlineno initially, will fixup in assemble(). */
+ if (!compiler_enter_scope(c, module, COMPILER_SCOPE_MODULE, mod, 0))
+ return NULL;
+ **switch (mod->kind) {
+ case Module_kind:
+ if (!compiler_body(c, mod->v.Module.body)) {
+ compiler_exit_scope(c);
+ return 0;
+ }
+ break;**
+ case Interactive_kind:
+ if (find_ann(mod->v.Interactive.body)) {
+ ADDOP(c, SETUP_ANNOTATIONS);
+ }
+ c->c_interactive = 1;
+ VISIT_SEQ_IN_SCOPE(c, stmt,
+ mod->v.Interactive.body);
+ break;
+ case Expression_kind:
+ VISIT_IN_SCOPE(c, expr, mod->v.Expression.body);
+ addNone = 0;
+ break;
+ default:
+ PyErr_Format(PyExc_SystemError,
+ "module kind %d should not be possible",
+ mod->kind);
+ return 0;
+ }
+ **co = assemble(c, addNone);**
+ compiler_exit_scope(c);
+ return co;
+}
+```
+
+- compiler_body() 는 모듈의 각 문(함수, 클래스, for, while …)을 순회하면 방문함
+- AST 노드 타입을 확인하는 asdl_seq_GET() 호출을 통해 이 문의 타입이 결정됨
+
+```c
+// Python/compile.c
+
+static int
+compiler_body(struct compiler *c, asdl_seq *stmts)
+{
+ int i = 0;
+ stmt_ty st;
+ PyObject *docstring;
+
+ /* Set current line number to the line number of first statement.
+ This way line number for SETUP_ANNOTATIONS will always
+ coincide with the line number of first "real" statement in module.
+ If body is empty, then lineno will be set later in assemble. */
+ if (c->u->u_scope_type == COMPILER_SCOPE_MODULE && asdl_seq_LEN(stmts)) {
+ st = (stmt_ty)asdl_seq_GET(stmts, 0);
+ SET_LOC(c, st);
+ }
+ /* Every annotated class and module should have __annotations__. */
+ if (find_ann(stmts)) {
+ ADDOP(c, SETUP_ANNOTATIONS);
+ }
+ if (!asdl_seq_LEN(stmts))
+ return 1;
+ /* if not -OO mode, set docstring */
+ if (c->c_optimize < 2) {
+ docstring = _PyAST_GetDocString(stmts);
+ if (docstring) {
+ i = 1;
+ st = (stmt_ty)asdl_seq_GET(stmts, 0);
+ assert(st->kind == Expr_kind);
+ VISIT(c, expr, st->v.Expr.value);
+ if (!compiler_nameop(c, __doc__, Store))
+ return 0;
+ }
+ }
+ **for (; i < asdl_seq_LEN(stmts); i++)
+ VISIT(c, stmt, (stmt_ty)asdl_seq_GET(stmts, i));
+ return 1;**
+}
+```
+
+- **VISIT** 매크로는 각 문 타입에 해당하는 ****Python/compile.c의 함수를 호출함
+
+```c
+// Python/compile.c
+
+#define VISIT(C, TYPE, V) {\
+ if (!compiler_visit_ ## TYPE((C), (V))) \
+ return 0; \
+}
+```
+
+- 모든 문을 포괄하는 stmt 타입의 경우 컴파일러는 compiler_visit_stmt를 호출해 Parser/Python.asdl에 정의된 하위문 타입들로 전환함
+- 예를 들어 파이썬 for문이 들어오면 compiler_visit_stmt()는 compiler_for()를 호출함
+- 모든 문과 표현식 타입에는 해당 타입에 대한 compiler_*() 함수가 존재하며, 간단한 타입들은 인라인으로 바이트코드 명령어를 생성하고, 더 복잡한 문 타입들은 다른 함수를 호출함
+
+```c
+// Python/compile.c
+
+static int
+compiler_visit_stmt(struct compiler *c, stmt_ty s)
+{
+ Py_ssize_t i, n;
+
+ /* Always assign a lineno to the next instruction for a stmt. */
+ SET_LOC(c, s);
+
+ switch (s->kind) {
+ case FunctionDef_kind:
+ return compiler_function(c, s, 0);
+ case ClassDef_kind:
+ return compiler_class(c, s);
+ case Return_kind:
+ return compiler_return(c, s);
+ case Delete_kind:
+ VISIT_SEQ(c, expr, s->v.Delete.targets)
+ break;
+ case Assign_kind:
+ n = asdl_seq_LEN(s->v.Assign.targets);
+ VISIT(c, expr, s->v.Assign.value);
+ for (i = 0; i < n; i++) {
+ if (i < n - 1)
+ ADDOP(c, DUP_TOP);
+ VISIT(c, expr,
+ (expr_ty)asdl_seq_GET(s->v.Assign.targets, i));
+ }
+ break;
+ case AugAssign_kind:
+ return compiler_augassign(c, s);
+ case AnnAssign_kind:
+ return compiler_annassign(c, s);
+ **case For_kind:
+ return compiler_for(c, s);**
+ case While_kind:
+ return compiler_while(c, s);
+ case If_kind:
+ return compiler_if(c, s);
+ case Raise_kind:
+ n = 0;
+ if (s->v.Raise.exc) {
+ VISIT(c, expr, s->v.Raise.exc);
+ n++;
+ if (s->v.Raise.cause) {
+ VISIT(c, expr, s->v.Raise.cause);
+ n++;
+ }
+ }
+ ADDOP_I(c, RAISE_VARARGS, (int)n);
+ break;
+ case Try_kind:
+ return compiler_try(c, s);
+ case Assert_kind:
+ return compiler_assert(c, s);
+ case Import_kind:
+ return compiler_import(c, s);
+ case ImportFrom_kind:
+ return compiler_from_import(c, s);
+ case Global_kind:
+ case Nonlocal_kind:
+ break;
+ case Expr_kind:
+ return compiler_visit_stmt_expr(c, s->v.Expr.value);
+ case Pass_kind:
+ break;
+ case Break_kind:
+ return compiler_break(c);
+ case Continue_kind:
+ return compiler_continue(c);
+ case With_kind:
+ return compiler_with(c, s, 0);
+ case AsyncFunctionDef_kind:
+ return compiler_function(c, s, 1);
+ case AsyncWith_kind:
+ return compiler_async_with(c, s, 0);
+ case AsyncFor_kind:
+ return compiler_async_for(c, s);
+ }
+
+ return 1;
+
+```
+
+- 컴파일러는 일련의 명령을 담고 있는 블록을 컴파일러 상태로 내보냄
+ - 명령 구조체는 명령 코드와 인자, 문장이 위치한 줄번호를 포함함
+
+ 
+
+ - 점프 명령일 경우 점프할 블록에 대한 포인터도 포함함
+ - 점프 명령은 한 명령에서 다른 명령으로의 점프를 실행하면, 절대 점프 방식(코드 객체 상에서 정확한 명령의 위치를 대상으로 사용)과 상대 점프 방식(다른 명령을 기준으로 점프 대상을 지정)의 점프 명령을 사용할 수 있음
+- 컴파일 단계가 끝나면, 블록 리스트가 완성되고, 각 프레임 블록은 명령 리스트와 다음 블록을 가리키는 포인터를 가짐
+- 어셈블러는 기본 프레임 블록들에 깊이 우선 탐색(DFS)를 실행하고, 명령들을 단일한 바이트 코드 시퀀스로 병합함
+
+```c
+// Python/compile.c
+
+static PyCodeObject *
+assemble(struct compiler *c, int addNone)
+{
+ basicblock *b, *entryblock;
+ struct assembler a;
+ int i, j, nblocks;
+ PyCodeObject *co = NULL;
+
+ /* Make sure every block that falls off the end returns None.
+ XXX NEXT_BLOCK() isn't quite right, because if the last
+ block ends with a jump or return b_next shouldn't set.
+ */
+ if (!c->u->u_curblock->b_return) {
+ NEXT_BLOCK(c);
+ if (addNone)
+ ADDOP_LOAD_CONST(c, Py_None);
+ ADDOP(c, RETURN_VALUE);
+ }
+
+ nblocks = 0;
+ entryblock = NULL;
+ for (b = c->u->u_blocks; b != NULL; b = b->b_list) {
+ nblocks++;
+ entryblock = b;
+ }
+
+ /* Set firstlineno if it wasn't explicitly set. */
+ if (!c->u->u_firstlineno) {
+ if (entryblock && entryblock->b_instr && entryblock->b_instr->i_lineno)
+ c->u->u_firstlineno = entryblock->b_instr->i_lineno;
+ else
+ c->u->u_firstlineno = 1;
+ }
+ if (!assemble_init(&a, nblocks, c->u->u_firstlineno))
+ goto error;
+ **dfs(c, entryblock, &a, nblocks);**
+
+ /* Can't modify the bytecode after computing jump offsets. */
+ assemble_jump_offsets(&a, c);
+
+ /* Emit code in reverse postorder from dfs. */
+ for (i = a.a_nblocks - 1; i >= 0; i--) {
+ b = a.a_postorder[i];
+ for (j = 0; j < b->b_iused; j++)
+ if (!assemble_emit(&a, &b->b_instr[j]))
+ goto error;
+ }
+
+ if (_PyBytes_Resize(&a.a_lnotab, a.a_lnotab_off) < 0)
+ goto error;
+ if (_PyBytes_Resize(&a.a_bytecode, a.a_offset * sizeof(_Py_CODEUNIT)) < 0)
+ goto error;
+
+ **co = makecode(c, &a);**
+ error:
+ assemble_free(&a);
+ return co;
+}
+```
+
+- 어셈블러는 기본 프레임 블록 그래프를 DFS로 탐색하는데, 기본 프레임 블록 그래프는 트리 구조인 CST와 AST와 노드가 명령을 담는 헝태의 그래프임
+- 어셈블러는 DFS를 사용해 기본 프레임 블록 그래프를 Control Flow Graph 형태로 변환함
+
+```c
+// Python/compile.c
+
+static void
+dfs(struct compiler *c, basicblock *b, struct assembler *a, int end)
+{
+ int i, j;
+
+ /* Get rid of recursion for normal control flow.
+ Since the number of blocks is limited, unused space in a_postorder
+ (from a_nblocks to end) can be used as a stack for still not ordered
+ blocks. */
+ for (j = end; b && !b->b_seen; b = b->b_next) {
+ b->b_seen = 1;
+ assert(a->a_nblocks < j);
+ a->a_postorder[--j] = b;
+ }
+ while (j < end) {
+ b = a->a_postorder[j++];
+ for (i = 0; i < b->b_iused; i++) {
+ struct instr *instr = &b->b_instr[i];
+ if (instr->i_jrel || instr->i_jabs)
+ dfs(c, instr->i_target, a, j);
+ }
+ assert(a->a_nblocks < j);
+ a->a_postorder[a->a_nblocks++] = b;
+ }
+}
+```
+
+- makecode는 compiler와 assembler를 입력으로 받아서 코드 객체를 생성함
+- PyCode_NewWithPosOnlyArgs()를 실행하기 전에 PyCode_Optimize()를 통해서 Python/peephole.c에서 제공하는 바이트코드 최적으로 진행한다
+ - 바이트코드 명령을 확인하고 특정 시나리오에 해당될 경우 해당 명령을 다른 명령으로 교체해주는 작업을 함, 예를 들어 return 문 뒤의 도달할 수 없는 명령을 제거하는 것
+ - 핍홀 최적화는 소규모의 명령 집합을 동등하거나 더 나은 성능을 제공하는 하나의 명령어나 더 짧은 명령어로 변환하는 방식으로 이루어짐
+
+```c
+static PyCodeObject *
+makecode(struct compiler *c, struct assembler *a)
+{
+ PyObject *tmp;
+ PyCodeObject *co = NULL;
+ PyObject *consts = NULL;
+ PyObject *names = NULL;
+ PyObject *varnames = NULL;
+ PyObject *name = NULL;
+ PyObject *freevars = NULL;
+ PyObject *cellvars = NULL;
+ PyObject *bytecode = NULL;
+ Py_ssize_t nlocals;
+ int nlocals_int;
+ int flags;
+ int posorkeywordargcount, posonlyargcount, kwonlyargcount, maxdepth;
+
+ consts = consts_dict_keys_inorder(c->u->u_consts);
+ names = dict_keys_inorder(c->u->u_names, 0);
+ varnames = dict_keys_inorder(c->u->u_varnames, 0);
+ if (!consts || !names || !varnames)
+ goto error;
+
+ cellvars = dict_keys_inorder(c->u->u_cellvars, 0);
+ if (!cellvars)
+ goto error;
+ freevars = dict_keys_inorder(c->u->u_freevars, PyTuple_GET_SIZE(cellvars));
+ if (!freevars)
+ goto error;
+
+ if (!merge_const_tuple(c, &names) ||
+ !merge_const_tuple(c, &varnames) ||
+ !merge_const_tuple(c, &cellvars) ||
+ !merge_const_tuple(c, &freevars))
+ {
+ goto error;
+ }
+
+ nlocals = PyDict_GET_SIZE(c->u->u_varnames);
+ assert(nlocals < INT_MAX);
+ nlocals_int = Py_SAFE_DOWNCAST(nlocals, Py_ssize_t, int);
+
+ flags = compute_code_flags(c);
+ if (flags < 0)
+ goto error;
+
+ **bytecode = PyCode_Optimize(a->a_bytecode, consts, names, a->a_lnotab);**
+ if (!bytecode)
+ goto error;
+
+ tmp = PyList_AsTuple(consts); /* PyCode_New requires a tuple */
+ if (!tmp)
+ goto error;
+ Py_DECREF(consts);
+ consts = tmp;
+ if (!merge_const_tuple(c, &consts)) {
+ goto error;
+ }
+
+ posonlyargcount = Py_SAFE_DOWNCAST(c->u->u_posonlyargcount, Py_ssize_t, int);
+ posorkeywordargcount = Py_SAFE_DOWNCAST(c->u->u_argcount, Py_ssize_t, int);
+ kwonlyargcount = Py_SAFE_DOWNCAST(c->u->u_kwonlyargcount, Py_ssize_t, int);
+ maxdepth = stackdepth(c);
+ if (maxdepth < 0) {
+ goto error;
+ }
+ **co = PyCode_NewWithPosOnlyArgs(posonlyargcount+posorkeywordargcount,
+ posonlyargcount, kwonlyargcount, nlocals_int,
+ maxdepth, flags, bytecode, consts, names,
+ varnames, freevars, cellvars, c->c_filename,
+ c->u->u_name, c->u->u_firstlineno, a->a_lnotab);**
+ error:
+ Py_XDECREF(consts);
+ Py_XDECREF(names);
+ Py_XDECREF(varnames);
+ Py_XDECREF(name);
+ Py_XDECREF(freevars);
+ Py_XDECREF(cellvars);
+ Py_XDECREF(bytecode);
+ return co;
+}
+```
+
+## 예제: ‘거의 같음’ 연산자 구현하기
+
+- 6장 예제에서 이어서 진행
+- Include/object.h 수정 ← **PyObject의 비교 함수에서 참조할 수 있도록 Py_AlE 연산자에 대한 #define 정의를 추가해야 함**
+
+ ```c
+ // Include/object.h
+
+ /* Rich comparison opcodes */
+ #define Py_LT 0
+ #define Py_LE 1
+ #define Py_EQ 2
+ #define Py_NE 3
+ #define Py_GT 4
+ #define Py_GE 5
+ **#define Py_AlE 6 // PyObject의 비교 함수에서 참조할 수 있도록, Py_AlE 연산자에 대한 #define 정의를 추가**
+
+ /*
+ * Macro for implementing rich comparisons
+ *
+ * Needs to be a macro because any C-comparable type can be used.
+ */
+ #define Py_RETURN_RICHCOMPARE(val1, val2, op) \
+ do { \
+ switch (op) { \
+ case Py_EQ: if ((val1) == (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \
+ case Py_NE: if ((val1) != (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \
+ case Py_LT: if ((val1) < (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \
+ case Py_GT: if ((val1) > (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \
+ case Py_LE: if ((val1) <= (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \
+ case Py_GE: if ((val1) >= (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \
+ **case Py_AlE: if ((val1) == (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \ // 비교 연산을 구현하기 위한 매크로에 Py_AlE에 대한 case를 추가**
+ default: \
+ Py_UNREACHABLE(); \
+ } \
+ } while (0)
+ ```
+
+- Objects/object.c 수정 ← **Py_AlE도 사용할 수 있도록 해야 함**
+
+ ```c
+ // Objects/object.c
+
+ /* Map rich comparison operators to their swapped version, e.g. LT <--> GT */
+ **int _Py_SwappedOp[] = {Py_GT, Py_GE, Py_EQ, Py_NE, Py_LT, Py_LE, Py_AlE}; // Py_AlE를 연산자 타입의 값으로 사용할 수 있도록 함**
+
+ **static const char * const opstrings[] = {"<", "<=", "==", "!=", ">", ">=", "~="}; // ~=를 클래스에 구현되지 않았을 때 에러메시지를 표시하는데 사용할 수 있도록 함**
+ ....
+
+ PyObject *
+ PyObject_RichCompare(PyObject *v, PyObject *w, int op)
+ {
+ PyThreadState *tstate = _PyThreadState_GET();
+
+ **assert(Py_LT <= op && op <= Py_AlE); // Py_AlE의 값인 6도 혀용할 수 있도록 수정, 기존에는 0 ~ 5 사이의 값을 확인했지만, 이제는 0 ~ 6 사이의 값을 확인함**
+ if (v == NULL || w == NULL) {
+ if (!_PyErr_Occurred(tstate)) {
+ PyErr_BadInternalCall();
+ }
+ return NULL;
+ }
+ if (_Py_EnterRecursiveCall(tstate, " in comparison")) {
+ return NULL;
+ }
+ PyObject *res = do_richcompare(tstate, v, w, op);
+ _Py_LeaveRecursiveCall(tstate);
+ return res;
+ }
+ ```
+
+- Lib/opcode.py 수정 ← **~= 를 비교 연산자로 사용할 수 있게 함**
+
+ ```python
+ # Lib/opcode.py
+
+ try:
+ from _opcode import stack_effect
+ __all__.append('stack_effect')
+ except ImportError:
+ pass
+
+ **cmp_op = ('<', '<=', '==', '!=', '>', '>=', '~=') # 비교 연산자 리스트에 ~= 를 추가**
+ ```
+
+- Python/compile.c 수정 ← **Py_AlE의 바이트 코드를 생성할 수 있도록 함**
+
+ ```python
+ static int compiler_addcompare(struct compiler *c, cmpop_ty op)
+ {
+ int cmp;
+ switch (op) {
+ case Eq:
+ cmp = Py_EQ;
+ break;
+ case NotEq:
+ cmp = Py_NE;
+ break;
+ case Lt:
+ cmp = Py_LT;
+ break;
+ case LtE:
+ cmp = Py_LE;
+ break;
+ case Gt:
+ cmp = Py_GT;
+ break;
+ case GtE:
+ cmp = Py_GE;
+ break;
+ **case AlE:
+ cmp = Py_AlE; // PyCmp_AlE인 BinOp 노드를 처리할 수 있도록 수정
+ break;**
+ case Is:
+ ADDOP_I(c, IS_OP, 0);
+ return 1;
+ case IsNot:
+ ADDOP_I(c, IS_OP, 1);
+ return 1;
+ case In:
+ ADDOP_I(c, CONTAINS_OP, 0);
+ return 1;
+ case NotIn:
+ ADDOP_I(c, CONTAINS_OP, 1);
+ return 1;
+ default:
+ Py_UNREACHABLE();
+ }
+ ADDOP_I(c, COMPARE_OP, cmp);
+ return 1;
+ }
+ ```
+
+- Objects/floatobject.c 수정 ← **거의 같음 연산자의 로직을 추가함**
+
+ ```c
+ // Objects/floatobject.c
+
+ Compare:
+ switch (op) {
+ case Py_EQ:
+ r = i == j;
+ break;
+ case Py_NE:
+ r = i != j;
+ break;
+ case Py_LE:
+ r = i <= j;
+ break;
+ case Py_GE:
+ r = i >= j;
+ break;
+ case Py_LT:
+ r = i < j;
+ break;
+ case Py_GT:
+ r = i > j;
+ break;
+ **case Py_AlE: { // 거의 같음 연산자를 수행할 수 있도록 추가
+ double diff = fabs(i - j);
+ double rel_tol = 1e-9;
+ double abs_tol = 0.1;
+ r = (((diff <= fabs(rel_tol * j)) ||
+ (diff <= fabs(rel_tol * i))) ||
+ (diff <= abs_tol));
+ }
+ break;**
+ }
+ return PyBool_FromLong(r);
+ ```
+
+- Python/ceval.c 수정 ← **거의 같음 연산자의 평가 루프를 추가함**
+
+ ```c
+ // Python/ceval.c
+
+ case TARGET(COMPARE_OP): {
+ **assert(oparg <= Py_AlE); // 평가 루프 수정, 자세한 내용은 다음 장에서 다룸**
+ PyObject *right = POP();
+ PyObject *left = TOP();
+ PyObject *res = PyObject_RichCompare(left, right, oparg);
+ SET_TOP(res);
+ Py_DECREF(left);
+ Py_DECREF(right);
+ if (res == NULL)
+ goto error;
+ PREDICT(POP_JUMP_IF_FALSE);
+ PREDICT(POP_JUMP_IF_TRUE);
+ DISPATCH();
+ }
+ ```
+
+- 결과
+
+ ```bash
+ make regen-token regen-pegen
+ make regen-ast
+ make -j2 -s
+ ./python
+
+ # 결과
+ >>> 1.0 ~= 1.01
+ True
+ >>> 1.0 == 1.01
+ False
+ >>> 1.1 ~= 1.101
+ True
+ >>> 1.1 == 1.101
+ False
+ >>> 1.0 ~= 1.5
+ False
+
+ # 추가로 COMPARE_OP에 ~=가 추가되었음을 알 수 있음
+ >>> co = compile("1.0 ~= 1.01", "test.py", "eval")
+ >>> import dis
+ >>> dis.dis(co.co_code)
+ 0 LOAD_CONST 0 (0)
+ 2 LOAD_CONST 1 (1)
+ 4 COMPARE_OP 6 (~=)
+ 6 RETURN_VALUE
+ ```
+
+
+## 정리 (python 3.13.0 기준)
+
+- 소스 코드 → 리더 → 렉서 → 파서 → 컴파일러 → 어셈블러 → 코드 객체 (컴파일은 모듈 단위로 진행됨)
+ - 리더는 소스 코드를 입력 받아 텍스트 형태로 반환함
+ - 렉서는 텍스트를 입력 받아 토큰 형태(Concrete Syntax Tree)로 반환함 ([`Parser/lexer/`](https://github.com/python/cpython/blob/main/Parser/lexer/) and [`Parser/tokenizer/`](https://github.com/python/cpython/blob/main/Parser/tokenizer/))
+ - 여기서 반환된 토큰은 단순히 토큰의 종류만 구분된 상태로, 파이썬 언어 구조와 의미 요소를 반영하고 있지 않음
+ - 파서는 토큰(Concrete Syntax Tree)을 입력 받아 Abstract Syntax Tree로 반환함([`Parser/parser.c`](https://github.com/python/cpython/blob/main/Parser/parser.c))
+ - 여기서 반환된 Abstract Syntax Tree는 파이썬 언어 구조와 의미 요소를 반영하고 있음
+ - 컴파일러는 Abstract Syntax Tree을 입력 받아 instruction sequence로 형태로 변환([`Python/compile.c`](https://github.com/python/cpython/blob/main/Python/compile.c))한 후 Control Flow Graph를 구성([`Python/flowgraph.c`](https://github.com/python/cpython/blob/main/Python/flowgraph.c))하고 최적화를 진행함
+ - Control Flow Graph는 논리적 실행 순서를 나타내지만, CPU가 실행하기 위한 명령어 구조는 아님
+ - 어셈블러는 Control Flow Graph를 입력 받아 bytecode 형태로 변환함([`Python/assemble.c`](https://github.com/python/cpython/blob/main/Python/assemble.c))
+ - bytecode는 Control Flow Graph를 CPU에서 실행 가능한 명령으로 순차적으로 나열한 형태임
+- 컴파일 과정을 통해서 생성된 코드 객체는 인터프리터로 넘겨저 실행되거나, .pyc 파일에 캐시됨
+
+# References
+
+- [https://devguide.python.org/internals/parser/](https://devguide.python.org/internals/parser/#)
+- [https://devguide.python.org/internals/compiler/](https://devguide.python.org/internals/compiler/)
+- [https://stackoverflow.com/questions/6889747/is-python-interpreted-or-compiled-or-both](https://stackoverflow.com/questions/6889747/is-python-interpreted-or-compiled-or-both)
diff --git a/book/docs/8_0_eval_loop.md b/book/docs/8_0_eval_loop.md
new file mode 100644
index 0000000..e00e439
--- /dev/null
+++ b/book/docs/8_0_eval_loop.md
@@ -0,0 +1,581 @@
+# 8. 평가 루프
+
+- [평가 루프](#평가-루프)
+ - [8.0 개요](#80-개요)
+ - [8.1 스레드 상태 생성하기](#81-스레드-상태-생성하기)
+ - [8.2 프레임 객체 생성하기](#82-프레임-객체-생성하기)
+ - [프레임 객체 초기화 API](#프레임-객체-초기화-api)
+ - [8.3 프레임 실행](#83-프레임-실행)
+ - [프레임 실행 추적](#프레임-실행-추적)
+ - [8.4 값 스택](#84-값-스택)
+ - [값 스택 시뮬레이션: 바이트 코드 명령 예제, BINARY_OR](#값-스택-시뮬레이션-바이트코드-명령-예제-binary_or)
+ - [바이트코드 예측](#바이트코드-예측)
+ - [8.6 요약](#86-요약)
+
+
+## 8.0 개요
+지금까지는 파이썬으로 작성된 코드들을 파싱하여, 어떻게 구문 분석을 실시하고, 코드 객체로 컴파일 하는지에 대해 알아보았습니다.
+컴파일된 코드 객체는 바이트 코드로 표현된 연산 리스트를 포함하게 됩니다.
+이번 장에서는 값 스택 이라는 개념을 소개하는데,
+컴파일된 코드 객체의 바이트 코드 연산들은 값 스택에서 변수를 생성하고 변경하여 사용하게 됩니다.
+CPython 에서 코드는 **평가 루프** 라는 개념을 통해 루프(Loop)를 돌며 실행되게 됩니다.
+
+
+
+
+
+
+다음은 이번 장을 통해 살펴볼 개념들입니다.
+
+
+1. 평가 루프를 통해 코드 객체를 입력 받아, **프레임 객체**를 생성하게 되며,
+2. 인터프리터는 최소 한개의 스레드를 가지게 됩니다.
+3. 각 스레드는 스레드의 상태를 나타내는 **스레드 상태**를 가지게 되며,
+4. 1번에서 만들어진 프레임 객체는 **각 스레드**에서 실행되게 됩니다.
+5. 값 스택을 통해, 만들어진 변수를 참조할 수 있습니다.
+
+
+
+
+
+
+## 8.1 스레드 상태 생성하기
+프레임을 실행하려면 스레드에 연결해야 합니다 (스레드에서 생성하기 떄문).
+인터프리터 상태에서 인터프리터는 스레드들을 연결 리스트의 형태로 스레드들을 관리하게 됩니다.
+
+앞서 설명하였듯이, 스레드는 각자 고유의 스레드 상태를 가지게 되는데,
+스레드 상태를 나타내는 PyThreadState 가 그 상태를 나타내게 됩니다.
+
+
+
+위의 PyThreadState는 _ts 의 별칭으로 사용되게 됩니다.
+코드를 살펴보게 되면, 고유 식별자, 다른 스레드 상태와 연결된 연결 리스트,
+인터프리터의 상태, 재귀 깊이 등을 나타내는 값들로 이루어져 있습니다.
+
+
+
+조금만 자세히 들여다보자면, 각 스레드는 스레드 상태(`PyThreadState`)를 가지게 되고,
+스레드 상태는 `PyInterpreterState` 내부에서 연결 리스트로 관리한다는 것을 알 수 있습니다.
+
+``` c
+/* Include/pystate.h */
+
+/* struct _ts is defined in cpython/pystate.h */
+typedef struct _ts PyThreadState;
+/* struct _is is defined in internal/pycore_interp.h */
+typedef struct _is PyInterpreterState;
+```
+
+
+
+``` c
+/* Include/internal/pycore_interp.h */
+
+// The PyInterpreterState typedef is in Include/pystate.h.
+struct _is {
+
+ struct _is *next;
+ struct _ts *tstate_head;
+
+ /* skip */
+}
+```
+
+
+
+
+## 8.2 프레임 객체 생성하기
+AST 를 거쳐 컴파일된 객체는 프레임 객체에 최종적으로 삽입되게 됩니다.
+파이썬 타입인 프레임 객체는 C 와 파이썬 코드 양쪽에서 참조할 수 있도록 설계되었습니다.
+
+프레임 객체는 **코드 객체의 명령** 을 실행하는데 필요한 런타임 데이터를 포함하고,
+런타임 데이터에는 **전역 변수, 지역 변수, 내장 모듈 등**이 포함됩니다.
+
+
+
+다음은 프레임 타입 객체 PyFrameObject 가 가진 프로퍼티입니다.
+
+
+
+
+
+다음은 프로퍼티를 가진 CPython 의 코드 입니다.
+
+
+
+인터프리터에 의해 만들어진 `PyFrameObject`는 초기화 과정을 거치게 되는데,
+`PyEval_EvalCode()` 라는 함수 안에서 초기화 과정을 거치게 됩니다.
+
+
+
+
+
+`_PyEval_EvalCode()` 함수를 따라 프레임에 대한 데이터들이 추가되고, 프레임이 해석되며, 코드가 실행되게 됩니다.
+
+
+
+### 프레임 객체 초기화 API
+프레임 객체를 초기화하는 API인 `PyEval_EvalCode()`는 코드 객체를 평가하기 위한 진입점입니다.
+
+`PyEval_EvalCode()`는 내부 함수` _PyEval_EvalCode()` 를 감싸는 Wrapper 함수입니다.
+`PyEval_EvalCode()`는 아래와 같은 호출 스택을 가집니다.
+
+|호출 스택|
+|:---:|
+|`_PyEval_EvalCode`|
+|`_PyEval_EvalCodeWithName`|
+|`PyEval_EvalCodeEx`|
+|`PyEval_EvalCode`|
+
+
+
+`_PyEval_EvalCode()`는 인터프리터 루프와 프레임 객체 동작의 상당 부분을 정의합니다.
+`PyEval_EvalCode()`는 아래와 같으며 3개의 인자를 받습니다.
+
+``` c
+/* Python/ceval.c */
+
+PyObject *
+PyEval_EvalCode(PyObject *co, PyObject *globals, PyObject *locals)
+{
+ return PyEval_EvalCodeEx(co,
+ globals, locals,
+ (PyObject **)NULL, 0,
+ (PyObject **)NULL, 0,
+ (PyObject **)NULL, 0,
+ NULL, NULL);
+}
+```
+- `co` : 코드 객체
+- `globals`, `locals` : 지역 및 전역 변수
+
+
+
+`_PyEval_EvalCode()`는 다양한 인자를 받는다.
+
+```c
+/* Python/ceval.c */
+
+_PyEval_EvalCode(PyThreadState *tstate,
+ PyObject *_co, PyObject *globals, PyObject *locals,
+ PyObject *const *args, Py_ssize_t argcount,
+ PyObject *const *kwnames, PyObject *const *kwargs,
+ Py_ssize_t kwcount, int kwstep,
+ PyObject *const *defs, Py_ssize_t defcount,
+ PyObject *kwdefs, PyObject *closure,
+ PyObject *name, PyObject *qualname)
+```
+
+- `tstate` : 코드를 평가할 스레드의 상태를 나타냄 (PyThreadState*)
+- `_co` : 프레임 객체에 삽입할 코드 객체 (PyCodeObject*)
+- `globals` : 전역 변수, 변수명을 키로 사용 (PyObject*-dict)
+- `locals` : 지역 변수, 변수명을 키로 사용 (PyObject*-dict)
+
+위 4개의 인자를 제외한 인자는 선택 인자로 기본 API에서는 사용하지 않습니다.
+
+
+
+여기까지 주요 흐름을 정리하자면 다음과 같습니다.
+1. 스레드 상태가 유효한지 확인한다.
+2. 프레임 객체를 선언하고 그 반환값을 초기화하는 등 초기화 작업을 진행한다.
+3. 프레임 객체를 생성한다.
+ ``` c
+ /* Python/ceval.c */
+
+ PyObject *
+ _PyEval_EvalCode(PyThreadState *tstate,
+ PyObject *_co, PyObject *globals, PyObject *locals,
+ PyObject *const *args, Py_ssize_t argcount,
+ PyObject *const *kwnames, PyObject *const *kwargs,
+ Py_ssize_t kwcount, int kwstep,
+ PyObject *const *defs, Py_ssize_t defcount,
+ PyObject *kwdefs, PyObject *closure,
+ PyObject *name, PyObject *qualname)
+ {
+ /* skip */
+
+ /* Create the frame */
+ f = _PyFrame_New_NoTrack(tstate, co, globals, locals);
+ if (f == NULL) {
+ return NULL;
+ }
+
+ /* skip */
+ }
+ ```
+4. 인자와 변수를 처리한다.
+5. 제너레이터 객체와 코루틴 객체를 처리한다.
+6. 프레임을 실행하고 결과를 저장한다.
+7. 프레임 객체의 참조 수를 확인하고 관련 객체들을 해제한다.
+8. 결과를 반환한다.
+
+
+
+
+여기서 `_PyFrame_New_NoTrack()`에 조금만 자세히 알아보도록 합시다.
+`_PyFrame_New_NoTrack()`는 `_PyEval_EvalCode()`의 기본 인자 4가지를 받습니다.
+
+```c
+/* Objects/frameobject.c */
+
+PyFrameObject* _Py_HOT_FUNCTION
+_PyFrame_New_NoTrack(PyThreadState *tstate, PyCodeObject *code,
+ PyObject *globals, PyObject *locals)
+```
+
+아래 과정을 통해 새로운 PyFrameObject를 생성하여 반환한다.
+- 프레임의 f_back 프로퍼티를 스레드 상태의 마지막 프레임으로 설정한다.
+- f_builtins 프로퍼티를 설정 → PyModule_GetDict() 로 builtins 모듈에서 내장 함
+수들을 불러온다.
+- f_code 프로퍼티에 평가 중인 코드 객체를 설정한다.
+- f_valuestack 프로퍼티에 빈 값 스택을 설정한다.
+- f_stackpop 에 f_valuestack 을 가리키는 포인터를 설정한다.
+- 전역 이름 공간 프로퍼티인 f_globals 에 인자로 받은 globals 를 설정한다.
+- 지역 이름 공간 프로퍼티인 f_locals 에 새 딕셔너리를 설정한다.
+- TraceBack에 줄 번호를 표시하기 위해 f_lineno 를 코드 객체의 co_firstlineno 로 설
+정한다.
+- 나머지 프로퍼티는 기본값으로 설정한다.
+
+
+
+위 과정을 모두 거쳐 생성된 프레임 객체(`PyFrameObject`)는 다음과 같습니다.
+
+
+
+
+
+|요소|설명|
+|---|---|
+|Built-in NameSpace|`len`, `print`와 같은 내장 함수 및 예외 등을 포함한다.|
+|Global NameSpace|모듈 레벨에서 정의한 변수, 함수, 클래스 등을 포함한다.|
+|Local NameSpace|함수나 메소드 호출에서 생성됨. 함수 내부에서 정의된 지역 변수를 포함한다.|
+|Value Stack|코드의 실행 중 발생하는 연산을 위한 임시 저장소 역할을 한다. 대부분의 연산들은 이 스택을 통해 수행된다.|
+|Code Object|실행 가능한 ByteCode와 이와 관련된 메타데이터(파일 이름, 줄 번호 등)을 포함한다.|
+
+
+
+|요소|설명|
+|---|---|
+|ByteCode Instructions|컴파일 후 생성된 ByteCode, 기계어 명령에 해당한다.|
+|Names|함수, 변수, 클래스 등의 식별자를 포함하는 리스트이다. 코드 실행 중 이름 참조 시 사용된다.|
+|Constants|실행 중 변경되지 않는 값들을 포함함. 코드 내에서 직접 사용되는 Literal 값을 의미한다. (숫자, 문자열, 튜플 등)|
+
+
+
+아래 코드를 예시로 살펴봅시다.
+
+```c
+def sample():
+ x = 5
+ print(x)
+
+ return 4.29
+```
+- `x`, `print`는 Names에 저장됩니다.
+- `4.29`는 Constants에 저장됩니다.
+- 해당 값들은 바이트코드에서 참조될 때, Names와 Constants에 저장된 인덱스를 통해 참조됩니다.
+
+
+
+## 8.3 프레임 실행
+6장 7장에서 살펴보았듯이, 코드 객체는 실행할 바이트 코드를
+이진 인코딩한 결과와 심벌 테이블, 변수 목록을 포함하게 됩니다.
+
+그리고 변수가 지역인지 전역인지는
+함수나 모듈 또는 블록이 호출된 방법에 따라 런타임에 결정되는데,
+이 정보는 `_PyEval_EvalCode()`에 의해 프레임에 추가됩니다.
+
+`_PyEval_EvalFrameDefault()`는 **기본 프레임 평가 함수**이며,
+이 함수가 모든 것을 통합하는 역할을 수행합니다.
+
+
+
+간단히 말해, 파이썬 프로그램이 실행될 때,
+각각의 코드 객체는 실행을 위해 ‘프레임’이라는 단위로 관리되고
+`_PyEval_EvalFrameDefault()`는 이러한 프레임들을 하나씩 받아서
+순차적으로 명령을 실행하게 됩니다.
+
+
+
+
+### 프레임 실행 추적
+Python 3.7 부터는 현재 쓰레드에서 추적 어트리뷰트를 활성화해서
+단계적으로 프레임을 실행할 수 있습니다.
+
+`PyFrameObject` 타입은 `PyObject *` 타입의 `f_trace` 프로퍼티를 가지는데,
+이 프로퍼티 값은 파이썬 함수를 가리키는 포인터입니다.
+
+아래 예제는 전역 추적 함수로 `my_trace()`를 설정해서
+현재 프레임에서 스택을 가져오고, 역어셈블된 명령 코드를 출력하며,
+디버깅을 위한 정보를 추가합니다.
+
+``` python
+import io
+import sys
+import dis
+import traceback
+
+def my_trace(frame, event, args):
+ frame.f_trace_opcodes = True
+ stack = traceback.extract_stack(frame)
+ pad = " "*len(stack) + "|"
+ if event == 'opcode':
+ with io.StringIO() as out:
+ dis.disco(frame.f_code, frame.f_lasti, file=out)
+ lines = out.getvalue().split('\\n')
+ [print(f"{pad}{l}") for l in lines]
+ elif event == 'call':
+ print(f"{pad}Calling {frame.f_code}")
+ elif event == 'return':
+ print(f"{pad}Returning {args}")
+ elif event == 'line':
+ print(f"{pad}Changing line to {frame.f_lineno}")
+ else:
+ print(f"{pad}{frame} ({event} - {args})")
+ print(f"{pad}-----------------------------------")
+ return my_trace
+
+sys.settrace(my_trace)
+
+# 데모용 코드 실행
+eval('"-".join([letter for letter in "hello"])')
+```
+
+
+
+
+
+`sys.settrace()`는 인자로 전달받은 함수를 현재 스레드 상태의 기본 추적 함수로 설정합니다.
+이 호출 이후 생성된 모든 새 프레임의 `f_trace`가 전달된 함수로 설정됩니다.
+
+이 토막 코드는 각 스택에서 코드를 출력하고 실행 전에 다음 명령을 가리킵니다.
+> 전체 ByteCode 명령 목록은 dis 모듈 문서에서 찾을 수 있다.
+
+``` python
+import dis
+dis.__dict__
+...
+Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.
+All Rights Reserved., 'credits': Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
+ for supporting Python development. See www.python.org for more information., 'license': Type license() to see the full license text, 'help': Type help() for interactive help, or help(object) for help about object., '_': None}, 'sys': , 'types': , 'collections': , 'io': , 'cmp_op': ('<', '<=', '==', '!=', '>', '>='), 'hasconst': [100], 'hasname': [90, 91, 95, 96, 97, 98, 101, 106, 108, 109, 116, 160], 'hasjrel': [93, 110, 122, 143, 154], 'hasjabs': [111, 112, 113, 114, 115, 121], 'haslocal': [124, 125, 126], 'hascompare': [107], 'hasfree': [135, 136, 137, 138, 148], 'opname': ['<0>', 'POP_TOP', 'ROT_TWO', 'ROT_THREE', 'DUP_TOP', 'DUP_TOP_TWO', 'ROT_FOUR', '<7>', '<8>', 'NOP', 'UNARY_POSITIVE', 'UNARY_NEGATIVE', 'UNARY_NOT', '<13>', '<14>', 'UNARY_INVERT', 'BINARY_MATRIX_MULTIPLY',
+...
+```
+
+
+
+
+## 8.4 값 스택
+값 스택은 코어 평가 루프 안에서 생성되게 됩니다.
+이 스택은 PyObject 인스턴스를 가르키는 포인터가 들어있는 리스트입니다.
+
+값 스택의 포인터는 변수나 함수 참조 등
+어떠한 파이썬 객체라도 가르킬 수 있다는 특징이 있습니다.
+
+> 평가루프에서 바이트코드 명령은 값 스택으로부터 입력을 취합니다.
+
+
+
+값 스택이라는 이름처럼 파이썬의 각 프레임 오브젝트 들은 스택처럼 값을 계산해나가면,
+PyFrameObject 들을 생성 소멸 해 나가면서
+각 명령 코드에 의해 스택 크기의 변화량을 반환합니다.
+
+
+
+
+### 값 스택 시뮬레이션 (바이트코드 명령 예제: BINARY_OR)
+값 스택은 코어 평가 루프 안에서 생성됩니다.
+이 스택은 `PyObject` 인스턴스를 가리키는 포인터가 들어 있는 리스트입니다.
+
+값 스택의 포인터는 변수나 함수 참조 등 어떠한 파이썬 객체라도 가리킬 수 있습니다.
+평가 루프에서 ByteCode 명령은 값 스택으로부터 입력을 취합니다.
+
+
+
+예를 들어 or 문을 사용한 파이썬 코드를 살펴보도록 합시다.
+
+``` python
+if left or right:
+ pass
+```
+컴파일러는 이 `or` 문을 `BINARY_OR` 명령으로 컴파일합니다.
+
+
+
+``` c
+static int
+binop(struct compiler *c, operator_ty op)
+{
+ switch (op) {
+ case Add:
+ return BINARY_ADD;
+ ...
+ case BitOr:
+ return BINARY_OR;
+ }
+}
+```
+평가 루프는 `BINARY_OR`일 경우에 값 스택에서 `left`와 `right` 두 개의 값을 꺼낸 후
+꺼낸 객체들을 인자로 `PyNumber_Or`를 호출합니다.
+
+그 후 연산 결과인 `res`를 스택의 맨 위에 추가합니다.
+
+``` c
+ ...
+ case TARGET(BINARY_OR): {
+ PyObject *right = POP();
+ PyObject *left = TOP();
+ PyObject *res = PyNumber_Or(left, right);
+ Py_DECREF(left);
+ Py_DECREF(right);
+ SET_TOP(res);
+ if (res == NULL)
+ goto error;
+ DISPATCH();
+ }
+
+```
+
+
+
+### 바이트코드 예측
+리스트 객체의 `append()` 메소드를 예로 들어보자.
+
+``` python
+my_list = []
+my_list.append(obj)
+```
+
+앞의 예시에서 `obj`는 리스트 끝에 추가하려는 객체입니다.
+리스트 추가 연산은 다음 두 연산을 포함합니다.
+
+1. `LOAD_FAST`: `obj`를 프레임의 `locals`에서 값 스택의 맨 위로 올린다.
+2. `LIST_APPEND`: 객체를 리스트에 추가한다.
+
+
+
+여기에서 바이트코드 명령인 `LOAD_FAST`은 설명은 다음과 같습니다.
+
+- **목적**: 지역 변수 `obj`를 값 스택의 맨 위로 로드한다.
+- **과정**:
+ 1. `GETLOCAL()`을 사용하여 `obj`의 포인터를 얻는다.
+ 2. 언바운드 지역 변수 에러를 처리한다 (변수가 정의되지 않은 경우).
+ 3. 객체의 레퍼런스 카운터를 증가시킨다.
+ 4. 포인터를 값 스택에 푸시한다.
+ 5. `FAST_DISPATCH` 매크로를 통해 다음 명령으로 빠르게 이동한다.
+
+다음은 `LOAD_FAST`를 처리하는 코드입니다.
+
+``` c
+// Python/ceval.c
+ ...
+ case TARGET(LOAD_FAST): {
+ PyObject *value = GETLOCAL(oparg); // 1.
+ if (value == NULL) {
+ format_exc_check_arg(
+ PyExc_UnboundLocalError,
+ UNBOUNDLOCAL_ERROR_MSG,
+ PyTuple_GetItem(co->co_varnames, oparg));
+ goto error; // 2.
+ }
+ Py_INCREF(value); // 3.
+ PUSH(value); // 4.
+ FAST_DISPATCH(); // 5.
+ }
+ ...
+
+```
+
+
+
+`obj`에 대한 포인터를 값 스택의 맨 위에 추가하면 다음 명령인 `LIST_APPEND`가 실행됩니다.
+`LIST_APPEND`에 대한 설명은 다음과 같습니다.
+
+- **목적**: 값 스택에서 객체를 꺼내 리스트의 끝에 추가한다.
+- **과정**:
+ 1. 값 스택에서 객체의 포인터를 꺼낸다 (`POP()`).
+ 2. 리스트 객체의 포인터를 스택에서 가져온다 (`PEEK(oparg)`).
+ 3. `PyList_Append()` 함수를 호출하여 객체를 리스트에 추가한다.
+ 4. 예외 처리를 수행하고, 에러 발생 시 에러 처리 루틴으로 이동한다.
+ 5. `PREDICT(JUMP_ABSOLUTE)`를 통해 다음 예상 명령으로 빠르게 이동한다.
+
+```c
+...
+ case TARGET(LIST_APPEND): {
+ PyObject *v = POP();
+ PyObject *list = PEEK(oparg);
+ int err;
+ err = PyList_Append(list, v);
+ Py_DECREF(v);
+ if (err != 0)
+ goto error;
+ PREDICT(JUMP_ABSOLUTE); // <-- 예측에 성공한다면 goto 문으로 바뀜
+ DISPATCH();
+ }
+...
+```
+
+`PREDICT`는 다음 명령이 `JUMP_ABNSOULTE`일 것이라고 예측하는 구문입니다.
+이 매크로는 바로 다음에 실행될 것이라고 예측되는
+연산의 `case` 문으로 점프하도록 컴파일러가 생성한 `goto` 문을 포함합니다.
+
+즉, CPU가 루프를 돌지 않고 예측한 명령으로 바로 점프할 수 있습니다.
+
+> 자주 함께 등장하는 명령 코드들에 대해서는 첫 번째 명령을 실행할 때 두 번째 명령을 함께 예측할 수 있습니다.
+예를 들어 COMPARE_OP 실행 후에는 POP_JUMP_IF_FALSE 또는 POP_JUMP_IF_TRUE가 실행되는 경우가 많습니다.
+>
+>
+> 명령 코드에 대한 통계를 수집하려면 2가지 선택지가 있습니다.
+>
+> 1. 예측을 활성화하고 일부 명령 코드가 조합된 것 처럼 결과를 해석한다.
+> 2. 예측을 비활성화하고 각 명령 코드에 대한 실행 빈도 카운터가 독립적으로 갱신되도록 한다.
+>
+> 계산된 `goto`를 사용할 수 있으면 `CPU`가 각 명령어 코드에 대해 별도의 분기 예측 정보를 기록할 수 있기 때문에 CPython 단에서 명령 코드 예측은 비활성화됩니다.
+>
+
+
+
+*※ 계산된 Goto*
+switch 문 내에서 각 case 라벨로 점프하는 대신
+goto문을 이용해서 점프하는 방식
+
+
+
+*※ 명령 코드 예측 관련 코드 (Python/ceval.c)*
+
+``` c
+#define PREDICT_ID(op) PRED_##op
+
+#if defined(DYNAMIC_EXECUTION_PROFILE) || USE_COMPUTED_GOTOS
+#define PREDICT(op) if (0) goto PREDICT_ID(op)
+#else
+#define PREDICT(op) \\
+ do { \\
+ _Py_CODEUNIT word = *next_instr; \\
+ opcode = _Py_OPCODE(word); \\
+ if (opcode == op) { \\
+ oparg = _Py_OPARG(word); \\
+ next_instr++; \\
+ goto PREDICT_ID(op); \\
+ } \\
+ } while(0)
+#endif
+```
+
+- `opcode`: 다음 바이트코드
+- `op`: 예측할 바이트 코드
+
+
+
+다음에 바이트 코드가 예측한 바이트와 동일하다면
+`goto`문을 통해 코드의 실행 흐름을 바꾸는 방식입니다.
+
+
+
+
+
+## 8.6 요약
+CPython 의 실행 중, 생성되는 파이썬 객체인 PyFrameObject 의 평가 루프에 대해서 알아보았으며,
+프레임이 어떻게 생성되고 소멸되는지 알아보는 장이었습니다.
+
+코어 평가 루프는 컴파일 된 파이썬 코드 그리고 그 기반이 되는 C 확장 모듈과 라이브러리,
+ 시스템 호출간의 인터페이스로서, 그 중요성을 잘 설명해주는 챕터였습니다.
\ No newline at end of file
diff --git a/book/docs/9_0_memory.md b/book/docs/9_0_memory.md
new file mode 100644
index 0000000..6dbe5cb
--- /dev/null
+++ b/book/docs/9_0_memory.md
@@ -0,0 +1,127 @@
+# 9. 메모리 관리
+
+- [메모리 관리](#메모리-관리)
+ - [9.0 개요](#90-개요)
+ - [9.1 C 메모리 할당](#91-C-메모리-할당)
+ - [9.2 파이썬 메모리 관리 시스템의 설계](#92-파이썬-메모리-관리-시스템의-설계)
+ - [9.3 CPython 메모리 할당자 (pymalloc)](#93-CPython-메모리-할당자)
+
+
+
+## 9.0 개요
+
+동적 타입 언어인 CPython에서 메모리를 어떻게 할당 하고 해제하는지 그리고 메모리 누수를 관리 하는 방법에 대해서 알아 보겠습니다.
+
+
+## 9.1 C 메모리 할당
+
+
+
+
+CPython을 구현하는 C 언어에서의 메모리 할당은 크게 3가지로 나뉩니다.
+
+1. 정적 메모리 할당(Static) : 컴파일 시간에 필요한 메모리가 계산되고 프로그램이 실행될 때 할당
+2. 자동 메모리 할당(Stack) : 함수 실행시에 스코프에 필요한 지역 변수가 콜 스택에 할당
+3. 동적 메모리 할당(Heap) : 런타임에 메모리를 동적으로 할당하며 제대로 반환하지 않으면 메모리 누수 발생
+
+
+## 9.2 파이썬 메모리 관리 시스템의 설계
+
+메모리 관점에서 파이썬 언어 설계의 특징
+
+1. 변수의 크기를 컴파일 시간에 정할 수 없음
+2. 코어 타입의 크기가 가변적임 (list, dict, int)
+3. 타입이 달라도 같은 이름을 재사용 가능
+
+파이썬 객체 메모리는 개발자가 직접 할당 하는 대신 파이썬 언어에서 제공하는 메모리 할당 API에 의해 자동으로 할당됩니다.
+
+메모리 할당 API는 C언어의 동적 메모리 할당에 의존하여 개발되었으며 메모리 누수를 막기 위해 가비지 컬렉션과 참조 카운팅을 사용해 메모리를 자동으로 해제하는 안전장치가 추가되어 있습니다.
+
+
+[이미지 출처][link01]
+
+[link01]: https://velog.io/@qlgks1/python-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B4%80%EB%A6%AC-memory-optimization
+
+CPython은 세가지 동적 메모리 할당자 도메인을 제공합니다.
+
+1. PYMEM_DOMAIN_OBJ : 파이썬 객체 메모리 할당
+2. PYMEM_DOMAIN_MEM : 레거시 API용도 https://github.com/python/cpython/blob/89867d2491c0c3ef77bc237899b2f0762f43c03c/Objects/obmalloc.c#L94C32-L94C32
+3. PYMEM_DOMAIN_RAW : 시스템 힙, 대용량 메모리, 비 객체 메모리 할당
+
+PYMEM_DOMAIN_MEM,PYMEM_DOMAIN_OBJ 할당자의 경우 기본 할당자로 pymalloc을 사용하며 GIL이 있는 상태에서 사용됩니다. PYMEM_DOMAIN_RAW의 경우에는 뮤텍스를 사용하여 스레드 안정성을 확보하며 malloc을 사용하여 시스템에서 직접 메모리를 할당 받습니다.
+
+각 도메인은 _Alloc, _Calloc, _Realoc, _Free 인터페이스를 구현합니다.
+
+
+## 9.3 CPython 메모리 할당자 (pymalloc)
+
+CPython의 메모리 할당 요청은 일반적으로 아래 표와 같이 고정된 크기의 작은 메모리를 요구 합니다.
+
+| CPython OBject | Size |
+|------|---|
+| PyObject | 16byte |
+| PyASCIIObject | 42byte |
+| PyCompactUnicodeObject | 72byte |
+| PyLongObject | 32byte |
+
+이러한 메모리 사용 패턴에서 메모리 할당을 위해 C언어의 malloc 및 free를 반복적으로 호출하는 경우 시스템 오버헤드를 발생 시키며 프로그램이 장기간 유지되는 경우 메모리 파편화를 유발 할 수 있습니다.
+
+CPython의 메모리 할당자는 시스템 할당자 위에 구축되어 있지만 CPython에 특화 되어 있습니다.
+메모리 할당 알고리즘은 아레나/풀/블록 으로 계층화된 구조로 메모리를 할당 합니다.
+
+
+
+
+ 1. 아레나(Arena)
+ - 가장 큰 단위의 메모리 그룹
+ - 256KB 단위로 할당
+ - 시스템 페이지 크기에 맞춰 정렬
+ - 시스템 힙에 할당
+ - 익명 메모리 매핑을 지원 하는 시스템에서 mmap()로 할당
+ - 아레나들은 이중 연결 리스트로 연결 되어 있음
+
+
+[이미지 출처][link02]
+
+[link02]: https://fanchao01.github.io/blog/2016/10/09/python-obmalloc/
+
+ 2. 풀(Pool)
+ - 메모리 풀에는 같은 크기의 블록들이 저장
+ - 풀에 담을 수 있는 블록의 최대 크기는 512byte
+ - 풀의 크기는 4096byte(=4KB)로 고정
+ - 아레나당 풀 개수도 64개로 고정 (64*4KB = 256KB)
+ - 같은 단위를 가지는 풀들은 풀 테이블(이중 연결 리스트)로 연결
+ - 풀 테이블은 크기 인덱스 i로 분할
+ - 인덱스가 i 일 때 usedpools[i+i]는 크기 단위가 i 인 사용 중인 풀 리스트의 헤더를 가리킴
+ - 풀이 포화 상태가 되면 usedpools 리스트에서 제거
+ - 풀에 있는 블록이 할당 해제되면 다시 usedpools[] 리스트 맨 앞에 배치되어 다음 할당 요청에 사용
+ - 풀의 모든 블록이 할당 해제되면 usedpools 리스트에서 제거되고 freepools 맨 앞으로 이동
+ - 풀의 상태
+ - 포화 : 모든 블록 할당
+ - 사용중 : 일부 블록 할당
+ - 미사용 : 풀은 할당 되었지만, 블록은 미 할당
+
+
+[이미지 출처][link03]
+
+[link03]: https://www.blog.subhashkumar.in/posts/python-memory-management/
+
+
+ 3. 블록(Block)
+ - 풀 내부에서 메모리는 블록 단위로 할당
+ - 블록은 고정 크기로 할당, 해제
+ - 사용되지 않은 블록은 풀 내부의 freeblock 에 연결
+ - 할당 해제된 블록은 freeblock 맨 앞에 연결
+ - 풀이 초기화 되면 첫 두 블록은 freeblock 리스트에 연결
+ - 블록 할당 과정
+ - 인덱스 설정
+ - 32bit 시스템에서는 블록 크기가 8byte 단위로 증가하므로 64가지 블록 크기 사용 가능
+ - 64bit 시스템에서는 블록 크기가 16byte 단위로 증가하므로 32가지 블록 크기 사용 가능
+ - 할당 요청된 크기에 따라 적합한 크기 인덱스가 결정
+ - 할당 요청이 0>> test_function()
+my_variable in locals(): True
+my_variable in globals(): False
+```
+
+이는 `my_variable`이 `test_function()`의 로컬 변수 딕셔너리에 존재하지만, 전역 변수 딕셔너리에는 존재하지 않음을 보여줍니다.
+
+#### 참조 카운트 증가시키기
+
+CPython의 소스 코드에는 `Py_INCREF()`와 `Py_DECREF()` 매크로가 Python 객체에 대한 참조를 증가시키고 감소시키는 API입니다.
+
+- 참조 증가(`Py_INCREF`): 객체가 변수에 할당되거나, 함수/메서드의 인수로 전달되거나, 함수에서 반환될 때 호출됩니다.
+- 참조 감소(`Py_DECREF`): 변수가 선언된 범위를 벗어나면 호출됩니다. 참조 수가 0이 되면 객체의 메모리가 해제됩니다.
+
+CPython 소스코드에서 `Py_INCREF`는 단순히 `ob_refcnt` 값을 1 증가시킵니다. `ob_refcnt`는 `PyObject` 인스턴스의 프로퍼티로, 객체의 참조 카운터입니다.
+
+```c
+static inline void _Py_INCREF(PyObject *op)
+{
+#ifdef Py_REF_DEBUG
+ _Py_RefTotal++;
+#endif
+ op->ob_refcnt++;
+}
+```
+
+- `PyObject`는 파이썬 객체를 나타내는 C 구조체입니다.
+- `op`는 `PyObject` 포인터 변수로, 파이썬 객체의 메모리 주소를 저장합니다.
+- `#ifdef Py_REF_DEBUG` ~ `#endif`
+ - 이는 C 전처리기 지시자로, 조건부 컴파일을 위해 사용됩니다.
+ - `#ifdef`는 "if defined"의 줄임말로, `Py_REF_DEBUG`라는 매크로가 정의되어 있는지 확인합니다.
+ - 만약 `Py_REF_DEBUG`가 정의되어 있다면, `#ifdef`와 `#endif` 사이의 코드가 컴파일됩니다. 그렇지 않으면 해당 코드는 무시됩니다.
+ - `Py_REF_DEBUG`는 디버깅 용도로 사용되는 매크로로, 참조 카운팅 관련 디버깅 정보를 추적하는 데 사용됩니다.
+- `_Py_RefTotal++;`, `op->ob_refcnt++;`
+ - `_Py_RefTotal`은 참조 카운팅 디버깅을 위해 사용됩니다. 이 변수는 `Py_REF_DEBUG`가 정의되어 있을 때만 증가합니다.
+ - `op->ob_refcnt`는 `PyObject` 구조체의 `ob_refcnt` 필드에 접근하는 코드입니다. 이 필드는 해당 객체의 참조 카운트를 저장합니다.
+
+Python 코드로 참조 카운팅을 조회하려면 `sys.getrefcount()`[^3]를 사용할 수 있습니다.
+
+```python
+def test_function():
+ my_variable = ['a', 'b', 'c']
+ print("my_variable in locals():", 'my_variable' in locals())
+ print("my_variable in globals():", 'my_variable' in globals())
+```
+
+```shell
+>>> import sys
+>>> print(sys.getrefcount(test_function))
+2
+```
+
+참조하는 개수는 1일 것 같지만 실제 `sys.getrefcount(test_function)`를 호출해보면 2가 출력됩니다. 이는 `getrefcount()`의 인수로 (임시) 참조가 포함되기 때문입니다.
+
+그럼 다음으로 작은 숫자를 할당해보겠습니다.
+
+```
+>>> a = 1
+>>> print(sys.getrefcount(a))
+4294967295
+```
+
+파이썬에서는 자주 사용되는 작은 정수들을 미리 캐싱해두고 재사용합니다. 이를 "정수 인터닝(integer interning)"이라고 합니다.
+
+파이썬에서는 `-5`부터 `256`까지의 정수들을 미리 생성해두고 필요할 때마다 같은 객체를 반환합니다. 따라서 이 범위 내의 정수들은 매우 높은 참조 개수를 가지게 됩니다. 실제로는 이 정수들의 참조 개수를 정확히 추적하지 않고, 대신 임의의 큰 값(4294967295)을 반환합니다.
+
+#### 참조 카운트 감소시키기
+
+`Py_DECREF` 는 객체의 참조 카운트를 감소시키는 역할을 합니다. 참조 카운트가 0이 되면 객체의 메모리를 해제합니다.
+
+```c
+static inline void _Py_DECREF(
+#ifdef Py_REF_DEBUG
+ const char *filename, int lineno,
+#endif
+ PyObject *op)
+{
+#ifdef Py_REF_DEBUG
+ _Py_RefTotal--;
+#endif
+ if (--op->ob_refcnt != 0) {
+#ifdef Py_REF_DEBUG
+ if (op->ob_refcnt < 0) {
+ _Py_NegativeRefcount(filename, lineno, op);
+ }
+#endif
+ }
+ else {
+ _Py_Dealloc(op);
+ }
+}
+```
+
+- `#ifdef Py_REF_DEBUG`와 `#endif` 사이의 코드는 디버그 모드에서만 컴파일됩니다.
+- `_Py_RefTotal`은 전역 참조 카운트를 감소시킵니다.
+- `op->ob_refcnt`를 감소시킨 후, 그 값이 0이 아닌지 확인합니다.
+ - 0이 아니라면, 아직 참조가 남아 있는 것이므로 메모리를 해제하지 않습니다.
+ - 디버그 모드에서는 참조 카운트가 음수인지 확인하고, 음수라면 `_Py_NegativeRefcount`를 호출하여 오류를 보고합니다.
+- 참조 카운트가 0이 되면 `_Py_Dealloc`을 호출하여 객체의 메모리를 해제합니다.
+
+#### 바이트코드 연산에서의 참조 카운팅
+
+다음 예제를 살펴보겠습니다. `y`에 대한 참조가 몇 개일까요?
+
+```python
+import sys
+
+y = "hello"
+
+def greet(message=y):
+ print(message.capitalize() + " " + y)
+
+messages = [y]
+
+greet(*messages)
+
+print(sys.getrefcount(y))
+```
+
+위 코드를 실행하면 `y`에 대한 총 참조 수는 4개가 아니라 6개입니다.
+
+- 1. `y = "hello"`: `y`가 문자열 객체를 참조합니다. 이 때 `STORE_NAME` 연산이 사용되며, 참조 카운트가 1 증가합니다.
+- 2. `def greet(message=y)`: `y`가 `greet` 함수의 기본 인자로 사용됩니다. 이 때 `LOAD_NAME` 연산이 사용되며, 참조 카운트가 1 증가합니다.
+- 3. `print(message.capitalize() + " " + y)`: `greet` 함수 내에서 `y`가 사용됩니다. 이 때 `LOAD_GLOBAL` 연산이 사용되며, 참조 카운트가 1 증가합니다.
+- 4. `messages = [y]`: `y`가 리스트의 요소로 사용됩니다. 이 때 `BUILD_LIST` 연산이 사용되며, 참조 카운트가 1 증가합니다.
+- 5. `greet(*messages)`: `greet` 함수를 호출할 때 `y`가 인자로 전달됩니다. 이 때 `CALL_FUNCTION` 연산이 사용되며, 참조 카운트가 1 증가합니다.
+- 6. `print(sys.getrefcount(y))`: `sys.getrefcount(y)`를 호출할 때 `y`가 인자로 전달됩니다. 이 때 `LOAD_GLOBAL` 연산이 사용되며, 참조 카운트가 1 증가합니다.
+
+연산을 예제로 살펴봅시다.
+
+```python
+a = 10
+b = 20
+c = a * b
+```
+
+a, b를 선언하고 a, b를 곱한 값을 c에 할당한 식을 살펴봅시다. 세 번째 연산인 `c = a * b`는 세 가지 연산으로 나눌 수 있습니다.
+
+- `LOAD_FAST`: a 변수를 확인하고 값 스택에 추가한 후 변수의 참조를 1 증가시킨다.
+- `LOAD_FAST`: b 변수를 확인하고 값 스택에 추가한 후 변수의 참조를 1 증가시킨다.
+- `BINARY_MULTIPLY`: 왼쪽 값에 오른 쪽 값을 곱하고 값 스택에 추가합니다.
+
+`LOAD_FAST` 연산은 주어진 이름의 객체를 로드하고 값 스택(value stack)의 맨 위에 푸시한 다음 참조 수를 1 증가시킵니다. 값 스택은 바이트코드 연산의 피연산자와 연산 결과를 저장하는 데 사용됩니다.
+
+`LOAD_FAST` 연산은 다음과 같이 구현되어 있습니다.
+
+```c
+case TARGET(LOAD_FAST): {
+ PyObject *value = GETLOCAL(oparg);
+ if (value == NULL) {
+ format_exc_check_arg(tstate, PyExc_UnboundLocalError,
+ UNBOUNDLOCAL_ERROR_MSG,
+ PyTuple_GetItem(co->co_varnames, oparg));
+ goto error;
+ }
+ Py_INCREF(value);
+ PUSH(value);
+ FAST_DISPATCH();
+}
+```
+
+위 코드에서 `GETLOCAL(oparg)`는 주어진 인덱스(`oparg`)에 해당하는 로컬 변수를 가져옵니다. 변수의 값이 성공적으로 로드되면 `Py_INCREF(value)`를 호출하여 참조 카운트를 1 증가시키고, `PUSH(value)`를 통해 값 스택에 해당 값을 푸시합니다. `FAST_DISPATCH()`는 매크로로 인터프리터의 메인 루프에서 다음 바이트코드 명령어로 빠르게 이동하는 역할을 합니다.
+
+`BINARY_MULTIPLY` 연산은 두 개의 피연산자를 값 스택에서 팝(pop)하고, 곱셈 연산을 수행한 후, 결과값을 다시 값 스택에 푸시합니다. 이 과정에서 피연산자들의 참조 카운트는 감소하게 됩니다.
+
+```c
+case TARGET(BINARY_MULTIPLY): {
+ PyObject *right = POP();
+ PyObject *left = TOP();
+ PyObject *res = PyNumber_Multiply(left, right);
+ Py_DECREF(left);
+ Py_DECREF(right);
+ SET_TOP(res);
+ if (res == NULL)
+ goto error;
+ DISPATCH();
+}
+```
+
+위 예에서 `POP()`과 `TOP()`을 통해 값 스택에서 피연산자들을 가져옵니다. `PyNumber_Multiply(left, right)`를 호출하여 곱셈 연산을 수행하고, 그 결과를 `res`에 저장합니다. 이후 `Py_DECREF(left)`와 `Py_DECREF(right)`를 호출하여 피연산자들의 참조 카운트를 감소시킵니다. 마지막으로 `SET_TOP(res)`를 통해 결과값을 값 스택의 맨 위에 설정합니다.
+
+이처럼 바이트코드 연산에서는 객체의 참조 카운트를 적절히 증가시키고 감소시키는 작업이 이루어집니다. 하지만 이러한 작업이 올바르게 이루어지지 않으면 메모리 누수나 잘못된 메모리 접근 등의 문제가 발생할 수 있습니다.
+
+#### 순환 참조와 가비지 컬렉션
+
+참조 카운팅의 가장 큰 단점 중 하나는 순환 참조(circular reference)를 처리하지 못한다는 것입니다. 순환 참조는 객체들이 서로를 참조하고 있어 참조 카운트가 0이 되지 않는 상황을 말합니다.
+
+```python
+x = []
+x.append(x)
+del x
+```
+
+x는 스스로를 참조하고 있기 때문에 x의 참조 카운트는 0이 되지 않고 1로 유지됩니다.
+
+이러한 문제를 해결하기 위해 CPython은 가비지 컬렉션(garbage collection) 메커니즘을 도입했습니다. 가비지 컬렉터는 주기적으로 실행되며, 참조 카운트가 0이 아니지만 도달할 수 없는(unreachable) 객체들을 찾아 메모리에서 해제합니다.
+
+```python
+import gc
+
+gc.set_debug(gc.DEBUG_SAVEALL) # 순환 참조를 gc.garbage에 남깁니다.
+
+x = []
+x.append(x)
+del x
+
+print("Before gc.collect():")
+for obj in gc.garbage:
+ print(obj)
+
+gc.collect() # 가비지 컬렉터 강제 실행
+
+print("After gc.collect():")
+for obj in gc.garbage:
+ print(obj)
+```
+
+```shell
+Before gc.collect():
+After gc.collect():
+[[...]]
+```
+
+- `gc.set_debug(gc.DEBUG_SAVEALL)`: 이 구문은 가비지 컬렉터 설정을 변경하여, 가비지 컬렉터가 회수한 모든 객체를 `gc.garbage` 리스트에 저장하도록 합니다. 이렇게 하면 순환 참조로 인해 회수된 객체들을 분석할 수 있습니다.
+- `x = []`: 빈 리스트를 생성합니다.
+- `x.append(x)`: 리스트 `x`가 자신을 포함하도록 합니다. 이는 `x` 내부에 `x` 자신에 대한 참조를 만들어 순환 참조를 생성합니다.
+- `del x`: 변수 `x`에 대한 참조를 삭제합니다. 이 시점에서 `x`는 메모리에 남아 있지만, 직접적인 참조는 존재하지 않습니다. 순환 참조로 인해, 가비지 컬렉터만이 이를 처리할 수 있습니다.
+- `gc.collect()`: 가비지 컬렉터를 강제로 실행하여 순환 참조를 포함한 미수거 객체들을 찾아내고 처리합니다. `gc.set_debug(gc.DEBUG_SAVEALL)` 설정으로 인해, 처리된 객체들은 `gc.garbage`에 저장됩니다.
+- `for obj in gc.garbage`: `gc.garbage`에 저장된 객체들을 순회하며 출력합니다. 이 경우, 자기 자신을 참조하는 리스트 `x`가 출력됩니다. 출력된 `[[...]]`는 리스트가 자기 자신을 참조하고 있음을 나타냅니다.
+
+위 예제에서는 가비지 컬렉터를 실행하기 전에는 `gc.garbage`가 비어 있지만, 가비지 컬렉터 실행 후에는 순환 참조 객체가 `gc.garbage`에 수집되는 것을 보여줍니다.
+
+### 참조 카운팅 정리
+
+CPython의 메모리 관리 방식인 참조 카운팅에 대해 알아보았습니다. 참조 카운팅은 객체에 대한 참조 수를 추적하여 더 이상 사용되지 않는 객체를 자동으로 메모리에서 해제합니다.
+
+- CPython은 C의 동적 메모리 할당 시스템을 기반으로 구축되어 있으며, 메모리 요구 사항은 런타임에 결정됩니다.
+- Python 객체는 `PyObject` 구조체로 표현되며, 객체의 타입과 참조 카운트 등의 정보를 포함하고 있습니다.
+- 참조 카운트는 `Py_INCREF()`와 `Py_DECREF()` 매크로를 통해 관리됩니다. 객체가 참조될 때마다 `Py_INCREF()`가 호출되어 참조 카운트가 증가하고, 참조가 해제될 때마다 `Py_DECREF()`가 호출되어 참조 카운트가 감소합니다.
+- 참조 카운트가 0이 되면 객체는 더 이상 사용되지 않는 것으로 간주되어 메모리에서 해제됩니다.
+- 참조 카운팅의 단점은 순환 참조를 처리하지 못한다는 것입니다. 이를 해결하기 위해 CPython은 가비지 컬렉션을 도입했습니다.
+- 가비지 컬렉터는 주기적으로 실행되며, 참조 카운트가 0이 아니지만 도달할 수 없는 객체들을 찾아 메모리에서 해제합니다.
+
+CPython의 메모리 관리는 참조 카운팅과 가비지 컬렉션이라는 두 가지 전략을 통해 이루어집니다. 이를 통해 개발자는 메모리 관리에 대해 크게 신경 쓰지 않고도 Python을 사용할 수 있습니다. 하지만 메모리 누수나 순환 참조 등의 문제를 완전히 방지하기 위해서는 메모리 관리 방식에 대한 이해가 필요합니다.
+
+## 가비지 컬렉션 (cyclic garbage collection)
+
+- 참조 카운터와 특수한 가비지 컬렉션 알고리즘을 사용하여 결국 순환 참조를 찾는 것
+- 그럼 어떻게 Garbage 객체를 찾을까요?
+ - “**모든**" 객체에 대해서 reference를 graph로 그리며, 접근 불가능한 cycle을 찾는 방법 밖에 없다.
+ - 모든 객체에 대해서 전수 조사를 하기에는 너무 느리기 때문에 **“Generation”** 이라는 최적화 기법을 사용한다.
+ - Young generation 객체는 자주, Old generation 객체는 상대적으로 가끔씩 scan을 수행하는 것이다.
+- 가비지 컬렉터(GC)는 내부적으로 `generation`(세대)과 `threshold`(임계값)로 가비지 컬렉션 주기와 객체를 관리한다.
+ - NUM_GENERATIONS
+ - `#define NUM_GENERATIONS 3`
+ - 세대는 0세대, 1세대, 2세대로 구분되는데 최근에 생성된 객체는 0세대(young)에 들어가고 오래된 객체일수록 2세대(old)에 존재한다.
+ - 한 객체는 단 하나의 세대에만 속한다.
+ - 한 번도 GC가 불리지 않은 객체는 0세대, 한 번 불렸지만 생존한 객체는 1세대, 두 번 이상 GC가 불렸지만 생존한 객체는 2세대에 속하게 된다.
+
+### 파이썬에서 가비지 컬렉터가 동작하는 원리
+
+```c
+>>> import gc
+>>> gc.get_threshold()
+(700, 10, 10)
+
+>>> gc.get_count() // 현재 임계값 수치
+(199, 3, 0)
+
+>>> gc.collect(0) // 특정 세대 (0 세대)의 쓰레기를 수동으로 수거
+388
+```
+
+- `threshold0` 이 의미하는 바와 `threshold1, threshold2` 가 의미하는 바가 다르다.
+ - `threshold0` 의 값은 “(number of allocations) - (number of deallocations)이 threshold를 넘으면 0세대 GC가 호출된다”를 의미한다.
+ - “객체 생성 수 - 객체 해제 수” 가 700을 넘으면 0세대 GC가 호출된다.
+ - `threshold1` 는 1세대에 대한 기준인데, 이는 “0세대 GC가 몇 번 호출되면, 1세대 GC를 호출할 것인지”를 의미한다.
+ - `threshold2` 는 “1세대 GC가 몇 번 호출되면, 2세대 GC를 호출할 것인지”를 의미한다. 다만, 2세대 GC는 매우 오랜 시간이 소요되기 때문에 아래 조건이 동시에 만족했을 때 진행된다.
+ - `long_lived_pending / long_lived_total` 이 25%를 넘을 경우
+ - `long_lived_pending` : 가비지 컬렉션을 거치지 않은 객체의 수
+ - `long_lived_total` : 존재하는 모든 객체의 수
+ -
+- 가비지 컬렉터는 0세대일수록 더 자주 가비지 컬렉션을 하도록 설계되었는데 이는 [**generational hypothesis**](http://www.memorymanagement.org/glossary/g.html#term-generational-hypothesis)에 근거한다.
+ - generational hypothesis의 두 가지 가설
+ - 대부분의 객체는 금방 도달할 수 없는 상태(unreachable)가 된다.
+ - 오래된 객체(old)에서 젊은 객체(young)로의 참조는 아주 적게 존재한다.
+- 수거할 때는 이전 세대를 현재 세대로 병합해서 진행한다.
+ - 1세대에서 collect()를 실행하면 0세대도 수거된다.
+ - 마찬가지로 2세대에서 collect()를 실행하면 0세대와 1세대도 수거된다.
+ - 그래서 2세대 GC는 오래 걸린다.
+
+### 추적에서 제외할 수 있는 객체들
+
+- 불변인 일부 컨테이너 인스턴스들은 추적 대상에서 빠질 수 있다.
+ - 튜플은 불변(immutable) 객체로 생성된 이후에 변경할 수 없음.
+ - 하지만 튜플의 항목들 중에 리스트나 딕셔너리 같은 가변(mutable) 객체가 있을 수도 있기 때문에 이를 체크한다.
+ - **tuple_object.c**
+
+ ```c
+ void
+ _PyTuple_MaybeUntrack(PyObject *op)
+ {
+ PyTupleObject *t;
+ Py_ssize_t i, n;
+
+ if (!PyTuple_CheckExact(op) || !_PyObject_GC_IS_TRACKED(op))
+ return;
+ t = (PyTupleObject *) op;
+ n = Py_SIZE(t);
+ for (i = 0; i < n; i++) {
+ PyObject *elt = **PyTuple_GET_ITEM**(t, i); // 각각 요소들 확인
+ /* Tuple with NULL elements aren't
+ fully constructed, don't untrack
+ them yet. */
+ if (!elt ||
+ _PyObject_GC_MAY_BE_TRACKED(elt)) // 추적 대상인지 타입 확인
+ return;
+ }
+ _PyObject_GC_UNTRACK(op); // 정수 타입 같은 불변 타입만 포함하고 있으면 추적 대상에서 제외한다.
+ }
+
+ ```
+
+ - 딕셔너리도 비어 있거나 불변 객체가 들어있을 경우 추적에서 제외할 수 있다.
+ - 추적하는 객체가 적을수록 빠르고 효과적인 가비지 컬렉션이 이루어진다.
+
+## 가비지 컬렉션 알고리즘
+
+1. 가비지 컬렉션 상태 GCState를 인터프리터로부터 얻는다.
+2. 가비지 컬렉터 활성화 여부를 확인한다.
+3. 가비지 컬렉터가 이미 실행 중인지 확인한다.
+4. 실행 중이지 않으면 수거 함수 collect()를 수거 상태 콜백과 함께 실행한다.
+5. 가비지 컬렉션이 완료됐다고 표시한다.
+
+```c
+/* API to invoke gc.collect() from C */
+Py_ssize_t
+PyGC_Collect(void)
+{
+ PyThreadState *tstate = _PyThreadState_GET();
+ GCState *gcstate = &tstate->interp->gc; // 1번
+
+ if (!gcstate->enabled) { // 2번
+ return 0;
+ }
+
+ Py_ssize_t n;
+ if (gcstate->collecting) { // 3번
+ /* already collecting, don't do anything */
+ n = 0;
+ }
+ else {
+ PyObject *exc, *value, *tb;
+ gcstate->collecting = 1;
+ _PyErr_Fetch(tstate, &exc, &value, &tb);
+ n = collect_with_callback(tstate, NUM_GENERATIONS - 1); // 4번
+ _PyErr_Restore(tstate, exc, value, tb);
+ gcstate->collecting = 0; // 5번
+ }
+
+ return n;
+}
+```
+
+### 수거 단계
+
+- 가비지 컬렉터는 수거 시마다 `PyGC_HEAD` 타입을 연결한 이중 연결 리스트를 이용한다.
+
+ ```c
+ /* GC information is stored BEFORE the object structure. */
+ typedef struct {
+ // Pointer to next object in the list.
+ // 0 means the object is not tracked
+ uintptr_t _gc_next;
+
+ // Pointer to previous object in the list.
+ // Lowest two bits are used for flags documented later.
+ uintptr_t _gc_prev;
+ } PyGC_Head;
+ ```
+
+> 셀 객체가 삭제될 때
+
+```c
+static void
+cell_dealloc(PyCellObject *op)
+{
+ _PyObject_GC_UNTRACK(op);
+ Py_XDECREF(op->ob_ref);
+ PyObject_GC_Del(op);
+}
+```
+
+1. `_PyObject_GC_UNTRACK` : 추적 대상에서 제외하라고 가비지 컬렉터에 요청한다.
+2. `Py_XDECREF` : 참조 카운터 감소를 위해 사용하는 표준 호출 (내부적으로 `Py_DECREF` 사용)
+3. `PyObject_GC_Del` : `gc_list_remove()` 를 호출해 가비지 컬렉션 대상 리스트에서 객체를 제거, 메모리 해제
+ - gc_list_remove()
+
+ 
+
+ ```c
+ static inline void
+ gc_list_remove(PyGC_Head *node)
+ {
+ PyGC_Head *prev = GC_PREV(node);
+ PyGC_Head *next = GC_NEXT(node);
+
+ _PyGCHead_SET_NEXT(prev, next);
+ _PyGCHead_SET_PREV(next, prev);
+
+ node->_gc_next = 0; /* object is not currently tracked */
+ }
+
+ ```
+
+### 순환 참조를 찾는 과정
+
+- 특정 세대에서 도달할 수 있는 객체(reachable)와 도달할 수 없는 객체(unreachable)를 구분하고 도달할 수 없는 객체 집합을 찾는다.
+
+1. 해당 세대의 모든 객체에 대해 참조 카운트 값 `ob→ob_refcnt`를 `ob→gc_refs`로 복사한다.
+2. 각각의 객체에서 참조하고 있는 다른 컨테이너 객체의 `gc_refs`를 감소시킨다.
+ - `subtract_refs()` 로 각 컨테이너 객체의 순회 함수를 호출 (`visit_decref()`)
+3. `gc_refs`가 0이면 그 객체는 컨테이너 집합 내부에서 자기들끼리 참조하고 있다는 뜻이다. (순환 참조)
+4. 도달할 수 없는 객체 리스트(unreachable)을 만들고 3번에 해당하는 객체들을 추가한다. 그리고 해당 객체들을 수거 대상 세대 리스트에서 모두 제거한다.
+5. unreachable 리스트로 이동되지 않은 객체들은 다음 세대로 이동한다. (생존)
+
+### 예시)
+
+```python
+a = [1]
+b = ["a"]
+c = [a, b]
+d = c
+x = [1, 2]
+x.append(x)
+e = Foo(0)
+f = Foo(1)
+e.x = f
+f.x = e
+del e
+del f
+```
+
+각 컨테이너 객체의 레퍼런스 카운트는 아래와 같다.
+
+```python
+# ref count
+[1] <- a, c = 2
+["a"] <- b, c = 2
+[a, b] <- c, d = 2
+Foo(0) <- Foo(1).x = 1
+Foo(1) <- Foo(0).x = 1
+```
+
+1번 과정에서 각 컨테이너 객체의 `gc_refs`가 설정된다.
+
+2번 과정에서 컨테이너 집합을 순회하며 `gc_refs`을 감소시킨다.
+
+```python
+[1] = 2 - 1 = 1 # [a, b]에 의해 참조당하므로 1 감소
+["a"] = 2 - 1 = 1 # [a, b]에 의해 참조당하므로 1 감소
+[a, b] = 2 # 참조당하지 않으므로 그대로
+Foo(0) = 1 - 1 = 0 # Foo(1)에 의해 참조당하므로 1 감소
+Foo(1) = 1 - 1 = 0 # Foo(0)에 의해 참조당하므로 1 감소
+```
+
+3번 과정을 통해 `gc_refs`가 0인 순환 참조 객체를 찾았다. 이 객체를 unreachable 리스트에 추가한다.
+
+```python
+ unreachable | reachable
+ | [1] = 1
+ Foo(0) = 0 | ["a"] = 1
+ Foo(1) = 0 | [a, b] = 2
+```
+
+이제 `Foo(0)`와 `Foo(1)`을 메모리에서 해제하면 가비지 컬렉션 과정이 끝난다.
+
+unreachable 리스트로 이동되지 않은 객체들은 다음 세대로 이동한다.
+
+- **deduce_unreachable 함수**
+
+ ```c
+ static inline void
+ deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable)
+ {
+ validate_list(base, collecting_clear_unreachable_clear);
+ /* Using ob_refcnt and gc_refs, calculate which objects in the
+ * container set are reachable from outside the set (i.e., have a
+ * refcount greater than 0 when all the references within the
+ * set are taken into account).
+ */
+ update_refs(base); // gc_prev is used for gc_refs
+ subtract_refs(base);
+
+ gc_list_init(unreachable);
+ move_unreachable(base, unreachable); // gc_prev is pointer again
+ validate_list(base, collecting_clear_unreachable_clear);
+ validate_list(unreachable, collecting_set_unreachable_set);
+ }
+ ```
+
+### 파이썬에서 가비지 컬렉터 API 사용하기
+
+- 디버그 모드에서 gc 모듈 활용
+
+ ```python
+ import gc
+ gc.set_debug(gc.DEBUG_STATS)
+
+ # gc: collecting generation 0...
+ # gc: objects in each generation: 2075 1755 70280
+ # gc: objects in permanent generation: 0
+ # gc: done, 926 unreachable, 0 uncollectable, 0.0004s elapsed
+ ```
+
+- `gc.DEBUG_COLLECTABLE` 을 사용하면 가비지 컬렉션 시점을 확인할 수 있다.
+
+ ```python
+ import gc
+ gc.set_debug(gc.DEBUG_COLLECTABLE)
+
+ # gc: collectable
+ # gc: collectable
+ # gc: collectable
+ # gc: collectable
+ # gc: collectable
+ ```
+
+- `gc.DEBUG_SAVEALL` 을 사용하면 수거된 객체들이 gc.garbage 리스트로 이동된다.
+
+## 미션
+
+- 원하는 라이브러리 import 해보고, 참조 카운트가 어떻게 바뀌는지 출력해보기
+
+ ```python
+ import sys
+
+ print(sys.getrefcount(1)) # 138
+ print(sys.getrefcount(2)) # 108
+ print(sys.getrefcount(3)) # 44
+
+ import matplotlib
+ print(sys.getrefcount(1)) # 1720
+ print(sys.getrefcount(2)) # 882
+ print(sys.getrefcount(3)) # 377
+ ```
+
+## References
+
+-
+-
+-
+- [http://www.arctrix.com/nas/python/gc](http://www.arctrix.com/nas/python/gc/)
+- [https://www.winterjung.dev/python-gc](https://www.winterjung.dev/python-gc/)
+
+[^1]:
+[^2]:
+[^3]:
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
diff --git a/book/images/10_parallel_and_concurrent/_main.png b/book/images/10_parallel_and_concurrent/_main.png
new file mode 100644
index 0000000..8b25aa4
Binary files /dev/null and b/book/images/10_parallel_and_concurrent/_main.png differ
diff --git a/book/images/10_parallel_and_concurrent/concurrency.png b/book/images/10_parallel_and_concurrent/concurrency.png
new file mode 100644
index 0000000..9e9e768
Binary files /dev/null and b/book/images/10_parallel_and_concurrent/concurrency.png differ
diff --git a/book/images/10_parallel_and_concurrent/mp1.png b/book/images/10_parallel_and_concurrent/mp1.png
new file mode 100644
index 0000000..21da431
Binary files /dev/null and b/book/images/10_parallel_and_concurrent/mp1.png differ
diff --git a/book/images/10_parallel_and_concurrent/mp2.png b/book/images/10_parallel_and_concurrent/mp2.png
new file mode 100644
index 0000000..9218b23
Binary files /dev/null and b/book/images/10_parallel_and_concurrent/mp2.png differ
diff --git a/book/images/10_parallel_and_concurrent/parallel.png b/book/images/10_parallel_and_concurrent/parallel.png
new file mode 100644
index 0000000..f89215c
Binary files /dev/null and b/book/images/10_parallel_and_concurrent/parallel.png differ
diff --git a/book/images/10_parallel_and_concurrent/pickle.png b/book/images/10_parallel_and_concurrent/pickle.png
new file mode 100644
index 0000000..f8cdd58
Binary files /dev/null and b/book/images/10_parallel_and_concurrent/pickle.png differ
diff --git a/book/images/10_parallel_and_concurrent/pip2.png b/book/images/10_parallel_and_concurrent/pip2.png
new file mode 100644
index 0000000..cd6a3ce
Binary files /dev/null and b/book/images/10_parallel_and_concurrent/pip2.png differ
diff --git a/book/images/10_parallel_and_concurrent/pipe.png b/book/images/10_parallel_and_concurrent/pipe.png
new file mode 100644
index 0000000..4791909
Binary files /dev/null and b/book/images/10_parallel_and_concurrent/pipe.png differ
diff --git a/book/images/10_parallel_and_concurrent/pipe2.png b/book/images/10_parallel_and_concurrent/pipe2.png
new file mode 100644
index 0000000..2acb20b
Binary files /dev/null and b/book/images/10_parallel_and_concurrent/pipe2.png differ
diff --git a/book/images/10_parallel_and_concurrent/pipe3.png b/book/images/10_parallel_and_concurrent/pipe3.png
new file mode 100644
index 0000000..fc12977
Binary files /dev/null and b/book/images/10_parallel_and_concurrent/pipe3.png differ
diff --git a/book/images/10_parallel_and_concurrent/process.png b/book/images/10_parallel_and_concurrent/process.png
new file mode 100644
index 0000000..5c59e18
Binary files /dev/null and b/book/images/10_parallel_and_concurrent/process.png differ
diff --git a/book/images/10_parallel_and_concurrent/process_thread.png b/book/images/10_parallel_and_concurrent/process_thread.png
new file mode 100644
index 0000000..1c2f17f
Binary files /dev/null and b/book/images/10_parallel_and_concurrent/process_thread.png differ
diff --git a/book/images/10_parallel_and_concurrent/queue1.png b/book/images/10_parallel_and_concurrent/queue1.png
new file mode 100644
index 0000000..002cae4
Binary files /dev/null and b/book/images/10_parallel_and_concurrent/queue1.png differ
diff --git a/book/images/10_parallel_and_concurrent/queue2.png b/book/images/10_parallel_and_concurrent/queue2.png
new file mode 100644
index 0000000..9d74e05
Binary files /dev/null and b/book/images/10_parallel_and_concurrent/queue2.png differ
diff --git a/book/images/10_parallel_and_concurrent/queue3.png b/book/images/10_parallel_and_concurrent/queue3.png
new file mode 100644
index 0000000..3883e94
Binary files /dev/null and b/book/images/10_parallel_and_concurrent/queue3.png differ
diff --git a/book/images/10_parallel_and_concurrent/queue4.png b/book/images/10_parallel_and_concurrent/queue4.png
new file mode 100644
index 0000000..cbf8ffc
Binary files /dev/null and b/book/images/10_parallel_and_concurrent/queue4.png differ
diff --git a/book/images/12_standard_library/00_library_directory.png b/book/images/12_standard_library/00_library_directory.png
new file mode 100644
index 0000000..010789c
Binary files /dev/null and b/book/images/12_standard_library/00_library_directory.png differ
diff --git a/book/images/13_test_suite/00_test_harness.png b/book/images/13_test_suite/00_test_harness.png
new file mode 100644
index 0000000..5af3295
Binary files /dev/null and b/book/images/13_test_suite/00_test_harness.png differ
diff --git a/book/images/14_c_extension/00_vscode_setting.png b/book/images/14_c_extension/00_vscode_setting.png
new file mode 100644
index 0000000..fe6a4f7
Binary files /dev/null and b/book/images/14_c_extension/00_vscode_setting.png differ
diff --git a/book/images/14_c_extension/01_vscode_setting.png b/book/images/14_c_extension/01_vscode_setting.png
new file mode 100644
index 0000000..1d0e21c
Binary files /dev/null and b/book/images/14_c_extension/01_vscode_setting.png differ
diff --git a/book/images/0_dev_env_setup/00_vscode_plugin.png b/book/images/2_settings/00_vscode_plugin.png
similarity index 100%
rename from book/images/0_dev_env_setup/00_vscode_plugin.png
rename to book/images/2_settings/00_vscode_plugin.png
diff --git a/book/images/0_dev_env_setup/01_tasks_explorer_result.png b/book/images/2_settings/01_tasks_explorer_result.png
similarity index 100%
rename from book/images/0_dev_env_setup/01_tasks_explorer_result.png
rename to book/images/2_settings/01_tasks_explorer_result.png
diff --git a/book/images/0_dev_env_setup/02_cpython_debugging.png b/book/images/2_settings/02_cpython_debugging.png
similarity index 100%
rename from book/images/0_dev_env_setup/02_cpython_debugging.png
rename to book/images/2_settings/02_cpython_debugging.png
diff --git a/book/images/3_compile/00_pgo.png b/book/images/3_compile/00_pgo.png
new file mode 100644
index 0000000..fd2f053
Binary files /dev/null and b/book/images/3_compile/00_pgo.png differ
diff --git a/book/images/4_grammar/train_diagram.svg b/book/images/4_grammar/00_train_diagram.svg
similarity index 100%
rename from book/images/4_grammar/train_diagram.svg
rename to book/images/4_grammar/00_train_diagram.svg
diff --git a/book/images/4_grammar/01_train_diagram_coffee.png b/book/images/4_grammar/01_train_diagram_coffee.png
new file mode 100644
index 0000000..477a600
Binary files /dev/null and b/book/images/4_grammar/01_train_diagram_coffee.png differ
diff --git a/book/images/4_grammar/02_train_diagram_while.jpeg b/book/images/4_grammar/02_train_diagram_while.jpeg
new file mode 100644
index 0000000..0781c3c
Binary files /dev/null and b/book/images/4_grammar/02_train_diagram_while.jpeg differ
diff --git a/book/images/4_grammar/03_train_diagram_try.png b/book/images/4_grammar/03_train_diagram_try.png
new file mode 100644
index 0000000..23dddab
Binary files /dev/null and b/book/images/4_grammar/03_train_diagram_try.png differ
diff --git a/book/images/5_config_and_input/config.png b/book/images/5_config_and_input/config.png
new file mode 100644
index 0000000..d89b6b2
Binary files /dev/null and b/book/images/5_config_and_input/config.png differ
diff --git a/book/images/5_config_and_input/input.png b/book/images/5_config_and_input/input.png
new file mode 100644
index 0000000..ee10876
Binary files /dev/null and b/book/images/5_config_and_input/input.png differ
diff --git a/book/images/5_config_and_input/interpreter_overview.png b/book/images/5_config_and_input/interpreter_overview.png
new file mode 100644
index 0000000..0334bb8
Binary files /dev/null and b/book/images/5_config_and_input/interpreter_overview.png differ
diff --git a/book/images/5_config_and_input/mermaid.png b/book/images/5_config_and_input/mermaid.png
new file mode 100644
index 0000000..4b3dbab
Binary files /dev/null and b/book/images/5_config_and_input/mermaid.png differ
diff --git a/book/images/6_rexing/00_CST_EXPR.jpg b/book/images/6_rexing/00_CST_EXPR.jpg
new file mode 100644
index 0000000..c8f6646
Binary files /dev/null and b/book/images/6_rexing/00_CST_EXPR.jpg differ
diff --git a/book/images/6_rexing/01_parse_tree.png b/book/images/6_rexing/01_parse_tree.png
new file mode 100644
index 0000000..efad4bc
Binary files /dev/null and b/book/images/6_rexing/01_parse_tree.png differ
diff --git a/book/images/6_rexing/02_Parser_Tokenizer.jpg b/book/images/6_rexing/02_Parser_Tokenizer.jpg
new file mode 100644
index 0000000..2f7f2b3
Binary files /dev/null and b/book/images/6_rexing/02_Parser_Tokenizer.jpg differ
diff --git a/book/images/6_rexing/03_Parsing_Name.jpg b/book/images/6_rexing/03_Parsing_Name.jpg
new file mode 100644
index 0000000..46b1f13
Binary files /dev/null and b/book/images/6_rexing/03_Parsing_Name.jpg differ
diff --git a/book/images/6_rexing/04_Input_to_Compiler.png b/book/images/6_rexing/04_Input_to_Compiler.png
new file mode 100644
index 0000000..81e5ef5
Binary files /dev/null and b/book/images/6_rexing/04_Input_to_Compiler.png differ
diff --git a/book/images/6_rexing/05_Grammar_Grammar.png b/book/images/6_rexing/05_Grammar_Grammar.png
new file mode 100644
index 0000000..1adfae9
Binary files /dev/null and b/book/images/6_rexing/05_Grammar_Grammar.png differ
diff --git a/book/images/6_rexing/06_Code_Objects_Properties.png b/book/images/6_rexing/06_Code_Objects_Properties.png
new file mode 100644
index 0000000..f1c9ee3
Binary files /dev/null and b/book/images/6_rexing/06_Code_Objects_Properties.png differ
diff --git a/book/images/6_rexing/07.AST_Visualization.png b/book/images/6_rexing/07.AST_Visualization.png
new file mode 100644
index 0000000..4532794
Binary files /dev/null and b/book/images/6_rexing/07.AST_Visualization.png differ
diff --git a/book/images/6_rexing/08.Node_Properties.png b/book/images/6_rexing/08.Node_Properties.png
new file mode 100644
index 0000000..f43d1e8
Binary files /dev/null and b/book/images/6_rexing/08.Node_Properties.png differ
diff --git a/book/images/6_rexing/09.Pow_Example_AST.png b/book/images/6_rexing/09.Pow_Example_AST.png
new file mode 100644
index 0000000..8ebaa8c
Binary files /dev/null and b/book/images/6_rexing/09.Pow_Example_AST.png differ
diff --git a/book/images/6_rexing/10.Pow_Example_Node_Properties.png b/book/images/6_rexing/10.Pow_Example_Node_Properties.png
new file mode 100644
index 0000000..e77ba50
Binary files /dev/null and b/book/images/6_rexing/10.Pow_Example_Node_Properties.png differ
diff --git a/book/images/7_compiler/00_compile_process.png b/book/images/7_compiler/00_compile_process.png
new file mode 100644
index 0000000..aa6295a
Binary files /dev/null and b/book/images/7_compiler/00_compile_process.png differ
diff --git a/book/images/7_compiler/01_compile_process2.png b/book/images/7_compiler/01_compile_process2.png
new file mode 100644
index 0000000..75a4a54
Binary files /dev/null and b/book/images/7_compiler/01_compile_process2.png differ
diff --git a/book/images/7_compiler/02_compile_status.png b/book/images/7_compiler/02_compile_status.png
new file mode 100644
index 0000000..fdd4777
Binary files /dev/null and b/book/images/7_compiler/02_compile_status.png differ
diff --git a/book/images/7_compiler/03_compile_instaviz.png b/book/images/7_compiler/03_compile_instaviz.png
new file mode 100644
index 0000000..a9cc5d9
Binary files /dev/null and b/book/images/7_compiler/03_compile_instaviz.png differ
diff --git a/book/images/7_compiler/04_compile_instaviz2.png b/book/images/7_compiler/04_compile_instaviz2.png
new file mode 100644
index 0000000..19dd5ad
Binary files /dev/null and b/book/images/7_compiler/04_compile_instaviz2.png differ
diff --git a/book/images/8_eval_loop/01_eval1.JPG b/book/images/8_eval_loop/01_eval1.JPG
new file mode 100644
index 0000000..85f5383
Binary files /dev/null and b/book/images/8_eval_loop/01_eval1.JPG differ
diff --git a/book/images/8_eval_loop/02_eval2.JPG b/book/images/8_eval_loop/02_eval2.JPG
new file mode 100644
index 0000000..323cfdf
Binary files /dev/null and b/book/images/8_eval_loop/02_eval2.JPG differ
diff --git a/book/images/8_eval_loop/03_eval3.JPG b/book/images/8_eval_loop/03_eval3.JPG
new file mode 100644
index 0000000..02ae3ec
Binary files /dev/null and b/book/images/8_eval_loop/03_eval3.JPG differ
diff --git a/book/images/8_eval_loop/04_eval4.JPG b/book/images/8_eval_loop/04_eval4.JPG
new file mode 100644
index 0000000..62964e5
Binary files /dev/null and b/book/images/8_eval_loop/04_eval4.JPG differ
diff --git a/book/images/8_eval_loop/05_eval5.JPG b/book/images/8_eval_loop/05_eval5.JPG
new file mode 100644
index 0000000..451938e
Binary files /dev/null and b/book/images/8_eval_loop/05_eval5.JPG differ
diff --git a/book/images/8_eval_loop/06_eval6.JPG b/book/images/8_eval_loop/06_eval6.JPG
new file mode 100644
index 0000000..be805d9
Binary files /dev/null and b/book/images/8_eval_loop/06_eval6.JPG differ
diff --git a/book/images/8_eval_loop/07_eval7.JPG b/book/images/8_eval_loop/07_eval7.JPG
new file mode 100644
index 0000000..ccd24d4
Binary files /dev/null and b/book/images/8_eval_loop/07_eval7.JPG differ
diff --git a/book/images/9_memory/01_C_memory_allocation.png b/book/images/9_memory/01_C_memory_allocation.png
new file mode 100644
index 0000000..0e8762e
Binary files /dev/null and b/book/images/9_memory/01_C_memory_allocation.png differ
diff --git a/book/images/9_memory/02_cpython_memory_hierarchy.png b/book/images/9_memory/02_cpython_memory_hierarchy.png
new file mode 100644
index 0000000..4c4d599
Binary files /dev/null and b/book/images/9_memory/02_cpython_memory_hierarchy.png differ
diff --git a/book/images/9_memory/03_arena.jpg b/book/images/9_memory/03_arena.jpg
new file mode 100644
index 0000000..845afcc
Binary files /dev/null and b/book/images/9_memory/03_arena.jpg differ
diff --git a/book/images/9_memory/04_pool.png b/book/images/9_memory/04_pool.png
new file mode 100644
index 0000000..4d8c53e
Binary files /dev/null and b/book/images/9_memory/04_pool.png differ
diff --git a/book/intro.md b/book/intro.md
index cc80f3b..fd30c38 100644
--- a/book/intro.md
+++ b/book/intro.md
@@ -1,9 +1,11 @@
# CPython Guide for PseudoLab
+가짜연구소 CPython 파헤치기 스터디팀에서 작성하는 CPython Guide 페이지 입니다.
+
### [CPython Github](https://github.com/python/cpython)
스터디에 활용되는 CPython Github 저장소 입니다.
-### [CPython 파헤치기 Notion 페이지](https://www.notion.so/chanrankim/CPython-868604c8879341808b85d4321bb07501?pvs=4)
-스터디에 진행 기록을 저장하는 Notion 페이지 입니다.
-
-가짜연구소 CPython 파헤치기 스터디팀에서 작성하는 CPython Guide 페이지 입니다.
\ No newline at end of file
+### CPython 파헤치기 Notion 페이지
+스터디 진행 기록을 저장하는 Notion 페이지 입니다.
+- [6기 (2023.03~2023.07)](https://www.notion.so/chanrankim/CPython-868604c8879341808b85d4321bb07501?pvs=4)
+- [8기 (2024.03~2024.06)](https://www.notion.so/chanrankim/Python-CPython-eb832e65c4b1443ba3b9be8d5fc5883a?pvs=4)
| |