LG DX DATA SCHOOL

12/24 상속, 정규 표현식

getfeelingsfrom 2025. 12. 24. 17:28

상속이란 

기존 클래스를 수정하지 않고 기능을 확장하기 위한 방법 

  • 부모 클래스의 모든 기능을 그대로 사용 가능 => 다형성 
  • 필요한 기능만 자식 클래스에서 추가 또는 변경 가능
  • 라이브러리나 공용 클래스를 활용할 때 특히 중요

 

  1. 코드를 공유
  2. 여러 클래스를 한꺼번에 제어 가능 = 업캐스팅(Upcasting) 화살표가 부모 클래스를 향하게 

🔁 업캐스팅(Upcasting)이란?
자식 클래스 객체를 부모 클래스 타입으로 참조하는 것을 말한다.
즉,객체는 자식 & 참조 변수 타입은 부모인 상태다.

parent = Child()   # 업캐스팅


이때 화살표(참조 방향)는 항상 부모 클래스를 향한다.

 

Sub(자식) is a Super 
👉 자식 클래스는 부모 클래스의 한 종류이다

  • Sub 클래스는 Super 클래스에 포함된다
  • 자식은 부모의 성질과 기능을 모두 가진다
  • 따라서 자식 객체는 부모 타입으로 참조 가능 (업캐스팅 가능)

  • Character : 공통 부모 클래스
  • Player / Enemy : Character를 상속받는 자식 클래스
  • Player is a Character
  • Enemy is a Character
  1. 일반화
    여러 클래스의 공통된 속성과 행위를 추출하여 상위 클래스로 묶는 것
    👉 구체적인 것들에서 공통점을 뽑아 더 추상적인 개념으로 만드는 과정
    설계 관점: Player, Enemy → Character (일반화)
  2. 구체화
    일반적인 클래스를 상속받아 더 구체적인 클래스로 확장하는 것
    구현 관점: Character → Player, Enemy (구체화)

 

📌 설계화와 구현 방식 정리

설계화 : Bottom- Up & Top-Down

구현 : Top-Down

 


1️⃣ 설계화 – Bottom-Up 방식

  • 구체적인 클래스부터 설계
  • 세부 기능 → 공통 개념을 추출
  • 여러 하위 클래스의 공통점을 묶어 상위 클래스(일반화) 생성
  • Player, Enemy → 공통 요소 추출 → Character
  • 현실 객체 분석에 유리
  • 클래스 구조 도출에 적합

 

2️⃣ 설계화 – Top-Down 방식

  • 가장 추상적인 개념부터 설계
  • 상위 클래스 먼저 정의
  • 이후 역할별로 하위 클래스(구체화) 분리
  • Character → Player / Enemy
  • 시스템 구조를 먼저 잡을 때 유리
  • 전체 아키텍처 파악에 적합

 

3️⃣ 구현(코딩) – Top-Down 방식

  • 부모 클래스부터 구현
  • 공통 기능을 먼저 완성
  • 이후 자식 클래스에서 기능 확장
  • 이유 : 코드 중복 방지
  • 상속·업캐스팅·다형성 활용 가능
  • 변경 영향 최소화

 


 

import random 
class Character:
    def __init__(self):
        self.__x = random.randint(0, 800) # __ : private : 자식에게 안 보여줌(외부접근 제어)
        self.__y = random.randint(0, 600)
        #self.__myShape = "Character"

    def display(self):
        print(self.__x, end='\t')
        print(self.__y, end='\t')
        #print(self.__myShape)
        #print("Character")

    def moveLeft(self,dist):
        self.__x -=dist
        self.display()

    def moveRight(self,dist):
        self.__x += dist
        self.display()

    def getX(self):#외부에서 쓸라고
        return self.__x
    def setX(self, x):
        self.__x=x
        return self.__x

    def getY(self):
        return self.__y
    def sety(self, y):
        self.__y=y
        return self.__y

    def moveUp(self,dist):
        self.__y -=dist
        self.display()
    def moveDown(self,dist):
        self.__y +=dist
        self.display()

class Player(Character):
    #override (함수 재정의 - 원형 유지 )
    def display(self): 
       # print(self.__x, end='\t') : self.__x 불가 
        # print(self.getX(), end='\t')
        # print(self.getY(), end='\t')
        
        super().display() #한단계위 부모꺼 사용 가능 
        print("Player")


class Enemy(Character):

    def display(self): #재정의
        super().display() #한단계위 부모꺼 사용 가능 
        print("Enemy")

 

 

1️⃣ 전체 구조 한눈에 보기

