Python to Scala OOP Translation
Basic Classes
# Python
class Person:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
def greet(self) -> str:
return f"Hello, I'm {self.name}"
// Scala
class Person(val name: String, val age: Int) {
def greet: String = s"Hello, I'm $name"
}
// Usage
val person = new Person("Alice", 30) // 'new' required for class
Data Classes → Case Classes
# Python
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
@dataclass(frozen=True)
class ImmutablePoint:
x: float
y: float
// Scala - case class is idiomatic
case class Point(x: Double, y: Double)
// Case classes are immutable by default
// They auto-generate: equals, hashCode, toString, copy, apply
// Usage
val p = Point(1.0, 2.0) // No 'new' needed for case class
val p2 = p.copy(x = 3.0) // Creates new instance with x changed
Properties
# Python
class Circle:
def __init__(self, radius: float):
self._radius = radius
@property
def radius(self) -> float:
return self._radius
@radius.setter
def radius(self, value: float):
if value < 0:
raise ValueError("Radius must be non-negative")
self._radius = value
@property
def area(self) -> float:
return 3.14159 * self._radius ** 2
// Scala
class Circle(private var _radius: Double) {
require(_radius >= 0, "Radius must be non-negative")
def radius: Double = _radius
def radius_=(value: Double): Unit = {
require(value >= 0, "Radius must be non-negative")
_radius = value
}
def area: Double = math.Pi * _radius * _radius
}
// Idiomatic Scala: prefer immutable case class
case class Circle(radius: Double) {
require(radius >= 0, "Radius must be non-negative")
def area: Double = math.Pi * radius * radius
}
Inheritance
# Python
class Animal:
def __init__(self, name: str):
self.name = name
def speak(self) -> str:
raise NotImplementedError
class Dog(Animal):
def speak(self) -> str:
return f"{self.name} says woof!"
class Cat(Animal):
def speak(self) -> str:
return f"{self.name} says meow!"
// Scala
abstract class Animal(val name: String) {
def speak: String // Abstract method
}
class Dog(name: String) extends Animal(name) {
override def speak: String = s"$name says woof!"
}
class Cat(name: String) extends Animal(name) {
override def speak: String = s"$name says meow!"
}
Abstract Classes and Interfaces
# Python
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float:
pass
@abstractmethod
def perimeter(self) -> float:
pass
def describe(self) -> str:
return f"Area: {self.area()}, Perimeter: {self.perimeter()}"
// Scala - abstract class
abstract class Shape {
def area: Double
def perimeter: Double
def describe: String = s"Area: $area, Perimeter: $perimeter"
}
// Scala - trait (preferred for interfaces)
trait Shape {
def area: Double
def perimeter: Double
def describe: String = s"Area: $area, Perimeter: $perimeter"
}
// Implementation
case class Rectangle(width: Double, height: Double) extends Shape {
def area: Double = width * height
def perimeter: Double = 2 * (width + height)
}
Multiple Inheritance → Traits
# Python
class Flyable:
def fly(self) -> str:
return "Flying!"
class Swimmable:
def swim(self) -> str:
return "Swimming!"
class Duck(Animal, Flyable, Swimmable):
def speak(self) -> str:
return "Quack!"
// Scala - use traits for mixins
trait Flyable {
def fly: String = "Flying!"
}
trait Swimmable {
def swim: String = "Swimming!"
}
class Duck(name: String) extends Animal(name) with Flyable with Swimmable {
override def speak: String = "Quack!"
}
Static Methods and Class Methods
# Python
class MathUtils:
PI = 3.14159
@staticmethod
def add(a: int, b: int) -> int:
return a + b
@classmethod
def from_string(cls, s: str) -> "MathUtils":
return cls()
// Scala - use companion object
class MathUtils {
// Instance methods here
}
object MathUtils {
val PI: Double = 3.14159
def add(a: Int, b: Int): Int = a + b
def fromString(s: String): MathUtils = new MathUtils()
}
// Usage
MathUtils.add(1, 2)
MathUtils.PI
Factory Pattern
# Python
class Shape:
@staticmethod
def create(shape_type: str) -> "Shape":
if shape_type == "circle":
return Circle()
elif shape_type == "rectangle":
return Rectangle()
raise ValueError(f"Unknown shape: {shape_type}")
// Scala - companion object with apply
sealed trait Shape
case class Circle(radius: Double) extends Shape
case class Rectangle(width: Double, height: Double) extends Shape
object Shape {
def apply(shapeType: String): Shape = shapeType match {
case "circle" => Circle(1.0)
case "rectangle" => Rectangle(1.0, 1.0)
case other => throw new IllegalArgumentException(s"Unknown shape: $other")
}
}
// Usage
val shape = Shape("circle")
Enums
# Python
from enum import Enum, auto
class Color(Enum):
RED = auto()
GREEN = auto()
BLUE = auto()
class Status(Enum):
PENDING = "pending"
APPROVED = "approved"
REJECTED = "rejected"
// Scala 3
enum Color:
case Red, Green, Blue
enum Status(val value: String):
case Pending extends Status("pending")
case Approved extends Status("approved")
case Rejected extends Status("rejected")
// Scala 2 - sealed trait pattern
sealed trait Color
object Color {
case object Red extends Color
case object Green extends Color
case object Blue extends Color
}
Singleton
# Python
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
// Scala - object is a singleton
object Singleton {
def doSomething(): Unit = println("I'm a singleton!")
}
// Usage
Singleton.doSomething()
Special Methods (Dunder Methods)
| Python | Scala |
|--------|-------|
| __init__ | Primary constructor |
| __str__ | toString |
| __repr__ | toString (case classes auto-generate) |
| __eq__ | equals (case classes auto-generate) |
| __hash__ | hashCode (case classes auto-generate) |
| __len__ | length or size method |
| __getitem__ | apply method |
| __setitem__ | update method |
| __iter__ | Extend Iterable trait |
| __add__ | + method |
| __lt__, __le__, etc. | Extend Ordered trait |
# Python
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __str__(self):
return f"Vector({self.x}, {self.y})"
// Scala
case class Vector(x: Double, y: Double) {
def +(other: Vector): Vector = Vector(x + other.x, y + other.y)
// toString auto-generated by case class
}