티스토리 뷰

반응형

객체 지향 프로그래밍

객체 지향 프로그래밍은 절차 지향 프로그래밍의 단점을 보완하며 등장한 개념이다.

그렇다면 우선, 절차 지향 프로그래밍이 뭔지 알아보자.


절차 지향 프로그래밍이란 ?

“컴퓨터 프로그램은 명령어의 목록이다.” 라는 개념으로 프로그래밍하는 것이다.

절차 지향 프로그래밍에서 프로그램은 유기적으로 연결된 하나의 흐름이다.

하나로 연결된 프로그램

프로그램의 수행 절차가 정해져있어 실행이 빠르다. 순서대로 작업을 수행하니 당연히 빠르게 수행할 수 있다. 그러나, 하나의 흐름으로 설계되어 있기 때문에 한 번 설계하고 나면 중간에 다른 작업을 추가하거나, 삭제하는 등의 변경이 어렵다. 프로그램이 확장되면서 데이터가 많아지고, 하드웨어가 발전하고 있는 상황에서 이 방법은 생산성이 매우 낮다.


객체 지향 프로그래밍의 등장

그래서 개발자들은 “프로그램을 하나로 연결되게 하지말고, 하나하나 독립된 덩어리들을 생성하고 그 덩어리들이 상호작용하게 만들면 어떨까?” 하고 생각한다.

이렇게 등장한 개념이 바로 객체 지향 프로그래밍이다. 객체 지향 프로그래밍에서 프로그램은 여러 개의 독립된 객체객체 간의 상호작용으로 이루어진 것이다.

여러 개의 객체와 객체들의 상호작용으로 이루어진 프로그램

예를 들어, 콘서트라는 프로그램을 설계한다고 가정하자. 콘서트의 구성요소를 가수, 감독, 관객 등으로 나눌 수 있다. 이 때 가수, 감독, 관객 각각을 객체라고 한다.

 

그렇다면 절차 지향에서 발전된 개념이 객체 지향일까? 그렇지 않다.

객체 지향 프로그램 내에서도 작업 수행 순서를 정의한 절차가 존재한다.

따라서 객체 지향 ⊃ 절차 지향의 개념으로 이해하는 것이 더 적절하다.

 

 

절차 지향 함수를 보완하기 위해 등장한 개념이지만, 여전히 단점은 존재한다.

 

하나, 설계 시 많은 시간과 노력이 필요하다. 다양한 객체들의 상호 작용을 위한 구조를 짜기 위해 머리를 싸매야한다.

둘, 실행 속도가 절차 지향보다 다소 느리다. 절차 지향이 컴퓨터의 처리구조와 비슷하기 때문에 속도가 빠른 장점이 있었다.


 

객체 지향 프로그래밍이 필요한 이유

이러한 단점이 존재함에도 개발자들이 객체 지향 프로그래밍을 선택한 이유는 무엇일까?

 

현실 세계를 프로그램에 반영하기 위해서는 추상화가 필요하다.

객체 지향 프로그래밍은 객체를 통해 현실 세계를 추상화할 수 있다.

 

또한, 객체 자체에 속성과 행동이 정의되어있어 내부 구조를 이해하지 못해도 그냥 단순히 객체를 가져와서 개발이 가능하다. 또한 다른 객체와 이리저리 조립이 가능하다. 이러한 특징 때문에 많은 인원이 참여하는 대규모 소프트웨어 개발이 용이해진다.

 

즉, 객체 지향 프로그래밍의 개발 용이성, 유지보수 편의성, 신뢰성을 통해 생산성이 증가한다.

객체의 개념

앞서 잠시 언급한 객체의 개념에 대해 좀 더 자세히 살펴보자.

가수 객체는 다음과 같은 속성기능을 가진다.

  • 가수 객체
    • 속성 : 나이, 성별, 앨범, 장르 등
    • 기능: 노래하기, 춤추기, 작곡하기 등

우리는 이 속성을 데이터로, 기능을 메서드라고 부른다.

앞에서 이야기한 “객체 자체에 속성과 행동이 정의되어있다”는 것이 이 때문이다.

또한 가수 객체는 현실의 가수 (지디, 이찬혁, 아이유 등)을 추상화한 개념이다.

 

→ 따라서 객체는 데이터와 메서드가 분리된 추상화된 구조라고 정의할 수 있다.

객체의 생성

이제 이 객체(Object)를 생성해보자. 객체를 생성하려면 먼저 클래스가 필요하다.

Object (= Instance)는 Class를 토대로 만들고, 생성된 Object가 실제 메모리에 할당된다.

객체를 인스턴스라고도 부른다. 그러나 두 개가 완전히 같은 개념은 아니다.

 

말하자면

객체는 그 자체로 객체이지만, 인스턴스는 클래스와의 관계 속에서 해당 클래스의 인스턴스다.

예를 들어, 가수 클래스의 지디 객체가 존재하는 경우를 생각해보자.

  • 지디는 객체다.
  • 지디가수의 인스턴스다.

다음과 같이 지디 객체를 표현할 수 있다.

  • 지디 는 인스턴스다.

그러나 위 표현은 틀렸다.


객체 지향 프로그래밍 언어 파이썬

name = 'Meeseeks'
print(type(name))

>>> <class 'str'>

name 변수에 문자열을 할당하고, type을 출력해보면 위와 같은 결과가 반환된다.

여기서 class에 주목해보자. 결과를 해석해보면 name 변수는 str 클래스를 가진다는 뜻이다.

 

그렇다 사실 우리가 파이썬에서 사용하는 모든 자료형은 사실 class였다..!
즉, 우리가 Class를 생성하는 것은, 우리만의 새로운 데이터 타입을 생성하는 것과 같다.

 

앞서 배운 개념을 적용해보자면 namestr 클래스를 기반으로 만들어진 객체다.

 

다른 예를 한 번 더 살펴보자.

students = ['하늘', 'Meeseeks']
students.append('redhorse')
print(students)
print(type(students))

>>> ['하늘', 'Meeseeks', 'redhorse']
>>> <class 'list'>

이 익숙한 코드를 객체 지향 프로그래밍 관점에서 다시 본다면,

  • Class : list
  • Object (Instance) : students
    • Data : [ ]안에 요소가 담김, 각 요소는 인덱스를 가짐
    • Method : append

이렇게 정의할 수 있다.

우리는 list 클래스를 기반으로 students 객체를 생성했고, students 객체가 실제 메모리에 할당된다.

클래스가 설계도라면, 인스턴스는 설계도를 통해 만들어낸 하나하나의 실체다.

 


Class 생성하기

클래스와 객체 개념에 대해 이해했으니 직접 클래스, 우리만의 데이터 타입을 정의해보자.

class IBA():
	
	def study(self):
		return 'fire!'

	def nogada(self):
		return 'holy moly'

class 를 사용해 IBA 클래스를 생성한다.

  • 인스턴스 메서드 study nogada 를 정의한다.
Meeseeks = IBA()

print(type(Meeseeks))
print(Meeseeks.nogada())

>>> <class 'IBA'>
>>> holy moly

IBA 클래스를 기반으로 Meeseeks 인스턴스를 생성한다.

  • Meeseeks 의 type을 출력해보면 IBA인 것을 확인할 수 있다.
  • Meeseeks IBA클래스 이기 때문에 IBA클래스의 인스턴스 메서드 nogada 를 사용할 수 있다.

