Hamutaro

안녕하세요

벌써 파이썬을 급진적으로 나가게 된 4일차라니..

시작해볼게요...

 

일단 아침 과제 30분 시작해볼게요..

 

위치를 가리키는 말

찾아볼 말 무엇을 알아 오면 되나 (힌트)
폴더 · 디렉터리 컴퓨터에서 파일을 보관하고 정리하는 동일한 공간을 의미하지만, 사용되는 맥락과 관점에서 약간의 차이가 있다.
폴더는 시각적인 개념으로 윈도우나 맥 등 그래픽 화면에서 일반 사용자가 이해하기 쉽도록 만든 단어이고, 디렉토리는 기술적인 개념으로 리눅스나 명령 프롬프트 등 텍스트 화면에서 개발자나 시스템 관리자가 사용하는 단어이다.
절대경로 파일 시스템의 최상위 루트 디렉터리(/ 또는 C:\) 부터 시작하여 특정 파일이나 폴더까지의 전체 파일 위치를 빠짐없이 컴퓨터에게 알려주는 주소, 항상 동일한 파일을 가리킴.
상대경로 현재 작업 중인 파일이나 디렉터리(현재 위치)를 기준으로 목적지 파일의 위치를 표현하는 주소
홈 디렉터리 운영체제에서 로그인한 개별 사용자(User)의 전용 폴더, 기호 '~'
. 과 .. . 은 현재 작업 중인 폴더를 의미
.. 은 현재 폴더 바로 한 단계 위에 있는 상위 폴더를 의미
둘 다 상대 경로 시스템의 기호이다.
/ 와 \ 컴퓨터 운영체제에 따라 경로를 구분하는 슬래시의 방향이 다르다.
윈도우는 백슬래시(\)를 사용하고, 맥이나 리눅스는 슬래시(/)를 주로 사용한다.

 

명령 이름의 뜻

명령 무엇의 줄임말인지 찾아봅니다
pwd Print Working Directory
ls List
cd Change Directory
mkdir Make Directory

 

명령 한 줄의 구조

# 명령은 이렇게 생겼습니다.
mkdir docs        명령 + 대상
ls -a             명령 + 옵션
cd ..             명령 + 대상(한 칸 위)

스스로 설명해 봅니다

정리한 것을 보지 않고 답해 봅니다. 막히면 위로 돌아가 다시 읽습니다.

  • 지금 있는 폴더를 화면에 띄우는 명령은 무엇입니까?
    • pwd
  • cd .. 를 입력하면 어디로 갑니까?
    • 현재 있는 디렉토리보다 상위 디렉토리
  • /c/Users/hong 을 윈도우 탐색기 주소창에 적으면 어떤 모양이 됩니까?
    • c:\Users\hong

⭐ 다 한 사람만 합니다

docs 안에 있는 상태에서, 홈으로 돌아가지 않고 agent_core 로 바로 이동해 봅니다. .. 를 쓰면 됩니다. 성공하면 그때 입력한 명령을 기록 파일에 적어 둡니다.

User@ ~/security-agent-toolkit/docs
$ cd ../agent_core/

 

모닝30분.. 끝..!


 

Python 정규표현식, 탐지 룰, API와 HTTP 정리

오늘은 로그 데이터를 단순히 읽는 수준을 넘어, 비정형 로그에서 원하는 값을 찾아 정형 데이터로 바꾸고, 그 데이터를 이용해 수상한 행동을 탐지하는 방법을 배웠다.

오후에는 파일 안의 정보만 보는 것을 넘어 외부 서비스와 데이터를 주고받기 위한 API와 HTTP의 기본 구조, 그리고 상태코드와 인증·인가까지 학습했다.


오늘 오전의 순서

  • split()이 조용히 틀리는 이유
  • 정규표현식 기호
  • re.search, re.findall
  • 그룹과 named group
  • 안 맞는 줄 따로 남기기
  • 탐지 룰 ① 브루트포스
  • 탐지 룰 ② 의심 IP

1. 정형 로그와 비정형 로그

정형 로그

정형 로그는 데이터의 구조가 미리 정해져 있다.

예를 들어 다음처럼 쉼표 기준으로 각 항목이 명확하게 나뉜다.

09:01,kim01,LOGIN_OK,10.0.3.21

구조를 보면 다음과 같다.

위치값

0 시간
1 사용자
2 이벤트
3 IP

이런 데이터는 split(",")을 이용하기 쉽다.


비정형 로그

비정형 로그는 표처럼 열과 행이 정확히 나뉘어 있지 않고, 한 줄의 텍스트 형태로 기록된다.

2026-09-29 09:12:00 WARN failed login for kim01 from 203.0.113.5

사람이 보면 시간, 로그 레벨, 사용자, IP를 알 수 있지만 프로그램 입장에서는 그냥 하나의 문자열이다.

그래서 원하는 값을 몇 번째 칸에 있는가가 아니라 어떤 모양을 가지고 있는가로 찾아야 한다.

이때 사용하는 것이 정규표현식이다.


2. 정규표현식이란?

정규표현식(Regular Expression) 은 문자열 속에서 원하는 패턴을 찾기 위한 규칙이다.

기존 방식이

몇 번째 위치에 있는 값인가?

를 기준으로 찾는 방식이었다면,

정규표현식은

숫자인가?
특정 단어 주변에 있는가?
특정 형태를 가지고 있는가?

를 기준으로 값을 찾는다.

즉, 위치가 아니라 모양으로 찾는 방법이다.


3. split()은 왜 위험할까?

다음 로그가 있다고 하자.

line = "2026-09-29 09:12:00 WARN failed login for kim01 from 203.0.113.5"

parts = line.split()

print(parts)

결과는 다음과 같다.

[
    '2026-09-29',
    '09:12:00',
    'WARN',
    'failed',
    'login',
    'for',
    'kim01',
    'from',
    '203.0.113.5'
]

따라서 사용자 이름은 다음처럼 꺼낼 수 있다.

print(parts[6])

결과:

kim01

문제는 로그 문장의 형태가 조금만 달라져도 발생한다.

예를 들어 다음 로그가 있다고 하자.

2026-09-29 09:12:00 WARN failed user login for kim01 from 203.0.113.5

중간에 user라는 단어 하나가 추가됐다.

그러면 모든 위치가 한 칸씩 밀린다.

그런데 파이썬은 오류를 내지 않는다.

그냥 다른 값을 꺼낸다.

이것이 더 위험하다.

프로그램이 멈추는 오류보다, 틀린 값을 정상 값처럼 처리하는 오류가 더 찾기 어렵다.


4. 정규표현식에서 자주 쓰는 기호

파이썬에서 정규표현식을 사용하려면 re 모듈을 불러온다.

import re

오늘 사용한 주요 기호는 다음과 같다.

기호의미예

\d 숫자 한 글자 3, 9
\w 문자·숫자·밑줄 한 글자 a, 7, _
[ ] 대괄호 안 문자 중 하나 [\d.]
+ 바로 앞 패턴이 1개 이상 반복 \d+
. 아무 글자 한 글자 a, 1, - 등
{1,3} 앞 패턴이 1~3개 반복 \d{1,3}

5. \d와 +

\d는 숫자 한 글자를 의미한다.

\d

예를 들어 다음 문자열에서

실패 3회

\d는 3을 찾을 수 있다.

여러 자리 숫자를 찾으려면 +를 붙인다.

\d+

예를 들어

실패 123회

에서 123 전체를 찾는다.

+는 왼쪽 패턴을 하나 이상 계속 이어서 찾는다는 의미다.


6. [\d.]의 의미

다음 패턴을 보자.

r"[\d.]+"

[\d.]는

숫자 또는 점

한 글자를 의미한다.

여기에 +가 붙었기 때문에 숫자나 점이 이어지는 동안 계속 찾는다.

따라서 다음 IP를 찾는 데 사용할 수 있다.

203.0.113.5

7. raw string의 r

정규표현식 앞에는 보통 r을 붙인다.

r"\d+"

이것을 raw string이라고 한다.

정규표현식에는 \d, \w처럼 역슬래시를 많이 사용한다.

r을 붙이면 파이썬이 역슬래시를 먼저 해석하지 않고 정규표현식에 그대로 전달한다.

따라서 정규표현식을 작성할 때는 다음처럼 쓰는 습관이 좋다.

re.search(r"\d+", text)

8. re.search()

re.search()는 문자열 안에서 처음으로 패턴과 맞는 부분 하나를 찾는다.

import re

m = re.search(r"\d+", "확인 필요: admin 실패 3회")

print(m.group())

결과:

3

re.search() 자체는 찾은 정보를 담고 있는 객체를 반환한다.

실제 문자열을 꺼내려면 .group()을 사용한다.

m.group()

9. re.findall()

re.findall()은 패턴에 맞는 값을 전부 찾아 리스트로 돌려준다.

import re

numbers = re.findall(r"\d+", "실패 3회, 성공 2회")

print(numbers)

결과:

['3', '2']

결과가 하나만 있어도 리스트다.

['3']

찾는 값이 없으면 빈 리스트가 나온다.

[]

10. re.search와 re.findall 비교

함수결과

re.search() 처음 맞는 것 하나
re.findall() 맞는 것 전부
re.search() 실패 None
re.findall() 실패 []

11. 그룹(Group)이란?

정규표현식으로 한 줄 전체를 찾는 것만으로는 부족하다.

다음 로그에서

2026-09-29 09:12:00 WARN failed login for kim01 from 203.0.113.5

우리가 원하는 것은 각각 다음 값이다.

  • 시간
  • 로그 레벨
  • 사용자
  • IP

이처럼 정규표현식 안에서 따로 꺼내고 싶은 부분을 소괄호로 묶는 것을 그룹이라고 한다.


12. 그룹 번호로 꺼내기

다음처럼 괄호를 사용할 수 있다.

pattern = r"(\d+)회"

m = re.search(pattern, "실패 3회")

print(m.group(1))

결과:

3

첫 번째 괄호는 group(1)이다.

하지만 그룹이 많아지면

1번이 뭐였지?
2번이 뭐였지?
3번이 IP였나?

처럼 헷갈릴 수 있다.

그래서 이름 붙인 그룹(named group) 을 사용한다.


13. named group

이름 붙인 그룹은 다음 형태를 사용한다.

(?P<이름>패턴)

예를 들어 사용자라면

(?P<user>[\w.]+)

와 같이 작성한다.


14. 비정형 로그 한 줄에서 네 값 꺼내기

다음 로그를 사용해 보자.

line = "2026-09-29 09:12:00 WARN failed login for kim01 from 203.0.113.5"

