TIl

OOP 2

아크몽 2024. 1. 25. 23:08

상속

기존 클래스의 속성과 메서드를 물려받아 새로운 하위 클래스를 생성하는것

필요한 이유

클래스 상속

상속없이 구현 하는 경우 문제점

  • 학생/ 교수 정보를 나타내기 어려움
    예시
    class Person:
      def __init__(self, name, age):
        self.name = name
        self.age =age
      def talk(self):
        print(self.name)
    
    #정보 구분 어려움
    s1 = Person('김학생',23)
    s1.talk() #김학생
    
    p1 = Person('박교수',59)
    p1.talk() #박교수
  • 메서드 중복 정의
    예시
    동일한 일을 하는 함수가 안에 있음
    class Professor(Person):   def __init__(self, name, age, department):     self.name = name     self.age = age     self.department = department   def talk(self): #중복     print(f'반갑습니다. {self.name}입니다.')
    
    
    class Student(Person):   def __init__(self, name, age, gpa):     self.name = name     self.age = age     self.gpa = gpa   def talk(self): #중복     print(f'반갑습니다. {self.name}입니다.')
상속을 사용한 계층구조 변경
class Person:   def __init__(self, name, age):     self.name = name     self.age = age    def talk(self):     print(f'반갑습니다. {self.name}입니다.')


class Professor(Person):   def __init__(self, name, age, department):     self.name = name     self.age = age     self.department = department


class Student(Person):   def __init__(self, name, age, gpa):     self.name = name     self.age = age     self.gpa = gpa

p1 = Professor('박교수',49,'컴퓨터공학과')
s1 = Student('김학생',20, 3.5)

print(p1.department)
print(s1.gpa)

p1.talk() #반갑습니다. 박교수입니다.
s1.talk() # 박갑습니다. 김학생입니다.

super()

부모 클래스 객체를 반환하는 내장 함수

사용 예시

중복 된 부분이 많음 ⇒ 재사용성 문제

class Person:   def __init__(self, name, age, number, email):     self.name = name     self.age = age     self.number = number     self.email = email


class Student(Person):   def __init__(self, name, age, number, email, student_id):     self.name = name  # 이부분을 줄이기 위함     self.age = age     self.number = number    => super().__init__()     self.email = email     self.student_id = student_id


class Student(Person):
    #매개변수는 줄일 수 없다. 함수의 문법때문에 위치인자는 설정해줘야 함   def __init__(self, name, age, number, email, student_id):      super().__init__(name, age, number, email)     self.student_id = student_id      def talk(self): #오버라이딩    print(f'방가방가. {self.name}입니다.  #def talk(self, msg): #오버로딩 , 파이썬은 지원안함 *msg 가변형 인자 사용가능하기 때문
    #    print(f'{msg} 안녕'})

왜? Person 대신 super()인가?

super()를 사용하면 name, age, number, email 속성은 부모 클래스 인스턴스에서 초기화 되고 자식 클래스 인스턴스는 거기를 참조하는 방식

  1. 만약에 super()를 안쓰고 있을 때 부모클래스가 이름이 바꼈을 때, 자식클래스에서 사용중이던 기존의 이름을 전부 다 바꿔줘야 함
  2. 다중상속을 고려해야 되서

다중 상속 (자바는 인터페이스로 대체함)

  • 둘 이상의 상위 클래스로부터 여러 행동이나 특징을 상속받을 수 있는 것
  • 상속 받은 모든 클래스의 요소를 활용 가능함
  • 중복된 속성이나 메서드가 있는 경우 상속 순서에 의해 결정됨
예시
class Person:   def __init__(self, name):     self.name = name    def greeting(self):     return f'안녕, {self.name}'

class Mom(Person):   gene = 'XX'    def swim(self):     return '엄마가 수영'

class Dad(Person):   gene = 'XY'    def walk(self):     return '아빠가 걷기'

class FirstChild(Dad, Mom):   def swim(self):     return '첫 째가 수영'    def cry(self):     return '첫 째가 응애'

baby1 = FirstChild('김싸피')
print(baby1.cry())
print(baby1.swim())
print(baby1.walk())
print(baby1.gene) #본인 X -> 먼저 나온 부모(Dad) -> 나중에 나온 부모(Mom)

다이아몬드 문제

  • 두 클래스 B와 C가 A에서 상속되고, 클래스 D가 B와 C 모두에서 상속 될 때 발생하는 모호함
  • B와 C가 재정의한 메서드가 A에 있고 D가 이를 재정의하지 않은 경우라면

⇒ D는 B의 메서드 중 어떤 버전을 상속하는가? 아니면 C의 메서드 버전을 상속하는가? D B C A순서로 간다(나는 DBAC 라고 생각했었는데…)

