메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

한빛랩스 - 지식에 가능성을 머지하다 / 강의 콘텐츠 무료로 수강하시고 피드백을 남겨주세요. ▶︎

IT/모바일

파이썬 루프 Vs. 펄 루프 비교분석

한빛미디어

|

2002-09-30

|

by HANBIT

13,156

저자: 아쯔(Aahz), 역 전순재

본 기사는 오라일리 오픈 소스 회의 2002 튜토리얼인 "[펄] 프로그래머를 위한 파이썬"에 기본을 두고 있지만, 여기에서는 더 자세하게 펄과 파이썬을 비교하려고 한다. 본 튜토리얼은 펄에서 채록한 예제들 중에서 파이썬을 채택하지 않은 예제를 다루고 있으며 초보자가 아닌 경험있는 프로그래머들을 대상으로 하고 있다. 본 기사에서는 파이썬 루프(loop) 구조를 펄 루프 구조와 비교해 보겠다.

펄(Perl)에는 두 가지 기본적인 루프 구조가 있다. While과 for/foreach가 그것이다. 여기에서는 " until EXPR"이나 do...while과 같은 변종은 다루지 않는다. 또한 for/foreach에 형태가 다른 두 가지 종루가 있다는 사실도 다루지 않는다. 역시 파이썬에도 두 가지 루프 구조가 있다. While과 for가 그것이다. 펄과는 달리, 파이썬 루프는 변종이 없다. 그 대신, for 루프는 널리 적용되는 특수한 프로토콜을 사용한다. 펄과 파이썬 모두 연속하는 열에 대해 루프를 돌리는 기능적인 구조를 가졌지만, 이와 관련된 내용은 본 기사의 범위를 벗어나므로 본 기사에서는 언급하지 않을 것이다.

