Python 到 Raku - 简而言之
此页面试图为来自 Python 背景的人们提供学习 Raku 的方法。我们在 Raku 中讨论了许多 Python 构造和惯用法的等价语法。
基本语法
Hello, world
让我们从打印 “Hello, world!” 开始吧。 Raku 中的 put 关键字相当于 Python 中的 print。与 Python 2 一样,括号是可选的。换行符添加到行尾。
- Python 2
print "Hello, world!"
- Python 3
print("Hello, world!")
- Raku
put "Hello, world!"
还有 say 关键字,其行为类似,但会调用其参数的 gist 方法。
- Raku
my $hello = "Hello, world!";
say $hello; # also prints "Hello, world!"
# same as: put $hello.gist
在 Python 中 '
和 "
是可互换的。在 Raku 中两者都可用于引用, 但双引号("
)表示应该进行插值。例如, 以 $
开头的变量和包含在花括号中的表达式会被插值。
- Raku
my $planet = 'earth';
say "Hello, $planet"; # Hello, earth
say 'Hello, $planet'; # Hello, $planet
say "Hello, planet number { 1 + 2 }"; # Hello, planet number 3
语句分隔符
在 Python 中,换行符表示语句的结束。有一些例外:换行符之前的反斜杠继续跨行语句。此外,如果有一个不匹配的开括号,方括号或花括号,则该语句将继续跨行,直到匹配的花括号被关闭。
在 Raku 中,分号表示语句的结束。如果分号是块的最后一个语句,则可以省略分号。如果有一个结束花括号后跟换行符,也可以省略分号。
- Python
print 1 + 2 + \
3 + 4
print ( 1 +
2 )
- Raku
say 1 + 2 +
3 + 4;
say 1 +
2;
块儿
在 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."
}
对于条件句中的表达式,括号在两种语言中都是可选的,如上所示。
变量
在 Python 中,变量是同时声明和初始化的:
foo = 12
bar = 19
在 Raku 中,my
声明符声明了一个词法变量。变量可以用 =
初始化。此变量可以先声明,然后再初始化或声明并立即初始化。
my $foo; # declare
$foo = 12; # initialize
my $bar = 19; # both at once
此外,你可能已经注意到,Raku 中的变量通常以符号开头 - 符号表示其容器的类型。 以 $
开头的变量持有标量。 以 @
开头的变量持有数组和以 %
开头的变量持有一个 hash(dict)。 如果用 \
声明它们,则不可变变量可以是无符号的。
- Python
s = 10
l = [1, 2, 3]
d = { a : 12, b : 99 }
print s
print l[2]
print d['a']
# 10, 2, 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>; # or %d{'a'}
say x;
# 10, 2, 12, 99
作用域
在 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 {
# do nothing
}
say x;
# x is 10
Python 中的 Lambdas 可以在 Raku 中写为块或尖号块。
- Python
l = lambda i: i + 12
- Raku
my $l = -> $i { $i + 12 }
构建 lambdas 的另一个Raku 惯用法是使用 Whatever star, *
。
- Raku
my $l = * + 12 # same as above
表达式中的 *
将成为参数的占位符,并在编译时将表达式转换为 lambda。 表达式中的每个 *
都是一个单独的位置参数。
有关子例程和块的更多结构,请参阅以下部分。
另一个例子(来自Python FAQ):
- 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
的可爱方式(也可以正常工作); unicode 上标 2 是一个数字。 许多其他 unicode 运算符正如你所期望的那样工作(指数, 分数, π),但是可以在 Raku 中使用的每个 unicode 运算符或符号都具有 ASCII 等价物。
控制流
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
。)
last
在 Raku 中退出一个循环,类似于 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
语句生成一个 generator
,就像 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;
}
Lambdas, 函数和子例程
在 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
}
# using variables with sigils
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>); # Alternative, more idiomatic
Raku 支持多重分派,因此可以通过将例程声明为 multi
来提供多个签名。
multi sub speak($word, $times) {
say $word for ^$times
}
multi sub speak(:$word, :$times) {
speak($word, $times);
}
speak('hi', 2);
speak(:word<hi>, :times<2>);
可以使用多种格式发送命名参数:
sub hello {...};
# all the same
hello(name => 'world'); # fat arrow syntax
hello(:name('world')); # pair constructor
hello :name<world>; # <> quotes words and makes a list
my $name = 'world';
hello(:$name); # lexical var with the same name
创建匿名函数可以使用带有块或尖号块的 sub
来完成。
- Python
square = lambda x: x ** 2
- Raku
my $square = sub ($x) { $x ** 2 }; # anonymous sub
my $square = -> $x { $x ** 2 }; # pointy block
my $square = { $^x ** 2 }; # placeholder variable
my $square = { $_ ** 2 }; # topic variable
占位符变量按字典顺序排列以形成位置参数。 因此这些是相同的:
my $power = { $^x ** $^y };
my $power = -> $x, $y { $x ** $y };
列表解析
可以组合 Postfix 语句修饰符和块以在 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)»
可以应用条件,但 if
关键字首先出现,而不像 Python 那样,if
是第二个出现。
- Python
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)»
使用 map
(就像 Python 的 map
一样)和 grep
(就像 Python 的 filter
一样)是另一种选择。
类和对象
这是 Python 文档中的一个示例。首先让我们回顾一下"实例变量”,这些变量在 Raku 中称为属性:
- 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>); # or: 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
trait:
- 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
trait 可以实现多重继承。或者,它可以与 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;
...
}
装饰器
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;
另一种方法是使用 trait:
# declare the trait 'greeter'
multi sub trait_mod:<is>(Routine $r, :$greeter) {
$r.wrap(sub {
say 'hello';
callsame;
})
}
sub world is greeter {
say 'world';
}
world;
上下文管理
Python 中的上下文管理器声明了在进入或退出作用域时发生的操作。
这是一个 Python 上下文管理器,可以打印字符串’hello’,‘world'和’bye’。
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';
}
一个相关的想法是’Phasers',它可以设置为在进入或离开一个区块时运行。
{
LEAVE say 'bye';
ENTER say 'hello';
say 'world';
}
input
在 Python 3 中,input
关键字用于提示用户。可以为此关键字提供可选参数,该参数将写入标准输出而不带尾随换行符:
user_input = input("Say hi → ")
print(user_input)
出现提示时,您可以输入 Hi
或任何其他字符串,这些字符串将存储在 user_input
变量中。这类似于 Raku 中的 prompt:
my $user_input = prompt("Say hi → ");
say $user_input; # OUTPUT: whatever you entered.