Scala 3 — Book

Scala for Python Developers

Language

本节提供了 Python 和 Scala 编程语言之间的比较。 它适用于懂得 Python 并希望了解 Scala 的程序员,特别是通过查看 Python 语言特性与 Scala 比较的示例。

介绍

在进入示例之前,第一部分提供了以下部分的相对简短的介绍和总结。 这两种语言首先在高层次上进行比较,然后在日常编程层次上进行比较。

高层次相似性

在高层次上,Scala 与 Python 有这些相似之处

  • 两者都是高级编程语言,您不必关心指针和手动内存管理等低级概念
  • 两者都有一个相对简单、简洁的语法
  • 两者都支持函数式编程
  • 两者都是面向对象的编程 (OOP) 语言
  • 两者都有推导:Python 有列表推导,Scala 有 for 推导
  • 两种语言都支持 lambdas 和 高阶函数
  • 两者都可以与 Apache Spark 一起用于大数据处理
  • 两者都有很多很棒的库

高层次差异

同样在高层次上,Python 和 Scala 之间的_差异_是:

  • Python 是动态类型的,Scala 是静态类型的
    • 虽然它是静态类型的,但 Scala 的类型推断等特性让它感觉像是一门动态语言
  • Python 被解释,Scala 代码被编译成 .class 文件,并在 Java 虚拟机 (JVM) 上运行
  • 除了在 JVM 上运行之外,Scala.js 项目允许您使用 Scala 作为 JavaScript 替代品
  • Scala Native 项目可让您编写“系统”级代码,并编译为本机可执行文件
  • Scala 中的一切都是一个_表达式_:像 if 语句、for 循环、match 表达式,甚至 try/catch 表达式都有返回值
  • Scala 习惯默认不变性:鼓励您使用不可变变量和不可变集合
  • Scala 对并发和并行编程有很好的支持

编程层次相似性

本节介绍您在日常编写代码时会看到 Python 和 Scala 之间的相似之处:

  • Scala 的类型推断常常让人感觉像是一种动态类型语言
  • 两种语言都不使用分号来结束表达式
  • 两种语言都支持使用重要的缩进而不是大括号和圆括号
  • 定义方法的语法类似
  • 两者都有列表、字典(映射)、集合和元组
  • 两者都有映射和过滤的推导
  • 使用 Scala 3 的顶级定义,您可以将方法、字段和其他定义放在任何地方
    • 一个区别是 Python 甚至可以在不声明单个方法的情况下运行,而 Scala 3 不能在顶层做_所有事_;例如,启动 Scala 应用程序需要一个 main 方法 (@main def)

编程层次差异

同样在编程级别,这些是您在编写代码时每天都会看到的一些差异:

  • 在 Scala 中编程感觉非常一致:
    • valvar 字段用于定义字段和参数
    • 列表、映射、集合和元组都以类似方式创建和访问;例如,括号用于创建所有类型—List(1,2,3), Set(1,2,3), Map(1->"one")—就像创建任何其他 Scala 类
    • 集合类 通常具有大部分相同的高阶函数
    • 模式匹配在整个语言中一致使用
    • 用于定义传递给方法的函数的语法与用于定义匿名函数的语法相同
  • Scala 变量和参数使用 val(不可变)或 var(可变)关键字定义
  • Scala 习惯用法更喜欢不可变的数据结构
  • Scala 通过 IntelliJ IDEA 和 Microsoft VS Code 提供了极好的 IDE 支持
  • 注释:Python 使用 # 表示注释; Scala 使用 C、C++ 和 Java 样式:///*...*//**...*/
  • 命名约定:Python 标准是使用下划线,例如 my_list; Scala 使用 myList
  • Scala 是静态类型的,因此您可以声明方法参数、方法返回值和其他地方的类型
  • 模式匹配和 match 表达式在 Scala 中被广泛使用(并且会改变你编写代码的方式)
  • Scala 中大量使用 trait;接口和抽象类在 Python 中使用较少
  • Scala 的 上下文抽象 和 _术语推导_提供了一系列不同的特性:
    • [扩展方法][extension-method]让您使用清晰的语法轻松地向类添加新功能
    • 多元等式 让您限制相等比较—在编译时——只有那些有意义的比较
  • Scala 拥有最先进的开源函数式编程库(参见 “Awesome Scala” 列表
  • 借助对象、按名称参数、中缀表示法、可选括号、扩展方法、高阶函数等功能,您可以创建自己的“控制结构”和 DSL
  • Scala 代码可以在 JVM 中运行,甚至可以编译为原生代码(使用 Scala NativeGraalVM .org)) 实现高性能
  • 许多其他好东西:样例类、伴生类和对象、宏、联合交集 类型、顶级定义、数字字面量、多参数列表和更多的