패턴은 다음과 같다.

PATTERN = r"(?P<time>[\d:]+) (?P<level>\w+) \w+ login for (?P<user>[\w.]+) from (?P<ip>[\d.]+)"

찾는다.

m = re.search(PATTERN, line)

사용자를 꺼내면

print(m.group("user"))

결과:

kim01

15. groupdict()

이름을 붙인 그룹을 한 번에 딕셔너리로 만들 수 있다.

print(m.groupdict())

결과:

{
    'time': '09:12:00',
    'level': 'WARN',
    'user': 'kim01',
    'ip': '203.0.113.5'
}

즉,

비정형 문자열 한 줄
        ↓
정규표현식
        ↓
딕셔너리 한 개

로 바뀐다.

이 과정이 로그 정규화에서 매우 중요하다.


16. 괄호 밖의 문자열은 왜 필요한가?

패턴을 다시 보자.

PATTERN = r"(?P<time>[\d:]+) (?P<level>\w+) \w+ login for (?P<user>[\w.]+) from (?P<ip>[\d.]+)"

여기서

login for
from

은 그룹이 아니다.

따라서 결과 딕셔너리에는 나오지 않는다.

하지만 해당 문장이 실제 로그 안에 존재해야 전체 패턴이 맞는다.

즉,

괄호 안 = 가져올 값
괄호 밖 = 패턴이 맞는지 판단하기 위한 조건

이라고 이해하면 된다.


17. 못 찾으면 None

re.search()는 패턴을 못 찾으면 None을 반환한다.

m = re.search(r"\d+", "숫자가 없는 문장")

print(m)

결과:

None

이 상태에서 다음 코드를 실행하면 오류가 난다.

m.group()

따라서 항상 먼저 확인해야 한다.

if m:
    print(m.group())

18. 안 맞는 줄을 버리지 않는 이유

정규표현식과 맞지 않는 로그가 있다고 해서 그냥 버리면 문제가 있다.

그 로그가

  • 원래 다른 종류의 정상 로그인지
  • 정규표현식 패턴이 틀린 것인지
  • 로그 자체가 깨진 것인지

알 수 없기 때문이다.

그래서 안 맞는 로그는 따로 남겨 두는 것이 좋다.

예:

unmatched_logs.txt

이렇게 하면 나중에 패턴을 수정하거나 분석할 수 있다.


19. 오전 정규화 흐름

오전까지의 전체 흐름은 다음과 같다.

비정형 로그
   ↓
re.search()
   ↓
패턴 일치?
   ↓
Yes → groupdict() → 딕셔너리
No  → unmatched_logs.txt
   ↓
normalized_logs.json

20. 탐지 룰이란?

정규화가 끝났다면 이제 데이터를 이용해 수상한 행동을 찾아야 한다.

탐지 룰은

이런 형태가 나타나면 수상하다고 판단한다

라는 기준을 코드로 만든 것이다.

보안 관제에서는 로그를 단순히 저장하는 것이 목적이 아니라,

어떤 행동이 이상한가?

를 찾는 것이 중요하다.


21. 브루트포스란?

브루트포스(Brute Force) 는 비밀번호나 인증 정보를 알아내기 위해 가능한 값을 반복적으로 시도하는 공격이다.

로그에서는 같은 계정에 대해 로그인 실패가 반복되는 형태로 나타날 수 있다.

예:

admin LOGIN_FAIL
admin LOGIN_FAIL
admin LOGIN_FAIL
admin LOGIN_FAIL

22. 임계값

몇 번부터 공격으로 볼지는 프로그램이 스스로 결정하지 않는다.

사람이 정한다.

예를 들어

THRESHOLD = 3

이라면

로그인 실패가 3회 이상이면 확인 필요

라는 규칙이 된다.

이렇게 상태가 달라지는 경계값을 임계값이라고 한다.


23. 룰 ① 브루트포스 — 계정별 실패 횟수

먼저 딕셔너리를 이용해 값을 세는 방법을 다시 보자.

fruits = ["사과", "배", "사과"]

count = {}

for name in fruits:

    if name in count:
        count[name] = count[name] + 1

    else:
        count[name] = 1

print(count)

결과:

{
    '사과': 2,
    '배': 1
}

로그에서도 같은 방법을 사용할 수 있다.

count = {}

for row in rows:

    if row["level"] == "WARN":

        user = row["user"]

        if user in count:
            count[user] = count[user] + 1

        else:
            count[user] = 1

이후 임계값과 비교한다.

THRESHOLD = 3

for user in count:

    if count[user] >= THRESHOLD:
        print(user, count[user])

24. 룰 ①의 시선

룰 ①은 사용자 계정 기준으로 본다.

admin → 몇 번 실패했는가?
kim01 → 몇 번 실패했는가?
lee02 → 몇 번 실패했는가?

즉,

한 계정을 얼마나 여러 번 두드렸는가?

를 보는 것이다.


25. 룰 ② 의심 IP

이번에는 시선을 반대로 바꾼다.

한 IP가 여러 계정을 시도한다면 수상할 수 있다.

예:

203.0.113.5 → admin
203.0.113.5 → kim01
203.0.113.5 → lee02

하나의 IP가 여러 계정을 건드리고 있다.


26. 딕셔너리 안에 리스트 넣기

다음 데이터를 보자.

pairs = [
    ("A", "kim"),
    ("A", "lee"),
    ("B", "kim")
]

목표는 다음처럼 만드는 것이다.

{
    'A': ['kim', 'lee'],
    'B': ['kim']
}

코드는 다음과 같다.

table = {}

for key, name in pairs:

    if key in table:

        if name not in table[key]:
            table[key].append(name)

    else:
        table[key] = [name]

27. 같은 계정을 두 번 넣지 않는 이유

다음처럼 같은 계정에 여러 번 로그인했다고 하자.

IP A → kim
IP A → kim
IP A → kim

이것을 그대로 세면 계정 수가 3개처럼 보인다.

하지만 실제로는 하나의 계정만 시도했다.

그래서 다음 조건을 사용한다.

if name not in table[key]:
    table[key].append(name)

즉, 서로 다른 계정만 저장한다.


28. 룰 ②의 시선

룰 ②는 IP 기준이다.

IP A
 ├─ admin
 ├─ kim
 └─ lee

한 IP에서 시도한 계정 수가 일정 개수 이상이라면 의심할 수 있다.

예:

if user_count >= 2:
    print("의심 IP")

이 2 역시 사람이 정한 임계값이다.

실제 환경에서는 정상 사용 패턴을 분석해서 조정해야 한다.


29. 탐지 룰 ①② 비교

룰기준무엇을 보는가

① 브루트포스 계정 같은 계정의 반복 실패
② 의심 IP IP 하나의 IP가 여러 계정 시도

오늘 오후의 순서

  • 탐지 룰 ③ 심야 성공 접속
  • 세 룰 한 번에 실행하기
  • API란 무엇인가
  • URL 구조
  • HTTP 메서드
  • 상태코드
  • 401과 403
  • API 키와 토큰

30. 룰 ③ 심야 성공 접속

오전의 룰 ①과 ②는 모두 실패를 기준으로 했다.

그런데 보안에서는 실패보다 성공이 더 위험한 경우도 있다.

예를 들어 공격자가 다음처럼 여러 번 로그인에 실패했다고 하자.

03:12 LOGIN_FAIL
03:13 LOGIN_FAIL
03:15 LOGIN_FAIL
03:17 LOGIN_OK

앞의 실패는 막힌 것이다.

하지만 마지막 성공은 실제로 계정에 들어간 것이다.

따라서 비정상적인 시간대의 로그인 성공을 탐지해야 한다.


31. 심야 접속을 의심하는 이유

심야 접속 자체가 반드시 공격이라는 뜻은 아니다.

하지만 일반적인 업무 시간대와 크게 다른 시간의 로그인은 정상 행동 패턴에서 벗어날 수 있다.

따라서 탐지 대상으로 삼을 수 있다.

예를 들어 오늘은

NIGHT_END = 6

으로 정하고

오전 6시 이전의 로그인 성공

을 탐지한다.

이 역시 환경에 따라 달라질 수 있는 임계값이다.


32. 문자열 시간에서 시(hour) 꺼내기

다음 시간 문자열이 있다고 하자.

time = "03:17:09"

콜론 기준으로 나눈다.

print(time.split(":"))

결과:

['03', '17', '09']

0번째 값이 시간이다.

hour = time.split(":")[0]

현재는 문자열이므로 숫자로 변환한다.

hour = int(time.split(":")[0])

결과:

3

33. 심야인지 확인하기

NIGHT_END = 6

if hour < NIGHT_END:
    print("심야 접속")

hour가 3이라면 조건이 참이다.


34. 성공 로그인까지 함께 확인하기

심야라고 해서 모든 로그가 위험한 것은 아니다.

로그인 성공일 때만 확인한다.

예:

if row["level"] == "INFO" and hour < NIGHT_END:
    print("심야 로그인 성공")

즉,

성공 로그인
AND
06시 이전

두 조건을 모두 만족해야 한다.


35. 세 탐지 룰 한눈에 보기

룰무엇을 본다예시 기준

① 브루트포스 계정별 로그인 실패 3회 이상
② 의심 IP IP별 시도한 계정 수 2개 이상
③ 심야 접속 심야 로그인 성공 06시 이전

중요한 점은 세 룰 모두 같은 로그 데이터 rows를 다른 시각으로 보는 것이라는 점이다.


36. 기준값은 코드 위에 모아 두기

임계값을 코드 안 여기저기에 직접 적지 않는 것이 좋다.

다음처럼 맨 위에 모아 둔다.

BRUTE_FORCE_THRESHOLD = 3
SUSPICIOUS_IP_THRESHOLD = 2
NIGHT_END = 6

그러면 기준을 바꿀 때 한 곳만 수정하면 된다.


37. 오전 산출물

오전 작업을 완료하면 다음과 같은 파일을 만들 수 있다.

agent_core/
 ├─ normalize_logs.py
 ├─ normalized_logs.json
 └─ unmatched_logs.txt

각 역할은 다음과 같다.

파일역할

normalize_logs.py 비정형 로그를 분석하고 정규화
normalized_logs.json 정규화된 로그 저장
unmatched_logs.txt 패턴과 맞지 않은 로그 저장

38. API란?

지금까지는 내가 가지고 있는 파일 안의 데이터만 사용했다.

하지만 로그에 이런 IP가 있다고 하자.

185.220.101.34

이 IP가 실제로 위험한 IP인지 여부는 우리 로그 파일만으로는 알 수 없다.

