此页面旨在为来自 Python 背景的用户提供学习 Raku 的方法。我们讨论了 Raku 中许多 Python 结构和习惯用法等效的语法。

基本语法§

你好,世界§

让我们从打印“你好,世界!”开始。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";                 # OUTPUT: «Hello, earth␤» 
say 'Hello, $planet';                 # OUTPUT: «Hello, $planet␤» 
say "Hello, planet number { 1 + 2 }"# OUTPUT: «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 中的变量通常以符号开头——这些符号表示其容器的类型。以 $ 开头的变量保存标量。以 @ 开头的变量保存数组,以 % 开头的变量保存哈希(字典)。以 \ 声明但未使用符号的无符号变量绑定到它们被分配的值,因此是不可变的。

请注意,从现在开始,我们将在大多数示例中使用无符号变量,以说明与 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 = 123;
my %d = => 12=> 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 123 -> \x {
    # do nothing 
}
say x;
# x is 10

Python 中的 Lambda 可以写成 Raku 中的块或尖括号块。

Python

l = lambda i: i + 12

Raku

my $l = -> $i { $i + 12 }

另一个用于构造 Lambda 的 Raku 习惯用法是 Whatever 星号,*

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 ** 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 12 -> $i {
    say $i
}
my $j = 1;
while $j < 3 {
    say $j;
    $j += 1
}

(Raku 还有一些其他循环构造:repeat...untilrepeat...whileuntilloop。)

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;
}

在 Raku 中,即使在列表推导之外,使用 `if` 作为语句修饰符(如上所示)也是可以接受的。

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 123 -> $i {
            take $i
        }
    }
}
 
for count() -> $c {
    say $c;
}

Python 中使用 `enumerate()` 和 `.items()` 机制来迭代列表或字典/映射,在 Raku 中都可以使用相同的 kv 方法来实现(因为列表的“键”是其类似数组的数字索引)。

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;
 
# Note that the iteration order will differ from Python 
for %elem-for-symbol.kv -> $symbol$element {
    say "Symbol '$symbol' stands for $element";
}

Lambda 表达式、函数和子例程>§

在 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 中,某些参数可能是“仅关键字”。在 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 speak($word$times{
  say $word for ^$times
}
multi 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 };

列表推导§

后缀语句修饰符和块可以组合起来,在 Raku 中轻松创建列表推导。

Python

print([ i * 2 for i in [3, 9]])                      # OUTPUT: «[6, 18]␤»

Raku

say ( $_ * 2 for 39 );                           # OUTPUT: «(6 18)␤» 
say ( { $^i * 2 } for 39 );                      # OUTPUT: «(6 18)␤» 
say ( -> \i { i * 2 } for 39 );                  # OUTPUT: «(6 18)␤»

可以应用条件语句,但 `if` 关键字位于首位,这与 Python 中 `if` 位于第二位的不同。

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

vs

say ( $_ * 2 if $_ > 1 for 123 );              # 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,9X (2,10) );      # OUTPUT: «(5 13 11 19)␤» 
say ( -> (\i, \j{ i + j } for (3,9X (2,10) );  # OUTPUT: «(5 13 11 19)␤» 
say ( -> ($i$j{ $i + $j } for (3,9X (2,10) );# OUTPUT: «(5 13 11 19)␤» 
say ( { $^a[0+ $^a[1} for (3,9X (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` 特性。

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;
    ...
}

装饰器§

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;

另一种方法是使用特性。

# declare the trait 'greeter' 
multi 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')

对于“进入”和“退出”事件,将块作为参数传递是一种选择。

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.

元组§

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 没有内置的元组类型。您可以使用 Raku 的列表类型或外部模块来获得相同的效果。

my $list1 = (1"two"3"hat");
my $list2 = (56"seven");
say $list1[1];                                 # OUTPUT: «two␤» 
my $list3 = (slip($list1), slip($list2));
my $list4 = (|$list1|$list2);                # equivalent to previous line 
say $list3;                                    # OUTPUT: «(1, two, 3, hat, 5, 6, seven)␤»