이번 절에서는 클래스와 객체의 개념을 정의하고, 파이썬이 어떻게 이 개념을 이용해 데이터를 관리하는지를 함께 설명한다. 이 내용은 앞으로 클래스를 프로그래밍에 활용하지 않겠다 하더라도 파이썬의 동작 원리를 이해하는 데 도움이 될 것이다. 쉬운 내용만은 아닐 수 있지만 최대한 이해하기 쉽게 쓰려고 노력했다. 열심히 읽어보도록 하자.

8.2.1 클래스: 데이터 유형

파이썬이 제공하는 기본 데이터 유형을 생각해 보자. 0, 1, 2는 정수, 0.0, 1.0, 2.0은 실수, '0', '1', '2'는 문자열이다. 이 외에도 불리언, 리스트, 사전, 집합 등 여러 가지 데이터 유형이 있다는 걸 배웠다. type() 함수를 이용하면 데이터의 유형을 구할 수 있다.

코드 8-10 데이터의 유형 확인하기

>>> type(0)             # 0의 유형은 int (정수)
<class 'int'>

>>> type('0')           # '0'의 유형은 str (문자열)
<class 'str'>

나는 4.1 데이터 유형의 확인과 변환에서 type() 함수의 반환 결과(<class 'int'>, <class 'str'>)를 읽는 법을 설명했다. class는 데이터의 유형이라는 의미고, 함께 출력되는 이름 'int', 'str'이 그 데이터의 종류를 나타낸다. 그런데 이 반환 데이터는 실제로 무엇일까? type() 함수가 반환한 결과를 다시 type() 함수에 전달해 그 유형을 확인해 보자.

코드 8-11 데이터의 유형을 나타내는 데이터의 유형은 무엇일까?

>>> type(type(0))        # 0의 유형의 유형은 type (유형)
<class 'type'>

>>> type(type(type(0)))  # 0의 유형의 유형의 유형도 type (유형)
<class 'type'>

type() 함수가 반환하는 데이터가 type이라는 데이터 유형임을 알 수 있다. 다시 말해, 데이터의 유형을 나타내기 위한 데이터가 존재한다는 뜻이다. 이 데이터가 바로 클래스다. int, float, str은 데이터 유형을 나타내는 데이터다. 0, 1, 2의 데이터 유형이 정수인 것처럼, int, float, str의 데이터 유형은 클래스다.

정리해보자.

  • 데이터: 값과 유형으로 구성된 정보.
  • 데이터 유형: 데이터가 어떤 범주에 속하는지를 나타내는 데이터의 구성요소. 클래스로 표현된다.
  • 클래스: 데이터 유형을 나타내는 데이터. int, float, str 등이 있다.

8.2.2 객체: 개별 데이터

컴퓨터가 데이터를 처리하려면 데이터를 메모리에 저장해두어야 한다. 디스크에서 읽어들이든, 네트워크로 전송받든, 사람이 입력하든 마찬가지다. 모두 메모리에 복사된 후 처리된다. 이렇게 메모리 위에 저장된 개별 데이터를 관리하기 위한 프로그래밍 개념이 객체(object)다.

파이썬의 객체는 정체성, 유형, 값의 세 가지 특성을 갖는다. 유형과 값은 지금까지 배운 데이터의 유형, 값과 동일한 개념이다. 정체성은 고유한 각각의 객체를 식별하기 위한 고유번호로, 객체가 메모리 속에서 위치한 주소 값이기도 하다. 객체의 정체성은 변하지 않으며 객체 하나에 하나씩만 있다. 동일한 유형, 동일한 값을 가진 데이터가 메모리 공간에 여러 개 존재할 수 있지만, 이들은 서로 별개의 객체이며 정체성도 서로 다르다.

객체와 변수를 구별하는 것도 중요하다. 객체를 변수에 대입해 둘 때가 많으므로 변수를 곧 객체로 오해할 수 있지만, 이는 실체와 이름을 혼동하는 것이다. 변수는 객체에 붙는 이름이며, 객체 하나에 이름 여러 개가 붙을 수 있다. 박연오는 나지만, ‘박연오’라는 이름이 나인 것은 아니며, 나라는 객체는 세상에 하나 뿐이지만, 나를 부르는 이름은 많다(‘나’, ‘너’, ‘그 녀석’, ‘박 팀장’, ‘로드 박’ 등등).

