cmod.ify

OOP(객체 지향 프로그래밍) 본문

BASIC/PYTHON

OOP(객체 지향 프로그래밍)

modifyC 2025. 12. 22. 18:57
728x90
반응형
  1. Encapsulation(캡슐화)
    1. 불필요한 부분을 외부 노출하지 않기
    2. 클래스와 인스턴스를 어떻게 만들 것인가?
  2. Inheritance(상속) 
    1. 상위 클래스의 모든 요소를 하위 클래스가 물려 받는 것(상위 클래스가 물려주는 것 xxx)
      1. 중복제거 : 같이 바꿔야 하는 값을 실수없이 변경 가
      2. Framework Library 사용
  3. Polymorphism(다형성)
    1. 동일한 코드가 호출하는 대상에 따라 다른 작업을 수행하는 것
  4. abstrat(추상화)

Class 구성

Attribute : 데이터

Method : 작업

Class와 Instance

Class: 자료형, 불변

Instance: 자료형을 기반으로 메모리를 할당받은 데이터 - 동적

Instance 생성 : 클래스이름([데이터])


Attribute

  • 클래스 속성 (Class Attribute): 클래스 자체에 소속되어 모든 인스턴스가 공유하는 변수 ( name, age, address 등)
  • 인스턴스 속성 (Instance Attribute): 각 객체(std1, std2 등)가 개별적으로 가지는 변수 (self.name 등)
class Student: 
    name = ''
    age = 0
    address = ''
    
    def printStdInfo(self):
        print(f'name : {self.name}')
        print(f'age : {self.age}')
        print(f'address : {self.address}')
    
std1 = Student()

std1.name = '수정'
std1.printStdInfo()
Student.printStdInfo(std1)

 


인스턴스 메서드 생성과 호출

메서드 생성

def 메서드이름(인스턴스를 위한 이름[,데이터]): 내용

 

메서드 호출

클래스 내부  : 이름만으로 호출

클래스 외부 : 인스턴스 메서드 호출

 

언바운드 호출 : 클래스이름.메서드이름(인스턴스[, 데이터])

- 언바운드는 어디에도 묶여있지 않은 상태를 뜻함. 클래스 자체가 호출

바운드호출 : 인스턴스이름.메서드이름([데이터])

- 바운드가 (~누구의 바운드) 묶여있는 것을 뜻함. 클래스에 묶여 있는 인스턴스로 호출한다는 느낌임

class Address:  #첫글자 대문자 관례
    # 인스턴스 메서드
    def printAddress(self):
        print("인스터스 메서드 만들기 연습")
   
#인스턴스 생성
addr = Address()
#바운드 호출: 인스턴스 이름으로 호출
addr.printAddress()
#언바운드 호출: 클래스 이름으로 호출
Address.printAddress(addr)

 


Method

 

클래스의 구성 요소 중 하나로, 객체가 하는 '동작'이나 '기능'

Getter와 Setter

객체 지향 언어에서는 속성(데이터)에 직접 대고 수정하거나 읽는 걸 별로 권장하지 않음. 데이터가 오염될 수도 있고 위험하니까 메서드를 거쳐서 접근하는 게 정석

1. Getter: 속성을 읽어올 때 쓰는 메서드

  • 이름은 보통 'get_속성명'으로 짓고, 뭘 가져오기만 하는 거라 매개변수는 따로 없음.
  • 내용은 그냥 속성값을 리턴해주는 문장 하나면 끝임.
  • 만약 속성이 True/False 같은 bool 형태면 get 대신 is를 써서 'is_속성명'으로 만들기도 함.
  • 리스트 같은 컨테이너 자료형이면 인덱스 번호를 받아서 그 위치의 데이터만 쏙 빼오도록 만듦.

2. Setter: 속성을 수정할 때 쓰는 메서드

  • 이름은 'set_속성명'으로 하고, 바꿀 데이터를 매개변수로 받아옴.
  • 단순히 값을 바꾸는 것뿐만 아니라, 메서드 안에서 "데이터가 올바른지" 검사하는 필터 역할을 할 수 있어서 좋음.
  • 컨테이너 자료형일 땐 인덱스 번호랑 수정할 값을 같이 받아서 특정 위치만 바꾸기도 함.

