'프로그래밍'에 해당되는 글 1건

  1. 2013.04.05 Duck Typing에 대하여. 2

Duck Typing에 대하여.

Coding 2013. 4. 5. 04:39

오리처럼 울고 오리처럼 걷고 오리의 깃털을 가진 축생은 오리라 할 수 있을까요?

카테고리가 코딩인데 뭔 뜬금없는 오리 이야기냐 하시겠지만, 일단 파이선에서는 그렇다고 합니다.

# coding=UTF-8[각주:1]

class Duck:
  def Quack(self):
    print "꽤애애애애애액!"
  def Feathers(self):
    print "이 오리는 녹색 깃털로 뒤덮여 있습니다."

class Person:
  def Quack(self):
    print "이 사람은 최대한 애처롭게 꽤애액 거립니다."
  def Feathers(self):
    print "이 사람은 임기응변으로 땅에 떨어진 오리의 깃털을 줍습니다."

donald = Duck()
jack = Person()

print "- 도날드의 경우 -"
donald.Quack()
donald.Feathers()
print ""
print "- 잭의 경우 -"
jack.Quack()
jack.Feathers()

이 코드를 실제로 돌려보면 이런 결과가 나옵니다.

1
2
3
4
5
6
7

- 도날드의 경우 - 꽤애애애애애액! 이 오리는 녹색 깃털로 뒤덮여 있습니다. - 잭의 경우 - 이 사람은 최대한 애처롭게 꽤애액 거립니다. 이 사람은 임기응변으로 땅에 떨어진 오리의 깃털을 줍습니다.

앞에서 "오리처럼 울고 오리처럼 걷고 오리의 깃털을 가진 축생"의 이야기를 꺼냈는데, 그러면 이 축생과 이 예의 공통점은 무엇일까요? 그렇습니다, 뭔가 미심쩍지만 Duck에게서 코더가 예상하고 있는 "Duck처럼 Quack할 수 있고 Duck의 Feathers를 갖고 있는" 특징을 그대로 가지고 있으니 Person도 Duck이라고 치는 것이지요. 이것을 보고 Duck typing이라고 합니다. 오리의 예를 들어서 성립된 개념이니까 Duck typing이라는 이름이 붙은 것이지요.[각주:2]

Duck typing이라는 용어가 생겨난 것 자체는 2000년으로 거슬러 올라간다고 합니다.[각주:3] 파이선 뉴스그룹에 이런 글이 올라온 것이 시초였죠.

그러니까, 그게 오리인지 검사하지 말고, 당신에게 오리의 무슨 행동이 필요한지에 따라 오리처럼 우는지, 오리처럼 걷는지, 등등, 등등 적절한 행동을 오리처럼 하는지 검사하세요.[각주:4]

- Alex Martelli, 2000

파이선은 인터페이스 같은 삽질을 하지 않고도[각주:5] 단지 클래스에서 적절한 함수의 이름을 불러주는 것만으로도 인터페이스 비스무리한 짓거리 내지는 인터페이스가 구현하고자 하는 바를 이룰 수 있습니다! 오오 파이선!

......이면 지금 이 포스팅의 제목이 파이선 강의가 되었어야겠죠.

저 Duck typing이라는 개념은 꽤 논란이 있는 개념입니다. 뭐 일단 앞에서 주구장창 말씀드렸다시피 다중상속이니 추상클래스 구현이니 같은 장대한 삽질을 하지 않고도 비슷한 일을 하는 함수가 이름이 같으면 코드를 그대로 컨트럴 씨브이를 시전해도 잘 돌아갑니다. 예를 들어 오리 클래스를 만들었으니 이번에는 오리랑 비슷하게 꽥꽥거리는 거위 클래스를 만들고 싶겠죠. 분명 이것도 꽥꽥거리고 깃털이 있지만 오리는 아니니 오리에서 상속시키기도 난감하고, 그래서 그냥 새로 하나 만들기로 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
# coding=UTF-8

class Duck:
  def Quack(self):
    print "꽤애애애애애액!"
  def Feathers(self):
    print "이 오리는 녹색 깃털로 뒤덮여 있습니다."

class Swan:
  def Quack(self):
    print "꽤애애애애애액!"
  def Feathers(self):
    print "이 거위는 매우 아름다운 흰 털에 싸여 있습니다."

이 거위(class Swan)도 물론 아까와 마찬가지로 울부짖을(?) 수 있습니다. 또, 갑자기 도날드의 종을 Swan으로 바꿔야 할 때도 다른 부분은 냅두고 donald = Duck() 부분만 donald = Swan()으로 바꿔준다면 쉽게 유지보수가 가능합니다.

이렇게 보면 기존 코드 재활용에도 좋고, 간편한게 덕 타이핑인 것 같습니다만, 문제는 오리처럼 울고 오리의 깃털을 가진 게 용일 때 발생합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# coding=UTF-8

class Dragon:
  def Quack(self):
    print "키에에에에에엥!"
    raise Exception("용이 빡쳐서 화염을 내뿜었습니다.")
  def Feathers(self):
    print "이 용은 오리의 그것과 닮은 녹회색 깃털을 달고 있습니다."

donald = Dragon()

print "- 용 도날드의 경우 -"
donald.Quack()
donald.Feathers()

보시다시피, Dragon은 분명 울기도 하고 깃털도 있지만, 이 놈의 울음소리는 예사 울음소리가 아니라 돌려보는 것 자체가 Exception이 됩니다(...). 이걸 돌려보면 아래와 같은 결과가 나타납니다.

1
2
3
4
5
6
7
8
- 용 도날드의 경우 -
키에에에에에엥!
Traceback (most recent call last):
  Line 13, in <module>
    donald.Quack()
  Line 6, in Quack
    raise Exception("용이 빡쳐서 화염을 내뿜었습니다.")