Character
 ├─ private: __x, __y
 ├─ moveLeft(), moveRight()
 ├─ moveUp(), moveDown()
 ├─ display()
 │
 ├─ Player (display 재정의)
 │
 └─ Enemy  (display 재정의)

 

  • Character: 모든 캐릭터의 공통 기능
  • Player / Enemy: 출력 방식만 다른 구체 클래스
  • 이동 로직은 모두 부모(Character)에 있음
  • 👉 일반화 + 구체화가 매우 깔끔한 구조


2️⃣ Character 클래스 설명 (설계 포인트)

🔐 private 멤버

self.__x
self.__y

 

  • __ → private
  • 외부 클래스, 자식 클래스에서 직접 접근 불가
  • 캡슐화 완벽

    🧭 이동 기능을 부모에 둔 이유
def moveUp(self, dist):
    self.__y -= dist
    self.display()


✔ Player와 Enemy 모두 이동 가능
✔ 중복 제거
✔ 자식에서 private 접근 문제 없음

📌 중요한 설계 판단
“공통 기능 + private 접근이 필요한 로직은 부모에 둔다”

 

3️⃣ Player 클래스 설명

class Player(Character):
    def display(self):
        super().display()
        print("Player")

 

  •  오버라이딩의 정석
  • 메서드 이름 동일 (display)
  • 매개변수 동일
  • 부모 기능 재사용 + 확장
super().display()
print("Player")

 

  • private 직접 접근 ❌
  •  부모 메서드 통해 간접 접근 ⭕


4️⃣ Enemy 클래스 설명

class Enemy(Character):
    def display(self):
        super().display()
        print("Enemy")

 

  • Player와 동일한 구조
  • 출력 문자열만 다름

5️⃣ 실행 흐름 예시 (중요)

c = Character()
p = Player()
e = Enemy()

c.moveUp(10)
p.moveUp(10)
e.moveUp(10)


실제 동작 흐름

 

  • moveUp() → Character의 메서드
  • 내부에서 self.display() 호출
  • 객체 타입에 따라
    • Character → Character.display
    • Player → Player.display
    • Enemy → Enemy.display
       이게 바로 다형성

 

📌 1️⃣ 단일 상속 (Single Inheritance)

: 하나의 클래스가 오직 하나의 부모 클래스만 상속받는 구조

구조

Parent
  ↑
 Child



특징

  • 부모 클래스 1개만 존재
  • 구조가 단순하고 이해하기 쉬움
  • 충돌 위험 없음
  • Python에서 가장 기본적인 상속 형태
class Character:
    pass

class Player(Character):   # 단일 상속
    pass

 

✔ Player는 Character 하나만 상속
✔ Player is a Character

 


📌 2️⃣ 조상을 갖는 단일 상속 (다단계 상속, Multilevel Inheritance)

: 단일 상속이 연속으로 이어져 조상 클래스까지 상속받는 구조

직접 부모는 1개

하지만 위로 올라가면 조상 클래스가 존재

구조

GrandParent
     ↑
   Parent
     ↑
   Child

 

  • 각 단계마다 단일 상속
  • 결과적으로 여러 세대의 기능을 모두 상속
class Character:
    pass

class Enemy(Character):
    pass

class BossEnemy(Enemy):   # 조상을 갖는 단일 상속
    pass


✔ BossEnemy → Enemy → Character
✔ BossEnemy는 Enemy와 Character의 모든 기능을 가짐

 

📌 3️⃣ 다중 상속 (Multiple Inheritance)

: 하나의 클래스가 두 개 이상의 부모 클래스를 동시에 상속받는 구조
👉 여러 부모의 기능을 한 번에 물려받는 상속 방식

구조

ParentA     ParentB
     \       /
      \     /
       Child



  • Child 클래스는 ParentA, ParentB 모두의 자식
  • IS-A 관계가 동시에 여러 개 성립
class Fly:
    def move(self):
        print("날아서 이동")

class Swim:
    def move(self):
        print("헤엄쳐서 이동")

class Duck(Fly, Swim):   # 다중 상속
    pass

d = Duck()
d.move()


✔ Duck은 Fly이면서 Swim
✔ 여러 부모의 기능을 동시에 가짐

❗ 다중 상속의 문제점 (중요)


1️⃣ 메서드 충돌 문제
위 예제에서처럼
부모 클래스 둘 다 move()를 가지고 있으면
👉 어떤 메서드를 호출할지 애매해짐

2️⃣ 다이아몬드 문제 (Diamond Problem)

      A
     / \
    B   C
     \ /
      D

 

  • B, C가 A를 상속
  • D가 B와 C를 동시에 상속
  • 👉 A의 메서드를 한 번 상속받는가?두 번 상속받는가?➡️ 구조가 복잡해지고 오류 가능성 증가

 


정규 표현식

: 문자열의 “패턴”을 표현하는 문법

“이런 형태의 문자열을 찾고/검증하고/치환해라”를 짧게 적는 방법
예) 주민번호 형태: \d{6}-\d{7}

