21. JSON 다루기
로봇 설정이나 웨이포인트 파일은 JSON 형식으로 저장하는 경우가 많습니다.
예를 들어 다음과 같은 JSON 파일이 있다고 가정합니다.
{
"robot_name": "delivery_robot",
"max_speed": 1.2,
"waypoints": [
[0.0, 0.0],
[1.0, 0.0],
[1.0, 1.0]
]
}
Python에서 JSON을 읽으려면 json 모듈을 사용합니다.
import json
with open("config.json", "r") as file:
config = json.load(file)
print(config["robot_name"])
print(config["max_speed"])
print(config["waypoints"])
Python 딕셔너리를 JSON 파일로 저장할 수도 있습니다.
import json
config = {
"robot_name": "delivery_robot",
"max_speed": 1.2,
"waypoints": [
[0.0, 0.0],
[1.0, 0.0],
[1.0, 1.0]
]
}
with open("config.json", "w") as file:
json.dump(config, file, indent=4)
한글이 들어간 JSON을 저장할 때는 다음처럼 쓰는 것이 좋습니다.
with open("config.json", "w", encoding="utf-8") as file:
json.dump(config, file, indent=4, ensure_ascii=False)
a. open("config.json", "w", encoding="utf-8")
open("config.json", "w", encoding="utf-8")
config.json 파일을 엽니다.
- "config.json": 저장할 파일 이름
- "w": 쓰기 모드
- encoding="utf-8": 한글이 깨지지 않도록 UTF-8 사용
주의할 점은 "w" 모드는 기존 파일이 있으면 내용을 지우고 새로 씁니다.
config['hangul_test']="한글입력"
입력하고 아래의 명령어로 파일을 저장합니다.
with open("config_han.json", "w", encoding="utf-8") as file:
json.dump(config, file, indent=4)
config_han.json 파일을 노트북에서 열어 보면 한글이 저장됨을 확인할 수 있습니다. jupyter_ws폴더로 이동하여 gedit로 config_han.json을 열어보면 한글 부분에 unicode 문자가 저장되어 있습니다.
b. with ... as file
with open(...) as file:
파일을 열고 작업이 끝나면 자동으로 닫아줍니다.
원래는 파일을 열면 마지막에 이렇게 닫아야 합니다.
file.close()
하지만 with를 쓰면 자동으로 닫히기 때문에 더 안전합니다.
c. json.dump(config, file, ...)
json.dump(config, file, indent=4, ensure_ascii=False)
config 데이터를 JSON 형식으로 바꿔서 파일에 저장합니다.
예를 들어:
config = {
"name": "드론",
"speed": 10
}
이라면 config.json 파일에 이렇게 저장됩니다.
{
"name": "드론",
"speed": 10
}
d. indent=4
indent=4
JSON 파일을 보기 좋게 들여쓰기합니다. 없으면 한 줄로 저장될 수 있습니다.
{"name":"드론","speed":10}
indent=4를 쓰면 보기 좋게 저장됩니다.
{
"name": "드론",
"speed": 10
}
e. ensure_ascii=False
ensure_ascii=False
한글을 그대로 저장하게 합니다.
이 옵션이 없으면 한글이 이런 식으로 저장될 수 있습니다.
"\ub4dc\ub860"
ensure_ascii=False를 쓰면 이렇게 저장됩니다.
"드론"
22. None
None은 값이 없다는 뜻입니다.
target = None
아직 목표 지점이 없거나, 센서 값이 유효하지 않을 때 사용할 수 있습니다.
target_position = None
if target_position is None:
print("No target position")
else:
print("Move to target")
None을 비교할 때는 보통 ==보다 is를 사용합니다.
if target_position is None:
print("target is none")
함수에서 실패를 표시할 때도 사용할 수 있습니다.
def find_nearest_obstacle(ranges):
valid_ranges = [r for r in ranges if r > 0]
if len(valid_ranges) == 0:
return None
return min(valid_ranges)
nearest = find_nearest_obstacle([-1, -1, -1])
if nearest is None:
print("No valid obstacle data")
else:
print(f"nearest obstacle: {nearest}")
23. Boolean 판단 규칙
Python에서는 값 자체가 참 또는 거짓처럼 평가될 수 있습니다.
다음 값들은 거짓으로 판단됩니다.
False
None
0
0.0
""
[]
{}
()
예를 들어 빈 리스트는 False처럼 동작합니다.
waypoints = []
if not waypoints:
print("No waypoints")
값이 있으면 True처럼 동작합니다.
waypoints = [[0.0, 0.0], [1.0, 0.0]]
if waypoints:
print("Waypoint list exists")
로봇 경로, 센서 데이터, 설정값 검사에서 자주 사용됩니다.
24. in 연산자
in은 어떤 값이 포함되어 있는지 확인할 때 사용합니다.
modes = ["manual", "auto", "emergency"]
if "auto" in modes:
print("auto mode supported")
딕셔너리에서는 key가 있는지 확인할 때 사용합니다.
config = {
"max_speed": 1.0,
"wheel_radius": 0.05
}
if "max_speed" in config:
print(config["max_speed"])
문자열에서도 사용할 수 있습니다.
message = "robot emergency stop"
if "emergency" in message:
print("Emergency message detected")
25. min(), max(), sum()
센서 데이터 처리에 매우 자주 쓰입니다.
ranges = [1.2, 0.8, 2.0, 0.5]
print(min(ranges)) # 0.5
print(max(ranges)) # 2.0
print(sum(ranges)) # 4.5
평균값 계산:
average = sum(ranges) / len(ranges)
print(average)
라이다 거리 중 가장 가까운 장애물을 찾을 때:
lidar_ranges = [2.0, 1.5, 0.9, 0.7, 1.2]
nearest_obstacle = min(lidar_ranges)
print(f"nearest obstacle: {nearest_obstacle} m")
단, 빈 리스트에 min()을 사용하면 에러가 납니다.
ranges = []
if ranges:
nearest = min(ranges)
print(nearest)
else:
print("No range data")
26. 정렬
리스트를 정렬할 때는 sort() 또는 sorted()를 사용합니다.
data = [3, 1, 4, 2]
data.sort()
print(data)
출력:
[1, 2, 3, 4]
sort()는 원본 리스트를 직접 변경합니다.
반면 sorted()는 정렬된 새 리스트를 반환합니다.
data = [3, 1, 4, 2]
sorted_data = sorted(data)
print(data)
print(sorted_data)
딕셔너리 리스트도 정렬할 수 있습니다.
robots = [
{"name": "robot_a", "battery": 80},
{"name": "robot_b", "battery": 50},
{"name": "robot_c", "battery": 95}
]
robots_sorted = sorted(robots, key=lambda robot: robot["battery"])
print(robots_sorted)
sorted() 함수는 robots 리스트를 정렬합니다. 이때 key 옵션에 lambda robot: robot["battery"]를 넣었기 때문에, 각 로봇 딕셔너리의 "battery" 값을 기준으로 정렬합니다.
배터리 값은 robot_b가 50, robot_a가 80, robot_c가 95이므로 작은 값부터 정렬하면 다음과 같은 순서가 됩니다.
[
{"name": "robot_b", "battery": 50},
{"name": "robot_a", "battery": 80},
{"name": "robot_c", "battery": 95}
]
마지막으로 print(robots_sorted)는 정렬된 결과를 화면에 출력합니다. 원본 robots 리스트는 그대로 두고, 정렬된 새 리스트가 robots_sorted에 저장됩니다.
배터리가 높은 순서로 정렬(내림차순)하려면 reverse=True를 사용합니다.
robots_sorted = sorted(
robots,
key=lambda robot: robot["battery"],
reverse=True
)
print(robots_sorted)
27. 람다 함수
lambda는 이름 없는 짧은 함수입니다.
double = lambda x: x * 2
print(double(3))
하지만 로봇 개발 초보 단계에서는 lambda를 남발하지 않는 것이 좋습니다. 주로 sorted()의 key처럼 간단한 기준을 줄 때 사용합니다.
points = [
{"x": 1.0, "y": 2.0},
{"x": 0.5, "y": 1.5},
{"x": 2.0, "y": 0.5}
]
points_sorted = sorted(points, key=lambda p: p["x"])
print(points_sorted)
28. zip()
zip()은 여러 리스트를 동시에 묶어서 반복할 때 사용합니다.
left_speeds = [1.0, 1.1, 1.2]
right_speeds = [1.0, 1.0, 1.1]
for left, right in zip(left_speeds, right_speeds):
print(f"left={left}, right={right}")
출력:
left=1.0, right=1.0
left=1.1, right=1.0
left=1.2, right=1.1
시간과 센서 데이터를 묶을 수도 있습니다.
times = [0.0, 0.1, 0.2]
distances = [1.5, 1.4, 1.3]
for t, d in zip(times, distances):
print(f"time={t}, distance={d}")
29. enumerate()
enumerate()는 반복하면서 인덱스도 같이 얻을 때 사용합니다.
waypoints = [
[0.0, 0.0],
[1.0, 0.0],
[1.0, 1.0]
]
for index, point in enumerate(waypoints):
print(f"{index}: {point}")
0: [0.0, 0.0]
1: [1.0, 0.0]
2: [1.0, 1.0]
시작 인덱스를 1로 바꿀 수도 있습니다.
for index, point in enumerate(waypoints, start=1):
print(f"waypoint {index}: {point}")
출력:
waypoint 1: [0.0, 0.0]
waypoint 2: [1.0, 0.0]
waypoint 3: [1.0, 1.0]
30. 언패킹
리스트나 튜플의 값을 여러 변수에 한 번에 넣을 수 있습니다.
position = [1.0, 2.0, 0.5]
x, y, yaw = position
print(x)
print(y)
print(yaw)
웨이포인트를 처리할 때도 좋습니다.
waypoint = [3.0, 4.0]
x, y = waypoint
print(f"x={x}, y={y}")
함수 반환값에서도 자주 사용합니다.
def get_velocity():
linear_x = 0.5
angular_z = 0.1
return linear_x, angular_z
linear_x, angular_z = get_velocity()
print(linear_x)
print(angular_z)
31. 가변 인자 *args
함수에 여러 개의 값을 유동적으로 전달하고 싶을 때 *args를 사용합니다. 개수는 정해지지 않아도 됩니다.
def print_values(*args):
for value in args:
print(value)
print_values(1, 2, 3)
print_values("x", "y", "z")
센서 값을 여러 개 받아 평균을 구할 수도 있습니다.
def average(*values):
if not values:
return 0.0
return sum(values) / len(values)
print(average(1.0, 2.0, 3.0))
32. 키워드 가변 인자 **kwargs
**kwargs는 이름이 있는 여러 인자를 딕셔너리 형태로 받습니다.
def print_config(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_config(max_speed=1.0, wheel_radius=0.05, use_lidar=True)
출력:
max_speed: 1.0
wheel_radius: 0.05
use_lidar: True
설정값을 유연하게 받는 함수에서 사용할 수 있습니다.
def create_robot_config(**kwargs):
config = {
"max_speed": 1.0,
"wheel_radius": 0.05,
"use_lidar": False
}
config.update(kwargs)
return config
config = create_robot_config(max_speed=1.5, use_lidar=True)
print(config)
33. 얕은 복사와 깊은 복사
리스트나 딕셔너리를 다룰 때 매우 중요합니다.
a = [1, 2, 3]
b = a
b[0] = 100
print(a)
print(b)
출력:
[100, 2, 3]
[100, 2, 3]
b = a는 복사가 아니라 같은 리스트를 가리키는 것입니다.
진짜 복사하려면 다음처럼 합니다.
a = [1, 2, 3]
b = a.copy()
b[0] = 100
print(a)
print(b)
출력:
[1, 2, 3]
[100, 2, 3]
하지만 중첩 리스트에서는 문제가 생길 수 있습니다.
path_a = [[0, 0], [1, 1]]
path_b = path_a.copy()
path_b[0][0] = 100
print(path_a)
print(path_b)
중첩 구조까지 완전히 복사하려면 copy.deepcopy()를 사용합니다.
import copy
path_a = [[0, 0], [1, 1]]
path_b = copy.deepcopy(path_a)
path_b[0][0] = 100
print(path_a)
print(path_b)
로봇 경로, 설정 딕셔너리, 센서 데이터 버퍼를 복사할 때 이 차이를 모르면 버그가 납니다.
34. 타입 힌트
타입 힌트는 변수나 함수 인자의 자료형을 표시하는 문법입니다.
def calculate_speed(distance: float, time: float) -> float:
return distance / time
이 부분은 함수가 결과로 float 값을 반환한다는 의미입니다. 즉, 계산 결과가 소수점 숫자일 수 있다는 뜻입니다.
Python은 타입 힌트를 강제로 검사하지는 않습니다. 하지만 코드를 읽기 쉬워지고, IDE나 코드 분석 도구가 실수를 더 잘 잡아줍니다.
로봇 개발에서는 타입 힌트를 쓰는 것이 좋습니다.
def limit_speed(speed: float, max_speed: float) -> float:
if speed > max_speed:
return max_speed
if speed < -max_speed:
return -max_speed
return speed
리스트 타입도 표시할 수 있습니다.
def average(values: list[float]) -> float:
if not values:
return 0.0
return sum(values) / len(values)
딕셔너리 타입도 표시할 수 있습니다.
def print_robot_config(config: dict[str, float]) -> None:
for key, value in config.items():
print(key, value)
None을 반환하는 함수는 -> None으로 표시합니다.
def stop_robot() -> None:
print("stop")
35. dataclass
클래스 문법 자체는 별도 주제지만, Python에서 설정값이나 데이터를 묶을 때 dataclass는 매우 유용합니다.
from dataclasses import dataclass
@dataclass
class RobotPose:
x: float
y: float
yaw: float
사용 예시는 다음과 같습니다.
pose = RobotPose(x=1.0, y=2.0, yaw=0.5)
print(pose.x)
print(pose.y)
print(pose.yaw)
일반 클래스보다 훨씬 간단하게 데이터 구조를 만들 수 있습니다.
로봇 설정값에도 사용할 수 있습니다.
from dataclasses import dataclass
@dataclass
class MotorConfig:
motor_id: int
gear_ratio: float
encoder_resolution: int
max_rpm: float
config = MotorConfig(
motor_id=1,
gear_ratio=30.0,
encoder_resolution=4096,
max_rpm=3000.0
)
print(config)
출력 예시:
MotorConfig(motor_id=1, gear_ratio=30.0, encoder_resolution=4096, max_rpm=3000.0)
딕셔너리보다 구조가 명확하고, 오타를 줄일 수 있습니다.
36. with 문
with는 자원을 안전하게 열고 닫을 때 사용합니다.
가장 대표적인 예는 파일입니다.
with open("log.txt", "w") as file:
file.write("robot started\n")
with를 사용하면 파일을 직접 close()하지 않아도 됩니다.
로봇 개발에서는 로그 파일, 설정 파일, 데이터 기록 파일을 자주 다루므로 with 문은 필수입니다.
나쁜 예:
file = open("log.txt", "w")
file.write("data\n")
file.close()
좋은 예:
with open("log.txt", "w") as file:
file.write("data\n")
37. 모듈과 import: 기능 가져오기
Python은 필요한 기능을 가져와서 씁니다.
import math
angle = math.radians(90)
print(angle)
출력:
1.5707963267948966
자주 쓰는 기본 모듈:
| math | 수학 계산 |
| time | 시간 지연, 시간 측정 |
| random | 랜덤값 |
| os | 파일, 폴더 경로 |
| csv | CSV 파일 처리 |
| json | JSON 파일 처리 |
시간 지연 예제:
import time
print("모터 ON")
time.sleep(1.0)
print("1초 후 모터 OFF")
로봇 제어 루프 예제:
import time
while True:
print("센서 읽기")
print("제어 계산")
print("명령 출력")
time.sleep(0.1)
time.sleep(0.1)은 0.1초 대기입니다.
즉, 대략 10Hz 루프입니다.
38. 모듈 실행 진입점 if __name__ == "__main__"
Python 파일을 직접 실행할 때만 특정 코드가 실행되게 만들 수 있습니다.
def main():
print("robot program start")
if __name__ == "__main__":
main()
이 구조는 매우 중요합니다.
예를 들어 robot_utils.py 파일이 있다고 가정합니다.
def calculate_speed(distance, time):
return distance / time
def main():
print("test")
print(calculate_speed(10, 2))
if __name__ == "__main__":
main()
이 파일을 직접 실행하면 main()이 실행됩니다.
python3 robot_utils.py
하지만 다른 파일에서 import하면 main()은 실행되지 않습니다.
from robot_utils import calculate_speed
speed = calculate_speed(5, 2)
print(speed)
로봇 프로젝트에서는 테스트 코드와 실제 재사용 함수를 분리할 때 꼭 필요합니다.
39. assert
assert는 조건이 맞는지 검사하는 문법입니다.
speed = 1.0
assert speed >= 0.0
조건이 거짓이면 에러가 발생합니다.
speed = -1.0
assert speed >= 0.0
메시지를 추가할 수도 있습니다.
speed = -1.0
assert speed >= 0.0, "speed must be positive"
로봇 코드에서는 계산 함수 테스트에 유용합니다.
def clamp(value, min_value, max_value):
if value < min_value:
return min_value
if value > max_value:
return max_value
return value
assert clamp(5, 0, 10) == 5
assert clamp(-1, 0, 10) == 0
assert clamp(15, 0, 10) == 10
단, assert를 안전 로직으로 사용하면 안 됩니다. 실제 로봇 정지, 충돌 방지, 비상 정지 같은 기능은 명시적인 조건문으로 처리해야 합니다.
40. 유용한 표준 라이브러리
Python에는 기본으로 제공되는 라이브러리가 많습니다. 로봇 개발에서 자주 쓰는 것들은 다음과 같습니다.
a. math
수학 계산에 사용합니다.
import math
angle = math.pi / 2
print(math.sin(angle))
print(math.cos(angle))
거리 계산:
import math
def distance_2d(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
return math.sqrt(dx * dx + dy * dy)
print(distance_2d(0, 0, 3, 4))
또는 hypot()을 사용할 수 있습니다.
import math
distance = math.hypot(3, 4)
print(distance) # 5.0
b. time
시간 지연이나 현재 시간 확인에 사용합니다.
import time
print("start")
time.sleep(1.0)
print("after 1 second")
현재 시간:
import time
now = time.time()
print(now)
간단한 주기 실행:
import time
period = 0.1
for i in range(10):
start_time = time.time()
print(f"loop {i}")
elapsed = time.time() - start_time
sleep_time = period - elapsed
if sleep_time > 0:
time.sleep(sleep_time)
c. os
파일 경로, 폴더 확인, 환경 변수 등에 사용합니다.
import os
print(os.getcwd())
파일 존재 확인:
import os
if os.path.exists("config.json"):
print("config exists")
else:
print("config not found")
폴더 생성:
import os
os.makedirs("logs", exist_ok=True)
d. pathlib
파일 경로를 다룰 때 os.path보다 보기 좋습니다.
from pathlib import Path
log_dir = Path("logs")
log_dir.mkdir(exist_ok=True)
log_file = log_dir / "robot_log.txt"
with open(log_file, "w") as file:
file.write("robot started\n")
파일 존재 확인:
from pathlib import Path
config_path = Path("config.json")
if config_path.exists():
print("config file exists")
e. csv
센서 데이터나 로그 데이터를 CSV로 저장할 때 사용합니다.
import csv
data = [
[0.0, 1.2],
[0.1, 1.3],
[0.2, 1.1]
]
with open("sensor_log.csv", "w", newline="") as file:
writer = csv.writer(file)
writer.writerow(["time", "distance"])
writer.writerows(data)
CSV 읽기:
import csv
with open("sensor_log.csv", "r") as file:
reader = csv.reader(file)
for row in reader:
print(row)
41. 초보자가 자주 하는 실수
a. =와 ==를 혼동함
speed = 1.0
=는 값을 넣는 것입니다.
if speed == 1.0:
print("speed is 1.0")
==는 같은지 비교하는 것입니다.
b. 리스트 복사를 잘못함
a = [1, 2, 3]
b = a
이건 복사가 아닙니다. 같은 리스트를 가리키는 것입니다.
b = a.copy()
또는 중첩 구조라면:
import copy
b = copy.deepcopy(a)
c. 빈 리스트에서 min() 사용
ranges = []
nearest = min(ranges)
이 코드는 에러가 납니다.
안전하게 작성해야 합니다.
if ranges:
nearest = min(ranges)
else:
nearest = None
d. 파일을 닫지 않음
나쁜 예:
file = open("log.txt", "w")
file.write("data")
좋은 예:
with open("log.txt", "w") as file:
file.write("data")
e. 예외 처리를 너무 넓게 함
나쁜 예:
try:
value = int("abc")
except:
print("error")
좋은 예:
try:
value = int("abc")
except ValueError:
print("invalid number")
무조건 except:로 모든 에러를 숨기면 실제 버그를 찾기 어렵습니다.
f. 변수 이름을 대충 지음
나쁜 예:
a = 1.0
b = 2.0
c = a / b
좋은 예:
distance = 1.0
time_sec = 2.0
speed = distance / time_sec
로봇 코드는 센서, 좌표, 속도, 시간, 각도 단위가 섞입니다. 변수 이름을 명확하게 짓지 않으면 나중에 반드시 고생합니다.
'강좌 > ROS2' 카테고리의 다른 글
| ROS 2 Python Topic 실습 : RobotStatus 메시지로 로봇 상태 주고받기 (0) | 2026.05.26 |
|---|---|
| Python 기본 타입 메시지 Publisher / Subscriber 예제 (0) | 2026.05.26 |
| VS Code 원격 개발 환경 (0) | 2026.05.24 |
| ROS 2 Humble rqt Plugins 정리 #3 (0) | 2026.05.24 |
| 로봇 개발자를 위한 Python 기초 교육 #2 (0) | 2026.05.24 |