이 절에서는 클래스와 객체의 정의를 알아본다. 클래스와 객체를 이해하면 파이썬에서 데이터를 관리하는 원리도 알 수 있다. 조금 어려울 수도 있지만, 중요한 내용이니 잘 읽어보자. 지금까지 학습을 이어온 여러분이라면 충분히 이해할 수 있을 것이다.

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'>

type() 함수의 반환 결과를 읽는 법은 4.5절에서 설명했다. 예를 들어, <class 'int'>는 데이터가 int 유형이라는 뜻이다. 그런데 type() 함수가 반환하는 이 데이터 자체는 무엇일까? 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)라는 단위로 메모리 위의 정보를 관리한다.

객체에는 값(value)·유형(type)·정체성(identity)이라는 세 특성이 있다. 값은 메모리에 기록된 내용이다. 가변 객체는 값이 바뀔 수 있지만 불변 객체는 값이 바뀌지 않는다. 유형은 데이터의 종류로, 유형에 따라 그 값을 어떻게 읽고 다루어야 할지가 결정된다. 정체성은 각각의 객체를 식별하기 위한 고유번호로, 객체가 메모리 속에서 위치한 주소 값이기도 하다. 값과 유형이 동일한 데이터가 데이터가 메모리 공간에 여러 개 존재할 수 있지만, 이들은 서로 별개의 객체이며 정체성이 서로 다르다.

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

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

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

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

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

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

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

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

>>> new_year = 1789               # 다른 객체

>>> year == new_year              # 두 객체의 값 비교
True

>>> type(year) == type(new_year)  # 두 객체의 유형 비교
True

>>> id(year) == id(new_year)      # 두 객체의 정체성 비교
False

>>> same_year = year              # 동일한 객체를 다른 변수에 대입하면,
>>> id(year) == id(same_year)     # 두 변수가 같은 객체를 가리킨다.
True

일반적으로는 객체의 값을 기준으로 비교하는 경우가 대부분이다. 그리고 객체의 정체성을 서로 비교해야 하는 경우는 드물다. 불변 데이터를 다룰 때는 객체의 값과 유형이 동일하면 같은 데이터라고 봐도 문제가 되지 않는다.

한편, 객체는 변수와 다르다는 점도 짚어 두자. 객체를 변수에 대입해 둘 때가 많으므로 변수를 곧 객체로 오해하기가 쉽다. year = 1789라고 대입해 두었을 때, 1789라는 값을 갖는 어떤 객체가 있고, year라는 변수가 그 객체를 가리킬 뿐이다. year 변수를 평가하면 그 객체를 구할 수 있으나, 그 객체는 그 변수가 아니고 그 변수 또한 그 객체가 아니다. 실체와 이름을 혼동하면 안 된다. 변수는 객체에 붙인 이름이고, 객체 하나에 이름을 여러 개 붙여둘 수도 있다.

개념 정리

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

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

연습문제

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

다음 질문에 답해 보라.

  1. 1 == 1.0을 평가하면 참이다. 그렇다면 11.0은 동일한 객체인가?
  2. type(a) == type(b)가 참이면, a == b도 참인가?
  3. id(a) == id(b)가 참이면, type(a) == type(a)a == b도 참인가?
  4. type(a) == type(b)a == b가 참이면, id(a) == id(b)도 참인가?

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

객체가 어떤 클래스에 속할 때, 그 객체를 그 클래스의 인스턴스(instance)라고 부른다. 영어 단어 ‘instance’는 ‘사례’라는 뜻이다. 어떤 범주에 속하는 사례는 여러 가지가 될 수 있듯이, 클래스에 속하는 인스턴스도 여러 개 존재할 수 있다. 1, 2, 3, 4는 모두 int 클래스의 인스턴스다.

객체가 인스턴스인지 검사하기

검사하려는 객체와 클래스가 정해져 있을 때, isinstance() 함수로 객체가 그 클래스에 속하는지(인스턴스인지) 검사할 수 있다.

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

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

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

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

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