3. 파이썬 스타일 관례

  • 자바 같은 다른 언어는 대문자를 섞어서 쓰지만(getName), 파이썬은 소문자 사이에 언더바를 넣는 스네이크 케이스(get_name)를 주로 씀.
  • 그리고 클래스 내부에서만 쓰는 속성이라는 걸 티 내려고 변수 이름 앞에 언더바(_)를 붙여서 관리하기도 함.
class Student: 
    age = 0
    address = ''
    school = '울랄라대학교'
    def getStdInfo(self):
        return self.school, self.name, self.age, self.address
    
    def setStdInfo(self, name, age, address):
        self.name = name
        self.age = age
        self.address = address
    

std1 = Student()

std1.setStdInfo('수정', 99, '서울시')
print(std1.getStdInfo())

 


특수 Attribute

이름 앞뒤에 언더바 두 개(__이름__)가 붙은 속성들인데, 파이썬이 특별한 목적을 위해 미리 생성

  • __doc__: 함수나 클래스가 무슨 일을 하는지 설명해 주는 도움말 문자열. 따로 안 적어주면 None이 나오고, 직접 수정도 가능함.

생성자 (Constructor)

클래스로 인스턴스를 만들 때, 메모리에 자리를 잡고 값을 초기화해 주는 역할을 함.

  • 파이썬에서는 __init__이라는 이름으로 이미 정해져 있음.
  • 인스턴스가 만들어질 때 자동으로 호출돼서 이름, 나이, 학교 같은 초기 데이터들을 세팅해 줌.
  • 클래스 변수(Student.count 같은 것)를 이용해서 전체 학생 수를 관리하는 용도로도 써먹음.
class Student: 
    count = 0
    def __init__(self, name, is_active=True):
            self.__name = name
            self.__age = 0
            self.__address = ''
            self.__school = '울랄라대학교'
            self.__is_active = is_active
            Student.count += 1
            self.__student_id = Student.count

소멸자 (Destructor)

인스턴스가 자기 할 일을 다 하고 메모리에서 사라질 때 호출되는 메서드임.

  • 이름은 __del__이고, 매개변수는 self 하나만 받음.
  • 보통 파일 연결을 끊거나 외부 데이터베이스 연결을 해제하는 등 뒷정리 코드를 넣을 때 사용함.
class Student: 
    count = 0
	def __del__(self):
        Student.count -= 1
        self.__student_id = Student.count

파이썬 메모리 해제 방식 (Reference Counting)

파이썬은 메모리 관리를 위해 '참조 카운트'라는 걸 사용함.

  • 카운트 증가: 데이터를 새로 만들면 1이 되고, 다른 변수가 그 데이터를 또 가리키면 카운트가 올라감.
  • 카운트 감소: 데이터를 가리키던 변수가 사라지면 카운트가 1씩 내려감.
  • 메모리 해제: 카운트가 0이 되는 순간, 파이썬은 "아, 이제 이 데이터는 아무도 안 쓰는구나"라고 판단해서 메모리에서 지워버림. (정확히는 다른 데이터를 저장할 수 있는 빈 공간으로 돌려줌)

Static Method (정적 메서드)

인스턴스 굳이 안 만들어도 클래스 이름으로 바로 부를 수 있는 메서드임.

  • 특징: 매개변수에 self가 없음. 그래서 개별 인스턴스의 속성이나 메서드에는 아예 접근이 불가능함.
  • 호출: 인스턴스로도 부를 순 있지만, 웬만하면 클래스 이름으로 부름
  • 작성법: 메서드 위에 @staticmethod라고 데코레이터를 붙여줘야 함.
  • 용도: 클래스 데이터(Student.count 등)를 수정하거나, 인스턴스 정보와는 상관없는 공통 기능을 만들 때 씀.
@staticmethod
    def init_count(initValue):
        Student.count = initValue

Class Method (클래스 메서드)