객체의 값은 객체를 평가함으로써 구할 수 있고, 유형은 type() 함수로, 정체성은 id() 함수로 구할 수 있다.

코드 8-12 객체의 값, 유형, 정체성 구하기

>>> a = 1789               # 객체를 만들어 변수에 대입

>>> a                      # 객체의 값 (객체 자신) 구하기
1789

>>> type(a)                # 객체의 유형 (클래스) 구하기
<class 'int'>

>>> id(a)                  # 객체의 정체성 (고유번호) 구하기
140711867085328

값이나 유형이 같더라도 정체성이 다를 수 있다. 데이터를 비교할 때는 비교 대상이 값인지, 유형인지, 정체성인지 헷갈리지 않도록 주의해야 한다.

코드 8-13 두 객체의 값, 유형, 정체성 비교

>>> a == 1789              # 두 객체의 값 비교
True

>>> type(a) == type(1789)  # 두 객체의 유형 비교
True

>>> id(a) == id(1789)      # 두 객체의 정체성 비교
False

>>> b = a                  # 동일한 객체를 다른 변수에 대입하면,
>>> id(a) == id(b)         # 두 이름이 객체 하나를 가리킨다.
True

주의해야 할 점이지만, 일반적인 경우(특히, 불변 데이터인 경우)에는 값과 유형이 동일하면 같은 데이터라고 봐도 큰 문제가 없다.

정리해보자.

  • 객체: 메모리에 존재하는 개별 데이터를 나타내는 개념
  • 객체의 값: 객체를 통해 구할 수 있는 정보 그 자체. 객체가 가변 데이터인 경우 값이 변할 수 있다.
  • 객체의 유형: 객체가 어떤 범주에 속하고 어떻게 다뤄야 하는지를 구별하기 위한 분류. 파이썬에서는 클래스로 유형을 나타내며, 이는 type() 함수로 구할 수 있다.
  • 객체의 정체성: 객체를 다른 객체와 구별하기 위한 고유번호이자, 메모리 상의 위치. id() 함수로 구할 수 있다.
  • 변수: 객체에 붙인 이름. 한 객체에 여러 개의 이름을 붙일 수도 있다.

파이썬은 모든 데이터를 객체로 관리한다. 모든 온갖 종류의 데이터, 함수, 클래스, 예외(9장), 모듈(10장)등은 모두 객체다.

연습문제

연습문제 8-3 객체 비교하기

다음 세 함수를 정의해 보아라.

  • 두 객체를 == 연산으로 비교하는 함수 equals()
  • 두 객체가 같은 유형인지 검사하는 함수 is_similar_to()
  • 두 객체가 동일한 객체인지 검사하는 함수 is_identical_to()

함수를 정의한 뒤에는 다음과 같이 함수를 테스트해 보고, 테스트 결과를 통해 b, c, d, e, f가 각각 어떤 점이 a와 연관이 있고 어떤 점이 그렇지 않은지를 비교해 설명해 보아라.

>>> a = ['python']
>>> b, c, d, e, f = a, a[:], ['python'], ['PYTHON'], ('python')

>>> equals(a, b), is_similar_to(a, b), is_identical_to(a, b)
(True, True, True)

>>> equals(a, c), is_similar_to(a, c), is_identical_to(a, c)
(True, True, False)

>>> equals(a, d), is_similar_to(a, d), is_identical_to(a, d)
(True, True, False)

>>> equals(a, e), is_similar_to(a, e), is_identical_to(a, e)
(False, True, False)

>>> equals(a, f), is_similar_to(a, f), is_identical_to(a, f)
(False, False, False)

8.2.3 클래스와 인스턴스의 관계

클래스가 데이터의 유형이라면, 한 클래스에는 그 범주에 속하는 객체(데이터)가 있을 것이다. 이 때, 어떤 클래스에 속하는 객체를 그 클래스의 인스턴스(instance)라고 부른다. 영어 단어 ‘instance’는 ‘실례’라는 뜻이다. ‘사람’이라는 범주에 나라는 물리적 실체가 속하는 것처럼, 어떤 클래스에 속하는 실제 데이터(인스턴스)가 여러 개 존재할 수 있다.

어떤 클래스의 인스턴스인지 검사하기

