본문 바로가기

카테고리 없음

[Python 28] 객체기술의 개요

 

 

 

이제부터는 객체(Object) 기술에 대해 살펴보도록 하자. 객체기술은 현대의 모든 정보기술의 근간을 이루는 기술로서 반드시 이해하고 넘어가야 하는 부분이다. 이번 글에서는 객체의 기본 개념에 대해 살펴보도록 한다.

 

○ 객체를 활용하여 행위의 주체를 표현할 수 있다.

 

컴퓨터 언어에서 객체(Object)는 어떤 것 (~thing)을 나타낸다. 함수는 행위(behaviour) 라고 계속 얘기를 했었는데, 사람들의 언어에서 행위는 동사에 해당한다. 그런데, 사람들의 언어에서 동사 앞에는 항상 그 행위의 주체가 되는 주어 (subject)가 나타난다. 즉, 사람의 언어에서 가장 일반적인 문장(statement)은 "누가(who) 무엇을(what) 어떻게(how)한다"의 형태이다. 우리가 영어공부하면서 문장의 5형식이라고 배웠던 것 중에 3형식에 해당하는 문장이다. 

 

여기서 어떻게(how)는 동사(verb)에 해당하고 프로그래밍에서는 함수(function)에 해당한다. 동사의 대상이 되는 무엇 (what)은 보통 함수의 호출과 함께 전달되는 인자(argument)에 해당한다. 여태까지는 사실 인자를 어떤 데이터라고 한정한 측면이 있지만, 사실 세상에 있는 모든 것들이 이 무엇(what)에 해당할 수 있다. 

 

세상의 모든 행위에는 주체가 있듯이, 함수에도 그 함수를 실행하는 주체(subject)를 정의할 수 있다. 여기서, 이 주체에 해당하는 것들을 객체(Object)라고 부른다. 객체라고 하니까 왠지 목적어(object) 같아서 주체로 사용된다니 이상하게 느껴질 수도 있겠다. 사실, 세상에 존재하는 것 들 중에 다른 것들과 구별할 수 있는(identifiable) 모든 것들을 객체라고 부른다. 실제로 존재하기도 하고, 이념적으로 사람들의 머리 속에서만 존재하기도 한다. 어쨌거나 객체란 우리가 어떤 어떤 것 (~thing) 이라고 부를 수 있는 모든 것들이라고 생각하면 된다. 그런데 이러한 것들은 어떤 행위의 주체가 되기도 하지만, 다른 어떤 행위의 객체가 되기도 한다. 예를 들어, (1) 사람이 비행기를 날리지만, (2) 비행기가 서쪽 하늘을 가로질러 날아가기도 한다. 여기서 전자의 비행기는 날림의 대상으로 사용되었지만, 후자의 비행기는 날아감의 주체로 사용되었다. 

 

자, 어떤 행위가 있다고 하자. 이 행위를 fly( )라는 함수로 표현했다고 하자. 그 내용 (메커니즘, mechanism) 은 아직 결정되지 않아 다음과 같이 간단하게 정의되었다고 해 보자. 

 

 

 

이대로 라면, 실제 fly( ) 를 호출했을 때,  fly( ) 가 제대로 실행되었는지 여부를 알 수 없으니, 다음과 같이 살짝 바꾸어 보자.  fly( ) 함수가 호출되면 "I'm flying" 이라는 텍스트가 출력되게끔 만들어 보았다. fly( ) 함수를 호출했는데 I'm flying 이라는 출력이 나타나면 fly( ) 함수 호출이 제대로 이루어진 것이 되는 셈이다. 실제로 fly( ) 의 메커니즘으로서 실제 "어떻게" 날아가는지에 대한 로직이 만들어지면 "나중에" 현재의 print( ) 문 대신에 함수의 몸통(body)으로 구현하면 되겠다. 참고로, 공학 분야에서 구현(implementation)이라는 용어가 자주 사용되는데 "만들어서 실현하다" 라는 의미 정도로 이해하면 좋겠다.

 

 

 

그럼, 이렇게 fly( ) 하는 "것들"을 우리는 뭐라고 부르는가? 도대체 무엇"들"이 이렇게 fly( ) 하는가? 만약에 우리가 그런 것들을 Plane 이라고 부른다면 다음과 같이 정의할 수 있다. 즉, 이렇게 fly( ) 하는 것들 (물론, 세상에 fly( ) 하는 것들은 많으나 우리의 메커니즘 대로 "이렇게~" fly( ) 하는 것) 을 Plane 이라고 부른다는 뜻이다. 그리고, 반대로 우리가 말하는 보통의 Plane 들은 무엇을 말하는 것이다? 그렇다. 이렇게 fly( ) 하는 것들을 말하는 것이다. 이런 의미를 파이썬에서는 다음과 같이 정의할 수 있다.

 

 

 

