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

使用 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. 

原文: https://docs.raku.org/language/py-nutshell