외부 IP 분석 서비스에 물어봐야 한다.

이때 사용하는 것이 API다.


39. API를 쉽게 이해하면

API는

내 프로그램과 다른 프로그램이 대화하기 위해 미리 정해 둔 공식 창구

라고 생각하면 된다.

식당으로 비유하면 다음과 같다.

손님
 ↓
메뉴판
 ↓
주문
 ↓
주방
 ↓
음식

프로그램에서는

내 프로그램
   ↓
API 요청
   ↓
외부 서버
   ↓
API 응답

이 된다.


40. HTTP란?

API에서 요청과 응답을 주고받을 때 많이 사용하는 규칙이 HTTP다.

기본 구조는 다음과 같다.

클라이언트
    ↓ 요청(Request)
서버
    ↓ 응답(Response)
클라이언트

오늘은 실제 HTTP 요청을 보내는 것은 아니고, 요청과 응답의 구조를 이해하는 단계다.

실제로 요청을 보내는 requests는 다음 단계에서 사용한다.


41. API 응답은 문자열로 올 수 있다

서버에서 다음과 같은 JSON 문자열이 왔다고 하자.

text = '{"city": "seoul", "count": 3}'

현재는 문자열이다.

따라서 바로 딕셔너리처럼 사용할 수 없다.

json.loads()를 사용한다.

import json

data = json.loads(text)

이제 딕셔너리가 된다.

print(data["city"])

결과:

seoul

숫자도 원래 자료형으로 변환된다.

print(data["count"] + 1)

결과:

4

42. URL 구조

다음 주소를 보자.

https://api.example.com/v1/ip/185.220.101.34?full=true

URL은 크게 네 부분으로 볼 수 있다.

부분예의미

방식 https:// 어떤 방식으로 통신할지
호스트 api.example.com 어느 서버에 요청할지
경로 /v1/ip/185.220.101.34 서버 안의 어느 기능인지
질의 ?full=true 추가 조건

43. HTTPS

주소 앞의

https://

는 HTTPS 방식을 사용한다는 뜻이다.

HTTPS는 통신 내용을 암호화해 전송한다.

즉, HTTP보다 안전하게 데이터를 주고받기 위한 방식이다.


44. 호스트

다음 부분은 호스트다.

api.example.com

쉽게 말하면

어느 서버에게 요청을 보낼 것인가?

를 나타낸다.


45. 경로

다음 부분은 경로다.

/v1/ip/185.220.101.34

같은 서버에서도 여러 기능을 제공할 수 있기 때문에 경로로 원하는 기능을 구분한다.

여기서는 특정 IP 정보를 조회하는 경로라고 볼 수 있다.


46. 질의(Query)

다음 부분은 질의다.

?full=true

? 뒤에 추가 조건을 적는다.

조건이 여러 개라면 &로 연결한다.

?full=true&lang=ko

47. HTTP 메서드

같은 URL이라도 무엇을 하려는지 표시해야 한다.

그 역할을 하는 것이 HTTP 메서드다.

대표적인 두 가지는 다음과 같다.

메서드의미

GET 데이터를 조회
POST 새로운 데이터를 생성

48. GET

GET은 데이터를 읽거나 조회할 때 사용한다.

예를 들어 IP 정보를 조회한다면 GET이 적합하다.

GET /ip/185.220.101.34

일반적인 조회 요청은 같은 요청을 다시 보내도 데이터를 하나 더 생성하지 않는다.


49. POST

POST는 새로운 데이터를 만들거나 서버에 데이터를 제출할 때 많이 사용한다.

예:

  • 회원가입
  • 주문 생성
  • 게시글 작성

같은 요청을 여러 번 보내면 데이터가 중복 생성될 수 있다.

따라서 GET과 성격이 다르다.


50. GET과 POST 비교

구분GETPOST

주요 목적 조회 생성·제출
예 검색, IP 조회 회원가입, 주문
반복 요청 일반적으로 같은 정보 조회 중복 생성 가능

51. 상태코드란?

HTTP 요청을 보내면 서버는 결과와 함께 숫자를 돌려준다.

이 숫자를 HTTP 상태코드라고 한다.

예:

200
404
500

세 자리 숫자의 첫 번째 자리만 봐도 대략적인 상태를 알 수 있다.


52. 상태코드 앞자리

앞자리의미대표 예

2 성공 200, 201
4 요청한 쪽 문제 401, 403, 404
5 서버 문제 500

예를 들어 상태코드가 다음과 같다면

code = 404

문자열로 바꾼다.

str(code)

결과:

"404"

첫 번째 글자를 꺼낸다.

print(str(code)[0])

결과:

4

53. 주요 상태코드

코드의미

200 요청 성공
201 새로운 데이터 생성 성공
401 인증 필요 또는 인증 실패
403 인증됐지만 권한 없음
404 요청한 자원을 찾을 수 없음
500 서버 내부 오류

54. 인증과 인가

401과 403을 이해하려면 인증과 인가의 차이를 알아야 한다.

인증(Authentication)

당신이 누구인가?

를 확인하는 과정이다.

예:

아이디 + 비밀번호
토큰
API 키

인가(Authorization)

확인된 당신에게 이 기능을 사용할 권한이 있는가?

를 확인하는 과정이다.

즉,

인증
 ↓
누구인지 확인
 ↓
인가
 ↓
무엇을 할 수 있는지 확인

순서로 이해할 수 있다.


55. 401과 403의 차이

둘 다 요청이 거부되는 상태지만 이유가 다르다.

비교401403

의미 누구인지 확인하지 못함 누구인지는 알지만 권한 없음
문제 인증 인가
예 토큰 만료 관리자 기능 접근
대응 다시 인증 권한 확인·요청

쉽게 말하면 다음과 같다.

401
"누구세요?"
403
"누군지는 아는데 들어갈 수 없습니다."

56. API 키

API를 이용할 때 사용자를 확인하기 위해 API 키를 사용할 수 있다.

API 키는 서비스에서 발급해 주는 긴 문자열이다.

요청마다 이 값을 함께 보낸다.

장점은 간단하다는 것이다.

하지만 키가 유출되면 직접 변경하기 전까지 계속 악용될 수 있기 때문에 관리가 중요하다.


57. 토큰

토큰도 사용자를 확인하기 위해 사용하는 문자열이다.

API 키와 비슷해 보이지만 보통 유효 기간이 있다.

예를 들어 로그인 후 토큰을 발급받고 일정 시간이 지나면 만료된다.

따라서 토큰이 유출되더라도 피해 시간이 제한될 수 있다.


58. Authorization 헤더

토큰은 보통 HTTP 요청의 헤더에 넣는다.

대표적인 형태가 다음이다.

headers = {
    "Authorization": "Bearer " + token
}

실제 전송 형태는 다음과 비슷하다.

Authorization: Bearer abcdef123456...

59. API 키와 토큰 비교

방식특징주의점

API 키 발급받은 긴 문자열을 요청마다 사용 유출되면 교체 전까지 사용 가능
토큰 인증 후 발급받아 사용 유효 기간 관리 필요

60. 오늘 전체 흐름

오늘 배운 내용을 하나로 연결하면 다음과 같다.

비정형 로그
    ↓
정규표현식
    ↓
named group
    ↓
딕셔너리
    ↓
정규화
    ↓
normalized_logs.json
    ↓
탐지 룰 실행
 ┌──────────────┬──────────────┬──────────────┐
 │              │              │
브루트포스     의심 IP       심야 성공 접속
 │              │              │
 └──────────────┴──────────────┘
                ↓
             경보
                ↓
       외부 정보가 필요하면
                ↓
               API
                ↓
        HTTP 요청 / 응답
                ↓
 상태코드 · 인증 · 인가 확인

핵심 정리

정규표현식

정규표현식은 문자열의 위치가 아니라 모양을 이용해 원하는 값을 찾는 방법이다.

re.search()

는 처음 맞는 값 하나를 찾고,

re.findall()

은 맞는 값을 모두 찾는다.

named group을 사용하면 비정형 문자열을 바로 딕셔너리 형태로 바꿀 수 있다.

(?P<user>...)
m.groupdict()

탐지 룰

정규화된 로그를 여러 관점에서 분석하면 서로 다른 이상 행동을 찾을 수 있다.

룰관점

브루트포스 계정별 실패 횟수
의심 IP IP별 접근 계정 수
심야 접속 비정상 시간대의 로그인 성공

탐지 기준은 절대적인 값이 아니라 실제 환경에 맞게 조정하는 임계값이다.


API와 HTTP

내 파일에 없는 정보를 얻으려면 외부 프로그램에 물어봐야 한다.

API는 다른 프로그램에 요청하기 위한 공식 창구이고, HTTP는 요청과 응답을 주고받는 규칙이다.

내 프로그램
    ↓
HTTP 요청
    ↓
API
    ↓
외부 서버
    ↓
HTTP 응답

상태코드

HTTP 응답의 상태코드를 보면 요청 결과를 빠르게 판단할 수 있다.

2xx → 성공
4xx → 요청 쪽 문제
5xx → 서버 문제

특히

401 → 인증 문제
403 → 권한 문제

라는 차이를 구분하는 것이 중요하다.


와
문제가 계속있어요진짜
저는 오늘도 문제한테 졌습니다..
그럼 아됴

 

안녕하세요.

긴 연휴를 끝내고 또 찾아왔습니다..

오늘도 화이팅 아자자..

 

 

오늘부터는 아침 과제(30분)이 추가됐는데요.

한번 해볼게요..!

 

더보기
더보기

보안 담당자는 왜 리눅스 명령어를 쓸까요?

> 일단 리눅스가 서버 환경에서 널리 쓰는 이유는 소스 코드가 공개되어 있고, 배포판마다 보안 업데이트 정책을 선택할 수 있으며, 명령줄과 스크립트를 통해 반복 작업을 자동화하기 좋아서 입니다.

그리고 서버 내 이상 징후를 실시간으로 탐지하고, 침해 사고 발생 시 공격자의 흔적을 추적하기 위해서 라고 합니다..

특히 GUI가 없는 서버 환경에서도 명령어를 통해서 시스템의 상태를 즉각적으로 파악해야한다고 하네요.

더보기
더보기

개발자와 보안 담당자는 왜 깃허브에 코드를 올릴까요?

> 일단 깃은 분산 버전 관리 시스템인데 깃허브는 그 중에서도 중앙 저장소 서비스이다.

