Zum Hauptinhalt springen

작업 스케줄링

Python으로 정기적인 작업을 자동으로 실행할 수 있습니다. 일일 백업, 정기 리포트, 데이터 수집 등을 스케줄링해보겠습니다.

schedule 라이브러리

설치하기

pip install schedule

기본 사용법

import schedule
import time

def job():
print("작업 실행!")

# 10초마다 실행
schedule.every(10).seconds.do(job)

# 10분마다 실행
schedule.every(10).minutes.do(job)

# 1시간마다 실행
schedule.every().hour.do(job)

# 매일 실행
schedule.every().day.do(job)

# 매주 월요일 실행
schedule.every().monday.do(job)

# 매주 수요일 13:15에 실행
schedule.every().wednesday.at("13:15").do(job)

# 매일 10:30에 실행
schedule.every().day.at("10:30").do(job)

# 스케줄 실행
while True:
schedule.run_pending()
time.sleep(1)

다양한 스케줄 패턴

시간 기반 스케줄

import schedule

def morning_routine():
print("좋은 아침입니다!")

def lunch_reminder():
print("점심 시간입니다!")

def evening_report():
print("일일 보고서 생성 중...")

# 매일 오전 8시
schedule.every().day.at("08:00").do(morning_routine)

# 매일 정오
schedule.every().day.at("12:00").do(lunch_reminder)

# 매일 오후 6시
schedule.every().day.at("18:00").do(evening_report)

# 평일 오전 9시
schedule.every().monday.at("09:00").do(morning_routine)
schedule.every().tuesday.at("09:00").do(morning_routine)
schedule.every().wednesday.at("09:00").do(morning_routine)
schedule.every().thursday.at("09:00").do(morning_routine)
schedule.every().friday.at("09:00").do(morning_routine)

간격 기반 스케줄

import schedule

def check_server():
print("서버 상태 확인")

def collect_data():
print("데이터 수집")

# 5분마다
schedule.every(5).minutes.do(check_server)

# 30분마다
schedule.every(30).minutes.do(collect_data)

# 2시간마다
schedule.every(2).hours.do(check_server)

# 3일마다
schedule.every(3).days.do(collect_data)

매개변수가 있는 작업

import schedule

def greet(name):
print(f"안녕하세요, {name}님!")

def send_report(email, report_type):
print(f"{email}{report_type} 리포트 발송")

# 매개변수와 함께 실행
schedule.every().day.at("09:00").do(greet, name="홍길동")
schedule.every().day.at("18:00").do(send_report,
email="admin@example.com",
report_type="일일")

작업 취소

import schedule

def some_task():
print("작업 실행")
# 조건이 만족되면 작업 취소
return schedule.CancelJob

# 작업 등록
job = schedule.every().day.do(some_task)

# 나중에 수동으로 취소
schedule.cancel_job(job)

# 모든 작업 취소
schedule.clear()

# 특정 태그의 작업만 취소
schedule.clear('daily-tasks')

태그 사용하기

import schedule

def backup():
print("백업 실행")

def report():
print("리포트 생성")

# 태그 지정
schedule.every().day.at("02:00").do(backup).tag('backup', 'critical')
schedule.every().day.at("09:00").do(report).tag('report', 'daily')

# 특정 태그의 작업만 실행
schedule.run_all(delay_seconds=0) # 모든 작업 즉시 실행
schedule.run_all('backup') # 'backup' 태그 작업만 실행

# 특정 태그의 작업 취소
schedule.clear('daily')

실전 예제

1. 일일 백업 자동화

import schedule
import time
import shutil
import os
from datetime import datetime
import zipfile

def create_backup():
"""매일 자동 백업"""

print(f"\n[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 백업 시작...")

# 백업 대상 폴더
source_dirs = [
'/Users/username/Documents',
'/Users/username/Projects'
]

# 백업 저장 위치
backup_dir = '/Users/username/Backups'
os.makedirs(backup_dir, exist_ok=True)

# 백업 파일명 (날짜 포함)
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_file = os.path.join(backup_dir, f'backup_{timestamp}.zip')