이것도 인스턴스 없이 클래스로 호출 가능함. 근데 정적 메서드랑은 약간 다름.

  • 특징: self 대신 첫 번째 매개변수로 **cls**를 무조건 넣어줘야 함.
  • 차이점: cls를 통해서 클래스 자체에 접근할 수 있음. 나중에 상속받았을 때도 자식 클래스 정보를 정확히 물고 온다는 장점이 있음.
  • 작성법: 메서드 위에 @classmethod라고 적어줘야 함.
@classmethod
    def init_count_cls(cls, initValue):
        Student.count = initValue

 

  • 일반 메서드: "학생 한 명"의 정보를 다룰 때 씀. (self 필요)
  • Static 메서드: "학생 인스턴스"랑은 상관없지만, 그냥 학생 클래스에 넣어두면 좋은 단순 계산/기능일 때 씀.
  • Class 메서드: "전체 학생 수"를 관리하거나, 상황에 따라 객체를 만드는 방식을 다르게 하고 싶을 때 씀. (cls 필요)

인스턴스 속성 생성 문제와 __slots__

파이썬은 원래 인스턴스에 없는 속성에 값을 넣으려고 하면, 그냥 "어? 없네? 내가 새로 만들어줄게!" 하고 자동으로 생성해버리는 특징이 있음.

  • 문제점: 오타가 나도 에러가 안 나고 엉뚱한 속성이 생길 수 있음 + 메모리를 더 많이 잡아먹음.
  • 해결책 (__slots__): 리스트 형태로 허용할 속성 이름들을 미리 적어두면, 그 외에 다른 속성은 아예 만들지 못하게 딱 막아버림. 메모리도 아끼고 실수도 방지할 수 있음.
__slots__ = ["__student_id", "__name", "__age", "__address", "__school", "__is_active"]

접근 지정자 (Private)

데이터를 아무나 못 건드리게 숨기는 기법임.

  • 방법: 속성 이름 앞에 언더바 두 개(__)를 붙이면 됨 (예: self.__name).
  • 효과: 클래스 안에서는 마음대로 쓸 수 있지만, 인스턴스를 통해서 밖에서 호출하려고 하면 "그런 거 없는데?"라며 접근을 막아버림 (Private 상태).
  • 접근: 이렇게 숨겨진 데이터는 getStdInfo() 같은 메서드를 통해서만 안전하게 읽어올 수 있음.
class Studen:
	def __init__(self, name, is_active=True):
            self.__name = name
            self.__age = 0
            self.__address = ''
            self.__school = '울랄라대학교'
            self.__is_active = is_active
            Student.count += 1
            self.__student_id = Student.count

    def getStdInfo(self):
            return self.__student_id, self.__school, self.__name, self.__age, self.__address, self.__is_active

프로퍼티 (Property)

속성에 직접 접근하는 것처럼 보이지만, 실제로는 내부적으로 Getter와 Setter 메서드를 실행하게 만드는 기능임.

  • 특징: 사용자는 std1.age = 20처럼 편하게 쓰는데, 실제로는 메서드가 돌면서 "나이가 150살이 넘지는 않는지" 같은 검사를 다 해줌. 올바른 데이터만 들어가도록 가이드하기 딱 좋음.
속성이름 = porperty(gfet=None, fset=None, fdel=None, doc=None)

 

데코레이터(@) 방식 사용법

  • @property: Getter 역할을 함. 변수처럼 값을 읽어올 때 호출됨.
  • @속성명.setter: Setter 역할을 함. 값을 저장하거나 수정하려고 할 때 호출됨. 메서드 안에서 조건문(if)을 써서 이상한 값이 들어오면 필터링할 수 있음.
class Studen:
    @property
        def age(self):
            return self.__age
        @age.setter
        def age(self, value):
            if value > 150 or value < 0:
                print("올바른 나이를 입력하세요")
            else:
                self.__age = value

 

 


Overloading vs Overriding

1. Overloading (오버로딩)

  • 한 클래스 안에 이름은 같은데 매개변수 개수나 자료형이 다른 메서드가 여러 개 있는 것임.
  • 파이썬은 기본적으로 오버로딩을 문법적으로 지원하지 않지만, 가변 인자나 디폴트 매개변수를 써서 비슷하게 흉내 낼 수 있음.

