October 2008 Archives
컴퓨터로 작업을 하다 보면 여러 대의 컴퓨터에 동시에 로그인해서 같은 작업을 해야 하는 경우가 많다.
UNIX/Linux의 X-windows상이면 예전에도 소개한 적이 있는 google에서도 쓴다는 clusterssh 같은 것을 이용할 수 있으나 현실적으로 회사에서 업무용으로 Windows 운영체제를 많이 사용하므로 X터미널이나 VMware를 통해 리눅스를 사용하거나 하지 않으면 쓰기 어렵다. 그래서 Windows용으로는 비슷한 것이 없는지 한 번 찾아보았는데...
PuTTYCS라는 아주 작고 멋진 프로그램을 발견했다. (google에서 한글페이지를 검색해보니 소개한 곳이 하나도 없는 것으로 봐서 한글 싸이트로는 제일 먼저 소개하는 것이라 생각됨) 이것은 공개 SSH클라이언트인 putty를 여러 개 띄우고 PuTTYCS에서 입력을 하면 모든 창에 같은 입력이 전달되어 동시에 같은 작업을 할 수 있게 해주는 프로그램으로 창들을 cascade,tile등의 형태로 정렬도 가능하고 filter를 통하여 특정 그룹의 창에만 작동하도록도 할 수 있다.
홈페이지: http://www.millardsoftware.com/ (왼쪽에서 PuTTYCS 메뉴를 선택하고 다운로드 하면 된다.)
사용법은 스크린샷을 올리고 구구절절 침을 튀겨가며 말하지 않아도 기본적 IT 소양을 갖춘 사람이라면 금방 사용할 수 있을 거라 생각됨~
UNIX/Linux의 X-windows상이면 예전에도 소개한 적이 있는 google에서도 쓴다는 clusterssh 같은 것을 이용할 수 있으나 현실적으로 회사에서 업무용으로 Windows 운영체제를 많이 사용하므로 X터미널이나 VMware를 통해 리눅스를 사용하거나 하지 않으면 쓰기 어렵다. 그래서 Windows용으로는 비슷한 것이 없는지 한 번 찾아보았는데...
PuTTYCS라는 아주 작고 멋진 프로그램을 발견했다. (google에서 한글페이지를 검색해보니 소개한 곳이 하나도 없는 것으로 봐서 한글 싸이트로는 제일 먼저 소개하는 것이라 생각됨) 이것은 공개 SSH클라이언트인 putty를 여러 개 띄우고 PuTTYCS에서 입력을 하면 모든 창에 같은 입력이 전달되어 동시에 같은 작업을 할 수 있게 해주는 프로그램으로 창들을 cascade,tile등의 형태로 정렬도 가능하고 filter를 통하여 특정 그룹의 창에만 작동하도록도 할 수 있다.
홈페이지: http://www.millardsoftware.com/ (왼쪽에서 PuTTYCS 메뉴를 선택하고 다운로드 하면 된다.)
사용법은 스크린샷을 올리고 구구절절 침을 튀겨가며 말하지 않아도 기본적 IT 소양을 갖춘 사람이라면 금방 사용할 수 있을 거라 생각됨~
요즘 그냥 재미로 Catalyst 라는 Perl로 만든 웹프레임웍을 들여다보고 있는데( 이걸 사용해서 뭘 만들겠다는 건 아니고 그냥 어떤 식으로 구현되어 있나, 내부적인 구조에 어떤 테크닉을 사용하고 있느냐가 궁금해서~ ) 코드 체계를 분석하려니 Catalyst에 사용되는 각종 객체들의 상속관계부터 파악해야 뭔가 기본적인 구조가 잡힐 것 같아서 혹시 이러한 클래스간의 상속관계를 그래프나 다이아그램으로 그려주는 모듈은 없는지 CPAN을 뒤져 보았다.
그 결과 찾은 모듈이 UML::Class::Simple 모듈!!!
UML::Class::Simple 모듈을 설치한 후 Catalyst Tutorial에 있는 MyApp 라는 예제 뼈대 웹 어플리케이션을 생성하고 UML::Class::Simple 모듈을 설치하면 생기는 명령어인 umlclass.pl 이란 명령으로 다음과 같이 명령을 내렸다.
>umlclass.pl -M Catalyst -M MyApp -I ./MyApp/lib/ -I /usr/share/perl5 -p "^(Catalyst|MyApp)" -o catalyst.png
명령옵션에 대한 자세한 설명은 http://search.cpan.org/dist/UML-Class-Simple/ 의 문서를 참고
그 결과는 ?
<크게 보려면 그림을 클릭>

