추상화(Abstraction)
캡슐화(Encapsulation)
상속(Inheritance)
다형성(Polymorphism)
우선 추상화라는 사전적 정의를 한번 짚어봅시다. 추상화는 필요한 부분, 중요한 부분을 통합하여 하나로 만드는 것을 말합니다. 좀 더 쉽게 말하면 중요한 특징을 찾아낸 후 간단하게 표현하는 것이죠. 예를 들어, 강아지 집을 만들고 싶어서 강아지 집 설계도를 만들어 보려고 합니다. 설계도를 만들기 전에 필수적으로 떠올려 하는 요소들이 있을 겁니다.
예를 들어 지붕, 창문, 누울 공간등이 있습니다. 이런 내용을 바탕으로 설계도 안에는 지붕 설계, 창문 설계, 누울 공간설계등이 들어갑니다. 이 과정이 추상화 과정입니다.
어떠한 class를 만들기 전에 그 class를 어떻게 만들 것인지 미리 설계하고 들어가는 과정으로 추상화 클래스(abstract class)라는 것을 만듭니다. 추상화 클래스란 추상화 메소드가 하나라도 있는 클래스를 추상화클래스라고 합니다.(추상화 클래스안에 일반메소드가 있어도 상관없습니다)
만약 추상화 클래스안의 메소드가 모두 추상화 메소드라면 인터페이스라고도 합니다.
아무튼, 추상화 클래스에서 만들어준 추상화 메소드나 변수는 다음에 상속 받을 자식 클래스안에서 반드시 오버라이딩 되어야 합니다.
예시로 한번 강아지 집(DogHouse) 클래스를 만들어봅시다. 지붕 메소드, 창문 메소드, 공간 메소드 그리고 변수 하나를 자식 클래스에서 만들어줄겁니다.
from abc import ABC, abstractmethod
class DogHouse(ABC):
@abstractmethod
def create_roof(self, roof_color): # 지붕 만들기
pass
@abstractmethod
def creat_windows(self, window_count): # 창문 만들기
pass
@abstractmethod
def creat_space(self, where): # 공간 사이즈 정하기
pass
@property
def count(self): # 집에 살 강아지 수
pass
추상화 클래스를 만들기 위해 ABC 모듈과 abstractmehtod를 import를 한 후 ABC를 상속받습니다. 모든 메소드마다 pass라고 되어 있는건 DogHouse 클래스의 메소드들을 MyDogHouse에서 오버라이딩하여 완성시켜주기 위함입니다.
그리고 마지막으로 dog_count라는 변수를 자식 클래스에서 만들어주고 싶어서 @property 데코레이팅하여 count라는 함수를 만들어주었습니다. 그리고 추상화 메소드로 만들고 싶은 메소드 위에 @abstractmethod라고 데코레이팅 해줍니다.
이제 위에서 만든 추상화 클래스를 상속받아 MyDogHouse 클래스를 만들어보겠습니다.
class MyDogHouse(DogHouse):
def create_roof(self, roof_color):
self.roof_color = roof_color
def creat_windows(self, window_count):
self.window_count = window_count
def creat_space(self, where):
self.where = where
@property
def count(self):
return self.dog_count
@count.setter
def count(self, dog_count):
self.dog_count = dog_count
def __str__(self):
return "강아지집 정보\n\n지붕 색: {}\n창문 갯수: {}\n공간: {}\n집에 사는 강아지수: {}".format(self.roof_color,self.window_count, self.where, self.dog_count)
DogHouse에 있던 모든 추상화 메소드를 오버라이딩하였고 count 함수에 대해 getter와 setter를 만들었습니다.
그리고 마지막으로 _str__라는 일반 메소드도 추가해주었습니다.
my_dog_house = MyDogHouse()
my_dog_house.create_roof("빨강")
my_dog_house.creat_windows(2)
my_dog_house.creat_space("4평")
my_dog_house.count = 3 # count함수의 setter를 이용하였습니다
print(my_dog_house)
>>>
강아지집 정보
지붕 색: 빨강
창문 갯수: 2
공간: 4평
집에 사는 강아지수: 3
외부 접근 차단하는 법:
__)를 붙여준다.class Citizen:
"""주민 클래스"""
drinking_age = 19 # 음주 가능 나이
def __init__(self, name, age, resident_id):
"""이름, 나이, 주민등록번호"""
self.name = name
self.set_age(age)
self.__resident_id = resident_id
def authenticate(self, id_field):
"""본인이 맞는지 확인하는 메소드"""
return self.__resident_id == id_field
def can_drink(self):
"""음주 가능 나이인지 확인하는 메소드"""
return self.__age >= Citizen.drinking_age
def __str__(self):
"""주민 정보를 문자열로 리턴하는 메소드"""
return self.name + "씨는 " + str(self.__age) + "살입니다!"
def get_age(self):
"""숨겨 놓은 인스턴스 변수 __age의 값을 받아오는 메소드"""
return self.__age
def set_age(self, value):
"""숨겨 놓은 인스턴스 변수 __age의 값을 설정하는 메소드"""
if value < 0:
print("나이는 0보다 작을 수 없습니다. 기본 값 0으로 나이를 설정하겠습니다")
self.__age = 0
else:
self.__age = value
# 주민 인스턴스 생성
young = Citizen("younghoon kang", 18, "87654321")
print(young.__str__()) # 출력: younghoon kang씨는 18살입니다!
print(young.__authenticate("87654321")) # 에러가 난다!!!
__가 붙은 변수에 접근하는 메서드
__가 붙은 변수는 외부에서 사용 불가능하지만, 내부 메서드에선 접근이 가능하다.getter, setter
캡술화된 변수의 값을 읽어주는 메서드는 getter, 할당해 주는 메서드를 setter라고 한다.
파이썬의 캡슐화와 캡슐화 문화
파이썬에서 캡슐화를 하기 위해 변수나 메소드를 숨기려면 이름 앞에 밑줄 2개(__)를 붙여야 한다고 했다. 그런데 사실 여기에는 특별한 원리가 숨어있다.
class Citizen:
"""주민 클래스"""
drinking_age = 19 # 음주 가능 나이
def __init__(self, name, age, resident_id):
"""이름, 나이, 주민등록번호"""
self.name = name
self.set_age(age)
self.__resident_id = resident_id
def authenticate(self, id_field):
"""본인이 맞는지 확인하는 메소드"""
return self.__resident_id == id_field
지금 Citizen 클래스의 변수 __age, __resident_id는 클래스 밖에서 접근할 수가 없다.
여기서 Citizen 클래스의 내부를 들여다 보면
# 시민 인스턴스 생성
young = Citizen("younghoon kang", 18, "87654321") # (1)
print(dir(young)) # (2)
(1) Citizen 클래스로 young이라는 인스턴스를 하나 생성
(2) dir라는 함수를 사용하면 인스턴스가 갖고 있는 모든 변수와 메소드를 볼 수 있다.
young 인스턴스의 모든 변수 및 메소드 이름을 확인해보자.
위 코드를 실행하면 아래와 같이 출력된다.
['_Citizen__age','_Citizen__resident_id','__class__', '__delattr__', '__dict__', '__dir__' ... ]
가장 앞에 있는 '_Citizen__age', '_Citizen__resident_id'는 바로 우리가 이름 앞에 밑줄 2개(__)를 붙였던 변수 __age, __resident_id이다.
사실 변수나 메소드 이름 앞에 밑줄 두 개(__)를 쓰며느 파이썬은 그 앞에 추가적으로 "_클래스 이름"을 덧붙여서 이름을 바꿔버린다. 이걸 파이썬에서는 네임 맹글링(name mangling)이라고 한다. 맹글링의 동사형인 맹들은 영어로 "마구 썰다", "엉망진창으로 만들다"라는 뜻이다. 여기서는 이름을 새로운 형태로 변환하는 것을 맹글링이라고 한다.
즉, 방금 봤던 것 처럼 __age는 _Citizen__age로, __resident_id는 _Citizen__resident_id로 바뀌는 것 이다.
그럼 이 바뀐 이름으로는 클래스 밖에서도 접근할 수 있을까?
# 시민 인스턴스 생성
young = Citizen("younghoon kang", 18, "87654321")
print(young._Citizen__age) # 출력: 18
print(young._Citizen__resident_id) # 출력: 87654321
young._Citizen__age = -10
print(young) # 출력: younghoon kang씨는 -10살입니다!
출력 결과를 보면 바뀐 이름으로는 클래스 밖에서도 접근이 가능하다.
정리하면 클래스 안에서 이름 앞에 밑줄 2개(__)를 붙인 변수나 메소드는 네임 맹글링되어 아예 새로운 이름을 가지게 된다. 그리고 새 이름으로는 클래스 밖에서 접근이 가능하다. 그럼 결국 클래스 밖에서 접근할 수 있는 방법이 있으니까 캡슐화가 안 된 것 아닐까? 맞다. 캡슐화가 안 된 것이다. 아까는 밑줄 2개(__)만 붙이면 된다더니 무슨 말일까?
사실 파이썬은 언어 차원에서 캡슐화를 지원하지 않는다. 캡슐화처럼 보이긴 하지만 알고보면 완벽한 캡슐화는 아니다. 다른 객체 지향 언어인 Java에서는 private이라는 키워드를 변수 이름 앞에 붙이면, 외부로부터의 접근이 완벽히 차단된다. 파이썬처럼 바뀐 새 이름으로 접근할 수 있다거나 하는 방법도 없다.
하지만 파이썬이 캡슐화를 지원하지 않는다고 해서 캡슐화를 아예 무시하는 것은 아니다. 파이썬 세계의 개발자들은 조금 다른 방식으로 캡슐화를 한다.
파이썬의 캡슐화 약속: _한개
@property, @age.setter
class Citizen:
def __init__(self, name, age, id):
self.name = name
self.age = age
self._id = id
def authenticate(self, id):
return self._id == id
@property
def age(self):
print("나이를 리턴합니다")
return self._age
@age.setter
def age(self, age):
print("나이를 지정합니다.")
if age > 0:
self._age = age
else:
print("나이가 0보다 작습니다. 나이를 0으로 설정하겠습니다.")
self._age = 0
person = Citizen("김판다", 25, "123456-1234567")
print(person.age)
person.age = 10
print(person.age)
>>>
나이를 지정합니다
나이를 리턴합니다
25
나이를 지정합니다
나이를 리턴합니다
10
객체를 사용할 땐 최대한 메소드로
1. 상속이란?
2. mro, isinstance, issubclass 함수
mro(Method resolution order)
class Mom:
pass
class Son(Mom):
pass
print(Son.mro())
>>>
[<class '__main__.Son'>, <class '__main__.Mom'>, <class 'object'>]
isinstance
class Mom:
pass
class Son(Mom):
pass
son = Son()
print(isinstance(son, Mom))
print(isinstance(son, object))
print(isinstance(son, list))
>>>
True
True
False
issubclass
class Mom:
pass
class Son(Mom):
pass
son = Son()
print(issubclass(Son, Mom))
print(issubclass(Son, object))
print(issubclass(Son, list))
>>>
True
True
False
3. overriding
super().__init__(똑같은 인자 념겨주기)로 상속을 명시할 수 있다. (이렇게 안해도 자동으로 상속은 된다.)class Employee:
"""직원 클래스"""
company_name = "코드잇 버거"
raise_percentage = 1.03
def __init__(self, name, wage):
"""인스턴스 변수 설정"""
self.name = name
self.wage = wage
class DeliveryMan(Employee):
"""배달원 클래스"""
raise_percentage = 1.1
def __init__(self, name, wage, on_standby):
super().__init__(name, wage)
self.on_standby = on_standby
상속의 원리
4. 다중상속
파이썬은 다중상속이 가능하다 (Java같은 언어는 상속 딱 하나만 가능)
방법
아래 예시의 C 클래스는 A, B를 다중상속 받았다.
class A:
def __init__(self, a):
self.a = a
class B:
def __init__(self, b):
self.b = b
class C(A, B):
부모 클래스의 이름을 명시해주는 예제
class C(A, B):
def __init__(self, a, b):
A.__init__(self, a)
B.__init__(self, b)
메소드 오버라이드 하는 예시
class C(A, B):
def __init__(self, a, b):
self.a = a
self.b = b
다중상속 문제 해결 솔루션
Method Override도 다형성의 한 예이다.
# 클래스 선언
class Person:
def __init__(self, name):
self.name = name
def work(self):
print(self.name + " works hard")
class Student(Person):
def work(self):
print(self.name + " studies hard")
class Engineer(Person):
def work(self):
print(self.name + " develops something")
# 객체 생성
student1 = Student("Kane")
developer1 = Engineer("Mark")
student1.work()
developer1.work()
>>>
Kane studies hard
Mark develops something
메서드명을 동일하게 해서 같은 모양의 코드가 다른 동작을 하도록 하는 다형성 예
https://www.fun-coding.org/PL&OOP1-8.html
https://seungjuitmemo.tistory.com/50?category=908971