This section provides a comparison between the Python and Scala programming languages.
It’s intended for programmers who know Python and want to learn about Scala, specifically by seeing examples of how Python language features compare to Scala.
Introduction
Before getting into the examples, this first section provides a relatively brief introduction and summary of the sections that follow.
The two languages are first compared at a high level, and then at an everyday programming level.
High level similarities
At a high level, Scala shares these similarities with Python:
- Both are high-level programming languages, where you don’t have to concern yourself with low-level concepts like pointers and manual memory management
- Both have a relatively simple, concise syntax
- Both support a functional style of programming
- Both are object-oriented programming (OOP) languages
- Both have comprehensions: Python has list comprehensions and Scala has
for
comprehensions
- Both languages have support for lambdas and higher-order functions
- Both can be used with Apache Spark for big data processing
- Both have a wealth of terrific libraries
High level differences
Also at a high level, the differences between Python and Scala are:
- Python is dynamically typed, and Scala is statically typed
- Though it’s dynamically typed, Python supports “gradual typing” with type hints, which are checked by static type checkers, like
mypy
- Though it’s statically typed, Scala features like type inference make it feel like a dynamic language
- Python is interpreted, and Scala code is compiled to .class files, and runs on the Java Virtual Machine (JVM)
- In addition to running on the JVM, the Scala.js project lets you use Scala as a JavaScript replacement
- The Scala Native project lets you write “systems” level code, and compiles to native executables
- Everything in Scala is an expression: constructs like
if
statements, for
loops, match
expressions, and even try
/catch
expressions all have return values
- Scala idioms favor immutability by default: you’re encouraged to use immutable variables and immutable collections
- Scala has excellent support for concurrent and parallel programming
Programming level similarities
This section looks at the similarities you’ll see between Python and Scala when you write code on an everyday basis:
- Scala’s type inference often makes it feel like a dynamically typed language
- Neither language uses semicolons to end expressions
- Both languages support the use of significant indentation rather than braces and parentheses
- The syntax for defining methods is similar
- Both have lists, dictionaries (maps), sets, and tuples
- Both have comprehensions for mapping and filtering
- Both have terrific IDE support
- With Scala 3’s toplevel definitions you can put method, field, and other definitions anywhere
- One difference is that Python can operate without even declaring a single method, while Scala 3 can’t do everything at the toplevel; for instance, a main method (
@main def
) is required to start a Scala application
Programming level differences
Also at a programming level, these are some of the differences you’ll see every day when writing code:
- Programming in Scala feels very consistent:
val
and var
fields are used consistently to define fields and parameters
- Lists, maps, sets, and tuples are all created and accessed similarly; for instance, parentheses are used to create all types—
List(1,2,3)
, Set(1,2,3)
, Map(1->"one")
—just like creating any other Scala class
- Collections classes generally have most of the same higher-order functions
- Pattern matching is used consistently throughout the language
- The syntax that’s used to define functions that are passed into methods is the same syntax that’s used to define anonymous functions
- Scala variables and parameters are defined with the
val
(immutable) or var
(mutable) keywords
- Scala idioms prefer immutable data structures
- Comments: Python uses
#
for comments; Scala uses the C, C++, and Java style: //
, /*...*/
, and /**...*/
- Naming conventions: The Python standard is to use underscores like
my_list
; Scala uses myList
- Scala is statically typed, so you declare types for method parameters, method return values, and in other places
- Pattern matching and
match
expressions are used extensively in Scala (and will change the way you write code)
- Traits are used heavily in Scala; interfaces and abstract classes are used less often in Python
- Scala’s contextual abstractions and term inference provide a collection of different features:
- Extension methods let you easily add new functionality to classes using a clear syntax
- Multiversal equality lets you limit equality comparisons—at compile time—to only those comparisons that make sense
- Scala has state-of-the-art open source functional programming libraries (see the “Awesome Scala” list)
- You can create your own “control structures” and DSLs, thanks to features like objects, by-name parameters, infix notation, optional parentheses, extension methods, higher-order functions, and more
- Scala code can run in the JVM and even be compiled to native images (using Scala Native and GraalVM) for high performance
- Many other goodies: companion classes and objects, macros, numeric literals, multiple parameter lists, intersection types, type-level programming, and more
Features compared with examples
Given that introduction, the following sections provide side-by-side comparisons of Python and Scala programming language features.
Python uses #
for comments, while the Scala comment syntax is the same as languages like C, C++, and Java:
# a comment
|
// a comment
/* ... */
/** ... */
|
Variable assignment
These examples demonstrate how to create variables in Python and Scala.
Create integer and string variables:
x = 1
x = "Hi"
y = """foo
bar
baz"""
|
val x = 1
val x = "Hi"
val y = """foo
bar
baz"""
|
Lists:
x = [1,2,3]
|
val x = List(1,2,3)
|
Dictionary/Map:
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
)
|
Set:
x = {1,2,3}
|
val x = Set(1,2,3)
|
Tuple:
x = (11, "Eleven")
|
val x = (11, "Eleven")
|
If a Scala field is going to be mutable, use var
instead of val
for variable definition:
However, the rule of thumb in Scala is to always use val
unless the variable specifically needs to be mutated.
FP style records
Scala case classes are similar to Python frozen dataclasses.
Constructor definition:
from dataclasses import dataclass, replace
@dataclass(frozen=True)
class Person:
name: str
age: int
|
case class Person(name: String, age: Int)
|
Create and use an instance:
p = Person("Alice", 42)
p.name # Alice
p2 = replace(p, age=43)
|
val p = Person("Alice", 42)
p.name // Alice
val p2 = p.copy(age = 43)
|
OOP style classes and methods
This section provides comparisons of features related to OOP-style classes and methods.
OOP style class, primary constructor:
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")
|
Create and use an instance:
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()
|
One-line method:
def add(a, b): return a + b
|
def add(a: Int, b: Int): Int = a + b
|
Multiline method:
def walkThenRun():
print('walk')
print('run')
|
def walkThenRun() =
println("walk")
println("run")
|
Interfaces, traits, and inheritance
If you’re familiar with Java 8 and newer, Scala traits are similar to those Java interfaces.
Traits are used all the time in Scala, while Python interfaces (Protocols) and abstract classes are used much less often.
Therefore, rather than attempt to compare the two, this example shows how to use Scala traits to build a small solution to a simulated math problem:
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
There are many other ways to use traits with classes and objects, but this gives you a little idea of how they can be used to organize concepts into logical groups of behavior, and then merge them as needed to create a complete solution.
Control structures
This section compares control structures in Python and Scala.
Both languages have constructs like if
/else
, while
, for
loops, and try
.
Scala also has match
expressions.
if
statement, one line:
if x == 1: print(x)
|
if x == 1 then println(x)
|
if
statement, multiline:
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")
|
Returning a value from if
:
min_val = a if a < b else b
|
val minValue = if a < b then a else b
|
if
as the body of a method:
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
loop:
i = 1
while i < 3:
print(i)
i += 1
|
var i = 1
while i < 3 do
println(i)
i += 1
|
for
loop with range:
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
loop with a list:
for i in ints: print(i)
for i in ints:
print(i)
|
for i <- ints do println(i)
|
for
loop, multiple lines:
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")
|
Multiple “range” generators:
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")
|
Generator with guards (if
expressions):
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)
|
Multiple if
conditions per line:
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)
|
Comprehensions:
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
expressions:
# 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")
|
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)
|
Accessing list elements:
a[0] a[1]
|
a(0) a(1) // just like all other method calls
|
Update list elements:
a[0] = 10
a[1] = 20
|
// ArrayBuffer is mutable
a(0) = 10
a(1) = 20
|
Combine two lists:
Iterate over a list:
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’s main sequence classes are List
, Vector
, and ArrayBuffer
.
List
and Vector
are the main classes to use when you want an immutable sequence, and ArrayBuffer
is the main class to use when you want a mutable sequence.
(A “buffer” in Scala is a sequence that can grow and shrink.)
Dictionary/Map
The Python dictionary is like the mutable Scala Map
class.
However, the default Scala map is immutable, and has a number of transformation methods to let you easily create new maps.
Dictionary/Map creation:
my_dict = {
'a': 1,
'b': 2,
'c': 3
}
|
val myMap = Map(
"a" -> 1,
"b" -> 2,
"c" -> 3
)
|
Accessing dictionary/map elements:
my_dict['a'] # 1
|
myMap("a") // 1
|
Dictionary/Map with a for
loop:
for key, value in my_dict.items():
print(key)
print(value)
|
for (key,value) <- myMap do
println(key)
println(value)
|
Scala has other specialized Map
classes for different needs.
Sets
The Python set is similar to the mutable Scala Set
class.
Set creation:
set = {"a", "b", "c"}
|
val set = Set(1,2,3)
|
Duplicate elements:
set = {1,2,1}
# set: {1,2}
|
val set = Set(1,2,1)
// set: Set(1,2)
|
Scala has other specialized Set
classes for different needs.
Tuples
Python and Scala tuples are also similar.
Tuple creation:
t = (11, 11.0, "Eleven")
|
val t = (11, 11.0, "Eleven")
|
Accessing tuple elements:
t[0] # 11
t[1] # 11.0
|
t(0) // 11
t(1) // 11.0
|
Methods on collections classes
Python and Scala have several of the same common functional methods available to them:
If you’re used to using these methods with lambda expressions in Python, you’ll see that Scala has a similar approach with methods on its collections classes.
To demonstrate this functionality, here are two sample lists:
numbers = [1,2,3] // python
val numbers = List(1,2,3) // scala
Those lists are used in the following table, that shows how to apply mapping and filtering algorithms to it.
Mapping with a comprehension:
x = [i * 10 for i in numbers]
|
val x = for i <- numbers yield i * 10
|
Filtering with a comprehension:
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
|
Mapping & filtering with a comprehension:
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
|
Mapping:
x = map(lambda x: x * 10, numbers)
|
val x = numbers.map(_ * 10)
|
Filtering:
f = lambda x: x > 1
x = filter(f, numbers)
|
val x = numbers.filter(_ > 1)
|
Scala collections methods
Scala collections classes have over 100 functional methods to simplify your code.
In Python, some of these functions are available in the itertools
module.
In addition to map
, filter
, and reduce
, other commonly-used methods in Scala are listed below.
In those method examples:
c
refers to a collection
p
is a predicate
f
is a function, anonymous function, or method
n
refers to an integer value
These are some of the filtering methods that are available:
Method |
Description |
c1.diff(c2) |
Returns the difference of the elements in c1 and c2 . |
c.distinct |
Returns the unique elements in c . |
c.drop(n) |
Returns all elements in the collection except the first n elements. |
c.filter(p) |
Returns all elements from the collection for which the predicate is true . |
c.head |
Returns the first element of the collection. (Throws a NoSuchElementException if the collection is empty.) |
c.tail |
Returns all elements from the collection except the first element. (Throws a UnsupportedOperationException if the collection is empty.) |
c.take(n) |
Returns the first n elements of the collection c . |
Here are a few transformer methods:
Method |
Description |
c.flatten |
Converts a collection of collections (such as a list of lists) to a single collection (single list). |
c.flatMap(f) |
Returns a new collection by applying f to all elements of the collection c (like map ), and then flattening the elements of the resulting collections. |
c.map(f) |
Creates a new collection by applying f to all elements of the collection c . |
c.reduce(f) |
Applies the “reduction” function f to successive elements in c to yield a single value. |
c.sortWith(f) |
Returns a version of c that’s sorted by the comparison function f . |
Some common grouping methods:
Method |
Description |
c.groupBy(f) |
Partitions the collection into a Map of collections according to f . |
c.partition(p) |
Returns two collections according to the predicate p . |
c.span(p) |
Returns a collection of two collections, the first created by c.takeWhile(p) , and the second created by c.dropWhile(p) . |
c.splitAt(n) |
Returns a collection of two collections by splitting the collection c at element n . |
Some informational and mathematical methods:
Method |
Description |
c1.containsSlice(c2) |
Returns true if c1 contains the sequence c2 . |
c.count(p) |
Counts the number of elements in c where p is true . |
c.distinct |
Returns the unique elements in c . |
c.exists(p) |
Returns true if p is true for any element in the collection. |
c.find(p) |
Returns the first element that matches p . The element is returned as Option[A] . |
c.min |
Returns the smallest element from the collection. (Can throw java.lang.UnsupportedOperationException.) |
c.max |
Returns the largest element from the collection. (Can throw java.lang.UnsupportedOperationException.) |
c slice(from, to) |
Returns the interval of elements beginning at element from , and ending at element to . |
c.sum |
Returns the sum of all elements in the collection. (Requires an Ordering be defined for the elements in the collection.) |
Here are a few examples that demonstrate how these methods work on a list:
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)
These methods show a common pattern in Scala: Functional methods that are available on objects.
None of these methods mutate the initial list a
; instead, they all return the data shown after the comments.
There are many more methods available, but hopefully these descriptions and examples give you a taste of the power that’s available in the pre-built collections methods.
Enums
This section compares enums (enumerations) in Python and Scala 3.
Creating enums:
from enum import Enum, auto
class Color(Enum):
RED = auto()
GREEN = auto()
BLUE = auto()
|
enum Color:
case Red, Green, Blue
|
Values and comparison:
Color.RED == Color.BLUE # False
|
Color.Red == Color.Blue // false
|
Parameterized enums:
N/A
|
enum Color(val rgb: Int):
case Red extends Color(0xFF0000)
case Green extends Color(0x00FF00)
case Blue extends Color(0x0000FF)
|
User-defined enum members:
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
|
Concepts that are unique to Scala
There are other concepts in Scala which currently don’t have equivalent functionality in Python.
Follow the links below for more details:
- Most concepts related to contextual abstractions, such as extension methods, type classes, implicit values
- Scala allows multiple parameter lists, which enables features like partially-applied functions, and the ability to create your own DSLs
- The ability to create your own control structures and DSLs
- Multiversal equality: the ability to control at compile time what equality comparisons make sense
- Infix methods
- Macros
Scala and virtual environments
In Scala, there is no need to explicitly set up the equivalent of a Python virtual environment. By default, Scala build tools manage project dependencies such that users do not have to think about manual package installation. For example, using the sbt
build tool, we specify dependencies inside build.sbt
file under libraryDependencies
setting, then executing
automatically resolves all dependencies for that particular project. The location of downloaded dependencies is largely an implementation detail of the build tool, and users do not have to interact with these downloaded dependencies directly. For example, if we delete the whole sbt dependencies cache, on the next compilation of the project, sbt simply resolves and downloads all the required dependencies again, automatically.
This differs from Python, where by default dependencies are installed in system-wide or user-wide directories, so to obtain an isolated environment on a per-project basis one has to create a corresponding virtual environment. For example, using the venv
module, we might create one for a particular project like so
cd myapp
python3 -m venv myapp-env
source myapp-env/bin/activate
pip install -r requirements.txt
This installs all the dependencies under the project’s myapp/myapp-env
directory and alters the shell environmental variable PATH
to look up dependencies from myapp-env
.
None of this manual process is necessary in Scala.