特性比较示例

鉴于该介绍,以下部分提供了 Python 和 Scala 编程语言功能的并排比较。

注释

Python 使用 # 表示注释,而 Scala 的注释语法与 C、C++ 和 Java 等语言相同:

# a comment
// a comment
/* ... */
/** ... */

变量赋值

这些例子演示了如何在 Python 和 Scala 中创建变量。

创建整数和字符串变量:

x = 1
x = "Hi"
y = """foo
       bar
       baz"""
val x = 1
val x = "Hi"
val y = """foo
           bar
           baz"""

列表:

x = [1,2,3]
val x = List(1,2,3)

字典/映射:

x = {
  "Toy Story": 8.3,
  "Forrest Gump": 8.8,
  "Cloud Atlas": 7.4
}
val x = Map(
  "Toy Story" -> 8.3,
  "Forrest Gump" -> 8.8,
  "Cloud Atlas" -> 7.4
)

集合:

x = {1,2,3}
val x = Set(1,2,3)

元组:

x = (11, "Eleven")
val x = (11, "Eleven")

如果 Scala 字段是可变的,请使用 var 而不是 val 来定义变量:

var x = 1
x += 1

然而,Scala 中的经验法则是始终使用 val,除非变量确实需要被改变。

OOP 风格的类和方法

本节比较了与 OOP 风格的类和方法相关的特性。

OOP 风格类,主构造函数:

class Person(object):
  def __init__(self, name):
    self.name = name

  def speak(self):
    print(f'Hello, my name is {self.name}')
class Person (var name: String):
  def speak() = println(s"Hello, my name is $name")

创建和使用实例:

p = Person("John")
p.name   # John
p.name = 'Fred'
p.name   # Fred
p.speak()
val p = Person("John")
p.name   // John
p.name = "Fred"
p.name   // Fred
p.speak()

单行方法:

def add(a, b): return a + b
def add(a: Int, b: Int): Int = a + b

多行方法:

def walkThenRun():
  print('walk')
  print('run')
def walkThenRun() =
  println("walk")
  println("run")

接口,traits,和继承

如果您熟悉 Java 8 和更新版本,Scala trait 类似于那些 Java 接口。 Traits 在 Scala 中一直使用,而 Python 接口和抽象类的使用频率要低得多。 因此,与其试图比较两者,不如用这个例子来展示如何使用 Scala trait来构建一个模拟数学问题的小解决方案:

trait Adder:
  def add(a: Int, b: Int) = a + b

trait Multiplier:
  def multiply(a: Int, b: Int) = a * b

// create a class from the traits
class SimpleMath extends Adder, Multiplier
val sm = new SimpleMath
sm.add(1,1)        // 2
sm.multiply(2,2)   // 4

还有许多其他方法可以将 trait 与类和对象一起使用,但这让您了解如何使用它们将概念组织成行为的逻辑组,然后根据需要将它们合并以创建一个完整的解决方案。

控制结构

本节比较 Python 和 Scala 中的控制结构。 两种语言都有类似 if/elsewhilefor 循环和 try 的结构。 Scala 也有 match 表达式。

if 语句,单行:

if x == 1: print(x)
if x == 1 then println(x)

if 语句,多行:

if x == 1:
  print("x is 1, as you can see:")
  print(x)
if x == 1 then
  println("x is 1, as you can see:")
  println(x)

if, else if, else:

if x < 0:
  print("negative")
elif x == 0:
  print("zero")
else:
  print("positive")
if x < 0 then
  println("negative")
else if x == 0 then
  println("zero")
else
  println("positive")

if 返回值:

min_val = a if a < b else b
val minValue = if a < b then a else b

if 作为方法体:

def min(a, b):
  return a if a < b else b
def min(a: Int, b: Int): Int =
  if a < b then a else b

while 循环:

i = 1
while i < 3:
  print(i)
  i += 1
var i = 1
while i < 3 do
  println(i)
  i += 1

有范围的 for 循环:

for i in range(0,3):
  print(i)
// preferred
for i <- 0 until 3 do println(i)

// also available
for (i <- 0 until 3) println(i)

// multiline syntax
for
  i <- 0 until 3
do
  println(i)

列表的 for 循环:

for i in ints: print(i)

for i in ints:
  print(i)
for i <- ints do println(i)

for 循环,多行:

for i in ints:
  x = i * 2
  print(f"i = {i}, x = {x}")