굳이 쓰는 이유는 협업할 때 누가 어떤 작업을 하고 있는지 한눈에 파악할 수 있고, 프로젝트의 진행 상황을 추적가능하며 변경사항을 배포 전에 검증하고 코드와 문서가 체계적으로 축적되어 문서관리가 용이하다는 장점이 있다.

그리고 중앙 저장소가 백업 역할을 하며 서버 장애 시에도 쉽게 복구할 수 있고 데이터 손실 위험도 낮고 중앙에서 체계적인 권한 관리가 가능하다.

또한 버전 관리의 필요성으로는 코드가 어떻게 수정되었는지 시간순으로 기록할 수 있고, 버그가 생기면 준제가 없던 과거의 상태로 코드를 쉽게 되돌릴 수 있다.

 

이 말들이 무슨 뜻인지 찾아봅시다..

찾아볼 말 무엇을 알아 오면 되나
터미널 컴퓨터 시스템과 상호작용을 하기 위한 텍스트 기반 인터페이
경로 (path) 일상 생활에서는 어떤 목적지로 향하는 길 | 컴퓨터에서 목적지란 디렉토리이며, 찾아가는 길을 경로라고 함.
깃배시 (Git Bash) Windows 환경에서 Unix/Linux 스타일의 커맨드 라인 경험을 제공하는 애플리케이션
윈도우에서 필요한 이유는 리눅스/유닉스 환경의 터미널 명령어와 개발 도구들을 윈도우에서 그대로 사용하기 위해서.

 

 


진짜 수업 시작..!

 

Python 예외 처리와 logging 정리

오늘은 로그 파일을 처리할 때 꼭 필요한 예외 처리와 logging을 배웠다.

전체 흐름은 다음과 같다.

  • try · except — 에러가 나도 프로그램이 멈추지 않게 하기
  • 예외 이름으로 나눠 잡기
  • finally — 성공·실패와 관계없이 마지막에 실행하기
  • logging — 프로그램 실행 기록을 파일에 남기기
  • 최종적으로 log_parser.py 형태로 조립하기

1. 예외(Exception)란?

파이썬에서 오류는 크게 문법 오류와 예외로 나눠 볼 수 있다.

구분설명

문법 오류 코드 자체의 문법이 잘못되어 프로그램이 시작되지 않음
예외(Exception) 프로그램은 실행되지만 실행 도중 문제가 발생함

예를 들어 리스트에 존재하지 않는 값을 꺼내려고 하면 IndexError가 발생한다.

items = ["apple", "banana"]

print(items[0])
print(items[5])

실행 결과에서 "apple"은 정상적으로 출력되지만, items[5]를 실행하는 순간 예외가 발생하고 프로그램이 멈춘다.

즉, 예외는 프로그램 실행 도중 발생하는 문제라고 이해하면 된다.


2. 왜 try · except가 필요한가?

지금까지 작성한 로그 처리 코드는 로그 데이터가 모두 정상이라는 전제에서 동작했다.

예를 들어 CSV 파일의 모든 줄이 다음처럼 정확히 4개의 값으로 구성되어 있다고 가정할 수 있다.

09:01,admin,192.168.0.10,LOGIN_FAIL

하지만 실제 로그 파일에는 다음과 같은 데이터가 섞일 수 있다.

09:01,admin,192.168.0.10,LOGIN_FAIL
09:03,lee02
잘못된 로그
09:05,park03,192.168.0.12,LOGIN_SUCCESS

로그가 20만 줄인데 단 한 줄이 잘못됐다는 이유로 프로그램 전체가 멈춰 버리면 곤란하다.

그래서 문제가 있는 줄은 건너뛰고 나머지 로그는 계속 처리할 수 있도록 try · except를 사용한다.


3. try · except 기본 구조

기본 형태는 다음과 같다.

try:
    시도할 코드
except 예외이름:
    예외가 발생했을 때 실행할 코드

예를 들어 다음과 같이 사용할 수 있다.

parts = ["09:01", "admin"]

try:
    event = parts[3]
except IndexError:
    print("필드가 부족한 로그입니다.")

parts[3]은 존재하지 않기 때문에 IndexError가 발생한다.

그러면 프로그램이 종료되는 대신 except IndexError: 부분이 실행된다.


4. try · except의 실행 흐름

try 안의 코드가 정상적으로 실행되면 except는 실행되지 않는다.

try:
    print("정상 실행")
except IndexError:
    print("오류 발생")

실행 결과:

정상 실행

반대로 try 안에서 예외가 발생하면 그 이후의 코드는 실행하지 않고 바로 except로 이동한다.

items = ["A", "B"]

try:
    print(items[5])
    print("이 문장은 실행되지 않습니다.")
except IndexError:
    print("IndexError 발생")

실행 결과:

IndexError 발생

5. split()과 IndexError

로그 처리에서는 문자열을 나누기 위해 split()을 많이 사용한다.

line = "09:01,admin,LOGIN_FAIL"

parts = line.split(",")

print(parts)

결과:

['09:01', 'admin', 'LOGIN_FAIL']

리스트의 위치는 0부터 시작한다.

인덱스값

0 "09:01"
1 "admin"
2 "LOGIN_FAIL"

따라서 다음 코드는 문제가 없다.

print(parts[2])

하지만 다음 코드는 존재하지 않는 위치를 요청하기 때문에 오류가 발생한다.

print(parts[3])
IndexError

이런 상황에서 try · except가 필요하다.

try:
    event = parts[3]
except IndexError:
    print("필드가 부족합니다.")

6. pass

예외가 발생했을 때 아무 작업도 하지 않고 그냥 넘어가고 싶다면 pass를 사용할 수 있다.

try:
    event = parts[3]
except IndexError:
    pass

pass는 말 그대로

아무것도 하지 않는다.

라는 의미다.

로그 처리에서는 잘못된 줄을 조용히 건너뛰고 싶을 때 사용할 수 있다.


7. try · except 사용 규칙

기본적으로 기억할 내용은 네 가지다.

규칙설명

try: 실행을 시도할 코드를 작성
except 예외이름: 지정한 예외가 발생했을 때 실행
예외가 발생하면 try의 남은 코드는 건너뜀
예외가 발생하지 않으면 except는 실행되지 않음

그리고 가능하면 다음처럼 사용하지 않는 것이 좋다.

except:
    pass

예외 이름 없이 except:만 작성하면 예상하지 못한 버그까지 모두 숨길 수 있기 때문이다.

따라서 다음처럼 예외 이름을 지정하는 것이 좋다.

except IndexError:
    pass

8. 여러 종류의 예외

프로그램에서 발생할 수 있는 예외는 하나가 아니다.

로그 처리에서 자주 볼 수 있는 예외는 다음과 같다.

예외발생 상황

IndexError 리스트에 존재하지 않는 인덱스를 사용
ValueError 값의 형태가 올바르지 않음
FileNotFoundError 존재하지 않는 파일을 열려고 함

9. ValueError

ValueError는 자료형 자체는 처리할 수 있지만 값의 모양이 적절하지 않을 때 발생한다.

예를 들어 다음 코드는 정상이다.

number = int("9")

print(number)

결과:

9

하지만 다음 코드는 숫자로 변환할 수 없는 문자열이기 때문에 ValueError가 발생한다.

number = int("hello")
ValueError

예외 처리는 다음과 같이 할 수 있다.

try:
    number = int("hello")
except ValueError:
    print("숫자로 변환할 수 없습니다.")

10. 시간을 처리할 때의 ValueError

로그 데이터에서는 시간 문자열을 자주 처리한다.

time_text = "09:01"

hour, minute = time_text.split(":")

결과는 다음과 같다.

hour = "09"
minute = "01"

필요하다면 숫자로 변환할 수도 있다.

hour = int(hour)
minute = int(minute)

하지만 값이 다음처럼 깨져 있다면 문제가 발생할 수 있다.

AA:BB
try:
    hour, minute = "AA:BB".split(":")
    hour = int(hour)
    minute = int(minute)

except ValueError:
    print("시간 형식이 잘못되었습니다.")

11. FileNotFoundError

존재하지 않는 파일을 열려고 하면 FileNotFoundError가 발생한다.

with open("not_exist.csv", encoding="utf-8") as f:
    data = f.read()

예외 처리를 추가하면 프로그램이 바로 종료되는 것을 막을 수 있다.

try:
    with open("not_exist.csv", encoding="utf-8") as f:
        data = f.read()

except FileNotFoundError:
    print("파일을 찾을 수 없습니다.")

12. except를 여러 개 사용하기

하나의 try에 여러 종류의 예외를 처리할 수 있다.

try:
    value = int(parts[3])

except IndexError:
    print("필드가 부족합니다.")

except ValueError:
    print("숫자로 변환할 수 없습니다.")

예외가 발생하면 위에서부터 확인한 뒤 일치하는 예외 하나만 실행된다.


13. finally

finally는 예외 발생 여부와 관계없이 항상 마지막에 실행되는 블록이다.

try:
    print("파일 처리 시작")

except FileNotFoundError:
    print("파일 없음")

finally:
    print("처리 종료")

예외가 발생하지 않아도 finally는 실행된다.

파일 처리 시작
처리 종료

예외가 발생해도 실행된다.

파일 없음
처리 종료

즉,

성공하든 실패하든 마지막에 반드시 실행해야 할 작업

을 넣는 곳이 finally다.


14. 예외 처리 한눈에 정리

코드의미

try: 실행을 시도할 코드
except IndexError: 인덱스 오류 처리
except ValueError: 값 형식 오류 처리
except FileNotFoundError: 파일 없음 오류 처리
pass 아무 작업도 하지 않고 넘어감
finally: 성공·실패와 관계없이 항상 실행

15. logging이란?

지금까지는 문제가 발생하면 print()를 사용했다.

print("깨진 로그입니다.")

하지만 print()는 화면에 잠깐 표시될 뿐 기록으로 남지 않는다.

예를 들어 프로그램이 새벽 동안 자동으로 실행되고 있었다면 아침에 출근했을 때

새벽에 어떤 오류가 발생했는가?

를 확인하기 어렵다.

이 문제를 해결하는 것이 logging이다.

logging은 프로그램의 실행 상태를 파일에 기록할 수 있는 파이썬 기본 기능이다.


16. print와 logging의 차이

print()logging

화면에 출력 파일에 기록 가능
실행 당시 확인 나중에 다시 확인 가능
중요도 구분 없음 INFO / WARNING / ERROR 등 중요도 구분
간단한 확인에 적합 실제 프로그램 운영·유지보수에 적합

17. logging 기본 설정

먼저 logging 모듈을 불러온다.

import logging

그리고 기본 설정을 한다.

