Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

Nonamed Develog

[TIL][240723] 파이썬 문법 빈틈을 메꾸자 (6) 본문

WHAT I LEARN/TIL

[TIL][240723] 파이썬 문법 빈틈을 메꾸자 (6)

노네임드개발자 2024. 7. 24. 00:04
새로 알게 된 점은 무엇인가?

객체 지향 프로그래밍의 4가지 특징

  1. 추상화
  2. 상속
  3. 다형성
  4. 캡슐화

아래에서 자세하게 알아보자.


1. 추상화: 객체의 공통적인 속성과 기능을 추출하여 정의하는 것이다.

class Person:
    def run(self):
        print("Person is running")
    
    def walk(self):
        print("Person is walking")
  • 가장 본질적이고 공통적인 부분만을 추출하여 표현할 수 있다.
  • 달리기, 걷기는 각각을 사람이 하는 행위이기 때문에 Person이라 정의할 수 있다.
  • 함수, 변수 클래스 모두 추상화이다.

 

 

2. 상속

  • 두 클래스 사이 부모-자식 관계를 정립하는 것이다.
  • 클래스는 상속이 가능하다. cf) 모든 파이썬 클래스는 Object를 상속받는다.
  • 부모 클래스의 속성, 메서드가 자식 클래스에 상속되므로 재사용성이 높고 유지 보수도 편리하다.
  • 자식 클래스는 부모 클래스에 정의된 속성, 행동, 관계 및 제약 조건을 모두 상속받는다.
  • 상속 관계에서의 이름 공간은 인스턴스 ➡️ 자식 클래스 ➡️ 부모 클래스 순으로 탐색할 수 있다.
class Person:
    age = 19

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def run(self):
        print("Person is running")

    @classmethod  # 클래스 메서드
    def get_age(cls):
        print("Person's age is: ", cls.age)

    @staticmethod  # 스태틱 메서드
    def wakeup():
        print("싫어...")


class Teacher(Person):  # Person 클래스에서 정의된 것을 상속
    pass


aiden = Teacher("aiden", 22)
aiden.run()  # Person is running
'''
Teacher class는 pass만 존재하기 떄문에 부모 클래스인 Person class의 정의를 사용할 수 있다.
Teacher의 인스턴스지만 Person에 상속되었기 떄문에 요구되는 arguments(name, age)를 넣어줘야 한다.
'''

 

오버라이딩: 자식 클래스에서 부모 클래스의 동일한 메서드를 사용하고 싶지만 기능을 바꾸고 싶을 때 사용한다. 이때 함수 정의(인자)를 동일하게 작성해야 한다.

 

cf) 오버로딩

  • 동일한 이름의 함수를 매개변수에 따라 다른 기능으로 동작하도록 할 수 있다. 파이썬에서는 오버로딩을 정식으로 지원하지 않지만 구현은 가능하다. 하지만 남발한다면 코드가 복잡해진다. 
  • 오버라이딩과 비교하면 오버라이딩이 클래스 상속 시 클래스의 메서드를 수정하는 것이라면 오버로딩은 하나의 메서드에 다형성을 부여하는 것이다. 오버로딩을 활용하면 메서드를 작성할 때 매개 변수의 숫자 제한이 없기 때문에 사실상 같은 이름으로 동작하는 많은 메소드를 만들 수 있다.

super(): 부모 클래스의 메서드를 가져와서 사용하고 싶을 때 사용한다.

# 오버라이딩과 super()
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def run(self):
        print("Person is running")

    @classmethod
    def get_age(cls):
        print("Person's age is: ", cls.age)

    def wakeup(self):
        print("싫어...")


class Teacher(Person):
    def wakeup(self):
        print("그래도 일어나야지...")  # 그래도 일어나야지...
        #오버라이딩. 부모클래스의 wakeup 정의(인자)를 동일하게 작성 후 새로운 기능 추가
        super().wakeup()  # 싫어...
        #super(). 부모클래스에 있는 wakeup 메소드의 기능을 가져와 사용