위 과정을 통해 int, str, list 등과 같은 IBA라는 새로운 데이터타입을 생성했고, IBA데이터 타입에 해당하는 Meeseeks 라는 변수를 생성했다.


객체 비교

이제 객체가 무엇인지 이해하게 되었다. 이전에는 단순히 변수에 값을 할당하고 해당 변수의 데이터 타입이 무엇인지만 알 수 있었다면, 이제 변수는 객체고 데이터타입은 클래스라는 것을 알 수 있다.

이제 두 객체가 있을 때, 두 개가 같은 것인지 판별하려고 한다. 이 때 우리는 == 과 is 두 가지를 사용할 수 있다. 얕은 복사, 깊은 복사에서 공부한 내용을 토대로 두 방법의 차이를 알아보자.

a = [1, 2, 3]
b = [1, 2, 3]

print(a == b, a is b)
>>> True False

a = [1, 2, 3]
b = a

print(a == b, a is b)
>>> True True

==은 단순히 내용만 같으면 True를 반환한다.

그러나 is 는 메모리를 공유하는 완전히 동일한 객체를 비교할 때에만 True를 반환한다.


클래스/인스턴스 변수

객체 지향 프로그래밍 개념에서는 클래스 변수와 인스턴스 변수를 나누어 정의한다.

다시 IBA클래스로 돌아가보자.

class IBA():

	position = '일반부원입니다.'
	member = 0

	def __init__(self, name, major, pre_course=0):
		self.name = name
		self.major = major
		self.pre_course = pre_course

IBA 클래스에는 클래스 변수인스턴스 변수가 존재한다.

클래스 변수는 해당 클래스에 속하는 모든 인스턴스가 공통적으로 가지는 변수다.

인스턴스 변수는 각 인스턴스들의 고유한 변수다.

 

위 코드에서 position member 는 클래스 변수이며, self.name, self.major, self.pre_course 는 인스턴스 변수다. 인스턴스 변수에서 self는 인스턴스 자신을 말한다.

member1 = IBA('Meeseeks', 'Business', pre_course=1)
member2 = IBA('Heundoonge', 'Design')

print(member1.position)
print(member1.name)
print(member1.major)
>>> 일반부원입니다.
>>> Meeseeks
>>> Business

print(member2.position)
print(member2.name)
print(member2.major)
>>> 일반부원입니다.
>>> Heundoonge
>>> Design

IBA 클래스인 인스턴스 member1 member2 를 생성하고, 각각 인스턴스 변수 값을 지정한다.

각 인스턴스의 변수들을 출력해보면 잘 입력된 것을 확인할 수 있다.

이때 position 은 클래스 변수이므로 두 인스턴스가 같은 값을 공유한다.

나머지 변수들은 인스턴스 변수이므로, 각각의 인스턴스마다 따로 할당된 값이 출력된다.

 


🙋🏻‍♀️ 클래스 변수와 기본 값을 가지는 인스턴스 변수의 차이점 ?
  • 정의되는 공간이 다르다.
  • 클래스 변수 : 모든 인스턴스가 공통으로 가지는 변수 → 변경되면 모든 인스턴스의 해당 변수값 변함
  • 인스턴스 변수 : 기본값을 설정하면 공통으로 가지는 변수같지만, 어쨌든 각자가 가지는 변수
→ 인스턴스별로 다른 값을 가질 수도 있는 변수는 인스턴스 변수로 사용하자 !
→ 어차피 인스턴스마다 같은 값을 가질 거라면, 굳이 인스턴스마다 설정하느니 클래스 변수로 사용하자 !

 


클래스와 인스턴스의 Namespace

클래스 변수와 인스턴스 변수가 따로 존재한다는 사실을 알게 되었다.

그런데, “한 파이썬 파일 안에서 변수가 정의되는 공간이 다른 것” 어디서 많이 본 듯하다.

바로 함수의 namespace다.

 

 

클래스와 인스턴스의 관계에서도 특정 속성(데이터, 변수)에 접근할 때 순서가 존재한다.

함수와 마찬가지로 안에서 밖으로, 인스턴스에서 클래스 순으로 탐색한다.
class Business():
	president = 'Meeseeks'
	professor = 'Hong'
	
	def __init__(self, president):
		self.president = president

IBA = Business('후니')
Caplus = Business('리미')

Business 클래스 내에 IBA , Caplus 인스턴스가 존재한다.

각 인스턴스는 professor 클래스 변수와 president 인스턴스 변수를 가진다.

 

 

특정 속성에 접근할 때는 인스턴스 → 클래스 순이라는 것을 배웠다. 각 인스턴스에 대해 변수를 출력해서 확인해보자.

print(IBA.president)
print(Caplus.president)

print(IBA.professor)

>>> 후니
>>> 리미
>>> Hong

president 속성값의 경우, 인스턴스의 namespace에서 찾을 수 있기 때문에, 클래스 name space까지 해당 속성값을 찾으러 가지 않고 인스턴스 변수를 반환한다.

professor 는 인스턴스의 namespace에 존재하지 않는다. 따라서 클래스 namespace에서 해당 속성값을 찾아 반환한다.


인스턴스 메서드

위 코드들에서 계속해서 __init__ 이 코드가 등장하는데, 어떤 의미일까?

클래스/인스턴스 변수처럼 메서드 또한 세 가지로 나눌 수 있다.

클래스 메서드, 인스턴스 메서드와 더불어 ‘정적 메서드’ 라는 것이 존재한다.

우선 인스턴스 메서드에 대해 알아보자.

  • 인스턴스 메서드는 인스턴스 변수를 사용하는 함수다.
  • 첫 번째 인자로 반드시 self 를 사용한다.
  • 가장 많이 쓰게 될 메서드다. (90% 이상!)

인스턴스 메서드에는 특이한 메서드가 있다. 특정 상황에서 자동으로 호출되는 메서드다. 우리는 이걸 매직 메서드라고 부른다. 계속 등장하는 __init__ 도 바로 이 매직 메서드 중 하나다.

 

__init__ 인스턴스를 생성하는 상황에서 자동으로 호출된다. 인스턴스를 생성할 때 어떤 작업을 수행하고 싶다면, 이 매직 메서드를 사용한다. 이 메서드는 특별히 생성자 메서드라고 부른다.

 

IBA 클래스 코드로 다시 돌아가보자.

class IBA():

	position = '일반부원입니다.'
	member = 0

	def __init__(self, name, major, pre_course=0):
		self.name = name
		self.major = major
		self.pre_course = pre_course

IBA 클래스의 인스턴스는 생성됨과 동시에 name , major, pre_course 변수를 생성한다.

이 작업을 수행하는 함수 __init__ 이 바로 생성자 메서드다.

 

 

이외에도

  • __str__ : 해당 클래스를 문자열처럼 사용해야하는 상황인 경우(print 함수)
  • __len__ : len함수에 넣으면 반환하는 값을 설정해주는 메서드

등의 매직 메서드가 있다. 눈치챘겠지만 매직 메서드는 __이름__ 형태로 생겼다.


클래스 메서드

클래스 메서드의 특징은 다음과 같다.

  • 클래스 변수를 사용하는 함수다.
  • 첫 번째 인자로 반드시 cls(클래스)가 전달된다.
  • 메서드 위에 @classmethod 데코레이터로 정의한다.
  • 각 인스턴스에 대해서는 사용할 필요가 없을 때, 클래스 메서드를 사용한다.
  • 인스턴스에서 사용해도 클래스 전체의 결과를 반환한다.
