끝나지 않는 프로그래밍 일기

 

 

인스턴스(Instance)

이번에는 객체지향 프로그래밍에서 핵심이 되는 요소 중 하나인 클래스(Class)에 대해서 알아보도록 하겠는데, 그 전에 인스턴스란 개념에 대해 먼저 알아보도록 하겠습니다. 여기서 인스턴스와 객체(Object)는 같은 의미인데, 인스턴스는 클래스에 의해 만들어진 객체를 인스턴스라고 합니다. 이 설명에서 한가지 유추해보자면, 클래스는 인스턴스를 만드는 하나의 틀이라고 볼 수 있습니다. 우리가 앞으로 배울 클래스란 녀석을 통해서 인스턴스를 계속 만들어 낼 수 있습니다. 이제는 한번 클래스가 무엇인지 살펴보도록 합시다.

 

클래스(Class)

앞에서 인스턴스에 대해서 간단히 알아보았는데, 파이썬에서의 클래스를 간단하게 설명하자면 변수와 함수를 모아넣은 것이라고 할 수 있습니다. 클래스가 어떠한 역할을 하는지 한가지 예를 들어볼까요? 우리가 쿠키를 만들때, 쿠키의 모양을 내기 위하여 모양틀을 이용하는 경우가 대부분입니다. 여기서 모양틀에 찍혀져서 만들어지는 어떤 모양의 쿠키를 인스턴스라 할 수 있으며, 모양틀을 클래스라 할 수 있습니다.

<그림 1-1. 다양한 모양의 틀>

정리하자면, 모양을 찍어낼 수 있는 틀을 가지고 서로 각각의 모양을 지닌 내용물을 만들어 낼 수 있는데 여기서 클래스는 그저 틀일 뿐이고, 인스턴스는 틀을 가지고 만들어진 내용물과 같은 것입니다. 우선은, 클래스의 이해를 돕기 위해 간단한 예제를 먼저 살펴보면서 클래스를 어떻게 선언하는지, 클래스란 어떤 것인지 알아보도록 하겠습니다.

>>> class Student:
	name = "김철수"
	def info(self):
		print("제 이름은 " + self.name + "입니다.")

>>> inst = Student()
>>> type(inst)
<class '__main__.Student'>
>>> inst.name
'김철수'
>>> inst.info()
제 이름은 김철수입니다.

위 예제를 보시면 1행~4행 까지는 Student라는 이름의 클래스를 선언하는것 이라고 보시면 됩니다. 클래스를 선언하는 방법은 위 예제를 통해 아래와 같은 형식을 따른다는 것을 알 수 있습니다.

class 클래스명:
	문장
	...

이어서 2행을 보시면 변수가 Student 클래스 내에 위치해있다는 것을 알 수 있습니다. 그리고 3~4행에서는 info라는 함수도 Student 클래스 안에 있다는 것 역시 보실 수 있습니다. 위에서 말했던대로, 클래스는 변수와 함수를 모아넣은 집합체라고 말할 수 있습니다. 6행에서는 인스턴스 객체를 만드는데 여기서 클래스의 이름을 호출하고 이를 변수에 집어넣음으로써 간단하게 인스턴스 객체를 만들 수 있습니다. 7행에서 type라는 내장 함수를 사용하여 변수 inst의 형식을 알아보는데, 8행의 결과를 통해 Student라는 클래스를 통해 생성된 인스턴스 객체임을 알 수 있습니다.

>>> inst.name
'김철수'
>>> inst.info()
제 이름은 김철수입니다.

위 코드에서의 1행은 인스턴스 객체인 inst의 name 변수를 가져오는 것이며, 3행은 info 함수를 호출하는 것이라고 볼 수 있습니다. 그런데, 코드를 자세히 훑어보니 이상한 점이 하나 보이지 않나요? Student 클래스 내의 info 함수의 인자를 보시면 첫번째로 self라는 녀석이 와있는 것을 확인하실 수 있습니다. 이 self라는 녀석이 어떤 기능을 하는 녀석인지 한번 보도록 할까요?

 