logging.basicConfig(
    filename="parser.log",
    level=logging.INFO
)

여기서 중요한 부분은 두 가지다.

filename

filename="parser.log"

로그를 저장할 파일 이름이다.

이를 지정하면 화면이 아니라 파일에 로그가 기록된다.

level

level=logging.INFO

어느 등급 이상의 로그를 기록할지 설정한다.


18. 로그 레벨

logging에는 로그의 중요도를 나타내는 로그 레벨이 있다.

오늘 배운 주요 레벨은 다음과 같다.

INFO
  ↓
WARNING
  ↓
ERROR

아래로 갈수록 더 심각한 상황이다.

레벨의미예시

INFO 정상적인 동작 기록 파일 읽기 완료
WARNING 주의가 필요한 상황 깨진 로그 발견
ERROR 심각한 오류 파일 열기 실패

19. logging 사용법

정상적인 실행 상태를 기록하고 싶다면 다음과 같이 작성한다.

logging.info("로그 파일 읽기 시작")

주의가 필요한 상황은 다음과 같다.

logging.warning("깨진 로그 발견")

심각한 오류는 다음과 같이 기록한다.

logging.error("파일을 열 수 없습니다.")

20. level=logging.INFO가 중요한 이유

다음 설정에서

logging.basicConfig(
    filename="parser.log"
)

level을 지정하지 않으면 기본값은 WARNING이다.

따라서 다음 코드는 파일에 남지 않을 수 있다.

logging.info("프로그램 시작")

INFO부터 기록하려면 반드시 다음처럼 작성한다.

logging.basicConfig(
    filename="parser.log",
    level=logging.INFO
)

21. 로그 메시지에 시간과 레벨 넣기

실제 프로그램에서는 언제 어떤 로그가 발생했는지 확인하는 것이 중요하다.

다음처럼 형식을 지정할 수 있다.

logging.basicConfig(
    filename="parser.log",
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s"
)

그러면 로그 파일에는 다음과 비슷한 기록이 남는다.

2026-09-28 14:20:11 INFO 로그 파일 읽기 시작
2026-09-28 14:20:11 WARNING 깨진 로그 발견
2026-09-28 14:20:11 ERROR 파일 처리 실패

22. 깨진 로그를 처리하는 예제

예를 들어 다음과 같은 로그가 있다고 하자.

09:01,admin,192.168.0.10,LOGIN_FAIL
09:03,lee02
09:05,park03,192.168.0.12,LOGIN_SUCCESS

코드는 다음과 같이 작성할 수 있다.

for line in lines:

    parts = line.strip().split(",")

    try:
        time = parts[0]
        user = parts[1]
        ip = parts[2]
        event = parts[3]

    except IndexError:
        logging.warning(f"깨진 로그: {line.strip()}")
        continue

    print(user, event)

깨진 줄을 만나도 프로그램 전체가 멈추지 않는다.

admin LOGIN_FAIL
park03 LOGIN_SUCCESS

깨진 로그는 별도로 파일에 남길 수 있다.


23. log_parser.py 형태로 조립하기

오늘 배운 내용을 합치면 로그 파서는 대략 다음 구조가 된다.

import logging

logging.basicConfig(
    filename="parser.log",
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s"
)

logging.info("로그 파서 시작")

try:
    with open("sample_logs_broken.csv", encoding="utf-8") as f:

        for line in f:

            parts = line.strip().split(",")

            try:
                time = parts[0]
                user = parts[1]
                ip = parts[2]
                event = parts[3]

            except IndexError:
                logging.warning(f"깨진 로그: {line.strip()}")
                continue

            print(time, user, ip, event)

except FileNotFoundError:
    logging.error("로그 파일을 찾을 수 없습니다.")

finally:
    logging.info("로그 파서 종료")

이 코드는 크게 세 가지 역할을 한다.

  1. 로그 파일이 없으면 FileNotFoundError를 처리한다.
  2. 파일 안에 깨진 줄이 있으면 IndexError를 처리한다.
  3. 프로그램의 시작·경고·종료 상황을 logging으로 기록한다.

Python 중첩 자료구조, JSON, 정규화 정리

오늘은 여러 개의 로그 데이터를 다루기 위해 필요한 중첩 자료구조, 데이터를 파일로 저장하고 다시 불러오는 JSON, 그리고 제각각인 로그 데이터를 같은 형태로 맞추는 정규화를 배웠다.

오늘의 흐름은 다음과 같다.

  • 중첩 자료구조 — 그릇 안의 그릇
  • JSON 네 명령 — dumps, loads, dump, load
  • 정규화 — 제각각인 데이터를 같은 형태로 만들기
  • normalize_logs.py 형태로 조립하기

1. 중첩 자료구조란?

중첩(Nested) 이란 하나의 자료구조 안에 또 다른 자료구조가 들어 있는 형태를 말한다.

예를 들어 다음과 같은 구조가 있다.

[
    {"user": "admin", "event": "LOGIN_FAIL"},
    {"user": "lee02", "event": "LOGIN_SUCCESS"}
]

바깥쪽은 리스트이고, 그 안에 여러 개의 딕셔너리가 들어 있다.

즉,

리스트
 ├─ 딕셔너리
 └─ 딕셔너리

와 같은 구조다.

실제 프로그램에서는 데이터를 한 개씩 따로 저장하기보다 여러 데이터를 묶어서 관리하는 경우가 많기 때문에 중첩 자료구조를 자주 사용한다.


2. 왜 중첩 자료구조가 필요한가?

로그 한 건을 다음처럼 딕셔너리로 표현할 수 있다.

log = {
    "user": "admin",
    "ip": "192.168.0.10",
    "event": "LOGIN_FAIL"
}

하지만 실제 로그는 한 건만 존재하지 않는다.

여러 건을 저장하려면 딕셔너리를 리스트에 넣을 수 있다.

logs = [
    {
        "user": "admin",
        "ip": "192.168.0.10",
        "event": "LOGIN_FAIL"
    },
    {
        "user": "lee02",
        "ip": "192.168.0.11",
        "event": "LOGIN_SUCCESS"
    }
]

그리고 나중에 계정별 통계를 만들고 싶다면 다음처럼 딕셔너리 안에 딕셔너리를 사용할 수도 있다.

report = {
    "admin": {
        "fail": 3,
        "success": 1
    },
    "lee02": {
        "fail": 2,
        "success": 4
    }
}

3. 리스트 안의 딕셔너리

리스트 안에 여러 개의 딕셔너리를 넣는 형태다.

books = [
    {"title": "파이썬", "price": 20000},
    {"title": "네트워크", "price": 25000}
]

구조를 그림처럼 보면 다음과 같다.

books
 ├─ [0]
 │   ├─ title : 파이썬
 │   └─ price : 20000
 │
 └─ [1]
     ├─ title : 네트워크
     └─ price : 25000

값을 꺼낼 때는 먼저 리스트의 번호를 선택한다.

print(books[0])

결과:

{'title': '파이썬', 'price': 20000}

그다음 해당 딕셔너리에서 원하는 키를 선택한다.

print(books[0]["title"])

결과:

파이썬

다른 데이터도 같은 방식이다.

print(books[1]["price"])

결과:

25000

즉,

books[0]["title"]

은

books
 ↓
0번째 데이터
 ↓
title

순서로 접근하는 것이다.


4. 리스트 안의 딕셔너리를 반복문으로 사용하기

로그처럼 동일한 구조의 데이터가 여러 건 있을 때는 for문과 함께 많이 사용한다.

logs = [
    {"user": "admin", "event": "LOGIN_FAIL"},
    {"user": "lee02", "event": "LOGIN_SUCCESS"},
    {"user": "park03", "event": "LOGIN_FAIL"}
]

for log in logs:
    print(log["user"])

결과:

admin
lee02
park03

for log in logs:가 실행될 때마다 리스트 안의 딕셔너리가 하나씩 log에 들어간다.

따라서 다음처럼 조건을 확인할 수도 있다.

for log in logs:

    if log["event"] == "LOGIN_FAIL":
        print(log["user"])

결과:

admin
park03

5. 딕셔너리 안의 딕셔너리

이번에는 딕셔너리 안에 또 다른 딕셔너리를 넣어 보자.

stores = {
    "seoul": {
        "count": 12,
        "open": True
    },
    "busan": {
        "count": 5,
        "open": False
    }
}

구조는 다음과 같다.

stores
 ├─ seoul
 │   ├─ count : 12
 │   └─ open : True
 │
 └─ busan
     ├─ count : 5
     └─ open : False

먼저 바깥쪽 키를 이용해 값을 꺼낼 수 있다.

print(stores["seoul"])

결과:

{'count': 12, 'open': True}

한 단계 더 들어가려면 다시 대괄호를 사용한다.

print(stores["seoul"]["count"])

결과:

12

즉,

stores["seoul"]["count"]

은

stores
 ↓
seoul
 ↓
count

순서로 들어간다.


6. 새로운 값을 추가하기

딕셔너리에 존재하지 않는 키를 지정하고 값을 넣으면 새로운 데이터가 만들어진다.

stores["daegu"] = {
    "count": 3,
    "open": True
}

결과적으로 stores는 다음과 같이 된다.

{
    "seoul": {"count": 12, "open": True},
    "busan": {"count": 5, "open": False},
    "daegu": {"count": 3, "open": True}
}

계정별 보고서를 만들 때도 같은 방식을 사용할 수 있다.

report["admin"] = {
    "fail": 0
}

7. 딕셔너리를 반복문으로 돌리기

딕셔너리를 for문으로 반복하면 기본적으로 키가 나온다.

report = {
    "admin": {"fail": 3},
    "lee02": {"fail": 2},
    "hacker": {"fail": 1}
}

for user in report:
    print(user)

결과:

admin
lee02
hacker

값도 함께 확인하려면 다음처럼 사용할 수 있다.

for user in report:
    print(user, report[user]["fail"])

결과:

admin 3
lee02 2
hacker 1

8. 중첩 자료구조 한눈에 정리

코드의미

logs[0] 리스트의 0번째 값을 가져온다
logs[0]["user"] 0번째 딕셔너리의 user 값을 가져온다
report["admin"] admin에 해당하는 딕셔너리를 가져온다
report["admin"]["fail"] admin의 fail 값을 가져온다
report["new"] = {...} 새로운 키와 값을 추가한다
for user in report: 딕셔너리의 키를 하나씩 가져온다

9. JSON이란?

JSON(JavaScript Object Notation) 은 데이터를 저장하거나 프로그램끼리 주고받을 때 많이 사용하는 데이터 형식이다.

