안녕하세요. 호랑인 입니다.
오늘은 파이썬 뿐만 아닌, 다양한 언어들의 핵심적인 부분이라 할 수 있는, 객체지향이라는 것을 알아볼 것입니다.
객체 지향 (object oriented) 이란 무슨 뜻일까요??
원래 옛날 컨퓨터 언어들의 경우에는 객체 지향이라는 것이 없었습니다. 예로 C와 (물론 사뭇 다른 구조체란 것이 있습니다.) 그 전의 프로그램 언어들인 Basic, Fortran, COBOL, 그리고 당연하지만 Assembly 에는 객체지향이 없었습니다. 이 말인 즉슨, "1"은 "1"이란 겁니다.
당시 컴퓨터는 사치연산을 제외하고는 "1"을 쓸 데가 없었기 때문에 이 만으로 충분했습니다. 하지만, 요즘은 손글씨 1을 읽는 프로그램들이 나오고 있죠. 과연 아직도 "1"은 "1"일 뿐인가요?
<포인트 1> "1" 은 "1" 만이 아니다.
이게 뭔 소리일까요? 1 은 숫자입니다. 근데 숫자이기만 한가요? '1'의 본질은 무엇일까요? 이것이 잘 안 와닿는 분들은 조금 더 간단한 예로, 고양이를 생각해봅시다. 컴퓨터에서 "고양이"를 가르치려면 어떻게 해야 할까요? 사실 방법이 없습니다. 왜냐하면 이는 고양이를 한번도 본적이 없는 인간에게도 설명이 불가능하기 때문이죠. 숫자 1도 마찬가지입니다. 1은 개수일 수도 있고, 문자일수도 있으며, 선형으로 생긴 손글씨일 수도 있다는 거죠.
당신이 고양이를 한번도 보지 못한 사람에게 고양이가 무엇인지를 설명해 줘야 한다면, 어떻게 설명하겠습니까?
"귀가 뾰족하고, 야옹이라고 울는, 털복숭이이고 귀여운 생명체" 등으로 설명할 수 있겠죠. 하지만 고양이마다 귀가 안 뾰족할 수도 있고, 야옹이라 안 울 수도 있으며, 털이 없는 이집트 고양이들도 있습니다. 그러면 어떻게 할까요?
여기에서 일부 사람들은 "과"를 생각해 냅니다. 생물 시간에 종속과목강문계 를 배우지 않았습니까? 그렇게 분류를 해 나아가는 거죠. 하지만 결국 중요한 것은, "고양이" 라는 사상 자체를 이해시킬 수는 없다는 것입니다.
<포인트 2> "고양이" 라는 사상 자체를 설명할 수는 없다.
그렇습니다. 세상에 "고양이" 라는 사물은 없습니다. 표준 고양이라는 것도 상상 속을 제외하고 존재하지 않습니다. 마치 이데아 같은 거죠. 세상에는 그저 "고양이"라는 포함 범주, 집합 안에 들어가는 아이들이 있을 뿐입니다. 그런데에도 우리는 어떤 아이가 고양이인지 아닌지를 구별할 수 있습니다. 어떻게 그렇게 할까요? 바로 고양이의 특성을 통해서 입니다. 즉, 어떤 동물이 털복숭이고, 귀가 뾰족하며 야옹하고 운다면, 우리는 이것이 높은 확률로 고양이라는 것을 알고 있는 것이죠.
<포인트 3> 사물의 종류는 그 사물의 특징으로써 정의된다.
자, 이제 이걸 어떻게 컴퓨터에게 가르칠지 생각해봅시다. 앞서 사물의 종류가 특징에 의해 정의된다면, 그 특징들을 모으면 반대로 그 사물을 정의할 수 있지 않을까요? 이 생각에서 "객체"라는 개념이 나오기 시작합니다.
본래 C 언어에서 구조체라는 것이 있었습니다. 이는 아주 간단한 것인데, 예로 학생 명부를 만든다 해봅시다. 그러면 그 학생의 학번과 이름을 같이 저장해 놓으면 좋지 않을까요? 그래서 메모리에 학번과 이름을 쓸 수 있는 공간을 한번에 정의하고, 앞의 정보는 학번(int)을, 뒤의 정보는 이름(string)을 저장한다는 사실까지 한번에 저장하는 것이 구조체입니다.
하지만, 이 학생을 이 두 가지로 정의할 수 있나요? 더 많은 스탯을 추가해 나아갈 수는 있어도, 이 학생은 스탯만이 결정하는 것이 아니라, 이 학생이 공부를 할 때의 변화, 잠을 잘 때의 변화 등 일 (function) 을 하는 것도 이 학생을 정의하는데에 포함이 됩니다. 1
<포인트 4> 사물을 정의하는 데에는 그 사물의 행동이나 환경에 대한 반응도 중요하다.
쉬운 예로 고양이는 야옹하고 울죠. ("울음소리"라는 스탯이라 생각할 수도 있고, "우는 행위"라 생각할 수도 있습니다.)
여기까지 잘 따라오셨나요? 축하드립니다. 이제 여러분은 객체의 개념을 받아들일 수 있는 생각의 바탕을 가지게 되셨습니다.
이제 여기에서 2장의 변수지정을 다시 한번 봅시다. 1 은 뭔가요?
갑자기 좀더 어렵게 느껴지지 않나요?
예컨데,
1 2 | a = 1 print(a) |
위와 같은 코드를 실행시키면, 그 안에는 어떻게 돌아갈까요? 머리가 복잡해지기 시작하셨나요? 숫자 1 을 우리가 아는 1 이라는 "모양"으로 출력하기까지 어떤 과정들을 거쳐야 할까요?
이 아이디어를 활용할 겸 해서 제가 일부러 앞의 변수지정 단원에서 한가지 타입을 안 적어놨습니다. 바로 파이썬의 복소수 타입인데요, 파이썬의 내장 복소수와는 사뭇 다르지만, 저희만의 복소수 타입을 만들어보려 합니다.
아래의 질문 과정을 잘 따라가 보시기 바랍니다. 나중에 실무에서 원하는 객체를 만들 때에도 거의 비슷한 과정을 따라가실 수 있습니다.
Q 복소수는 무엇으로 이루어져 있나요? (어떤 데이터나 스텟이 복소수를 정의하나요?)
A 실수부(float)와 허수부(float)로 이루어져 있습니다.
Q 복소수가 이행해야할 함수가 있나요?
A 사칙연산 (+-*/) 을 이행할 줄 알아야 합니다.
Q 복소수를 출력하면 어떻게 나와야 하나요?
A a+bi (a,b는 실수) 꼴로 표현되어야 합니다.
Q 복소수를 다른 타입 (예로 int) 로 바꾼다면, 어떻게 해야 할까요? (필수는 아닙니다. 고양이가 int 꼴로 바뀔 필요가 꼭 있을리 없죠.)
A 정수나 실수가 되면 (복소수의 크기 / 실수부)( 둘중 자기마음대로) 가 되었으면 좋겠습니다.
Q 기타 필요한게 있습니까? (있다면 나중에 제게 물어봐주세요)
자 이제 구체화 해봅시다.
우선, 고양이 객체를 간소화해서 만들어봅시다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Cat: def __init__(self): self.color = 'yellow' self.ear = 'pointy' self.age = 4 self.sound = 'meow' def meow(self): print(self.sound) a = Cat() a.meow() # meow |
위의 코드를 한번 봅시다.
class Cat 이라는 부분은 Cat 이라는 집합을 정의할 것이라는 의미입니다.
그 아래에 def __init__(self): 라는 부분이 있습니다. 아직 함수를 안배우신 분들은 조금 낯설 수 있지만, __init__ 이라는 것은 이 집합에 속하는 원소를 생성할 때 어떤 것을 실행할지를 의미합니다. 즉, alice = Cat() 라며, alice라는 변수에 고양이 집합에 속하는 원소를 저장하겠다라는 의미입니다.
self 는 영어를 아시는 분이라면 이해하실 수 있듯이, 자기 자신을 의미합니다. 즉, Cat이라는 집합 안에 있는 모든 원소들은 color, ear, age, sound 라는 특징 슬롯을 갖으며, 그 안에 적혀 있는 값이 자동으로 들어간다는 것입니다. 따라서, 아래와 같이 이를 출력할 수 있게 됩니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class Cat: def __init__(self): self.color = 'yellow' self.ear = 'pointy' self.age = 4 self.sound = 'meow' def meow(self): print(self.sound) alice = Cat() # a member of Cat is saved in 'alice' print(alice.color) # yellow print(alice.ear) # pointy print(alice.age) # 4 print(alice.sound) # meow alice.meow() # meow |
def meow(self): 라는 부분은 Cat 집합의 원소들이 기본적으로 행할 수 있는 meow 라는 행동을 정의하는 부분입니다. 즉, alice = Cat() 이든, tom = Cat() 이든 간에, Cat 집합 안에 원소들을 만든 이상, 그들은 meow라는 행위를 할 수 있습니다. 한 가지 눈치채셔야 할 점은 self.sound, 즉 init 에서 정의된 자신의 특성을 이 때에 마음껏 활용할 수 있다는 것입니다. 지금은 print 를 사용하여 출력만 했지만, 출력 뿐만 아니라 더하기 곱하기는 물론, 모든 함수를 적용시킬 수 있고, 그에 따라 이 값이 변한 사실을 alice라는 변수는 기억하고 있을 것입니다.
한가지 주의해야할 점이 있지만, 만약 alice라는 고양이는 purr 라는 행동 또한 할 수 있는데, 모든 고양이가 할 수 있는 것은 아니라면, Cat 안에 def purr(self): 라는 구문을 넣어선 안되겠죠?
여기서 일부 사람들은 생각할 것입니다. 이걸 어디다 쓸 수 있을까? 사실 실용적인 예시를 원하신다면, 아래의 배너를 누르시거나, 조금더 실용적인 것은 본 블로그의 야찌 게시판에 있는 구현 관련 글을 보시면 확실히 아실 수 있을 것입니다.
우선 여기서 끝내는 것은 너무 아쉽기 때문에 하나 더 하도록 하겠습니다. 위에서 본 Cat 이라는 집합에는 치명적인 문제점이 하나 있습니다. 바로, Cat 이라는 집합의 모든 원소는 색과 귀 모양, 나이 와 소리가 같다는 것이죠. 이게 말이 됩니까? 따라서, 우리는 Cat의 원소들, 즉 고양이들의 특성을 바꿔줄 수 있는 방법이 필요합니다. 우선 가장 쉬운 아래의 방법이 있습니다.
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 | class Cat: def __init__(self): self.color = 'yellow' self.ear = 'pointy' self.age = 4 self.sound = 'meow' def meow(self): print(self.sound) alice = Cat() # a member of Cat is saved in 'alice' alice.color = 'brown' alice.ear = 'furry' alice.age = 6 alice.sound = 'purrr' print(alice.color) # brown print(alice.ear) # furry print(alice.age) # 6 print(alice.sound) # purrr alice.meow() # purrr |
어떤가요? alice라는 고양이가 완전히 새로운 아이로 다시 태어났죠? 이런 일들을 자유자재로 할 수 있고, 자신이 원하는 특성만을 주어주고, 이 특성을 자유자재로 변형하면서 계속 저장할 수 있다는 점이 class 를 사용하는 가장 큰 이유라고 할 수 있을 것 같습니다.
그런데, 여기에도 문제점이 있습니다. 만약 자신의 나이를 밝히기 싫다면? 그리고 마음대로 누군가가 색을 바꿔버리게 하고 싶지 않다면? 이럴 경우에는 이 변수가 외부에서 변형되는 것을 막아야 합니다. 하지만 그래도 1년마다 나이는 먹어야 하고, 색도 자신이 원하면 염색은 할 수 있어야 겠죠? 이때 바로 숨겨진 특성들을 사용하는 것입니다.
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 | class Cat: def __init__(self): self.name = 'Tim' self.__color = 'yellow' def change_color_to_purple(self): self.__color = purple def print_color(self): print(self.__color) alice = Cat() # a member of Cat is saved in 'alice' print(alice.name) # Tim **꼭 alice 일 필요가 없음에 주의 print(alice.__color) # Error alice.print_color() # yellow alice.name = 'Alice' # Okay alice.color = 'brown' # Error alice.change_color_to_purple() # Okay print(alice.name) # Alice print(alice.__color) # Error alice.print_color() # purple |
변수 이름 앞에 __ (키보드 0 오른쪽의 것 두 개) 를 붙이므로써 이를 집합 내부에서만 조절할 수 있게 바꿀 수 있습니다. 하지만 여기에서 문제가 여려개 생기는데요, 우선 첫번째 질문은 이제 초기화를 어떻게 하냐는 겁니다. 말인 즉슨, 아까는 color 변수에 접근해서 갈색 고양이 원소를 만들 수 있었는데, 이제는 못 만든다는 거죠. 또한, 색을 보라색으로 바꿀 수는 있는데 원하는 색으로 염색을 할 수가 없지 않습니까... 그래서 저희는 이제 인자를 받아야 하는 상황이 도착하게 됩니다.
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 | class Cat: def __init__(self, Color = 'brown', Name): self.name = Name self.__color = Color def change_color(self, Color): self.__color = Color def print_color(self): print(self.__color) tim = Cat('yellow', 'Tim') print(tim.name) # Tim tim.print_color() # yellow alice = Cat(Color='red', Name='Alice') print(alice.name) # Alice alice.print_color() # red rex = Cat(Name='Rex') print(rex.name) # Rex rex.print_color() # brown rex.change_color('blue') rex.print_color() # blue |
다음 게시글에서 배울 함수라는 부분인데요, 아시는 분은 그냥 보시고 모르시는 분들은 그냥 이해해보시기 바랍니다. __init__ 함수에서 이제 이름과 색을 추가로 입력 받습니다. 그리고 받은 값을 각각 Color 와 Name 이라는 변수에 저장하고, 이는 만들어진 고양의 self.__color 와 self.name 에 저장되어 각 고양이들의 특징을 저장해줄 수 있게 됩니다. 그런데 잘 보면 Color = 'brown' 이라는 구문이 있습니다. 즉, 별다른 얘기가 없으면, 색은 갈색이라 생각하겠다는 의미입니다. rex의 예시를 보시면 이를 아실 수 있습니다. change_color 함수도 마찬가지입니다. Color 라는 변수를 추가로 받고, 이를 토대로 __color 변수를 바꾸겠다는 것입니다. 이것이 외부에게 숨겨진 체로도 자신의 비밀 특성을 원하는 데로 바꾸는 방법입니다.
1 2 3 4 5 6 7 | class Complex: def __init__(self): # 내용 def function(): # 내용 |
위를 보시면, class 를 정의하는 기본 뼈대가 나와 있습니다. 갑자기 어려워 진거 같아보입니다. 저기 저 def 가 뭔가요? 함수를 아직 안배웠으니, 당연합니다.
우선 침착하게 제 말을 따라가 보면서 이해를 해 봅시다. 일본어를 몰라도 알고 있는 한자가 있으면 그것만 뜨문뜨문 읽어서 내용을 이해하려 할 때랑 비슷하게, 문법은 문법일 뿐, 내용을 이해하는 것이 정말 중요한 것이란 것을 기억합시다.
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 32 | class Complex: def __init__(self, real=0, imag=0): self.real_num = real self.imag_num = imag def __add__(self, other): real = self.real_num + other.real_num imag = self.imag_num + other.imag_num return Complex(real, imag) def __sub__(self, other): real = self.real_num - other.real_num imag = self.imag_num - other.imag_num return Complex(real, imag) def __mul__(self, other): real = self.real*other.real - self.imag*other.imag img = self.real*other.imag + self.imag*other.real return Complex(real, other) def __div__(self, other): assert !(other.real==0 && other.imag==0) # 계산해보세요... 이건 좀 귀찮네요 # (a+bi)/(c+di)을 손으로 유리화해서 풀어서 넣으면 됩니다. def __abs__(self): return self.real**2 + self.imag**2 def __str__(self): return str(self.real) + '+' + str(self.imag) + ' i' |
휴... 지금 컴퓨터에서 돌려볼 수 있는 방법이 없어서 잘 돌아가는지 확인은 못해봤습니다... 혹여나 오타나 오류 있으면 알려주세요 ~
자 이제 이 난해한 코드 덩어리를 조금씩 씹어 삼켜봅시다.
우선, 가장 먼저 class Complex가 있습니다. 이건 그냥 문법입니다. Complex, 즉 복소수라는 객체를 만들 것이라는 겁니다. Complex 부분에는 원하는 이름을 붙이면 됩니다.
복소수를 결정하는 특징에는 실수부와 허수부가 있습니다. 이를 각각 self.real_num 과 self.imag_num 이라는 변수에 저장하도록 합시다. 만약 input 에서 별다른 값을 지정해주지 않으면, 무난하게 둘다 0으로 초기화하도록 하죠.
자 그러면 많은 분들이 의아해하실 법한, 도대체 저 나머지 괴랄한 것은 무엇일까요? 바로 사칙연산을 구현한 부분입니다. 당연히 def plus(self): def minus(self): 등등으로 하면 되지만, + - * / 라는 좋은, 익숙한 것이 있는데 왜 굳이 이렇게 합니까. 그래서 (복수수의 원소) (사칙연산 기호) (또다른 복소수의 원소) 라는 구문을 정의하겠다는 것입니다. 이를 유식한 방법으로 연산자 오버라이딩 이라 합니다. 즉, 원래 int float 등에만 국한된 연산자들의 정의를 이 집합 아이들의 경우에만 원하는 다른 함수로 덮어씌움으로써 사용하겠다는 겁니다.
__add__(self, other): 라 함은, 우선 self는 집합내에 정의되는 모든 함수에는 필수로 적어야 합니다. 정확히는 꼭 self 가 아니더라도 상관 없지만, 대부분의 사람들이 self 나 this 적어도 둘 중 하나를 씁니다. 왜냐하면, 자기자신을 의미하기 때문이죠. self.__color 와 같이 사용할 수 있는 것도, 결국 위의 고양이 예시에서 alice.meow() 라고 하는 순간, meow(alice) 가 호출되는 것이기 때문에, print(self.sound)가 print(alice.sound) 가 되는 것이기 때문입니다. 자, 그리고 덧셈을 하려면 덧셈의 뒤에 올 아이도 하나 있어야 겠죠? 저는 이를 other라는 변수에 저장하기로 했습니다. 이름은 마음대로입니다. this, that / self, other / 만약 성별이 정해진 상황이라면 him, her 등으로 다 쓰실 수 있습니다.
add를 하려면 어떤 결과를 돌려줘야 할까요? 실수부는 실수부끼리, 허수는 허수부끼리 더한 값을 각각 실수부와 허수부로 갖고 있는 Complex class의원소가 결과이기 때문에 return(결과물을 돌려주는 함수) Complex(새로운 실수부, 새로운 허수부) 라는 구문을 쓰게 됩니다.
같은 과정을 나머지에도 하시면 됩니다. 다만 div , 즉 나눗셈은 문제가 있는데요, 나누는 아이의 크기가 0이면 안됩니다. 그럴 때 쓰는 것이 assert 입니다. 풀어쓰자면 make sure that... 정도입니다. 따라서 make sure that abs(other) == 0 이 아니게 (!는 부정의 의미) 하하는 것입니다. 만약 맞다면, 프로그램이 종료되게 됩니다.
아주 긴 여정이었습니다. 처음 쓰기 시작한 날과 게시일자를 비교해보신다면 제가 이걸 쓰는데 상당히 오랜 시간 (5일)이 걸렸다는 사실을 아실 수 있을 것입니다. 이제 다음 게시물은 아마도 함수에 대한 짧은 소개일 것입니다. 그것을 읽고, 다시 한번 이 글을 읽어보시면, 더 많은 것을 이해하실 수 있을 것입니다.
그럼에도 왜 제가 나머지 그 어떤 책이나 강좌와도 다르게 class 개념을 먼저 했는가 하면,파이썬의 모든 것은 object라는 class의 원소로 정의되어 있기 때문입니다. int, float 는 물론이고, 함수까지 모든 것은 object라는 집합의 원소입니다. 이것을 알고 나머지를 보는 것이, 파이썬을 이해하는 데에 있어 가장 실용적이라고 당장 장담은 못하지만, 넓은 시야를 제공해 줄것이라 믿습니다.
- 일, 활동 (function)과 프로그래밍에서의 함수 (function) 사이의 언어유희입니다. 재미없었으면 죄송합니다. [본문으로]
'내용 정리 > Python (초보자를 위한 파이썬)' 카테고리의 다른 글
[초보자를 위한 파이썬] 메모리, 포인터, list (0) | 2018.07.30 |
---|---|
[초보자를 위한 파이썬] 출력과 변수지정 (0) | 2018.06.17 |
[초보자를 위한 파이썬] 파이썬 설치 (3) | 2018.06.16 |