class IBA():

	position = '일반부원입니다.'
	member = 0

	def __init__(self, name, major, pre_course=0):
		self.name = name
		self.major = major
		self.pre_course = pre_course
		IBA.recruit()

	@classmethod
	def recruit(cls):
		cls.member += 1

	@classmethod
	def get_member(cls):
		print(cls.member)

member1 = IBA('Meeseeks', 'Business', pre_course=1)
member2 = IBA('Heundoonge', 'Design')

IBA.get_member()

>>> 2

recruit 메서드는 잠시 무시하고 get_member 메서드를 살펴보자.

 

데코레이터를 지정해주고, get_member 클래스 메서드를 정의하였다.

첫 번째 인자로 cls 를 사용하며, 메서드 안에서 클래스 변수인 member 를 사용한다.

이렇게 정의한 클래스 메서드는 클래스.메서드() 형태로 호출한다.

IBA.get_member() 를 호출한 결과 2가 반환되는 것을 확인할 수 있다.

반환되는 결과는 특정 인스턴스에 대한 결과가 아닌 전체 클래스에 대한 결과인 것을 알 수 있다.


스태틱 메서드

마지막 스태틱 메서드의 특징을 살펴보자.

  • 인스턴스 변수, 클래스 변수 모두 사용하지 않는 함수
  • 인스턴스/클래스 변수를 사용하지 않지만, 해당 클래스와 연관있는 함수일 때 사용한다.
  • 메서드 위에 @staticmethod 데코레이터로 정의한다.
  • 주로 해당 클래스를 한정하는 용도로 사용한다.
  • 스태틱 메서드를 사용하여 인스턴스/클래스 상태를 수정할 수 없다.

클래스 메서드, 인스턴스 메서드, 스태틱 메서드

세 메서드를 다음과 같이 정리할 수 있다.

class MyClass:
	def method(self):
		return 'instance method'

	@classmethod
	def classmethod(cls):
		return 'class method'
	
	@staticmethod
	def staticmethod():
		return 'static method'

my_class = MyClass()
print(my_class.method())
print(my_class.classmethod())
print(my_class.staticmethod())

인스턴스 메서드

  • 가장 많이 쓰는 메서드
  • self 를 인자로 받음
  • 함수 내에 인스턴스, 클래스 변수를 사용할 수 있다.
  • 인스턴스가 사용할 수 있다.

클래스 메서드

  • @classmethod 데코레이터를 함수 위에 붙인다.
  • cls 를 인자로 받음
  • 함수 내에 클래스 변수를 사용할 수 있다.
  • 인스턴스, 클래스가 사용할 수 있다.

스태틱 메서드

  • @staticmethod 데코레이터를 함수 위에 붙인다.
  • 인스턴스, 클래스 외의 변수를 인자로 받음
  • 함수 내에 인스턴스, 클래스 변수가 아닌 것을 사용한다.
  • 인스턴스, 클래스가 사용할 수 있다.

클래스와 인스턴스가 사용하는 메서드

조금 헷갈리니 다시 한 번 정리해보자.

우리는 메서드를 사용할 때 클래스.메서드() 또는 인스턴스.메서드() 형식으로 사용한다.

클래스와 인스턴스가 사용할 수 있는 메서드는 각각 다르다.

Method/메서드 호출 주체 Class Instance

Static O O
Class O O
Instance X O

정리해보면,

인스턴스는 모든 메서드를 사용할 수 있고, 클래스는 인스턴스 메서드를 사용할 수 없다.

우리가 개발할 때는 다음을 기준으로 어떤 메서드를 생성할지 선택하면 된다.

 

메서드 안쪽에서 인스턴스 변수를 사용할 것인스턴스 메서드로 생성한다.

메서드 안쪽에서 클래스 변수만 사용할 것클래스 메서드로 생성한다.

메서드 안쪽에 인스턴스/클래스 변수 둘 다 사용하지 않을 것스태틱 메서드로 생성한다.


데코레이터

클래스 메서드를 사용할 때 메서드 위에 @classmethod 데코레이터를 사용한다는 것을 배웠다.

이 때 등장하는 데코레이터는 무엇일까?

 

 

데코레이터를 사용하면 아주 쉽게 한 함수에 다른 함수의 기능을 부여할 수 있다.

def professor(name):
	return f'반갑네, {name}교수일세.'

def student(name):
	return f'안녕하세요, 학부생 {name}입니다.'

print(professor('홍'))
print(student('Meeseeks'))

>>> '반갑네, 홍교수일세.'
>>> '안녕하세요, 학부생 Meeseeks입니다.'

professor 함수와 student 함수는 입력받은 이름과 함께 인사를 건네는 함수다.

그냥 인사만 하니 너무 삭막해보여서 인사 뒤에 눈웃음을 추가하려고 한다.

두 함수 모두 수정하여 출력하자니 귀찮다.

 

이 때 데코레이터를 사용하면 기존 함수는 수정하지 않고 매우 간단하게 원하는 값을 얻을 수 있다.

def smile(func):
	def wrapper(name):
		print(func(name), end = '')
		print('^^')
	return wrapper

@smile
def professor(name):
	return f'반갑네, {name}교수일세.'

@smile
def student(name):
	return f'안녕하세요, 학부생 {name}입니다.'

professor('홍')
student('Meeseeks')

>>> '반갑네, 홍교수일세.^^'
>>> '안녕하세요, 학부생 Meeseeks입니다.^^'

데코레이터를 사용하여 기존 함수를 변경하지 않고 새로운 기능을 추가해주었다.

먼저, smile 함수를 정의해주었다. smile 함수의 구성을 좀 더 자세히 보면 다음과 같다.

  • 입력값으로 함수를 받는 smile 이라는 함수를 정의한다.
    • 입력값으로 name을 받는 wrapper라는 함수를 정의한다.
    • 기존 함수의 기능 가져옴
    • 추가할 기능
    • wrapper 반환

→ 데코레이터로 사용할 함수 정의

[ wrapper 함수 정의

       [ 기존 함수 기능 가져옴, 새로운 기능 추가 ]

 wrapper 함수 반환 ]

그 다음 smile 함수의 기능을 추가하고 싶은 함수 위에 데코레이터 @smile 을 붙여준다.


객체 지향의 핵심 개념

객체 지향에는 핵심 개념이 존재한다.

  • 추상화
  • 상속
  • 다형성
  • 캡슐화

이 네 가지의 핵심 개념을 차례대로 하나씩 알아보자.


추상화

디테일은 숨기고, 핵심만 드러내기

추상화는 위에서 설명했으니 생략 !


상속

코드 재사용 + 기능 확장

기존 클래스의 속성과 기능을 그대로 사용하면서, 다른 기능을 추가한 새로운 클래스가 필요하다면 상속관계를 이용하면 된다.

상속이라는 단어처럼 기존의 클래스를 부모 클래스, 부모 클래스로부터 상속받아 만들어진 새 클래스를 자식 클래스라고 한다.

상속관계에서 Namespace는 인스턴스 → 자식 클래스 → 부모 클래스 순서다.

class Person():
	def greeting(self):
		print('안녕하세요!')

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

	def self_introduce(self):
		print(f'IBA 부원 {self.name}입니다.')

