Python

Equivalencias idiomáticas entre Kotlin y Python. Kotlin se presenta como referencia en cada concepto.

Modelado de dominio y datos

Cada lenguaje ofrece herramientas distintas para dar forma a un dominio: distinguir qué garantías son del compilador y cuáles son solo convención evita errores de diseño.

Equivalencia desde Kotlin

Clases de valor

Cercana

Una dataclass congelada distingue EvaluationBudget de int en las anotaciones y genera igualdad por valor. La validación del invariante debe escribirse explícitamente en __post_init__, ya que Python no la deriva automáticamente.

Referencia Kotlin: Declarar un tipo nominal para un dato primitivo y validar su invariante al construirse.

Mecanismo
@dataclass(frozen=True) con validación en __post_init__.
Idea conservada
El tipo distingue EvaluationBudget de int en las anotaciones, valida el invariante al construirse y genera igualdad por valor.
Riesgo de diseño
Confiar solo en las anotaciones de tipo para impedir pasar un int sin validar; Python no las impone en tiempo de ejecución.
Recomendación
Valida el invariante en __post_init__ y trata las anotaciones como documentación reforzada por un verificador estático, no como una garantía en tiempo de ejecución.
Convención del curso
Valida en __post_init__, no en un método de fábrica aparte.
Biblioteca estándar
dataclasses.dataclass(frozen=True).

Diferencias relevantes

  • Python no impone el tipo en tiempo de ejecución fuera de esta clase: nada evita construir el dato de otra forma salvo la disciplina y un verificador estático.
  • frozen=True impide reasignar remaining después de construir la instancia, pero no impide mutar objetos internos si los hubiera.

__post_init__ valida el invariante tras construir la instancia congelada.

Kotlin
@JvmInline
value class EvaluationBudget(val remaining: Int) {
    init {
        require(remaining > 0) { "El presupuesto debe ser positivo." }
    }
}

val budget = EvaluationBudget(1000) // remaining = 1000
Python
from dataclasses import dataclass

@dataclass(frozen=True, slots=True)
class EvaluationBudget:
    remaining: int

    def __post_init__(self) -> None:
        if self.remaining <= 0:
            raise ValueError("El presupuesto debe ser positivo.")

budget = EvaluationBudget(1000)  # remaining = 1000

Fuentes

Equivalencia desde Kotlin

Construcción controlada

Cercana

En Python, fábrica que valida por convención expresa la responsabilidad del fixture: Impide crear una edad negativa.

Referencia Kotlin: Un constructor privado y una fábrica validan el invariante.

Mecanismo
fábrica que valida por convención
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar fábrica que valida por convención.
Recomendación
Expón el contrato del fixture y usa fábrica que valida por convención solo para la garantía que realmente ofrece.

Diferencias relevantes

  • La privacidad de construcción y la validación son responsabilidades distintas; declara ambas.
  • La forma idiomática de Python debe conservarse aunque la sintaxis difiera de Kotlin.

Impide crear una edad negativa.

def age(value: int) -> int:
    if value < 0: raise ValueError("age")
    return value

Fuentes

Equivalencia desde Kotlin

Clases de datos

Cercana

En Python, @dataclass expresa la responsabilidad del fixture: Modela una medición con igualdad por sus datos.

Referencia Kotlin: data class genera operaciones de valor conocidas.

Mecanismo
@dataclass
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar @dataclass.
Recomendación
Expón el contrato del fixture y usa @dataclass solo para la garantía que realmente ofrece.

Diferencias relevantes

  • No atribuyas copia, destructuración o igualdad estructural a un mecanismo que no las provee.
  • La forma idiomática de Python debe conservarse aunque la sintaxis difiera de Kotlin.

Modela una medición con igualdad por sus datos.

@dataclass(frozen=True)
class Measurement:
    name: str
    value: int

Fuentes

Equivalencia desde Kotlin

Singletons

Cercana

En Python, instancia de módulo expresa la responsabilidad del fixture: Expone una configuración compartida única.

Referencia Kotlin: object declara una instancia única del lenguaje.

Mecanismo
instancia de módulo
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar instancia de módulo.
Recomendación
Expón el contrato del fixture y usa instancia de módulo solo para la garantía que realmente ofrece.