2. Overriding (오버라이딩)

  • 부모 클래스에 이미 있는 메서드를 자식 클래스에서 입맛에 맞게 다시 만드는 것임.
  • 목적: 기존 기능을 확장하거나 아예 새로 덮어쓰기 위함임.
  • 주의할 점은 이미 내용이 있는 메서드를 확장하는 개념이지, 아무 내용 없는 추상 메서드를 만드는 것과는 결이 다름.

Operator Overloading (연산자 오버로딩)

숫자 더하기나 문자열 합치기에 쓰이는 +, - 같은 연산자를 내가 만든 인스턴스끼리도 쓸 수 있게 만드는 기능임.

  • 원리: 연산자마다 정해진 특수 메서드 이름이 있는데, 그걸 클래스 안에 정의해주면 됨.
  • 만약 기존에 정의가 안 되어 있었다면 새로운 기능이 생기는 거고, 이미 있다면 내가 만든 로직으로 바뀜.

1. + 연산자 (__add__)

  • std1 + std2라고 쓰면 내부적으로는 std1.__add__(std2)가 호출됨.
  • 예시처럼 self.name + other.name을 리턴하게 하면 학생 인스턴스끼리 더했을 때 이름이 합쳐진 결과가 나옴.
class Student:
    #init 생략
    # + 연산자 오버로딩
    def __add__(self, other):
        return self.name + other.name
        
std1 = Student(name='수정')

std1.setStdInfo('수정', 20, '서울시')
std2 = Student(name='김ㅇㅇ')
result = std1 + std2
print(result)

2. 문자열 변환 (__str__)

  • 인스턴스를 print()하거나 str()로 변환할 때 어떤 내용을 보여줄지 결정하는 메서드임.
  • 원래는 메모리 주소 같은 게 나오지만, 이걸 정의해두면 self.__name처럼 내가 보고 싶은 정보를 예쁘게 출력할 수 있음.
 class Student: 
 	def __str__(self):
       return self.__name
  
std1 = Student(name='수정')

std1.setStdInfo('수정', 20, '서울시')

print(str(std1))

 


Singleton Pattern (싱글톤 패턴)

인스턴스를 여러 개 만들지 않고, 프로그램 전체에서 딱 하나만 유지하도록 보장하는 디자인 패턴임.

  • 원리: __new__ 메서드를 이용함. 인스턴스를 새로 만들려고 할 때마다 이미 만들어진 게 있는지 확인하고, 있으면 새로 안 만들고 기존 걸 그대로 돌려줌.
  • 작동 방식:
    1. 클래스 내부에 인스턴스를 저장할 공간(__instance)을 미리 비워둠.
    2. 처음 호출될 때만 진짜로 인스턴스를 생성해서 저장함.
    3. 그다음부터는 이미 저장된 인스턴스를 무조건 반환함.
  • 결과: 위 코드에서 sub1 is sub2를 출력하면 True가 나옴. 즉, 변수 이름은 두 개지만 실제 메모리에 있는 놈은 똑같은 놈임.
  • 용도: 데이터베이스 연결 객체나 설정 정보처럼, 여기저기서 여러 개 만들면 꼬이고 리소스 낭비되는 기능에 주로 사용함.
class Singleton:
    __instance = None
    
    def __new__(cls, *agrs, **kwargs):
        if cls.__instance is None:
            cls.__instance  = object.__new__(cls, *agrs, **kwargs)
        return cls.__instance

class Sub(Singleton):
    a = 10

sub1 = Sub()
sub2 = Sub()
print(sub1 is sub2)

 


Inheritance (상속)

부모(상위) 클래스가 가진 데이터와 기능을 자식(하위) 클래스가 그대로 물려받는 것임.

  • 용어 정리:
    • 물려주는 쪽: Super 클래스, Based 클래스
    • 물려받는 쪽: Sub 클래스, Derived 클래스
  • 상속의 종류:
    • 단일 상속: 부모 클래스 하나만 상속받음.
    • 다중 상속: 여러 개의 부모 클래스로부터 기능을 다 가져옴. (파이썬은 괄호 안에 나열해서 사용 가능)
  • 목적: 똑같은 코드 또 쓰기 싫을 때(중복 제거), 혹은 이미 만들어진 기능을 좀 더 확장하고 싶을 때 씀.