객체의 유형(클래스)을 구하려면 type() 함수를 사용하면 된다. 질문을 좀 더 한정해서, 객체가 어떤 유형에 속하는지(어떤 유형의 인스턴스인지) 확인할 수도 있다. isinstance() 함수를 사용하면 된다.

코드 8-14 isinstance() 함수를 이용해 객체가 유형에 속하는지 확인하기

>>> isinstance(1789, int)  # 1789는 int의 인스턴스인가?
True

>>> isinstance(3.14, int)  # 3.14는 int의 인스턴스인가?
False

isinstance() 함수의 이름은 “인스턴스인가? (is instance?)”라는 뜻이다.

한 객체는 동시에 여러 클래스의 인스턴스일 수 있다

한 객체의 유형은 클래스 하나로 정해져 있다. type() 함수는 하나의 값만을 반환한다. 그런데 한 객체는 동시에 여러 클래스의 인스턴스일 수 있다. 왜 그럴까?

나는 사람이라는 범주에 속한다. 그런데 나는 사람일 뿐 아니라 동물이기도 하고, 생물이기도 하다. 사람이라는 범주는 동물이라는 상위 범주에 속하고, 다시 동물은 생물이라는 상위 범주에 속하기 때문이다. 이를 파이썬 방식으로 표현하면, 나는 사람이라는 클래스의 인스턴스이고, 동시에 동물 클래스와 생물 클래스의 인스턴스이기도 한 것이다.

파이썬에서 클래스는 상위 범주를 나타내는 다른 클래스의 하위 범주가 될 수 있다. 객체의 유형 자체는 하나의 클래스이지만, 그 유형의 상위 클래스가 있다면 그 객체는 그 상위 클래스의 인스턴스이기도 하다.

코드 8-15 한 객체는 동시에 여러 클래스의 인스턴스일 수 있다

>>> year = 1789

>>> isinstance(year, int)     # 정수의 인스턴스인가?
True

>>> isinstance(year, float)   # 실수의 인스턴스인가?
False

>>> isinstance(year, object)  # object의 인스턴스인가?
True

1789isinstance() 함수로 확인해보니, int의 인스턴스이기도 하고 object의 인스턴스이기도 하다. object 클래스는 모든 클래스의 최상위 범주인 특별한 클래스다.

인스턴스 만들기

파이썬의 모든 데이터는 객체이고, 모든 객체에는 자기가 속한 유형(클래스)이 있다. 그러므로 파이썬의 모든 데이터는 어떤 클래스의 인스턴스다. 파이썬에서 데이터를 표현하는 것은 결국 인스턴스를 만드는 것이다. 인스턴스를 만드는 방법은 크게 두 가지가 있다.

첫번째 방법은 파이썬 코드 그 자체를 평가해 객체를 만드는 것이다. 수, 문자열 등의 몇몇 기본 데이터 유형은 코드로 표현한 내용을 평가를 통해 그대로 그 표현과 동일한 내용의 객체를 만드는 것이 가능하다. 이런 코드를 리터럴(literal)이라고 부른다. ‘literal’은 ‘문자 그대로’라는 뜻이다.

코드 8-16 리터럴을 평가해 객체 만들기

>>> 1789              # 정수 리터럴: 정수 인스턴스 만들기
1789

>>> 3.1415            # 실수 리터럴: 실수 인스턴스 만들기
3.1415

>>> 1-2j              # 복소수 리터럴: 복소수 인스턴스 만들기
(1-2j)

>>> '파이썬'          # 문자열 리터럴: 문자열 인스턴스 만들기
'파이썬'

리터럴과 유사하게, 리스트, 사전, 이름 없는 함수(람다 식) 등은 일정한 양식에 따라 코드를 작성함으로써 그 결과로 인스턴스를 생성할 수 있다. 이들은 문법적으로는 리터럴과 구별되지만, 코드가 곧 그에 대응하는 객체로 평가된다는 점에서 리터럴과 거의 동일한 개념으로 이해해도 무방하다.

코드 8-17 식을 평가해 객체 만들기

>>> [1, 2, 3]         # 리스트 식: 리스트 인스턴스 만들기
[1, 2, 3]

>>> {'year': 1789}    # 사전 식: 사전 인스턴스 만들기
{'year': 1789}

>>> lambda x: x * x   # 람다 식: 함수 인스턴스 만들기
<function <lambda> at 0x7ffa0a43ce18>