member1 = IBA('Meeseeks')
member1.greeting()
member1.self_introduce()

>>> '안녕하세요!'
>>> 'IBA 부원 Meeseeks입니다.'

Person 클래스가 부모 클래스, IBA 클래스가 자식 클래스인 상속관계가 만들어졌다.

자식 클래스 생성시에 인자로 부모 클래스를 넣어주면 두 클래스의 상속이 이루어진다.

자식 클래스는 부모 클래스의 모든 변수와 메서드를 사용할 수 있다.

member1 은 IBA 의 인스턴스다. IBA 클래스에는 greeting 메서드가 없지만, greeting 메서드를 가지고 있는 Person 클래스를 상속받았기 때문에 member1 이 해당 메서드를 사용할 수 있는 것이다.

여러 클래스에서 상속을 받을 수도 있다. 이를 다중상속이라고 부른다. 만약, 여러 부모 클래스에 중복된 변수나 메서드가 있는 경우, 자식 클래스는 상속의 순서대로 해당 변수와 메서드를 사용하게 된다.

class Person():
	def greeting(self):
		print('안녕하세요!')

class Business():
	def greeting(self):
		print('안녕하세요, 경영학과 학부생입니다.')

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

	def self_introduce(self):
		print(f'IBA 부원 {self.name}입니다.')

member1 = IBA('Meeseeks')
member1.greeting()

>>> '안녕하세요, 경영학과 학부생입니다.'

Person 과 Business 두 개의 부모 클래스를 가지는 IBA 클래스가 있다.

두 부모 클래스 모두 greeting 메서드를 가지고 있다.

이 때 자식 클래스인 IBA 의 인스턴스에서 greeting 메서드를 불러오면 부모 클래스 둘 중 앞에 있는 Business 의 greeting 메서드를 가져온다.

자식 클래스에서 부모 클래스의 변수나 메서드를 사용하고 싶다면 super() 메서드를 사용한다.

class Person():
	def greeting(self):
		print('안녕하세요!')

class Business():
	major = '경영학과'

	def greeting(self):
		print('안녕하세요, 경영학과 학부생입니다.')

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

	def self_introduce(self):
		print(f'IBA 부원 {self.name}입니다.')
		print(f'전공은 {super().major}입니다.')

member1 = IBA('Meeseeks')
member1.self_introduce()

IBA 클래스의 self_introduce() 메서드에서 Business 클래스의 변수 major 를 사용했다.

이 때 super() 를 사용해서 부모 클래스의 변수를 불러온다. super() 외에도 해당 인스턴스의 클래스가 어떤 부모 클래스를 가지는지 확인하는 mro() 등 상속관계에서 사용할 수 있는 다양한 메서드가 있다.


🙋🏻‍♀️ 자식 클래스에서 굳이 왜 super()써서 부모 클래스의 생성자 메서드 가져오나?

 

자식 클래스의 __init__ 에서 super().__init__ 가져오면 따로 인스턴스 변수를 만들어서 할당할 필요없이 부모클래스와 같아 사용할 수 있다.


다형성

서로 다른 클래스의 객체들이 동일한 메세지에 대해 다르게 응답한다.

메서드 오버라이딩을 통해 객체들이 서로 다른 값을 반환하도록 설계할 수 있다.

메서드 오버라이딩은 부모 클래스의 메서드를 자식 클래스에서 변경하는 것이다.

부모 클래스 메서드의 기본 기능은 그대로 사용하지만, 특정 기능을 추가하고 싶을 때 사용한다.

이 또한 super() 를 사용한다.

class Person():
	def greeting(self):
		print('안녕하세요!')

class Business():
	major = '경영학과'

	def greeting(self):
		print('안녕하세요, 경영학과 학부생입니다.')

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

	def greeting(self):
		super().greeting()
		print(f'IBA 부원 {self.name}입니다.')

member1 = IBA('Meeseeks')
member1.greeting()

super() 를 사용하여 Business 의 greeting 메서드를 가져와 이름을 소개하는 새로운 기능을 추가한다.

  • greeting 메서드의 기본 기능 : ‘안녕하세요, 경영학과 학부생입니다.’
  • 새로운 기능 : ‘IBA 부원 Meeseeks입니다.’

super() 를 사용하지 않고 자식 클래스에서 같은 이름의 greeting 메서드를 새로 만든다면,

부모 클래스 greeting 메서드의 기능은 사용할 수 없고 단순히 덮어쓰기가 된다.


캡슐화

객체의 일부 구현에 대해 외부의 접근을 제어하는 것

클래스 내부적에 정의된 메서드와 속성에 대한 접근을 제어한다.

접근을 제어하는 방법은 3가지가 있다.

  • Public Access Modifier : 모두 접근가능, 기본 설정
  • Protected Access Modifier : 상속 관계에서만 접근가능
  • Private Access Modifier : 나만 가능 (내 클래스 안쪽에서만 가능)

이 세 가지를 접근제어자라고 부른다. 접근제어자들을 하나씩 살펴보자.


Public Member

  • 언더바가 없는 메서드와 속성
  • 클래스 내외부 어디에서나 호출가능
  • 하위 클래스에서의 오버라이드 허용
  • 가장 일반적이다.

Protected Memebr

  • 언더바 1개로 시작하는 메서드나 속성 : _age
  • 암묵적으로 부모 클래스와 자식클래스에서만 호출가능
  • : 파이썬 상에서 자동으로 제어되지 않고 개념적으로만 존재한다.
  • 하위 클래스에서의 오버라이드 허용

Private Memeber

  • 언더바 2개로 시작하는 메서드나 속성 : __age
  • 클래스 내부에서만 호출가능
  • : 파이썬 자체에서 호출이 불가능하게 제어한다.
  • 하위 클래스에서 상속 및 호출 불가능

<aside> 🙋🏻‍♀️ 매직 메서드도 프라이빗 ? → 상속 및 호출 가능한데 이름생긴거 땜에 헷갈림

</aside>


getter 메서드와 setter 메서드

접근이 제어된 메서드와 변수는 허용된 위치가 아닌 곳에서는 사용할 수 없을까?

getter 메서드와 setter 메서드를 활용하면 사용할 수 있다.

getter 메서드는 변수의 값을 조회하는 메서드, setter 메서드는 변수에 값을 할당하는 메서드다.

이 두 메서드와 데코레이터를 사용해서 Protected Access Modifier로 제어되고 있는 변수를 외부에서 호출해보겠다.

class IBA():
	def __init__(self):
		self._age = 0

	@property
	def age(self):
		return self._age

	@age.setter
	def age(self, age):
		self._age = age

member1 = IBA()
member1.age = 25
print(member1.age)

getter 메서드는 해당 함수명(age)을 외부에서 변수로 사용하고, 내부의 변수(_age)를 반환한다.

@property 데코레이터를 사용한다.

setter 메서드는 해당 함수명(age)을 외부에서 변수로 사용하고, 내부의 변수(_age)에 값을 할당한다. @함수명.setter 데코레이터를 사용한다.

setter 메서드 이전에 getter 메서드를 먼저 정의해야한다.


*정의한 클래스를 모듈로 사용하기

import 로 클래스를 정의해둔 파일을 불러와 모듈로 사용할 수 있다.

모듈로 불러온 클래스의 변수와 메서드가 먼저 실행됨 !