객체 하나의 유형은 클래스 하나로 정해져 있다. type() 함수는 하나의 값만을 반환한다. 그런데 한 객체는 동시에 여러 클래스의 인스턴스일 수 있다. 범주에 계층이 있기 때문이다. 다음 식을 생각해보자.

사람 ⊂ 동물 ⊂ 생물

여러분은 사람이라는 범주에 속한다. 그런데 여러분은 또한 동물이며 생물이기도 하다. 사람은 동물이라는 더 넓은 범주에 속하고, 동물은 생물이라는 더 넓은 범주에 속하기 때문이다. 파이썬 방식으로 표현하면, 여러분은 ‘사람’ 클래스의 인스턴스이고, 동시에 ‘동물’ 클래스와 ‘생물’ 클래스의 인스턴스이기도 하다.

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

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

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

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

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

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

인스턴스 만들기

파이썬의 모든 데이터는 객체이고, 모든 객체는 어떤 유형(클래스)에 속한다. 즉, 파이썬의 모든 데이터는 인스턴스다. 파이썬에서 데이터를 생성하는 것은 곧 인스턴스를 생성하는 것이다.

인스턴스를 만드는 방법에는 크게 세 가지가 있다. 첫번째 방법은 파이썬 코드를 평가하면 바로 객체가 되는 것이다. 수, 문자열 같은 기본 데이터 유형은 코드를 평가하면 그대로 객체가 된다. 예를 들어, 1이라는 코드는 수 1을 나타내는 객체로 평가된다. 이런 코드를 리터럴(literal)이라고 부른다. ‘literal’은 ‘문자 그대로’라는 뜻이다.

코드 8-16 리터럴을 평가해 인스턴스 만들기

>>> 1789      # 정수 리터럴: 정수 인스턴스가 만들어진다
1789

>>> 3.1415    # 실수 리터럴: 실수 인스턴스가 만들어진다
3.1415

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

>>> '파이썬'  # 문자열 리터럴: 문자열 인스턴스가 만들어진다
'파이썬'

인스턴스를 만드는 두번째 방법은 식을 평가하는 것이다. 식을 평가하면 결과가 나오는데, 그 결과는 데이터다. 즉, 식은 인스턴스를 만들어낸다. 그동안 몰랐을 뿐, 여러분은 이 책을 학습하는 과정에서 수많은 인스턴스를 만들어 본 셈이다.

코드 8-17 식을 평가해 인스턴스 만들기

>>> 5 * 5             # 산술 식: 수 인스턴스 만들기
25

>>> [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]

클래스 이름에 괄호를 붙여 실행하면, 괄호 속에 전달한 인자에 맞는 인스턴스가 생성된다. 마치 함수 호출처럼 보이지만 함수 호출과는 다르다. 4.5절에서 “데이터 유형의 이름은 데이터를 그 유형으로 변환하는 함수”라고 설명했다. 이것은 엄밀히 따지면 틀린 설명이다. ‘데이터 유형의 이름’은 사실 함수가 아니라 클래스다. 그리고 그 이름을 통해 수행되는 과정은 함수 호출이 아니라 인스턴스화다. 파이썬에서는 한 번 만들어진 객체의 유형을 바꿀 수 없다. 인스턴스화 과정에서 전달된 데이터 유형에 대응되는 새로운 객체(인스턴스)를 만들어내는 것일 뿐이다.

개념 정리

  • 인스턴스: 어떤 클래스에 속하는 객체. 객체는 동시에 여러 클래스의 인스턴스일 수 있다.
  • isinstance() 함수로 객체가 어떤 클래스의 인스턴스인지 확인할 수 있다.
  • 인스턴스 만들기: 리터럴·식을 평가하거나 인스턴스화(클래스이름())를 수행해 클래스의 인스턴스를 만들 수 있다.

연습문제

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

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

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