Diferencias relevantes

  • Distingue unicidad por módulo, por proceso y por contenedor de inyección.
  • La forma idiomática de Python debe conservarse aunque la sintaxis difiera de Kotlin.

Expone una configuración compartida única.

settings = {"retries": 3}

Fuentes

Equivalencia desde Kotlin

Enumeraciones

Cercana

En Python, enum.Enum expresa la responsabilidad del fixture: Representa un estado finito de una tarea.

Referencia Kotlin: enum class declara constantes nombradas y puede contener datos o comportamiento.

Mecanismo
enum.Enum
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar enum.Enum.
Recomendación
Expón el contrato del fixture y usa enum.Enum solo para la garantía que realmente ofrece.

Diferencias relevantes

  • Un enum de constantes y una unión de variantes con datos no son automáticamente equivalentes.
  • La forma idiomática de Python debe conservarse aunque la sintaxis difiera de Kotlin.

Representa un estado finito de una tarea.

class Status(Enum):
    NEW = "new"
    DONE = "done"

Fuentes

Equivalencia desde Kotlin

Jerarquías selladas

Aproximada

match despacha según la forma de cada dataclass, pero Python no comprueba la exhaustividad en tiempo de compilación: un caso faltante solo se nota en tiempo de ejecución, o pasa desapercibido si hay un case _ final.

Referencia Kotlin: Declarar una familia cerrada de alternativas para que el compilador exija manejar cada caso.

Mecanismo
Unión de tipos (Completed | Failed) y match estructural sobre cada dataclass.
Idea conservada
Cada variante es una clase de datos independiente; match despacha según la forma de cada una.
Riesgo de diseño
Agregar un case _ genérico que ignora el resultado, ocultando el error cuando aparece una tercera variante.
Recomendación
Evita un case _ silencioso; si necesitas uno, haz que levante una excepción para detectar variantes no manejadas.
Convención del curso
No agregues un case _ que ignore el resultado sin levantar una excepción.
Mecanismo del lenguaje
match estructural (PEP 634) sobre una unión de tipos.
Biblioteca estándar
dataclasses para las variantes.

Diferencias relevantes

  • Python no comprueba la exhaustividad en tiempo de compilación; un caso faltante solo falla en tiempo de ejecución si no hay un case _ final, o pasa desapercibido si lo hay.
  • Un verificador de tipos externo (por ejemplo pyright) puede señalar match no exhaustivos sobre una unión, pero no es parte del intérprete.

match despacha por la forma de cada dataclass, sin comprobación de exhaustividad del intérprete.

Kotlin
sealed interface SearchResult {
    data class Completed(val bestValue: Double) : SearchResult
    data class Failed(val reason: String) : SearchResult
}

fun describe(result: SearchResult): String = when (result) {
    is SearchResult.Completed -> "Completed(${result.bestValue})"
    is SearchResult.Failed -> "Failed(${result.reason})"
}
Python
from dataclasses import dataclass

@dataclass(frozen=True)
class Completed:
    best_value: float

@dataclass(frozen=True)
class Failed:
    reason: str

SearchResult = Completed | Failed

def describe(result: SearchResult) -> str:
    match result:
        case Completed(best_value=value):
            return f"Completed({value})"
        case Failed(reason=reason):
            return f"Failed({reason})"

Fuentes

Equivalencia desde Kotlin

Alias de tipos

Cercana

En Python, TypeAlias expresa la responsabilidad del fixture: Da un nombre legible a una firma de transformación.

Referencia Kotlin: typealias crea un nombre alternativo, no un nuevo tipo nominal.

Mecanismo
TypeAlias
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar TypeAlias.
Recomendación
Expón el contrato del fixture y usa TypeAlias solo para la garantía que realmente ofrece.

Diferencias relevantes

  • No presentes un alias como una frontera de dominio cuando no cambia la identidad de tipo.
  • La forma idiomática de Python debe conservarse aunque la sintaxis difiera de Kotlin.

Da un nombre legible a una firma de transformación.

Transform: TypeAlias = Callable[[int], int]

Fuentes