Perl의 Attribute (2)

| | Comments (2) | TrackBacks (0)
앞선 포스팅 을 통해서 Perl의 attribute를 소개했는데 attributes 모듈을 통해서 어떤 작업을 하려면 무척이나 복잡하고 귀찮을 경우가 많다. 따라서 이러한 작업을 쉽게 해주는 모듈이 나왔는데 그것이 Attribute::Handlers 라는 모듈이다.

attributes모듈과 Attribute::Handlers 라는 모듈은 모두 Perl이 배포될 때 기본적으로 포함되는 CORE모듈이므로 별도로 모듈을 설치할 필요가 없다.

Attribute::Handlers 모듈은 내부적으로 attribute를 사용할 때 필요한 MODIFY_[TYPE]_ATTRIBUTES 같은 서브루틴 생성과 심볼테이블 검색 및 캐싱을 자동적으로 해주기 때문에 이전의 예제에서 봤던 복잡한 작업의 수고를 덜어준다.

그러면 Attribute::Handlers 모듈의 기본적인 골격을 통해 어떻게 동작하는지 보도록 하자.

<예제코드>

#!/usr/bin/perl
use strict;
use warnings;
use Attribute::Handlers;

# MyAttr attribute가 오면 호출되는 서브루틴을 다음과 같이 정의
sub MyAttr : ATTR {
    my ($package, $symbol, $referent, $attr, $data, $phase) = @_;
    print "@_\n";
    print "\$package : $package\n";
    print "\$symbol name: ",*{$symbol}{NAME},"\n";
    print "\$referent :",$referent,"\n";
    print "\$attr : $attr\n";
    print "\$data (dereference): @$data\n";
    print "\$phase: $phase\n";
    print "-------------------------------\n";
}

sub test : MyAttr(qw/arg1 arg2/) {
    print "test\n";
}

<결과>
main GLOB(0x680a30) CODE(0x604f90) MyAttr ARRAY(0x65e050) CHECK
$package : main
$symbol name: test
$referent :CODE(0x604f90)
$attr : MyAttr
$data (dereference): arg1 arg2
$phase: CHECK

결과를 보면 알겠지만 attribute를 정의할 때 넘어가는 인자는 차례로 attribute가 정의된 패키지 $package , attribute가 정의된 대상 typeglob의 레퍼런스(여기서는 \*test) $symbol, attribute가 정의된 것의 레퍼런스(여기서는 test 서브루틴의 레퍼런스) $referent , attribute의 이름 $attr , attribute에 부가적으로 정의해준 데이터들의 레퍼런스 $data, Perl의 실행 시 어떤 단계에서 적용할 것인가를 말하는 $phase(지정하지 않으면 기본으로 CHECK)  가 넘어감을 알 수 있다.

어떤 attribute를 처리하는 sub MyAttr : ATTR  식의 서브루틴을 정의할 때 어떠한 변수타입이나 서브루틴에만 적용되도록 다음처럼 명시적으로 지정할 수 있으며

sub MyAttrSub : ATTR(CODE)     # 서브루틴(코드)에만 적용되는 MyAttrSub attribute를 정의
sub MyAttrScalar : ATTR(SCALAR) #  스칼라에만 적용되는 MyAttrScalar attribute를 정의

다음과 같이 Perl 실행 시 어떤 단계(phase)에서 적용될지를 추가적으로 명시적으로 지정할 수도 있다.

sub MyAttrSub : ATTR(CODE,CHECK)
sub MyAttrScalar : ATTR(SCALAR,BEGIN)
sub SomeAttr : ATTR(HASH,BEGIN,END)    #혹은 여러단계에 적용하도록도 가능

* BEGIN,CHECK등 Perl이 실행될 때 적용되는 단계(phase)는 http://perldoc.perl.org/perlmod.html#BEGIN%2c-UNITCHECK%2c-CHECK%2c-INIT-and-END 을 참고

만약에 다음과 같이 :으로 시작하는 attribute가 여러 개 나온다면

