Python to Raku — 한눈에
Python to Raku — 한눈에 (Nutshell)
Python에 익숙한 사람이 Raku를 배울 때, '이걸 Raku에서는 어떻게 쓰지?' 하는 순간이 수없이 많아요. 이 페이지는 Python 배경지식이 있는 사람을 위해 Raku에서 Python 구조·관용구에 해당하는 문법을 짚어주는 참고서예요.
본문
이 페이지는 Python 배경의 사람들이 Raku를 배울 수 있는 길을 제공하려는 시도예요. 여러 Python 구성과 관용구에 대해 Raku의 동등 문법을 다뤄요.
기본 문법 (Basic syntax)
Hello, world
"Hello, world!" 출력부터 시작할게요. Raku의
Python 2
print "Hello, world!"
Python 3
print("Hello, world!")
Raku
put "Hello, world!"
비슷하게 동작하지만 인자의 gist 메서드를 호출하는
Raku
my $hello = "Hello, world!";
say $hello; # 역시 "Hello, world!" 출력
# put $hello.gist 와 동일
Python에서 '와 "는 서로 바꿔 쓸 수 있어요. Raku에서는 둘 다 따옴표로 쓸 수 있지만, 이중 따옴표(")는 보간이 수행됨을 나타내요. 예를 들어 $로 시작하는 변수와 중괄호에 든 표현식이 보간돼요.
Raku
my $planet = 'earth';
say "Hello, $planet"; # OUTPUT: «Hello, earth»
say 'Hello, $planet'; # OUTPUT: «Hello, $planet»
say "Hello, planet number { 1 + 2 }"; # OUTPUT: «Hello, planet number 3»
문장 구분자 (Statement separators)
Python에서 개행은 문장의 끝을 나타내요. 예외가 몇 가지 있어요: 개행 앞의 백슬래시는 문장을 여러 줄로 계속시켜요. 또한 짝이 안 맞는 여는 괄호·대괄호·중괄호가 있으면 짝이 맞는 괄호가 닫힐 때까지 문장이 여러 줄로 계속돼요.
Raku에서는 세미콜론이 문장의 끝을 나타내요. 세미콜론은 블록의 마지막 문장이면 생략할 수 있어요. 닫는 중괄호 다음에 개행이 오는 경우에도 세미콜론을 생략할 수 있어요.
Python
print 1 + 2 + \
3 + 4
print ( 1 +
2 )
Raku
say 1 + 2 +
3 + 4;
say 1 +
2;
블록 (Blocks)
Python에서 들여쓰기가 블록을 나타내요. Raku는 중괄호를 사용해요.
Python
if 1 == 2:
print("Wait, what?")
else:
print("1 is not 2.")
Raku
if 1 == 2 {
say "Wait, what?"
} else {
say "1 is not 2."
}
위에서 보듯 조건식에서 괄호는 두 언어 모두 선택적이에요.
변수 (Variables)
Python에서 변수는 선언과 초기화를 동시에 해요:
foo = 12
bar = 19
Raku에서 my 선언자는 어휘 변수를 선언해요. 변수는 =로 초기화할 수 있어요. 먼저 선언하고 나중에 초기화하거나, 선언과 동시에 초기화할 수 있어요.
my $foo; # 선언
$foo = 12; # 초기화
my $bar = 19; # 한 번에 둘 다
또한 눈치챘겠지만, Raku의 변수는 보통 시길(sigil)로 시작해요. 시길은 컨테이너의 타입을 나타내는 기호예요. $로 시작하는 변수는 스칼라를 담아요. @로 시작하는 변수는 배열, %로 시작하는 변수는 해시(dict)를 담아요. \로 선언하고 사용할 때는 시길 없이 쓰는 시길리스(sigilless) 변수는 할당된 값에 바인딩되어 있어서 불변(immutable)이에요.
이제부터 대부분의 예시에서 Python과의 유사성을 보여주려고 시길리스 변수를 쓸 거예요. 기술적으로 맞지만, 일반적으로는 그 불변성(또는 시그니처에서 쓰일 때 타입 독립성)이 필요하거나 강조되어야 할 곳에 시길리스 변수를 쓸 거예요.
Python
s = 10
l = [1, 2, 3]
d = { 'a' : 12, 'b' : 99 }
print(s)
print(l[2])
print(d['a'])
# 10, 3, 12
Raku
my $s = 10;
my @l = 1, 2, 3;
my %d = a => 12, b => 99;
my \x = 99;
say $s;
say @l[1];
say %d<a>; # 또는 %d{'a'}
say x;
# 10, 2, 12, 99
스코프 (Scope)
Python에서 함수와 클래스는 새 스코프를 만들지만, 다른 블록 구성자(예: 루프, 조건문)는 스코프를 만들지 않아요. Python 2에서 리스트 컴프리헨션은 새 스코프를 만들지 않지만, Python 3에서는 만든다.
Raku에서는 모든 블록이 어휘 스코프를 만들어요.
Python
if True:
x = 10
print(x)
# x is now 10
Raku
if True {
my $x = 10
}
say $x
# error, $x is not declared in this scope
my $x;
if True {
$x = 10
}
say $x
# ok, $x is 10
Python
x = 10
for x in 1, 2, 3:
pass
print(x)
# x is 3
Raku
my \x = 10;
for 1, 2, 3 -> \x {
# 아무것도 하지 않기
}
say x;
# x is 10
Python의 람다는 Raku에서 블록이나 포인티 블록(->)으로 쓸 수 있어요.
Python
l = lambda i: i + 12
Raku
my $l = -> $i { $i + 12 }
람다를 구성하는 또 다른 Raku 관용구는 Whatever 스타 *예요.
Raku
my $l = * + 12 # 위와 동일
표현식의 *는 인자의 자리표시자가 되어 컴파일 시간에 표현식을 람다로 바꿔요. 표현식의 각 *는 별도의 위치 파라미터예요.
서브루틴과 블록에 대한 더 많은 구조는 아래 섹션을 보세요.
Python
squares = []
for x in range(5):
squares.append(lambda: x ** 2)
print(squares[2]())
print(squares[4]())
# both 16 since there is only one x
Raku
my \squares = [];
for ^5 -> \x {
squares.append({ x² });
}
say squares[2]();
say squares[4]();
# 4, 16 since each loop iteration has a lexically scoped x,
^N은 range(N)과 비슷하다는 점에 주의하세요. 마찬가지로 N..^M은 range(N, M)처럼 동작해요 (N에서 M-1까지의 리스트). 범위 N..M은 N에서 M까지의 리스트예요. .. 전이나 후의 ^는 리스트의 시작점이나 끝점(또는 둘 다)이 제외되어야 함을 나타내요.
또한 x²는 x ** 2(이것도 잘 동작해요)를 쓰는 귀여운 방법이에요. 유니코드 위첨자 2는 숫자를 제곱해요. 다른 많은 유니코드 연산자도 예상대로 동작하지만(지수, 분수, π), Raku에서 쓸 수 있는 모든 유니코드 연산자·기호에는 ASCII 동등 형태가 있어요.
제어 흐름 (Control flow)
Python에는 for 루프와 while 루프가 있어요:
for i in 1, 2:
print(i)
j = 1
while j < 3:
print(j)
j += 1
# 1, 2, 1, 2
Raku에도 for 루프와 while 루프가 있어요:
for 1, 2 -> $i {
say $i
}
my $j = 1;
while $j < 3 {
say $j;
$j += 1
}
(Raku에는 몇 가지 더 많은 루프 구성도 있어요: repeat...until, repeat...while, until, loop.)
Raku의 last는 루프를 떠나며, Python의 break에 해당해요. Python의 continue는 Raku의 next예요.
Python
for i in range(10):
if i == 3:
continue
if i == 5:
break
print(i)
Raku
for ^10 -> $i {
next if $i == 3;
last if $i == 5;
say $i;
}
위처럼 if를 문장 수식어로 쓰는 것은 리스트 컴프리헨션 밖에서도 Raku에서 허용돼요.
Python for 루프 안의 yield 문(제너레이터를 만드는)은 Raku의 gather/take 구성과 비슷해요. 이 둘은 모두 1, 2, 3을 출력해요.
Python
def count():
for i in 1, 2, 3:
yield i
for c in count():
print(c)
Raku
sub count {
gather {
for 1, 2, 3 -> $i {
take $i
}
}
}
for count() -> $c {
say $c;
}
Python의 enumerate()와 .items() 메커니즘으로 리스트나 dict/map을 반복하는 사용은 둘 다 Raku에서 같은
Python
elems = ["neutronium", "hydrogen", "helium", "lithium"]
for i, e in enumerate(elems):
print("Elem no. %d is %s" % (i, e))
symbols = ["n", "H", "He", "Li"]
elem4Symbol = {s: e for s, e in zip(symbols, elems)}
for symbol, elem in elem4Symbol.items():
print("Symbol '%s' stands for %s" % (symbol, elem))
# Elem no. 0 is neutronium
# Elem no. 1 is hydrogen
# Elem no. 2 is helium
# Elem no. 3 is lithium
# Symbol 'H' stands for hydrogen
# Symbol 'He' stands for helium
# Symbol 'Li' stands for lithium
# Symbol 'n' stands for neutronium
Raku
my @elems = <neutronium hydrogen helium lithium>;
for @elems.kv -> $i, $e {
say "Elem no. $i is $e"
}
my @symbols = <n H He Li>;
my %elem-for-symbol;
%elem-for-symbol{@symbols} = @elems;
# 반복 순서는 Python과 다를 수 있음에 주의
for %elem-for-symbol.kv -> $symbol, $element {
say "Symbol '$symbol' stands for $element";
}
람다, 함수, 서브루틴
Python의 def로 함수(서브루틴)를 선언하는 것은 Raku에서 sub로 합니다.
def add(a, b):
return a + b
sub add(\a, \b) {
return a + b
}
return은 선택적이에요. 마지막 표현식의 값이 반환값으로 쓰여요:
sub add(\a, \b) {
a + b
}
# 시길을 가진 변수 사용
sub add($a, $b) {
$a + $b
}
Python 2 함수는 위치 인자나 키워드 인자로 호출할 수 있어요. 이는 호출자가 결정해요. Python 3에서는 일부 인자가 "keyword only"일 수 있어요. Raku에서는 위치·명명 인자가 루틴의 시그니처에 의해 결정돼요.
Python
def speak(word, times):
for i in range(times):
print word
speak('hi', 2)
speak(word='hi', times=2)
Raku
위치 파라미터:
sub speak($word, $times) {
say $word for ^$times
}
speak('hi', 2);
명명 파라미터는 콜론으로 시작해요:
sub speak(:$word, :$times) {
say $word for ^$times
}
speak(word => 'hi', times => 2);
speak(:word<hi>, :times<2>); # 대안, 더 관용적
Raku는 다중 디스패치를 지원하므로, 루틴을 multi로 선언하면 여러 시그니처를 제공할 수 있어요.
multi speak($word, $times) {
say $word for ^$times
}
multi speak(:$word, :$times) {
speak($word, $times);
}
speak('hi', 2);
speak(:word<hi>, :times<2>);
명명 파라미터는 다양한 형식으로 보낼 수 있어요:
sub hello {...};
# 전부 동일
hello(name => 'world'); # fat arrow 문법
hello(:name('world')); # pair 생성자
hello :name<world>; # <> 가 말을 인용해 리스트를 만듦
my $name = 'world';
hello(:$name); # 같은 이름의 어휘 변수
익명 함수는 sub, 블록, 포인티 블록으로 만들 수 있어요.
Python
square = lambda x: x ** 2
Raku
my $square = sub ($x) { $x ** 2 }; # 익명 sub
my $square = -> $x { $x ** 2 }; # 포인티 블록
my $square = { $^x ** 2 }; # 자리표시자 변수
my $square = { $_ ** 2 }; # topic 변수
자리표시자 변수는 위치 파라미터를 형성하도록 사전순으로 정렬돼요. 그래서 다음은 같아요:
my $power = { $^x ** $^y };
my $power = -> $x, $y { $x ** $y };
리스트 컴프리헨션 (List comprehensions)
후위 문장 수식어와 블록을 결합하면 Raku에서 리스트 컴프리헨션을 쉽게 만들 수 있어요.
Python
print([ i * 2 for i in [3, 9]]) # OUTPUT: «[6, 18]»
Raku
say ( $_ * 2 for 3, 9 ); # OUTPUT: «(6 18)»
say ( { $^i * 2 } for 3, 9 ); # OUTPUT: «(6 18)»
say ( -> \i { i * 2 } for 3, 9 ); # OUTPUT: «(6 18)»
조건을 적용할 수 있지만, Python에서 if가 두 번째에 오는 것과 달리 Raku에서는 if 키워드가 먼저 와요.
print [ x * 2 for x in [1, 2, 3] if x > 1 ] # OUTPUT: «[4, 6]»
vs
say ( $_ * 2 if $_ > 1 for 1, 2, 3 ); # OUTPUT: «(4 6)»
중첩 루프에는 크로스 프로덕트 연산자 X가 도움이 돼요:
print([ i + j for i in [3,9] for j in [2,10] ]) # OUTPUT: «[5, 13, 11, 19]»
다음 중 하나가 됩니다:
say ( { $_[0] + $_[1] } for (3,9) X (2,10) ); # OUTPUT: «(5 13 11 19)»
say ( -> (\i, \j) { i + j } for (3,9) X (2,10) ); # OUTPUT: «(5 13 11 19)»
say ( -> ($i, $j) { $i + $j } for (3,9) X (2,10) );# OUTPUT: «(5 13 11 19)»
say ( { $^a[0] + $^a[1] } for (3,9) X (2,10) ); # OUTPUT: «(5 13 11 19)»
Python의 map처럼 동작하는 map과 Python의 filter처럼 동작하는 grep을 쓰는 것도 대안이에요.
클래스와 객체 (Classes and objects)
여기 Python
Python:
class Dog:
def __init__(self, name):
self.name = name
Raku:
class Dog {
has $.name;
}
생성된 각 클래스에 대해 Raku는 명명 인자를 받는 생성자 메서드 new를 기본으로 제공해요.
Python:
d = Dog('Fido')
e = Dog('Buddy')
print(d.name)
print(e.name)
Raku
my $d = Dog.new(:name<Fido>); # 또는: Dog.new(name => 'Fido')
my $e = Dog.new(:name<Buddy>);
say $d.name;
say $e.name;
Raku의 클래스 속성은 몇 가지 방법으로 선언할 수 있어요. 한 방법은 어휘 변수와 그에 접근하는 메서드를 선언하는 거예요.
Python:
class Dog:
kind = 'canine' # class attribute
def __init__(self, name):
self.name = name # instance attribute
d = Dog('Fido')
e = Dog('Buddy')
print(d.kind)
print(e.kind)
print(d.name)
print(e.name)
Raku:
class Dog {
my $kind = 'canine'; # class attribute
method kind { $kind }
has $.name; # instance attribute
}
my $d = Dog.new(:name<Fido>);
my $e = Dog.new(:name<Buddy>);
say $d.kind;
say $e.kind;
say $d.name;
say $e.name;
Raku에서 속성을 변경하려면 속성에 is rw 트레잇을 사용해야 해요:
Python:
class Dog:
def __init__(self, name):
self.name = name
d = Dog()
d.name = 'rover'
Raku:
class Dog {
has $.name is rw;
}
my $d = Dog.new;
$d.name = 'rover';
상속은 is로 해요:
Python
class Animal:
def jump(self):
print ("I am jumping")
class Dog(Animal):
pass
d = Dog()
d.jump()
Raku
class Animal {
method jump {
say "I am jumping"
}
}
class Dog is Animal {
}
my $d = Dog.new;
$d.jump;
다중 상속은 is 트레잇을 필요한 만큼 사용해서 가능해요. 또는 also 키워드와 함께 사용할 수도 있어요.
Python
class Dog(Animal, Friend, Pet):
pass
Raku
class Animal {}; class Friend {}; class Pet {};
...;
class Dog is Animal is Friend is Pet {};
또는
class Animal {}; class Friend {}; class Pet {};
...;
class Dog is Animal {
also is Friend;
also is Pet;
...
}
데코레이터 (Decorators)
Python의 데코레이터는 함수를 다른 함수로 감싸는 방법이에요. Raku에서는 wrap으로 해요.
Python
def greeter(f):
def new():
print('hello')
f()
return new
@greeter
def world():
print('world')
world();
Raku
sub world {
say 'world'
}
&world.wrap(sub () {
say 'hello';
callsame;
});
world;
대안으로 트레잇을 쓸 수도 있어요:
# 'greeter' 트레잇 선언
multi trait_mod:<is>(Routine $r, :$greeter) {
$r.wrap(sub {
say 'hello';
callsame;
})
}
sub world is greeter {
say 'world';
}
world;
컨텍스트 매니저 (Context managers)
Python의 컨텍스트 매니저는 스코프에 진입하거나 나올 때 일어나는 동작을 선언해요.
'hello', 'world', 'bye' 문자열을 출력하는 Python 컨텍스트 매니저가 여기 있어요.
class hello:
def __exit__(self, type, value, traceback):
print('bye')
def __enter__(self):
print('hello')
with hello():
print('world')
"enter"와 "exit" 이벤트에 대해 블록을 인자로 전달하는 것이 한 가지 옵션이에요:
sub hello(Block $b) {
say 'hello';
$b();
say 'bye';
}
hello {
say 'world';
}
관련 아이디어로 블록에 진입하거나 나올 때 실행되도록 설정할 수 있는 '
{
LEAVE say 'bye';
ENTER say 'hello';
say 'world';
}
input
Python 3에서 input 키워드는 사용자에게 프롬프트를 표시하는 데 쓰여요. 이 키워드에 선택적 인자를 제공할 수 있는데, 이는 후행 개행 없이 표준 출력에 기록돼요:
user_input = input("Say hi → ")
print(user_input)
프롬프트가 표시되면 Hi나 다른 문자열을 입력할 수 있고, user_input 변수에 저장돼요. 이것은 Raku의
my $user_input = prompt("Say hi → ");
say $user_input; # OUTPUT: 입력한 무엇이든.
튜플 (Tuples)
Python 튜플은 불변 시퀀스예요. 시퀀스 요소가 같은 타입일 필요는 없어요.
Python
tuple1 = (1, "two", 3, "hat")
tuple2 = (5, 6, "seven")
print(tuple1[1]) # OUTPUT: «two»
tuple3 = tuple1 + tuple2
print(tuple3) # OUTPUT: «(1, 'two', 3, 'hat', 5, 6, 'seven')»
Raku
Raku에는 내장 Tuple 타입이 없어요. Raku에서 List 타입으로 같은 동작을 얻거나, 외부 모듈에서 얻을 수 있어요.
my $list1 = (1, "two", 3, "hat");
my $list2 = (5, 6, "seven");
say $list1[1]; # OUTPUT: «two»
my $list3 = (slip($list1), slip($list2));
my $list4 = (|$list1, |$list2); # 앞줄과 동등
say $list3; # OUTPUT: «(1, two, 3, hat, 5, 6, seven)»