위와 같은 형태를 클래스(class)의 선언(declaration) 또는 정의(definition) 라고 부른다. 첫번째 줄에 가장 앞에 나타나는 class 라는 키워드는 "~라는 것들"이라고 생각하면 된다. 그 다음에 나오는 Plane 과 결합하면 "Plane 이라는 것들"이 되는 셈이다. 다시 얘기하면, 첫줄의 class Plane : 이라는 것은 "세상에 있는 어떤 것들을 우리는 Plane 이라고 부르는데, 그 Plane 이라는 것들이 무엇이냐 하면은..."의 의미가 된다.

 

그럼, 그 다음에는 무엇이 와야 하겠는가? 당연히 Plane 이 무엇인지가 나와야 겠다. 현재는 fly( ) 라는 함수가 정의에 포함되어 있는데 한데 묶어서 읽어 본다면 이런 의미가 된다. "우리가 Plane 이라고 부르는 것들은 이렇게 fly( ) 하는 것들을 가리킨다"의 의미다. 

 

근데 클래스 정의에서의 Plane 은 마치 머리속에 있는 비행기와 같아서 실제로 사람을 태우고 하늘을 날 수는 없다. 실제로 fly( ) 하려면 실제 fly( ) 할 수 있는 Plane 이 있어야 한다. 여기서, 실제로 fly( ) 할 수 있는 Plane 을 Plane  객체라고 부른다.

 

 

○ 클래스와 객체의 차이

 

모든 언어에서 하나의 명사는 2개의 의미로 사용되고 있다. 하나는 각각의 개체(entity, 객체와 동일시해도 됨)를 가리키는 의미로, 또 다른 하나는 그런 개체들이 모여있는 전체라는 의미를 가리킨다. 전자를 객체라고 부르고, 후자를 클래스라고 부른다. 객체는 실체가 있는 것이고, 클래스는 마치 하나의 이미지와 같다. 

 

아이가 커가는 과정에서 사물을 파악하는 인지능력이 발전하는데, 이 과정에서 객체와 클래스가 어떻게 조화롭게 대응되는지 한번 살펴보자. 

 

아이가 태어 났다. 아이는 내내 집에 있다가 이제 처음으로 아빠랑 세상 밖으로 나들이를 나갔다. 나들이를 나갔다가 처음으로 이상하게 생긴 동물을 보았다.  "아빠, 저건 뭐야?" 아빠가 대답한다. "아, 저건 강아지야". 아기 머리속에 강아지의 사진이 저장되고 강아지라는 이름표(tag) 가 붙는다. 그리고 또 길을 가는데, 또 다른 동물을 보았다. 아까 본 동물보다는 키도 훨씬 크고 털도 많았다. "아빠, 저건 뭐야?" 아빠가 대답한다. "아, 저것도 강아지란다". 아기가 혼란스럽다. 전에 본 강아지랑 방금 본 강아지는 전혀 달라 보이는데 왜 똑 같이 "강아지"라고 하는지에 대한 의문이 생긴다. "아, 뭔가 공통점이 있으니까 같은 이름으로 불리는거겠지?" 라는 생각과 함께 알고 있던 2개의 사례(인스턴스, instance) 간에 공통점을 찾아내서 일종의 대표적 이미지를 만들게 된다. 이런 과정을 보통 추상화(abstraction) 과정이라고 부르기도 한다. 이제 이러한 추상적 이미지에 강아지란 이름표가 붙게 된다. 

나들이를 계속 하는데, 또 다른 동물을 보게 된다. 내가 알고 있는 강아지랑 비슷해 보여서 "아빠, 저것도 강아지야?" 했더니 아빠가 "아냐, 저건 고양이란다" 라고 한다. 똑같이 강아지인줄 알았는데, 저 동물은 고양이라고 불린다고 한다. 이름이 다르니 뭔가 다른게 있는거겠지? 그게 뭘까? 강아지랑은 다르게 행동이 날렵하고, 잠깐 정지자세로  그윽하게 응시하기도 하고, 담벼락이나 높은 나무에 쉽게 올라간다. 이런 차이점이 발견되면 고양이의 이미지도 만들어지지만, 강아지의 이미지도 뚜렷해 지는 효과를 가져오게 된다. 물론, 이러한 움직이는 생물들은 움직이지 않는 생물들인 풀, 꽃, 나무랑은 또 다르다.  그래서, 고양이와 강아지는 크게 하나로 묶이기도 한다. 그 묶음에는 어떤 이름표가 붙으면 좋을까? "사람이랑 친한 생물들 - 얘들은 움직임" 정도면 괜찮을 것 같다. 

 