try:
# ZIP 파일 생성
with zipfile.ZipFile(backup_file, 'w', zipfile.ZIP_DEFLATED) as zipf:
for source_dir in source_dirs:
if not os.path.exists(source_dir):
print(f"경고: {source_dir} 폴더가 없습니다.")
continue

print(f"백업 중: {source_dir}")

for root, dirs, files in os.walk(source_dir):
for file in files:
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, os.path.dirname(source_dir))
zipf.write(file_path, arcname)

# 백업 파일 크기 확인
size_mb = os.path.getsize(backup_file) / (1024 * 1024)
print(f"백업 완료: {backup_file} ({size_mb:.2f} MB)")

# 오래된 백업 정리 (30일 이상 된 파일 삭제)
cleanup_old_backups(backup_dir, days=30)

except Exception as e:
print(f"백업 실패: {e}")

def cleanup_old_backups(backup_dir, days=30):
"""오래된 백업 파일 삭제"""

now = time.time()
cutoff = now - (days * 86400) # days를 초로 변환

for filename in os.listdir(backup_dir):
if filename.startswith('backup_') and filename.endswith('.zip'):
file_path = os.path.join(backup_dir, filename)
file_time = os.path.getmtime(file_path)

if file_time < cutoff:
os.remove(file_path)
print(f"삭제: {filename} (생성 후 {days}일 경과)")

# 매일 새벽 2시에 백업 실행
schedule.every().day.at("02:00").do(create_backup)

print("백업 스케줄러 시작")
print("매일 02:00에 백업이 실행됩니다.")

# 테스트를 위해 즉시 실행
# create_backup()

while True:
schedule.run_pending()
time.sleep(60) # 1분마다 체크

2. 이메일 리포트 자동 발송

import schedule
import time
from datetime import datetime
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
import os

def send_daily_report():
"""일일 리포트 이메일 발송"""

print(f"\n[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 리포트 생성 중...")

# 리포트 데이터 생성 (예시)
report_data = generate_report()

# 이메일 설정
sender_email = "your_email@gmail.com"
sender_password = "your_app_password"
recipient_email = "recipient@example.com"

# 이메일 메시지 생성
message = MIMEMultipart()
message['From'] = sender_email
message['To'] = recipient_email
message['Subject'] = f"일일 리포트 - {datetime.now().strftime('%Y-%m-%d')}"

# 이메일 본문
body = f"""
<html>
<body>
<h2>일일 리포트</h2>
<p>날짜: {datetime.now().strftime('%Y년 %m월 %d일')}</p>

<h3>주요 지표</h3>
<ul>
<li>총 방문자: {report_data['visitors']:,}명</li>
<li>신규 가입: {report_data['signups']:,}명</li>
<li>매출: {report_data['revenue']:,}원</li>
</ul>

<p>상세 내용은 첨부 파일을 확인해주세요.</p>
</body>
</html>
"""

message.attach(MIMEText(body, 'html'))

