사전 Python
구현체

Python

gabury1고친 사람 github-actions[bot]

Python 은 사람이 쓴 코드를 받아 바로 실행해 주는 프로그래밍 언어입니다. 실행 파일을 미리 만드는 단계가 없어서 고치고 바로 돌려 보는 일을 짧게 되풀이할 수 있습니다. 값의 타입은 미리 선언하지 않고 프로그램이 도는 동안 정해집니다.

쉽고 빠른 이해

Python 은 사람이 쓴 코드를 받아 바로 실행해 주는 언어입니다. 3D 도구 Blender 는 이 실행기를 프로그램 안에 넣어 두고 사용자가 쓴 스크립트를 돌립니다.

실행 파일을 따로 만들지 않으니 고쳐서 바로 돌려 보는 걸음이 짧습니다. 잘못된 값이 들어와도 프로그램이 통째로 죽는 대신 오류가 예외로 올라오고, 거치던 호출 경로가 화면에 찍힙니다.

  1. 소스 코드를 바이트코드라는 중간 형태로 옮깁니다.
  2. 가상 머신이 그 바이트코드를 한 명령씩 실행합니다.
  3. 옮겨 둔 바이트코드는 파일로 남겨 두고 다음 실행에서 다시 씁니다.

대가는 속도와 동시 실행입니다. 미리 컴파일해 둔 프로그램보다 대체로 느리게 돌고, 한 프로세스 안에서는 한 번에 한 스레드만 파이썬 코드를 돌립니다.

그래서 고치고 바로 돌려 보기를 되풀이하는 일에 고릅니다 — 원격 기계를 다루는 자동화, 수치 계산, 다른 프로그램 안에 넣는 스크립트가 그런 일입니다. 한 프로세스로 여러 코어를 채워야 하는 계산은 반대쪽입니다.

상세

이 절은 Python 이 코드를 받아 결과를 내기까지 무엇을 하는 물건인지를 끝냅니다. 보는 대상은 소스 파일 한 벌과 그것을 읽어 돌리는 실행기이고, 재는 것은 그 사이에 무엇이 끼어 있느냐입니다.

Python 은 인터프리터 방식의 대화형 객체 지향 프로그래밍 언어입니다. "인터프리터 방식"은 소스 파일을 실행 파일로 따로 만들어 두지 않고 그대로 실행할 수 있다는 뜻입니다. "대화형"은 한 줄씩 쳐 넣으면 그때그때 결과를 돌려주는 방식으로도 쓸 수 있다는 뜻입니다.

인터프리터 방식이라는 말의 경계는 흐릿합니다. Python 에는 바이트코드 컴파일러가 있어서, 컴파일하는 언어와 완전히 갈라지지는 않습니다.

언어가 갖춘 것으로는 모듈과 예외, 동적 타이핑, 아주 고수준의 동적 자료형, 클래스가 꼽힙니다. 동적 타이핑은 값의 타입을 미리 선언하지 않고 프로그램이 도는 동안 정하는 방식입니다. 이 편 맨 앞에서 말한 것이 그것입니다.

객체 지향 말고도 절차형과 함수형 같은 여러 방식의 프로그래밍을 받아 줍니다. 시스템 콜과 여러 라이브러리로 통하는 길도 있습니다. C 나 C++ 로 기능을 덧붙일 수 있습니다. 리눅스와 macOS 를 포함한 여러 유닉스 계열과 윈도우에서 돕니다.

컴파일하는 걸음이 없어서 고치고 돌리고 디버깅하는 한 바퀴가 매우 빠릅니다. 잘못된 값이 들어와도 메모리를 잘못 건드려 프로그램이 죽는 세그멘테이션 폴트가 나지는 않습니다. 실행기가 오류를 발견하면 예외를 일으키고, 프로그램이 그 예외를 잡지 않으면 거쳐 온 호출 경로를 찍어 줍니다. 대신 인터프리터 방식의 언어는 컴파일하는 언어보다 프로그램이 대체로 느리게 돕니다.

