Python은 높은 수준의 추상화를 제공하는 범용 프로그래밍 언어다. 문법이 간결하고 개발 속도가 빠르며, 웹 서버부터 데이터 분석, 자동화, AI, 머신러닝까지 사용 범위가 넓다.
Python은 C++이나 Java처럼 소스 코드를 먼저 네이티브 실행 파일로 만드는 방식과는 다르다. 일반적인 CPython 환경에서는 Python 소스가 바이트코드로 컴파일된 뒤 Python 가상 머신에서 실행된다.
Python Source -> Python Compiler -> Bytecode -> Python Virtual Machine -> Execution
여기서 Python이라는 언어와 CPython이라는 구현체를 구분할 필요가 있다. 우리가 일반적으로 설치해서 사용하는 Python은 대부분 CPython이다.
Python의 특징을 이해하려면 단순한 문법보다 동적 타입, 객체 모델, 함수, iterable, 예외 처리, 모듈, 메모리 관리, 비동기 처리를 연결해서 보는 것이 좋다.
변수와 객체
Python에서는 변수를 선언할 때 타입을 적지 않는다.
name = "Player"
health = 100
speed = 3.5
다음과 같은 코드도 가능하다.
value = 100
value = "Hello"
변수의 타입이 고정되어 있는 것이 아니라 변수 이름이 객체를 참조한다.
value -> Integer Object
다시 할당하면:
value -> String Object
Python에서는 이 차이가 상당히 중요하다.
C++의 경우 변수 자체가 특정 타입의 저장 공간을 의미하는 경우가 많지만, Python에서는 이름과 객체를 분리해서 생각하는 편이 정확하다.
기본 타입
Python의 대표적인 타입은 다음과 같다.
int / float / complex / bool / str / list / tuple / set / dict / None
예를 들어:
age = 24
height = 175.5
name = "Player"
alive = True
items = [1, 2, 3]
Python의 거의 모든 값은 객체다.
정수도 객체이고 문자열도 객체이며 함수와 클래스 역시 객체로 취급된다.
value = 10
print(type(value))
결과는 다음과 같은 형태다.
<class 'int'>
동적 타입
Python은 동적 타입 언어다.
value = 10
value = "Hello"
컴파일 시점에 변수 타입을 고정하지 않는다.
대신 프로그램 실행 중 객체의 타입에 따라 연산이 결정된다.
value = 10
print(value + 20)
이런 유연성 덕분에 코드를 빠르게 작성할 수 있지만, 타입 관련 오류가 런타임에 발생할 가능성도 있다.
그래서 규모가 커진 Python 프로젝트에서는 Type Hint와 정적 분석 도구를 함께 사용하는 경우가 많다.
Type Hint
Python은 동적 타입 언어지만 타입 정보를 코드에 명시할 수 있다.
def add(a: int, b: int) -> int:
return a + b
변수에도 사용할 수 있다.
name: str = "Player"
health: int = 100
Type Hint 자체가 일반적인 CPython 실행을 정적으로 제한하는 것은 아니다.
def add(a: int, b: int) -> int:
return a + b
add("10", "20")
이 코드가 타입 힌트만으로 실행 단계에서 자동 차단되는 것은 아니다.
대신 mypy, pyright 같은 도구를 사용하면 개발 단계에서 타입 문제를 검사할 수 있다.
조건문
Python의 조건문은 중괄호 대신 들여쓰기로 블록을 구분한다.
if health > 0:
print("Alive")
else:
print("Dead")
elif를 사용해 여러 조건을 표현할 수 있다.
if health <= 0:
state = "Dead"
elif health < 30:
state = "Danger"
else:
state = "Normal"
Python에서 들여쓰기는 단순한 스타일 규칙이 아니라 문법의 일부다.
if health > 0:
print("Alive")
이런 코드는 문법적으로 잘못된 코드다.
반복문
Python에는 for와 while이 있다.
for i in range(10):
print(i)
range(10)은 0부터 9까지의 값을 순회할 수 있는 객체를 만든다.
컬렉션도 직접 순회할 수 있다.
players = ["A", "B", "C"]
for player in players:
print(player)
while은 조건이 유지되는 동안 반복한다.
while health > 0:
update()
Python의 for는 C++이나 Java의 전통적인 인덱스 기반 for와 다르게 iterable을 순회하는 방식이 중심이다.
함수
함수는 def로 정의한다.
def add(a, b):
return a + b
호출은 다음과 같다.
result = add(10, 20)
Python에서는 함수도 객체다.
def add(a, b):
return a + b
operation = add
print(operation(10, 20))
함수를 다른 변수에 저장하거나 다른 함수의 인자로 전달할 수 있다.
이런 특성이 Python의 함수형 프로그래밍 스타일과 콜백 구조의 기반이 된다.
기본 인자
Python에서는 함수 매개변수에 기본값을 지정할 수 있다.
def attack(damage=10):
print(damage)
호출할 때 값을 생략할 수 있다.
attack()
attack(50)
다만 가변 객체를 기본값으로 사용하는 것은 주의해야 한다.
def add_item(item, items=[]):
items.append(item)
return items
이런 코드는 함수 호출마다 새로운 리스트가 만들어지는 것이 아니다.
기본 인자는 함수가 정의될 때 한 번 평가되기 때문에 예상하지 못한 상태 공유가 발생할 수 있다.
보통 다음과 같이 작성한다.
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
가변 인자
Python은 여러 개의 인자를 받을 수 있다.
def add(*values):
return sum(values)
사용:
result = add(1, 2, 3, 4)
**를 사용하면 키워드 인자를 딕셔너리 형태로 받을 수 있다.
def print_info(**kwargs):
print(kwargs)
호출:
print_info(name="Player", level=10)
리스트
Python의 list는 가장 많이 사용하는 자료구조 중 하나다.
scores = [10, 20, 30]
값을 추가할 수 있다.
scores.append(40)
수정도 가능하다.
scores[0] = 100
슬라이싱도 제공한다.
scores[1:3]
리스트는 동적으로 크기가 변경되며 여러 타입의 객체를 저장할 수 있다.
values = [10, "Hello", True]
물론 실제 프로젝트에서는 하나의 리스트에 서로 다른 타입을 무분별하게 넣는 것보다 일관된 데이터 구조를 유지하는 편이 좋다.
Tuple
Tuple은 여러 값을 묶는 자료구조다.
position = (100, 200)
리스트와 달리 일반적인 tuple은 생성 후 내부 값을 변경할 수 없다.
position = (100, 200)
# position[0] = 300
불변 데이터나 여러 값을 하나의 값처럼 묶을 때 사용할 수 있다.
함수에서 여러 값을 반환할 때도 tuple이 사용된다.
def get_position():
return 100, 200
x, y = get_position()
Dictionary
Python의 dict는 키와 값을 저장하는 자료구조다.
player = {
"name": "Knight",
"health": 100,
"level": 10
}
값을 가져올 수 있다.
print(player["health"])
추가와 수정도 가능하다.
player["health"] = 50
player["exp"] = 1000
키를 이용해 빠르게 값을 찾는 자료구조가 필요할 때 자주 사용한다.
다만 Python의 dict는 단순한 JSON 비슷한 데이터 저장 용도로만 사용되는 것이 아니다. Python 내부적으로 해시 테이블 기반의 매핑 자료구조로 동작하며 다양한 코드에서 핵심적인 역할을 한다.
Set
set은 중복되지 않는 값들의 집합이다.
ids = {1, 2, 3, 3}
print(ids)
결과에는 중복된 3이 하나만 존재한다.
집합 연산도 지원한다.
a = {1, 2, 3}
b = {2, 3, 4}
print(a & b)
print(a | b)
각각 교집합과 합집합을 구할 수 있다.
중복 제거가 필요하거나 집합 기반의 연산이 필요한 경우 유용하다.
List Comprehension
Python의 대표적인 문법 중 하나다.
squares = [x * x for x in range(10)]
기존 반복문으로 작성하면:
squares = []
for x in range(10):
squares.append(x * x)
List Comprehension을 사용하면 데이터 변환 작업을 한 줄로 표현할 수 있다.
조건도 넣을 수 있다.
even_numbers = [x for x in range(10) if x % 2 == 0]
하지만 복잡한 로직까지 전부 한 줄로 밀어 넣으면 가독성이 떨어진다. 문법적으로 가능하다는 것과 좋은 코드라는 것은 별개의 문제다.
Iterable과 Iterator
Python의 for문을 제대로 이해하려면 iterable을 알아야 한다.
다음 객체들은 대표적인 iterable이다.
list / tuple / dict / set / str / range
for value in [1, 2, 3]:
print(value)
내부적으로는 iterable에서 iterator를 얻고 값을 하나씩 꺼내는 방식으로 순회한다.
개념적으로는:
Iterable -> Iterator -> next() -> Value
직접 iterator를 사용할 수도 있다.
values = [1, 2, 3]
iterator = iter(values)
print(next(iterator))
print(next(iterator))
이 구조를 이해하면 Generator와 yield도 자연스럽게 연결된다.
Generator
Generator는 값을 한 번에 전부 만들어두지 않고 필요할 때 하나씩 생성할 수 있다.
def numbers():
yield 1
yield 2
yield 3
사용:
for value in numbers():
print(value)
일반적인 return과 달리 yield를 만나면 함수 실행 상태를 유지한 채 값을 반환하고 다음 요청이 들어오면 이어서 실행한다.
대량의 데이터를 한 번에 메모리에 올리고 싶지 않을 때 유용하다.
Data Source -> Generator -> Value 1 -> Value 2 -> Value 3
파일이나 대규모 데이터 처리에서도 사용할 수 있다.
객체지향 프로그래밍
Python도 클래스를 이용한 객체지향 프로그래밍을 지원한다.
class Player:
def __init__(self, name, health):
self.name = name
self.health = health
def attack(self):
print("Attack")
객체를 생성한다.
player = Player("Knight", 100)
player.attack()
self는 현재 객체를 나타낸다.
Java나 C++처럼 this라는 키워드를 암묵적으로 사용하는 대신 Python에서는 일반적인 인스턴스 메서드의 첫 번째 매개변수로 명시한다.
상속
Python은 클래스 상속을 지원한다.
class Character:
def attack(self):
print("Character Attack")
class Player(Character):
def attack(self):
print("Player Attack")
사용:
character = Player()
character.attack()
여러 부모 클래스를 상속하는 다중 상속도 지원한다.
class A:
pass
class B:
pass
class C(A, B):
pass
Python의 다중 상속은 MRO(Method Resolution Order)를 기반으로 메서드 탐색 순서를 결정한다.
복잡한 상속 구조에서는 MRO를 이해해야 동작을 정확히 예측할 수 있다.
Duck Typing
Python에서는 객체의 구체적인 타입보다 해당 객체가 어떤 동작을 제공하는지를 중요하게 보는 경우가 많다.
class Player:
def attack(self):
print("Player")
class Monster:
def attack(self):
print("Monster")
def attack(target):
target.attack()
attack() 함수는 Player인지 Monster인지 확인하지 않는다.
attack()을 호출할 수 있으면 된다.
이런 철학을 흔히 Duck Typing이라고 표현한다.
객체의 실제 타입 -> 객체가 제공하는 동작
Python의 동적 타입 특성과 잘 맞는 방식이다.
예외 처리
Python에서는 try, except를 사용해 예외를 처리한다.
try:
value = int("abc")
except ValueError:
print("Invalid number")
여러 예외를 처리할 수도 있다.
try:
value = int(text)
except ValueError:
print("Invalid value")
except TypeError:
print("Invalid type")
예외를 직접 발생시키는 것도 가능하다.
if health < 0:
raise ValueError("Health cannot be negative")
예외가 발생했을 때 정리 작업이 필요하다면 finally를 사용할 수 있다.
try:
connect()
except Exception:
handle_error()
finally:
disconnect()
Context Manager
Python에서 리소스 관리는 with 문을 사용하는 경우가 많다.
with open("data.txt", "r") as file:
data = file.read()
블록을 벗어나면 파일을 닫는 정리 작업이 자동으로 처리된다.
직접 리소스를 열고 닫는 방식보다 안전하다.
with -> Acquire Resource -> Use Resource -> Release Resource
파일뿐만 아니라 Lock, DB Connection 등 수명 관리가 필요한 다양한 객체에 사용할 수 있다.
Module
Python에서는 .py 파일 하나가 기본적으로 하나의 모듈이 될 수 있다.
# math_util.py
def add(a, b):
return a + b
다른 파일에서:
import math_util
result = math_util.add(10, 20)
특정 함수만 가져올 수도 있다.
from math_util import add
모듈을 사용하면 코드를 여러 파일로 분리하고 기능별로 관리할 수 있다.
Package
여러 모듈을 하나의 구조로 관리할 수도 있다.
project -> main.py / game -> player.py / monster.py / item.py
다른 모듈에서:
from game.player import Player
프로젝트가 커질수록 모듈과 패키지 구조가 중요해진다.
가상환경
Python 프로젝트에서는 가상환경을 사용하는 경우가 많다.
python -m venv .venv
가상환경을 사용하면 프로젝트마다 독립적인 Python 패키지 환경을 구성할 수 있다.
Project A -> Library Version A
Project B -> Library Version B
프로젝트마다 필요한 패키지 버전이 다를 수 있기 때문에 의존성을 분리하는 것이 중요하다.
패키지 관리
Python에서는 pip가 대표적인 패키지 관리 도구다.
pip install requests
프로젝트의 의존성을 파일로 관리할 수도 있다.
requests / numpy / pandas
실제 Python 프로젝트에서는 pyproject.toml을 중심으로 패키지와 프로젝트 설정을 관리하는 방식도 많이 사용된다.
Python 생태계에서는 Poetry, uv 같은 도구도 사용된다.
Decorator
Python의 Decorator는 함수를 다른 함수로 감싸서 기능을 추가할 수 있게 한다.
def log_call(func):
def wrapper():
print("Start")
func()
print("End")
return wrapper
사용할 때는 @를 붙인다.
@log_call
def attack():
print("Attack")
실행하면:
Start -> Attack -> End
Decorator는 로깅, 권한 검사, 캐싱, 실행 시간 측정 등 공통적인 처리를 함수에 추가할 때 사용할 수 있다.
웹 프레임워크에서도 상당히 많이 사용한다.
Lambda
간단한 함수를 한 줄로 작성할 때 lambda를 사용할 수 있다.
add = lambda a, b: a + b
다만 복잡한 로직을 Lambda에 넣는 것은 가독성이 떨어진다.
실제 코드에서는 이름을 붙여야 의미가 명확한 함수라면 일반적인 def를 사용하는 편이 좋다.
메모리 관리
Python은 일반적으로 개발자가 직접 메모리를 해제하지 않는다.
CPython에서는 주로 Reference Counting과 Garbage Collector를 이용해 메모리를 관리한다.
player = Player()
other = player
객체를 두 개의 이름이 참조하고 있다.
player / other -> Player Object
참조가 모두 사라지면 해당 객체는 회수될 수 있다.
player = None
other = None
CPython에서는 참조 카운트가 0이 되는 객체를 즉시 처리할 수 있지만, 순환 참조 같은 문제를 처리하기 위해 별도의 cyclic garbage collector도 사용한다.
Mutable과 Immutable
Python에서 객체의 가변성은 상당히 중요하다.
대표적인 immutable 객체:
int / float / bool / str / tuple
대표적인 mutable 객체:
list / dict / set
예를 들어 문자열은 변경할 수 없다.
text = "Hello"
text += " World"
기존 문자열 객체를 수정하는 것이 아니라 새로운 문자열 객체를 만들고 text가 그것을 참조하게 된다.
반면 리스트는 내부 상태를 변경할 수 있다.
values = [1, 2, 3]
values.append(4)
이 차이는 함수에 객체를 전달할 때도 영향을 준다.
얕은 복사와 깊은 복사
Python에서 객체를 복사할 때는 참조 구조를 고려해야 한다.
a = [[1, 2], [3, 4]]
b = a.copy()
b 자체는 새로운 리스트지만 내부 리스트까지 복사된 것은 아니다.
a -> [1, 2] / [3, 4]
b -> [1, 2] / [3, 4]
내부 객체까지 독립적으로 복사하려면 deepcopy()를 사용할 수 있다.
import copy
b = copy.deepcopy(a)
복잡한 객체 구조에서는 이런 참조 관계를 이해하지 않고 복사하면 예상하지 못한 상태 공유가 발생할 수 있다.
비동기 프로그래밍
Python에서는 asyncio를 이용해 비동기 프로그래밍을 할 수 있다.
import asyncio
async def load_data():
await asyncio.sleep(1)
return "Data"
실행은 이벤트 루프를 통해 관리한다.
async def main():
data = await load_data()
print(data)
asyncio.run(main())
여기서 await는 현재 coroutine을 중단하고 다른 작업이 실행될 수 있도록 한다.
Coroutine A -> await -> Event Loop -> Coroutine B -> Coroutine A Resume
Node.js의 Promise와 async/await를 이해했다면 개념적으로 비슷한 부분이 있다.
다만 Python의 asyncio는 Python의 coroutine과 event loop를 기반으로 하는 별도의 실행 모델이다.
Thread와 Process
Python은 Thread와 Process를 모두 사용할 수 있다.
Thread -> 같은 Process의 메모리 공간 공유
Process -> 독립된 메모리 공간
하지만 CPython에는 GIL(Global Interpreter Lock)이 존재한다.
일반적인 CPython 구현에서는 하나의 프로세스 안에서 Python 바이트코드를 여러 스레드가 동시에 실행하는 데 제약이 있다.
그래서 CPU-bound 작업에서 단순히 Python Thread를 여러 개 만든다고 CPU 코어를 모두 활용하는 것은 아니다.
CPU 연산을 병렬화해야 한다면 multiprocessing이나 native extension, 별도의 실행 환경 등을 고려할 수 있다.
반면 I/O-bound 작업에서는 Thread가 여전히 유용할 수 있다.
Python의 실행 구조
Python을 전체적으로 보면 다음과 같은 구조로 이해할 수 있다.
Python Source -> Compiler -> Bytecode -> Python Runtime -> Execution
언어 차원에서는:
Python -> Dynamic Typing / Object / Function / Iterable / Generator / Exception / Module / Class / Decorator
실행 환경에서는:
CPython -> Reference Counting / Garbage Collector / GIL / Python Virtual Machine
비동기 환경에서는:
async def -> Coroutine -> await -> Event Loop -> I/O
이 구조를 나눠서 보면 Python의 특성을 이해하기 편하다.
Python의 장점
Python의 가장 큰 장점은 개발 속도와 생태계다.
문법이 간결하고 표준 라이브러리가 풍부하며, 외부 패키지도 매우 많다.
특히 다음 분야에서 강하다.
Web Backend / Data Analysis / AI / Machine Learning / Automation / Scripting / Scientific Computing / DevOps
AI와 데이터 분야에서 Python이 널리 사용되는 것도 언어 자체의 성능 때문이라기보다는 NumPy, PyTorch, TensorFlow, pandas 같은 생태계가 잘 구축되어 있기 때문이다.
실제 계산의 상당 부분은 C/C++ 또는 GPU 코드로 내려가고 Python은 이를 조합하고 제어하는 역할을 맡는 경우도 많다.
Python의 한계
Python은 개발 생산성이 높은 대신 C++ 같은 언어와 비교하면 일반적인 Python 코드의 실행 성능이 낮은 편이다.
특히 CPU-bound 작업에서 차이가 크게 나타날 수 있다.
C++ -> Native Machine Code -> CPU
Python -> Python Runtime -> Object / Dynamic Dispatch -> CPU
Python의 객체 모델과 동적 타입 시스템에는 그만큼 런타임 비용이 존재한다.
또한 CPython의 GIL 때문에 CPU-bound 멀티스레딩에도 제약이 있다.
그렇다고 Python이 느리기 때문에 실무에서 적합하지 않은 것은 아니다.
I/O 중심의 서버, 자동화, 데이터 처리, AI 모델 제어처럼 개발 생산성과 라이브러리 활용이 중요한 영역에서는 성능보다 전체 개발 비용이 더 중요한 경우가 많다.
Python과 C/C++
Python이 다른 언어보다 흥미로운 부분은 C/C++과 함께 사용되는 경우가 많다는 것이다.
예를 들어 Python 코드에서 NumPy의 연산을 호출한다고 해서 모든 계산이 Python 인터프리터에서 하나씩 실행되는 것은 아니다.
Python -> NumPy API -> Native C / C++ -> CPU
AI 분야에서도 비슷하다.
Python -> PyTorch -> C++ Runtime -> CUDA -> GPU
Python은 전체 시스템의 상위 레벨을 담당하고 무거운 연산은 native code나 GPU에서 처리하는 구조가 가능하다.
이것이 Python이 실행 속도 자체는 빠르지 않으면서도 고성능 데이터 처리와 AI 생태계에서 널리 사용될 수 있는 이유 중 하나다.
정리
Python을 큰 흐름으로 정리하면 다음과 같다.
기본 문법 -> Dynamic Typing -> Object -> Function -> Iterable / Iterator -> List / Dict / Set / Tuple -> Class / Duck Typing -> Module / Package -> Exception / Context Manager -> Decorator / Generator -> Memory Management -> Thread / Process -> Asyncio -> Python Runtime
Python을 처음 보면 문법이 단순해서 쉽게 느껴진다.
하지만 실제로 언어의 구조를 이해하려면 변수와 객체의 관계, mutable과 immutable, iterable과 iterator, generator, decorator, coroutine 같은 개념까지 연결해서 봐야 한다.
특히 Python에서는 변수가 객체를 직접 담는 것이 아니라 객체를 참조한다는 점, 모든 것이 객체라는 점, 함수 역시 객체라는 점이 여러 기능의 기반이 된다.
그리고 Python의 성능을 이해하려면 CPython의 실행 구조와 GIL, 객체 모델까지 내려가야 한다.
결국 Python은 동적 타입 + 객체 중심 모델 + 강력한 내장 자료구조 + 함수형 문법 + 거대한 생태계를 기반으로 개발 생산성을 높이는 언어라고 정리할 수 있다.
C++이 메모리와 하드웨어에 가까운 제어권을 제공하는 언어라면, Python은 상당 부분을 추상화하고 개발자가 문제 자체에 집중할 수 있도록 만든 언어에 가깝다.