이 글은 Python 3.12를 기준으로 작성되었다.
print는 Python에서 지원하는 출력 함수이다.
이 글에서는 CPython에서 print를 어떻게 구현하였는지 알아 볼 것 이다.
Python/bltinmodule.c
static PyObject *
builtin_print_impl(PyObject *module, PyObject *args, PyObject *sep,
PyObject *end, PyObject *file, int flush)
/*[clinic end generated code: output=3cfc0940f5bc237b input=c143c575d24fe665]*/
{
int i, err;
if (file == Py_None) {
PyThreadState *tstate = _PyThreadState_GET();
file = _PySys_GetAttr(tstate, &_Py_ID(stdout));
if (file == NULL) {
PyErr_SetString(PyExc_RuntimeError, "lost sys.stdout");
return NULL;
}
/* sys.stdout may be None when FILE* stdout isn't connected */
if (file == Py_None) {
Py_RETURN_NONE;
}
}
if (sep == Py_None) {
sep = NULL;
}
else if (sep && !PyUnicode_Check(sep)) {
PyErr_Format(PyExc_TypeError,
"sep must be None or a string, not %.200s",
Py_TYPE(sep)->tp_name);
return NULL;
}
if (end == Py_None) {
end = NULL;
}
else if (end && !PyUnicode_Check(end)) {
PyErr_Format(PyExc_TypeError,
"end must be None or a string, not %.200s",
Py_TYPE(end)->tp_name);
return NULL;
}
for (i = 0; i < PyTuple_GET_SIZE(args); i++) {
if (i > 0) {
if (sep == NULL) {
err = PyFile_WriteString(" ", file);
}
else {
err = PyFile_WriteObject(sep, file, Py_PRINT_RAW);
}
if (err) {
return NULL;
}
}
err = PyFile_WriteObject(PyTuple_GET_ITEM(args, i), file, Py_PRINT_RAW);
if (err) {
return NULL;
}
}
if (end == NULL) {
err = PyFile_WriteString("\n", file);
}
else {
err = PyFile_WriteObject(end, file, Py_PRINT_RAW);
}
if (err) {
return NULL;
}
if (flush) {
PyObject *tmp = PyObject_CallMethodNoArgs(file, &_Py_ID(flush));
if (tmp == NULL) {
return NULL;
}
Py_DECREF(tmp);
}
Py_RETURN_NONE;
}
import sys
print("hello", "world", sep="$", end="@", file=sys.stdout, flush=False)
hello$world@
PyAPI_DATA(PyObject) _Py_NoneStruct; // line 842
#define Py_None (&_Py_NoneStruct) // line 843
#define Py_RETURN_NONE return Py_None // line 850
...
// file이 None일 경우 sys.stdout으로 초기화
if (file == Py_None) {
PyThreadState *tstate = _PyThreadState_GET();
file = _PySys_GetAttr(tstate, &_Py_ID(stdout));
// file을 가져오지 못할때 error
if (file == NULL) {
PyErr_SetString(PyExc_RuntimeError, "lost sys.stdout");
return NULL;
}
// file이 None일 경우
if (file == Py_None) {
Py_RETURN_NONE;
}
}
...
출력할 file object를 지정하지 않았을 때 sys.stdout을 가져오는 로직이다.
...
// file이 None일 경우 sys.stdout으로 초기화
if (file == Py_None) {
PyThreadState *tstate = _PyThreadState_GET();
file = _PySys_GetAttr(tstate, &_Py_ID(stdout));
// file을 가져오지 못할때 error
if (file == NULL) {
PyErr_SetString(PyExc_RuntimeError, "lost sys.stdout");
return NULL;
}
// file이 None일 경우
if (file == Py_None) {
printf("stdout is None");
Py_RETURN_NONE;
}
}
...
다음과 같이 수정한 후 build한다.
import sys
sys.stdout = None
print("hello", "world", sep="$", end="@", file=sys.stdout, flush=False)
stdout is None
추가한 printf문이 작동하는 것을 확인 할 수 있다.
...
// sep이 None일 경우 null로 초기화
if (sep == Py_None) {
sep = NULL;
}
// sep이 unicode가 아닐경우 error
else if (sep && !PyUnicode_Check(sep)) {
PyErr_Format(PyExc_TypeError,
"sep must be None or a string, not %.200s",
Py_TYPE(sep)->tp_name);
return NULL;
}
...
sep을 지정하지 않았을 때 null로 초기화 한다. 또한 sep이 unicode인지 검사한다.
...
// sep이 None일 경우 null로 초기화
if (sep == Py_None) {
printf("sep is None\n");
sep = NULL;
}
// sep이 unicode가 아닐경우 error
else if (sep && !PyUnicode_Check(sep)) {
PyErr_Format(PyExc_TypeError,
"sep must be None or a string, not %.200s",
Py_TYPE(sep)->tp_name);
return NULL;
}
...
다음과 같이 수정한 후 build한다.
import sys
print("hello", "world", sep=None)
sep is None
hello world
추가한 printf문이 작동하는 것을 확인 할 수 있다.
...
// end가 None일 경우 null로 초기화
if (end == Py_None) {
end = NULL;
}
// end가 unicode가 아닐경우 error
else if (end && !PyUnicode_Check(end)) {
PyErr_Format(PyExc_TypeError,
"end must be None or a string, not %.200s",
Py_TYPE(end)->tp_name);
return NULL;
}
...
end를 지정하지 않았을 때 null로 초기화 한다. 또한 end가 unicode인지 검사한다.
...
// end가 None일 경우 null로 초기화
if (end == Py_None) {
printf("end in None\n");
end = NULL;
}
// end가 unicode가 아닐경우 error
else if (end && !PyUnicode_Check(end)) {
PyErr_Format(PyExc_TypeError,
"end must be None or a string, not %.200s",
Py_TYPE(end)->tp_name);
return NULL;
}
...
다음과 같이 수정한 후 build한다.
end in None
hello world
추가한 printf문이 작동하는 것을 확인 할 수 있다.
...
// args 길이만큼 반복
for (i = 0; i < PyTuple_GET_SIZE(args); i++) {
if (i > 0) {
// sep이 null이면 " " 추가
if (sep == NULL) {
err = PyFile_WriteString(" ", file);
}
// sep이 null이 아니면 sep 추가
else {
err = PyFile_WriteObject(sep, file, Py_PRINT_RAW);
}
// error시 종료
if (err) {
return NULL;
}
}
// args의 i번째 요소 추가
err = PyFile_WriteObject(PyTuple_GET_ITEM(args, i), file, Py_PRINT_RAW);
// error시 종료
if (err) {
return NULL;
}
}
...
args의 요소들을 file에 추가한다.
...
// args 길이만큼 반복
for (i = 0; i < PyTuple_GET_SIZE(args); i++) {
if (i > 0) {
// sep이 null일 경우 " " 추가
if (sep == NULL) {
err = PyFile_WriteString(" ", file);
}
// sep이 null이 아닐 경우 sep 추가
else {
err = PyFile_WriteObject(sep, file, Py_PRINT_RAW);
}
// error시 종료
if (err) {
return NULL;
}
}
// args의 i번째 요소 추가
err = PyFile_WriteObject(PyTuple_GET_ITEM(args, i), file, Py_PRINT_RAW);
printf("index : %d", i);
// error시 종료
if (err) {
return NULL;
}
}
...
다음과 같이 수정한 후 build한다.
print("hello", "world")
index : 0
index : 1
hello world
for 문을 반복 할때마다. 추가한 printf문이 작동한다.
...
// end가 null일 경우 "\n" 추가
if (end == NULL) {
err = PyFile_WriteString("\n", file);
}
// end가 null이 아닐 경우 end 추가
else {
err = PyFile_WriteObject(end, file, Py_PRINT_RAW);
}
// error시 종료
if (err) {
return NULL;
}
...
end를 file에 추가한다.
...
if (flush) {
PyObject *tmp = PyObject_CallMethodNoArgs(file, &_Py_ID(flush));
if (tmp == NULL) {
return NULL;
}
Py_DECREF(tmp);
}
...
flush가 True일 경우 처리한다.
print는 효율을 위해 file의 buffer에 출력할 것을 모아서 꽉 차거나 특정 경우에만 buffer를 출력한다.
하지만 flush가 True일 경우 바로바로 출력한다.
import time
print("Flush is False")
for i in range(5):
print(f'{i+1}', end=' ', flush=False)
time.sleep(1)
print('\n')
print("Flush is True:")
for i in range(5):
print(f'{i+1}', end=' ', flush=True)
time.sleep(1)
print('\nDone!')
Flush is False
1 2 3 4 5
Flush is True:
1 2 3 4 5
Done!
돌려보면 flush가 False인 쪽은 한번에 출력되고,
flush가 True인 쪽은 1초마다 출력된다.
더 깊게 다루고 싶지만 그러면 분량 조절도 힘들고 내용도 어려워서 여기까지만 하겠다.
GPT가 똑똑해져서 혼자 CPython에 대해 알아보는게 굉장히 쉬워진 것 같다.
기회가 된다면 CPython에 대해 더 다뤄보겠다.