소스에서 실행까지

소스 코드는 먼저 바이트코드로 옮겨집니다. 바이트코드는 프로그램을 실행기 안에서 다루기 좋게 바꿔 놓은 중간 표현입니다. 이 중간 표현은 가상 머신 위에서 돈다고 말합니다. 가상 머신이 바이트코드 하나하나에 해당하는 기계어를 실행합니다.

flowchart TD
    S["소스 코드 · .py"] --> C["바이트코드 컴파일러"]
    C --> B["바이트코드"]
    B --> V["가상 머신"]
    C --> P[".pyc 파일에 저장"]
    P --> V

옮겨 둔 바이트코드는 .pyc 파일에 저장됩니다. 같은 파일을 두 번째로 실행할 때는 소스에서 다시 옮기는 걸음을 건너뛸 수 있어 더 빠릅니다. 대신 이 바이트코드는 다른 Python 가상 머신에서 돌 것을 기대할 수 없고, 판이 바뀌어도 그대로 쓰일 것을 기대할 수 없습니다.

모든 것이 객체다

Python 프로그램 안의 데이터는 전부 객체이거나 객체 사이의 관계로 나타납니다. 코드조차 객체로 표현됩니다. 객체 하나는 정체성과 타입과 값을 가집니다. 정체성은 한 번 만들어지면 바뀌지 않고, is 연산자가 두 객체의 정체성을 견줍니다.

객체의 타입은 그 객체가 어떤 연산을 받아 주는지를 정합니다. 타입 역시 만들어진 뒤에는 바뀌지 않습니다. 다 쓴 객체를 프로그램이 대놓고 없애는 방법은 없습니다. 아무 데서도 닿을 수 없게 되면 수거될 수 있는데, 언제 수거하는지는 구현에 맡겨져 있습니다. 닿을 수 있는 객체를 거두지만 않는다면 수거를 미루거나 아예 안 해도 됩니다.

언어와 그 구현

"Python" 이라는 이름은 언어를 가리키기도 하고 그것을 구현한 물건을 가리키기도 합니다. 언어 쪽을 기술하는 참조 문서가 따로 있는데, 문법과 어휘 분석을 뺀 나머지는 형식 명세가 아니라 영어 산문으로 적혀 있습니다. 읽기는 쉬운 대신 모호한 대목이 남습니다.

널리 쓰이는 구현은 CPython 하나입니다. 다른 구현들도 계속 지지를 얻고 있습니다. 구현 세부를 언어 문서에 너무 많이 넣는 것은 위험합니다 — 구현은 바뀔 수 있고, 같은 언어의 다른 구현은 다르게 돌 수 있기 때문입니다. 그래서 CPython 만의 특징은 짧은 구현 노트로 군데군데 끼워 두는 정도로 남겨 두었습니다. 실행하려고 내려받는 물건이 무엇인지는 아래 「갈래」 절이 받습니다.

어느 구현이든 내장 모듈과 표준 모듈을 함께 들고 옵니다. 실행기와 그 두꺼운 표준 라이브러리는 주요 플랫폼마다 소스나 바이너리로 값 없이 받을 수 있습니다. 재배포도 자유롭습니다.

포기한 것

이 절은 Python 이 안 하기로 정한 셋을 봅니다. 대상은 스레드와 타입 검사와 옛 판 호환이고, 재는 것은 무엇을 내주고 무엇을 얻었느냐입니다. 셋 다 버그가 아니라 정해 놓은 결정입니다.

한 프로세스 안의 동시 실행

CPython 은 한 번에 한 스레드만 파이썬 바이트코드를 실행하게 하는 장치를 둡니다. 이것을 GIL(Global Interpreter Lock, 전역 인터프리터 잠금)이라고 부릅니다.