하위 클래스의 속성 사용과 __init__ 법칙

상속을 받으면 부모의 속성도 내 것처럼 쓰고 싶지만, 부모 클래스의 인스턴스 초기화 과정이 반드시 선행되어야 함.

1. 하위 클래스에 __init__이 없는 경우

  • 파이썬이 알아서 부모의 __init__을 호출해 줌. 그래서 아무 작업 안 해도 부모의 속성을 물려받아 쓸 수 있음.

2. 하위 클래스에 __init__을 직접 만드는 경우

  • 자식 클래스의 __init__이 실행되느라 부모의 __init__이 무시될 수 있음.
  • 부모에게 매개변수가 필요한 경우: 반드시 자식의 __init__ 안에서 super().__init__(매개변수)를 명시적으로 호출해야 함. 그래야 부모의 데이터도 정상적으로 세팅됨.
  • 부모에게 매개변수가 필요 없는 경우: 직접 안 써줘도 묵시적으로 호출되긴 하지만, 코드의 명확성을 위해 적어주는 경우도 많음.
class Super:
    def __init__(self, name):
        print("super의 init")
        self.name = name
    def greeting(self):
        print("상위 클래스 메서드")

class Sub(Super):
    def __init__(self):
        super().__init__("name!!!")
    def hello(self):
        print("하위 클래스 메서드")
        print(self.name)

sub = Sub()
sub.hello() 
sub.greeting()

 

Method Overriding (메서드 오버라이딩)

부모(상위) 클래스에 이미 만들어진 메서드를 자식(하위) 클래스에서 내 입맛에 맞게 다시 정의

 

1. 목적: 기능 확장

  • 단순히 부모 기능을 버리는 게 아니라, 부모가 해오던 일에 내가 원하는 기능을 더 추가해서 확장하는 게 주된 목적임.
  • 이때 super().메서드명()을 호출해서 부모의 기존 기능을 먼저 실행해주고, 그 아래에 내가 추가하고 싶은 코드를 적어주는 방식을 많이 씀.

2. 생성과 소멸의 순서

  • 생성 (상위 → 하위): 찰흙으로 공을 만들 때 속(노른자)을 먼저 빚고 겉(흰자)을 감싸는 것과 같음. 부모가 먼저 메모리에 만들어져야 자식이 그걸 감싸면서 태어날 수 있음.
  • 소멸 (하위 → 상위): 생성의 역순임. 겉에 있는 흰자(자식)가 먼저 사라져야 그 안에 있는 노른자(부모)도 비로소 소멸할 수 있음.
class Super:
    def __init__(self, name):
        print("super의 init")
        self.name = name
    def greeting(self):
        print("상위 클래스 메서드")

class Sub(Super):
    def __init__(self):
        super().__init__("name!!!")
    def insa(self):
        print("하위 클래스 메서드")
        print(self.name)
    
    #메서드 오버라이딩
    def greeting(self):
        super().greeting()
        print("하위 클래스의 메서드")

sub = Sub()
sub.insa() 
sub.greeting()

 

상속과 오버라이딩 차이

구분 상속 (Inheritance) 오버라이딩 (Overriding)
의미 부모의 모든 걸 물려받는 것 물려받은 걸 내 식대로 고치는 것
관계 상속이 되어야 오버라이딩도 가능함 상속받은 메서드 중에서 골라 고침
비유 부모님께 집을 물려받음 물려받은 집의 벽지 색을 바꿈

 

다중 상속과 추상 클래스

1. 다중 상속과 확장성

상위 클래스가 여러 개인 하위 클래스를 만드는 것. (ex. 스마트폰 = 전화기 + 카메라)

  • 특징: 여러 클래스의 속성을 한 번에 물려받아 확장성이 매우 뛰어남.
  • 주의점: 유지보수가 어려워 실무에선 권장하지 않음. 이름이 겹치면 .mro()로 우선순위 확인 필수.

2. Method Overriding (메서드 오버라이딩)

부모 클래스의 메서드를 자식 클래스에서 내 입맛에 맞게 다시 정의하는 것.

