원래 type hinting을 주제로 글을 쓰려다가, 타입 추론에 대해 글을 써내려가다 보니 이거 하나만으로도 글 하나가 대충 완성될 것 같았다. 그래서 이번에는 그냥 타입 추론 이야기를 해보려고 한다.

타입 추론

Python은 타입 검사가 동적이기 때문에, 모든 타입이 컴파일 타임이 아니라 런타임에 결정된다.

a = 1
b = 'planb'
c = 3.0

컴파일 타임에는 세 변수 모두 타입을 모르는 상태였다가, 코드가 실제로 실행되는 런타임에 statement들이 하나씩 해석되며 aint로, bstr로, cfloat로 추론된다. 타입 추론이 가능한 JavaScriptKotlin같은 언어에서 변수의 선언문 앞에 붙이는 var, val, const같은 변수 선언용 예약어가 없다는 점이 특징인데, 이 때문에 초기값 명시 없는 변수 선언이 불가능하다. 예를 들어 JavaScript의 경우, 아래와 같이 변수의 선언초기화를 두 개의 독립적인 statement로 나눌 수 있다.

var a;
a = 3;

그러나 Python은 초기값 없이 변수를 선언하는 것이 불가능하다.

a  # -> 여기서 'a'가 정의되어 있지 않다는 NameError가 발생
a = 3

그래서 '변수가 선언되었으나 초기화는 되지 않았다'를 표현하기 위해, None을 초기화 값으로 사용하곤 한다.

a = None
a = 3

사실 JavaScript도 정의만 해 둔 변수는 자동으로 초기값이 undefined로 들어가는 것을 생각하면 이와 비슷한 맥락이기는 하다. Python은 초기값을 자동으로 넣어준다는 일이 불가능한데, 이는 a가 변수 선언식임을 확정지을 수 없기 때문이다. 조금 더 풀어서 설명하자면,

JavaScript의 var a;에는 var라는 변수 선언을 위한 예약어가 있기 때문에 컴파일러가 당연히 변수 선언에 관한 instruction을 수행할테고, '초기값이 없으니 undefined로 채운다'라는 논리까지 수행하도록 언어의 스펙을 만들어낼 수 있게 된다. 초기값 명시 없는 변수 선언이 가능한 것이다. 그러나 Python의 a는 이게 변수 선언을 의미하는 것인지, 어딘가에 선언되어 있을 a접근하는 expression을 의미하는 것인지를 판단할 수 없으니, 초기값 명시 없이 변수를 선언하는 것이 불가능한 것이다.

사고방식의 전환

나는 파이썬을 하기 전까진 Java를 했었는데, 파이썬을 시작하고 나서 가장 까다로웠던 게 클래스였다. 그 이유는 Java와 파이썬이 인스턴스 변수를 다루는 방식이 달라서였는데, Java의 예는 다음과 같다.

public class Person {
  private String _name;
  private int _age;

  public Person(String name, int age) {
    this._name = name;
    this._age = age;
  }
}

클래스에서 사용할 인스턴스 변수를 class level에 미리 정의해놓고, this로 접근하는 방식이다. Python에서는 아래처럼 작성한다.

class Person:
  def __init__(self, name, age):
    self._name = name
    self._age = age

class level에 무언가를 따로 정의해두지 않았음에도 불구하고, self에 잘 들어간다. 이는 파이썬에서 모든 객체가 dynamic attribute기 때문에 가능한 일이다.

class Tmp:
  pass

t = Tmp()
t.a = 3
t.b = 4
print(t.a) # 3