위의 사례에서 강아지라고 부를 수 있는 대상은 2개이다. 아기가 처음으로 만났던 그 진짜 살아 움직이는 생명체랑 아기 머리속에 강아지라는 이름표가 달린 이미지. 전자를 객체 (object) 또는 인스턴스(instance)라고 부르고, 후자를 클래스 (class)라고 부른다. 

 

이러한 의미의 대립과 조화는 인간의 언어에 너무 흔하게 나타난다. 예를 들어, 아래의 2개 문장을 보자. 이 2개 문장에는 공통적으로 비행기(Plane)이란 단어가 사용되고 있다. 하지만, 각각이 나타내는 의미는 서로 다르다. (1)에서의 비행기는 클래스이고 (2)에서의 비행기는 객체이다. 

 

(1) 우리나라에서 미국으로 가려면 비행기 외에는 대안이 없다.   
(2) 내일 미국 가는 비행기 안에서 읽을 책을 하나 사야겠다.

 

나는 지금 새로 이사 온 동네에 있는 시립도서관을 가는 중이다. 처음 가는 도서관이어서 내가 찾고 싶은 책을 찾지 못하는게 아닐까 걱정이 된다. 아래의 2개 문장에서 사서 란 단어가 사용되고 있는데, 그 의미는 서로 다르다. (1)에서의 사서는 "책을 관리하고, 검색이나 책 대여를 도와주는 역할을 하는 도서관 직원" 의 의미지만, (2)에서는 진짜 사서 선생님이다. 대화를 할 수 있고, 검색이나 대여 서비스를 요청할 수 있는 진짜 사서선생님.

 

(1) 이 도서관에도 사서 선생님은 계시겠지?
(2) 도서관에 도착해서, 마침 사서 선생님이 입구에 나와 계셔서 도움을 받을 수 있었다. 

 

마지막으로 하나의 사례만 더 들어 보자.  우리의 언어생활에서 흔하게 나타나는 예제이다. 어느 것이 클래스이고, 어느 것이 객체인가?  (1)번의 모니터가 객체이고 (2)번의 모니터는 클래스이다. 

(1) 집에서 쓰고 있는 모니터가 고장났다
(2) 아빠한테 모니터 하나 새로 사달라고 말해봐야 겠다. 

 

○ 메쏘드의 호출은 그 서비스를 수행하는 객체가 생성되어야 가능해진다

 

우리가 저 위에서 클래스로 정의한 Plane 를 가져왔다. 이 정의는 무엇을 의미하는가?

 

 

우리 세상에서 Plane 이라고 부르는 것들 (들~, 복수니까 클래스다 ^^)은 fly( ) 할 줄 안다. 다시 말하지만, 지금은 어떻게 fly( ) 하는지가 중요한 것은 아님. 이렇게 Plane  객체들이 하는 fly( ) 라는 기능은 일종의 서비스(service) 이다. Plane이 fly( ) 해줄 것을 원하는 외부의 다른 객체 (이 경우에는 고객 customer 객체겠다) 가 그 서비스를 요청(request) 함으로써 이루어지게 된다. 비행기가 자기가 알아서, 자기가 필요할 때 fly( ) 해 버리면 큰일나겠다. 

 

하지만, 머리속의 추상화된  Plane 은 fly( ) 서비스를 제공할 수가 없다. 실제 Plane 객체에게만 서비스를 요청할 수 있다. 그래서 클래스 정의 후에는 객체 생성 과정이 필요하다.

 

객체의 생성은 아래와 같은 간단한 코드로 이루어진다. 

 

 

 