self

이 self는 현재의 인스턴스 객체를 가리키는 기능을 하는 녀석으로써, 쉽게 말하자면 self의 사전적 의미 그대로 자기 자신을 의미합니다. 클래스 내에서 함수가 정의될 경우에는 첫번째 매개변수로 self가 따라붙어야 합니다. 아래와 같이 말입니다.

class Dog:
    def cry(self):
        print("왈왈!")

dog = Dog()
dog.cry()

그럼 문득 'self가 도대체 뭔데 따라붙어야 하는거지?'와 같은 의문이 들지도 모르겠습니다. 위의 예제에서 한번 self를 빼보고 실행해보면 아래와 같은 에러를 보실 수 있습니다.

TypeError: cry() takes 0 positional arguments but 1 was given

cry()는 위치 인수(positional argument)를 아무것도 받지 않지만 한 개가 더 들어왔다는 얘기입니다. 우리는 인수를 써주지 않았는데 누가 넘겼다는 걸까요? 파이썬은 dog가 클래스 Dog의 인스턴스고, dog.cry()와 같이 인스턴스 메서드를 호출하려고 하면 파이썬은 내부적으로 'Dog.cry(dog)'와 같이 변형합니다. 따라서, 위와 같은 에러가 발생하는 것입니다. 그럼 아래와 같이 호출하면 어떨까요?

>>> Dog.cry()
왈왈!

파이썬 3.x 버전에서는 잘 동작하는 것을 확인할 수 있습니다. 파이썬 2.7 이하의 버전에서는 @staticmethod 데코레이터가 없으면 이러한 문장을 사용할 수 없습니다. (데코레이터에 대해서는 아직 몰라도 상관이 없습니다. 이 부분은 추후에 설명합니다.)

TypeError: unbound method cry() must be called with Dog instance as first argument (got nothing instead)

self를 제외한 뒤 저렇게 호출하는 방법은 사용하지 마시기 바랍니다. 정적 메서드를 정의하여 쓰고 싶다면 @staticmethod와 함께 사용하길 바랍니다. 위와 같이 작성하는 경우에는 일반 함수 정의와 다를 것이 없으며, 정적 메서드와 동일하게 객체와 관련된 변수나 값을 참조할 수 없습니다. 이어서, 예제 하나를 더 보도록 합시다.

class Dog:
    def cry(self):
        print("왈왈!")
        print(id(self))

dog = Dog()
dog.cry()
print(id(dog))

파이썬의 내장 함수인 id()는 넘겨받은 객체의 고유 주소를 돌려주는 함수입니다. 위의 예제를 실행해보면 self의 주소와 인스턴스 dog의 고유 주소가 동일한 것을 확인할 수 있습니다. 위에서도 말했듯이, 'dog.cry()'는 'Dog.cry(dog)'로 변형되기 때문에 어떻게 보면 당연한 결과입니다. 그리고 self는 파이썬의 키워드는 아니며 관례적으로 사용하는 이름이므로, 임의로 바꿀 수는 있습니다. 그래도 왠만하면 관례를 따르는 것이 좋겠죠?

class Dog:
    def cry(inst):
        print("왈왈!")

dog = Dog()
dog.cry()

참고로, 클래스 내부의 멤버에 접근할 때에도 앞에 self.를 붙여주는 것을 잊지 마시기 바랍니다.

class Dog:
	name = "멍멍이"
	def cry(self):
		print(self.name + ": 왈왈!")

만약 위 예제에서 self.를 제외한다면, "global name 'name' is not defined"와 같은 에러가 발생합니다. 내용 그대로 전역에서 name이란 변수를 찾게 되는데, 우리는 전역에 name을 정의하지 않았으므로 이와 같은 에러가 발생하는 것입니다.