왜 필요한가?

  • 복잡한 문자열 처리(검사/추출/치환)를 짧고 직관적으로 구현 가능
  • split → 조건 검사 → 재조립 같은 긴 코드를 정규식 한 줄로 줄일 수 있음
  • 예) re.sub()로 뒷자리 마스킹

핵심 메타문자(기초)
1) 문자 클래스 [ ]

[abc] : a/b/c 중 한 글자

범위: [a-z], [0-9], [A-Z], [가-힣]

제외: [^0-9] (숫자 아닌 것)



2) 자주 쓰는 축약

\d 숫자 = [0-9]

\D 숫자 아님

\s 공백류(화이트스페이스)

\S 공백 아님

\w 문자/숫자/_

\W 그 외



3) 점 .

. : \n 제외 모든 문자 1개

줄바꿈 포함하려면 re.DOTALL(re.S)



4) 반복

* : 0회 이상

+ : 1회 이상

{m} : m회

{m,n} : m~n회

? : 0 또는 1회 ({0,1})

re 모듈 사용 흐름
컴파일
import re
p = re.compile('[a-z]+')


검색 메서드 4개

match() : 문자열 처음부터 매치

search() : 전체에서 첫 매치

findall() : 매치되는 모든 문자열을 리스트

finditer() : 매치 결과를 반복자(match 객체들)



match 객체 주요 메서드

group() : 매치된 문자열

start() / end() : 시작/끝 인덱스

span() : (start, end)


컴파일 옵션

re.DOTALL (re.S) : .이 줄바꿈 포함

re.IGNORECASE (re.I) : 대소문자 무시

re.MULTILINE (re.M) : ^, $가 각 줄 기준

re.VERBOSE (re.X) : 정규식을 여러 줄 + 주석으로 가독성 향상



역슬래시(\) 문제와 raw string
: \는 정규식에서도 특수 의미가 있어서 이스케이프가 꼬이기 쉬움

해결: raw string 사용 권장

re.compile(r'\\section')


 

 

 

 

 

 


예시 

1) 정규 표현식 없이 구현 

data = """
park 800905-1049118
kim  700905-1059119
"""

result = []
for line in data.split("\n"):
    word_result = []
    for word in line.split(" "):
        if len(word) == 14 and word[:6].isdigit() and word[7:].isdigit():
            word = word[:6] + "-" + "*******"
        word_result.append(word)
    result.append(" ".join(word_result))
print("\n".join(result))

 

2) 정규표현식으로 구현 

data = """
park 800905-1049118
kim  700905-1059119
"""

import re 
pattern = r'(\d{6})[-]?\d{7}'
masked_data = [re.sub(pattern, r'\1-*******', line)for line in data.split('\n')]
print (masked_data)

1️⃣ 정규식 패턴

pattern = r'(\d{6})[-]?\d{7}'

 

  • (\d{6}) : 숫자 6자리 캡처 그룹 (앞자리)
  • [-]? : 하이픈 -이 있어도 되고 없어도 됨
  • \d{7} : 숫자 7자리 (뒷자리)

 

2️⃣ 리스트 컴프리헨션

[line별로 정규식 치환 for line in data.split('\n')]

 

  • 입력 텍스트를 줄 단위로 처리
  • 결과를 리스트로 반환

 

3️⃣re.sub() 치환

re.sub(pattern, r'\1-*******', line)

 

  • \1 : 첫 번째 캡처 그룹(앞 6자리) 재사용 [그대로 라는 뜻]
  • 뒤 7자리는 *******로 마스킹'

 


#문제 1

import re 

phone_list= ["01012345678", "010-987-6543", "010.1111.2222", "010 5555 4444"]

result_phone = []
for line in phone_list:
    only_number = re.sub(r'[^0-9]','',line)
    pattern = r'(\d{3})(\d{3,4})(\d{4})'
    format_number = re.sub(pattern, r'\1-\2-\3', only_number)
    result_phone.append(format_number)

print(result_phone)

 

핵심 1단계: 숫자만 남기기

only_number = re.sub(r'[^0-9]','', line)


정규표현식 해석

  • [^0-9]
    • [] : 문자 집합
    • ^ (집합 안에서) : 부정
    • 0-9 : 숫자
      → “숫자가 아닌 모든 문자”

      동작
  • 숫자가 아닌 문자를 전부 ''(빈 문자열)로 치환
  • 즉, 제거 효과

    변환 예시
   원본 문자열  결과 (only_number)
01012345678 01012345678
010-987-6543 0109876543
010.1111.2222 01011112222
010 5555 4444 01055554444

 

 


3) 핵심 2단계: 전화번호 패턴 정의

pattern = r'(\d{3})(\d{3,4})(\d{4})'