# 파일 첨부 (선택사항)
report_file = 'daily_report.csv'
if os.path.exists(report_file):
with open(report_file, 'rb') as f:
part = MIMEBase('application', 'octet-stream')
part.set_payload(f.read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', f'attachment; filename={report_file}')
message.attach(part)

try:
# SMTP 서버 연결 및 전송
with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
server.login(sender_email, sender_password)
server.send_message(message)

print("리포트 발송 완료!")

except Exception as e:
print(f"이메일 발송 실패: {e}")

def generate_report():
"""리포트 데이터 생성 (예시)"""
# 실제로는 데이터베이스에서 가져옴
return {
'visitors': 1234,
'signups': 56,
'revenue': 5678900
}

# 매일 오전 9시에 리포트 발송
schedule.every().day.at("09:00").do(send_daily_report)

# 매주 월요일 오전 10시에 주간 리포트
def send_weekly_report():
print("주간 리포트 발송")
# 주간 리포트 로직

schedule.every().monday.at("10:00").do(send_weekly_report)

print("리포트 스케줄러 시작")
print("- 매일 09:00: 일일 리포트")
print("- 매주 월요일 10:00: 주간 리포트")

while True:
schedule.run_pending()
time.sleep(60)

3. 웹 데이터 정기 수집

import schedule
import time
from datetime import datetime
import requests
from bs4 import BeautifulSoup
import csv
import os

def collect_stock_prices():
"""주식 가격 정기 수집"""

print(f"\n[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 데이터 수집 시작...")

stocks = ['AAPL', 'GOOGL', 'MSFT', 'AMZN']
data = []

for stock in stocks:
try:
# API 또는 웹 스크래핑으로 데이터 수집
# 여기서는 예시로 랜덤 데이터 사용
import random
price = random.uniform(100, 500)

data.append({
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'stock': stock,
'price': round(price, 2)
})

print(f"{stock}: ${price:.2f}")

except Exception as e:
print(f"{stock} 수집 실패: {e}")

# CSV 파일에 저장
save_to_csv(data)

def save_to_csv(data):
"""데이터를 CSV 파일에 추가"""

filename = f"stock_prices_{datetime.now().strftime('%Y%m')}.csv"
file_exists = os.path.exists(filename)

with open(filename, 'a', newline='', encoding='utf-8') as f:
fieldnames = ['timestamp', 'stock', 'price']
writer = csv.DictWriter(f, fieldnames=fieldnames)

# 파일이 없으면 헤더 추가
if not file_exists:
writer.writeheader()

writer.writerows(data)

print(f"데이터 저장 완료: {filename}")

def analyze_daily_data():
"""하루 수집한 데이터 분석"""

print(f"\n[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 일일 분석 중...")

filename = f"stock_prices_{datetime.now().strftime('%Y%m')}.csv"

if not os.path.exists(filename):
print("분석할 데이터가 없습니다.")
return

# 데이터 읽기 및 분석
stocks_data = {}

with open(filename, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)

for row in reader:
# 오늘 날짜 데이터만 필터링
if row['timestamp'].startswith(datetime.now().strftime('%Y-%m-%d')):
stock = row['stock']
price = float(row['price'])

if stock not in stocks_data:
stocks_data[stock] = []

stocks_data[stock].append(price)

# 통계 출력
print("\n일일 주가 통계:")
for stock, prices in stocks_data.items():
if prices:
avg = sum(prices) / len(prices)
min_price = min(prices)
max_price = max(prices)

print(f"{stock}:")
print(f" 평균: ${avg:.2f}")
print(f" 최저: ${min_price:.2f}")
print(f" 최고: ${max_price:.2f}")
print(f" 변동: ${max_price - min_price:.2f}")

# 매 30분마다 데이터 수집
schedule.every(30).minutes.do(collect_stock_prices)

# 매일 오후 6시에 일일 분석
schedule.every().day.at("18:00").do(analyze_daily_data)

print("데이터 수집 스케줄러 시작")
print("- 30분마다: 주가 데이터 수집")
print("- 매일 18:00: 일일 분석")

# 즉시 한 번 실행
collect_stock_prices()

while True:
schedule.run_pending()
time.sleep(60)

4. 시스템 모니터링

import schedule
import time
from datetime import datetime
import psutil
import platform

def monitor_system():
"""시스템 리소스 모니터링"""

print(f"\n[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 시스템 모니터링")

# CPU 사용률
cpu_percent = psutil.cpu_percent(interval=1)
print(f"CPU 사용률: {cpu_percent}%")

# 메모리 사용률
memory = psutil.virtual_memory()
print(f"메모리 사용률: {memory.percent}%")
print(f"사용 중: {memory.used / (1024**3):.2f} GB / {memory.total / (1024**3):.2f} GB")

# 디스크 사용률
disk = psutil.disk_usage('/')
print(f"디스크 사용률: {disk.percent}%")
print(f"남은 공간: {disk.free / (1024**3):.2f} GB")

# 경고 체크
alerts = []

if cpu_percent > 80:
alerts.append(f"⚠️ CPU 사용률 높음: {cpu_percent}%")

if memory.percent > 80:
alerts.append(f"⚠️ 메모리 사용률 높음: {memory.percent}%")

if disk.percent > 90:
alerts.append(f"⚠️ 디스크 공간 부족: {disk.percent}%")

# 경고 출력
if alerts:
print("\n경고:")
for alert in alerts:
print(alert)

# 알림 전송 (이메일, 메시지 등)
send_alert(alerts)
else:
print("✓ 모든 시스템 정상")

def send_alert(alerts):
"""경고 알림 전송"""
# 실제로는 이메일, Slack, SMS 등으로 알림
print("\n알림 전송됨:")
for alert in alerts:
print(f" {alert}")

def daily_system_report():
"""일일 시스템 보고서"""

print(f"\n[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 일일 시스템 보고서")

# 시스템 정보
print(f"\n시스템 정보:")
print(f"OS: {platform.system()} {platform.release()}")
print(f"프로세서: {platform.processor()}")

# 부팅 시간
boot_time = datetime.fromtimestamp(psutil.boot_time())
print(f"부팅 시간: {boot_time.strftime('%Y-%m-%d %H:%M:%S')}")

uptime = datetime.now() - boot_time
print(f"가동 시간: {uptime.days}{uptime.seconds // 3600}시간")

# 프로세스 정보
print(f"\n실행 중인 프로세스: {len(psutil.pids())}개")

# 네트워크 정보
net_io = psutil.net_io_counters()
print(f"\n네트워크:")
print(f"송신: {net_io.bytes_sent / (1024**3):.2f} GB")
print(f"수신: {net_io.bytes_recv / (1024**3):.2f} GB")

# 5분마다 시스템 모니터링
schedule.every(5).minutes.do(monitor_system)

# 매일 오전 9시에 일일 보고서
schedule.every().day.at("09:00").do(daily_system_report)

print("시스템 모니터링 시작")
print("- 5분마다: 리소스 모니터링")
print("- 매일 09:00: 일일 시스템 보고서")

# 즉시 한 번 실행
monitor_system()

while True:
schedule.run_pending()
time.sleep(60)

5. 데이터베이스 정리 작업

import schedule
import time
from datetime import datetime, timedelta
import sqlite3

def cleanup_old_logs():
"""오래된 로그 데이터 삭제"""

print(f"\n[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 데이터베이스 정리 시작...")

# 30일 이상 된 데이터 삭제
cutoff_date = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')

try:
conn = sqlite3.connect('app.db')
cursor = conn.cursor()

# 오래된 로그 삭제
cursor.execute("""
DELETE FROM logs
WHERE created_at < ?
""", (cutoff_date,))

deleted_count = cursor.rowcount

# 오래된 세션 삭제
cursor.execute("""
DELETE FROM sessions
WHERE last_activity < ?
""", (cutoff_date,))

deleted_count += cursor.rowcount

conn.commit()
conn.close()

print(f"정리 완료: {deleted_count}개 레코드 삭제")

# 데이터베이스 최적화
optimize_database()

except Exception as e:
print(f"정리 실패: {e}")

def optimize_database():
"""데이터베이스 최적화"""

print("데이터베이스 최적화 중...")

try:
conn = sqlite3.connect('app.db')
conn.execute("VACUUM")
conn.close()

print("최적화 완료")

except Exception as e:
print(f"최적화 실패: {e}")

def backup_database():
"""데이터베이스 백업"""

print(f"\n[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 데이터베이스 백업 중...")

import shutil
import os

source = 'app.db'
backup_dir = 'backups'
os.makedirs(backup_dir, exist_ok=True)

timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
destination = os.path.join(backup_dir, f'app_backup_{timestamp}.db')

try:
shutil.copy2(source, destination)

# 파일 크기 확인
size_mb = os.path.getsize(destination) / (1024 * 1024)
print(f"백업 완료: {destination} ({size_mb:.2f} MB)")

except Exception as e:
print(f"백업 실패: {e}")

# 매일 새벽 3시에 정리 작업
schedule.every().day.at("03:00").do(cleanup_old_logs)

# 매일 새벽 2시에 백업
schedule.every().day.at("02:00").do(backup_database)

# 매주 일요일 새벽 4시에 최적화
schedule.every().sunday.at("04:00").do(optimize_database)

print("데이터베이스 관리 스케줄러 시작")
print("- 매일 02:00: 데이터베이스 백업")
print("- 매일 03:00: 오래된 데이터 정리")
print("- 매주 일요일 04:00: 데이터베이스 최적화")

while True:
schedule.run_pending()
time.sleep(60)

백그라운드 실행

데몬 프로세스로 실행

import schedule
import time
import daemon
import lockfile

def my_task():
print(f"작업 실행: {time.strftime('%Y-%m-%d %H:%M:%S')}")

def run_scheduler():
schedule.every(10).seconds.do(my_task)

while True:
schedule.run_pending()
time.sleep(1)

# 데몬으로 실행
with daemon.DaemonContext(
working_directory='/path/to/working/dir',
pidfile=lockfile.FileLock('/var/run/scheduler.pid')
):
run_scheduler()

systemd 서비스 (Linux)

# /etc/systemd/system/scheduler.service

[Unit]
Description=Python Scheduler Service
After=network.target

[Service]
Type=simple
User=username
WorkingDirectory=/home/username/scheduler
ExecStart=/usr/bin/python3 /home/username/scheduler/scheduler.py
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
# 서비스 등록 및 실행
sudo systemctl daemon-reload
sudo systemctl enable scheduler.service
sudo systemctl start scheduler.service

# 상태 확인
sudo systemctl status scheduler.service

# 로그 확인
sudo journalctl -u scheduler.service -f

nohup으로 백그라운드 실행

# 백그라운드로 실행 (로그 저장)
nohup python3 scheduler.py > scheduler.log 2>&1 &

# 프로세스 확인
ps aux | grep scheduler.py

# 프로세스 종료
kill <PID>

cron과의 비교

cron 사용법

# crontab 편집
crontab -e

# 매일 오전 2시에 백업 스크립트 실행
0 2 * * * /usr/bin/python3 /path/to/backup.py

# 매 5분마다 실행
*/5 * * * * /usr/bin/python3 /path/to/monitor.py

# 평일 오전 9시에 실행
0 9 * * 1-5 /usr/bin/python3 /path/to/report.py

# crontab 목록 보기
crontab -l

schedule vs cron

schedule 장점:

  • Python 코드 내에서 모든 것을 관리
  • 조건부 실행이 쉬움
  • 디버깅이 쉬움
  • 크로스 플랫폼

cron 장점:

  • 시스템 레벨 관리
  • 더 안정적
  • 재시작 후에도 자동 실행
  • 별도 프로세스 불필요
# schedule: Python 코드 내에서 관리
import schedule

def backup():
# 조건부 실행이 쉬움
if is_weekday() and not is_holiday():
perform_backup()

schedule.every().day.at("02:00").do(backup)

APScheduler (고급)

더 강력한 스케줄링이 필요하다면 APScheduler를 사용하세요.

설치

pip install apscheduler

사용 예제

from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from datetime import datetime
import time

def job():
print(f"작업 실행: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")

# 스케줄러 생성
scheduler = BackgroundScheduler()

# 인터벌 기반 작업
scheduler.add_job(job, 'interval', seconds=10)

# cron 스타일 작업
scheduler.add_job(
job,
CronTrigger(hour=9, minute=0),
id='morning_job'
)

# 특정 날짜/시간에 한 번 실행
scheduler.add_job(
job,
'date',
run_date=datetime(2024, 12, 31, 23, 59, 59)
)

# 스케줄러 시작
scheduler.start()

print("APScheduler 시작")

try:
# 메인 스레드 유지
while True:
time.sleep(1)
except (KeyboardInterrupt, SystemExit):
scheduler.shutdown()

자주 묻는 질문

Q1. schedule이 정확한 시간에 실행되지 않아요.

A: schedule은 정확한 타이밍을 보장하지 않습니다. 더 정확한 실행이 필요하면 APScheduler를 사용하세요.

# schedule: 대략적인 시간
schedule.every().day.at("10:30").do(job)

# APScheduler: 더 정확한 시간
from apscheduler.schedulers.blocking import BlockingScheduler

scheduler = BlockingScheduler()
scheduler.add_job(job, 'cron', hour=10, minute=30)
scheduler.start()

Q2. 스케줄러가 멈춰있는 것 같아요.

A: time.sleep()이 너무 길거나 작업이 블로킹되고 있을 수 있습니다.

# 나쁜 예
while True:
schedule.run_pending()
time.sleep(3600) # 1시간 대기 - 너무 김!

# 좋은 예
while True:
schedule.run_pending()
time.sleep(1) # 1초마다 체크

Q3. 작업이 실행 중일 때 다음 스케줄이 겹치면 어떻게 되나요?

A: 기본적으로 이전 작업이 끝나기를 기다립니다. 병렬 실행이 필요하면 스레드를 사용하세요.

import threading

def threaded_job():
thread = threading.Thread(target=long_running_task)
thread.start()

schedule.every(5).minutes.do(threaded_job)

Q4. 스케줄러가 실행 중인지 어떻게 확인하나요?

A: 로깅을 추가하고 상태 파일을 생성하세요.

import schedule
import logging
from datetime import datetime

# 로깅 설정
logging.basicConfig(
filename='scheduler.log',
level=logging.INFO,
format='%(asctime)s - %(message)s'
)

def job():
logging.info("작업 실행")
print("작업 실행")

# 상태 파일 업데이트
def update_status():
with open('scheduler.status', 'w') as f:
f.write(datetime.now().isoformat())

schedule.every(1).minutes.do(update_status)
schedule.every(5).minutes.do(job)

logging.info("스케줄러 시작")
print("스케줄러 시작")

while True:
schedule.run_pending()
time.sleep(1)

Q5. 에러가 발생해도 스케줄러가 계속 실행되게 하려면?

A: try-except로 에러를 처리하세요.

import schedule
import time
import traceback

def safe_job():
try:
# 실제 작업
risky_operation()
except Exception as e:
print(f"오류 발생: {e}")
traceback.print_exc()
# 에러 알림 전송
send_error_notification(str(e))

def risky_operation():
# 에러가 발생할 수 있는 작업
pass

def send_error_notification(error_msg):
# 에러 알림 (이메일, Slack 등)
pass

schedule.every(10).minutes.do(safe_job)

while True:
try:
schedule.run_pending()
time.sleep(1)
except KeyboardInterrupt:
print("\n스케줄러 종료")
break
except Exception as e:
print(f"스케줄러 오류: {e}")
time.sleep(5) # 잠시 대기 후 재시작

마치며

축하합니다! Python 학습 여정을 완주하셨습니다.

지금까지 배운 내용:

  • Python 기초: 변수, 자료형, 제어문, 함수
  • 객체지향 프로그래밍: 클래스, 상속, 모듈
  • 파일 및 예외 처리: 파일 입출력, 에러 처리
  • 자동화: 파일, 엑셀, 웹, 스케줄링

이제 여러분은:

  • 반복적인 업무를 자동화할 수 있습니다
  • 데이터를 수집하고 분석할 수 있습니다
  • 실용적인 프로그램을 만들 수 있습니다
  • 문제를 코드로 해결할 수 있습니다

다음 단계

Python 학습을 계속하고 싶다면:

  1. 웹 개발: Flask, Django로 웹 애플리케이션 만들기
  2. 데이터 과학: pandas, numpy로 데이터 분석하기
  3. 머신러닝: scikit-learn, TensorFlow로 AI 모델 만들기
  4. API 개발: FastAPI로 REST API 서버 구축하기
  5. GUI 프로그래밍: tkinter, PyQt로 데스크톱 앱 만들기

계속 성장하는 방법

  • 실전 프로젝트: 배운 내용을 실제 문제에 적용해보세요
  • 오픈소스 기여: GitHub에서 프로젝트에 참여해보세요
  • 코드 리뷰: 다른 사람의 코드를 읽고 배우세요
  • 커뮤니티 참여: Python 커뮤니티에서 질문하고 답변하세요

"가장 좋은 학습 방법은 만드는 것입니다."

여러분의 Python 여정이 계속되길 응원합니다!