알흠답지 아니한가?
그 결과 찾은 모듈이 UML::Class::Simple 모듈!!!
UML::Class::Simple 모듈을 설치한 후 Catalyst Tutorial에 있는 MyApp 라는 예제 뼈대 웹 어플리케이션을 생성하고 UML::Class::Simple 모듈을 설치하면 생기는 명령어인 umlclass.pl 이란 명령으로 다음과 같이 명령을 내렸다.
>umlclass.pl -M Catalyst -M MyApp -I ./MyApp/lib/ -I /usr/share/perl5 -p "^(Catalyst|MyApp)" -o catalyst.png
명령옵션에 대한 자세한 설명은 http://search.cpan.org/dist/UML-Class-Simple/ 의 문서를 참고
그 결과는 ?
<크게 보려면 그림을 클릭>

알흠답지 아니한가?
http://kldp.org/node/98881#comment-462014
http://web.suapapa.net:8080/wordpress/?p=391
에서 사랑의 이름궁합을 Python으로 만들 걸 보고 재미있겠다 싶어 Perl로 한 번 만들어 봤음~
suapapa님은 ㅈ를 2획 ㅊ을 3획으로 했던데 http://www.happyname.co.kr/name/index_name_08.htm 을 보면 ㅈ는 3획 ㅊ는 4획 이 맞는 것 같아서 고침.
온라인상에서 해보기 -> http://aero.sarang.net/love
참고:
Lingua::KO::Hangul::Util 모듈
http://www.kfunigraz.ac.at/~katzer/korean_hangul_unicode.html
<LoveMatching.pm 모듈>
<LoveMatching_test.pl 테스트코드>
<결과>
>perl LoveMatching_test.pl
이 강 명 만 박 수
[2 ,4 ,7 ,6 ,7 ,4]
[6 ,1 ,3 ,3 ,1]
[7 ,4 ,6 ,4]
[1 ,0 ,0]
100 %
<명령행에서 실행 예>
>perl -MLoveMatching=match_by_name -Mutf8 -e 'match_by_name("연정훈","한가인")'
연 한 정 가 훈 인
[5 ,6 ,6 ,3 ,6 ,3]
[1 ,2 ,9 ,9 ,9]
[3 ,1 ,8 ,8]
[4 ,9 ,6]
[3 ,5]
35 %
http://web.suapapa.net:8080/wordpress/?p=391
에서 사랑의 이름궁합을 Python으로 만들 걸 보고 재미있겠다 싶어 Perl로 한 번 만들어 봤음~
suapapa님은 ㅈ를 2획 ㅊ을 3획으로 했던데 http://www.happyname.co.kr/name/index_name_08.htm 을 보면 ㅈ는 3획 ㅊ는 4획 이 맞는 것 같아서 고침.
온라인상에서 해보기 -> http://aero.sarang.net/love
참고:
Lingua::KO::Hangul::Util 모듈
http://www.kfunigraz.ac.at/~katzer/korean_hangul_unicode.html
<LoveMatching.pm 모듈>
package LoveMatching;
use strict;
use warnings;
use utf8;
use Lingua::KO::Hangul::Util qw/:all/;
use base qw/Exporter/;
use version; our $VERSION = qv("0.0.1");
our @EXPORT_OK = qw/match_by_name/;
sub match_by_name {
my ($name1,$name2) = @_;
my @name1chars = split //, $name1;
my @name2chars = split //, $name2;
my @namepool;
while (@name1chars) {
push @namepool, shift @name1chars;
push @namepool, shift @name2chars;
}
binmode STDOUT, ':encoding(UTF-8)';
print "@namepool\n";
my @cntpool = map { get_stroke_count($_) } @namepool;
print love_sum( @cntpool ), " %\n";
}
sub love_sum {
my @cnts = @_;
print "[", join(' ,', @cnts),"]\n";
if ( (join '',@cnts) > 100 ) {
my @newcnts;
foreach ( 0 .. $#cnts-1 ) {
push @newcnts, ($cnts[$_]+$cnts[$_+1]) % 10;
}
return love_sum(@newcnts);
}
else {
return (join '', @cnts)+0;
}
}
sub get_stroke_count {
my ($chr) = @_;
my %stroke_cnt = (
#초성
"\x{1100}" => 1, "\x{1102}" => 1, "\x{1103}" => 2, "\x{1105}" => 3, # ㄱㄴㄷㄹ
"\x{1106}" => 3, "\x{1107}" => 4, "\x{1109}" => 2, "\x{110B}" => 1, # ㅁㅂㅅㅇ
"\x{110C}" => 3, "\x{110E}" => 4, "\x{110F}" => 2, "\x{1110}" => 3, # ㅈㅊㅋㅌ
"\x{1111}" => 4, "\x{1112}" => 3, # ㅍㅎ
#중성
"\x{1161}" => 2, "\x{1163}" => 3, "\x{1165}" => 2, "\x{1167}" => 3, # ㅏㅑㅓㅕ
"\x{1169}" => 2, "\x{116D}" => 3, "\x{116E}" => 2, "\x{1172}" => 3, # ㅗㅛㅜㅠ
"\x{1173}" => 1, "\x{1175}" => 1, # ㅡㅣ
#종성
"\x{11A8}" => 1, "\x{11AB}" => 1, "\x{11AE}" => 2, "\x{11AF}" => 3, # ㄱㄴㄷㄹ
"\x{11B7}" => 3, "\x{11B8}" => 4, "\x{11BA}" => 2, "\x{11BC}" => 1, # ㅁㅂㅅㅇ
"\x{11BD}" => 3, "\x{11BE}" => 4, "\x{11BF}" => 2, "\x{11C0}" => 3, # ㅈㅊㅋㅌ
"\x{11C1}" => 4, "\x{11C2}" => 3, # ㅍㅎ
);
my @jamos = map { split //, decomposeJamo($_) } split //, decomposeSyllable($chr);
my $strokes = 0;
foreach (@jamos) { $strokes += $stroke_cnt{$_} }
return $strokes;
}
1;
<LoveMatching_test.pl 테스트코드>
#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use LoveMatching qw/match_by_name/;
match_by_name("이명박","강만수");
<결과>
>perl LoveMatching_test.pl
이 강 명 만 박 수
[2 ,4 ,7 ,6 ,7 ,4]
[6 ,1 ,3 ,3 ,1]
[7 ,4 ,6 ,4]
[1 ,0 ,0]
100 %
<명령행에서 실행 예>
>perl -MLoveMatching=match_by_name -Mutf8 -e 'match_by_name("연정훈","한가인")'
연 한 정 가 훈 인
[5 ,6 ,6 ,3 ,6 ,3]
[1 ,2 ,9 ,9 ,9]
[3 ,1 ,8 ,8]
[4 ,9 ,6]
[3 ,5]
35 %
앞선 포스팅 을 통해서 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가 초기화될 때 까지
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가 초기화될 때 까지
요즘 많이 쓰이는 Perl 기반 웹프레임웍인 Catalyst 같은 코드들을 보면
sub some_sub : Private {
....
}
같은 형식으로 attribute라는 것(여기서는 : Private )을 쓰는 걸 종종 볼 수 있다.
이것은 Perl을 제법 공부했다는 사람에게도 생소한 경우가 많으며, 예제나 튜토리얼을 따라서 그대로 쓰고 있기는 하지만 그 내부 동작을 제대로 이해하지 못하고 기계적으로 쓰는 경우도 많다. 그 이유는 attribute의 동작을 이해하기 위해서는 Perl을 배우는 데 있어 고급주제에 속하는 모듈과 심볼테이블의 동작방식을 속속들이 알아야 하기 때문이다.
Perl에서는 perlsub 문서를 보면 알겠지만 lvalue 같은 표준적으로 제공하는 attribute가 이미 존재한다.
my $val;
sub canmod : lvalue {
# return $val; this doesn't work, don't say "return"
$val;
}
sub nomod {
$val;
}
이렇게 lvalue attribute를 정의한 서브루틴은 그 자체로 lvalue로 사용하여 값을 할당할 수 있다.
canmod() = 5; # assigns to $val
nomod() = 5; # ERROR
Perl 5.6 부터는 여기에 사용자가 나름대로의 attribute를 정의할 수 있도록 확장되었다.(문서상에는 아직 실험적인 기능이라고 하지만 이미 많은 모듈과 프레임웍에서 널리 사용되고 있어 거의 정착된 기능으로 봐도 무방할 것 같다.)
그럼 처음으로 돌아가서 attribute는 무엇일까? attribute는 어떤 변수나 서브루틴에 별도의 의미를 부여하는 것이라고 보면 된다. 예를 들어 서브루틴이 실행될 때 서브루틴이 시작하고 끝나는 것을 추적하는 어떤 로그를 남기고 싶다고 하면 모든 서브루틴내에서 그 루틴을 포함하는 것이 아니라 다음처럼 기존의 서브루틴들에 _log라는 attribute만 붙여주면 서브루틴이 실행될 때 _log attribute 속성을 가지고 있으면 자동으로 로그를 찍도록 하겠다는 것이다.
sub mysub : _log {
....
}
sub yoursub : _log {
....
}
그러면 일단 나름대로의 attribute를 정의하고 코드를 실행해보자.
sub mysub : Loud {
print "Hello, World!\n";
}
그러면 다음과 같은 에러가 난다.
Invalid CODE attribute: Loud at -e line 1
BEGIN failed--compilation aborted at -e line 1.
이것은 서브루틴에 정의한 Loud attribute가 유효하지 않다는 것이다. (서브루틴에 attribute를 붙였기 때문에 CODE attribute 라는 에러가 났다. )
그러면 : Loud 같은 attribute가 붙었을 때 Perl 내부적으로 어떻게 동작하는지 살펴보자.
Perl은 attribute 형식의 문법이 나오면 내부적으로 다음과 같은 코드로 실행된다.
sub mysub : Loud { ... 형식으로 나왔다면 다음과 같은 코드가 내부적으로 실행된다.
use attributes ( 'main', \&mysub, 'Loud');
뒤에 따르는 LIST는 차례대로 호출한 측의 패키지 이름, attribute가 사용된 것의 레퍼런스(여기서는 mysub 서브루틴의 레퍼런스), attribute의 목록(여러 개면 여러 개가 차례로)이 된다.
use는 모듈을 로딩하여 사용할 때 쓰는 명령어임은 잘 알고 있을 것이다. 하지만 use가 내부적으로 어떻게 동작하는지는 잘 모르는 사람이 많다. 다시 한 번 정리하고 넘어가자면
use SomeModule LIST; 는 (예: use List::Util qw/shuffle sum/; 이것은 use List::Util ('shuffle','sum'); 과 같은 의미)
BEGIN {
require SomeModule;
SomeModule->import LIST;
}
와 같은 의미이다.
그러면 use attributes ( 'main', \&mysub, 'Loud'); 는 어떻게 동작할까? 그렇다
BEGIN {
require attributes;
attributes->import('main',\&mysub,'Loud');
}
처럼 동작한다. 여기서 import는 모듈에서 모듈의 심볼과 호출 측 네임스페이스 내의 심볼을 연결시키는 심볼 export작업에 주로 사용되는 기본 서브루틴(따로 정의하지 않으면 비어 있음)으로 이것을 나름대로 정의 해서 다양한 용도로 쓸 수 있다. Perl의 고급 테크닉의 시작은 이것을 어떻게 잘 이해하고 이용하느냐가 첫 걸음이 된다.
그러면 이제 그다음 동작은 어떻게 진행되는지 보려면 어디로 추적해 들어가야 할까?
그렇다. attributes 모듈의 import 서브루틴이 어떻게 만들어져 있는지 보면 된다.
<attributes 모듈의 import 서브루틴>
sub import {
@_ > 2 && ref $_[2] or do {
require Exporter;
goto &Exporter::import;
};
my (undef,$home_stash,$svref,@attrs) = @_;
my $svtype = uc reftype($svref);
my $pkgmeth;
$pkgmeth = UNIVERSAL::can($home_stash, "MODIFY_${svtype}_ATTRIBUTES")
if defined $home_stash && $home_stash ne '';
my @badattrs;
if ($pkgmeth) {
my @pkgattrs = _modify_attrs($svref, @attrs);
@badattrs = $pkgmeth->($home_stash, $svref, @pkgattrs);
...
...
이 소스를 보고
attributes->import('main',\&mysub,'Loud'); 가 호출 되었을 때 어떻게 되는지 분석해보자
->연산자를 통해서 모듈 내의 서브루틴을 호출하면 -> 왼쪽의 것이 첫 번째 인자로 넘어간다. 따라서
import('attributes','main',\&mysub,'Loud'); 로 호출이 된다.
그러면 $home_stash는 'main', $svref은 \&mysub, @attrs은 'Loud' 가 될 것이다.
그다음 $svtype은 $svref의 레퍼런스 타입이 되므로 'CODE' 가 된다.
그다음 UNIVERSAL::can('main',"MODIFY_CODE_ATTRIBUTES") 가 실행되어 MODIFY_CODE_ATTRIBUTES라는 서브루틴이 실행가능한지를 채크하고 그 결과를 $pkgmeth 변수(성공이면 MODIFY_CODE_ATTRIBUTES 서브루틴에 대한 레퍼런스) 에 넣고 attribute들의 적합성을 채크한 다음 해당 $pkgmeth->('main',\&mysub, ('Loud')) 으로 해당 서브루틴을 호출한다.
그러면 attribute를 원하는대로 처리해주려면 attribute를 정의해준 변수나 서브루틴등 타입에 따라 MODIFY_[TYPE]_ATTRIBUTES( 서브루틴이면 MODIFY_CODE_ATTRIBUTES, 스칼라면 MODIFY_SCALAR_ATTRIBUTES 등) 라는 서브루틴을 정의해주면 될 것이라는 걸 눈치 챌 수 있을 것이다.
<테스트코드>
#!/usr/bin/perl
use strict;
use warnings;
sub MODIFY_CODE_ATTRIBUTES {
print "MODIFY_CODE_ATTRIBUTES\n";
print "@_\n";
return;
}
sub mysub : Loud {
print "Hello, World!\n";
}
<결과>
MODIFY_CODE_ATTRIBUTES
main CODE(0x663d20) Loud
여기서 넘어온 인자(@_)가 $pkgmeth->('main',\&mysub, ('Loud')) 호출에서 넘겨준 것과 같음을 알 수 있다.
attributes 모듈은 특정한 타입에 대해서 attribute들을 저장하고 가져오기 위한 FETCH_[TYPE]_ATTRIBUTES 서브루틴도 지원한다. 이것은 attributes 모듈로 넘어가는 인자가 변수나 서브루틴(코드)의 레퍼런스이기 때문에(레퍼런스는 메모리상에 위치한 주소로 unique한 값이다.) 필요하다면 그 레퍼런스를 해시의 키등으로 사용하여 어떤 attribute를 저장해놨다가 나중에 다시 꺼내어 볼 수 있는 것이다.
이 모든 것을 하나의 샘플코드로 정리해 보면 다음과 같다.
<샘플코드>
#!/usr/bin/perl
use strict;
use warnings;
my %attrs;
sub MODIFY_SCALAR_ATTRIBUTES {
print "MODIFY_SCALAR_ATTRIBUTES\n";
print "@_\n";
my($package,$scalar_ref,@attrs)=@_;
$attrs{$scalar_ref}=\@attrs; # %attrs 해시에 넘어온 attributes를 저장
return;
#return 'attr4'; # 지원하지 않는 속성을 return을 통해서 넘겨주면 에러가 발생되므로 예외 처리에 사용가능
}
sub FETCH_SCALAR_ATTRIBUTES {
print "FETCH_SCALAR_ATTRIBUTES\n";
print "@_\n";
my($package,$scalar_ref)=@_;
return $attrs{$scalar_ref}; # 저장했던 attributes를 넘겨준다.
}
sub MODIFY_CODE_ATTRIBUTES {
print "MODIFY_CODE_ATTRIBUTES\n";
print "@_\n";
my($package,$code_ref,@attrs)=@_;
$attrs{$code_ref}=\@attrs; # %attrs 해시에 넘어온 atttributes를 저장
return;
}
sub FETCH_CODE_ATTRIBUTES {
print "FETCH_CODE_ATTRIBUTES\n";
print "@_\n";
my($package,$code_ref)=@_;
return $attrs{$code_ref}; # 저장했던 attributes를 넘겨준다.
}
my $var : attr1 attr2 attr3;
# 위처럼 attribute를 사용하면 실제로는 아래 처럼 동작한다.
#use attributes ('main', \$var, 'attr1', 'attr2', 'attr3');
# attributes 모듈의 import가 두번째 인자의 TYPE에 따라 FETCH_[TYPE]_ATTRIBUTES가
# 정의되어 있으면 호출한다.
print "@{ attributes::get(\$var) }\n";
# FETCH_SCALAR_ATTRIBUTES 가 호출된다. 인자는 패키지이름과 레퍼런스 2개
sub test : attr1 attr2 attr3 {
print "test\n";
}
#use attributes ('main', \&test, 'attr1', 'attr2', 'attr3');
#위와 마찬가지로 뒤의 리스트들은 attributes 모듈의 import의 인자로 들어간다.
print "@{ attributes::get(\&test) }\n";
# FETCH_CODE_ATTRIBUTES 가 호출된다
<결과>
MODIFY_CODE_ATTRIBUTES
main CODE(0x65e0c0) attr1 attr2 attr3
MODIFY_SCALAR_ATTRIBUTES
main SCALAR(0x604fa0) attr1 attr2 attr3
FETCH_SCALAR_ATTRIBUTES
main SCALAR(0x604fa0)
attr1 attr2 attr3
FETCH_CODE_ATTRIBUTES
main CODE(0x65e0c0)
attr1 attr2 attr3
위 예제는 attribute의 동작방식을 알아보기 위한 것이지 실제 어떻게 사용하는지의 예제로는 적합하지 않다.
다음은 어떤 서브루틴에 Quiet라는 attribute를 주면 해당 서브루틴이 표준출력으로 출력하는 모든 글자를 소문자로 만드는 예제이다.
<Quiet.pm>
package Quiet;
use strict;
use warnings;
use IO::Capture::Stdout;
my @cache;
CHECK {
# attributes 모듈은 BEGIN 단계에서 로딩되므로 그 시점에서는 서브루틴이 완전히 로딩되지 않아서
# 서브루틴의 코드에 대한 조작을 할 수 없다. 따라서 MODIFY_CODE_ATTRIBUTES 에서 cache에 던져놓고
# 서브루틴이 로딩이 끝난 CHECK 단계에서 조작하기 위해 CHECK 서브루틴에 그 작업을 정의한다.
no strict 'refs';
my %code_cache;
foreach (@cache) {
my ($pkg, $ref, @attrs) = @$_;
unless ($code_cache{$pkg}) {
$code_cache{$pkg} = {};
foreach my $sym ( values %{$pkg.'::'} ) {
next unless *{$sym}{CODE};
$code_cache{$pkg}->{*{$sym}{CODE}} = *{$sym}{NAME};
}
}
my $sym = $pkg . '::' . $code_cache{$pkg}->{$ref};
no warnings 'redefine';
*{$sym} = sub {
my $capture = IO::Capture::Stdout->new();
$capture->start();
# call the original subroutine
my @rets = $ref->(@_);
$capture->stop();
foreach ( $capture->read ) { print "\L$_" }
return @rets;
};
}
}
sub MODIFY_CODE_ATTRIBUTES {
my ($package,$code_ref,@attrs) = @_;
push @cache, [$package, $code_ref, @attrs];
return grep { $_ !~ /^Quiet$/ } @attrs; # Quiet attribute가 아니면 에러발생
}
1;
<Quiet_test.pl>
#!/usr/bin/perl
use strict;
use warnings;
use base qw/Quiet/;
# We want this function to be loud
sub foo : Quiet {
print "Purple is a nice color.\n";
print "Hello, World !\n";
}
foo();
<결과>
purple is a nice color.
hello, world !
이 코드가 이해된다면 고수들이 만든 예전에는 암호 같아만 보였던 모듈 및 코드들이 이해되기 시작하면서 Perl을 공부하는 데 있어 새로운 재미를 느낄 수 있는 길이 열릴 것이라고 본다.
하지만 뭐가 이리 복잡해? 라고 생각하는 사람도 있을 텐데 다행히 이런 attribute를 다루는 dirty한 작업을 손쉽게 해줄 수 있는 모듈들이 존재한다. 다음번에는 그러한 모듈인 Attribute::Handlers 라는 모듈을 소개해보도록 하겠다.
참고:
http://search.cpan.org/perldoc?attributes
http://www.myemy.com/article/class-component/
sub some_sub : Private {
....
}
같은 형식으로 attribute라는 것(여기서는 : Private )을 쓰는 걸 종종 볼 수 있다.
이것은 Perl을 제법 공부했다는 사람에게도 생소한 경우가 많으며, 예제나 튜토리얼을 따라서 그대로 쓰고 있기는 하지만 그 내부 동작을 제대로 이해하지 못하고 기계적으로 쓰는 경우도 많다. 그 이유는 attribute의 동작을 이해하기 위해서는 Perl을 배우는 데 있어 고급주제에 속하는 모듈과 심볼테이블의 동작방식을 속속들이 알아야 하기 때문이다.
Perl에서는 perlsub 문서를 보면 알겠지만 lvalue 같은 표준적으로 제공하는 attribute가 이미 존재한다.
my $val;
sub canmod : lvalue {
# return $val; this doesn't work, don't say "return"
$val;
}
sub nomod {
$val;
}
이렇게 lvalue attribute를 정의한 서브루틴은 그 자체로 lvalue로 사용하여 값을 할당할 수 있다.
canmod() = 5; # assigns to $val
nomod() = 5; # ERROR
Perl 5.6 부터는 여기에 사용자가 나름대로의 attribute를 정의할 수 있도록 확장되었다.(문서상에는 아직 실험적인 기능이라고 하지만 이미 많은 모듈과 프레임웍에서 널리 사용되고 있어 거의 정착된 기능으로 봐도 무방할 것 같다.)
그럼 처음으로 돌아가서 attribute는 무엇일까? attribute는 어떤 변수나 서브루틴에 별도의 의미를 부여하는 것이라고 보면 된다. 예를 들어 서브루틴이 실행될 때 서브루틴이 시작하고 끝나는 것을 추적하는 어떤 로그를 남기고 싶다고 하면 모든 서브루틴내에서 그 루틴을 포함하는 것이 아니라 다음처럼 기존의 서브루틴들에 _log라는 attribute만 붙여주면 서브루틴이 실행될 때 _log attribute 속성을 가지고 있으면 자동으로 로그를 찍도록 하겠다는 것이다.
sub mysub : _log {
....
}
sub yoursub : _log {
....
}
그러면 일단 나름대로의 attribute를 정의하고 코드를 실행해보자.
sub mysub : Loud {
print "Hello, World!\n";
}
그러면 다음과 같은 에러가 난다.
Invalid CODE attribute: Loud at -e line 1
BEGIN failed--compilation aborted at -e line 1.
이것은 서브루틴에 정의한 Loud attribute가 유효하지 않다는 것이다. (서브루틴에 attribute를 붙였기 때문에 CODE attribute 라는 에러가 났다. )
그러면 : Loud 같은 attribute가 붙었을 때 Perl 내부적으로 어떻게 동작하는지 살펴보자.
Perl은 attribute 형식의 문법이 나오면 내부적으로 다음과 같은 코드로 실행된다.
sub mysub : Loud { ... 형식으로 나왔다면 다음과 같은 코드가 내부적으로 실행된다.
use attributes ( 'main', \&mysub, 'Loud');
뒤에 따르는 LIST는 차례대로 호출한 측의 패키지 이름, attribute가 사용된 것의 레퍼런스(여기서는 mysub 서브루틴의 레퍼런스), attribute의 목록(여러 개면 여러 개가 차례로)이 된다.
use는 모듈을 로딩하여 사용할 때 쓰는 명령어임은 잘 알고 있을 것이다. 하지만 use가 내부적으로 어떻게 동작하는지는 잘 모르는 사람이 많다. 다시 한 번 정리하고 넘어가자면
use SomeModule LIST; 는 (예: use List::Util qw/shuffle sum/; 이것은 use List::Util ('shuffle','sum'); 과 같은 의미)
BEGIN {
require SomeModule;
SomeModule->import LIST;
}
와 같은 의미이다.
그러면 use attributes ( 'main', \&mysub, 'Loud'); 는 어떻게 동작할까? 그렇다
BEGIN {
require attributes;
attributes->import('main',\&mysub,'Loud');
}
처럼 동작한다. 여기서 import는 모듈에서 모듈의 심볼과 호출 측 네임스페이스 내의 심볼을 연결시키는 심볼 export작업에 주로 사용되는 기본 서브루틴(따로 정의하지 않으면 비어 있음)으로 이것을 나름대로 정의 해서 다양한 용도로 쓸 수 있다. Perl의 고급 테크닉의 시작은 이것을 어떻게 잘 이해하고 이용하느냐가 첫 걸음이 된다.
그러면 이제 그다음 동작은 어떻게 진행되는지 보려면 어디로 추적해 들어가야 할까?
그렇다. attributes 모듈의 import 서브루틴이 어떻게 만들어져 있는지 보면 된다.
<attributes 모듈의 import 서브루틴>
sub import {
@_ > 2 && ref $_[2] or do {
require Exporter;
goto &Exporter::import;
};
my (undef,$home_stash,$svref,@attrs) = @_;
my $svtype = uc reftype($svref);
my $pkgmeth;
$pkgmeth = UNIVERSAL::can($home_stash, "MODIFY_${svtype}_ATTRIBUTES")
if defined $home_stash && $home_stash ne '';
my @badattrs;
if ($pkgmeth) {
my @pkgattrs = _modify_attrs($svref, @attrs);
@badattrs = $pkgmeth->($home_stash, $svref, @pkgattrs);
...
...
이 소스를 보고
attributes->import('main',\&mysub,'Loud'); 가 호출 되었을 때 어떻게 되는지 분석해보자
->연산자를 통해서 모듈 내의 서브루틴을 호출하면 -> 왼쪽의 것이 첫 번째 인자로 넘어간다. 따라서
import('attributes','main',\&mysub,'Loud'); 로 호출이 된다.
그러면 $home_stash는 'main', $svref은 \&mysub, @attrs은 'Loud' 가 될 것이다.
그다음 $svtype은 $svref의 레퍼런스 타입이 되므로 'CODE' 가 된다.
그다음 UNIVERSAL::can('main',"MODIFY_CODE_ATTRIBUTES") 가 실행되어 MODIFY_CODE_ATTRIBUTES라는 서브루틴이 실행가능한지를 채크하고 그 결과를 $pkgmeth 변수(성공이면 MODIFY_CODE_ATTRIBUTES 서브루틴에 대한 레퍼런스) 에 넣고 attribute들의 적합성을 채크한 다음 해당 $pkgmeth->('main',\&mysub, ('Loud')) 으로 해당 서브루틴을 호출한다.
그러면 attribute를 원하는대로 처리해주려면 attribute를 정의해준 변수나 서브루틴등 타입에 따라 MODIFY_[TYPE]_ATTRIBUTES( 서브루틴이면 MODIFY_CODE_ATTRIBUTES, 스칼라면 MODIFY_SCALAR_ATTRIBUTES 등) 라는 서브루틴을 정의해주면 될 것이라는 걸 눈치 챌 수 있을 것이다.
<테스트코드>
#!/usr/bin/perl
use strict;
use warnings;
sub MODIFY_CODE_ATTRIBUTES {
print "MODIFY_CODE_ATTRIBUTES\n";
print "@_\n";
return;
}
sub mysub : Loud {
print "Hello, World!\n";
}
<결과>
MODIFY_CODE_ATTRIBUTES
main CODE(0x663d20) Loud
여기서 넘어온 인자(@_)가 $pkgmeth->('main',\&mysub, ('Loud')) 호출에서 넘겨준 것과 같음을 알 수 있다.
attributes 모듈은 특정한 타입에 대해서 attribute들을 저장하고 가져오기 위한 FETCH_[TYPE]_ATTRIBUTES 서브루틴도 지원한다. 이것은 attributes 모듈로 넘어가는 인자가 변수나 서브루틴(코드)의 레퍼런스이기 때문에(레퍼런스는 메모리상에 위치한 주소로 unique한 값이다.) 필요하다면 그 레퍼런스를 해시의 키등으로 사용하여 어떤 attribute를 저장해놨다가 나중에 다시 꺼내어 볼 수 있는 것이다.
이 모든 것을 하나의 샘플코드로 정리해 보면 다음과 같다.
<샘플코드>
#!/usr/bin/perl
use strict;
use warnings;
my %attrs;
sub MODIFY_SCALAR_ATTRIBUTES {
print "MODIFY_SCALAR_ATTRIBUTES\n";
print "@_\n";
my($package,$scalar_ref,@attrs)=@_;
$attrs{$scalar_ref}=\@attrs; # %attrs 해시에 넘어온 attributes를 저장
return;
#return 'attr4'; # 지원하지 않는 속성을 return을 통해서 넘겨주면 에러가 발생되므로 예외 처리에 사용가능
}
sub FETCH_SCALAR_ATTRIBUTES {
print "FETCH_SCALAR_ATTRIBUTES\n";
print "@_\n";
my($package,$scalar_ref)=@_;
return $attrs{$scalar_ref}; # 저장했던 attributes를 넘겨준다.
}
sub MODIFY_CODE_ATTRIBUTES {
print "MODIFY_CODE_ATTRIBUTES\n";
print "@_\n";
my($package,$code_ref,@attrs)=@_;
$attrs{$code_ref}=\@attrs; # %attrs 해시에 넘어온 atttributes를 저장
return;
}
sub FETCH_CODE_ATTRIBUTES {
print "FETCH_CODE_ATTRIBUTES\n";
print "@_\n";
my($package,$code_ref)=@_;
return $attrs{$code_ref}; # 저장했던 attributes를 넘겨준다.
}
my $var : attr1 attr2 attr3;
# 위처럼 attribute를 사용하면 실제로는 아래 처럼 동작한다.
#use attributes ('main', \$var, 'attr1', 'attr2', 'attr3');
# attributes 모듈의 import가 두번째 인자의 TYPE에 따라 FETCH_[TYPE]_ATTRIBUTES가
# 정의되어 있으면 호출한다.
print "@{ attributes::get(\$var) }\n";
# FETCH_SCALAR_ATTRIBUTES 가 호출된다. 인자는 패키지이름과 레퍼런스 2개
sub test : attr1 attr2 attr3 {
print "test\n";
}
#use attributes ('main', \&test, 'attr1', 'attr2', 'attr3');
#위와 마찬가지로 뒤의 리스트들은 attributes 모듈의 import의 인자로 들어간다.
print "@{ attributes::get(\&test) }\n";
# FETCH_CODE_ATTRIBUTES 가 호출된다
<결과>
MODIFY_CODE_ATTRIBUTES
main CODE(0x65e0c0) attr1 attr2 attr3
MODIFY_SCALAR_ATTRIBUTES
main SCALAR(0x604fa0) attr1 attr2 attr3
FETCH_SCALAR_ATTRIBUTES
main SCALAR(0x604fa0)
attr1 attr2 attr3
FETCH_CODE_ATTRIBUTES
main CODE(0x65e0c0)
attr1 attr2 attr3
위 예제는 attribute의 동작방식을 알아보기 위한 것이지 실제 어떻게 사용하는지의 예제로는 적합하지 않다.
다음은 어떤 서브루틴에 Quiet라는 attribute를 주면 해당 서브루틴이 표준출력으로 출력하는 모든 글자를 소문자로 만드는 예제이다.
<Quiet.pm>
package Quiet;
use strict;
use warnings;
use IO::Capture::Stdout;
my @cache;
CHECK {
# attributes 모듈은 BEGIN 단계에서 로딩되므로 그 시점에서는 서브루틴이 완전히 로딩되지 않아서
# 서브루틴의 코드에 대한 조작을 할 수 없다. 따라서 MODIFY_CODE_ATTRIBUTES 에서 cache에 던져놓고
# 서브루틴이 로딩이 끝난 CHECK 단계에서 조작하기 위해 CHECK 서브루틴에 그 작업을 정의한다.
no strict 'refs';
my %code_cache;
foreach (@cache) {
my ($pkg, $ref, @attrs) = @$_;
unless ($code_cache{$pkg}) {
$code_cache{$pkg} = {};
foreach my $sym ( values %{$pkg.'::'} ) {
next unless *{$sym}{CODE};
$code_cache{$pkg}->{*{$sym}{CODE}} = *{$sym}{NAME};
}
}
my $sym = $pkg . '::' . $code_cache{$pkg}->{$ref};
no warnings 'redefine';
*{$sym} = sub {
my $capture = IO::Capture::Stdout->new();
$capture->start();
# call the original subroutine
my @rets = $ref->(@_);
$capture->stop();
foreach ( $capture->read ) { print "\L$_" }
return @rets;
};
}
}
sub MODIFY_CODE_ATTRIBUTES {
my ($package,$code_ref,@attrs) = @_;
push @cache, [$package, $code_ref, @attrs];
return grep { $_ !~ /^Quiet$/ } @attrs; # Quiet attribute가 아니면 에러발생
}
1;
<Quiet_test.pl>
#!/usr/bin/perl
use strict;
use warnings;
use base qw/Quiet/;
# We want this function to be loud
sub foo : Quiet {
print "Purple is a nice color.\n";
print "Hello, World !\n";
}
foo();
<결과>
purple is a nice color.
hello, world !
이 코드가 이해된다면 고수들이 만든 예전에는 암호 같아만 보였던 모듈 및 코드들이 이해되기 시작하면서 Perl을 공부하는 데 있어 새로운 재미를 느낄 수 있는 길이 열릴 것이라고 본다.
하지만 뭐가 이리 복잡해? 라고 생각하는 사람도 있을 텐데 다행히 이런 attribute를 다루는 dirty한 작업을 손쉽게 해줄 수 있는 모듈들이 존재한다. 다음번에는 그러한 모듈인 Attribute::Handlers 라는 모듈을 소개해보도록 하겠다.
참고:
http://search.cpan.org/perldoc?attributes
http://www.myemy.com/article/class-component/

