앞선 포스팅 을 통해서 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가 초기화될 때 까지