Bidirectional conversion between Python and Typescript. Use when migrating projects between these languages in either direction. Extends meta-convert-dev with Python↔Typescript specific patterns. Use when migrating Python projects to TypeScript, translating Pythonic patterns to TypeScript idioms, or refactoring Python codebases into TypeScript. Extends meta-convert-dev with Python-to-TypeScript specific patterns.
Instrucciones de origen · Vista previa de solo lectura
name
convert-python-typescript
description
Bidirectional conversion between Python and Typescript. Use when migrating projects between these languages in either direction. Extends meta-convert-dev with Python↔Typescript specific patterns. Use when migrating Python projects to TypeScript, translating Pythonic patterns to TypeScript idioms, or refactoring Python codebases into TypeScript. Extends meta-convert-dev with Python-to-TypeScript specific patterns.
Python ↔ Typescript Conversion
Bidirectional conversion between Python and Typescript. This skill extends meta-convert-dev with Python↔Typescript specific type mappings, idiom translations, and tooling.
Python uses a straightforward import system, while TypeScript has ES modules with explicit imports/exports.
Import and Export Patterns
Python:
# math_utils.py - Python module
def add(a: float, b: float) -> float:
return a + b
def multiply(a: float, b: float) -> float:
return a * b
# Explicit public API (optional)
__all__ = ['add', 'multiply']
# Private function (convention)
def _internal_helper():
pass
TypeScript:
// mathUtils.ts - TypeScript module
export function add(a: number, b: number): number {
return a + b;
}
export function multiply(a: number, b: number): number {
return a * b;
}
// Private function (not exported)
function internalHelper() {
// ...
}
// Default export (Python has no equivalent)
export default class Calculator {
// ...
}
Import Patterns
Python:
# Named imports
from math_utils import add, multiply
# Module import
import math_utils
# Import everything (not recommended)
from math_utils import *
# Import with alias
from math_utils import add as addition
# Relative imports
from . import sibling_module
from .. import parent_module
from ..utils import helper
TypeScript:
// Named imports
import { add, multiply } from './mathUtils';
// Default import
import Calculator from './mathUtils';
// Import with alias
import { add as addition } from './mathUtils';
// Namespace import
import * as MathUtils from './mathUtils';
// Side-effect only import
import './polyfills';
// Type-only imports (erased at runtime)
import type { User, Config } from './types';
// mypackage/
// ├── index.ts // Barrel export (like __init__.py)
// ├── core.ts
// └── utils.ts
// mypackage/index.ts
export { CoreClass } from './core';
export { utilityFunction } from './utils';
export type { Config } from './types';
// Or re-export all
export * from './core';
export * from './utils';
Dynamic Imports
Python:
# Dynamic import
import importlib
module_name = "json"
json_module = importlib.import_module(module_name)
# Lazy imports
def expensive_operation():
import heavy_module # Only loaded when function called
return heavy_module.process()
# Conditional imports (type checking)
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from expensive_module import ExpensiveClass
TypeScript:
// Dynamic import (returns Promise)
async function loadModule() {
const module = await import('./heavyModule');
return module.process();
}
// Conditional type import
import type { ExpensiveClass } from './expensiveModule';
// Dynamic import with type
type MathUtils = typeof import('./mathUtils');
Error Handling Translation
Python Exception Model → TypeScript Error Patterns
Both languages use exception-based error handling, but TypeScript also supports Result-like patterns.
Aspect
Python
TypeScript
Base class
Exception
Error
Try-catch
try/except/else/finally
try/catch/finally
Throwing
raise Exception()
throw new Error()
Re-throwing
raise (without args)
throw (without args)
Type checking
isinstance() or specific except
instanceof
Exception Translation
Python:
class AppError(Exception):
"""Base exception for application errors."""
def __init__(self, message: str, code: str):
super().__init__(message)
self.code = code
class NotFoundError(AppError):
"""Raised when a resource is not found."""
def __init__(self, resource: str):
super().__init__(f"{resource} not found", "NOT_FOUND")
self.resource = resource
class ValidationError(AppError):
"""Raised when validation fails."""
def __init__(self, message: str, errors: list[str]):
super().__init__(message, "VALIDATION_ERROR")
self.errors = errors
TypeScript:
class AppError extends Error {
constructor(message: string, public code: string) {
super(message);
this.name = this.constructor.name;
// Maintain proper prototype chain
Object.setPrototypeOf(this, new.target.prototype);
}
}
class NotFoundError extends AppError {
constructor(public resource: string) {
super(`${resource} not found`, "NOT_FOUND");
}
}
class ValidationError extends AppError {
constructor(message: string, public errors: string[]) {
super(message, "VALIDATION_ERROR");
}
}
Error Handling Patterns
Python:
def load_config(path: str) -> Config:
try:
with open(path) as f:
content = f.read()
data = json.loads(content)
return Config(**data)
except FileNotFoundError:
raise NotFoundError(path) from None
except json.JSONDecodeError as e:
raise ValidationError(f"Invalid JSON in {path}") from e
except Exception:
# Re-raise unexpected errors
raise
# Usage
try:
config = load_config("config.json")
except NotFoundError as e:
print(f"Config not found: {e.resource}")
except ValidationError as e:
print(f"Invalid config: {e.errors}")
TypeScript:
function loadConfig(path: string): Config {
try {
const content = fs.readFileSync(path, 'utf-8');
const data = JSON.parse(content);
return data as Config;
} catch (error) {
if (error.code === 'ENOENT') {
throw new NotFoundError(path);
} else if (error instanceof SyntaxError) {
throw new ValidationError(`Invalid JSON in ${path}`, [error.message]);
} else {
// Re-throw unexpected errors
throw error;
}
}
}
// Usage
try {
const config = loadConfig('config.json');
} catch (error) {
if (error instanceof NotFoundError) {
console.error(`Config not found: ${error.resource}`);
} else if (error instanceof ValidationError) {
console.error(`Invalid config: ${error.errors}`);
} else {
throw error;
}
}
Result Pattern (Alternative to Exceptions)
Python:
from dataclasses import dataclass
from typing import Generic, TypeVar
T = TypeVar('T')
E = TypeVar('E')
@dataclass
class Ok(Generic[T]):
value: T
@dataclass
class Err(Generic[E]):
error: E
Result = Ok[T] | Err[E]
def divide(a: float, b: float) -> Result[float, str]:
if b == 0:
return Err("Division by zero")
return Ok(a / b)
# Usage
result = divide(10, 2)
match result:
case Ok(value):
print(f"Result: {value}")
case Err(error):
print(f"Error: {error}")
TypeScript:
// Result type pattern
type Result<T, E> =
| { success: true; data: T }
| { success: false; error: E };
function divide(a: number, b: number): Result<number, string> {
if (b === 0) {
return { success: false, error: "Division by zero" };
}
return { success: true, data: a / b };
}
// Usage
const result = divide(10, 2);
if (result.success) {
console.log(`Result: ${result.data}`);
} else {
console.error(`Error: ${result.error}`);
}
Both languages support async/await, but with different runtimes and patterns.
Aspect
Python
TypeScript
Runtime
asyncio event loop (explicit)
V8 event loop (built-in)
Promise/Future
Coroutine[Any, Any, T]
Promise<T>
Concurrent
asyncio.gather()
Promise.all()
Race
asyncio.wait(..., FIRST_COMPLETED)
Promise.race()
Timeout
asyncio.wait_for()
Promise.race() + timeout
Basic Async Functions
Python:
import asyncio
import aiohttp
async def fetch_user(id: str) -> User:
async with aiohttp.ClientSession() as session:
async with session.get(f'/api/users/{id}') as response:
if not response.ok:
raise Exception(f"Failed to fetch user {id}")
data = await response.json()
return User(**data)
# Entry point requires event loop
async def main():
user = await fetch_user('123')
print(user.name)
if __name__ == '__main__':
asyncio.run(main())
TypeScript:
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`Failed to fetch user ${id}`);
}
return await response.json();
}
// Can await at top level (in modules)
const user = await fetchUser('123');
console.log(user.name);
// Or in async function
async function main() {
const user = await fetchUser('123');
console.log(user.name);
}
main();
Concurrent Execution
Python:
# Sequential (slow)
user1 = await fetch_user('1')
user2 = await fetch_user('2')
# Concurrent with gather
users = await asyncio.gather(
fetch_user('1'),
fetch_user('2'),
fetch_user('3')
)
# With TaskGroup (Python 3.11+)
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(fetch_user('1'))
task2 = tg.create_task(fetch_user('2'))
task3 = tg.create_task(fetch_user('3'))
users = [task1.result(), task2.result(), task3.result()]
# Handle errors separately
results = await asyncio.gather(
fetch_user('1'),
fetch_user('2'),
return_exceptions=True
)
for result in results:
if isinstance(result, Exception):
print(f"Error: {result}")
from typing import AsyncIterator
async def generate_pages(start: int, end: int) -> AsyncIterator[Page]:
for i in range(start, end + 1):
yield await fetch_page(i)
# Async iteration
async for page in generate_pages(1, 10):
process(page)
# Async comprehension
pages = [page async for page in generate_pages(1, 10)]
TypeScript:
async function* generatePages(start: number, end: number): AsyncIterable<Page> {
for (let i = start; i <= end; i++) {
yield await fetchPage(i);
}
}
// Async iteration
for await (const page of generatePages(1, 10)) {
process(page);
}
// Convert to array
const pages: Page[] = [];
for await (const page of generatePages(1, 10)) {
pages.push(page);
}
Timeout and Cancellation
Python:
# Timeout with wait_for
try:
user = await asyncio.wait_for(fetch_user('1'), timeout=5.0)
except asyncio.TimeoutError:
print("Request timed out")
# Timeout with timeout context manager (3.11+)
try:
async with asyncio.timeout(5.0):
user = await fetch_user('1')
except asyncio.TimeoutError:
print("Request timed out")
# Cancellation
task = asyncio.create_task(fetch_user('1'))
# Later...
task.cancel()
try:
await task
except asyncio.CancelledError:
print("Task was cancelled")
TypeScript:
// Timeout with Promise.race
async function fetchWithTimeout<T>(
promise: Promise<T>,
ms: number
): Promise<T> {
const timeout = new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
);
return Promise.race([promise, timeout]);
}
try {
const user = await fetchWithTimeout(fetchUser('1'), 5000);
} catch (error) {
if (error.message === 'Timeout') {
console.error('Request timed out');
}
}
// Cancellation with AbortController
const controller = new AbortController();
const signal = controller.signal;
// Later...
controller.abort();
// Use with fetch
const response = await fetch(url, { signal });
Metaprogramming Translation
Decorators
Python:
from functools import wraps
from typing import Callable, TypeVar, ParamSpec
P = ParamSpec('P')
T = TypeVar('T')
# Function decorator
def log_calls(func: Callable[P, T]) -> Callable[P, T]:
@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
print(f"Calling {func.__name__}")
result = func(*args, **kwargs)
print(f"Finished {func.__name__}")
return result
return wrapper
@log_calls
def process_data(data: list) -> int:
return len(data)
# Class decorator
def singleton(cls):
instances = {}
@wraps(cls)
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Database:
pass
TypeScript:
// Function decorator (experimental)
function logCalls(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling ${propertyKey}`);
const result = original.apply(this, args);
console.log(`Finished ${propertyKey}`);
return result;
};
}
class Service {
@logCalls
processData(data: any[]): number {
return data.length;
}
}
// Class decorator
function singleton<T extends { new(...args: any[]): {} }>(constructor: T) {
let instance: T;
return class extends constructor {
constructor(...args: any[]) {
if (instance) {
return instance;
}
super(...args);
instance = this as any;
}
};
}
@singleton
class Database {
// ...
}
// Higher-order function (alternative to decorators)
function logCalls<T extends (...args: any[]) => any>(fn: T): T {
return ((...args: any[]) => {
console.log(`Calling ${fn.name}`);
const result = fn(...args);
console.log(`Finished ${fn.name}`);
return result;
}) as T;
}
const processData = logCalls((data: any[]) => data.length);
// Using Proxy for dynamic properties
function createDynamicObject() {
return new Proxy({}, {
get(target, prop) {
if (prop in target) {
return target[prop];
}
return `Dynamic: ${String(prop)}`;
},
set(target, prop, value) {
target[prop] = value;
return true;
},
deleteProperty(target, prop) {
delete target[prop];
return true;
}
});
}
// Usage
const obj = createDynamicObject();
console.log(obj.anything); // "Dynamic: anything"
Zero and Default Values
Null/None Handling
Python
TypeScript
Notes
None
null
Explicit null
None
undefined
Default absence
Optional[T]
T | null
Nullable value
T | None
T | undefined
Optional value
Union[T, None]
T | null | undefined
Fully optional
Python:
from typing import Optional
def find_user(id: str) -> Optional[User]:
user = database.get(id)
return user # Can be None
# Usage - explicit None check
user = find_user('123')
if user is not None:
print(user.name)
else:
print("User not found")
# Or with walrus operator
if (user := find_user('123')) is not None:
print(user.name)
TypeScript:
function findUser(id: string): User | null {
const user = database.get(id);
return user ?? null; // Convert undefined to null
}
// Usage - explicit null check
const user = findUser('123');
if (user !== null) {
console.log(user.name);
} else {
console.log('User not found');
}
// Optional chaining
const name = findUser('123')?.name ?? 'Unknown';
// Nullish coalescing
const user = findUser('123') ?? defaultUser;
# List comprehension
squares = [x**2 for x in range(10)]
# With filter
evens = [x for x in range(10) if x % 2 == 0]
# Nested
matrix = [[i+j for j in range(3)] for i in range(3)]
# Dict comprehension
word_lengths = {word: len(word) for word in ["hello", "world"]}
# Set comprehension
unique_lengths = {len(word) for word in ["hello", "world", "hi"]}
TypeScript:
// Array map
const squares = Array.from({ length: 10 }, (_, i) => i ** 2);
// Or
const squares = [...Array(10).keys()].map(x => x ** 2);
// With filter
const evens = [...Array(10).keys()].filter(x => x % 2 === 0);
// Nested
const matrix = Array.from({ length: 3 }, (_, i) =>
Array.from({ length: 3 }, (_, j) => i + j)
);
// Object from entries
const wordLengths = Object.fromEntries(
["hello", "world"].map(word => [word, word.length])
);
// Set
const uniqueLengths = new Set(["hello", "world", "hi"].map(w => w.length));
Pattern 2: With Statement → Try-Finally / Using
Python:
# Context manager
with open("file.txt") as f:
content = f.read()
# File automatically closed
# Custom context manager
from contextlib import contextmanager
@contextmanager
def timer():
start = time.time()
yield
end = time.time()
print(f"Elapsed: {end - start:.2f}s")
with timer():
# Code to time
time.sleep(1)
TypeScript:
// Try-finally pattern
let file: fs.FileHandle | null = null;
try {
file = await fs.open("file.txt");
const content = await file.readFile('utf-8');
} finally {
await file?.close();
}
// Using Disposable pattern (TypeScript 5.2+)
class Timer implements Disposable {
private start = Date.now();
[Symbol.dispose]() {
const end = Date.now();
console.log(`Elapsed: ${(end - this.start) / 1000}s`);
}
}
{
using timer = new Timer();
// Code to time
await sleep(1000);
} // Timer automatically disposed
// Async disposable
class FileHandle implements AsyncDisposable {
async [Symbol.asyncDispose]() {
await this.close();
}
}
{
await using file = await fs.open("file.txt");
// Use file
} // Automatically closed
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None: ...
class Closeable(Protocol):
def close(self) -> None: ...
# Structural typing - no explicit inheritance needed
class Window:
def draw(self) -> None:
print("Drawing window")
def close(self) -> None:
print("Closing window")
def render(obj: Drawable) -> None:
obj.draw()
window = Window()
render(window) # Works without explicit inheritance
TypeScript:
// Interface (structural typing built-in)
interface Drawable {
draw(): void;
}
interface Closeable {
close(): void;
}
// Structural typing - no explicit implements needed (but recommended)
class Window implements Drawable, Closeable {
draw(): void {
console.log("Drawing window");
}
close(): void {
console.log("Closing window");
}
}
function render(obj: Drawable): void {
obj.draw();
}
const window = new Window();
render(window); // Works due to structural typing
Pattern 6: Enum → Const Object or String Literal Union
Python:
from enum import Enum, StrEnum
# Enum class
class Status(Enum):
PENDING = "pending"
ACTIVE = "active"
COMPLETED = "completed"
# Or StrEnum (Python 3.11+)
class Status(StrEnum):
PENDING = "pending"
ACTIVE = "active"
COMPLETED = "completed"
# Usage
def process(status: Status) -> None:
if status == Status.PENDING:
print("Pending...")
elif status is Status.ACTIVE:
print("Active!")
# With Literal
from typing import Literal
StatusType = Literal["pending", "active", "completed"]
def handle(status: StatusType) -> None:
if status == "pending":
print("Pending...")
TypeScript:
// String enum
enum Status {
Pending = "pending",
Active = "active",
Completed = "completed"
}
function process(status: Status): void {
if (status === Status.Pending) {
console.log("Pending...");
} else if (status === Status.Active) {
console.log("Active!");
}
}
// Const object (preferred - no runtime overhead)
const Status = {
Pending: "pending",
Active: "active",
Completed: "completed"
} as const;
type Status = typeof Status[keyof typeof Status];
function handle(status: Status): void {
if (status === Status.Pending) {
console.log("Pending...");
}
}
// String literal union (most TypeScript-like)
type Status = "pending" | "active" | "completed";
function handleSimple(status: Status): void {
switch (status) {
case "pending":
console.log("Pending...");
break;
case "active":
console.log("Active!");
break;
case "completed":
console.log("Completed!");
break;
}
}
Pattern 7: Generators → Generator Functions
Python:
def fibonacci(n: int):
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b
# Usage
for num in fibonacci(10):
print(num)
# Generator expression
squares = (x**2 for x in range(10))
TypeScript:
function* fibonacci(n: number): Generator<number> {
let [a, b] = [0, 1];
for (let i = 0; i < n; i++) {
yield a;
[a, b] = [b, a + b];
}
}
// Usage
for (const num of fibonacci(10)) {
console.log(num);
}
// No generator expression equivalent - use Array methods
const squares = Array.from({ length: 10 }, (_, i) => i ** 2);
Common Pitfalls
1. Python None → TypeScript null vs undefined
Problem: Python has one "null" value (None), TypeScript has two (null and undefined).
Python:
def find_user(id: str) -> User | None:
# ...
return None # Only one way to represent "no value"
TypeScript:
// Need to decide: null or undefined?
function findUser(id: string): User | null {
return null; // Explicit absence
}
// Or
function findUser(id: string): User | undefined {
return undefined; // Implicit absence (default)
}
// Often both are possible
function findUser(id: string): User | null | undefined {
// ...
}
Solution: Choose a convention:
Use null for explicit "not found" cases
Use undefined for optional/uninitialized values
Or stick to one consistently (prefer undefined for simplicity)
2. Mutable vs Immutable Collections
Problem: Python's mutable defaults vs TypeScript's const behavior.
Python:
# Mutable list
items = [1, 2, 3]
items.append(4) # Modifies in place
# To make immutable, use tuple
items = (1, 2, 3) # Cannot be modified
TypeScript:
// const reference, but mutable content
const items = [1, 2, 3];
items.push(4); // Works! const doesn't freeze content
// To make immutable
const items: readonly number[] = [1, 2, 3];
items.push(4); // Error: push doesn't exist on readonly array
// Or use as const
const items = [1, 2, 3] as const;
items.push(4); // Error
Solution: Use readonly for truly immutable arrays in TypeScript.
3. Truthy/Falsy Values
Problem: Different falsy values between languages.
Python:
# Python falsy values: None, False, 0, "", [], {}, ()
if user: # False for None or empty dict
print(user.name)
# Be explicit
if user is not None: # Only checks None
print(user.name)
TypeScript:
// TypeScript falsy: null, undefined, false, 0, "", NaN
if (user) { // False for null, undefined, or empty object
console.log(user.name);
}
// Be explicit
if (user !== null && user !== undefined) {
console.log(user.name);
}
// Or use nullish coalescing
const name = user?.name ?? "Unknown";
Solution: Use explicit comparisons when the distinction matters.
4. Integer Division
Problem: Python has floor division (//), TypeScript only has float division.
Python:
result = 5 / 2 # 2.5 (float division)
result = 5 // 2 # 2 (floor division)
class Counter:
# Class variable (shared across instances!)
count = 0
def __init__(self, initial: int = 0):
# Instance variable (per-instance)
self.count = initial
TypeScript:
class Counter {
// Instance property (per-instance by default)
count: number = 0;
// Static property (shared across instances)
static globalCount: number = 0;
constructor(initial: number = 0) {
this.count = initial;
}
}
Solution: Understand that TypeScript class properties are instance variables by default, unlike Python.
6. String Formatting
Problem: Python f-strings vs TypeScript template literals.
Python:
name = "Alice"
age = 30
message = f"Hello, {name}! You are {age} years old."
# Multi-line
message = f"""
Hello, {name}!
You are {age} years old.
"""
TypeScript:
const name = "Alice";
const age = 30;
const message = `Hello, ${name}! You are ${age} years old.`;
// Multi-line (indentation matters!)
const message = `
Hello, ${name}!
You are ${age} years old.
`;
Solution: Both support similar template syntax, but TypeScript preserves all whitespace.
const user = { name: "Alice" };
const email = user["email"]; // undefined (no error)
const email2 = user.email; // undefined (no error)
// With strict types
interface User {
name: string;
email?: string; // Explicitly optional
}
const user: User = { name: "Alice" };
const email = user.email; // string | undefined
// Nullish coalescing for default
const email = user.email ?? "default@example.com";
Solution: TypeScript's optional properties provide type safety, but always returns undefined for missing keys.
8. Import Side Effects
Problem: Python executes module on first import, TypeScript has clearer side-effect semantics.
Python:
# config.py
print("Loading config...") # Executes on first import
DATABASE_URL = "..."
# main.py
import config # Prints "Loading config..."
import config # Does NOT print again (cached)
TypeScript:
// config.ts
console.log("Loading config..."); // Executes on first import
export const DATABASE_URL = "...";
// main.ts
import { DATABASE_URL } from './config'; // Prints "Loading config..."
import './config'; // Side-effect import (explicit)
Solution: Both cache modules, but TypeScript makes side-effect imports more explicit.