返回 Skill 列表
extension
分类: 开发与工程无需 API Key

python-scala-libraries

指南,用于将常见的Python库和惯用法映射到Scala的等效项。在转换使用标准库模块(如json、datetime、os、re、logging)的Python代码,或需要等效的Scala库进行HTTP请求、测试或异步操作时使用。

person作者: jakexiaohubgithub

Python to Scala Library Mappings

JSON Handling

# Python
import json

data = {"name": "Alice", "age": 30}
json_str = json.dumps(data)
parsed = json.loads(json_str)

# With dataclass
from dataclasses import dataclass, asdict

@dataclass
class Person:
    name: str
    age: int

person = Person("Alice", 30)
json.dumps(asdict(person))
// Scala - circe (most popular)
import io.circe._
import io.circe.generic.auto._
import io.circe.syntax._
import io.circe.parser._

case class Person(name: String, age: Int)

val person = Person("Alice", 30)
val jsonStr: String = person.asJson.noSpaces
val parsed: Either[Error, Person] = decode[Person](jsonStr)

// Scala - play-json
import play.api.libs.json._

case class Person(name: String, age: Int)
implicit val personFormat: Format[Person] = Json.format[Person]

val json = Json.toJson(person)
val parsed = json.as[Person]

Date and Time

# Python
from datetime import datetime, date, timedelta
import pytz

now = datetime.now()
today = date.today()
specific = datetime(2024, 1, 15, 10, 30)
formatted = now.strftime("%Y-%m-%d %H:%M:%S")
parsed = datetime.strptime("2024-01-15", "%Y-%m-%d")
tomorrow = now + timedelta(days=1)

# Timezone
utc_now = datetime.now(pytz.UTC)
// Scala - java.time (recommended)
import java.time._
import java.time.format.DateTimeFormatter

val now = LocalDateTime.now()
val today = LocalDate.now()
val specific = LocalDateTime.of(2024, 1, 15, 10, 30)
val formatted = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
val parsed = LocalDate.parse("2024-01-15")
val tomorrow = now.plusDays(1)

// Timezone
val utcNow = ZonedDateTime.now(ZoneOffset.UTC)
val instant = Instant.now()

File Operations

# Python
from pathlib import Path
import os

# Reading
with open("file.txt", "r") as f:
    content = f.read()
    lines = f.readlines()

# Writing
with open("file.txt", "w") as f:
    f.write("Hello")

# Path operations
path = Path("dir/file.txt")
path.exists()
path.parent
path.name
path.suffix
list(Path(".").glob("*.txt"))
os.makedirs("new/dir", exist_ok=True)
// Scala - java.nio.file (standard) + scala.io
import java.nio.file.{Files, Path, Paths}
import scala.io.Source
import scala.util.Using

// Reading
val content = Source.fromFile("file.txt").mkString
Using(Source.fromFile("file.txt")) { source =>
  source.getLines().toList
}

// Writing
Files.writeString(Paths.get("file.txt"), "Hello")

// Path operations
val path = Paths.get("dir/file.txt")
Files.exists(path)
path.getParent
path.getFileName
// Extension: path.toString.split('.').lastOption
Files.createDirectories(Paths.get("new/dir"))

// os-lib (better alternative)
// import os._
// os.read(os.pwd / "file.txt")
// os.write(os.pwd / "file.txt", "Hello")

Regular Expressions

# Python
import re

pattern = r"\d+"
text = "abc123def456"

# Search
match = re.search(pattern, text)
if match:
    print(match.group())

# Find all
matches = re.findall(pattern, text)

# Replace
result = re.sub(pattern, "X", text)

# Split
parts = re.split(r"\s+", "a b  c")
// Scala
val pattern = """\d+""".r
val text = "abc123def456"

// Search
pattern.findFirstIn(text) match {
  case Some(m) => println(m)
  case None => ()
}

// Find all
val matches = pattern.findAllIn(text).toList

// Replace
val result = pattern.replaceAllIn(text, "X")

// Split
val parts = """\s+""".r.split("a b  c").toList

// Pattern matching with regex
val datePattern = """(\d{4})-(\d{2})-(\d{2})""".r
"2024-01-15" match {
  case datePattern(year, month, day) => s"$year/$month/$day"
  case _ => "no match"
}

HTTP Requests

# Python
import requests

response = requests.get("https://api.example.com/data")
data = response.json()

response = requests.post(
    "https://api.example.com/data",
    json={"key": "value"},
    headers={"Authorization": "Bearer token"}
)
// Scala - sttp (recommended)
import sttp.client3._
import sttp.client3.circe._
import io.circe.generic.auto._

