Wireframe

Python 101: 변수와 값의 유형

파이썬 초급 강좌 두 번째 시간으로 오늘은 변수와 값의 유형에 대해 살펴보고자 한다. 지난 시간에는 값을 출력해보는 방법에 대해 살펴보면서 문자열이 무엇인지 살펴보았고, 변수에 대해서도 간략하게 언급했었다. 오늘은 변수와 값에 대해서 이야기보려고 한다. 개인적으로 파이썬 기초 개념 중에서 가장 중요한 것을 이 글에서 다루게 되지 싶다.

프로그래밍에 관심이 있다면, 혹은 이 글을 읽기 전에 어떤 종류가 됐든 프로그래밍/코딩 관련 서적을 뒤적여 본 경험이 있다면 아니 적어도 학교에서 수학시간에라도 변수(variable)라는 말을 들어보기는 했을 것이다. 그리고 흐릿하게 나마 그 의미를 알고 있을지도 모르겠다. 만약 이전에 프로그래밍을 공부해본 경험이 있는 사람이라면 “변수는 값을 담는 그릇같은 것이야”라고 자신만만하게 그 뜻을 알려줄 수도 있을 것이다.

변수와 값에 대한 기본 원리

결론부터 이야기하자면, 지금부터 ‘변수는 그릇’이라는 개념은 머릿속에서 지워버리기 바란다. 파이썬의 개념 체계에서 가장 중요한 세 가지 원리가 있다. 지금 이 원칙들을 이해하기 어렵겠지만, 일단은 머리 속에 기억해두도록하자.

  1. 모든 값은 “객체”이다. 사실 파이썬에서는 모든 것이 객체이다.
  2. 변수는 객체 붙이는 이름표다. 파이썬에서는 ‘대입’이라는 개념이 없다.
  3. 어쨌거나 여러분은 이 개념을 이해하게 될 것이다.

변수가 ‘어떤 값을 담아두는 그릇’이라는 비유는 C를 비롯한 저수준 언어에서는 통용 가능한 개념이지만, 파이썬에서는 들어맞지 않는 메타포이다. 사실 어떤 언어가 됐든, 이러한 비유를 사용하여 개념이나 사실을 너무 평면적으로 만드는 것은 문제가 있다고 생각한다.

어쨌든 핵심 개념은 앞에서 언급했듯이 단, 두 개이다. 모든 값은 객체이고, 변수는 객체에 이름을 붙이는 것이다. 심지어 파이썬의 표준문서에서도 변수에 뭘 대입한다는 표현은 쓰지 않는다. 대신에 많은 “제대로”된 파이썬 서적이나 문서는 대입이라는 말 대신에 “바인딩”되었다는 표현(bound)을 많이 쓴다. 이는 단어 그대로 이름과 객체가 연결되었다는 뜻이다. 심지어 나는 이미 10년전에 이 연재를 처음 시작하면서 이 점을 강조해왔는데, 아직도 이러한 개념이 널리 사용되는 것 같지는 않아서 좀 안타까운 생각이 든다.

그러면 객체는 무엇일까? 객체는 어떤 상자나 가방이 있고 그 속에 어떤 값들과 기능들이 모여서 들어있는 것이라고 생각할 수 있다. 정수 1을 예로 들자면, 정수 1을 가리키는 객체는 실제 정수값 1은 물론, 정수라는 값이 가지는 공통적인 어떤 속성이나 기능, 정수라는 유형에 대해서 행할 수 있는 연산 같은 것들을 모두 담고 있는 가방이나 상자인 셈이다.

문자열을 생각하면 좀 더 이야깃거리가 생길 수 있을 것 같다. “hello”라는 문자열이 있다고 생각해보자. 일단 이 문자열은 'h', 'e', 'l', 'l', 'o' 의 다섯 글자가 모여서 만들어졌다. 즉 “문자열의 길이”라는 속성이 있을 것이다. 그리고 이 문자열 값으로부터 행할수 있는 몇 가지 동작이 있을 것인데, 소문자들로 구성된 문자열인 “hello”는 사실 자기 자신을 모두 대문자로 만드는 방법도 알고 있다. 마찬가지로 소문자로 만드는 방법도 알고 있을 것이고, 그 과정에 필요한 자기 자신이 소문자인지, 대문자인지 알아내는 방법도 알고 있다. 문자열을 좀 더 자세히 들여다 보는 시간에 다시 알게 되겠지만, 그 외에도 문자열이 스스로를 검사하는 여러 가지 수단이 있고, 문자열을 연결하거나, 반복하여 늘리거나 하는 등의 기능도 가지고 있다.