for
  i <- ints
do
  val x = i * 2
  println(s"i = $i, x = $x")

多“范围”生成器:

for i in range(1,3):
  for j in range(4,6):
    for k in range(1,10,3):
      print(f"i = {i}, j = {j}, k = {k}")
for
  i <- 1 to 2
  j <- 4 to 5
  k <- 1 until 10 by 3
do
  println(s"i = $i, j = $j, k = $k")

带守卫(if 表达式)的生成器:

for i in range(1,11):
  if i % 2 == 0:
    if i < 5:
      print(i)
for
  i <- 1 to 10
  if i % 2 == 0
  if i < 5
do
  println(i)

每行有多个 if 条件:

for i in range(1,11):
  if i % 2 == 0 and i < 5:
    print(i)
for
  i <- 1 to 10
  if i % 2 == 0 && i < 5
do
  println(i)

推导:

xs = [i * 10 for i in range(1, 4)]
# xs: [10,20,30]
val xs = for i <- 1 to 3 yield i * 10
// xs: Vector(10, 20, 30)

match 表达式:

# From 3.10, Python supports structural pattern matching
# You can also use dictionaries for basic “switch” functionality
match month:
  case 1:
    monthAsString = "January"
  case 2:
    monthAsString = "February"
  case _:
    monthAsString = "Other"
val monthAsString = month match
  case 1 => "January"
  case 2 => "February"
  _ => "Other"

switch/match:

# Only from Python 3.10
match i:
  case 1 | 3 | 5 | 7 | 9:
    numAsString = "odd"
  case 2 | 4 | 6 | 8 | 10:
    numAsString = "even"
  case _:
    numAsString = "too big"
val numAsString = i match
  case 1 | 3 | 5 | 7 | 9 => "odd"
  case 2 | 4 | 6 | 8 | 10 => "even"
  case _ => "too big"

try, catch, finally:

try:
  print(a)
except NameError:
  print("NameError")
except:
  print("Other")
finally:
  print("Finally")
try
  writeTextToFile(text)
catch
  case ioe: IOException =>
    println(ioe.getMessage)
  case fnf: FileNotFoundException =>
    println(fnf.getMessage)
finally
  println("Finally")

匹配表达式和模式匹配是 Scala 编程体验的重要组成部分,但这里只展示了几个 match 表达式功能。 有关更多示例,请参见 控制结构 页面。

集合类

本节比较 Python 和 Scala 中可用的 集合类,包括列表、字典/映射、集合和元组。

列表

Python 有它的列表,Scala 有几个不同的专门的可变和不可变序列类,具体取决于您的需要。 因为 Python 列表是可变的,所以它最直接地与 Scala 的 ArrayBuffer 进行比较。

Python 列表 & Scala序列:

Match expressions and pattern matching are a big part of the Scala programming experience, but only a few match expression features are shown here. See the Control Structures page for many more examples.

Collections classes

This section compares the collections classes that are available in Python and Scala, including lists, dictionaries/maps, sets, and tuples.

Lists

Where Python has its list, Scala has several different specialized mutable and immutable sequence classes, depending on your needs. Because the Python list is mutable, it most directly compares to Scala’s ArrayBuffer.

Python list & Scala sequences:

a = [1,2,3]
// use different sequence classes
// as needed
val a = List(1,2,3)
val a = Vector(1,2,3)
val a = ArrayBuffer(1,2,3)

获取列表元素:

a[0]
a[1]
a(0)
a(1)
// just like all other method calls

更新列表元素:

a[0] = 10
a[1] = 20
// ArrayBuffer is mutable
a(0) = 10
a(1) = 20

合并两个列表:

c = a + b
val c = a ++ b

遍历列表:

for i in ints: print(i)

for i in ints:
  print(i)
// preferred
for i <- ints do println(i)

// also available
for (i <- ints) println(i)

Scala 的主要序列类是 ListVectorArrayBufferListVector 是当你想要一个不可变序列时使用的主要类,而 ArrayBuffer 是当你想要一个可变序列时使用的主要类。 (Scala 中的“缓冲区”是一个可以增长和缩小的序列。)

字典/映射

Python 字典就像_可变的_ Scala Map 类。 但是,默认的 Scala 映射是_不可变的_,并且有许多转换方法可以让您轻松创建新映射。

字典/映射 创建:

my_dict = {
  'a': 1,
  'b': 2,
  'c': 3
}
val myMap = Map(
  "a" -> 1,
  "b" -> 2,
  "c" -> 3
)

获取字典/映射元素:

my_dict['a']   # 1
myMap("a")   // 1

for 循环的字典/映射:

for key, value in my_dict.items():
  print(key)
  print(value)
for (key,value) <- myMap do
  println(key)
  println(value)

Scala 有其他专门的 Map 类来满足不同的需求。

集合

Python 集合类似于_可变的_ Scala Set 类。

集合创建:

set = {"a", "b", "c"}
val set = Set(1,2,3)

重复元素:

set = {1,2,1}
# set: {1,2}
val set = Set(1,2,1)
// set: Set(1,2)

Scala 有其他专门的 Set 类来满足不同的需求。

元组

Python 和 Scala 元组也很相似。

元组创建:

t = (11, 11.0, "Eleven")
val t = (11, 11.0, "Eleven")

获取元组元素:

t[0]   # 11
t[1]   # 11.0
t(0)   // 11
t(1)   // 11.0

集合类上的方法

Python 和 Scala 有几个相同的常用函数方法可供它们使用:

  • map
  • filter
  • reduce

如果您习惯于在 Python 中将这些方法与 lambda 表达式一起使用,您会发现 Scala 在其集合类中使用了类似的方法。 为了演示此功能,这里有两个示例列表:

numbers = (1,2,3)           // python
val numbers = List(1,2,3)   // scala

下表中使用了这些列表,显示了如何对其应用映射和过滤算法。

映射与推导:

x = [i * 10 for i in numbers]
val x = for i <- numbers yield i * 10

有推导的过滤:

evens = [i for i in numbers if i % 2 == 0]
val evens = numbers.filter(_ % 2 == 0)
// or
val evens = for i <- numbers if i % 2 == 0 yield i

映射 & 有推导的过滤:

x = [i * 10 for i in numbers if i % 2 == 0]
val x = numbers.filter(_ % 2 == 0).map(_ * 10)
// or
val x = for i <- numbers if i % 2 == 0 yield i * 10

映射:

x = map(lambda x: x * 10, numbers)
val x = numbers.map(_ * 10)

过滤:

f = lambda x: x > 1
x = filter(f, numbers)
val x = numbers.filter(_ > 1)

Scala 集合方法:

Scala 集合类有超过 100 种功能方法来简化您的代码。 除了 mapfilterreduce,下面列出了其他常用的方法。 在这些方法示例中:

  • c 指的是一个集合
  • p 是谓词
  • f 是一个函数、匿名函数或方法
  • n 指的是一个整数值

以下是一些可用的过滤方法:

方法 说明
c1.diff(c2) 返回 c1c2 中元素的差异。
c.distinct 返回 c 中的唯一元素。
c.drop(n) 返回集合中除前 n 个元素之外的所有元素。
c.filter(p) 返回集合中谓词为 true 的所有元素。
c.head 返回集合的第一个元素。 (如果集合为空,则抛出 NoSuchElementException。)
c.tail 返回集合中除第一个元素之外的所有元素。 (如果集合为空,则抛出 UnsupportedOperationException。)
c.take(n) 返回集合 c 的前 n 个元素。

以下是一些转换方法:

方法 说明
c.flatten 将集合的集合(例如列表列表)转换为单个集合(单个列表)。
c.flatMap(f) 通过将 f 应用于集合 c 的所有元素(如 map)返回一个新集合,然后将结果集合的元素展平。
c.map(f) 通过将 f 应用于集合 c 的所有元素来创建一个新集合。
c.reduce(f) reduction 函数 f 应用于 c 中的连续元素以产生单个值。
c.sortWith(f) 返回由比较函数 f 排序的 c 版本。

一些常见的分组方法:

方法 说明
c.groupBy(f) 根据 f 将集合划分为集合的 Map
c.partition(p) 根据谓词 p 返回两个集合。
c.span(p) 返回两个集合的集合,第一个由 c.takeWhile(p) 创建,第二个由 c.dropWhile(p) 创建。
c.splitAt(n) 通过在元素 n 处拆分集合 c 来返回两个集合的集合。

一些信息和数学方法:

方法 说明
c1.containsSlice(c2) 如果 c1 包含序列 c2,则返回 true
c.count(p) 计算 c 中元素的数量,其中 ptrue
c.distinct 返回 c 中的不重复的元素。
c.exists(p) 如果集合中任何元素的 ptrue ,则返回 true
c.find(p) 返回匹配 p 的第一个元素。该元素以 Option[A] 的形式返回。
c.min 返回集合中的最小元素。 (可以抛出_java.lang.UnsupportedOperationException_。)
c.max 返回集合中的最大元素。 (可以抛出_java.lang.UnsupportedOperationException_。)
c slice(from, to) 返回从元素 from 开始到元素 to 结束的元素间隔。
c.sum 返回集合中所有元素的总和。 (需要为集合中的元素定义 Ordering。)