sub setup : Chained('/') : PathPart('foo') : Args(1) {..}

:이 등장할때 마다 Chained, PathPart, Args attribute에 대해 각각 호출이 일어나게 된다.

Attribute::Handlers 모듈은 이 이외에도 더 많은 기능을 지원하는 데 자세한 내용은 Attribute::Handlers 모듈 문서를 참고하도록 하자.

그러면 이제 응용해서 어떤 서브루틴에 _log 라는 attribute를 정의하면 서브루틴의 실행전과 후에 로그를 찍는 프로그램을 만들어 보자.

<예제 프로그램>

#!/usr/bin/perl
use strict;
use warnings;

use constant DEBUG => 1;

use Attribute::Handlers;

sub _log : ATTR(CODE) {
    my ($pkg, $sym, $code) = @_; # 여기서 필요한 건 앞의 3개의 인자 뿐

    if( DEBUG ) {
        my $name = *{ $sym }{NAME};

        no warnings 'redefine';

        *{ $sym } = sub {
            print "Entering sub $pkg\:\:$name\n";
            my @ret = $code->( @_ );   # $code 코드(서브루틴) 레퍼런스를 통해 원래 서브루틴 실행
            print "Leaving sub $pkg\:\:$name\n";
            return @ret;
        };
    }
}

sub do_something : _log {
    print "I'm doing something.\n";
}

do_something();

<결과>

Entering sub main::do_something
I'm doing something.
Leaving sub main::do_something

결과를 보면 do_something 서브루틴의 호출 전과 후에 원하는 대로 로그 메시지가 찍혔음을 볼 수 있다.

그럼 마지막으로 이전에 복잡하게 구현했던 어떤 서브루틴의 모든 출력을 소문자로 만들어주는 Quiet attribute의 반대가 되는 모든 출력을 대문자로 만들어주는 Loud 라는 attribute를 Attribute::Handlers 모듈을 통해서 구현해 보자.

<Loud.pm>

package Loud;
use strict;
use warnings;
use IO::Capture::Stdout;
use Attribute::Handlers;

sub Loud :ATTR(CODE) {
    my ($package, $symbol, $referent, $attr, $data, $phase) = @_;
    no strict 'refs';
    no warnings 'redefine';

    # redefines subroutines given the "Loud" attribute
    *{$package.'::'.*{$symbol}{NAME}} = sub {

        my $capture = IO::Capture::Stdout->new();
        $capture->start();

        # call the original subroutine
        my @rets = $referent->(@_);

        $capture->stop();
        foreach ( $capture->read ) { print "\U$_" }
        return @rets;
    }
}

1;

<Loud_test.pl>

#!/usr/bin/perl
use strict;
use warnings;
use base qw/Loud/;

# We want this function to be loud
sub foo : Loud {
  print "Purple is a nice color.\n";
  print "Hello, World !\n";
}

foo();

<결과>

PURPLE IS A NICE COLOR.
HELLO, WORLD !

Loud.pm 파일을 보면 알겠지만 Quiet.pm을 만들때 수동으로 해주었던 많은 dirty한 작업들 없이 원하는 바만 깔끔하게 구현 할 수 있음을 볼 수 있다.

사실 attribute라는 건 사용자 입장에는 자기가 이해하고 새로 만들 것이 아니면 원 개발자가 정의해준대로 그냥 사용해도 상관없다. 하지만 그 내부동작을 이해하고자 노력하는 과정에서 배우고 얻는게 많으므로 충분히 분석해볼 만한 가치가 있다고 본다.

추가로 Catalyst 같은 Perl 기반의 유명 웹 프레임웍에서는 attribute가 어떠한 과정을 거쳐서 어떻게 쓰이는지 궁금하다면 일본의 Yappo씨가 블로그에 올린 포스팅을 참고하면 된다.
네이버 인조이 재팬을 통한 번역: Catalyst의 Attributes가 초기화될 때 까지