객체 지향 프로그래밍


객체 지향 프로그래밍은 절차 지향 프로그래밍의 단점을 보완하며 등장한 개념이다.

그렇다면 우선, 절차 지향 프로그래밍이 뭔지 알아보자.


절차 지향 프로그래밍이란 ?

“컴퓨터 프로그램은 명령어의 목록이다.” 라는 개념으로 프로그래밍하는 것이다.

절차 지향 프로그래밍에서 프로그램은 유기적으로 연결된 하나의 흐름이다.

하나로 연결된 프로그램

프로그램의 수행 절차가 정해져있어 실행이 빠르다. 순서대로 작업을 수행하니 당연히 빠르게 수행할 수 있다. 그러나, 하나의 흐름으로 설계되어 있기 때문에 한 번 설계하고 나면 중간에 다른 작업을 추가하거나, 삭제하는 등의 변경이 어렵다. 프로그램이 확장되면서 데이터가 많아지고, 하드웨어가 발전하고 있는 상황에서 이 방법은 생산성이 매우 낮다.


객체 지향 프로그래밍의 등장

그래서 개발자들은 “프로그램을 하나로 연결되게 하지말고, 하나하나 독립된 덩어리들을 생성하고 그 덩어리들이 상호작용하게 만들면 어떨까?” 하고 생각한다.

이렇게 등장한 개념이 바로 객체 지향 프로그래밍이다. 객체 지향 프로그래밍에서 프로그램은 여러 개의 독립된 객체객체 간의 상호작용으로 이루어진 것이다.

여러 개의 객체와 객체들의 상호작용으로 이루어진 프로그램

예를 들어, 콘서트 라는 프로그램을 설계한다고 가정하자. 콘서트 의 구성요소를 가수, 감독, 관객 등으로 나눌 수 있다. 이 때 가수, 감독, 관객 각각을 객체라고 한다.

그렇다면 절차 지향에서 발전된 개념이 객체 지향일까? 그렇지 않다.

객체 지향 프로그램 내에서도 작업 수행 순서를 정의한 절차가 존재한다.

따라서 객체 지향 ⊃ 절차 지향의 개념으로 이해하는 것이 더 적절하다.

절차 지향 함수를 보완하기 위해 등장한 개념이지만, 여전히 단점은 존재한다.

하나, 설계 시 많은 시간과 노력이 필요하다. 다양한 객체들의 상호 작용을 위한 구조를 짜기 위해 머리를 싸매야한다.

둘, 실행 속도가 절차 지향보다 다소 느리다. 절차 지향이 컴퓨터의 처리구조와 비슷하기 때문에 속도가 빠른 장점이 있었다.


객체 지향 프로그래밍이 필요한 이유

이러한 단점이 존재함에도 개발자들이 객체 지향 프로그래밍을 선택한 이유는 무엇일까?

현실 세계를 프로그램에 반영하기 위해서는 추상화가 필요하다.

객체 지향 프로그래밍은 객체를 통해 현실 세계를 추상화할 수 있다.

또한, 객체 자체에 속성과 행동이 정의되어있어 내부 구조를 이해하지 못해도 그냥 단순히 객체를 가져와서 개발이 가능하다. 또한 다른 객체와 이리저리 조립이 가능하다. 이러한 특징 때문에 많은 인원이 참여하는 대규모 소프트웨어 개발이 용이해진다.

즉, 객체 지향 프로그래밍의 개발 용이성, 유지보수 편의성, 신뢰성을 통해 생산성이 증가한다.


객체의 개념

앞서 잠시 언급한 객체의 개념에 대해 좀 더 자세히 살펴보자.

가수 객체는 다음과 같은 속성기능을 가진다.

  • 가수 객체
    • 속성 : 나이, 성별, 앨범, 장르 등
    • 기능: 노래하기, 춤추기, 작곡하기 등

우리는 이 속성을 데이터로, 기능을 메서드라고 부른다.

앞에서 이야기한 “객체 자체에 속성과 행동이 정의되어있다”는 것이 이 때문이다.

또한 가수 객체는 현실의 가수 (지디, 이찬혁, 아이유 등)을 추상화한 개념이다.

→ 따라서 객체는 데이터와 메서드가 분리된 추상화된 구조라고 정의할 수 있다.


객체의 생성

이제 이 객체(Object)를 생성해보자. 객체를 생성하려면 먼저 클래스가 필요하다.

Object (= Instance)는 Class를 토대로 만들고, 생성된 Object가 실제 메모리에 할당된다.

객체를 인스턴스라고도 부른다. 그러나 두 개가 완전히 같은 개념은 아니다.

말하자면

객체는 그 자체로 객체이지만, 인스턴스는 클래스와의 관계 속에서 해당 클래스의 인스턴스다.

예를 들어, 가수 클래스의 지디 객체가 존재하는 경우를 생각해보자.

  • 지디는 객체다.
  • 지디는 가수의 인스턴스다.

다음과 같이 지디 객체를 표현할 수 있다.

  • 지디 는 인스턴스다.

그러나 위 표현은 틀렸다.


객체 지향 프로그래밍 언어 파이썬

name = 'Meeseeks'
print(type(name))

>>> <class 'str'>

name 변수에 문자열을 할당하고, type을 출력해보면 위와 같은 결과가 반환된다.

여기서 class에 주목해보자. 결과를 해석해보면 name 변수는 str 클래스를 가진다는 뜻이다.

그렇다 사실 우리가 파이썬에서 사용하는 모든 자료형은 사실 class였다..!

즉, 우리가 Class를 생성하는 것은, 우리만의 새로운 데이터 타입을 생성하는 것과 같다.

앞서 배운 개념을 적용해보자면 name은 str 클래스를 기반으로 만들어진 객체다.

다른 예를 한 번 더 살펴보자.

students = ['하늘', 'Meeseeks']
students.append('redhorse')
print(students)
print(type(students))

>>> ['하늘', 'Meeseeks', 'redhorse']
>>> <class 'list'>

이 익숙한 코드를 객체 지향 프로그래밍 관점에서 다시 본다면,

  • Class : list
  • Object (Instance) : students
    • Data : [ ]안에 요소가 담김, 각 요소는 인덱스를 가짐
    • Method : append

이렇게 정의할 수 있다.

우리는 list 클래스를 기반으로 students 객체를 생성했고, students 객체가 실제 메모리에 할당된다.

클래스가 설계도라면, 인스턴스는 설계도를 통해 만들어낸 하나하나의 실체다.


Class 생성하기

클래스와 객체 개념에 대해 이해했으니 직접 클래스, 우리만의 데이터 타입을 정의해보자.

class IBA():
	
	def study(self):
		return 'fire!'

	def nogada(self):
		return 'holy moly'

class 를 사용해 IBA 클래스를 생성한다.

  • 인스턴스 메서드 study 와 nogada 를 정의한다.
Meeseeks = IBA()

print(type(Meeseeks))
print(Meeseeks.nogada())

>>> <class 'IBA'>
>>> holy moly

IBA 클래스를 기반으로 Meeseeks 인스턴스를 생성한다.

  • Meeseeks 의 type을 출력해보면 IBA 인 것을 확인할 수 있다.
  • Meeseeks 는 IBA 클래스 이기 때문에 IBA 클래스의 인스턴스 메서드 nogada 를 사용할 수 있다.