이것을 보면 여러분이 그동안 몰랐을 뿐, 이 책을 학습하는 과정에서 수많은 객체(인스턴스)를 만들어 왔다는 것을 눈치챌 수 있다. 리터럴을 이용해 객체를 만드는 방법은 파이썬 문법에 의해 정의되어 있는 방법이다. 물론, 문법으로 정할 수 있는 표현 규칙의 범위가 한정되어 있기 때문에, 모든 클래스에 이런 방법을 제공할 수는 없다. 파이썬이 제공하는 기본 클래스 가운데 일부만을 이 방법으로 만들 수 있다.

클래스의 인스턴스를 만들어내는 좀 더 일반적인 방법은 인스턴스화(instantiation)라는 방법이다. 함수를 호출할 때처럼 클래스 이름에 괄호를 붙여(클래스이름()) 인스턴스화를 수행할 수 있다. 다음은 인스턴스화를 수행해 정수(int)의 인스턴스와 리스트(list)의 인스턴스를 만드는 예다.

코드 8-18 인스턴스화를 이용해 클래스의 인스턴스 만들기

>>> int()            # 정수의 기본 인스턴스 만들기
0

>>> int(1789)        # 1789에 대응하는 정수의 인스턴스 만들기
1789

>>> int('1789')      # '1789'에 대응하는 정수의 인스턴스 만들기
1789

>>> int(1789.0)      # 1789.0에 대응하는 정수의 인스턴스 만들기
1789

>>> list()           # 리스트의 기본 인스턴스 만들기
[]

>>> list((0, 1, 2))  # (0, 1, 2)에 대응하는 리스트의 인스턴스 만들기
[0, 1, 2]

>>> list(range(3))   # range(3)에 대응하는 리스트의 인스턴스 만들기
[0, 1, 2]

위 코드에서 확인할 수 있는 것처럼 클래스의 이름은 함수의 이름처럼 괄호를 붙여 호출 할 수 있다. 이 표현은 겉보기에 함수 호출과 거의 똑같이 생겼고 예상되는 동작도 동일하다. 인스턴스화 표현은 괄호 속에 전달받은 인자를 참고하여 클래스의 인스턴스를 생성해 반환한다. 실제로는 함수 호출이 아니라 클래스의 인스턴스화를 수행하는 것이고 내부 동작에서는 차이가 있다. 이에 관해서는 8.3.4 인스턴스의 초기화에서 좀더 설명하겠다.

4.5 데이터 유형의 확인과 변환에서 “데이터 유형의 이름은 데이터를 그 유형으로 변환하는 함수”라고 설명한 것을 기억할지 모르겠다. 이것은 엄밀히 따지면 틀린 설명이다. ‘데이터 유형의 이름’은 사실 함수가 아니라 클래스다. 그리고 그 이름을 통해 수행되는 과정은 함수 호출이 아니라 인스턴스화다. 파이썬에서는 한 번 만들어진 객체의 유형을 바꿀 수 없다. 인스턴스화 과정에서 전달된 데이터 유형에 대응되는 새로운 객체(인스턴스)를 만들어내는 것일 뿐이다.

정리해보자.

  • 인스턴스: 어떤 클래스에 속하는 객체. 객체는 동시에 여러 클래스의 인스턴스일 수 있다. isinstance() 함수로 객체가 어떤 클래스의 인스턴스인지 확인할 수 있다.
  • 인스턴스 만들기: 문법에 정의된 특별한 표현(리터럴과 식)을 이용하거나 인스턴스화(클래스이름())를 수행해 클래스의 인스턴스를 만들 수 있다. 두 방법 모두 그동안 여러 번 사용해 보았다.

연습문제

연습문제 8-4 인스턴스 생성하기

클래스의 인스턴스화를 수행해, 다음 인스턴스를 생성해 보아라.

  • 원소가 없는 튜플(tuple)
  • 원소가 없는 집합(set)
  • 1 이상 10 미만의 자연수를 원소로 갖는 집합(set)
  • 사용자로부터 '1789'라는 텍스트를 입력받아, 그것을 해석한 정수(int)
  • 1789라는 정수를 나타내는 문자열(str)
  • 원소가 없는 리스트([])가 참인지 거짓인지 평가한 불리언(bool)