flowchart TD
    T1["스레드 1"] --> L["전역 인터프리터 잠금"]
    T2["스레드 2"] --> L
    T3["스레드 3"] --> L
    L --> B["바이트코드 실행 · 한 번에 하나"]
    B -.->|입출력을 하거나 확장 모듈이 놓아줄 때| L

내준 것은 여러 코어를 동시에 쓰는 병렬성 상당 부분입니다. 얻은 것은 구현의 단순함입니다. 실행기 전체를 하나로 잠가 두면 dict 같은 핵심 내장 타입을 포함한 객체 모델이 동시 접근에 대해 저절로 안전해집니다. 실행기를 여러 스레드로 굴리기도 그만큼 쉬워집니다.

잠금을 늘 쥐고 있는 것은 아닙니다. 압축이나 해싱처럼 계산량이 많은 일을 하는 확장 모듈 가운데는 그동안 잠금을 놓도록 만들어진 것이 있습니다. 입출력을 할 때는 언제나 놓습니다.

Python 3.13 부터는 --disable-gil 빌드 설정으로 이 잠금을 끌 수 있습니다. 그렇게 빌드한 다음 -X gil=0 을 주거나 PYTHON_GIL=0 환경 변수를 두고 돌리면 됩니다. 여러 스레드를 쓰는 프로그램의 성능이 나아지고 여러 코어를 쓰기도 쉬워집니다.

이 결정을 되무르는 값도 글로 남아 있습니다. PEP(Python Enhancement Proposal, 파이썬 개선 제안) 703 은 잠금 없이 도는 빌드를 제안합니다. 그러면서 실행기를 스레드에 안전하게 만드느라 실행 부담이 늘어난다고 적습니다. 단일 스레드 프로그램의 실행 부담을 pyperformance 1.0.6 으로 쟀습니다. Intel Skylake 에서 6 퍼센트, AMD Zen 3 에서 5 퍼센트였습니다. 이 제안서는 지난 문서로 표시되어 있습니다. 최신 정본은 자유 스레딩 지원 문서입니다.

돌아가는 중의 타입 검사

Python 에는 함수 인자와 변수에 타입을 적어 두는 문법이 있습니다. def greeting(name: str) -> str: 처럼 적는 것이 그것입니다. 그런데 실행기는 이 표기를 강제하지 않습니다.

적어 둔 값은 __annotations__ 속성으로 실행 중에도 읽을 수 있지만, 읽는 것과 검사하는 것은 다릅니다. 검사는 소스 코드를 따로 훑는 바깥 도구가 맡습니다. 타입 검사기나 편집기, 린터가 그 도구입니다. 린터는 프로그램을 돌려 보지 않고 소스만 훑어서 의심스러운 대목을 짚어 주는 도구이고, 타입 검사기는 그 린터를 아주 힘세게 만든 것에 가깝습니다.

flowchart TD
    S["타입을 적어 둔 소스"] --> R["실행기"]
    S --> X["바깥 검사 도구"]
    R --> A["적어 둔 값을 읽기만 한다"]
    R --> N["검사하지 않고 그대로 실행"]
    X --> C["소스를 훑어 타입이 맞는지 검사"]

내준 것은 돌아가는 중에 타입이 틀린 것을 붙잡을 기회이고, 얻은 것은 표기를 안 붙인 코드가 그대로 돈다는 것입니다. PEP 484 는 Python 이 앞으로도 동적 타입 언어로 남는다고 못 박습니다. 타입 힌트를 의무로 만들 뜻도, 관례로라도 그럴 뜻도 없다고 적습니다.

2 에서 3 으로 넘어가는 소스 호환

Python 2.8 은 없습니다. PEP 404 가 2.8 을 "내지 않는 일정" 이라는 형식으로 적어 두고, 2.7 이 Python 2 갈래의 끝이라고 선언합니다. 2.7 에서 올라가는 공식 경로는 Python 3 입니다.

