cmod.ify
OOP(객체 지향 프로그래밍) 본문
- Encapsulation(캡슐화)
- 불필요한 부분을 외부 노출하지 않기
- 클래스와 인스턴스를 어떻게 만들 것인가?
- Inheritance(상속)
- 상위 클래스의 모든 요소를 하위 클래스가 물려 받는 것(상위 클래스가 물려주는 것 xxx)
- 중복제거 : 같이 바꿔야 하는 값을 실수없이 변경 가
- Framework Library 사용
- 상위 클래스의 모든 요소를 하위 클래스가 물려 받는 것(상위 클래스가 물려주는 것 xxx)
- Polymorphism(다형성)
- 동일한 코드가 호출하는 대상에 따라 다른 작업을 수행하는 것
- 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__ 메서드를 이용함. 인스턴스를 새로 만들려고 할 때마다 이미 만들어진 게 있는지 확인하고, 있으면 새로 안 만들고 기존 걸 그대로 돌려줌.
- 작동 방식:
- 클래스 내부에 인스턴스를 저장할 공간(__instance)을 미리 비워둠.
- 처음 호출될 때만 진짜로 인스턴스를 생성해서 저장함.
- 그다음부터는 이미 저장된 인스턴스를 무조건 반환함.
- 결과: 위 코드에서 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) |
'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 |