2편. Python 변수와 자료형 – Java보다 유연한 선언 방식 비교
📚 목차
1. Java 개발자에게 '타입'이 중요한 이유
2. Python은 타입을 어떻게 다룰까? - 동적 타이핑(Dynamic Typing)
3. Java vs Python 자료형 완전 비교
4. Python 동적 타이핑의 장점과 위험성
5. 타입 힌트(Type Hints)의 등장 - Python이 바뀌고 있다
✔ 마무리 - Python에서 타입은 '선택'이다
Python 학습의 가장 기본적인 부분이면서도 Java와 가장 큰 차이를 보이는 '변수 선언과 자료형'에 대해 다룹니다.
Java에서는 변수를 선언할 때 항상 타입을 명시해야 하지만, Python에서는 그렇지 않습니다.
이 차이가 왜 발생하며, Python의 '동적 타이핑(Dynamic Typing)'이 무엇인지, 그리고 이로 인해 얻는 장점과 발생할 수 있는 문제점은 무엇인지 상세히 알아보겠습니다.

1. Java 개발자에게 ‘타입’이 중요한 이유
Java는 강타입(Strongly Typed) 언어이자 정적 타이핑(Statically Typed) 언어입니다.
이는 변수를 선언할 때 반드시 해당 변수가 저장할 데이터의 타입을 명시해야 함을 의미하며, 이 타입 검사는 컴파일 시점에 이루어집니다.
컴파일러는 이 타입 정보를 이용하여 프로그램의 안정성과 예측 가능성을 높입니다.
✔️ Java의 변수 선언 및 자료형 예시:
// 정수형 변수 선언 및 초기화
int age = 30;
// 문자열 변수 선언 및 초기화
String name = "Alice";
// 실수형 변수 선언 및 초기화
double price = 19.99;
// 불리언 변수 선언 및 초기화
boolean isActive = true;
// 배열 선언 및 초기화
String[] colors = {"Red", "Green", "Blue"};
// List 선언 및 초기화 (제네릭으로 타입 제한)
import java.util.ArrayList;
import java.util.List;
List<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);
Java에서는 age 변수는 오직 정수만, name 변수는 오직 문자열만 저장할 수 있습니다.
만약 age = "hello";와 같이 다른 타입의 값을 할당하려 하면 컴파일 시 오류가 발생하여 프로그램이 실행되기 전에 문제를 발견할 수 있습니다.
이는 개발자가 의도하지 않은 타입의 데이터가 변수에 할당되는 것을 방지하여 런타임 오류를 줄이고 코드의 안정성을 높이는 장점이 있습니다.
✔️Java의 엄격한 타입 검사 예시 (컴파일 오류):
int count = 10;
// count = "열 개"; // 컴파일 오류: Incompatible types. Required: int, Found: String
String message = "Hello";
// int number = message; // 컴파일 오류: Incompatible types. Required: int, Found: String
// 명시적 형 변환 (Type Casting)이 필요한 경우
double value = 10.5;
int intValue = (int) value; // 명시적으로 double을 int로 변환
System.out.println(intValue); // 출력: 10
이러한 엄격한 타입 규칙 덕분에 Java는 대규모 엔터프라이즈 시스템처럼 안정성이 중요한 환경에서 널리 사용됩니다.
2. Python은 타입을 어떻게 다룰까? - 동적 타이핑(Dynamic Typing)
Python은 Java와 달리 동적 타이핑(Dynamic Typing) 언어입니다. 즉, 변수를 선언할 때 타입을 명시할 필요가 없습니다.
Python 인터프리터는 변수에 값이 할당되는 런타임(runtime) 시점에 그 값의 타입을 자동으로 결정합니다.
✔️Python의 변수 선언 및 자료형 예시:
# 정수형 변수 할당
age = 30
print(f"age: {age}, type: {type(age)}")
# 문자열 변수 할당
name = "Alice"
print(f"name: {name}, type: {type(name)}")
# 실수형 변수 할당
price = 19.99
print(f"price: {price}, type: {type(price)}")
# 불리언 변수 할당
is_active = True
print(f"is_active: {is_active}, type: {type(is_active)}")
# 리스트 할당
colors = ["Red", "Green", "Blue"]
print(f"colors: {colors}, type: {type(colors)}")
# 딕셔너리 할당
person = {"name": "Bob", "age": 25}
print(f"person: {person}, type: {type(person)}")
출력 결과
age: 30, type: <class 'int'>
name: Alice, type: <class 'str'>
price: 19.99, type: <class 'float'>
is_active: True, type: <class 'bool'>
colors: ['Red', 'Green', 'Blue'], type: <class 'list'>
person: {'name': 'Bob', 'age': 25}, type: <class 'dict'>
Python에서 age = 30이라고 할당하면, Python은 30이 정수형(int) 임을 인식하고 age 변수가 int 타입의 값을 참조하도록 합니다.
여기서 중요한 점은 Python의 변수가 실제 값을 직접 저장하는 것이 아니라, 값이 저장된 객체를 참조(reference)한다는 것입니다.
Java의 변수가 '상자'라면, Python의 변수는 '이름표'와 같습니다.
이 이름표는 언제든 다른 타입의 객체에 붙을 수 있습니다.
✔️Python에서 변수가 다른 타입의 값을 재할당할 수 있는 예시:
data = 10 # data는 int 타입의 객체를 참조
print(f"1. data: {data}, type: {type(data)}, id: {id(data)}")
data = "Hello" # 이제 data는 str 타입의 새로운 객체를 참조
print(f"2. data: {data}, type: {type(data)}, id: {id(data)}")
data = [1, 2, 3] # 이제 data는 list 타입의 새로운 객체를 참조
print(f"3. data: {data}, type: {type(data)}, id: {id(data)}")
출력 결과:
1. data: 10, type: <class 'int'>, id: 140735876403216
2. data: Hello, type: <class 'str'>, id: 1785501306352
3. data: [1, 2, 3], type: <class 'list'>, id: 1785502281856
id() 함수는 객체의 메모리 주소를 반환합니다.
위 예시에서 data 변수에 다른 타입의 값을 할당할 때마다 id() 값이 변경되는 것을 볼 수 있습니다.
이는 data라는 이름표가 다른 메모리 위치에 있는 다른 타입의 객체를 가리키도록 변경되었음을 의미합니다.
Java에서는 불가능한 이러한 '변수의 타입 변경'이 Python에서는 매우 자연스럽게 이루어집니다.
3. Java vs Python 자료형 완전 비교
Java 개발자에게 익숙한 자료형들을 Python에서는 어떻게 표현하는지 비교해 보겠습니다.
✔️ 정수형
| Java | Python |
| int, long 등 여러 타입 존재 | int 하나로 모두 표현 (자동 확장) |
| 오버플로우 주의 필요 | 제한 없이 큰 숫자 가능 |
🔸Java:
int (32비트), long (64비트), short, byte 등 크기에 따라 다양한 정수형을 제공하며, 각 타입의 범위를 넘어설 경우 오버플로우에 주의해야 합니다.
//Java 7 이상에서는 2_147_483_647처럼 정수 리터럴 안에 밑줄(_)을 사용하는 것이 가능합니다.
int javaInt = 2_147_483_647; // int의 최대값
// int overflowInt = 2_147_483_648; // 컴파일 오류 또는 런타임 오류
long javaLong = 9_223_372_036_854_775_807L; // long의 최대값
🔸Python:
단일 int 타입으로 모든 정수를 표현합니다. 내부적으로는 필요한 만큼 메모리를 동적으로 할당하여 매우 큰 정수도 제한 없이 다룰 수 있어 오버플로우 걱정이 없습니다.
python_int = 123
#Python 3.6 이상에서 도입된 기능으로, Java처럼 숫자의 가독성을 높이기 위해 도입되었습니다.
python_large_int = 9_223_372_036_854_775_807 * 100 # Java long 범위를 훨씬 초과하는 값
print(f"python_int: {python_int}, type: {type(python_int)}")
print(f"python_large_int: {python_large_int}, type: {type(python_large_int)}")
print(f"크기 비교: {python_large_int > 9_223_372_036_854_775_807}")
✔️ 문자열(String)
Java Python
| Java | Python |
| String 클래스 (불변) | str 타입 (불변) |
| +, String.format(), StringBuilder | +, f-string으로 간결하게 표현 |
🔸Java:
String 클래스를 사용하며, 불변(Immutable) 객체입니다.
문자열 연산(+, concat()) 시 새로운 String 객체가 생성됩니다. 문자열 내 변수를 삽입하려면 String.format()이나 StringBuilder를 주로 사용합니다.
String javaString = "Hello, Java!";
String combinedString = javaString + " How are you?";
String formattedString = String.format("My name is %s and I am %d years old.", "Bob", 25);
System.out.println(formattedString);
🔸Python:
str 타입으로 표현하며, 역시 불변(Immutable) 객체입니다.
문자열 연산은 Java와 유사하게 동작합니다. Python 3.6부터 도입된 f-string (Formatted String Literals)은 문자열 내 변수 삽입을 매우 편리하게 만듭니다.
python_string = "Hello, Python!"
combined_string = python_string + " How are you?"
print(f"python_string: {python_string}, type: {type(python_string)}")
print(f"combined_string: {combined_string}")
name = "Alice"
age = 30
# f-string을 사용한 문자열 포매팅 (매우 강력하고 편리)
formatted_string = f"My name is {name} and I am {age} years old."
print(formatted_string)
✔️ 부동소수점(float, double)
| Java | Python |
| float, double로 구분 | float 하나 (64비트 double과 유사) |
| 소수 연산 정밀도 이슈 존재 | 동일하게 존재 |
🔸Java:
float (단정밀도, 32비트), double (배정밀도, 64비트)을 제공합니다. 일반적으로 정밀도가 높은 double을 많이 사용합니다. 부동소수점 연산의 정밀도 문제(IEEE 754 표준)는 두 언어 모두에서 존재합니다.
float javaFloat = 3.14f; // f 접미사 필요
double javaDouble = 2.71828;
double sum = 0.1 + 0.2; // 정확히 0.3이 아닐 수 있음
System.out.println(sum); // 예: 0.30000000000000004
🔸Python:
단일 float 타입으로 부동소수점을 표현하며, 일반적으로 Java의 double과 유사한 배정밀도(64비트)입니다. Python 역시 부동소수점 정밀도 문제를 가집니다.
python_float = 3.141592
print(f"python_float: {python_float}, type: {type(python_float)}")
sum_float = 0.1 + 0.2
print(sum_float) # 예: 0.30000000000000004
✔️ 블리언(Boolean)
| Java | Python |
| true / false (소문자) | True / False (대문자) |
🔸Java:
boolean 키워드를 사용하여 true 또는 false 값을 가집니다.
boolean javaBoolean = true;
boolean isGreater = (10 > 5);
System.out.println(isGreater); // 출력: true
🔸Python:
bool 타입으로 표현하며, True 또는 False 값을 가집니다. (첫 글자가 대문자임을 주의하세요!)
python_boolean = False
print(f"python_boolean: {python_boolean}, type: {type(python_boolean)}")
is_greater = (10 > 5)
print(is_greater) # 출력: True
✔️ 리스트/배열(Lists/Arrays)
| Java | Python |
| int[], ArrayList<T>로 고정 타입 | list: 어떤 타입도 자유롭게 혼합 가능 |
🔸Java:
배열(Array): 고정된 크기를 가지며, 선언 시 타입을 명시하므로 동일한 타입의 요소만 저장할 수 있습니다.
int[] numbers = new int[5];
컬렉션(Collection) - ArrayList 등: 가변 크기를 가지며, 제네릭(List<String>)을 사용하여 저장할 요소의 타입을 제한합니다.
ArrayList<Object>를 사용하면 다양한 타입의 객체를 저장할 수 있지만, 이는 권장되지 않으며 사용 시 명시적 형 변환이 필요합니다.
// Java 배열 예시
String[] javaArray = {"apple", "banana", "cherry"};
System.out.println(javaArray[0]); // apple
// javaArray[0] = 123; // 컴파일 오류
// Java ArrayList 예시
import java.util.ArrayList;
import java.util.List;
List<String> javaStringList = new ArrayList<>();
javaStringList.add("first");
javaStringList.add("second");
System.out.println(javaStringList.get(0)); // first
// javaStringList.add(123); // 컴파일 오류
List<Object> javaObjectList = new ArrayList<>();
javaObjectList.add("mixed string");
javaObjectList.add(456);
System.out.println((String)javaObjectList.get(0)); // mixed string (형 변환 필요)
🔸Python:
list 타입은 매우 유연하며, 서로 다른 타입의 요소를 함께 저장할 수 있습니다.
크기도 동적으로 변하며, Java의 ArrayList<Object>와 유사하지만, Python에서는 이것이 기본 동작이자 일반적인 사용법입니다.
python_list = ["apple", 123, True, 3.14, "banana"] # 다른 타입의 요소를 함께 저장 가능
print(f"Original list: {python_list}")
print(f"Type of list: {type(python_list)}")
python_list.append("new item") # 동적으로 크기 변경
print(f"List after append: {python_list}")
python_list.remove(123) # 요소 제거
print(f"List after remove: {python_list}")
print(f"First element: {python_list[0]}") # 인덱스 접근
print(f"Slice from index 1 to 3: {python_list[1:4]}") # 슬라이싱
✔️ 딕셔너리/맵(Dictionary/Maps)
| Java | Python |
| Map<String, Integer> 등 타입 지정 필요 | dict: 키-값 자유롭게 선언 |
🔸Java:
Map 인터페이스를 구현한 HashMap, TreeMap 등을 사용하며, 키와 값의 타입을 제네릭으로 명시합니다.
키는 일반적으로 hashCode()와 equals() 메서드를 올바르게 구현해야 합니다.
import java.util.HashMap;
import java.util.Map;
Map<String, Integer> javaMap = new HashMap<>();
javaMap.put("Alice", 30);
javaMap.put("Bob", 25);
System.out.println(javaMap.get("Alice")); // 30
javaMap.put("Alice", 31); // 값 업데이트
System.out.println(javaMap.get("Alice")); // 31
System.out.println(javaMap.containsKey("Bob")); // true
🔸Python:
dict 타입으로 표현하며, 키-값 쌍을 저장합니다.
키는 불변(Immutable) 객체(문자열, 숫자, 튜플 등)여야 하며, 값은 어떤 타입이든 올 수 있습니다. 매우 자주 사용되는 강력한 자료구조입니다.
python_dict = {"Alice": 30, "Bob": 25, "city": "Seoul", 123: "number_key"}
print(f"Original dict: {python_dict}")
print(f"Type of dict: {type(python_dict)}")
print(f"Alice's age: {python_dict['Alice']}") # 키로 값 접근
python_dict['Alice'] = 31 # 값 업데이트
print(f"Alice's new age: {python_dict['Alice']}")
python_dict['Charlie'] = 40 # 새로운 키-값 추가
print(f"Dict after adding Charlie: {python_dict}")
del python_dict['Bob'] # 요소 삭제
print(f"Dict after deleting Bob: {python_dict}")
print(f"Keys: {python_dict.keys()}")
print(f"Values: {python_dict.values()}")
print(f"Items: {python_dict.items()}")
✔️ 튜플(Tuples) - Python 고유의 불변 시퀀스
🔸Java:
직접적인 튜플 타입은 없지만, List나 사용자 정의 클래스, 또는 Apache Commons Lang의 Pair/Triple 등으로 유사한 기능을 구현합니다.
🔸Python:
tuple은 list와 유사하지만 불변(Immutable)입니다. 한 번 생성되면 요소를 추가, 삭제, 변경할 수 없습니다.
함수에서 여러 값을 반환할 때 유용하게 사용됩니다.
my_tuple = (1, "hello", 3.14)
print(f"my_tuple: {my_tuple}, type: {type(my_tuple)}")
print(f"First element: {my_tuple[0]}")
# my_tuple[0] = 5 # TypeError: 'tuple' object does not support item assignment
# 튜플 언패킹 (unpacking)
x, y, z = my_tuple
print(f"x: {x}, y: {y}, z: {z}")
✔️ 셋(Sets) - 고유한 요소들의 집합
🔸Java:
Set 인터페이스를 구현한 HashSet, TreeSet 등을 사용하며, 중복을 허용하지 않고 순서가 없습니다.
import java.util.HashSet;
import java.util.Set;
Set<String> javaSet = new HashSet<>();
javaSet.add("apple");
javaSet.add("banana");
javaSet.add("apple"); // 중복은 무시됨
System.out.println(javaSet); // [banana, apple] (순서 없음)
🔸Python:
set 타입은 수학의 집합과 동일하게 중복된 요소를 허용하지 않고 순서가 없습니다. 집합 연산(합집합, 교집합, 차집합)에 매우 효율적입니다.
python_set = {"apple", "banana", "apple", "cherry"}
print(f"python_set: {python_set}, type: {type(python_set)}") # {'apple', 'banana', 'cherry'}
python_set.add("grape")
print(f"Set after adding grape: {python_set}")
set1 = {1, 2, 3}
set2 = {3, 4, 5}
print(f"Union: {set1.union(set2)}") # {1, 2, 3, 4, 5}
print(f"Intersection: {set1.intersection(set2)}") # {3}
4. Python 동적 타이핑의 장점과 위험성
Python의 동적 타이핑은 개발자에게 많은 편리함을 제공하지만, 동시에 몇 가지 주의할 점도 있습니다.
✔️ 장점
🔸코드 간결성 및 생산성: 변수 선언 시 타입을 명시할 필요가 없어 코드가 짧아지고 타이핑 양이 줄어들어 개발 속도가 빨라집니다. 특히 스크립트 작성이나 빠른 프로토타이핑에 매우 유리합니다.
Java: String name = "Alice";
Python: name = "Alice
🔸높은 유연성: 하나의 변수가 런타임에 여러 타입의 데이터를 참조할 수 있어, 유연한 코드 작성이 가능합니다.
예를 들어, 리스트나 딕셔너리에 다양한 타입의 요소를 쉽게 저장하고 처리할 수 있습니다. 이는 다양한 데이터 형태를 다루는 데이터 분석이나 웹 개발에서 큰 이점으로 작용합니다.
🔸빠른 프로토타이핑 및 실험: 아이디어를 빠르게 코드로 옮겨 실행해 보고 결과를 확인할 수 있어, 초기 개발 단계나 아이디어 검증에 매우 효율적입니다.
✔️ 단점
🔸런타임 오류 가능성 증가: 타입 관련 오류(예: 존재하지 않는 메서드 호출, 잘못된 타입의 값으로 연산)가 컴파일 시점이 아닌, 프로그램이 실행되는 런타임에 발생합니다. 이는 오류를 발견하고 디버깅하는 데 더 많은 시간과 노력이 필요하게 만들 수 있습니다.
특히 대규모 프로젝트에서는 심각한 문제를 야기할 수 있습니다.
def greet(name):
# name이 문자열이라고 가정하지만, 실제로는 다를 수 있음
print("Hello, " + name)
greet("Alice") # OK
# greet(123) # 런타임 오류 발생: TypeError: can only concatenate str (not "int") to str
🔸코드 가독성 및 유지보수성 저하: 변수의 타입을 코드만 보고는 명확히 알기 어려워, 다른 개발자(또는 미래의 자신)가 코드를 이해하고 수정하기 어렵게 만들 수 있습니다. 이는 특히 대규모 프로젝트나 협업 환경에서 코드의 유지보수 비용을 증가시킵니다.
🔸성능 오버헤드: 인터프리터가 런타임에 변수의 타입을 추론하고 관리해야 하므로, 정적 타입 언어에 비해 약간의 성능 오버헤드가 발생할 수 있습니다. 이는 특히 타입 검사가 자주 필요한 복잡한 연산에서 두드러질 수 있습니다.
5. 타입 힌트(Type Hint)의 등장 – Python이 바뀌고 있다
Python 3.5 버전부터는 이러한 동적 타이핑의 단점을 보완하고 코드의 가독성 및 유지보수성을 높이기 위해 타입 힌트(Type Hints) 기능이 도입되었습니다.
타입 힌트는 변수나 함수의 매개변수, 반환 값 등에 개발자가 원하는 타입 정보를 명시할 수 있도록 해줍니다. 이는 Python 인터프리터에 의해 강제되지 않고, 주로 코드 분석 도구(MyPy 등), IDE(PyCharm, VS Code) 및 린터(linter)가 개발자에게 타입 관련 오류를 경고하거나 코드 자동 완성 기능을 제공하는 데 활용됩니다.
from typing import List, Union, Optional
def calculate_average(numbers: List[float]) -> float:
"""부동소수점 숫자 리스트의 평균을 계산하는 함수"""
if not numbers:
return 0.0
return sum(numbers) / len(numbers)
def process_data(data: Union[str, int]) -> str:
"""문자열 또는 정수 데이터를 처리하여 문자열로 반환하는 함수"""
if isinstance(data, int):
return f"Processed number: {data * 2}"
return f"Processed string: {data.upper()}"
def get_user_name(user_id: int) -> Optional[str]:
"""사용자 ID에 해당하는 이름을 반환 (없을 수 있음)"""
if user_id == 1:
return "Alice"
return None
# 타입 힌트와 일치하는 호출
avg_result = calculate_average([10.5, 20.0, 30.5])
print(f"Average: {avg_result}, type: {type(avg_result)}")
processed_str = process_data("hello world")
print(f"Processed string: {processed_str}")
processed_num = process_data(123)
print(f"Processed number: {processed_num}")
user1_name = get_user_name(1)
print(f"User 1 name: {user1_name}")
user2_name = get_user_name(2)
print(f"User 2 name: {user2_name}")
# 타입 힌트에 맞지 않는 호출 (런타임 오류는 발생하지 않지만, 타입 체커가 경고를 줌)
# calculate_average("not a list") # MyPy: Argument "numbers" has incompatible type "str"; expected "List[float]"
# process_data([1, 2]) # MyPy: Argument "data" has incompatible type "List[int]"; expected "Union[str, int]"
Java 개발자에게는 이러한 타입 힌트가 익숙하고 반가울 것입니다.
타입 힌트를 사용하면 Python 코드의 가독성과 유지보수성이 향상되며, 런타임 오류를 사전에 방지하는 데 도움을 줄 수 있습니다.
이는 특히 협업 환경이나 대규모 프로젝트에서 코드 품질을 높이는 데 기여합니다.
✔ 마무리 - Python에서 타입은 ‘선택’이다
Java와 달리 Python은 타입을 명시하지 않아도 코드가 실행됩니다.
이는 Python의 설계 철학, 즉 “개발자에게 자유를 주되, 책임은 스스로 져야 한다”는 철학을 반영한 것입니다.
하지만 자유로운 만큼, 명확한 코드 작성 습관과 타입 힌트 도입은 매우 중요합니다.
Python의 타입 힌트는 단순한 보조 기능이 아니라, 언어 자체가 대규모 개발에도 적합하도록 진화하고 있다는 증거입니다.
📌 요약
🔸Python은 동적 타이핑 언어로, 변수 선언 시 타입 명시가 필요 없다.
🔸 변수는 값이 아닌 객체를 참조하는 이름표이며, 언제든 다른 타입의 객체로 변경 가능하다.
🔸 자유로운 타입 처리는 생산성과 유연성을 높이지만, 오류 탐지 지연이라는 단점도 있다.
🔸 타입 힌트를 통해 코드 안정성과 가독성을 높일 수 있다.
※ 게시된 글 및 이미지 중 일부는 AI 도구의 도움을 받아 생성되거나 다듬어졌습니다.
'3.SW개발 > Python' 카테고리의 다른 글
| [Java관점]4편. Python 함수와 메서드 – 호출 방식과 선언 구조 비교 (0) | 2025.11.11 |
|---|---|
| [Java관점]3편. Python 조건문과 반복문 – Java와 다른 제어 구조 한눈에 보기 (1) | 2025.11.11 |
| [Java관점]1편. Python 설치와 구조 이해 – Java 개발자를 위한 입문 가이드 (0) | 2025.11.10 |
| [Python요약]7. Python 자료형 정리 : List, Tuple, Dictionary, Set, Sequence, Range (3) | 2025.11.05 |
| [Python요약]6. Python 병렬 처리 : GIL, threading, asyncio, multiprocessing (0) | 2025.11.05 |