[Python] print()

변도진·2024년 6월 17일
0

Python

목록 보기
10/14
post-thumbnail

이 글은 Python 3.12를 기준으로 작성되었다.

print

print는 Python에서 지원하는 출력 함수이다.
이 글에서는 CPython에서 print를 어떻게 구현하였는지 알아 볼 것 이다.

CPython

코드

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;
}

매개변수

  • module: 현재 모듈 객체(코드에서 사용 x)
  • args: 출력할 객체들을 담고 있는 튜플
  • sep: 객체들 사이에 삽입될 구분자 (기본값은 "", Python에서의 기본 값 None)
  • end: 출력의 끝에 추가될 문자열 (기본값은 "\n", Python에서의 기본 값 None)
  • file: 출력 스트림 (기본값은 sys.stdout, Python에서의 기본 값 None)
  • flush: 출력 버퍼를 즉시 플러시할지 여부를 나타내는 정수(Python에서의 기본 값 False)

코드

import sys
print("hello", "world", sep="$", end="@", file=sys.stdout, flush=False)

결과

hello$world@

반환

  • Py_RETURN_NONE (PyObject _Py_NoneStruct의 주소)

코드

PyAPI_DATA(PyObject) _Py_NoneStruct;  // line 842
#define Py_None (&_Py_NoneStruct) // line 843
#define Py_RETURN_NONE return Py_None // line 850

코드 해설

1. file 설정

코드

...
	// 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문이 작동하는 것을 확인 할 수 있다.

2. sep 설정

코드

...
	// 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문이 작동하는 것을 확인 할 수 있다.

3. end 설정

코드

...
	// 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문이 작동하는 것을 확인 할 수 있다.

4. file에 wirte

코드

...
	// 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문이 작동한다.

5. file에 end write

코드

...
	// 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에 추가한다.

6. flush

코드

...
	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에 대해 더 다뤄보겠다.

참조

https://github.com/python/cpython/tree/3.12

profile
낚시하고 싶다.

0개의 댓글