① 목적: 기능 확장

  • 부모의 기능을 단순히 무시하는 게 아니라, 부모가 하던 일에 내 기능을 더하는 것이 핵심.
  • super().메서드명()을 호출해 부모 기능을 먼저 실행하고, 그 뒤에 내 코드를 추가함.

② 생성과 소멸의 순서

  • 생성 (상위 → 하위): 찰흙 공을 만들 때 속(부모)을 먼저 빚고 겉(자식)을 감싸는 것과 같음. 부모가 먼저 메모리에 있어야 자식이 태어남.
  • 소멸 (하위 → 상위): 생성의 역순. 겉면인 자식이 먼저 사라져야 안쪽의 부모도 소멸함.

3. 추상 클래스 (Abstract Class)와 템플릿

인스턴스를 만들 수 없는 '뼈대' 전용 클래스. 반드시 1개 이상의 추상 메서드를 가져야 함.

  • 추상 메서드: 내용은 비어있고(pass), 자식 클래스가 반드시 오버라이딩해야만 하는 메서드.
  • 만드는 법: abc 모듈 가져오기 → (metaclass=ABCMeta) 설정 → @abstractmethod 붙이기.
  • 목적: Template Programming. 모양(Template)은 부모가 잡고, 실제 내용(Implementation)은 자식이 채움. (메뉴판 = 템플릿, 요리 = 구현)
import abc

class Login(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def login(self, id, pw): pass

class LoginInfo(Login):
    def login(self, id, pw):
        print("Login Complete!")
        # 부모의 추상 메서드를 오버라이딩하여 확장
        return super().login(id, pw)

 

 

4. 도메인(Domain)과 구조 (MSA)

  • 도메인: 소프트웨어가 해결하려는 현실 세계의 업무 범위 (배달 앱의 '주문', '음식' 등).
  • MSA 계층: Controller - Service - Repository 구조에서 Service를 쪼개어 복잡한 문제를 해결함.

5. 폴리모피즘 (Polymorphism, 다형성)

추상 클래스를 상속받은 여러 클래스들이 각자 다른 방식으로 attack()을 구현하는 것.

class TFT(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def attack(self): pass

class Aionia(TFT):
    def attack(self): print("아이오니아")

class Gonghu(TFT):
    def attack(self): print("공허")

# 같은 attack()을 호출해도 객체마다 결과가 다름 (다형성)
units = [Aionia(), Gonghu()]
for u in units: u.attack()

 

 

 

OOP 개념과 클래스 정리

OOP 핵심 개념 구체적인 내용 핵심 목적
캡슐화
(Encapsulation)
__속성 (Private), Getter/Setter,
@property
데이터를 숨겨서 보호하고, 정해진 방법으로만 접근하게 함
상속 (Inheritance) Super/Sub 클래스, super().__init__(),
__slots__
부모의 코드를 재사용해서 중복을 제거하고 기능을 확장함
다형성
(Polymorphism)
Method Overriding,
Operator Overloading (__add__ 등)
같은 이름의 메서드나 연산자가 상황에 따라 다르게 동작하게 함
추상화 (Abstraction) Method, Static Method, Class Method 상세한 구현은 몰라도 필요한 기능(메서드)만 호출해서 쓰게 함

 

추가 주요 내용 정리

개념 주요 특징 한 줄 요약
생성자/소멸자 __init__, __del__ 객체가 태어날 때(초기화)와 죽을 때(뒷정리) 실행되는 특수 메서드
메모리 관리 참조 카운트 (Reference Counting) 나를 가리키는 변수가 0개가 되면 메모리에서 자동으로 삭제됨
싱글톤 패턴 __new__를 이용한 인스턴스 제한 프로그램 전체에서 딱 하나의 인스턴스만 공유해서 사용함
변수 명명법 스네이크 케이스 / 파스칼 케이스 클래스는 대문자 시작, 나머지는 소문자와 언더바(_) 사용 (PEP 8)
728x90
반응형

'BASIC > PYTHON' 카테고리의 다른 글

Exception Handling(예외 처리)  (0) 2025.12.23
유용한 함수  (0) 2025.12.23
모듈과 패키지  (0) 2025.12.23
PEP 8  (0) 2025.12.22
AoP와 데코레이터  (0) 2025.12.19