사람이 읽기 비교적 쉽고, 여러 프로그래밍 언어에서 사용할 수 있다는 장점이 있다.

예를 들어 다음과 같은 형태다.

{
  "user": "admin",
  "event": "LOGIN_FAIL",
  "count": 3
}

파이썬의 딕셔너리와 매우 비슷하게 생겼다.

그래서 파이썬 데이터를 파일로 저장하거나 다른 프로그램으로 전달할 때 JSON을 많이 사용한다.


10. 왜 JSON이 필요한가?

파이썬에서 다음과 같은 딕셔너리를 만들었다고 하자.

report = {
    "admin": 3,
    "lee02": 2
}

이 데이터는 프로그램이 실행되는 동안에는 사용할 수 있다.

하지만 프로그램을 종료하면 메모리에 있던 값은 사라진다.

따라서 다음 프로그램에서도 사용하려면 파일로 저장해야 한다.

이때 사용하는 대표적인 형식이 JSON이다.

파이썬 데이터
       ↓
     JSON
       ↓
파일 저장 / 다른 프로그램에 전달

11. 직렬화란?

직렬화(Serialize) 란 프로그램 메모리 안에 있는 데이터 구조를 파일이나 네트워크로 전달할 수 있는 형태로 변환하는 것을 말한다.

쉽게 표현하면

파이썬의 딕셔너리
        ↓
문자열 형태의 JSON

으로 바꾸는 과정이다.

반대로 JSON 문자열을 다시 파이썬 자료형으로 바꾸는 과정도 가능하다.


12. JSON에서 사용하는 네 가지 명령

파이썬에서는 json 모듈을 사용한다.

import json

가장 중요한 함수는 네 개다.

함수변환

json.dumps() 파이썬 값 → JSON 문자열
json.loads() JSON 문자열 → 파이썬 값
json.dump() 파이썬 값 → JSON 파일
json.load() JSON 파일 → 파이썬 값

핵심은 s가 붙었는지 여부다.

dumps / loads
      ↓
문자열(string)

dump / load
      ↓
파일(file)

13. dumps — 파이썬 값을 문자열로

다음 딕셔너리가 있다고 하자.

import json

menu = {
    "name": "coffee",
    "price": 4500
}

json.dumps()를 사용하면 JSON 문자열로 변환된다.

text = json.dumps(menu)

print(text)

결과:

{"name": "coffee", "price": 4500}

중요한 점은 text가 딕셔너리가 아니라 문자열이라는 것이다.

확인해 보면 다음과 같다.

print(type(text))

결과:

<class 'str'>

14. loads — 문자열을 다시 파이썬 값으로

이번에는 JSON 문자열을 다시 파이썬 자료형으로 바꿔 보자.

back = json.loads(text)

print(back)

결과:

{'name': 'coffee', 'price': 4500}

다시 딕셔너리가 되었기 때문에 다음처럼 사용할 수 있다.

print(back["price"])

결과:

4500

정리하면 다음과 같다.

딕셔너리
   ↓
dumps
   ↓
문자열
   ↓
loads
   ↓
딕셔너리

15. dump — 파이썬 값을 JSON 파일에 저장

이번에는 데이터를 실제 파일로 저장해 보자.

import json

menu = {
    "name": "coffee",
    "price": 4500
}

with open("menu.json", "w", encoding="utf-8") as f:
    json.dump(menu, f)

이 코드를 실행하면 menu.json 파일이 만들어진다.

파일 내용은 대략 다음과 같다.

{"name": "coffee", "price": 4500}

16. load — JSON 파일을 다시 읽기

저장된 JSON 파일은 json.load()를 사용해 읽는다.

with open("menu.json", encoding="utf-8") as f:
    back = json.load(f)

print(back)

결과:

{'name': 'coffee', 'price': 4500}

따라서 다음과 같이 사용할 수 있다.

print(back["name"])

결과:

coffee

17. dumps와 dump의 차이

둘은 이름이 비슷하지만 목적이 다르다.

dumps

text = json.dumps(data)

파이썬 데이터를 문자열로 반환한다.

dump

json.dump(data, f)

파이썬 데이터를 파일에 저장한다.

즉,

s 있음 → string
s 없음 → file

으로 기억하면 쉽다.


18. loads와 load의 차이

마찬가지로 읽을 때도 구분된다.

loads

data = json.loads(text)

문자열을 읽는다.

load

data = json.load(f)

파일을 읽는다.


19. ensure_ascii=False

JSON 파일에 한글을 저장할 때 다음 옵션을 사용할 수 있다.

ensure_ascii=False

예를 들어 다음 데이터를 저장한다고 하자.

menu = {
    "name": "아메리카노",
    "price": 4500
}

다음처럼 저장할 수 있다.

with open("menu.json", "w", encoding="utf-8") as f:
    json.dump(
        menu,
        f,
        ensure_ascii=False
    )

그러면 한글이 그대로 저장된다.

{
  "name": "아메리카노",
  "price": 4500
}

20. indent

indent는 JSON 파일을 보기 좋게 줄바꿈해 주는 옵션이다.

json.dump(
    menu,
    f,
    ensure_ascii=False,
    indent=2
)

indent=2는 들여쓰기를 두 칸 사용한다는 뜻이다.

옵션이 없으면 다음처럼 한 줄로 저장될 수 있다.

{"name":"coffee","price":4500}

indent=2를 적용하면 다음처럼 읽기 쉬워진다.

{
  "name": "coffee",
  "price": 4500
}

21. JSON 한눈에 정리

코드의미

json.dumps(data) 파이썬 값 → JSON 문자열
json.loads(text) JSON 문자열 → 파이썬 값
json.dump(data, f) 파이썬 값 → JSON 파일
json.load(f) JSON 파일 → 파이썬 값
ensure_ascii=False 한글을 그대로 저장
indent=2 들여쓰기와 줄바꿈 적용

22. 정규화란?

정규화(Normalize) 는 형식이 서로 다른 데이터를 일정한 형태로 맞추는 작업이다.

예를 들어 다음 두 로그가 있다고 하자.

09:01,admin,192.168.0.10,LOGIN_FAIL
09:02,lee02,192.168.0.11,login_fail

사람이 보면 둘 다 같은 LOGIN_FAIL 이벤트다.

하지만 프로그램 입장에서는

LOGIN_FAIL
login_fail

은 서로 다른 문자열이다.

따라서 그대로 통계를 내면 서로 다른 사건으로 계산될 수 있다.

이 문제를 해결하기 위해 데이터를 정규화한다.


23. 문자열을 대문자로 맞추기

문자열을 모두 대문자로 바꾸려면 .upper()를 사용한다.

event = "login_fail"

event = event.upper()

print(event)

결과:

LOGIN_FAIL

따라서 다음과 같은 데이터가 있더라도

login_fail
LOGIN_FAIL
Login_Fail

모두

LOGIN_FAIL

로 맞출 수 있다.


24. 필수 필드 확인하기

로그에는 반드시 필요한 값이 있을 수 있다.

예를 들어 로그의 구조가 다음과 같다고 하자.

시간,사용자,IP,이벤트

정상적인 로그는 4개의 값이 필요하다.

09:01,admin,192.168.0.10,LOGIN_FAIL

split(",")으로 나누면

[
    "09:01",
    "admin",
    "192.168.0.10",
    "LOGIN_FAIL"
]

총 4개의 값이 나온다.

따라서 다음과 같이 확인할 수 있다.

if len(parts) >= 4:
    print("정상 로그")

len()은 리스트 안에 값이 몇 개인지를 알려준다.


25. 값이 부족한 로그

다음 로그는 IP가 없다.

09:01,admin,LOGIN_FAIL

나누면 다음과 같다.

[
    "09:01",
    "admin",
    "LOGIN_FAIL"
]

값이 세 개뿐이다.

이 상태에서 무조건

ip = parts[2]
event = parts[3]

처럼 접근하면 parts[3]에서 IndexError가 발생한다.

따라서 먼저 길이를 확인할 수 있다.

if len(parts) >= 4:
    time = parts[0]
    user = parts[1]
    ip = parts[2]
    event = parts[3]

26. 기본값 사용하기

데이터가 없더라도 프로그램에서 처리해야 하는 경우 기본값을 사용할 수 있다.

예를 들어 IP가 없으면

unknown

이라는 값을 넣을 수 있다.

ip = "unknown"

다음처럼 조건을 이용할 수도 있다.

if len(parts) >= 4:
    ip = parts[2]
else:
    ip = "unknown"

이렇게 하면 값이 없어도 항상 ip라는 필드를 유지할 수 있다.


27. 정규화 전과 후

정규화 전 데이터가 다음과 같다고 하자.

09:01,admin,192.168.0.10,login_fail
09:02,lee02,192.168.0.11,LOGIN_FAIL
09:03,park03,,Login_Success

정규화를 거치면 다음처럼 일정한 구조로 만들 수 있다.

[
  {
    "time": "09:01",
    "user": "admin",
    "ip": "192.168.0.10",
    "event": "LOGIN_FAIL"
  },
  {
    "time": "09:02",
    "user": "lee02",
    "ip": "192.168.0.11",
    "event": "LOGIN_FAIL"
  },
  {
    "time": "09:03",
    "user": "park03",
    "ip": "unknown",
    "event": "LOGIN_SUCCESS"
  }
]

모든 데이터가 같은 키를 가지고 있고 이벤트 이름도 같은 형태로 맞춰졌다.

이 상태가 되면 이후의 분석이 훨씬 쉬워진다.


28. 왜 정규화가 중요한가?

정규화하지 않은 데이터는 분석 과정에서 여러 문제를 만들 수 있다.

예를 들어 다음 이벤트들이 있다고 하자.

LOGIN_FAIL
login_fail
Login_Fail

정규화하지 않으면 프로그램은 세 개를 다른 값으로 볼 수 있다.

하지만 모두 .upper()를 적용하면

LOGIN_FAIL
LOGIN_FAIL
LOGIN_FAIL

로 통일된다.

따라서 정확한 집계가 가능해진다.

정규화의 장점은 다음과 같다.

장점설명

일관성 모든 데이터가 같은 형태를 가짐
분석 편의성 조건문과 통계 처리가 쉬워짐
오류 감소 없는 필드나 잘못된 형식으로 인한 문제 감소
확장성 이후 다른 프로그램에서도 쉽게 사용 가능

29. normalize_logs.py 조립

오늘 배운 내용을 하나로 합치면 다음과 같은 구조가 된다.

import json

normalized_logs = []