Python 3 은 언어를 처음부터 다시 그리는 것이 아니라 그때까지 자란 흠을 걷어내는 쪽이었습니다. 다만 Python 2 와의 완전한 하위 호환을 지키는 것은 처음부터 논외였습니다. 그렇다고 문법과 의미를 이유 없이 바꾸는 것도 받아들이지 않았습니다.

내준 것은 옛 코드가 손대지 않고 도는 것이고, 얻은 것은 여러 판을 함께 돌보는 부담을 내려놓은 것입니다. 여러 판 유지가 개발자들의 자원을 크게 잡아먹는다는 것이 2 계열을 2.7 에서 끝낸 이유로 적혀 있습니다. Python 2 코드는 대개 3 으로 쉽게 옮길 수 있습니다. 2to3 같은 도구가 전부 옮겨 주는 때도 있습니다. 2.7 과 3.x 양쪽에서 고치지 않고 도는 부분집합도 작지 않습니다.

예시

Python 을 끌어다 쓰는 프로그램 셋을 봅니다. 각 프로젝트의 공식 문서를 놓고, 그 프로젝트가 Python 을 어느 층에 두었는지를 읽습니다. 고른 이유까지 문서에 적어 둔 것은 셋 중 하나뿐입니다.

NumPy

NumPy 는 Python 으로 과학 계산을 하는 기본 꾸러미입니다. 여러 차원 배열 객체와 거기서 파생된 객체들, 그리고 배열을 빠르게 다루는 여러 연산을 제공합니다. 핵심에 ndarray 객체가 있고, 같은 타입의 데이터를 n 차원으로 담습니다.

왜 Python 위에 얹었는지는 그 문서가 직접 답합니다. 두 수열을 원소별로 곱하는 일을 Python 반복문으로 쓰면, 원소가 수백만 개일 때 반복문의 낭비를 그대로 치릅니다. 같은 일을 C 로 쓰면 훨씬 빠르지만 Python 으로 쓸 때의 이점을 잃고, 데이터의 차원이 늘수록 쓸 코드도 늘어납니다.

Python
c = a * b

NumPy 에서는 이 한 줄이 앞의 반복문과 같은 일을 합니다. 원소별 연산이 기본 동작이고, 그 연산은 미리 컴파일된 C 코드가 실행합니다. 문서는 이것을 양쪽의 이점을 다 가져온 것이라고 적습니다 — 코드는 Python 만큼 단순하게 두고 속도는 C 에 가깝게 냅니다.

Ansible

Ansible 은 기계 여러 대를 원격으로 다루는 자동화 도구입니다. 명령을 내리는 쪽을 제어 노드라고 부르고, 이 제어 노드에는 Python 이 설치된 유닉스 계열 기계를 거의 아무거나 쓸 수 있습니다. Red Hat, Debian, Ubuntu, macOS, BSD 계열, 그리고 윈도우의 리눅스 하위 시스템 배포판이 여기 듭니다.

다뤄지는 쪽인 관리 노드에는 Ansible 을 설치하지 않아도 됩니다. 대신 Ansible 이 만들어 보낸 Python 코드를 돌릴 Python 이 필요합니다. 모듈에 따라 예외가 있어서, 네트워크 장비를 다루는 모듈은 그 장비에 Python 을 요구하지 않습니다. Ansible 이 왜 Python 을 골랐는지는 이 문서에 적혀 있지 않습니다(미확인).

Blender

Blender 는 3D 저작 도구이고, Python 실행기를 프로그램 안에 넣어 두었습니다. 이 실행기는 Blender 가 시작할 때 올라와서 도는 내내 살아 있습니다. 사용자 인터페이스를 그리는 스크립트가 여기서 돌고, Blender 자체 도구 가운데 일부도 여기서 돕니다.