패턴 구성

  • (\d{3})
    • \d : 숫자 1개
    • {3} : 정확히 3개
      → 국번 (예: 010)
  • (\d{3,4})
    • 숫자 3개 또는 4개
      → 중간 번호
    • 010-123-4567 (3자리)
    • 010-1234-5678 (4자리)
  • (\d{4})
    • 숫자 4개
      → 끝 번호


      전체 의미
010 | 123 or 1234 | 4567


4) 핵심 3단계: 하이픈 형식으로 재조합

format_number = re.sub(pattern, r'\1-\2-\3', only_number)


치환 문자열 해석

 

  • \1 : 첫 번째 그룹 ((\d{3}))
  • \2 : 두 번째 그룹 ((\d{3,4}))
  • \3 : 세 번째 그룹 ((\d{4}))
    → 그룹 사이에 - 삽입

  • 예시 변환
숫자 문자열 변환 결과
01012345678 010-1234-5678
0109876543  010-987-6543
01011112222  010-1111-2222
01055554444  010-5555-4444

 


5) 최종 출력 결과

['010-1234-5678',
 '010-987-6543',
 '010-1111-2222',
 '010-5555-4444']



6) 이 코드의 핵심 포인트 정리

  • 입력 형식이 제각각이어도 문제 없음: -, ., 공백 등 어떤 구분자든 제거
  • 숫자 길이 기반으로 재구성: 중간 번호를 {3,4}로 처리해 유연성 확보
  • **정규식 그룹 + 치환 참조(\1, \2, \3)**의 전형적인 활용 예

 

#문제 2

import re 

def pw_checker(pw): 
    pattern = r'^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[!@#$%^&*]).{8,}$'
    p = re.compile(pattern)
    check = p.match(pw)
    if check: 
        print ('조건을 만족합니다.: ', check.group())
    else: 
        print('조건을 만족하지 않습니다.', pw)

pw_checker("Abcdefg123!2@")
pw_checker("abcdefg")

 

설명 

# 1. 정규표현식 패턴 정의 (가장 중요한 부분)
    # ^                 : 문자열의 시작
    # (?=.*[A-Z])       : [조건1] 대문자가 하나라도 있는가? (검사만 하고 커서 제자리)
    # (?=.*[a-z])       : [조건2] 소문자가 하나라도 있는가? (검사만 하고 커서 제자리)
    # (?=.*[0-9])       : [조건3] 숫자가 하나라도 있는가? (검사만 하고 커서 제자리)
    # (?=.*[!@#$%^&*])  : [조건4] 특수문자가 하나라도 있는가? (검사만 하고 커서 제자리)
    # .{8,}             : [최종] 위 조건을 다 만족하면, 아무 글자나 8개 이상 가져와라.
    # $                 : 문자열의 끝

정규표현식 패턴을 조각내서 해석

패턴:

^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[!@#$%^&*]).{8,}$


(1) ^ ... $ : 문자열 전체를 검사

  • ^ : 문자열의 시작
  • $ : 문자열의 끝
    즉, “문자열 일부만”이 아니라 비밀번호 전체가 이 규칙에 맞아야 합니다.


(2) (?= ... ) : 전방탐색(lookahead)

  • (?=...) 는 “이 조건이 뒤에 존재해야 한다” 를 의미하지만, 실제로 그 내용을 소비(consuming)하지는 않습니다.
    즉, 자리 이동 없이 “포함 여부”만 검사하는 장치입니다.
  • (?=.*[A-Z])
    • .* : 어떤 문자든(개행 제외) 0개 이상
    • [A-Z] : 대문자 1개
      → “어딘가에 대문자가 최소 1개 있어야 함”
    • (?=.*[a-z])
      → “어딘가에 소문자가 최소 1개 있어야 함”
    • (?=.*[0-9])
      → “어딘가에 숫자가 최소 1개 있어야 함”
    • (?=.*[!@#$%^&*])
      → “어딘가에 !@#$%^&* 중 하나가 최소 1개 있어야 함”
      (주의: 여기서 허용하는 특수문자는 이 집합에 있는 것만 “필수 포함” 조건으로 검사합니다.


(3) .{8,}

  • . : 임의의 문자 1개
  • {8,} : 8개 이상
    → “전체 길이가 최소 8자 이상이어야 함”
    정리하면 이 패턴은:
    길이 8자 이상, 대문자 최소 1, 소문자 최소 1, 숫자 최소 1, 지정 특수문자(!@#$%^&*) 최소 1

 

 

'LG DX DATA SCHOOL' 카테고리의 다른 글

12/30 기술 통계학 + R  (0) 2025.12.30
12/29 통계 기반 데이터 분석 (넘파이/ 판다스)  (0) 2025.12.29
12/23 객체 지향 복습 / 판다스 (Pandas)  (0) 2025.12.23
12/22 객체 지향  (0) 2025.12.22
12/19  (0) 2025.12.19