즉, 파이썬의 ‘값’이라는 건 단순히 숫자값을 표현하는 바이트의 연속은 아니며, 내부적으로 좀 더 복잡한 구조를 가지고 있으나 그것을 우리에게 드러내지 않을 뿐이다. 파이썬은 사용자에게 복잡함을 숨기려고 하지만, 아이러니하게도 그러한 파이썬을 제대로 이해하기 위해서는 파이썬이 감추려 드는 것들 어느 정도는 파고 들어서 알아 내야 한다.

타입

모든 객체는 타입이라는 정보를 가지고 있다. 타입은 유형이다. 이미 몇 번 언급된 바 있는 정수나 문자열은 딱 봐서 서로 성질이 다른 것 같다. 정수 1 과 숫자 “1”은 (물론 실생활에서 우리는 이것 역시 따로 구분하지 않기 때문에 혼동하고 있는 사람이 많을 것이지만….) 비슷하게는 생겼다하겠지만, 결코 아닌 것이다. 정수 1과 1은 서로 더하면 2가 되지만, 숫자(글자) “1” 은 “1” 과 더할 수나 있나? 아니면 정수 1에 숫자 “1”을 더할 수 있을까?

파이썬에는 이론적으로는 무수히 많은 타입이 있을 수 있지만, 여기서는 기본적으로 파이썬에서 제공하는 원시 타입 몇가지를 소개하고자 한다. 파이썬에서는 기본적으로 다음과 같은 값의 유형을 제공하고 있다.

숫자값들

int 타입은 기본적으로 정수를 말한다. 다른 언어의 정수와 달리 파이썬의 정수는 표현할 수 있는 범위에 제약이 없다. 예를 들어 C의 int 타입의 경우 4바이트로 그 크기가 정해져 있기 때문에 표현할 수 있는 값의 경우의 수가 2의 32승으로 제한된다. 이는 부호를 고려했을 때 -2,147,483,648 ~ 2,147,483,647 범위에 해당한다. 하지만 파이썬의 정수는 자리수에 제약을 받지 않는다. 따라서 50자리, 100자리 정수도 근사값이 아닌 참값으로 계산할 수 있다. 그렇게 큰 값이 무슨 필요가 있을까 생각이 들 수도 있겠지만, 피보나치 수열 같은 것만 만들어봐도 수십 자리 숫자가 등장하는 것이 금방이다.

소수점을 포함하는 실수는 float 타입이다. float 타입은 64비트 배정밀도 실수값이며, 크기가 제한되어 있으므로 당연히 근사값이다. 그리고 숫자에 접미사 j 를 붙여서 복소수를 표현할 수 있다.

파이썬에서 정수와 실수와 복소수는 기본적인 사칙연산 및 나머지, 거듭제곱 연산 등에서 호환된다. (계산된 결과는 더 넒은 개념의 값으로 확장된다. 예를 들어 정수 + 실수 = 실수) float의 경우 정밀도의 제약이 있으나, 사용자가 정의하기 따라서 정밀도를 조정할 수 있는 Decimal 이라든지, 아예 분수로 값을 다룰 수 있게 하는 Fractions 같은 모듈을 제공하고 있다.

문자열 및 바이트

문자열에 대해서는 지난 글에서 간단히 소개했다. 문자열을 하나 이상의 문자/문자들로 이루어진 텍스트 데이터를 표현한다. 사실 정수나 실수 같은 숫자값 형식보다 프로그래머가 더 많이 사용하는 데이터 유형이라 해도 무방하다. 문자열은 흔히 생각하는 것보다 훨썬 더 많은 조작이나 연산과 관련될 수 있고, 많은 분야 혹은 문제에서 기본적인 데이터 유형이 되기 때문에 가장 기본이 되고 중요한 데이터 타입 중 하나이다. 시간과 기회가 허락한다면 문자열과 관련된 예제나 퀴즈 같은 것을 많이 접하고 연습하기 바란다.