내장된 실행기는 보통의 Python 환경이라, 바깥 교재의 예제 코드도 그대로 돌아갑니다. 여기에 Blender 가 bpy 와 mathutils 같은 자기 모듈을 얹어서, 스크립트가 Blender 의 데이터와 클래스와 함수에 닿게 합니다.

flowchart TD
    subgraph BL["Blender 프로세스"]
        subgraph PY["내장 Python 실행기"]
            SC["사용자 스크립트"]
            M["bpy · mathutils"]
        end
        DATA["Blender 내부 데이터 · 클래스 · 함수"]
    end

    SC --> M
    M --> DATA
Python
import bpy
bpy.data.objects["Cube"].data.vertices[0].co.x += 1.0

이 두 줄은 "Cube" 라는 이름의 객체에 붙은 꼭짓점 하나를 옮깁니다. Blender 의 내부 데이터를 바로 고치는 것이라, 대화형 콘솔에서 치면 3D 뷰포트가 그 자리에서 바뀝니다. Blender 가 왜 Python 을 골랐는지는 이 문서에 적혀 있지 않습니다(미확인).

배경

Python 이 나오기 전에 무엇이 불편했는지를 봅니다. 1980년대 암스테르담의 한 연구소에서 쓰이던 ABC 라는 언어와 Amoeba 라는 분산 운영체제를 놓고, 그 둘이 못 해 준 일을 읽습니다.

Guido van Rossum 은 그 연구소에서 ABC 라는 교육용 언어를 만드는 일을 몇 해 했습니다. ABC 는 우아하고 힘이 셌지만 유닉스와 C 를 쓰는 세계에서는 퍼지지 못했습니다. 그 까닭은 새 원시 연산을 덧붙이기가 어려웠던 것이라고 Guido van Rossum 본인이 추측으로 적어 두었습니다. 원시 연산은 언어가 기본으로 들고 있는 가장 작은 단위의 연산입니다. ABC 는 콘솔에서 문자열을 읽고 콘솔에 문자열을 쓰는 정도의 입출력만 가진, 통짜로 닫힌 시스템이었습니다.

불평을 없애려고 ABC 라는 언어나 그 구현을 확장하는 길도 없었습니다. 그 확장 불가능함이 ABC 의 가장 큰 문제 가운데 하나였다고 적혀 있습니다.

그다음 옮겨 간 곳이 Amoeba 라는 분산 운영체제를 만드는 쪽이었습니다. 여기서는 시스템 관리 유틸리티를 C 로 쓰는 데 시간이 너무 오래 걸렸습니다. 그렇다고 Bourne 셸로 쓸 수도 없었습니다. Amoeba 는 설계가 새로워서 제공하는 원시 연산이 전통적인 셸의 것과 많이 달랐고, 셸에서는 그 시스템 콜에 쉽게 닿을 수 없었기 때문입니다. 필요한 것은 ABC 를 닮은 문법을 쓰면서 Amoeba 의 시스템 콜에 닿는 언어였습니다. Amoeba 전용으로 만드는 것은 어리석다고 보아 일반적으로 확장 가능한 언어를 만들기로 정했습니다. "C 와 셸 사이의 틈을 메운다" 가 오랫동안 이 언어의 표어였습니다.

작업은 1989년 12월 크리스마스 연휴에 시작됐습니다. 이름은 그때 즐겨 읽던 BBC 코미디 각본에서 왔습니다. 짧고 독특하면서 살짝 알쏭달쏭한 이름이 필요했습니다. 이름 짓기를 오래 붙들지 않기로 해서 맨 처음 떠오른 "Monty Python's Flying Circus" 를 골랐습니다. 이듬해에는 Amoeba 프로젝트 안에서 쓰이기 시작했고, 1991년 2월에 공개되었습니다.

갈래

같은 언어를 무엇이 구현했느냐가 이 절의 축입니다. 무엇으로 쓰였고 어느 실행 환경 위에서 도는지가 소절마다 갈리는 점입니다. 이들 각각은 참조 문서가 적어 놓은 언어와 어딘가 다르거나, 표준 문서에 없는 것을 들여옵니다.

