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

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’는 ‘실례’라는 뜻이다. ‘사람’이라는 범주에 나라는 물리적 실체가 속하는 것처럼, 어떤 클래스에 속하는 실제 데이터(인스턴스)가 여러 개 존재할 수 있다.

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

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

개념 정리

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

연습문제

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

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

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