bytes 는 어떤 임의의 이진데이터를 어떠한 추상화 없이 그대로 다룰 때 사용하는 타입으로, 연속된 여러 바이트 그 자체이다. 사실 컴퓨터의 내부, 저 아래쪽 저수준의 세계에서는 정수, 실수, 문자열 등등 종류를 막론하고 모든 데이터와 코드가 이진 값으로 인식되며, 우리가 접하는 파이썬 코드에서는 ‘추상화’라는 방법을 통해서 여러 규칙을 덧데어서 구분해주고 있는 셈이다. bytes는 거의 문자열을 네트워크나 파일을 통해서 기록/전송하거나 저수준 이미지 처리와 같은 특수한 분야에서 접하게 될 것이며, 당분간은 직접 사용할 일은 없을 것이기 때문에, 이런 게 있다는 것 정도만 알아두면 되겠다.

부울 대수 (boolean)

부울 대수는 쉽게 말해서 참, 거짓이다. 파이썬에서 참 / 거짓은 True , False 로 표현하며, 이 때 각각의 첫 글자는 대문자로 표시한다. 파이썬 3에 와서 이 두 개의 단어는 키워드의 지위를 부여 받았다. (그래서 파이썬 2.x 에서는 True = 0 과 같은 코드를 사용해도 잘 돌아갔다. 부울 대수는 참/거짓을 구분하기 위해 정의되는 값인데, 반드시 bool 타입이 아니어도 ifwhile 구문의 조건 값이 될 수 있다. 다음의 몇 가지 경우를 제외하면 대부분의 파이썬 객체는 모두 “참”으로 평가된다.

복합 타입

복합 타입(역시 그냥 개인적으로, 임의로 구분하기 위해 붙인 이름이다.)은 그 자체로 어떤 완전한 값이 된다기 보다는 다른 객체를 포함하거나 필요로 하는 타입을 의미한다. 주로 여러 원소(element, item)을 포함하는 집합이 여기에 해당할 수 있는데, 다른 언어의 배열에 해당하는 리스트(list)나 집합(set), 사전(dict), 튜플(tuple)을 이런 복합으로 구분할 수 있다.

복합 타입은 대부분 다른 객체들을 아이템 혹은 원소로 삼아 포함하는 집합(collectioin) 혹은 컨테이너 성격의 타입이다. 일반적인 원시 값 타입의 객체들은 그 내부의 구성이 변경되지 않음에 비해, 복합 타입은 대부분 내부의 구성이 변경될 수 있다. (예외적으로 튜플을 생성된 후에 원소를 변경할 수 없다.). 복합 타입에는 다음과 같은 것들이 있는데, 다들 고유한 특성이 있고, 그 자체로도 하나의 자료 구조의 기능을 가지고 있기 때문에 나중에 각각의 타입에 대해서 보다 자세하게 다뤄 이야기할 예정이다.

변수

변수는 어떤 객체를 지칭하기 위해 붙이는 이름이다. 따라서 어떤 변수도 객체 그 자체가 되지는 않는 대신, 변수가 ‘평가’되어야 하는 시점에는 자신이 가리키고 있는 객체로 취급된다. 변수는 객체 그 자체가 아니므로, 파이썬에서는 객체에는 타입이 있지만, 변수에는 타입이 없다. (C언어에서는 어떤 변수가 int 타입인지, char 타입인지를 따지게 되고, 변수의 타입에 따라 변수의 사이즈가 달라진다.) 따라서 모든 변수는 가리키는 객체의 타입에 상관없이 모두 같은 크기를 가지며, 가리키는 객체의 타입에 제약이 없다.

a = "Hello" # a => str
a = 1       # a => int

위 예제에서 a 는 처음에는 문자열을 가리키는 변수였다가, 다음 어느 순간에는 정수를 가리키는 변수인 것으로 보인다. 이처럼 파이썬에서는 변수 자체가 어떤 타입을 가지지는 않으며, 변수에 타입을 지정하는 어떤 공식적인 문법도 존재하지 않느다.

변수의 스코프

변수는 선언된 범위에 따라서 참조 가능한 영역이 정해진다. 예를 들어서 어떤 함수 내부에서 정의된 변수가 있다면, 해당 변수는 함수의 동작이 끝난 시점에서는 파괴되었을 것이기 때문에 참조할 수 없을 것이다. 어떤 프로그래밍 언어이든 특정한 이름의 소코프에 관한 내용은 중요한 편이다. 다행히 파이썬은 이 문제에 있어서는 매우 단순하다. 왜냐하면 파이썬에는 단 2가지의 이름 범위만 존재하기 때문이다.

  1. 파이썬의 이름 공간은 모듈/함수 두 개만 존재한다. 모듈 이름 공간은 전역 변수가 정의되는 영역이며, 함수 이름 공간은 함수의 지역 변수가 정의되는 공간이다.
  2. 몇 번째 라인에 있든 상관없이, 함수 내에서 {변수} = {값} 과 같은 바인딩 구문(binding statemnet)를 사용했다면, 좌변에 있는 변수는 해당 스코프에서 정의된 것으로 간주된다.
  3. 어떤 이름(변수)를 참조한다면, 현재 액세스 가능한 지역 변수의 이름 공간에서 검색한다. 만약 존재하지 않는다면 전역 이름 공간에서 검색한다. 검색되지 않는 이름을 참조했다면 Unbound Error 예외가 발생한다.

변수의 스코프 관련해서는 따로 작성해놓은 글이 있으니, 참고하기 바란다. : [파이썬의 이름 공간과 변수의 스코프]

타입에 대한 약속

문자열을 화면에 출력할 때 print() 함수를 사용한다고 했는데, 이 함수에는 문자열 뿐만 아니라 1과 같은 정수나, 3.14 같은 실수, 혹은 리스트나 사전 등의 아무 타입의 객체를 전달해도 그 내용일 출력된다. 그렇다면 print() 함수는 어떻게 그렇게 다양한 타입들을 표현할 수 있는 것일까?

여기에는 한 가지 비밀이 숨어 있는데, 사실 print() 는 문자열만 출력할 수 있다는 점이다. 대신에 다른 객체들에게 “너 자신을 문자열로 변환해서 알려줘”라는 요청을 한다. 그러니까, 보통의 파이썬 객체는 자신을 문자열로 표현하는 방법을 알고 있고, print()는 그러한 성질을 이용한다는 것이다. 이 지점은 조금 미묘한데, print()는 전달되는 값의 타입이 무엇인지는 상관하지 않고 그저 “자신을 문자열로 표현할 수 있는 객체”이기만 하면 상관하지 않는다는 것이다.

이는 이러한 암묵적인 약속을 근거로, “출력가능한 모든 객체”라면 모두 어떤 공통적인 속성을 갖는다는 전제가 작동하고 있다는 것을 의미한다. 이렇게 타입에 무관하게 어떤 속성을 기대할 수 있는 것을 다른 언어에서는 ‘프로토콜’이라고 부른다. 파이썬에서는 이러한 프로토콜이 언어적 기능으로 지원되는 것은 아니기에 공식 문서 등에서는 따로 언급하지는 않지만, 내부적으로 마련된 프로토콜들이 있고 이에 의존해서 어떤 기능들을 제공하는 것 또한 사실이다.

이 장에서는 주로 데이터로서의 타입에 대한 기본적인 몇 가지를 소개했다. 하지만 파이썬에서는 그보다 훨씬 더 중요한 몇 가지 타입들이 있다. 이러한 추상적인 타입을 다루기에 앞서, 오늘 소개만 간략하게 했던 리스트나 사전과 같은 자료 구조와 관련 있는 타입들을 먼저 소개하고, 이어서 그런 타입들에 대해서도 소개해 보도록 하겠다. 다음 글을 기다려주시라.

Exit mobile version