flowchart TD
    L["Python 언어"]
    L --> N
    L --> J
    L --> D

    subgraph N["기계 위에서 바로 도는 실행기"]
        CP["CPython · C 로 구현"]
        PP["PyPy · Python 으로 구현"]
    end

    subgraph J["Java 가상 머신 위"]
        JY["Jython · Java 로 구현"]
    end

    subgraph D[".NET 위"]
        IP["IronPython · IL 을 만들어 낸다"]
        PN["Python for .NET · .NET 이 관리하는 애플리케이션"]
    end

    PN -.->|실행기로 가져다 쓴다| CP

그림 아래쪽의 .NET(Microsoft 가 만든 애플리케이션 실행 환경)에는 이름이 비슷한 둘이 함께 서 있습니다. 둘은 같은 층에 있지 않습니다. IronPython 은 Python 을 처음부터 다시 구현했고, Python for .NET 은 CPython 을 실행기로 그대로 가져다 씁니다.

CPython

원본이자 가장 많이 관리되는 구현입니다. C 로 쓰였습니다. 새 언어 기능은 대개 여기에 먼저 나타납니다.

Jython

Java 로 구현한 Python 입니다. Java 애플리케이션의 스크립트 언어로 쓰거나, Java 클래스 라이브러리를 가져다 애플리케이션을 만드는 데 쓸 수 있습니다. Java 라이브러리의 테스트를 만드는 데도 자주 쓰입니다.

IronPython

.NET 을 겨냥한 완전한 Python 구현입니다. IL(Intermediate Language, 중간 언어)을 만들어 내고, Python 코드를 .NET 어셈블리로 곧장 컴파일합니다.

Python for .NET

이름은 IronPython 과 비슷하지만 속이 다릅니다. 실행기로는 CPython 을 그대로 쓰면서, 자신은 .NET 이 관리하는 애플리케이션으로 돌아 .NET 라이브러리를 쓸 수 있게 합니다.

PyPy

전부 Python 으로 쓰인 구현입니다. 다른 구현에는 없는 기능을 몇 가지 받아 줍니다. 스택 없는 실행과 JIT(Just In Time, 적시) 컴파일러가 그것입니다. 스택 없는 실행이 무엇을 가리키는지는 참조 문서가 이름만 들고 풀지 않습니다(미확인).

관련 항목

소스가 실행되기까지 거치는 단계

인터프리터 · 바이트코드 · 가상 머신 · 컴파일러 · REPL · 표준 라이브러리

Python 을 구현한 물건

CPython · PyPy · Jython · IronPython · JIT 컴파일

다 쓴 객체를 거두는 방식

참조 카운팅 · 가비지 컬렉션 · 순환 참조 · 약한 참조 · 메모리 누수

여러 스레드가 함께 돌 때 생기는 문제

GIL · 스레드 · 멀티프로세싱 · 경쟁 조건 · 교착 상태 · 락 · 원자적 연산

값의 타입을 정하는 때와 방법

동적 타이핑 · 정적 타이핑 · 덕 타이핑 · 타입 힌트 · 타입 검사기 · 애너테이션

언어의 값을 정하는 문서

PEP · 언어 명세 · 하위 호환성 · 용어집

코드를 묶고 배포하는 단위

모듈 · 패키지 · 네임스페이스 · 가상환경 · 패키지 관리자 · C 확장 모듈

흐름을 다루는 문법

제너레이터 · 이터레이터 · 데코레이터 · 코루틴 · 이벤트 루프 · 예외 처리 · 컨텍스트 매니저

Python 을 끌어다 쓰는 제품

NumPy · Ansible · Blender · PyTorch · Django

견주어 볼 다른 프로그래밍 언어

C · Java · JavaScript · Go · Ruby · Perl

다른 이름: 파이썬