with open("sample_logs.csv", encoding="utf-8") as f:

    for line in f:

        line = line.strip()

        if not line:
            continue

        parts = line.split(",")

        if len(parts) < 3:
            continue

        time = parts[0]
        user = parts[1]

        if len(parts) >= 4:
            ip = parts[2]
            event = parts[3]
        else:
            ip = "unknown"
            event = parts[2]

        if ip == "":
            ip = "unknown"

        event = event.upper()

        log = {
            "time": time,
            "user": user,
            "ip": ip,
            "event": event
        }

        normalized_logs.append(log)

with open(
    "normalized_logs.json",
    "w",
    encoding="utf-8"
) as f:

    json.dump(
        normalized_logs,
        f,
        ensure_ascii=False,
        indent=2
    )

30. normalize_logs.py의 흐름

위 코드의 흐름을 단계별로 보면 다음과 같다.

로그 파일 읽기
      ↓
한 줄씩 가져오기
      ↓
split(",")으로 나누기
      ↓
필드 개수 확인
      ↓
없는 값에 기본값 적용
      ↓
event를 대문자로 변경
      ↓
딕셔너리 생성
      ↓
리스트에 추가
      ↓
JSON 파일로 저장

31. 한 줄씩 살펴보기

먼저 결과를 저장할 리스트를 만든다.

normalized_logs = []

로그 파일을 읽는다.

with open("sample_logs.csv", encoding="utf-8") as f:

한 줄씩 처리한다.

for line in f:

앞뒤의 공백과 줄바꿈을 제거한다.

line = line.strip()

빈 줄이면 건너뛴다.

if not line:
    continue

쉼표 기준으로 나눈다.

parts = line.split(",")

필요한 필드가 너무 부족한 데이터는 건너뛴다.

if len(parts) < 3:
    continue

이벤트 이름을 대문자로 통일한다.

event = event.upper()

한 건의 로그를 딕셔너리로 만든다.

log = {
    "time": time,
    "user": user,
    "ip": ip,
    "event": event
}

리스트에 저장한다.

normalized_logs.append(log)

마지막으로 JSON 파일로 저장한다.

json.dump(
    normalized_logs,
    f,
    ensure_ascii=False,
    indent=2
)

32. 정규화 한눈에 정리

코드의미

.upper() 문자열을 모두 대문자로 변경
len(parts) 데이터가 몇 칸인지 확인
if len(parts) >= 4: 필요한 칸이 있는지 확인
"unknown" 값이 없을 때 사용할 기본값
.append() 정규화한 로그를 리스트에 추가
json.dump() 정규화 결과를 JSON 파일로 저장

 

오늘도 몰아쳣어여

진짜로 연휴 끝나자마자 공부를 하니까 정말 힘이드네여..

 

안녕하세요.

벌써 실습을 시작하고 둘쨋날이 찾아왔네요..

지금까지 계속 셀프 브랜딩과 함께 했는데, 갑자기 냅다 실습으로 던져졌기 때문에 조금 서운합니다.

그럼 시작.

 

오늘의 순서
  1. 조건문 — 한 건을 판단한다
  2. 반복문 — 모든 로그에 같은 판단을 한다
  3. 집합과 Counter — 사람 수와 횟수를 센다
  4. 종합 — 로그 목록에서 확인할 계정을 찾는다
  5. 함수 — 여러 줄의 일에 이름을 붙인다.
  6. 파일 — 코드 밖의 글자를 읽고 쓴다.
  7. 모듈 — 함수를 다른 파일에 두고 불러 쓴다.
  8. CSV — 칸 이름이 붙은 표 파일을 읽는다.

 

조건문

값에 따라 실행할 길을 고른다.

 

1) 참일 때만 들여쓴 줄을 실행한다.

2) if ·else로 두 갈래를 만든다.

3) elif로 세 단계를 나눈다.

 

조건문 용어

용어(한글발음) 한 줄 뜻 비유
조건문
if, 이프
조건이 참일 때만 그 줄을 실행하는 문장 갈림길의 표지판
엘리프
elif
앞 조건이 거짓일 때 다시 따져 보는 갈래 두 번째 표지판
들여쓰기
indent, 인덴트
줄 앞의 빈칸으로 묶음을 나타내는 것 문단의 들여쓰기

 

조건에 쓰는 비교기호 여섯 개

기호 뜻 예 - fail_count가 5일 때 결과
== 같다 fail_count == 5 True
!= 다르다 fail_count != 5 False
> 크다 fail_count > 5 False
>= 크거나 같다 fail_count >= 5 True
< 작다 fail_count < 3 False
<= 작거나 같 fail_count <= 5 True

 

조건 연산자

and는 두 조건이 모두 참이여야 합니다 > 둘 중 하나라도 거짓이면 전체가 거짓임.

or은 하나만 참이여도 됩니다.

 

지킬 것 네 가지

지킬 것 이렇게
if 뒤에 조건, 줄 끝에 콜론 if log["result"] == "FAIL":
참일 때 할 일은 네칸 들여쓰기 들여쓰기가 끝나는 곳이 if 의 끝
갈래가 여럿이면 elif, 나머지는 else else 뒤에는 조건을 쓰지 않는다
범위가 겹치면 큰 기준을 먼저 >= 5 를 >= 2 위에

 

반복문

같은 일을 모든 값에 적용한다.

 

for

1) 목록에서 하나씩 꺼낸다.

2) 로그 딕셔너리를 하나 씩 본다.

3) 실패한 계정만 모은다.

# for 값 in 목록
# 🔎 코드를 읽는 법
# `for 값 in 목록`은 ‘목록에서 값을 하나씩 꺼내며’라고 읽습니다.
# 들여쓰기가 끝나는 곳까지가 한 번에 반복할 일입니다.

users = ["kim01", "lee02", "park03"]  # 확인할 계정 목록

for user in users:  # 계정을 하나씩 꺼내 반복
    print(user)
    
kim01
lee02
park03

 

반복문 용어

용어(한글발음) 한 줄 뜻 비유
반복문
for, 포
모아 둔 것을 하나씩 꺼내 같은 일을 되풀이 출석부 한 명씩 부르기
while
와일
조건이 참인 동안 계속 되풀이 물이 끓을 때까지 기다리기

 

range() 함수

정해진 횟수만큼 되풀이한다.

정수 시컨스를 생성하는 Python의 내장 함수로 반복문에 자주 사용됩니다!

# range(stop): 0부터 stop-1까지의 정수를 생성합니다.
print(list(range(5))) # 출력: [0, 1, 2, 3, 4]

# range(start, stop): start부터 stop-1까지의 정수를 생성합니다.
print(list(range(2, 6))) # 출력: [2, 3, 4, 5]

# range(start, stop, step): start부터 stop-1까지 step 간격으로 정수를 생성합니다.
range(start, stop, step): start부터 stop-1까지 step 간격으로 정수를 생성합니다.
range()는 for 반복문과 함께 사용하여 특정 횟수만큼 반복 작업을 수행할 수 있습니다.
for i in range(3):
   print(i) # 출력: 0, 1, 2

while

조건이 참인 동안 되풀이한다.

fail_count = 0

while fail_count < 3:
fail_count = fail_count + 1
print(fail_count)

1
2
3
for (담아 둔 것의 개수만큼) while (조건이 참인 동안)
리스트·딕셔너리·문자열을 돈다 몇 번 돌지 미리 모를 때 쓴다
개수가 정해져 있어 저절로 끝난다 조건을 바꾸는 줄이 꼭 필요하다
로그 목록 돌기는 거의 for 없으면 멈추지 않는다

 

반복문 지킬것 네 가지

지킬 것 이렇게
① for 값 in 목록 : 으로 쓴다 값 이름은 목록 이름의 단수형 — logs 에서 log
② 줄 끝에 콜론, 반복할 일은 네 칸 들여쓰기 if 와 같은 규칙
③ 모을 때는 빈 목록을 반복문 위에 한 번만 안에 만들면 바퀴마다 비워진다
④ 결과는 반복이 끝난 뒤 한 번만 출 print 를 안에 두면 바퀴마다 찍힌다

 

집합 · Counter

중복을 지우고 횟수를 센다. 실패한 사람이 몇 명인지, 각자 몇 번 실패했는 지를 구분합니다.

 

1) 중복을 지우고 싶을 때

2) 값마다 횟수를 세고 싶을 때

3) 계정과 횟수를 함께 돌고 싶을 때

4) 기준 이상인 계정만 찾고 싶을 때

 

🔎 코드를 읽는 법
`set`은 ‘몇 명’, `Counter`는 ‘각자 몇 번’이라는 질문에 답합니다.
먼저 무엇을 세려는지 말로 정하세요.

 

집계 용어

용어 (한글발음) 한 줄 뜻 비유
집합
set, 셋
같은 값을 한 번만 담는 그릇 중복 없는 출석 명단
카운터
Counter
값마다 몇 번 나왔는지 세어 주는 도구 정 자로 표시하는 계수기
임계값
Threshold, 스레숄드
이 값을 넘으면 조치한다고 정해둔 선 수위 경보선
하드코딩
hard coding, 하드코딩
바뀔 값을 코드 한가운데 박아두는 것 벽에 못으로 박아 놓기



1.1) set()은 같은 값을 하나만 남깁니다. 

set은 순서를 지키지 않습니다. (순서가 필요하면 리스트를 쓰면 됨.)

set은 번호로 꺼낼 수 없다.

# 중복이 있는 실패 계정 목록을 준비합니다.
failed_users = ["kim01", "kim01", "lee02", "park03", "kim01"]
unique_users = set(failed_users)  # 중복 제거

print(unique_users)
print(f"실패 계정 {len(unique_users)}명")  # 서로 다른 계정 수 확인

{'kim01', 'park03', 'lee02'}
실패 계정 3명

set 메서드

메서드 뜻
add() 집합에 요소 추가하기
remove(), discard() 집합에서 요소 제거하기
union() 합집합 구하기
intersection() 교집합 구하기
difference() 차집합 구하기

 

2.1) Counter는 값마다 나온 횟수를 딕셔너리처럼 돌려줍니다.

Counter를 쓰려먼 맨 위에 한 줄(import 선언 해야함. ex.from collections import Counter)값마다 몇 번인지 센다.없는 계정은 0이다.(딕셔너리에서는 없는 키 값은 KeyError가 발생함, 근데 Counter는 0을 출력)

.items()는 키와 값을 한 쌍씩 꺼낸다.

from collections import Counter  # 항목별 횟수를 세는 도구

# 중복이 있는 실패 계정 목록을 준비합니다.
failed_users = ["kim01", "kim01", "lee02", "park03", "kim01"]
counts = Counter(failed_users)  # 계정별 횟수 계산