以下是一些示例,展示了这些方法如何在列表上工作:

val a = List(10, 20, 30, 40, 10)      // List(10, 20, 30, 40, 10)
a.distinct                            // List(10, 20, 30, 40)
a.drop(2)                             // List(30, 40, 10)
a.dropRight(2)                        // List(10, 20, 30)
a.dropWhile(_ < 25)                   // List(30, 40, 10)
a.filter(_ < 25)                      // List(10, 20, 10)
a.filter(_ > 100)                     // List()
a.find(_ > 20)                        // Some(30)
a.head                                // 10
a.headOption                          // Some(10)
a.init                                // List(10, 20, 30, 40)
a.intersect(List(19,20,21))           // List(20)
a.last                                // 10
a.lastOption                          // Some(10)
a.slice(2,4)                          // List(30, 40)
a.tail                                // List(20, 30, 40, 10)
a.take(3)                             // List(10, 20, 30)
a.takeRight(2)                        // List(40, 10)
a.takeWhile(_ < 30)                   // List(10, 20)

这些方法展示了 Scala 中的一个常见模式:对象上可用的函数式方法。 这些方法都不会改变初始列表“a”; 相反,它们都返回的数据在注释后显示。

还有更多可用的方法,但希望这些描述和示例能让您体验到预建集合方法的强大功能。

枚举

本节比较 Python 和 Scala 3 中的枚举。

创建枚举:

from enum import Enum, auto
class Color(Enum):
    RED = auto()
    GREEN = auto()
    BLUE = auto()
enum Color:
  case Red, Green, Blue

值和比较:

Color.RED == Color.BLUE  # False
Color.Red == Color.Blue  // false

参数化枚举:

N/A
enum Color(val rgb: Int):
  case Red   extends Color(0xFF0000)
  case Green extends Color(0x00FF00)
  case Blue  extends Color(0x0000FF)

用户定义枚举成员:

N/A
enum Planet(
    mass: Double,
    radius: Double
  ):
  case Mercury extends
    Planet(3.303e+23, 2.4397e6)
  case Venus extends
    Planet(4.869e+24, 6.0518e6)
  case Earth extends
    Planet(5.976e+24, 6.37814e6)
  // more planets ...

  // fields and methods
  private final val G = 6.67300E-11
  def surfaceGravity = G * mass /
    (radius * radius)
  def surfaceWeight(otherMass: Double)
    = otherMass * surfaceGravity

Scala 独有的概念

Scala 中的有些概念目前在 Python 中没有等效的功能。 请点击以下链接了解更多详情:

  • 大多数与上下文抽象相关的概念,如扩展方法、类型类、隐式值
  • Scala 允许多参数列表,从而实现部分应用函数等特性,以及创建自己的 DSL 的能力
  • 样例类,对于函数式编程和模式匹配非常有用
  • 创建自己的控制结构和 DSL 的能力
  • 模式匹配和 match 表达式
  • 多重等式:在编译时控制哪些等式比较有意义的能力
  • 中缀方法
  • 宏和元编程

Scala 和虚拟环境

在 Scala 中,无需显式设置 Python 虚拟环境的等价物。默认情况下,Scala 构建工具管理项目依赖项,因此用户不必考虑手动安装包。例如,使用 sbt 构建工具,我们在 libraryDependencies 设置下的 build.sbt 文件中指定依赖关系,然后执行

cd myapp
sbt compile

自动解析该特定项目的所有依赖项。 下载依赖的位置很大程度上是构建工具的一个实现细节,用户不必直接与这些下载的依赖交互。 例如,如果我们删除整个 sbt 依赖项缓存,则在项目的下一次编译时,sbt 会自动重新解析并再次下载所有必需的依赖项。

这与 Python 不同,默认情况下,依赖项安装在系统范围或用户范围的目录中,因此要在每个项目的基础上获得隔离环境,必须创建相应的虚拟环境。 例如,使用 venv 模块,我们可以像这样为特定项目创建一个

cd myapp
python3 -m venv myapp-env
source myapp-env/bin/activate
pip install -r requirements.txt

这会在项目的 myapp/myapp-env 目录下安装所有依赖项,并更改 shell 环境变量 PATH 以从 myapp-env 查找依赖项。 在 Scala 中,这些手动过程都不是必需的。

Contributors to this page: