Python 到 Raku - 简而言之

此页面试图为来自 Python 背景的人们提供学习 Raku 的方法。我们在 Raku 中讨论了许多 Python 构造和惯用法的等价语法。

基本语法

Hello, world

让我们从打印 “Hello, world!” 开始吧。 Raku 中的 put 关键字相当于 Python 中的 print。与 Python 2 一样,括号是可选的。换行符添加到行尾。

  • Python 2
  1. print "Hello, world!"
  • Python 3
  1. print("Hello, world!")
  • Raku
  1. put "Hello, world!"

还有 say 关键字,其行为类似,但会调用其参数的 gist 方法。

  • Raku
  1. my $hello = "Hello, world!";
  2. say $hello; # also prints "Hello, world!"
  3. # same as: put $hello.gist

在 Python 中 '" 是可互换的。在 Raku 中两者都可用于引用, 但双引号(")表示应该进行插值。例如, 以 $ 开头的变量和包含在花括号中的表达式会被插值。

  • Raku
  1. my $planet = 'earth';
  2. say "Hello, $planet"; # Hello, earth
  3. say 'Hello, $planet'; # Hello, $planet
  4. say "Hello, planet number { 1 + 2 }"; # Hello, planet number 3

语句分隔符

在 Python 中,换行符表示语句的结束。有一些例外:换行符之前的反斜杠继续跨行语句。此外,如果有一个不匹配的开括号,方括号或花括号,则该语句将继续跨行,直到匹配的花括号被关闭。

在 Raku 中,分号表示语句的结束。如果分号是块的最后一个语句,则可以省略分号。如果有一个结束花括号后跟换行符,也可以省略分号。

  • Python
  1. print 1 + 2 + \
  2. 3 + 4
  3. print ( 1 +
  4. 2 )
  • Raku
  1. say 1 + 2 +
  2. 3 + 4;
  3. say 1 +
  4. 2;

块儿

在 Python 中,缩进用于表示块。 Raku 使用花括号表示块儿。

  • Python
  1. if 1 == 2:
  2. print "Wait, what?"
  3. else:
  4. print "1 is not 2."
  • Raku
  1. if 1 == 2 {
  2. say "Wait, what?"
  3. } else {
  4. say "1 is not 2."
  5. }

对于条件句中的表达式,括号在两种语言中都是可选的,如上所示。

变量

在 Python 中,变量是同时声明和初始化的:

  1. foo = 12
  2. bar = 19

在 Raku 中,my 声明符声明了一个词法变量。变量可以用 = 初始化。此变量可以先声明,然后再初始化或声明并立即初始化。

  1. my $foo; # declare
  2. $foo = 12; # initialize
  3. my $bar = 19; # both at once

此外,你可能已经注意到,Raku 中的变量通常以符号开头 - 符号表示其容器的类型。 以 $ 开头的变量持有标量。 以 @ 开头的变量持有数组和以 % 开头的变量持有一个 hash(dict)。 如果用 \ 声明它们,则不可变变量可以是无符号的。

  • Python
  1. s = 10
  2. l = [1, 2, 3]
  3. d = { a : 12, b : 99 }
  4. print s
  5. print l[2]
  6. print d['a']
  7. # 10, 2, 12
  • Raku
  1. my $s = 10;
  2. my @l = 1, 2, 3;
  3. my %d = a => 12, b => 99;
  4. my \x = 99;
  5. say $s;
  6. say @l[1];
  7. say %d<a>; # or %d{'a'}
  8. say x;
  9. # 10, 2, 12, 99

作用域

在 Python 中,函数和类创建一个新的作用域,但没有其他的块构造函数(例如循环,条件)创建一个作用域。在 Python 2 中,列表推导不会创建新的作用域,但在 Python 3 中,它们创建新的作用域。

在 Raku 中,每个块都创建了一个词法作用域

  • Python
  1. if True:
  2. x = 10
  3. print x
  4. # x is now 10
  • Raku
  1. if True {
  2. my $x = 10
  3. }
  4. say $x
  5. # error, $x is not declared in this scope
  1. my $x;
  2. if True {
  3. $x = 10
  4. }
  5. say $x
  6. # ok, $x is 10
  • Python
  1. x = 10
  2. for x in 1, 2, 3:
  3. pass
  4. print x
  5. # x is 3
  • Raku
  1. my \x = 10;
  2. for 1, 2, 3 -> \x {
  3. # do nothing
  4. }
  5. say x;
  6. # x is 10

Python 中的 Lambdas 可以在 Raku 中写为块或尖号块。

  • Python
  1. l = lambda i: i + 12
  • Raku
  1. my $l = -> $i { $i + 12 }

构建 lambdas 的另一个Raku 惯用法是使用 Whatever star, *

  • Raku
  1. my $l = * + 12 # same as above

表达式中的 将成为参数的占位符,并在编译时将表达式转换为 lambda。 表达式中的每个 都是一个单独的位置参数。

有关子例程和块的更多结构,请参阅以下部分。

另一个例子(来自Python FAQ):

  • Python
  1. squares = []
  2. for x in range(5):
  3. squares.append(lambda: x ** 2)
  4. print squareslink:[2]
  5. print squareslink:[4]
  6. # both 16 since there is only one x
  • Raku
  1. my \squares = [];
  2. for ^5 -> \x {
  3. squares.append({ x² });
  4. }
  5. say squareslink:[2];
  6. say squareslink:[4];
  7. # 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 ** 2 的可爱方式(也可以正常工作); unicode 上标 2 是一个数字。 许多其他 unicode 运算符正如你所期望的那样工作(指数, 分数, π),但是可以在 Raku 中使用的每个 unicode 运算符或符号都具有 ASCII 等价物。

控制流

Python 有 for 循环和 while 循环:

  1. for i in 1, 2:
  2. print i
  3. j = 1
  4. while j < 3:
  5. print j
  6. j += 1
  7. # 1,2,1,2

Raku 也有 for 循环和 while 循环:

  1. for 1, 2 -> $i {
  2. say $i
  3. }
  4. my $j = 1;
  5. while $j < 3 {
  6. say $j;
  7. $j += 1
  8. }

(Raku 还有一些循环结构:repeat …​ untilrepeat …​ whileuntilloop。)

last 在 Raku 中退出一个循环,类似于 Python 中的 break。 Python 中的 continue 在 Raku 中是 next

  • Python
  1. for i in range(10):
  2. if i == 3:
  3. continue
  4. if i == 5:
  5. break
  6. print i
  • Raku
  1. for ^10 -> $i {
  2. next if $i == 3;
  3. last if $i == 5;
  4. say $i;
  5. }

使用 if 作为语句修饰符(如上所述)在 Raku 中是可接受的,甚至在列表解析之外也可以。

Python for 循环中的 yield 语句生成一个 generator,就像 Raku 中的 gather/take 构造一样。这两个都打印 1,2,3。

  • Python
  1. def count():
  2. for i in 1, 2, 3:
  3. yield i
  4. for c in count():
  5. print c
  • Raku
  1. sub count {
  2. gather {
  3. for 1, 2, 3 -> $i {
  4. take $i
  5. }
  6. }
  7. }
  8. for count() -> $c {
  9. say $c;
  10. }

Lambdas, 函数和子例程

在 Python 中用 def 声明的函数(子例程)在 Raku 中是用 sub 来完成的。

  1. def add(a, b):
  2. return a + b
  3. sub add(\a, \b) {
  4. return a + b
  5. }

return 是可选的; 最后一个表达式的值被用作返回值:

  1. sub add(\a, \b) {
  2. a + b
  3. }
  1. # using variables with sigils
  2. sub add($a, $b) {
  3. $a + $b
  4. }

可以使用位置参数或关键字参数调用 Python 2 函数。这些是由调用者决定的。在 Python 3 中,一些参数可能是”keyword only”的。在 Raku 中,位置参数和命名参数由例程的签名确定。

  • Python
  1. def speak(word, times):
  2. for i in range(times):
  3. print word
  4. speak('hi', 2)
  5. speak(word='hi', times=2)
  • Raku

位置参数

  1. sub speak($word, $times) {
  2. say $word for ^$times
  3. }
  4. speak('hi', 2);

以冒号开头的命名参数:

  1. sub speak(:$word, :$times) {
  2. say $word for ^$times
  3. }
  4. speak(word => 'hi', times => 2);
  5. speak(:word<hi>, :times<2>); # Alternative, more idiomatic

Raku 支持多重分派,因此可以通过将例程声明为 multi 来提供多个签名。

  1. multi sub speak($word, $times) {
  2. say $word for ^$times
  3. }
  4. multi sub speak(:$word, :$times) {
  5. speak($word, $times);
  6. }
  7. speak('hi', 2);
  8. speak(:word<hi>, :times<2>);

可以使用多种格式发送命名参数:

  1. sub hello {...};
  2. # all the same
  3. hello(name => 'world'); # fat arrow syntax
  4. hello(:name('world')); # pair constructor
  5. hello :name<world>; # <> quotes words and makes a list
  6. my $name = 'world';
  7. hello(:$name); # lexical var with the same name

创建匿名函数可以使用带有块或尖号块的 sub 来完成。

  • Python
  1. square = lambda x: x ** 2
  • Raku
  1. my $square = sub ($x) { $x ** 2 }; # anonymous sub
  2. my $square = -> $x { $x ** 2 }; # pointy block
  3. my $square = { $^x ** 2 }; # placeholder variable
  4. my $square = { $_ ** 2 }; # topic variable

占位符变量按字典顺序排列以形成位置参数。 因此这些是相同的:

  1. my $power = { $^x ** $^y };
  2. my $power = -> $x, $y { $x ** $y };

列表解析

可以组合 Postfix 语句修饰符和块以在 Raku 中轻松创建列表解析。

  • Python
  1. print [ i * 2 for i in 3, 9 ] # OUTPUT: «[6, 18]
  2. »
  • Raku
  1. say ( $_ * 2 for 3, 9 ); # OUTPUT: «(6 18)
  2. »
  3. say ( { $^i * 2 } for 3, 9 ); # OUTPUT: «(6 18)
  4. »
  5. say ( -> \i { i * 2 } for 3, 9 ); # OUTPUT: «(6 18)
  6. »

可以应用条件,但 if 关键字首先出现,而不像 Python 那样,if 是第二个出现。

  • Python
  1. print [ x * 2 for x in 1, 2, 3 if x > 1 ] # OUTPUT: «[4, 6]
  2. »

vs

  1. say ( $_ * 2 if $_ > 1 for 1, 2, 3 ); # OUTPUT: «(4 6)
  2. »

对于嵌套循环,交叉乘积运算符 X 将会有帮助:

  1. print [ i + j for i in 3,9 for j in 2,10 ] # OUTPUT: «[5, 13, 11, 19]
  2. »

变成以下任何一个:

  1. say ( { $_[0] + $_[1] } for (3,9) X (2,10) ); # OUTPUT: «(5 13 11 19)
  2. »
  3. say ( -> (\i, \j) { i + j } for (3,9) X (2,10) ); # OUTPUT: «(5 13 11 19)
  4. »

使用 map(就像 Python 的 map 一样)和 grep(就像 Python 的 filter 一样)是另一种选择。

类和对象

这是 Python 文档中的一个示例。首先让我们回顾一下”实例变量”,这些变量在 Raku 中称为属性:

  • Python
  1. class Dog:
  2. def __init__(self, name):
  3. self.name = name
  • Raku
  1. class Dog {
  2. has $.name;
  3. }

对于每个创建的类,Raku 默认提供构造函数方法 new,它接受命名参数。

  • Python
  1. d = Dog('Fido')
  2. e = Dog('Buddy')
  3. print d.name
  4. print e.name
  • Raku
  1. my $d = Dog.new(:name<Fido>); # or: Dog.new(name => 'Fido')
  2. my $e = Dog.new(:name<Buddy>);
  3. say $d.name;
  4. say $e.name;