Exception: 용이 빡쳐서 화염을 내뿜었습니다.

깃털을 볼 새도 없이 용이 화염을 내뿜어서 프로그램이 조기종료되어버렸습니다! 이처럼 클래스가 Quack이란 메서드를 가지고 있긴 한데 그 Quack이 우리가 생각하는 그 Quack이 아니라면 저런 식으로 얼마든지 문제를 일으킬 소지가 있는 것입니다. 물론 Dragon에 대해 첨부된 문서를 보고 거기서 Quack에 대한 설명을 주의깊게 읽었다면 저런 문제를 일으킬 리가 없기는 합니다.

덕 타이핑 자체는 꽤나 편리한 개념이라 정적 타입 언어인 C#과 .NET 프레임워크에까지 도입되는 기염을 토하게 됩니다. dynamic이라는 키워드가 그것인데요, 예를 들어 dynamic q;를 선언해두고 q.Quack()을 호출하면 q라는 클래스에 인자 없는 Quack이라는 함수가 있다면 그걸 그대로 부르게 됩니다.

하지만 위에서 보여준 여러 가지 문제점들 때문에 저는 개인적으로 덕 타이핑을 쓰지 않습니다. 일단 덕 타이핑의 강점은 복붙이 편리하다는 점인데, 저렇게 문서 다 체크하고 앉아있으면 그 편의성도 결국에는 날려먹게 되죠. 또한, 함수를 호출하기 전에 최소한 그 함수가 속해 있는 클래스가 무엇인지 기본적으로는 알아야 한다는 것이 제가 프로그램을 짤 때 기본적인 철칙입니다. 오리의 비유를 다시 들자면, 그게 오리처럼 날아다니건 깃털이 오리같이 생겼건 오리과에 속해 있지 않으면 오리고 뭐고  무엇보다 결정적으로 제가 주로 쓰는 C#에는 dynamic 같은 짓 안 해도 인터페이스추상 클래스라는 신의 발명품이 있습니다.

interface IAnatidae[각주:6]
{
    public void Quack();
    public string Feathers { get; }
}

class Duck : IAnatidae
{
    public void Quack()
    {
        Console.WriteLine("꽤애애애애애액!");
    }

    public string Feathers
    {
        get { return "녹색 깃털"; }
    }
}

여기서 오리가 될지 뭐가 될지는 모르겠지만 오리과에 속하는 뭔가를 풀고 싶으면 IAnatidae의 변수를 하나 선언해주면 됩니다. 뭔진 모르겠지만 그건 IAnatidae의 Quack 메서드와 Feathers 속성을 그대로 가지고 있을 거거든요. 그리고 그걸 울게 만들고 싶다면 그 변수에서 Quack 메서드 한 방 때려주면 됩니다. 복붙도 쉽습니다. 상대가 같은 IAnatidae면 선언할 때 생성자 하나 바꿔주면 땡입니다. 물론 누군가 악의적으로 Dragon을 IAnatidae로 속여서 그걸로 문제를 일으키는 것까지 막지는 못합니다. 하지만 최소한 "오리처럼 운"다고 해서 용이 오리가 되지는 않습니다.

두 번째 문제는 역시 성능입니다. 덕 타이핑이 개입된다는 얘기는, 메서드나 속성의 이름을 가지고 함수를 찾아야 한다는 얘기입니다. 그러니까, 클래스를 본다→이름 표에서 "Quack"을 찾는다→Quack들 중에 인자를 받지 않으며 메서드인 것을 찾는다→찾아낸 메서드를 호출한다 라는 과정을 거쳐야 한다는 얘깁니다. 반면에 앞에서 말씀드린 인터페이스를 사용하는 언어에서는 클래스를 본다→IAnatidae 인터페이스에 정의된 Quack이 어디에 구현되어 있는지 주소표를 본다→구현된 메서드를 호출한다 라는 과정으로 이루어집니다. 앞에 비하면 빠르겠죠. 물론 그 성능차이를 씹어먹을 정도로 덕 타이핑이 편하면 모를까, 저는 앞의 이유로 덕 타이핑이 그 정도로 편하다고 생각하지는 않습니다.

이런 이유로 저는 덕 타이핑이란 개념 자체는 나쁘지 않다고 생각합니다만, 그것을 제가 실제로 사용하지는 않습니다. 여러분의 생각은 어떠신가요?

  1. 원래의 영어로 된 예제에서는 이 놈이 필요 없었습니다. 그러나 본 블로그의 주 언어는 한국어(...)이므로 설명을 위해 print 문에 한글을 쓰다보니 저 주석이 필요해지더군요. [본문으로]
  2. 저 "오리처럼 울고 오리처럼 걷는 축생"의 이야기는 사실 프로그래밍이라는 개념이 생기기 한참 전에 나온 "오리 테스트"라는 개념입니다. 귀납 논증을 까거나(오리처럼 걷는다고 다 오리냐?) 쉽게 이해시킬 때(오리처럼 걷고 오리처럼 우니까 새로운 오리의 종이 틀림없어) 사용되는 개념이죠. [본문으로]
  3. 출처 http://en.wikipedia.org/wiki/Duck_typing [본문으로]
  4. In other words, don't check whether it IS-a duck: check whether it QUACKS-like-a duck, WALKS-like-a duck, etc, etc, depending on exactly what subset of duck-like behaviour you need to play your language-games with. [본문으로]
  5. ......태클 거실 분들을 위해, 물론 파이선에도 인터페이스와 추상 클래스라는 개념은 존재합니다. 다만 인터페이스가 Duck typing보다 꽤나 늦게 도입된 편입니다. [본문으로]
  6. I오리과(......) [본문으로]
Posted by 애쉬군
,