print(counts)
print(counts["kim01"])  # kim01의 횟수만 확인

Counter({'kim01': 3, 'lee02': 1, 'park03': 1})
3

2.2) .items()는 키와 값을 한 쌍씩 꺼낸다.

한 번 꺼낼 때마다 (계정, 횟수) 두 값이 붙어서 나옵니다.

.items() 없이 돌면 계정 이름만 나옵니다. 두 이름에 나눠 담을 수 없습니다.(ValueError)

 

from collections import Counter  # 항목별 횟수를 세는 도구

# 실패 계정별 횟수를 계산합니다.
counts = Counter(["kim01", "kim01", "lee02", "park03", "kim01"])
for user, count in counts.items():  # 계정과 횟수를 함께 반복
    print(f"{user}: {count}회")

kim01: 3회
lee02: 1회
park03: 1회

Counter와 기준값의 지킬 것 네 가지

지킬 것 이렇게
① 맨 위에 from collections import Counter 빠뜨리면 NameError
② Counter( 목록 ) 으로 세고, 대괄호로 한 계정 없는 계정은 0
③ 계정과 횟수를 같이 쓰려면 .items() for user, count in counts.items():
④ 기준값은 맨 위에 대문자 이름으   THRESHOLD = 2

 

set vs counter

set counter
몇 명인지 알 수 있다 {'kim01':3, 'lee02':1} 모양
누가 몇 번인지는 사라진다 .items

 

더보기

 (함수, 메서드의 차이) 미니과제(가독성있는 문서화,,?)

1. 함수(function)

기본 구조 >> 함수명()

- 독립적으로 존재하는 코드의 묶음

- 특정 클래스에 종속되지 않는다.

> 호출방식 : 이름만으로 직접 호출한다. (ex: print(), len(), my_func())

 

2. 메서드(method)

기본 구조 >> object.method_name()

- 클래스 내부에 정의되어 특정 객체에 종속된 함수

- 첫번째 인자로 self를 포함함.

> 호출방식 : 객체 뒤에 점(.)을 찍고 호출한다. (ex: object.split(), object.append())

 

함수

함수는 여러 줄의 코드를 묶어 이름을 붙인 것.

이름을 부르면 묶인 코드가 실행됨.

 

1) 적어 두고, 부를 때 실행된다.

2) 부를 때 값을 넘긴다.

 

함수 용어

용어 (한글발음) 한 줄 뜻 비유
함수
function, 펑션
여러 줄짜리 일에 이름을 붙여 둔 것 이름 붙인 조리법
매개변수
parameter, 파라미터
함수가 받아 쓰는 값의 이름 조리법의 재료 칸
기본값
default, 디폴트
안 넘겨주면 대신 쓰는 값 기본 매운맛
return
리턴
함수가 결과를 밖으로 돌려주는 것 완성된 요리 내주기

 

함수를 만드는 다섯가지 규칙

  1. def로 시작하고 한 칸 띄운 뒤 이름을 쓴다.
  2. 이름 뒤에는 괄호 (), 줄 끝에는 콜론 :을 붙인다. 하나라도 빠뜨리면 SyntaxError가 남.
  3. 함수가 하는 일은 그 아래에 네 칸 들여쓴다. 들여쓰기가 끝나는 곳이 함수의 끝.
  4. 이름은 무엇을 하는지 알 수 있게 짓는다. 소문자와 밑줄을 쓴다. — count_failed, show_alert.
  5. 정의가 먼저, 호출이 나중이다. 부를 때도 괄호를 꼭 붙인다.

🔎 코드를 읽는 법
`def` 줄은 ‘이런 일을 이 이름으로 부르겠다’고 적어 두는 것

적기만 하면 아무 일도 일어나지 않음. 이름 뒤에 괄호를 붙여 불러야 실행됨.

def say_hello():
	print("안녕하세요")
    
say_hello()  # 부르는 순간 실행된다
say_hello()  # 또 부르면 또 실행된다
    
안녕하세요
안녕하세요

함수에서 쓰는 말

말 뜻 예
정의한다 함수를 적어 두는 일. def 로 시작한다 def show_alert(user):
호출한다 이름을 불러 실제로 실행하는 일 show_alert("kim01")
매개변수 함수가 받아 쓸 값에 붙인 이름 (괄호 안) user
인자 부를 때 실제로 넘기는 값 "kim01"
반환값 함수가 돌려주는 결과 return count 의 count

 

return

> 결과를 돌려준다.

def add(a, b):
	return a + b
    
result = add(3, 5)  # 돌려받은 8이 result에 담긴다
print(result)

8

파일

코드 밖의 글자를 읽고 쓴다.

 

1) 파일에 글자를 쓰고 싶을 때

2) 파일 전체를 읽고 싶을 때

3) 한 줄씩 읽고 싶을 때

4) 특정 글자가 든 줄만 고르고 싶을 때

5) 기존 파일 뒤에 이어서 쓰고 싶을 때

6) 내 코드를 파일로 남기고 싶을 때

 

파일 용어

용어 (한글발음) 한 줄 뜻 비유
open
오픈
파일을 여는 파이썬 명령 서랍 열기
mode
파일 모드, 모드
읽기·쓰기·덧붙이기 중 무엇으로 열지 읽기 전용인지 필기 가능인지
with문
위드
블록이 끝나면 파일을 알아서 닫는 문장 자동으로 닫히는 문
encoding
인코딩
글자를 숫자로 바꿔 저장하는 방식 글자를 적는 규칙

 

모드 세 가지

모드 무엇을 하나 원래 내용은
"r" 읽기
모드를 안 쓰면 이것
파일을 읽는다 그대로 있다
"w" 새로 쓰기 없으면 만들고, 있으면 새로 쓴다 통째로 지워진다
"a" 덧붙이기 맨 뒤에 이어서 쓴다 그대로 두고 뒤에 붙인다

 

파일 읽기

> read()는 통째로, 모드를 적지 않으면 기본이 읽기 모드입니다.

with open("memo.txt", "w", encoding="utf-8") as f:
f.write("첫 번째 줄\n두 번째 줄\n")
with open("memo.txt", encoding="utf-8") as f:
print(f.read())

첫번째 줄
두번째 줄

with를 쓰는 이유! >> 닫는 것을 잊지 않으려고

1) 연 파일은 닫아야 한다 > 안 닫으면 쓴 내용이 저장되지 않거나 다른 프로그램이 못 연다

2) with 는 묶음이 끝나면 저절로 닫는다 > 중간에 오류가 나도 닫힌다

3) 한글 파일에는 encoding="utf-8" > 없으면 컴퓨터마다 다르게 읽어 글자가 깨진다

 

파일 지킬 것 네 가지

지킬 것 이렇게
① with open("memo.txt", encoding="utf-8") as f: 로 연다 닫는 것을 파이썬이 대신 해 준다
② 모드는 셋 — 안 쓰면 읽기, "w" 새로 쓰기, "a" 덧붙이기 기록을 쌓을 때는 반드시 "a
③ 한글 파일에는 encoding="utf-8" 열 때마다 붙인다
④ write 는 줄을 안 바꾼다 줄 끝마다 을 직접 붙인다

 

모듈

함수를 다른 파일에 두고 불러 쓴다.

 

1) 자주 쓰는 함수를 따로 모아 두고 싶을 때

2) 파이썬에 이미 들어 있는 도구를 쓰고 싶을 때

3) 지금 날짜와 시각을 쓰고 싶을 때

4) 글자로 적힌 시각에서 시만 꺼내고 싶을 때

 

모듈 용어

용어 (한글발음) 한 줄 뜻 비유
모듈
module, 모듈
함수 여럿을 모아 둔 파이썬 파일 하나 요리책 한 권
임포트
import
다른 파일의 함수를 끌어다 쓰는 것 옆 책에서 조리법 빌리기
표준 라이브러리
standard library
파이썬을 깔면 함께 오는 모듈 묶음 기본으로 딸린 요리책

 

%%writefile

> 셀을 실행하지 말고 파일로 저장하라. (코랩한테 시키는 명령어)

셀에 쓰는 줄 코랩이 하는 일 화면에 나오는 
%%writefile utils.py 셀 내용을 utils.py 파일로 저장한다.
코드는 실행하지 않는다
Writing utils.py
!python utils.py 저장한 파일을 실행한다 코드의 출력
왼쪽 📁 파일 탭 만들어진 파일을 눈으로 확인한다 utils.py

 

표준 라이브러리

>> 이미 들어 있는 요리 책

모듈 무엇을 하나 언제 쓰나
collections Counter 가 여기 있다 오전에 이미 썼다
datetime 날짜와 시각을 다룬다 지금
csv 쉼표로 나뉜 표 파일을 읽는다 8교시
json · logging 결과 저장 · 기록 남기기 다음 시간

 

CSV

칸 이름이 붙은 표 파일을 읽는다.

 

1) 표 파일을 줄마다 읽고 싶을 때

2) 칸 이름으로 값을 꺼내고 싶을 때

3) 칸 순서가 바뀐 파일이 와도 괜찮은 이유

4) 이름을 남이 알아보게 짓고 싶을 때

 

CSV 용어

용어 (한글발음) 한 줄 뜻 비유
CSV
씨에스브이
쉼표로 칸을 나눈 표 파일 줄 친 장부
header
헤더
표 첫 줄에 적힌 칸 이름들 장부의 항목 이름
DictReader
딕트리더
표를 딕셔너리로 읽어 주는 도구 이름표를 붙여 주는 기계
PEP8
펩에이트
파이썬 코드를 어떻게 적을지 정한 관례 회사 문서 서식

 

 


 

종합은 대충 로그목록에서 같은 데이터를 이용해서 조건문, 반복문, Counter, 기준값 판정을 하는 문제를 풀어봤습니다.

오전 수업에 대한 전체적인 총평은 비전공자가 냅다 배우기에는 속성,, 강의였다,,?

저는 전공자라서 한번 배웠던 경험으로 배우면서 까먹었던 개념을 다시 한번 익힐 수 있게 되었던 것 같고,,

바위 틈새에 모래 끼워넣는 느낌으로 배웠습니다..

 

 

와 위에는 오전에 썼던 거구요..

지금 오후 다끝나고 블로그 정리하니까 너무 힘드네여,,,,

몰아치는,, 하루,,,,,,,,,,,,,,,,,, 내일부터는 추석이라 4일동안 쉽니다.

그때 동안 리프레쉬하고 다시 열정가득,,? 돌아와볼게여,, 아됴.

+ Recent posts