Raku 中的类属性可以通过几种方式声明。一种方法是仅声明一个词法变量和一个访问它的方法。

  • Python
  1. class Dog:
  2. kind = 'canine' # class attribute
  3. def __init__(self, name):
  4. self.name = name # instance attribute
  5. d = Dog('Fido')
  6. e = Dog('Buddy')
  7. print d.kind
  8. print e.kind
  9. print d.name
  10. print e.name
  • Raku
  1. class Dog {
  2. my $kind = 'canine'; # class attribute
  3. method kind { $kind }
  4. has $.name; # instance attribute
  5. }
  6. my $d = Dog.new(:name<Fido>);
  7. my $e = Dog.new(:name<Buddy>);
  8. say $d.kind;
  9. say $e.kind;
  10. say $d.name;
  11. say $e.name;

为了在 Raku 中改变属性,必须在属性上使用 is rw trait:

  • Python
  1. class Dog:
  2. def __init__(self, name):
  3. self.name = name
  4. d = Dog()
  5. d.name = 'rover'
  • Raku
  1. class Dog {
  2. has $.name is rw;
  3. }
  4. my $d = Dog.new;
  5. $d.name = 'rover';

继承使用 is 来完成:

  • Python
  1. class Animal:
  2. def jump(self):
  3. print ("I am jumping")
  4. class Dog(Animal):
  5. pass
  6. d = Dog()
  7. d.jump()
  • Raku
  1. class Animal {
  2. method jump {
  3. say "I am jumping"
  4. }
  5. }
  6. class Dog is Animal {
  7. }
  8. my $d = Dog.new;
  9. $d.jump;

根据需要多次使用 is trait 可以实现多重继承。或者,它可以与 also 关键字一起使用。

  • Python
  1. class Dog(Animal, Friend, Pet):
  2. pass
  • Raku
  1. class Animal {}; class Friend {}; class Pet {};
  2. ...;
  3. class Dog is Animal is Friend is Pet {};

  1. class Animal {}; class Friend {}; class Pet {};
  2. ...;
  3. class Dog is Animal {
  4. also is Friend;
  5. also is Pet;
  6. ...
  7. }

装饰器

Python 中的装饰器是一种将函数包装在另一个函数中的方法。在 Raku 中,这是通过 wrap 完成的。

  • Python
  1. def greeter(f):
  2. def new():
  3. print 'hello'
  4. f()
  5. return new
  6. @greeter
  7. def world():
  8. print 'world'
  9. world();
  • Raku
  1. sub world {
  2. say 'world'
  3. }
  4. &world.wrap(sub () {
  5. say 'hello';
  6. callsame;
  7. });
  8. world;

另一种方法是使用 trait:

  1. # declare the trait 'greeter'
  2. multi sub trait_mod:<is>(Routine $r, :$greeter) {
  3. $r.wrap(sub {
  4. say 'hello';
  5. callsame;
  6. })
  7. }
  8. sub world is greeter {
  9. say 'world';
  10. }
  11. world;

上下文管理

Python 中的上下文管理器声明了在进入或退出作用域时发生的操作。

这是一个 Python 上下文管理器,可以打印字符串’hello’,’world’和’bye’。

  1. class hello:
  2. def __exit__(self, type, value, traceback):
  3. print 'bye'
  4. def __enter__(self):
  5. print 'hello'
  6. with hello():
  7. print 'world'

对于 “enter” 和 “exit” 事件,将块作为参数传递将是一种方法:

  1. sub hello(Block $b) {
  2. say 'hello';
  3. $b();
  4. say 'bye';
  5. }
  6. hello {
  7. say 'world';
  8. }

一个相关的想法是’Phasers‘,它可以设置为在进入或离开一个区块时运行。

  1. {
  2. LEAVE say 'bye';
  3. ENTER say 'hello';
  4. say 'world';
  5. }

input

在 Python 3 中,input 关键字用于提示用户。可以为此关键字提供可选参数,该参数将写入标准输出而不带尾随换行符:

  1. user_input = input("Say hi → ")
  2. print(user_input)

出现提示时,您可以输入 Hi 或任何其他字符串,这些字符串将存储在 user_input 变量中。这类似于 Raku 中的 prompt

  1. my $user_input = prompt("Say hi → ");
  2. say $user_input; # OUTPUT: whatever you entered.