루프 구조에 변종이 있을 수 있는지의 여부가 펄과 파이썬 사이의 기본적인 차이를 보여준다. 펄의 모토는 TMTOWTDI(There"s More Than One Way To Do It, 목적을 달성하는 데는 여러 가지 길이 있다)이다. 반면에 파이썬은 이에 대항하는 모토를 내세우고 있다. "오직 한가지 길만 있다.(There"s Only One Way)"라고 내세우는 것처럼 말이다. 파이썬 모토는 파이썬 디자인 철학의 한 요소를 짧게 표현해 준다. "목적을 달성하는 데 가장 확실한 길은 (되도록이면 오직) 하나만 있어야 한다." 파이썬의 나머지 디자인 철학을 보려면, 파이썬 상호대화 인터프리터를 시동하고 (파이썬 2.1.2 이상) "import this"라고 타이핑해 넣으면 된다.

파이썬의 루프 구조가 실제로 어떻게 작동하는가? 기본적인 펄의 관용구에서부터 시작해 보자.
    while () {
        print;
    }
그리고 똑 같은 기능을 하는 파이썬 관용구와 이 코드를 비교해 보자.
    import sys
    for line in sys.stdin:
        sys.stdout.write(line)
여기에서 특히 더 주목해야 할 것은 파이썬이 while 루프 대신에 for 루프를 사용한다는 것이다. 이에는 다음과 같은 두 가지 이유가 있다.
  • 파이썬은 표현식 안에서 할당을 허락하지 않는다. 루프 구조의 일부로 할당을 하기 위해서는 반드시 for 루프를 사용해야 한다.
  • 파이썬 2.2에서는 반복자(iterators)을 도입했으며, 파이썬에서 파일 객체는 그 자체로 반복자이다. 반복자는 next() 메소드를 가진 객체이다. next() 메소드는 한 연속열의 요소들을 한 번에 하나씩 만들어 낸다. for 루프는 반복자와 작동하도록 디자인되어 있다.
파이썬 2.2 이전 버전에서라면, 위의 루프는 다음과 같이 작성되었을 것이다.
    import sys
    while 1:
        line = sys.stdin.readline()
        if not line:
            break
        sys.stdout.write(line)
일반적으로, 파이썬의 for 루프는 펄의 for/foreach 리스트 형태와 거의 똑같이 작동한다. 숫자로 된 연속열을 루프 하려면, 리스트를 하나 만들어야 한다.
    for i in range(10):
        print i
이 코드는 숫자를 0에서부터 9까지 인쇄할 것이다. 펄의 배열(arrays)처럼, 파이썬 리스트(lists)는 0을 기본으로 해서 첨자가 추가된다. 그리고 range() 함수는 그 점을 만족한다. range()가 실제 숫자로 된 리스트를 만들어내는지 증명해보기 위해, 파이썬 인터프리터를 시동해 보자.
    >>> range(10)
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
이것을 다음의 펄 표준 지표화(indexing) 루프와 비교해 보자.
    for ($i=0; $i<10; $i++) {
        print "$i\n";
    }
또는
    foreach $i (0..9) {
        print "$i\n";
    }
range() 함수가 최대 지표보다 하나 더 크게 값을 지정하는 법에 특히 주의하자. 이렇게 하면 더 쉽게 len() 함수를 사용할 수 있기 때문이다. 일반적으로, range() 함수는 전체 리스트를 만들어도 문제가 되지 않을 만큼 충분히 빠를 뿐만 아니라 메모리도 별로 소비하지 않는다. 그래도 걱정될 경우를 대비하여 파이썬에는 한 번에 숫자 하나씩 만을 만들어 내는 xrange() 함수가 구비되어 있다.

할당할 능력이 없다는 점만 제외하면, 파이썬 while 루프는 이와 동등한 펄 루프와 거의 동일하게 작동한다.

펄:
    $done = 0;
    while (!$done) {
        $input = getInput();
        if (defined($input)) {
            process($input);
        } else {
            $done = 1;
        }
    }
파이썬:
    done = False
    while not done:
        input = getInput()
        if input is not None:
            process(input)
        else:
            done = True
False는 파이썬 2.2.1에서 새로이 내장된 값이라는 것을 주목하자(파이썬 2.3에서, 불린(boolean) 연산은 1/0 대신에 True/False를 반환하게 될 것이다). 일반적으로, 빈 값이라면 참값으로 false를 생성한다. False, None, 0, "", (), [], {}, 그리고 클래스 실체가 __nonzero__() 메소드나 또는 __len__() 메소드로 평가되면 0을 반환한다.

펄에서 getInput() 함수가 스칼라(scalar) 대신 해시(hash)나 배열(array)을 반환하기 시작하면, while 루프가 수정되어야 한다는 점에 주목하자. 반면, 파이썬은 계속해서 사전이나 리스트에 잘 작동할 것이다. 이는 파이썬에서 모든 것은 참조 의미구조(reference semantics)로 달성되기 때문이다(파이썬에서는 참조점들을 직접적으로 접근하지 않기 때문에 "바인딩(binding)"라고 불린다). 명시적으로 참조(references)를 사용하면 펄에서도 똑같은 효과를 얻을 수 있다.

게다가 반복자(iterators)외에도 파이썬 2.2에서는 발생자(generators)도 추가되었다. 발생자(Generators)는 반복자(iterator)를 반환하는 함수로서 일종의 재개가능한 중단(resumable closures)과 같은 것이라고 생각하면 도움이 될 것이다. 다음은 grep을 구현한 서브셋이다:
    from __future__ import generators
    import sys
    import re

    def grep(seq, regex):
        regex = re.compile(regex)
        for line in seq:
            if regex.search(line):
                yield line
"yield" 키워드는 평범한 함수를 발생자(generator)로 변화시킨다. 발생자는 그 발생자를 포장한 반복자 하나를 반환한다. yield가 실행될 때마다 ("return"이 그런 것과 마찬가지로, 결과를 반환할 때마다), 발생자(generator)는 멈추지만, 그 반복자의 next() 메소드를 다시 또 호출하는 동안, (모든 지역 변수들을 포함하여) 발생자 함수의 스택 프레임은 그대로 유지된다. 반복자는 자신의 next()가 맨 처음 호출되기 전까지 발생자의 어떤 코드도 실행하지 않는다.

이야기를 다음의 구체적인 예제로 되돌려 보자. grep()을 호출하면 반복자를 하나 반환한다. 반복자의 next() 메소드를 호출할 때마다 일치된 것을 하나씩 반환한다. 이런 일은 for 루프 내부에서 묵시적으로 일어난다.
     regex = r"\s+\w+\s+"
     for line in grep(sys.stdin, regex):
         sys.stdout.write(line)
이 모든 것의 이점은 간단하고, 명료하며, 효율적이라는 것이다. 아주 쉬운 코드를 작성할 수 있다. 그러나 반환하기 전에 전체 리스트를 만들어서 메모리를 먹어 치울 필요는 없다. 발생자(Generators)들은 파이프라인 처리가 가능하다. 다른 유닉스 유틸리티를 다시 만든다고 생각해 보자. uniq과 같은 유틸리티 말이다. 소스가 리스트임에도 불구하고, 적어도 임시 리스트를 만들 필요가 없다.

"yield"가 파이썬 2.2에서는 새로운 키워드이기 때문에 "from __future__" 서술문이 필요하며 그것에 접근하려면 명시적으로 접근되어야 한다. 파이썬 2.3이 배포되면, "from __future__ import generators"는 더 이상 필요하지 않게 될 것이다. 그러나 그대로 유지하더라도 아무해도 없을 것이다 (2.2이상의 어느 파이썬 버전 하에서도 코드 실행이 허용된다).

펄 코드를 파이썬으로 변환하는 것에 관하여 더 자세한 정보가 필요하면, Python/Perl Phrasebook을 참조하자. (이 책은 7년이나 되었지만, 지금도 아주 쓸모있다.)

한참 구식이 된 나의 펄 지식을 재충전 시켜준 캐시 멀리칸(Cathy Mullican, menolly@spy.net)에게 특별 감사의 말을 전한다.
아쯔(Aahz)는 파이썬으로 프로그래밍을 3년 이상 해오면서 파이썬을 사용하는 법을 사람들에게 즐겁게 가르치고 있다.
TAG :
댓글 입력
자료실

최근 본 상품0