aiden = Teacher("aiden", 22)
aiden.wakeup()

 

다중 상속: 여러 클래스로부터 상속을 받아서 새로운 클래스를 만드는 방법이다.

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

    def run(self):
        print("Person is running")

    def wakeup(self):
        print("싫어...")


class Teacher:
    def wakeup(self):
        print("그래도 일어나야지...")

    def teach(self):
        print("Teacher is teaching")


class Student(Person, Teacher):  # 다중 상속
    pass
'''
상속한 클래스의 순서에 따라 실행 값이 달라진다,
(Person, Teacher)  # 싫어...
(Teacher, Person)  # 그래도 일어나야지...
'''

aiden = Student("aiden", 22)
aiden.wakeup()

 

상속 관련 함수와 메서드

  • isinstance(Object, class명) 메서드
    class명의 instance 거나 subclass*인 경우 True이다.
class Person: pass

class Teacher(Person): pass

class Student(Person): pass

p1 = Teacher()
p2 = Student()

print(isinstance(p1, Person))  # True, Teacher는 Person의 subclass
print(isinstance(p1, Teacher))  # True
print(isinstance(p1, Student))  # False, p1은 Teacher 인스턴스
print(isinstance(p2, Person))  # True, Student는 Person의 subclass
print(isinstance(p2, Teacher))  # False, p2는 Studen 인스턴스
print(isinstance(p2, Student))  # True

 

  • mro 메서드(Method Resolution Order)
    - 해당 인스턴스의 클래스가 어떤 부모 클래스를 가지는지 확인하는 메서드
    - 기존 (인스턴스 ➡️ 클래스) 순서로 이름 공간을 탐색하는 과정에서 상속 관계에 있으면
      인스턴스 ➡️ 자식 클래스 ➡️ 부모 클래스로 확장한다.

3. 다형성(Polymorphism)

  • 겉으로 봤을 때 같은 코드라도 적절하게 다른 메서드가 호출된다. (self가 대표적)
  • 동일한 메서드가 클래스에 따라 다르게 행동할 수 있다.
  • 서로 다른 클래스에 속해 있는 객체들이 동일한 메시지에 대해 다른 방식으로 응답할 수 있다.

4. 캡슐화

  • 클래스 작성 시 내부적인 세부사항을 캡슐화한다. 이는 외부로부터 직접적인 접근을 차단하는 것을 의미한다.
  • 파이썬에서 '언어적'으로 존재하지 않지만 개발자들 사이에 '암묵적'으로 존재한다. 일종의 관례가 있다.
아래 3가지 접근제어자를 통해서 캡슐화할 수 있다.
- Public Access Modifier: 상시 접근 가능
- Protected Access Modifier: 상속받은 관계에서만 접근 제한
- private Access Modifier: 상속받아도 접근 불가, 해당 클래스 내부에서만 가능
  • Public Member
    - 일반적으로 작성되는 메서드와 속성의 대다수
    - 언더바 없이 시작하는 메서드, 속성
    - 언제 어디서나 호출, 오버라이딩 가능
  • Protected Member
    - 암묵적 규칙에 의해 부모 클래스 내부 자식 클래스에서만 호출 가능
    - 언더바 1개로 시작하는 메서드, 속성
    - 하위 클래스 오버라이딩 가능
  • Private Member
    - 해당 클래스 내부에서만 가능
    - 언더바 2개로 시작하는 메서드, 속성
class A:
    def __init__(self):
        self.public = "Public"
        self._protected = "Protected"  # 언더바 1개
        self.__private = "Private"  # 언더바 2개

class B(A):
    def pr(self):
        print(self.public)  # 일반적인 상속으로 Public 출력
        print(self._protected)  # B는 A의 상속된 클래스이므로 Protected 출력. 상속된 클래스가 아니면 오류.
        print(self.__private)  # private은 해당 클래스 내부에서만 가능하기에 오류

b = B()
b.pr()

 