파이썬에서의 해결책

  • MRO ( Method Resolustion Order, 메서드 결정 순서) 알고리즘을 사용하여 클래스 목록을 생성
  • 부모 클래스로부터 상속된 속성들의 검색을 깊이 우선으로, 왼쪽에서 오른쪽으로, 계층 구조에서 겹치는 같은 클래스를 두 번 검색하지 않음
  • 그래서, 속성이 D에서 발견되지 않으면, B에서 찾고, 거기에서도 발견되지 않으면, C에서 찾고, 이런 식으로 진행됨

Super()

부모 클래스 객체를 반환하는 내장 함수

⇒ 다중 상속 시 MRO를 기반으로 현재 클래스가 상속하는 모든 부모 클래스 중 다음에 호출된 메서드를 결정하여 자동으로 호출

  • 2가지 사용 사례
  1. 단일 상속 구조
    • 명시적으로 이름을 지정하지 않고 부모 클래스를 참조할 수 있으므로, 코드를 더 유지 관리하기 쉽게 만들 수 있음
    • 클래스 이름이 변경되거나 부모 클래스가 교체되어도 super()를 사용하면 코드 수정이 더 적게 필요
  2. 다중 상속 구조
    • MRO를 따른 메서드 호출
    • 복잡한 다중 상속 구조에서 발생할 수 있는 문제를 방지
예시 1
class ParentA:   def __init__(self):     self.value_a = 'ParentA'    def show_value(self):     print(f'Value from ParentA: {self.value_a}')

class ParentB:   def __init__(self):     self.value_b = 'ParentB'    def show_value(self):     print(f'Value from ParentB: {self.value_b}')

class Child(ParentA,ParentB):   def __init__(self):     super().__init__()     self.value_c = 'Child'   def show_value(self):     super().show_value()     print(f'Value from Child:{self.value_c}')     # return super().show_value()

child = Child()
child.show_value()
print(child.value_a) # o
print(child.value_b) # x ParentB 의 init 메서드를 쓰지 않았기 때문에 이 값이 없음
print(child.value_c) # O
예시 2 출력순서
Stack 방식으로 쌓인다.
class A:   def __init__(self):     print('A Constructor')

class B(A):   def __init__(self):     super().__init__()     print('B Constructor')

class C(A):   def __init__(self):     super().__init__()     print('C Constructor')

class D(B, C):   def __init__(self):     super().__init__()     print('D Constructor')

obj = D() # A~ C~ B~ D~
print(D.mro()) # 출력 D, B, C, A, object 까지 

D B C A 순으로 밑에서부터 쌓이니까 출력은 A C B D 순으로 된다.(재귀함수도 같은 방식)

mro() 메서드

  • 해당 인스턴스의 클래스가 어떤 부모 클래스를 가지는지 확인하는 메서드
  • 기존의 인스턴스 ⇒ 클래스 순으로 이름 공간을 탐색하는 과정에서 상속 관계에 있으면 인스턴스 → 자식 클래스 → 부모 클래스로 확장

mro() 메소드가 필요한 이유

  • 부모 클래스들이 여러번 엑세스 되지 않도록,

    각 클래스에서 지정된 왼쪽에서 오른쪽으로 가는 순서를 보존하고,

    각 부모를 오직 한번만 호출하고,

    부모들의 우선순위에 영향을 주지 않으면서 서브 클래스를 만드는 단조적인 구조 형성

⇒ 프로그래밍 언어의 신뢰성 있고 확장성 있는 클래스를 설계할 수 있도록 도움

⇒ 클래스 간의 메서드 호출 순서가 예측 가능하게 유지, 코드의 재사용성과 유지보수성이 향상

버그

소프트웨어에서 발생하는 오류 또는 결함

프로그램의 예상된 동작과 실제 동작 사이의 불일치

디버깅

소프트웨어에서 발생하는 버그를 찾아내고 수정하는 과정

프로그램의 오작동 원인을 식별하여 수정하는 작업

디버깅 방법
  1. print 함수 활용(특정 함수 결과, 반복/조건 결과 등 나눠서 생각, 코드는 bisection으로 나눠서 생각
  2. 개발 환경 등에서 제공하는 기능 활용 (breakpoint, 변수조회 등)
  3. Python tutor 활용
  4. 뇌 컴파일, 눈 디버깅 등

에러 : 프로그램 실행 중에 발생하는 예외 상황

문법 에러

프로그램의 구문이 올바르지 않은 경우 발생 ( 오타, 괄호 및 콜론 누락 등의 문법적 오류)

  • invalid syntax (문법 오류) : : 같은거 빼면 발생
  • assign to literal (잘못된 할당) : 5=3
  • EOL (End of Line) : print('hello
  • EOF ( End of file) print(

예외 Exception

프로그램 실행 중에 감지되는 에러

내장 예외 Build-in Exception

예외 상황을 나타내는 예외 클래스들

⇒ 파이썬에서 이미 정의되어 있으며, 특정 예외 상황에 대한 처리를 위해 사용

예시

  • ZeroDivisionError : 나누기 또는 모듈로 연산의 두 번째 인자가 0일 때 발생
    10/0 # ZeroDivisionError: division by zero
  • NameError : 지역 또는 전역 이름을 찾을 수 없을 때 발생
    print(name_error) # NameError: name 'name_error' is not defined
  • TypeError 타입 불일치
    '2' + 2 # TypeError: can only concatenate str (not "int") to str
  • 인자 누락 - TypeError

    sum()  # TypeError: sum() takes at least 1 positional argument (0 given)

  • 인자 초과 - TypeError

    sum(1, 2, 3)  # TypeError: sum() takes at most 2 arguments (3 given)

  • 인자 타입 불일치 TypeError
    import random
    random.sample(1, 2)
    # TypeError: Population must be a sequence.  For dicts or sets, use sorted(d).
  • ValueError
    연산이나 함수에 문제가 없지만 부적절한 값을 가진 인자를 받았고,
    상황이 IndexError 처럼 더 구체적인 예외로 설명되지 않는 경우 발생
    int('1.5') # ValueError: invalid literal for int() with base 10: '3.5'
    range(3).index(6) # ValueError: 6 is not in range
  • IndexError 시퀀스 인덱스가 범위를 벗어날 때 발생
    empty_list = []
    empty_list[2]
    # IndexError: list index out of range
  • KeyError 딕셔너리에 해당 키가 존재하지 않는 경우
    person = {'name': 'Alice'}
    person['age']  # KeyError: 'age'
  • ModuleNotFoundError 모듈을 찾을 수 없을 때 발생
    import hahaha  # ModuleNotFoundError: No module named 'hahaha'`
  • ImportError 임포트 하려는 이름을 찾을 수 없을 때 발생
    from random import hahaha
    # ImportError: cannot import name 'hahaha' from 'random'
  • KeyboardInterrupt
    사용자가 Control-C 또는 Delete를 누를 때 발생
    - 무한루프 시 강제 종료
  • IndentationError - 잘못된 들여쓰기와 관련된 문법 오류
    for i in range(10):
    print(i) # IndentationError: expected an indented block

try-except 구조

try 블록 안에는 예외가 발생할 수 있는 코드를 작성

except 블록 안에는 예외가 발생했을 때 처리할 코드를 작성

예외가 발생하면 프로그램 흐름은 try 블록을 빠져나와 해당 예외에 대응하는 except 블록으로 이동

try:
  #예외가 발생할 수 있는 코드
except: # 특정 에러이름을 붙이지 않으면, 모든 에러를 제외시킨다.
  # 예외 처리 코드

복수 예외 처리 연습

  • 100을 사용자가 입력한 값으로 나누고 출력하는 코드를 작성해보시오.
    • 먼저, 발생 가능한 에러가 무엇인지 예상해보기
    num = int(input('100 으로 나눌 값을 입력하시오 : '))
    print(100/num)
    int('a') 문자열을 int로 형변환 : ValueError
    100/int('0') 0으로 숫자를 나눔 : ZeroDivisionError
    • 발생 가능한 에러를 모두 명시하거나 & 별도로 작성하기
    try:   num = int(input('100으로 나눌 숫자를 입력하시오:'))
        print(100/num)
    
    except (ValueError, ZeroDivisionError):
        print('제대로 입력해주세요.')
    
    또는
    except ValueError:   print('숫자를 넣어주세요.')
    except ZeroDivisionError:   print('0으로 나눌 수 없어')
    except:   print('에러가 발생했어')

내장 예외의 상속 계층 구조 주의

Base Exception 가 가장 근본적임 ⇒ 최대한 마지막에 나오도록

내장 예외 클래스도 상속 계층 구조를 가짐

⇒ except 절로 분기 시 반드시 하위 클래스를 먼저 확인 할 수 있도록 작성해야함

참고

as 키워드

as 키워드를 활용하여 에러 메시지를 except 블록에서 사용 가능

EAFP

Easier to Ask for Forgiveness than Permission

허락보다 용서가 쉽다.

예외 처리를 중심으로 코드를 작성하는 접근 방식( try-except )

LBYL

Look Before You Leap

징검다리도 두들겨보고 건너간다.

값 검사를 중심으로 코드를 작성하는 접근 방식 ( if-else )

추가로 배운 것

통합 모델링 언어: 객체 지향 모델링 방법

오버라이딩 Overriding: 부모의 메소드를 자식클래스에서 재정의ㄴ

오버로딩 Overloading: 메소드의 이름은 같고 매개변수의 개수나 타입이 다른 경우, 파이썬은 지원 X, *msg 가변형 인자를 사용하기 때문에 필요없음