"객체 지향 사고 프로세스"와 "인프런 - 우리를 위한 프로그래밍 : 파이썬 중급 (Inflearn Original)"을 참고하여 작성했습니다.
지난 포스팅에서는 객체가 무엇인지, 객체는 파이썬에서 어떻게 구현이 되는지, 객체 지향 프로그래밍의 장점들에 대해서 알아봤습니다. 오늘은 객체 지향 프로그래밍 이해하기 2편으로 객체 지향의 특징과 특징들이 어떻게 파이썬에서 활용되고 있는지, 메소드 종류에 대해서 살펴보겠습니다. 혹시 잘못된 부분이 있다면 언제든지 알려주시면 감사하겠습니다.
작성된 내용은 아래와 같습니다.
- 객체 지향의 특징 : 캡슐화, 상속, 다형성
- 메소드의 종류 : 인스턴스 메소드, 클래스 메소드, 스태틱 메소드
1. 객체 지향의 특징
객체 지향의 특징인 캡슐화(encapsulation), 상속(inheritance), 다형성(polymorphism)에 대해 알아보고자 합니다.
1) 캡슐화(encapsulation)
어렸을 때, 포켓몬스터라는 만화를 본 적 있다면, 쉽게 이해할 수 있을 것 같습니다. 몬스터 볼 내부에는 어떤 포켓몬이 들어 있고, 몬스터 볼을 던지면 안에서 포켓몬이 나오게 되고, 포켓몬은 따로 어떤 것을 해주지 않아도 공격 기술들을 알고 있습니다. 이처럼 몬스터볼 내부에 포켓몬에 대한 정보인 속성과 포켓몬이 할 수 있는 기술에 대한 행동이 포함되어 있는 것을 캡슐화라고 합니다.
캡슐화는 객체 속에 속성뿐만 아니라, 행위도 들어있다는 사실에 기반하여 정의할 수 있는 개념입니다. 캡슐화의 개념 안에는 정보은닉의 개념도 포함되어 있습니다. 정보은닉은 속성이나 행위에 접근하지 못하게 제한하는 것을 말합니다. 예를 들어, 대학교에서 학생 정보를 저장하는 공간이 있다고 가정해봅시다.
class Student(object):
def __init__(self, name, age, id, grade):
self._name = name
self._age = age
self._id = id
self.__grade = grade #__는 정보에 바로 접근할 수 없도록 막음.
@property #property decorator : 숨겨진 변수를 반환하게 해줌
def grade(self):
return self.__grade
# 인스턴스 생성
Kim = Student('Kim', 24, 201701234, 4.0)
# 변수 호출
print(Kim._name) # kim
print(Kim._age) # 24
print(Kim._id) # 201701234
print(Kim.__grade) # AttributeError: 'Student' object has no attribute '__grade'
# grade를 활용해서 호출하기
grade1 = Kim.grade
print(grade1) # 4.0
만약, 아무나 대학교 학생 정보에 접근할 수 있다면 문제가 생기겠죠? 위에 코드에서는 학생의 성적을 접근하지 못하도록 설정했습니다. 파이썬에서는 언더바(_) 2개를 인스턴스 변수 앞에 붙여줘서 선언을 하면 직접 접근하지 못하도록 설정이 가능합니다. 변수 호출했을 때, 다른 속성들은 호출이 가능했지만 __grade에 대해서는 Error가 뜨는 것을 확인할 수 있습니다. 만약 grade를 방법으로 접근하기 위해서는 위에서 보이는 grade 함수처럼 property 데코레이터를 활용하면 접근이 가능합니다.
또 다른 예로, 사용자가 라면을 끓여먹으려고 한다고 해봅시다. 가정집에서 라면을 끓여 먹으려면, 가스레인지를 작동시켜서 라면을 끓여먹겠죠. 사용자 입장에서는 잘 끓인 라면이 필요합니다. 굳이 가스레인지가 어떤 원리로 가스를 받아서 작동하는지는 궁금하지 않습니다. 이처럼 사용자에게 보여주지 않아도 되는 내용을 숨기기도 합니다.
2) 상속(inheritance)
상속은 많이 들어봤던 단어라 익숙하실 겁니다. 말 그대로 부모의 것을 물려받는 것입니다. 부모 클래스(super class)의 속성과 행위를 모두 상속받을 수 있습니다. 만약, 상속이라는 개념이 없다고 한다면, 사람의 클래스 내부의 회사원, 학생, 교사의 공통적인 내용들을 각각 클래스에 만들어줘야 합니다. 또한 이렇게 공통적인 속성과 행위를 각각 복제하다 보면, 오류와 불일치가 발생할 수 있습니다. 그래서 이러한 문제들을 상속으로 해결할 수 있습니다.
class Person(object):
def __init__(self, name, age):
self._name = name
self._age = age
def introduce(self):
return f'My name is {self._name}, I am {self._age} years old.'
class Student(Person): # 상속할 때, object 대신에 부모클래스를 적어줘야한다.
def __init__(self, name, age, id, grade):
super().__init__(name, age) #super()를 활용해서 부모클래스의 속성을 가져올 수 있음.
self._id = id
self.__grade = grade
# 인스턴스 생성
Kim = Student('kim',24, 201701234, 4.0)
# 부모클래스로부터 상속받은 함수 사용
print(Kim.introduce()) # My name is kim, I'm 24 years old.
위의 코드에서 보시는 것처럼 상속을 받을 때에는 class 선언 시 object 대신 상속받을 클래스명을 넣어야 합니다. Person은 부모 클래스로 따로 상속받는 대상이 없기 때문에, object로 되어 있지만, Student는 Person으로부터 상속을 받기 때문에 object 대신 Person을 넣어줘야 합니다. 이렇게 해야 introduce 함수를 Person을 상속받은 Student 클래스에서도 사용이 가능합니다. 또한 부모 클래스의 속성을 받아오기 위해서 __init__ 매직 메소드 아래에 super().__init__을 통해서 Person의 속성을 받아올 수 있습니다.
3) 다형성(polymorphism)
다형성은 말 그대로 다양한 형상을 의미합니다. 이 부분은 예를 들어서 설명하는 것이 이해가 빠를 것 같습니다. 대부분의 동물들은 울음소리를 내지만, 그 종류에 따라 각각 다른 울음소리를 갖습니다. 이처럼 동물이 짖는 것은 동일하나 행동의 모습을 다르게 하는 경우 다형성이라는 개념이 필요하게 됩니다. 이번에는 동물들을 예로 들어서 설명해보려고 합니다.
class Animal(object):
def __init__(self, name):
self._name = name
def bark(self): # Abstract method
raise NotImplementedError() # "아직 구현하지 않은 부분입니다"라는 에러입니다.
class Cat(Animal): # 상속 받음
def bark(self):
return "Meow!"
class Dog(Animal): # 상속 받음
def bark(self):
return "Woof!"
animals = [Cat('Mong'), Cat('Kong'), Dog('Choco')]
for animal in animals:
print(animal.bark()) # Meow! Meow! Woof!
Animal이라는 부모 클래스 밑으로 Cat과 Dog라는 자식 클래스를 만들었습니다. 모든 클래스는 bark라는 울음소리를 내는 함수를 가지고 있습니다. 하지만, Dog와 Cat은 각각 다른 울음소리를 가지고 있기 때문에 같은 함수이지만 다른 내용의 리턴 값을 주도록 했습니다. 같은 이름의 함수이지만 각각의 인스턴스는 본인이 가지고 있는 함수를 먼저 호출하게 됩니다.
2. 메소드
클래스를 기반으로 프로그래밍하면서 만날 수 있는 메소드에 대해서 알아보고자 합니다. 파이썬에서 클래스 내부에 구성할 수 있는 메소드에는 총 3가지가 존재합니다. self를 인자로 받는 인스턴스 메소드, cls를 인자로 받는 클래스 메소드, 인자를 넣지 않아도 되는 스태틱 메소드가 있습니다. 메소드를 설명하기 위해서 대학교에 대한 예시를 들어보겠습니다. 대학교는 대학생의 학비를 매년 동결할 지 인상할 지를 결정하게 됩니다. 만약 인상하게 된다고 한다면, 학생 전부의 등록금에 인상률을 곱해줘야 합니다. 이를 클래스 기반으로 표현해보면 아래 접은글과 같지만 아래에서 나눠서 설명하겠습니다!
class Student(object):
# 클래스 변수
tuition_per_raise = 1.0
def __init__(self, name, age, id, tuition):
self._name = name
self._age = age
self._id = id
self._tuition = tuition
# 인스턴스 메소드
def tuition_info(self):
return f'{self._name}\'s tuition fee : {self._tuition*Student.tuition_per_raise}'
# 클래스 메소드
@classmethod
def raise_price(cls, per): #cls : Student 클래스
if per <= 1:
print('Please Enter 1 Or More')
return
cls.tuition_per_raise = per
print('tuition increased.')
# 스태틱 메소드
@staticmethod
def under_age_26(inst):
if inst._age < 27:
return 'under 26'
return 'over 26'
# 인스턴스 생성
Kim = Student('Kim',26,201701234,100)
Lee = Student('Lee',28,201501223,200)
# 등록금 확인
print(Kim.tuition_info()) # Kim's tuition fee : 100.0
print(Lee.tuition_info()) # Lee's tuition fee : 200.0
# 가격 인상
Student.tuition_per_raise = 1.4
# 인상 후 가격 확인
print(Kim.tuition_info()) # Kim's tuition fee : 140.0
print(Lee.tuition_info()) # Lee's tuition fee : 280.0
# 가격 인상
Student.raise_price(1.6)
# 인상 후 가격 확인
print(Kim.tuition_info()) # Kim's tuition fee : 160.0
print(Lee.tuition_info()) # Lee's tuition fee : 320.0
# 스태틱 메소드 호출
print(Kim.under_age_26(Kim)) # under 26
print(Lee.under_age_26(Lee)) # over 26
1) 인스턴스 메소드
인스턴스 메소드는 클래스 내부에 존재하는 대부분에 해당됩니다. 특징으로는 self라는 인자를 받습니다. 인스턴스 메소드는 각 객체의 값에 접근해서 본인의 정보를 보여주는 메소드를 작성할 수도 있습니다. 이처럼 인스턴스 변수(self._변수명)들과 관련된 작업에 필요한 메소드입니다.
class Student(object):
# 클래스 변수
tuition_per_raise = 1.0
def __init__(self, name, age, id, tuition):
self._name = name
self._age = age
self._id = id
self._tuition = tuition
# 인스턴스 메소드
def tuition_info(self):
return f'{self._name}\'s tuition fee : {self._tuition*Student.tuition_per_raise}'
# 인스턴스 생성
Kim = Student('Kim',26,201701234,100)
Lee = Student('Lee',28,201501223,200)
# 등록금 확인
print(Kim.tuition_info()) # Kim's tuition fee : 100.0
print(Lee.tuition_info()) # Lee's tuition fee : 200.0
2) 클래스 메소드
클래스 메소드는 cls를 인자로 받으며, 항상 클래스 메소드 위에는 @classmethod라는 데코레이터를 달아줘야 합니다. 클래스 메소드는 인스턴스와 관련이 없는 클래스에 대한 메소드로, 클래스 변수에 대한 작업을 하는 메소드입니다. 여기서는 등록금이 인상되는 비율을 숫자로 받아서 1보다 큰지 확인 후 크다면 클래스 변수를 변경하는 함수를 만들었습니다.
class Student(object):
# 클래스 변수
tuition_per_raise = 1.0
# 클래스 메소드
@classmethod
def raise_price(cls, per): #cls : Student 클래스
if per <= 1:
print('Please Enter 1 Or More')
return
cls.tuition_per_raise = per
print('tuition increased.')
# 가격 인상방법1
Student.tuition_per_raise = 1.4
# 인상 후 가격 확인
print(Kim.tuition_info()) # Kim's tuition fee : 140.0
print(Lee.tuition_info()) # Lee's tuition fee : 280.0
# 가격 인상방법2
Student.raise_price(1.6)
# 인상 후 가격 확인
print(Kim.tuition_info()) # Kim's tuition fee : 160.0
print(Lee.tuition_info()) # Lee's tuition fee : 320.0
클래스 변수에 접근하는 방법은 2가지가 있는데, 인상 방법 1은 클래스에 직접 접근하는 방식으로 별로 추천하지 않는 방법입니다. 미리 클래스 메소드를 구현해놓고 메소드를 활용하는 방식인 인상방법2가 더 나은 방법입니다.
3) 스태틱 메소드
스태틱 메소드는 cls나 self를 인자로 받지 않습니다. 그래서 클래스, 인스턴스에 대해 상관없이 비교적 자유롭게 사용할 수 있는 메소드입니다. 여기서는 26세보다 많은지 적은 지를 나누는 메소드를 만들었습니다.
class Student(object):
# 스태틱 메소드
@staticmethod
def under_age_26(inst):
if inst._age < 27:
return 'under 26'
return 'over 26'
# 스태틱 메소드 호출
print(Kim.under_age_26(Kim)) # under 26
print(Lee.under_age_26(Lee)) # over 26
스태틱 메소드도 클래스 메소드와 동일하게 @staticmethod라는 데코레이터를 붙여줘야 합니다. 사실 여기서 데코레이터가 뭔지에 대해 알아야 하지만 그 내용은 다음에 설명하도록 하겠습니다. 여기까지 읽어주셔서 감사합니다!