위 코드는 private를 출력할 수 없어 오류가 난다. 하지만 굳이 private를 출력하고 싶다면 방법이 또 있다.

class A:
    def __init__(self):
        self.public = "Public"
        self._protected = "Protected"
        self.__private = "Private"

    def get_private(self):  # private를 얻기 위한 메서드를 추가한다.
        print(self.__private)

class B(A):
    def pr(self):
        print(self.public)
        print(self._protected)
        print(self.__private)

b = B()
b_pr = b.get_private()  # B 클래스에서 A 클래스의 __private에 접근 할 수 있다.

 


디버깅

  • 잘못된 프로그램을 수정하는 것이다.
  • 에러메시지가 발생하는 경우(문법이 틀렸을 경우) ➡️ 해당하는 위치를 찾아 메시지를 통해 해결할 수 있다.
  • 로직 에러가 발생하는 경우(생각이 틀렸을 경우) ➡️ 실행은 되지만 내가 원하는 값이 나오지 않았을 때
  • 어떻게 해야 할까?
    - 뇌컴파일, 눈디버깅
    - print() 함수의 생활화
    - breakpoint 등 개발 환경에서 제공하는 기능 활용
    - python tutor 활용

문법 에러

  • 에러가 발생하면 프로그램은 실행되지 않는다. 이때 에러 메시지와 함께 파일명, 줄번호, ^(caret)을 통해 문제가 발생한 곳을 알려준다.
  • 우리가 사용하는 IDE가 잡아주기도 한다.

예외

 

  • 프로그램 실행 도중 예기치 못한 상황 발생 시 문법적으로 맞더라도 프로그램은 즉시 멈춘다. 이 때 감지되는 에러들을 예외라고 한다.
  • 여러 타입(type)으로 나타나고, 타입이 메시지의 일부로 출력된다.
  • 모든 예외는 위 그림과 같이 Exception class를 상속받는다.
  • 사용자 정의 예외를 만들어 관리할 수 있다.
예외 처리: 에러가 발생하여 프로그램을 종료하는 대신 코드를 정상 작동 시켜 예외 메시지를 남길 수 있다.

try 문

- 오류가 발생할 가능성이 있는 코드를 실행
- 예외가 발생되지 않으면 except 없이 실행 종료된다.
except 문
- 예외가 발생하면 except 절이 실행된다.
- 예외 상황을 처리하는 코드를 받아서 실행된다.
# 예외 처리 기본 구조
try:
	# try 명령문
except 예외그룹1 as 변수1:
	# 예외처리 명령문1
except 예외그룹2 as 변수2:
	# 예외처리 명령문2
finally: # 여기는 선택사항
	# fianlly 명령문
# 예외 처리 예시
user_input = input("Enter a number: ")

try:
    user_input = int(user_input)  # 숫자만 입력해야하는데 문자를 입력하는 고객님...
except ValueError:
    print("Please enter a number")  # 숫자를 입력하라고 알려준 다음 프로그램 종료
except Exception:
    print("Please enter a number")  # 문자 외 다른 이상한 짓을 방지하기 위해 상위 클래스인 Exception 추가
finally:
    print("프로그램을 종료합니다.")  # 굳이 안써도 되지만 진짜 이상한 짓을 할 경우 프로그램 강제 종료

print(user_input)

 

무엇을 느꼈고 내일은 무엇을 할까?

드디어 파이썬 세션을 마쳤다. 블로그에 정리하는 게 오래 걸릴 만큼 파이썬은 나에게 아직 멀리 있는 듯하다. 그리고 이번 파이썬 세션을 기반으로 Stack과 Queue를 구현해 봤다. 이전보다 조금은 수월해진 느낌이라 빨리 구현하고 알고리즘을 공부하려 했지만 파이썬 마지막 세션까지 마무리하고 알고리즘으로 진입해야겠다 생각했다. 내일은 다시 Stack과 Queue 구현에 몰입을 해봐야겠다.