val backend = HttpURLConnectionBackend()

val response = basicRequest
  .get(uri"https://api.example.com/data")
  .send(backend)

val postResponse = basicRequest
  .post(uri"https://api.example.com/data")
  .body("""{"key": "value"}""")
  .header("Authorization", "Bearer token")
  .send(backend)

Logging

# Python
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

logger.debug("Debug message")
logger.info("Info message")
logger.warning("Warning message")
logger.error("Error message", exc_info=True)
// Scala - scala-logging with logback
import com.typesafe.scalalogging.LazyLogging

class MyClass extends LazyLogging {
  logger.debug("Debug message")
  logger.info("Info message")
  logger.warn("Warning message")
  logger.error("Error message", exception)
}

// Alternative: slf4j directly
import org.slf4j.LoggerFactory

val logger = LoggerFactory.getLogger(getClass)

Testing

# Python - pytest
import pytest

def test_addition():
    assert 1 + 1 == 2

@pytest.fixture
def sample_data():
    return {"key": "value"}

def test_with_fixture(sample_data):
    assert sample_data["key"] == "value"

@pytest.mark.parametrize("input,expected", [
    (1, 2),
    (2, 4),
])
def test_double(input, expected):
    assert input * 2 == expected
// Scala - ScalaTest
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

class MySpec extends AnyFlatSpec with Matchers {
  "Addition" should "work" in {
    1 + 1 shouldEqual 2
  }

  it should "handle negative numbers" in {
    -1 + 1 shouldEqual 0
  }
}

// Table-driven tests
import org.scalatest.prop.TableDrivenPropertyChecks._

val examples = Table(
  ("input", "expected"),
  (1, 2),
  (2, 4)
)

forAll(examples) { (input, expected) =>
  input * 2 shouldEqual expected
}

// Scala - munit (simpler alternative)
class MySuite extends munit.FunSuite {
  test("addition") {
    assertEquals(1 + 1, 2)
  }
}

Async/Concurrent Operations

# Python
import asyncio

async def fetch_data(url: str) -> str:
    # async HTTP call
    await asyncio.sleep(1)
    return "data"

async def main():
    results = await asyncio.gather(
        fetch_data("url1"),
        fetch_data("url2"),
    )
// Scala - Future (standard)
import scala.concurrent.{Future, ExecutionContext}
import scala.concurrent.ExecutionContext.Implicits.global

def fetchData(url: String): Future[String] = Future {
  Thread.sleep(1000)
  "data"
}

val results: Future[List[String]] = Future.sequence(List(
  fetchData("url1"),
  fetchData("url2")
))

// Scala - cats-effect IO (preferred for FP)
import cats.effect.IO
import cats.syntax.parallel._

def fetchData(url: String): IO[String] = IO.sleep(1.second) *> IO.pure("data")

val results: IO[List[String]] = List(
  fetchData("url1"),
  fetchData("url2")
).parSequence

Configuration

# Python
import os
from dataclasses import dataclass

@dataclass
class Config:
    db_host: str = os.getenv("DB_HOST", "localhost")
    db_port: int = int(os.getenv("DB_PORT", "5432"))
// Scala - pureconfig
import pureconfig._
import pureconfig.generic.auto._

case class Config(dbHost: String, dbPort: Int)

val config = ConfigSource.default.loadOrThrow[Config]

// application.conf (HOCON format)
// db-host = "localhost"
// db-host = ${?DB_HOST}
// db-port = 5432

Command Line Arguments

# Python
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--name", required=True)
parser.add_argument("--count", type=int, default=1)
args = parser.parse_args()
// Scala - scopt
import scopt.OParser

case class Config(name: String = "", count: Int = 1)

val builder = OParser.builder[Config]
val parser = {
  import builder._
  OParser.sequence(
    opt[String]("name").required().action((x, c) => c.copy(name = x)),
    opt[Int]("count").action((x, c) => c.copy(count = x))
  )
}

OParser.parse(parser, args, Config()) match {
  case Some(config) => // use config
  case None => // arguments are bad
}

// Scala - decline (FP style)
import com.monovore.decline._

val nameOpt = Opts.option[String]("name", "Name")
val countOpt = Opts.option[Int]("count", "Count").withDefault(1)

val command = Command("app", "Description") {
  (nameOpt, countOpt).tupled
}

Build Tools Comparison

| Python | Scala | |--------|-------| | pip/poetry | sbt/mill | | requirements.txt | build.sbt | | pyproject.toml | build.sbt | | setup.py | build.sbt | | virtualenv | Project-local dependencies (automatic) |