위 과정을 통해 int, str, list 등과 같은 IBA 라는 새로운 데이터타입을 생성했고, IBA 데이터 타입에 해당하는 Meeseeks 라는 변수를 생성했다.


객체 비교

이제 객체가 무엇인지 이해하게 되었다. 이전에는 단순히 변수에 값을 할당하고 해당 변수의 데이터 타입이 무엇인지만 알 수 있었다면, 이제 변수는 객체고 데이터타입은 클래스라는 것을 알 수 있다.

이제 두 객체가 있을 때, 두 개가 같은 것인지 판별하려고 한다. 이 때 우리는 == 과 is 두 가지를 사용할 수 있다. 얕은 복사, 깊은 복사에서 공부한 내용을 토대로 두 방법의 차이를 알아보자.

a = [1, 2, 3]
b = [1, 2, 3]

print(a == b, a is b)
>>> True False

a = [1, 2, 3]
b = a

print(a == b, a is b)
>>> True True

==은 단순히 내용만 같으면 True를 반환한다.

그러나 is 는 메모리를 공유하는 완전히 동일한 객체를 비교할 때에만 True를 반환한다.


클래스/인스턴스 변수

객체 지향 프로그래밍 개념에서는 클래스 변수와 인스턴스 변수를 나누어 정의한다.

다시 IBA 클래스로 돌아가보자.

class IBA():

	position = '일반부원입니다.'
	member = 0

	def __init__(self, name, major, pre_course=0):
		self.name = name
		self.major = major
		self.pre_course = pre_course

IBA 클래스에는 클래스 변수인스턴스 변수가 존재한다.

클래스 변수는 해당 클래스에 속하는 모든 인스턴스가 공통적으로 가지는 변수다.

인스턴스 변수는 각 인스턴스들의 고유한 변수다.

위 코드에서 position 과 member 는 클래스 변수이며, self.name self.major self.pre_course 는 인스턴스 변수다. 인스턴스 변수에서 self는 인스턴스 자신을 말한다.

member1 = IBA('Meeseeks', 'Business', pre_course=1)
member2 = IBA('Heundoonge', 'Design')

print(member1.position)
print(member1.name)
print(member1.major)
>>> 일반부원입니다.
>>> Meeseeks
>>> Business

print(member2.position)
print(member2.name)
print(member2.major)
>>> 일반부원입니다.
>>> Heundoonge
>>> Design

IBA 클래스인 인스턴스 member1 와 member2 를 생성하고, 각각 인스턴스 변수 값을 지정한다.

각 인스턴스의 변수들을 출력해보면 잘 입력된 것을 확인할 수 있다.

이때 position 은 클래스 변수이므로 두 인스턴스가 같은 값을 공유한다.

나머지 변수들은 인스턴스 변수이므로, 각각의 인스턴스마다 따로 할당된 값이 출력된다.


🙋🏻‍♀️ 클래스 변수와 기본 값을 가지는 인스턴스 변수의 차이점 ?
  • 정의되는 공간이 다르다.
  • 클래스 변수 : 모든 인스턴스가 공통으로 가지는 변수 → 변경되면 모든 인스턴스의 해당 변수값 변함
  • 인스턴스 변수 : 기본값을 설정하면 공통으로 가지는 변수같지만, 어쨌든 각자가 가지는 변수

→ 인스턴스별로 다른 값을 가질 수도 있는 변수는 인스턴스 변수로 사용하자 !

→ 어차피 인스턴스마다 같은 값을 가질 거라면, 굳이 인스턴스마다 설정하느니 클래스 변수로 사용하자 !


클래스와 인스턴스의 Namespace

클래스 변수와 인스턴스 변수가 따로 존재한다는 사실을 알게 되었다.

그런데, “한 파이썬 파일 안에서 변수가 정의되는 공간이 다른 것” 어디서 많이 본 듯하다.

바로 함수의 namespace다.

클래스와 인스턴스의 관계에서도 특정 속성(데이터, 변수)에 접근할 때 순서가 존재한다.

함수와 마찬가지로 안에서 밖으로, 인스턴스에서 클래스 순으로 탐색한다.

class Business():
	president = 'Meeseeks'
	professor = 'Hong'
	
	def __init__(self, president):
		self.president = president

IBA = Business('후니')
Caplus = Business('리미')

Business 클래스 내에 IBA , Caplus 인스턴스가 존재한다.

각 인스턴스는 professor 클래스 변수와 president 인스턴스 변수를 가진다.

특정 속성에 접근할 때는 인스턴스 → 클래스 순이라는 것을 배웠다. 각 인스턴스에 대해 변수를 출력해서 확인해보자.

print(IBA.president)
print(Caplus.president)

print(IBA.professor)

>>> 후니
>>> 리미
>>> Hong

president 속성값의 경우, 인스턴스의 namespace에서 찾을 수 있기 때문에, 클래스 name space까지 해당 속성값을 찾으러 가지 않고 인스턴스 변수를 반환한다.

professor는 인스턴스의 namespace에 존재하지 않는다. 따라서 클래스 namespace에서 해당 속성값을 찾아 반환한다.


인스턴스 메서드

위 코드들에서 계속해서 __init__ 이 코드가 등장하는데, 어떤 의미일까?

클래스/인스턴스 변수처럼 메서드 또한 세 가지로 나눌 수 있다.

클래스 메서드, 인스턴스 메서드와 더불어 ‘정적 메서드’ 라는 것이 존재한다.

우선 인스턴스 메서드에 대해 알아보자.

  • 인스턴스 메서드는 인스턴스 변수를 사용하는 함수다.
  • 첫 번째 인자로 반드시 self 를 사용한다.
  • 가장 많이 쓰게 될 메서드다. (90% 이상!)

인스턴스 메서드에는 특이한 메서드가 있다. 특정 상황에서 자동으로 호출되는 메서드다. 우리는 이걸 매직 메서드라고 부른다. 계속 등장하는 __init__ 도 바로 이 매직 메서드 중 하나다.

__init__ 은 인스턴스를 생성하는 상황에서 자동으로 호출된다. 인스턴스를 생성할 때 어떤 작업을 수행하고 싶다면, 이 매직 메서드를 사용한다. 이 메서드는 특별히 생성자 메서드라고 부른다.

IBA 클래스 코드로 다시 돌아가보자.

class IBA():

	position = '일반부원입니다.'
	member = 0

	def __init__(self, name, major, pre_course=0):
		self.name = name
		self.major = major
		self.pre_course = pre_course

IBA 클래스의 인스턴스는 생성됨과 동시에 name , major, pre_course 변수를 생성한다.

이 작업을 수행하는 함수 __init__ 이 바로 생성자 메서드다.

이외에도

  • __str__ : 해당 클래스를 문자열처럼 사용해야하는 상황인 경우(print 함수)
  • __len__ : len함수에 넣으면 반환하는 값을 설정해주는 메서드

등의 매직 메서드가 있다. 눈치챘겠지만 매직 메서드는 __이름__ 형태로 생겼다.


클래스 메서드

클래스 메서드의 특징은 다음과 같다.

  • 클래스 변수를 사용하는 함수다.
  • 첫 번째 인자로 반드시 cls(클래스)가 전달된다.
  • 메서드 위에 @classmethod 데코레이터로 정의한다.
  • 각 인스턴스에 대해서는 사용할 필요가 없을 때, 클래스 메서드를 사용한다.
  • 인스턴스에서 사용해도 클래스 전체의 결과를 반환한다.
class IBA():

	position = '일반부원입니다.'
	member = 0

	def __init__(self, name, major, pre_course=0):
		self.name = name
		self.major = major
		self.pre_course = pre_course
		IBA.recruit()

	@classmethod
	def recruit(cls):
		cls.member += 1

	@classmethod
	def get_member(cls):
		print(cls.member)

member1 = IBA('Meeseeks', 'Business', pre_course=1)
member2 = IBA('Heundoonge', 'Design')

IBA.get_member()

>>> 2

recruit 메서드는 잠시 무시하고 get_member 메서드를 살펴보자.

데코레이터를 지정해주고, get_method클래스 메서드를 정의하였다.

첫 번째 인자로 cls 를 사용하며, 메서드 안에서 클래스 변수인 member 를 사용한다.

이렇게 정의한 클래스 메서드는 클래스.메서드() 형태로 호출한다.

IBA.get_member() 를 호출한 결과 2가 반환되는 것을 확인할 수 있다.

반환되는 결과는 특정 인스턴스에 대한 결과가 아닌 전체 클래스에 대한 결과인 것을 알 수 있다.


스태틱 메서드

마지막 스태틱 메서드의 특징을 살펴보자.

  • 인스턴스 변수, 클래스 변수 모두 사용하지 않는 함수
  • 인스턴스/클래스 변수를 사용하지 않지만, 해당 클래스와 연관있는 함수일 때 사용한다.
  • 메서드 위에 @staticmethod 데코레이터로 정의한다.
  • 주로 해당 클래스를 한정하는 용도로 사용한다.
  • 스태틱 메서드를 사용하여 인스턴스/클래스 상태를 수정할 수 없다.

클래스 메서드, 인스턴스 메서드, 스태틱 메서드

세 메서드를 다음과 같이 정리할 수 있다.

class MyClass:
	def method(self):
		return 'instance method'

	@classmethod
	def classmethod(cls):
		return 'class method'
	
	@staticmethod
	def staticmethod():
		return 'static method'

my_class = MyClass()
print(my_class.method())
print(my_class.classmethod())
print(my_class.staticmethod())

인스턴스 메서드

  • 가장 많이 쓰는 메서드
  • self 를 인자로 받음
  • 함수 내에 인스턴스, 클래스 변수를 사용할 수 있다.
  • 인스턴스가 사용할 수 있다.

클래스 메서드

  • @classmethod 데코레이터를 함수 위에 붙인다.
  • cls 를 인자로 받음
  • 함수 내에 클래스 변수를 사용할 수 있다.
  • 인스턴스, 클래스가 사용할 수 있다.

스태틱 메서드

  • @staticmethod 데코레이터를 함수 위에 붙인다.
  • 인스턴스, 클래스 외의 변수를 인자로 받음
  • 함수 내에 인스턴스, 클래스 변수가 아닌 것을 사용한다.
  • 인스턴스, 클래스가 사용할 수 있다.

클래스와 인스턴스가 사용하는 메서드

조금 헷갈리니 다시 한 번 정리해보자.

우리는 메서드를 사용할 때 클래스.메서드() 또는 인스턴스.메서드() 형식으로 사용한다.

클래스와 인스턴스가 사용할 수 있는 메서드는 각각 다르다.

Method/메서드 호출 주체 Class Instance

     
Static O O
Class O O
Instance X O

정리해보면,

인스턴스는 모든 메서드를 사용할 수 있고, 클래스는 인스턴스 메서드를 사용할 수 없다.

우리가 개발할 때는 다음을 기준으로 어떤 메서드를 생성할지 선택하면 된다.

→ 메서드 안쪽에서 인스턴스 변수를 사용할 것 ⇒ 인스턴스 메서드로 생성한다.
→ 메서드 안쪽에서 클래스 변수만 사용할 것 ⇒ 클래스 메서드로 생성한다.
→ 메서드 안쪽에 인스턴스/클래스 변수 둘 다 사용하지 않을 것 ⇒ 스태틱 메서드로 생성한다.

 


데코레이터

클래스 메서드를 사용할 때 메서드 위에 @classmethod 데코레이터를 ****사용한다는 것을 배웠다.

이 때 등장하는 데코레이터는 무엇일까?

 

데코레이터를 사용하면 아주 쉽게 한 함수에 다른 함수의 기능을 부여할 수 있다.

def professor(name):
	return f'반갑네, {name}교수일세.'

def student(name):
	return f'안녕하세요, 학부생 {name}입니다.'

print(professor('홍'))
print(student('Meeseeks'))

>>> '반갑네, 홍교수일세.'
>>> '안녕하세요, 학부생 Meeseeks입니다.'

professor 함수와 student 함수는 입력받은 이름과 함께 인사를 건네는 함수다.

그냥 인사만 하니 너무 삭막해보여서 인사 뒤에 눈웃음을 추가하려고 한다.

두 함수 모두 수정하여 출력하자니 귀찮다.

 

이 때 데코레이터를 사용하면 기존 함수는 수정하지 않고 매우 간단하게 원하는 값을 얻을 수 있다.

def smile(func):
	def wrapper(name):
		print(func(name), end = '')
		print('^^')
	return wrapper

@smile
def professor(name):
	return f'반갑네, {name}교수일세.'

@smile
def student(name):
	return f'안녕하세요, 학부생 {name}입니다.'

professor('홍')
student('Meeseeks')

>>> '반갑네, 홍교수일세.^^'
>>> '안녕하세요, 학부생 Meeseeks입니다.^^'

데코레이터를 사용하여 기존 함수를 변경하지 않고 새로운 기능을 추가해주었다.

 

먼저, smile 함수를 정의해주었다. smile 함수의 구성을 좀 더 자세히 보면 다음과 같다.

  • 입력값으로 함수를 받는 smile 이라는 함수를 정의한다.
    • 입력값으로 name을 받는 wrapper라는 함수를 정의한다.
    • 기존 함수의 기능 가져옴
    • 추가할 기능
    • wrapper 반환

→ 데코레이터로 사용할 함수 정의

[ wrapper 함수 정의

         [ 기존 함수 기능 가져옴, 새로운 기능 추가 ]

   wrapper 함수 반환 ]

 

그 다음 smile 함수의 기능을 추가하고 싶은 함수 위에 데코레이터 @smile 을 붙여준다.


객체 지향의 핵심 개념

객체 지향에는 핵심 개념이 존재한다.

  • 추상화
  • 상속
  • 다형성
  • 캡슐화

이 네 가지의 핵심 개념을 차례대로 하나씩 알아보자.


추상화

디테일은 숨기고, 핵심만 드러내기

추상화는 위에서 설명했으니 생략 !


상속

코드 재사용 + 기능 확장

 

기존 클래스의 속성과 기능을 그대로 사용하면서, 다른 기능을 추가한 새로운 클래스가 필요하다면 상속관계를 이용하면 된다.

상속이라는 단어처럼 기존의 클래스를 부모 클래스, 부모 클래스로부터 상속받아 만들어진 새 클래스를 자식 클래스라고 한다.

상속관계에서 Namespace는 인스턴스 → 자식 클래스 → 부모 클래스 순서다.

class Person():
	def greeting(self):
		print('안녕하세요!')

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

	def self_introduce(self):
		print(f'IBA 부원 {self.name}입니다.')

member1 = IBA('Meeseeks')
member1.greeting()
member1.self_introduce()

>>> '안녕하세요!'
>>> 'IBA 부원 Meeseeks입니다.'

Person 클래스가 부모 클래스, IBA 클래스가 자식 클래스인 상속관계가 만들어졌다.

자식 클래스 생성시에 인자로 부모 클래스를 넣어주면 두 클래스의 상속이 이루어진다.

 

 

자식 클래스는 부모 클래스의 모든 변수와 메서드를 사용할 수 있다.

member1 IBA 의 인스턴스다. IBA 클래스에는 greeting 메서드가 없지만, greeting 메서드를 가지고 있는 Person 클래스를 상속받았기 때문에 member1 이 해당 메서드를 사용할 수 있는 것이다.

 

 

여러 클래스에서 상속을 받을 수도 있다. 이를 다중상속이라고 부른다. 만약, 여러 부모 클래스에 중복된 변수나 메서드가 있는 경우, 자식 클래스는 상속의 순서대로 해당 변수와 메서드를 사용하게 된다.

class Person():
	def greeting(self):
		print('안녕하세요!')

class Business():
	def greeting(self):
		print('안녕하세요, 경영학과 학부생입니다.')

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

	def self_introduce(self):
		print(f'IBA 부원 {self.name}입니다.')

member1 = IBA('Meeseeks')
member1.greeting()

>>> '안녕하세요, 경영학과 학부생입니다.'

Person 과 Business 두 개의 부모 클래스를 가지는 IBA 클래스가 있다.

두 부모 클래스 모두 greeting 메서드를 가지고 있다.

이 때 자식 클래스인 IBA 의 인스턴스에서 greeting 메서드를 불러오면 부모 클래스 둘 중 앞에 있는 Business greeting 메서드를 가져온다.

 

자식 클래스에서 부모 클래스의 변수나 메서드를 사용하고 싶다면 super()메서드를 사용한다.

class Person():
	def greeting(self):
		print('안녕하세요!')

class Business():
	major = '경영학과'

	def greeting(self):
		print('안녕하세요, 경영학과 학부생입니다.')

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

	def self_introduce(self):
		print(f'IBA 부원 {self.name}입니다.')
		print(f'전공은 {super().major}입니다.')

member1 = IBA('Meeseeks')
member1.self_introduce()

IBA 클래스의 self_introduce() 메서드에서 Business 클래스의 변수 major 를 사용했다.

이 때 super() 를 사용해서 부모 클래스의 변수를 불러온다. super()외에도 해당 인스턴스의 클래스가 어떤 부모 클래스를 가지는지 확인하는 mro() 등 상속관계에서 사용할 수 있는 다양한 메서드가 있다.


🙋🏻‍♀️ 자식 클래스에서 굳이 왜 super()써서 부모 클래스의 생성자 메서드 가져오나?

 

자식 클래스의 __init__ 에서 super().__init__ 가져오면 따로 인스턴스 변수를 만들어서 할당할 필요없이 부모클래스와 같아 사용할 수 있다.


다형성

서로 다른 클래스의 객체들이 동일한 메세지에 대해 다르게 응답한다.

 

메서드 오버라이딩을 통해 객체들이 서로 다른 값을 반환하도록 설계할 수 있다.

메서드 오버라이딩은 부모 클래스의 메서드를 자식 클래스에서 변경하는 것이다.

부모 클래스 메서드의 기본 기능은 그대로 사용하지만, 특정 기능을 추가하고 싶을 때 사용한다.

이 또한 super() 를 사용한다.

class Person():
	def greeting(self):
		print('안녕하세요!')

class Business():
	major = '경영학과'

	def greeting(self):
		print('안녕하세요, 경영학과 학부생입니다.')

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

	def greeting(self):
		super().greeting()
		print(f'IBA 부원 {self.name}입니다.')

member1 = IBA('Meeseeks')
member1.greeting()

super() 를 사용하여 Business greeting 메서드를 가져와 이름을 소개하는 새로운 기능을 추가한다.

  • greeting 메서드의 기본 기능 : ‘안녕하세요, 경영학과 학부생입니다.’
  • 새로운 기능 : ‘IBA 부원 Meeseeks입니다.’

super() 를 사용하지 않고 자식 클래스에서 같은 이름의 greeting 메서드를 새로 만든다면,

부모 클래스 greeting 메서드의 기능은 사용할 수 없고 단순히 덮어쓰기가 된다.


캡슐화

객체의 일부 구현에 대해 외부의 접근을 제어하는 것

클래스 내부적에 정의된 메서드와 속성에 대한 접근을 제어한다.

접근을 제어하는 방법은 3가지가 있다.

  • Public Access Modifier : 모두 접근가능, 기본 설정
  • Protected Access Modifier : 상속 관계에서만 접근가능
  • Private Access Modifier : 나만 가능 (내 클래스 안쪽에서만 가능)

이 세 가지를 접근제어자라고 부른다. 접근제어자들을 하나씩 살펴보자.


Public Member

  • 언더바가 없는 메서드와 속성
  • 클래스 내외부 어디에서나 호출가능
  • 하위 클래스에서의 오버라이드 허용
  • 가장 일반적이다.

Protected Memebr

  • 언더바 1개로 시작하는 메서드나 속성 : _age
  • 암묵적으로 부모 클래스와 자식클래스에서만 호출가능
  • : 파이썬 상에서 자동으로 제어되지 않고 개념적으로만 존재한다.
  • 하위 클래스에서의 오버라이드 허용

Private Memeber

  • 언더바 2개로 시작하는 메서드나 속성 : __age
  • 클래스 내부에서만 호출가능
  • : 파이썬 자체에서 호출이 불가능하게 제어한다.
  • 하위 클래스에서 상속 및 호출 불가능

🙋🏻‍♀️ 매직 메서드도 프라이빗 ? → 상속 및 호출 가능한데 이름생긴거 땜에 헷갈림

getter 메서드와 setter 메서드

접근이 제어된 메서드와 변수는 허용된 위치가 아닌 곳에서는 사용할 수 없을까?

getter 메서드와 setter 메서드를 활용하면 사용할 수 있다.

 

getter 메서드는 변수의 값을 조회하는 메서드, setter 메서드는 변수에 값을 할당하는 메서드다.

이 두 메서드와 데코레이터를 사용해서 Protected Access Modifier로 제어되고 있는 변수를 외부에서 호출해보겠다.

class IBA():
	def __init__(self):
		self._age = 0

	@property
	def age(self):
		return self._age

	@age.setter
	def age(self, age):
		self._age = age

member1 = IBA()
member1.age = 25
print(member1.age)

getter 메서드는 해당 함수명(age)을 외부에서 변수로 사용하고, 내부의 변수(_age)를 반환한다.

@property 데코레이터를 사용한다.

 

setter 메서드는 해당 함수명(age)을 외부에서 변수로 사용하고, 내부의 변수(_age)에 값을 할당한다. @함수명.setter 데코레이터를 사용한다.

 

setter 메서드 이전에 getter 메서드를 먼저 정의해야한다.


*정의한 클래스를 모듈로 사용하기

import 로 클래스를 정의해둔 파일을 불러와 모듈로 사용할 수 있다.

모듈로 불러온 클래스의 변수와 메서드가 먼저 실행됨 !

반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
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 31
글 보관함
반응형