오늘은 파이썬 뿐만 아닌, 다양한 언어들의 핵심적인 부분이라 할 수 있는, 객체지향이라는 것을 알아볼 것입니다.
객체 지향 (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 =1print(a)
위와 같은 코드를 실행시키면, 그 안에는 어떻게 돌아갈까요? 머리가 복잡해지기 시작하셨나요? 숫자 1 을 우리가 아는 1 이라는 "모양"으로 출력하기까지 어떤 과정들을 거쳐야 할까요?
이 아이디어를 활용할 겸 해서 제가 일부러 앞의 변수지정 단원에서 한가지 타입을 안 적어놨습니다. 바로 파이썬의 복소수 타입인데요, 파이썬의 내장 복소수와는 사뭇 다르지만, 저희만의 복소수 타입을 만들어보려 합니다.
아래의 질문 과정을 잘 따라가 보시기 바랍니다. 나중에 실무에서 원하는 객체를 만들 때에도 거의 비슷한 과정을 따라가실 수 있습니다.
Q 복소수는 무엇으로 이루어져 있나요? (어떤 데이터나 스텟이 복소수를 정의하나요?)
A 실수부(float)와 허수부(float)로 이루어져 있습니다.
Q 복소수가 이행해야할 함수가 있나요?
A 사칙연산 (+-*/) 을 이행할 줄 알아야 합니다.
Q 복소수를 출력하면 어떻게 나와야 하나요?
A a+bi (a,b는 실수) 꼴로 표현되어야 합니다.
Q 복소수를 다른 타입 (예로 int) 로 바꾼다면, 어떻게 해야 할까요? (필수는 아닙니다. 고양이가 int 꼴로 바뀔 필요가 꼭 있을리 없죠.)
A 정수나 실수가 되면 (복소수의 크기 / 실수부)( 둘중 자기마음대로) 가 되었으면 좋겠습니다.
그 아래에 def __init__(self): 라는 부분이 있습니다. 아직 함수를 안배우신 분들은 조금 낯설 수 있지만, __init__ 이라는 것은 이 집합에 속하는 원소를 생성할 때 어떤 것을 실행할지를 의미합니다. 즉, alice = Cat() 라며, alice라는 변수에 고양이 집합에 속하는 원소를 저장하겠다라는 의미입니다.
self 는 영어를 아시는 분이라면 이해하실 수 있듯이, 자기 자신을 의미합니다. 즉, Cat이라는 집합 안에 있는 모든 원소들은 color, ear, age, sound 라는 특징 슬롯을 갖으며, 그 안에 적혀 있는 값이 자동으로 들어간다는 것입니다. 따라서, 아래와 같이 이를 출력할 수 있게 됩니다.
classCat:
def__init__(self):
self.color ='yellow'self.ear ='pointy'self.age =4self.sound ='meow'defmeow(self):
print(self.sound)
alice = Cat() # a member of Cat is saved in 'alice'print(alice.color) # yellowprint(alice.ear) # pointyprint(alice.age) # 4print(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의 원소들, 즉 고양이들의 특성을 바꿔줄 수 있는 방법이 필요합니다. 우선 가장 쉬운 아래의 방법이 있습니다.
classCat:
def__init__(self):
self.color ='yellow'self.ear ='pointy'self.age =4self.sound ='meow'defmeow(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) # brownprint(alice.ear) # furryprint(alice.age) # 6print(alice.sound) # purrr
alice.meow() # purrr
어떤가요? alice라는 고양이가 완전히 새로운 아이로 다시 태어났죠? 이런 일들을 자유자재로 할 수 있고, 자신이 원하는 특성만을 주어주고, 이 특성을 자유자재로 변형하면서 계속 저장할 수 있다는 점이 class 를 사용하는 가장 큰 이유라고 할 수 있을 것 같습니다.
그런데, 여기에도 문제점이 있습니다. 만약 자신의 나이를 밝히기 싫다면? 그리고 마음대로 누군가가 색을 바꿔버리게 하고 싶지 않다면? 이럴 경우에는 이 변수가 외부에서 변형되는 것을 막아야 합니다. 하지만 그래도 1년마다 나이는 먹어야 하고, 색도 자신이 원하면 염색은 할 수 있어야 겠죠? 이때 바로 숨겨진 특성들을 사용하는 것입니다.
classCat:
def__init__(self):
self.name ='Tim'self.__color ='yellow'defchange_color_to_purple(self):
self.__color = purple
defprint_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() # Okayprint(alice.name) # Aliceprint(alice.__color) # Error
alice.print_color() # purple
변수 이름 앞에 __ (키보드 0 오른쪽의 것 두 개) 를 붙이므로써 이를 집합 내부에서만 조절할 수 있게 바꿀 수 있습니다. 하지만 여기에서 문제가 여려개 생기는데요, 우선 첫번째 질문은 이제 초기화를 어떻게 하냐는 겁니다. 말인 즉슨, 아까는 color 변수에 접근해서 갈색 고양이 원소를 만들 수 있었는데, 이제는 못 만든다는 거죠. 또한, 색을 보라색으로 바꿀 수는 있는데 원하는 색으로 염색을 할 수가 없지 않습니까... 그래서 저희는 이제 인자를 받아야 하는 상황이 도착하게 됩니다.
classCat:
def__init__(self, Color ='brown', Name):
self.name = Name
self.__color = Color
defchange_color(self, Color):
self.__color = Color
defprint_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
classComplex:
def__init__(self):
# 내용deffunction():
# 내용
위를 보시면, class 를 정의하는 기본 뼈대가 나와 있습니다. 갑자기 어려워 진거 같아보입니다. 저기 저 def 가 뭔가요? 함수를 아직 안배웠으니, 당연합니다.
우선 침착하게 제 말을 따라가 보면서 이해를 해 봅시다. 일본어를 몰라도 알고 있는 한자가 있으면 그것만 뜨문뜨문 읽어서 내용을 이해하려 할 때랑 비슷하게, 문법은 문법일 뿐, 내용을 이해하는 것이 정말 중요한 것이란 것을 기억합시다.
휴... 지금 컴퓨터에서 돌려볼 수 있는 방법이 없어서 잘 돌아가는지 확인은 못해봤습니다... 혹여나 오타나 오류 있으면 알려주세요 ~
자 이제 이 난해한 코드 덩어리를 조금씩 씹어 삼켜봅시다.
우선, 가장 먼저 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 2와 Python 3에서의 출력 함수가 조금 다릅니다. 일단 두 버젼에서 둘다 사용할 수 있는 문법으로 나가겠습니다.
1
print ('Hello World!')
위의 코드를 넣고, 실행시켜 봅시다. 그러면, Hello World! 라는 메세지가 출력되는 것을 보실 수 있을 것입니다.
파이썬 2를 사용하시는 분들은 ( ) 를 빼도 코드가 돌아갈 것입니다. 둘이 어떤 차이로 인해 이런 일이 벌어지는지는 아마 나아중에 나올 것 같습니다. 하지만, 코드를 작성하시다 보면 파이썬 2 를 사용하시는 분들도 ( ) 를 치는 것이 덜 헷갈리는 경우가 많아 자주 쓰게 되실 것입니다.
위의 코드가 어떻게 돌아가는지는 나중에 알아보도록 하고, 우선 print 라는 구문을 사용해서 원하는 내용을 출력할 수 있다는 것을 알아놓도록 합시다.
이제 원래 목표였던 변수지정을 배워 봅시다.
변수에는 여러 타입이 있습니다. 이들에는 int, long, float, complex, string, list, tuple, dictionary 가 있습니다. 뭔가 어려워 보이실 수 있지만, 전혀 그렇지 않습니다.
하나하나 보도록 하죠.
int 는 integer, 즉 정수의 약자입니다.
1
2
a =1print (a) # 1
위의 코드를 실행시켜보면, 1이라는 결과가 나올 것입니다. # 1 이라는 것이 갑자기 나와 놀라셨을 수도 있는데요, 이를 "주석" 이라 합니다.
주석은 # 으로 시작해서 원하는 내용을 쓰시면 되고, 코드를 보시면 알 수 있듯이 회색 처리되며, 프로그램이 도는데에 그 어떤 영향을 주지 않습니다. 그래서 주로 어떤 코드가 어떤 역할을 하는지에 대한 간단한 메세지들을 적어 놓을 때 사용합니다.
1
2
3
defevolve(self):
# This Function evolves the neuron to the next step# ...실제 함수...
저는 이 주석을 출력 결과를 적는 데에 사용하도록 하겠습니다.
다시 본주제로 돌아가, a = 1이라는 코드를 적으면, python 은 알아서 1이 정수라는 것을 이해 하고, a 에 정수값 1을 저장합니다. 그리고 앞에서 배운 print를 사용해서 이를 출력할 수 있습니다.
원래 다른 언어들은 int의 값에 범위가 있습니다. int8, int16, int32, ... 등등으로 그 한계가 다르기도 하죠.
파이썬 2 에서는 int에 한계값이 있습니다. 2147483647 까집니다. 그 이상의 정수를 저장하고 싶으시면, 아래의 long을 보세요. (그럴일이 많지는 않을 겁니다.) 하지만 파이썬 3 에는 그런게 없으니, 맘 놓고 사용하시면 됩니다. 이는 나중에 numpy 를 배울 때 자세히 알아보도록 합시다.
이번에는 long 과 float 를 봅시다. 둘은 엄연히 다른 것인데에도 사람들이 모르는 경우가 많더군요. 하지만 너무 기분 나빠할 건 없습니다. 사람들이 잘 모르고 있다는 것은 평상시에 이것에 대해 고민할 필요가 없다는 뜻이기도 하기 때문입니까요.
** 물론 이러한 애들 때문에 python만을 배우다 다른 언어를 배우려 하면 정말 어렵게 다가올 수도 있습니다. 파이썬에 갇히는 거죠 ㅠ
위에서 a = 1이라는 코드를 사용해서 정수값을 저장할 수 있다 배웠습니다. float와 long도 결국은 소수를 저장하는 일이니, 비슷하게 하면 되지 않을까 생각이 들지 않나요?
1
2
3
4
a =3.141592print a # 3.141592print (type(a)) # float
새로운 문법이 나왔습니다. 바로 type이라는 내장함수입니다. 위의 예시를 보면 아실 수 있둣이, type( ) 안에 변수 이름을 넣고 print를 하면, 그 변수가 어떤 종류인지가 나옵니다.
이제 long 을 봅시다. long은 정말 독특한 애입니다. 원래 long 이란 변수타입은 64비트 실수를 저장해주는 변수인데요, 파이썬의 float는 이미 그 한계값이 무한입니다. 그렇기 때문에 long이라는, 확장형 float가 필요가 없죠. 굳이 필요하다고 해도, 더 정확한 값을 주기 위함일 텐데, 결론부터 말하자면 파이썬의 long은 말그대로 '긴' 정수를 저장하는데에 사용됩니다.
여기서 잠깐, python 3 에서는 long 변수 타입이 사라졌습니다. 그냥 int 자체가 무한까지 가능하기 때문이죠.
1
2
3
4
a =216541531321563213212321321L# very big integerprint a # 216541531321563213212321321print (type(a)) # long
보시면 알 수 있듯이, 뒤에 L 을 붙이면 됩니다. 하지만 long을 쓰실 일은 정말, 정말 거의 없습니다. 보통 큰 데이터를 저장해야 한다고 할 때 그 값이 정수인 경우는 그리 많지 않기 때문입니다. Python 3에서 int 에 병합된 것도 그 이유 중 하나입니다.
다음 변수 타입은 string 입니다. string은 우리가 흔히 쓰는 '글자' 와 '문장'을 저장하는 데에 쓰입니다.
사실 이미 위에서 한번 사용했는데요, 'Hello World' 를 따옴표 안에 넣은 것이 string을 생성하는 방법입니다.
1
2
3
a ='Hello World !'print (a) # Hello World !
쉽죠? 여기에서 알아야 할 점은 python의 print 는 자동으로 다음줄로 넘간다는 것입니다. 이게 무슨 뜻이냐고요? 아래의 예제를 보시죠.
보면 알 수 있듯이, 별도의 엔터(개행) 없이도 알아서 매번 다음 줄로 넘어간다는 겁니다. 이게 당장 보기에는 편해보일 수 있으나, 그렇지 않은 경우도 많습니다. 우선, 형 변환을 알아보고 그 예를 보여드리겠습니다.
참고로 """ 라 적힌 것은 복수의 줄로 연결되는 주석입니다. """ 로 열어서 """ 로 닫으면 되고, 기존의 # 주석과 동일하되, 여러 줄에 걸쳐서 사용할 수 있습니다. 대신 다른 코드의 뒤에서 시작하는 것은 추천하지 않고, 제가 위에서 쓴 것처럼 새로운 줄에서 시작하세요.
형변환은 정말 간단합니다. 제가 생각하는 파이썬의 꽃 중 하나죠. 포기하는 것은 많습니다만, 가장 pythonic 한 것 중 하나라 생각합니다.
형변환은 말 그대로 type을 바꾸는 일입니다. 예를 들어, 1 이나 3.14 같은 수들은 수로써도 쓸 수 있고, string으로도 쓸 수 있습니다. 그럴 경우, 3.14를 string 처럼 쓰고 싶다면 어떻게 해야 할까요? string으로 형변환을 해주면 됩니다.
지금까지 배운 모든 type들은 서로서로 형변환이 되며, 주의할 점은 float를 int로 형변환하면 (당연하지만) 소숫점 아래의 수가 사라지고(반올림이 아니라 버림입니다), int 를 float로 형변환한 다음에는 int 속성을 더이상 사용할 수 없다는 것입니다. (이는 추후에 설명하겠습니다)
1
2
3
4
5
6
7
8
a =3.14# floatprint (len(a)) # error: object float have no len()
b =str(a) # stringprint (len(b)) # 4
c =int(a) # intprint (c) # 3
len() 이라는 함수는 string type에 대해 정의되어 있는 함수로, 길이 length의 준말입니다. 말 그대로 해당 string의 길이를 나타냅니다. 처음에는 a 가 float 형 변수로 선언되었기 때문에, len 이라는 함수에 넣으면 error가 발생합니다. 에러 베세지를 읽어보면, float에는 len() 이 없다고 되어 있습니다.
이제 b는 a를 string으로 형 바꿈한 것입니다. 함수 이름은 string의 준말인 str 입니다. 그리고 나서 len() 을 살행하니, 3 . 1 4 총 4글자이기 때문에 4가 출력되었습니다.
c는 a를 int로 형바꿈 한 것입니다. 보면 소숫점 아래의 수들이 모두 없어진 것을 알 수 있습니다.
이제 다시 print 로 돌아가봅시다.
이번에는 조금 더 실용적인 것을 print 하고 싶다고 해보죠. 어떤 변수 stu_num 이라는 변수에 학번이 int로, stu_name 에 이름이 string으로 저장되어 있다 해봅시다. 그리고 "(학생 이름) has student number of (학번)" 이라 출력 해봅시다.
1
2
3
4
5
6
7
8
9
10
11
12
stu_num =85
stu_name ="Alex"print(stu_name)
print(" has the student number of ")
print(str(stu_num))
"""Alex has the student number of 85"""
고쳐야 할 것이 몇개 보입니다. 우선, stu_num 을 str로 형변환해서 출력한 것은 나쁘지 않은 선택입니다. 나중에 object의 __str__( ) 을 배울 때 다시 말씀드리겠습니다. 하지만 딱 보이는 것이 바로 자동 줄넘김입니다. 저희는 한 줄을 원하는데 말이죠.
그럴 때에는 여러 방법이 있습니다.
1. 쉽표를 붙여주는 방법
2. string concentrate
3. format (이건 나중에 정규형식과 함께 배우겠습니다.)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
stu_num =85
stu_name ="Alex"# 1번print(stu_name,)
print(" has the student number of ",)
print(str(stu_num))
# 2번print(stu_name +" has the student number of "+str(stu_num))
"""Alex has the student number of 85"""
보면 알 수 있듯이, 1번은 단순히 뒤에 쉽표를 붙인 겁니다. 쉽표는 자동 개행을 막는 역할을 합니다.
2번은 여러개의 string 들은 + 연산자로 하나의 string으로 바뀔 수 있다는 점을 사용한 것입니다. 대신 이는 오직 string 끼리만 되니, 당연히 stu_num 변수는 str을 사용해서 형변환을 해줘야겠죠?
우선 이 정도면 기본적인 변수지정은 끝났다고 생각합니다. 물론 complex라는 형도 있지만, 저는 지금까지 그 형을 사용해야 할만한 경우를 마주친 적이 없기도 하고, 보통 복소수를 다루고 있을 경우 다른 라이브러리들을 사용하고 있을 경우가 더 많아서 다루지 않겠습니다. 궁금하신 분들은 python document complex 라 구글에 치면 나올 것입니다. 감사합니다.
초보자를 위한 파이썬 카테고리가 생김과 동시에 파이썬을 설치하는 방법에 대한 글이 올라왔습니다.
이제 코딩은 할 줄 알면 좋은 것이 아니라, 할 줄 모르면 안되는 영역에 들어서기 시작했다 생각합니다. 그만큼 프로그래밍 언어들도 단순한 연산 속도가 아닌, 간결함, 단순함 등의 요소들을 중요시 여기는 애들이 하나 둘씩 생기고 있습니다. 파이썬은 이들의 선구자 중 하나로, 입문자들이 간단한 코드를 작성하기 시작하는데에 이보다 쉬운 언어는 아직까지 없는 것 같습니다. 모두 열공합시다 !
조만간 파이썬을 가르칠 일이 있을 것 같아서 그 자료를 정리하는 겸 포스팅도 하려고 합니다.
이 포스팅은 지극히 실용적인 내용만을 다룰 것이나, 그래도 자신이 뭘하고 있는지는 알아야 실수를 방지할 수 있기 때문에, 자료 구조가 어떻게 작동하고 있는지 등은 제대로 나갈 것입니다.
오늘은 파이썬 설치하는 내용만 간단히 소개하고 마치려 하는데요, 파이썬을 설치하는 방법은 매우 간단합니다. https://www.python.org/ 에 들어가서 download python을 하시면 됩니다.
하지만, 가장 중요한 것 중 하나는 python 2와 python 3 중 어떤 것을 받는지입니다.
우선, 윈도우 XP 나 그 이전의 운영체제를 사용하시는 분들은 선택의 여지 없이 python 2를 설치하시면 됩니다.
여기로 가셔서 아래에 Looking for specific releases? 라 적힌 부분으로 가서 python 2.7.3, 2.7.6, 2.7.10 정도 중에서 원하시는 것을 받으시면 됩니다. 뭔지 잘 모르겠다 싶으면 2.7.10 을 다운 받으면 문제사항은 없을 것입니다.
python 3을 설치하고 싶으신 분들은 별 고민 없이 Download Python 3.6.X 라고 적힌 버튼을 누르면 알아서 설치해 줄 것입니다.
XP 이후에 나온 Window 7, 8, 10 유저들은 그러면 어떤 것을 설치해야 할까요?
첫째로, 자신이 수업을 듣고 있는 상황이라면, 그 수업 때 사용하는 버젼을 사용하시기 바랍니다.
그것이 아니라면, 아래의 영역 중에서 고르세요.
1. 쌩 초보자: 나는 파이썬을 해본적이 없거나, print("Hello World")랑 변수 지정 정도 밖에 모른다. => python 3
2. 초보자: 나는 작은 콘솔 프로그램을 짜면서 놀고 있다. 가위바위보 게임이나 콘솔 오목 등 => python 3
3. 초보 공학자: 나는 그냥 라이브러리 이것저것 가지고 와서 떡칠하는 것을 즐긴다. => python 2
** 별로 좋은 습관은 아니지만, 그걸 즐긴다면 아직은 python 2가 "그대로" 사용할 수 있는 라이브러리가 더 많습니다.
4. 중급 공학자: 나는 라이브러리를 적당히 쓸 줄 알고, __future__ 등을 통해 python 2,3 을 연결할 줄 안다. => python 3
5. 고급: 나는 이미 파이썬 대부분이 어떻게 돌아가는 지 이해하고 있다. => 상관 없음
** 어차피 python 3 가 python 2 보다 절대적 우월함을 갖는다 하고 싶진 않습니다. 이 정도 레벨이면 아무 언어나 잡으세요. 언어는 도구일 뿐.
여기에 하나 첨언하자면, python을 사용해서 행렬 계산을 하거나 그래프를 그리는 등의 일을 하려면, 다른 라이브러리를 참조하는 경우가 매우 빈번하게 일어납니다. 원래는 그때마다 패키지를 설치해주어야 하지만, 이 패키지들이 이미 설치가 끝나 있는 배포들이 존재합니다.
아나콘다 프로젝트 가 대표적인 예입니다. 링크를 타고 가서 스크롤을 아래로 내리면, 다운로드 버튼이 있을 것입니다. 그걸 다운 받고 실행시키면 준비 끝입니다. 아나콘다 말고도 WinPython (이유는 모르겠지만 저 노트북은 아나콘다가 안깔려서 이걸 씁니다. 내용물?은 거의 같습니다.)
그러면 이제 파이썬 설치는 끝났으니, 코딩을 시작해야겠죠? 근데 어디에 뭘 쳐야 프로그램이 될까요? 사실 파이썬을 포함한 프로그램 언어들은 웬만하면 윈도우 메모장으로 작업한 뒤에, 확장자를 바꿔주면 작동합니다. 하지만, 이 방법은 1. 예쁘지도 않고, 2. 오타가 난 것을 알 수 있는 방법이 없으며, 3. 실시간으로 오류가 나는지 여부를 알 수도 없습니다. 그래서 사용하는 것이 IDE 입니다.
IDE는 프로그램을 작성하는데에 있어서의 메모장 같은 역할을 합니다. 당연히 코드를 작성하는데에 최적화가 되어 있어, 명령어들의 색을 다르게 입혀주기도 하고, 자동으로 탭을 띄어 주거나 ( ) { } [ ] 들을 닫아주는 등의 역할을 합니다. 그리고 오류가 날 경우 어느 줄에서 어떻게 오류가 났는지를 더 쉽게 알 수 있게 해줍니다.
IDE로는 정말 많은후보군이 있습니다. 많이들 쓰는 PyCharm 라는 좋은 프로그램이 있지만, 유료라는 문제가 있습니다. 그 다음으로 자주 쓰는 것으로 Sublime Text 가 있는데, 얘는 유료임에도 무료로 쓸 수 있는 방법이 구글에 널려있습니다. 다만 초보자라면 다운 받는 과정이 복잡하게 다가올 수 있습니다. 이 외에도 Wing IDE 나 Eclipse (원래 java 코딩을 하기 위한 애지만, 적어도 파이썬 2 까지는 지원합니다.) 등도 가능합니다.
만약 위에서 Anaconda나 Winpython을 다운 받으셨다면, IDE에 대한 걱정은 더이상 하실 필요가 없습니다! 이미 Spyder라는 프로그램이 설치되어 있을 것입니다. 그냥 그걸 사용하세요. 다른 사람들과의 협업이 개인이 하는 일보다 더 중요시 되는 상황이 아니라면, 충분히 좋다고 생각합니다.
이제 파이썬 설치가 끝났고, IDE도 있으니, 본격적으로 코딩 하는 법을 익혀보도록 합시다.
오늘은 한 가지 좋은 소식과 한 좋은 소식을 가지고 왔는데요, 하나씩 말씀드리도록 하겠습니다.
우선, 좋은 소식부터 말씀드리자면, 야찌 게임의 AI 를 다 만들었습니다! 그냥 일반적인 유전자 알고리즘 프로그램이기 때문에 그렇게 오래걸리진 않은 것 같습니다. 제가 좀만 더 덜 멍청했더라면 어제 끝낼 수 있었을 텐데 아쉽네요... ㅠㅜ
나쁜 소식을 말씀드리자면, 실제로 학습을 시켜봤지만, 실력이 늘지 않습니다... 정말 치명적인 문제죠. 지금 원인 발견 및 해결을 위해 노력하고 있습니다. (사실 앞서 만들어 놓은 계획이 대충 만든 것이기 때문에 실제로 학습속도가 매우 느리거나 불가능할 수도 있습니다.) 하지만 그때에는 그냥 하드 코딩으로 언제든지 갈아타면 되는 것이기 때문에, 매우 마음이 아프지만 우선 만든 과정을 소개해 드릴까 합니다.
우선, 큰 그림을 그려봅시다.
YahtzeePlayer 라는 object와 YahtzeeLearner 라는 object, 두 개를 만들 예정입니다.
큰 아이디어는 아래의 github에서 얻었습니다만, 실제로 만들어보니 많이 다른 상황이었기 때문에 다 만들어야 했습니다.
일단 YahtzeeLearner를 한번 볼까요? 이 object는 YahtzeePlayer를 발전시킬 것입니다.
따라서 player를 input으로 받는 것으로 시작해야겠죠?
마지막에 위와 같은 코드를 사용해서 돌릴 것이기 때문에, Learner에도 run 함수를 만들어 줍시다.
제가 만든 run 함수입니다. 이걸 이해하기 위해 자료구조를 설명해드리도록 하겠습니다.
보면, self.player.Chromo라는 변수가 있습니다. 이것이 gene pool 이라고 생각하시면 되겠습니다. Chromo를 대문자로 쓴 것은 Chromo 내부에 있는 gene 하나하나가 chromosome 이라는 변수의 형태로 저장되어 있기 때문입니다. chromosome은 각각 854 개의 숫자를 갖고 있는 numpy array입니다. 이중에서 853개는 실제로 계산에 필요한 데이터이고 마지막 854번째 칸은 점수를 보관할 공간입니다. 야찌 게임은 다행이도 게임의 점수가 int로 나오기 때문에 굳이 float array를 사용하지 않아도 돼 효율에 약영향을 미치지 않을 것 같습니다.
자 그러면 위의 코드를 뜯어봅시다....
1. self.plater.fitness() 라는 것은 훗날 정의할 함수입니다. 매우 간단한 함수인데요, 각 chromosome 을 사용해서 야찌를 플레이하고, 그 점수를 각 chromosome의 마지막 index에 저장해 주는 함수입니다.
2. population = self.player.Chromo 이는 player의 gene pool을 가지고 옵니다.
그 다음 4 줄은 그냥 print 관련된 함수들이니, 넘어가겠습니다.
3. if self.player.check_stop(): break 이거는 몇 번 이상 돌지 말라는 의미입니다. 제가 프로그램을 돌려놓고 나서 끄는 걸 잊는 경우가 많아 이를 방지하기 위해 만들어놨습니다.
4. population = self.next_generation() 가장 중요한 줄입니다. 다음 population을 만들어야죠. 이 함수에 대한 설명이 바로 연결될 것입니다.
어떻게 next_generation을 만들까요?
우선 코드를 가지고 와 봤습니다. 천천히 봅시다.
우선, parents generator를 만들었습니다. 사실 저는 원래 for generator는 편리해서 자주 쓰는데에 비해 yield를 사용한 코딩은 많이 하지 않지만, 위에 올려 놓은 github 코드에서 yield를 사용했고, 저 또한 yield 없이 코딩을 해보았으나, 이 방법이 더 마음에 들어 저 부분을 따라하게 되었습니다.
next_population 이라는 빈 list를 하나 만들고,
이 list의 크기가 정해놓은 크기 이하인 동안, parents를 만들고, crossing이 일어날지, mutation이 일어날지 여부를 random.random()으로 결정해서 child chromosome을 만들고, 모두 next_population에 넣으면 됩니다.
보통의 경우 코딩을 할때 list의 최대 크기는 짝수로 지정해 놓겠지만(보통 끝자리를 0으로 맞추니까요) 혹시 모를 경우를 대비해 정해놓은 size까지만 사용하는 코드가 마지막에 추가됩니다.
여기까지가 YahtzeeLearner object에 대한 설명입니다.
그러면 이제 YahtzeePlayer를 볼까요? 위에서 보면 알 수 있듯이, 상당히 많은 함수들을 갖고 있어야 합니다.
우선 위에서 이름이 한번씩은 불려진 한수들의 신제 모습입니다.
parents는 우선 무시하고, 나머지 세개를 봅시다.
crossover는 생물시간에 배웠겠지만, 두 chromosome이 겹치면서 서로의 정보를 나눠 갖는? 형태의 변화를 의미합니다. 자세한건 검색해보시기 바랍니다.
결국 중요한 것은 A와 B의 유전자가 아래와 같이 된다는 것입니다.
AAAAAAAAA => AAAABBAAA BBBBBBBBBB => BBBBBAABBB
따라서 랜덤 index 두개를 잡고 치환해주면, 끝입니다. 두 값이 같으면 어떻게 되는가? 운이 없게도 crossing 이 일어나지 않는 거죠. (1/853의 확률입니다)
mutation은 말 그대로 랜덤 index의 값에 랜덤 int를 더하는 것입니다.
check_stop은 Iteration 횟수가 정해 놓은 것을 넘는지 확인하는 한수입니다. 이게 True 면, 프로그램은 종료합니다.
이게 parents를 만드는 코드입니다. generator를 return 하는 함수죠. Generator에서 일어나는 귀찮은 일들을 모두 없애기 위해 while True문을 넣었습니다. 이 generator가 일을 그만두어야 하는 조건이 없기 때문이죠. 그러면 tournament가 뭐냐 하면 위를 보시면 됩니다.
간단하게 두 수컷을 싸움 붙여서 꼬리에 있는 점수 라벨로 싸우고, 이긴 사람이 아빠가 되며, 이는 암컷 둘에도 동일하게 적용됩니다.
이게 끝입니다. 남은 건 저 853개의 숫자 형태 chromosome과 야찌 게임과 무슨 상관이 있냐에 대한 문제가 남았군요.
제일 간단한 부분입니다. input이 19개 였고, hidden이 10개면, 그냥 19*10의 matrix 연산을 하면 됩니다. (bias에 대한 설명은 생략하겠습니다.)
이렇게 할때 총 필요한 숫자의 개수를 계산해 보면, 853개가 나오는 것입니다.
여기 코드는 나름 더럽기 때문에 생략하도록 하겠습니다. 정말 궁급하면 댓글 달아주시거나, 이메일 주세요.
자... 이제 코딩이 끝났습니다. 돌리면 됩니다. 근데 앞서 말씀드렸다 싶이, 얘가 점수가 안 올라요 ㅠㅜ.... 이 문제는 해결되는 데로 다시 포스팅을 하도록 하겠습니다.
2. 이때, Yahtzee 가 두번 이상 나올 때엔 원래 100점의 보너스와 함께 추가 주사위 횟수를 준다던가, 하나의 0점이 아닌 점수를 50점으로 체워준다던가 하는, 다양한 보너스 룰이 있습니다. 이것은 무시하기로 했습니다. 이외의 보너스는 취급합니다.
즉, 1, 2, 3, 4, 5, 6 칸의 합이 63 이상이어서 생기는 보너스 35점은 계산에 넣되, 더블 야찌 보너스는 생각하지 않습니다.
3. 플레이는 번갈아가며 한 스테이지를 진행하여 최종 총합에 따라 승패가 정해집니다.
... 그리하여, 오늘부터는 야찌 프로그램을 만들어 보겠습니다. 어떻게 만들까에 대해서 많은 생각을 해봤습니다. 제가 output 결과를 볼 수 있는 방법이 별로 없더군요.
그래서 생각한 것이 GUI 를 만들고 winapi를 사용해서 이를 조작하는 것인데.... 솔직히 말씀드리자면 너무 귀찮아서 그냥 콘솔에 printout 하겠습니다. 이를 어떻게 하나, 하시는 분들이 계신다면 나중에 시간이 남을 경우 포스팅을 하나 하겠습니다.
---------------------- 무엇을 print 할 것인가? ---------------------------
어차피 training 을 한창 하고 있을 때에는 print를 하지 않을 것입니다. 제가 그 과정을 다 따라갈 수도 없고, 따라갈 생각도 없기 때문에, 불필요한 프로세스의 낭비를 하고 싶진 않기 때문입니다. 즉, print 할 내용은 게임이 잘 돌아가는지를 확인하는 용도 정도로만 생각해 주세요.
1. 무작위의 주사위 5개
(이 사이에 우선 랜덤으로 뽑아서 바꿀 여부를 결정해 놓을 것입니다.)
2. 골라진 주사위 번호들, 다시 굴려진 결과
(이 사이에 우선 랜덤으로 뽑아서 바꿀 여부를 결정해 놓을 것입니다.)
3. 골라진 주사위 번호들, 다시 굴려진 결과
4. 어떤 점수판을 선택하였는가
5. 결과로 나온 점수판 전체 출력
정도로 할 것입니다.
---------------------- 내부 구조 (점수판) ---------------------------
우선, 점수판을 어떻게 해놓을까요? 이것이 중요한 이유는 이미 고른 점수판을 다시 고르면 안되기 때문입니다.
가능한 방법은 많습니다. 저는 파이썬 사용자이기 때문에, dictionary 라는 것을 사용하여 그 dictionary의 key를 주사위의 번호로, value를 값과 골라진 여부 boolean의 list 혹은 tuple을 넣는 것도 방법입니다. 가장 깔끔한 방법입니다. 개인적으로 다른 사람들과 공동 작업을 한다면, 이 방법을 썼을 것 같습니다.
매우 간단한 방법도 있습니다. 모든 점수를 -1로 초기화 해 놓는 것입니다. 그래서 해당 값이 양수인 경우에만 이후 점수반영에 포함시키고, 음수일 때에만 새로운 값을 넣을 수 있게 하는 것입니다.
당연하지만, 이미 선택되었는가 여부의 list를 별도로 갖고 있는 것도 방법입니다.
저는 제 귀찮이즘 세포에게 패배하였기에 마지막 방법으로 가겠습니다. (제일 구현에 드는 코드 줄 수가 적을 것 같습니다...)
---------------------- 내부 구조 (classes) ---------------------------
우선, 야찌 class 를 만들어봅시다.
YahtzeeGame class 를 만들었습니다. 이 클라스 내에는 어떤 값들이 있어야 할까요??
실행해보면 잘 나온다는 것을 알 수 있습니다. 물론, dictionary를 사용했기 때문에 점수판 내의 순서는 더 이상 보장되지 않습니다. 그리고 위의 이미지에서 하나 정정하자면, bonus1과 bonus2의 경우는 선택되어서는 안되기 때문에 초기값을 True로 설정했습니다.
이외에도 init 함수 안에는 몇 가지가 더 들어가야 할 것 같습니다.
각각 주사위를 다시 굴릴 수 있는 횟수(최대 2)와 실제 주사위의 눈금 5개, 그리고 주사위를 고정할지 여부를 결정하는 boolean 5개입니다.
두 번때 Dice_int 는 디버깅을 하기 위해 만들어놓은 것입니다. 예를 들어, 풀 하우스 인식 함수가 제대로 작동하는지 알아보려면, 그것이 나올 때까지 기다리는 것보단 만드는 것이 쉽기 때문이죠. 실제 게임이 돌아갈 때에는 지워야 합니다.