Assignment 기호 오른쪽에 보이는 함수 Plane( )은 생성자 함수(constructor) 라고 부른다. 객체를 생성한다, 객체를 만든다는 것도 일종의 행위이다. 그래서 하나의 함수가 된다. 그런데, 이 함수는 사용자(개발자)가 만드는 것이 아니라 클래스의 정의에 맞게 파이썬이 만들어서 실행해 주는 함수이다. 이 생성자 함수는 매우 특별한 특징이 있는데, 클래스의 이름과 똑 같다. Plane 클래스가 정의되면, 그 정의에 맞는 Plane  객체를 만드는 함수인 Plane( ) 함수가 자동적으로 만들어진다. 물론, 나중에 조금 복잡한 클래스인 경우에는 객체를 초기화하는데 필요한 값들을 인자로 전달하는 경우가 있다. 이런 경우는 조금 있다가 보기로 하자.

 

생성자 함수인 Plane( )이 실행되면, 실제 fly( ) 할 수 있는 Plane 객체가 생성된다. 데이터와 함수도 이름이 있듯이, 이 객체에도 이름을 부여하여야, 계속해서 이 객체와 커뮤니케이션 (서비스 호출) 할 수 있게 된다. 위의 코드처럼 p = Plane( ) 이라고 하게 되면 Plane 객체를 만든 후에, 만들어진 그 객체를 p 라는 이름으로 가리킬 수 있게 된다. 

 

이렇게 객체를 하나 생성하게 되면 실제 메쏘드 호출이 가능해 진다 메쏘드 호출은 다음과 같이 한다.  실제 우리 나라 말로 표현한다면 "p (라는 이름으로 불리는 Plane 객체가) fly 하도록 요청한다"는 의미로 p.fly( ) 라고 나타낸다. 우리가 함수에서 익히 사용해 오던 함수 호출 앞에 멤버 오퍼레이터(member operator) 라고 부르는 닷( . )이 있고 닷( . ) 앞에 그 행위의 주체가 오게 된다. 그런데, 실제로 호출해 보면 아래와 같이 에러가 난다. 에러를 보면 "fly( ) takes 0 positional arguments but 1 was given" 이라고 한다. fly( ) 함수는 매개변수가 1개 있는데, 호출에서 인자가 0개이어서 에러라는 뜻이다. 왜 인자가 1개가 있다는거지?

 

 

 

○ 클래스에서 함수의 호출과 self의 의미

 

지금은 조금 복잡할 수 있는 얘기여서 다음에 자세하게 한번 살펴보는 것으로 하되, 실제 우리가 p.fly( ) 형태로 메쏘드 호출을 하면 실제로는 Plane.fly(p)  형태로 함수 호출이 이루어진다. 무슨 뜻이다? 어떤 함수가 호출되는가 ? (Plane 클래스에서 선언된 fly( ) 함수가 호출된다. 그래서, Plane.fly( ) 이다)

 

즉, p.fly( ) 함수 호출은 Plane.fly( ) 로 바뀌어서 호출이 된다. 그런데, Plane.fly( ) 호출에는 실제 어느 Plane 객체가 fly( ) 하는지에 대한 정보가 나타나 있지 않다. 그래서, 인자로 알려주는 것이다. 그래서, Plane.fly(p) 로 호출이 이루어지게 된다. 그래서 실제로 fly( ) 함수는 실제로는 인자가 없지만, 디폴트로 fly( ) 할 Plane 객체의 이름(참조자)를 일종의 "숨겨진" 인자로 받게 된다. 일반적으로 self 라는 이름의 매개 변수로 받는다. 뭐 a, b, c, x, y, z... 이런 흔한 이름의 변수로 받아도 되지만 모든 사람들이 self 로 받는다. self 란 이름이 준 표준인 셈이다. 실제로 이렇게 호출을 해 보면 아래와 같이 동작하게 된다. 

 

 

즉, 아래에서 보는 바와 같이 Plane.fly(p)와 p.fly( )는 정확히 동일한 문장이다. Plane 클래스의 정의와 fly(self) 함수의 정의를 본다면 Plane.fly(p) 형태의 호출이 알맞은 것 처럼 보이기는 하나, 전통적으로 닷( . ) 앞에는 객체가 오는 형태인 p.fly( ) 가 선호된다. 메쏘드의 호출과 정의 상에 인자 개수가 미스매치가 발생하는 이유는 이와 같다. 

 

 

이번 글에서 우리는 클래스와 객체가 무엇인지를 살펴보았고, 실제 파이썬을 통해 단순한 클래스를 하나 선언한 후에 객체를 생성하고 실제 메쏘드를 호출해 보았다. 사실 이게 기초이면서 전부이기도 하다. 이번 글이 이해가 됐으면 객체 공부는 시작하자마자 끝이다. 뭐, 더할 것이 없다. 물론, 글은 계속 이어지겠지만... ^^