No sections found
We couldn't find anything matching your search query. Try adjusting your keywords.
Learn Modern Python 3 for Javascript Developers
In 2026, JavaScript rules the browser, but Python drives the intelligence of the world. If you want to build AI, wrangle big data, or construct robust backend APIs with FastAPI, you need Python.
But this isn't the chaotic scripting language of 2015. Modern Python is statically type-hinted, strictly linted, heavily reliant on structural pattern matching, and backed by ultra-fast Rust-based tooling (uv, ruff). Let's remap your Node.js mental models into idiomatic, high-performance Python.
2. The Rosetta Stone: Types & Variables
Javascript uses const and let to define variable bindings. Python has no keyword to declare a variable; you just assign it. However, modern Python mandates Type Hints in professional codebases.
| Concept | JS/TS (ES2026) | Modern Python 3.12+ |
|---|---|---|
| Immutable / Constant | const x = 42; | x: Final[int] = 42 |
| Mutable Variable | let x = 42; | x: int = 42 |
| String Interpolation | `Hi ${name}` | f"Hi {name}" |
| Array / List | const nums: number[] = [1, 2, 3]; | nums: list[int] = [1, 2, 3] |
| Object / Dictionary | const map: Record<string, int> = {"k": 1}; | mapping: dict[str, int] = {"k": 1} |
| Null / None | let val: number | null = null; | val: int | None = None |
| Boolean Logic | if (x && y || !z) { ... } | if x and y or not z: ... |
3. Syntax & Comprehensions
Javascript relies heavily on arrow functions and higher-order array methods (.map(), .filter()). Python relies on Significant Whitespace (indentation instead of curly braces) and List Comprehensions.
const users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 17 },
{ name: "Charlie", age: 30 }
];
// JS uses chained higher-order functions
const adultNames = users
.filter(u => u.age >= 18)
.map(u => u.name);
console.log(adultNames); // ["Alice", "Charlie"]
users = [
{"name": "Alice", "age": 25},
{"name": "Bob", "age": 17},
{"name": "Charlie", "age": 30}
]
# Python uses List Comprehensions: [expression for item in iterable if condition]
adult_names = [
u["name"] for u in users if u["age"] >= 18
]
print(adult_names) # ['Alice', 'Charlie']
Why not use `map` and `filter` in Python?
Python does have map() and filter(), and it does have lambda functions (lambda x: x + 1 instead of x => x + 1). However, they are generally considered unidiomatic and less readable by the Python community. List comprehensions are faster and the preferred "Pythonic" way to transform data.
4. Guess the Number Game
Python Features Introduced: Synchronous blocking I/O, `while` loops, and exception handling (`try/except`).
import * as readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
// JS is non-blocking. To get user input synchronously, we must use await.
const rl = readline.createInterface({ input, output });
const secret = Math.floor(Math.random() * 100) + 1;
console.log("Guess the number between 1 and 100!");
while (true) {
const guessStr = await rl.question("> ");
const guess = parseInt(guessStr.trim(), 10);
if (Number.isNaN(guess)) {
console.log("Please type a valid number!");
continue;
}
if (guess < secret) console.log("Higher!");
else if (guess > secret) console.log("Lower!");
else {
console.log("You win!");
break;
}
}
rl.close();
import random
def main() -> None:
secret_number = random.randint(1, 100)
print("Guess the number between 1 and 100!")
# Python standard I/O blocks the thread automatically. No async needed!
while True:
try:
# input() pauses execution and waits for the user.
guess = int(input("> ").strip())
except ValueError:
print("Please type a valid number!")
continue
if guess < secret_number:
print("Higher!")
elif guess > secret_number:
print("Lower!")
else:
print("You win!")
break
# Standard boilerplate to prevent execution when imported as a module
if __name__ == "__main__":
main()
5. Arithmetic Command Line Game
Python Features Introduced: f-strings (introduced in 3.6, vastly upgraded in 3.12).
import * as readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
const rl = readline.createInterface({ input, output });
console.log("Solve addition! Type 'quit' to exit.");
while (true) {
const a = Math.floor(Math.random() * 10) + 1;
const b = Math.floor(Math.random() * 10) + 1;
// Template Literals use backticks and ${}
const userInput = (await rl.question(`What is ${a} + ${b}? `)).trim();
if (userInput === 'quit') break;
const answer = parseInt(userInput, 10);
if (Number.isNaN(answer)) {
console.log("Enter a number or 'quit'.");
} else if (answer === a + b) {
console.log("Correct!");
} else {
console.log(`Wrong! It was ${a + b}.`);
}
}
rl.close();
import random
def main() -> None:
print("Solve the addition problems! Type 'quit' to exit.")
while True:
a, b = random.randint(1, 10), random.randint(1, 10)
# f-strings: Prefix string with 'f' and use {} for expressions
# In Python 3.12+, you can even nest f-strings and quotes inside {}
user_input = input(f"What is {a} + {b}? ").strip()
if user_input == "quit":
print("Thanks for playing!")
break
try:
if int(user_input) == a + b:
print("Correct!")
else:
print(f"Wrong! It was {a + b}.")
except ValueError:
print("Please enter a number or 'quit'.")
if __name__ == "__main__": main()
6. Structural Pattern Matching (State Machines)
Python Features Introduced: match/case (Python 3.10+) and Enum. Python lacked a switch statement for 30 years. When they finally added it, they didn't just add a switch; they added full structural pattern matching (similar to Rust).
import * as readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
const AppState = { MENU: 'MENU', PLAYING: 'PLAYING', QUIT: 'QUIT' };
const Operation = { ADD: 'ADD', MULTIPLY: 'MULTIPLY' };
const rl = readline.createInterface({ input, output });
let state = AppState.MENU;
let op = Operation.ADD;
while (state !== AppState.QUIT) {
switch (state) {
case AppState.MENU:
const menuIn = (await rl.question("1. Play 2. Multiply 3. Quit\n> ")).trim();
// Don't forget your breaks! JS falls through by default.
if (menuIn === "1") state = AppState.PLAYING;
else if (menuIn === "2") op = Operation.MULTIPLY;
else if (menuIn === "3") state = AppState.QUIT;
break;
case AppState.PLAYING:
const a = Math.floor(Math.random() * 10) + 1;
const b = Math.floor(Math.random() * 10) + 1;
// Ternary operators are heavily used in JS
const symbol = op === Operation.ADD ? '+' : '*';
const correct = op === Operation.ADD ? a + b : a * b;
const ansStr = (await rl.question(`What is ${a} ${symbol} ${b}? `)).trim();
if (ansStr === 'menu') { state = AppState.MENU; continue; }
const ans = parseInt(ansStr, 10);
if (!Number.isNaN(ans)) {
if (ans === correct) console.log("Correct!");
else console.log(`Wrong, it was ${correct}`);
}
break;
}
}
rl.close();
import random
from enum import Enum, auto
class Operation(Enum):
ADD = auto()
MULTIPLY = auto()
class AppState(Enum):
MENU = auto()
PLAYING = auto()
QUIT = auto()
def main() -> None:
state = AppState.MENU
op = Operation.ADD
while state != AppState.QUIT:
# Match/Case! No 'break' statements required. No fall-through.
match state:
case AppState.MENU:
user_in = input("1. Play 2. Multiply 3. Quit\n> ").strip()
match user_in:
case "1": state = AppState.PLAYING
case "2": op = Operation.MULTIPLY
case "3": state = AppState.QUIT
case _: print("Invalid option.") # Catch-all
case AppState.PLAYING:
a, b = random.randint(1, 10), random.randint(1, 10)
# Python's inline ternary: Value IF True ELSE Value
sym, cor = ("+", a+b) if op == Operation.ADD else ("*", a*b)
ans = input(f"What is {a} {sym} {b}? ('menu' to exit) ").strip()
if ans == "menu":
state = AppState.MENU
continue
try:
if int(ans) == cor:
print("Correct!")
else:
print(f"Wrong, it was {cor}")
except ValueError:
pass
if __name__ == "__main__": main()
7. Data Modeling: DataClasses & Pydantic
In JavaScript, you often just pass around raw objects `{ id: 1, name: "Alice" }`. In modern Python, you structure your data safely using @dataclass (built-in) or Pydantic (industry standard for validation in FastAPI).
1
Standard @dataclass
Generates __init__, __repr__, and __eq__ automatically, saving huge amounts of boilerplate. Does not perform runtime type validation.
from dataclasses import dataclass
@dataclass
class User:
id: int
name: str
is_active: bool = True
# Instantiation is clean
u = User(id=1, name="Alice")
print(u) # User(id=1, name='Alice', is_active=True)
# WARNING: Python does NOT stop this at runtime:
# bad_u = User(id="not-an-int", name=55)
2 Pydantic (Industry Standard)
Pydantic strictly enforces types at runtime. It will attempt to coerce data (e.g., `"1"` to `1`) and throw loud Validation Errors if it fails.
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
is_active: bool = True
# Pydantic coerces the string "1" into the int 1!
u = User(id="1", name="Alice")
print(u.id) # 1 (Integer)
# This will throw a ValidationError at runtime:
# bad_u = User(id="abc", name="Bob")
8. Concurrency: Asyncio & The GIL
JavaScript is fundamentally single-threaded and built entirely around a non-blocking Event Loop. Python is fundamentally multi-threaded but handicapped by the GIL (Global Interpreter Lock), meaning only one thread can execute Python byte-code at a time.
JS / Node.js
You never think about threads. You just write await fetch(). Under the hood, Node hands the network request to C++ (libuv), freeing the JS call stack instantly.
Python (Asyncio)
You must explicitly start an event loop. If you write a blocking `requests.get()` inside an async function, the entire server freezes. You must use specific async libraries like `httpx` or `aiohttp`.
import asyncio
import httpx # A modern, async-compatible alternative to 'requests'
async def fetch_user(user_id: int) -> dict:
# Await hands control back to the Python Event Loop
async with httpx.AsyncClient() as client:
response = await client.get(f"https://api.example.com/users/{user_id}")
return response.json()
async def main():
# Gather runs multiple coroutines concurrently (Like Promise.all)
tasks = [fetch_user(i) for i in range(1, 4)]
users = await asyncio.gather(*tasks)
for u in users:
print(u["name"])
# You must manually start the event loop in Python
if __name__ == "__main__":
asyncio.run(main())
9. Python Specific Gotchas & Beginner Mistakes
Python is famous for being "executable pseudo-code", but it has a few sharp edges that routinely cut developers coming from C-family languages.
The Mutable Default Argument
In JS, default parameters are evaluated every time the function is called. In Python, default parameters are evaluated exactly once when the function is defined. If you use a mutable default like a list or dict, it is shared across all calls!
def add_item(item, box=[]):
box.append(item)
return box
print(add_item("A")) # ['A']
print(add_item("B")) # ['A', 'B'] WAIT WHAT?!
def add_item(item, box=None):
if box is None:
box = []
box.append(item)
return box
print(add_item("A")) # ['A']
print(add_item("B")) # ['B']
== versus is
In JS, === checks if two arrays occupy the same memory address. In Python, == checks for value equality (it looks inside the lists). is checks for identity (memory address). Only use is when comparing to None, True, or False.
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True (Values match)
print(a is b) # False (Different memory addresses)
if a is None: # Correct way to check for None
pass
10. Ecosystem: UV & Ruff (The 2026 Standards)
Historically, Python dependency management was a nightmare (pip, venv, poetry, pipenv). In recent years, Astral built tools in Rust that completely revolutionized the Python ecosystem.
uv
Replaces `pip`, `virtualenv`, `poetry`, and `pyenv`. It resolves dependencies 10-100x faster than pip.
# Initialize a new project
uv init my_project
# Add a dependency (resolves instantly)
uv add fastapi
# Run a script seamlessly
uv run main.py
Ruff
The ultimate linter and formatter. It replaces Flake8, Black, isort, and dozens of plugins, running in milliseconds.
# Format code (replaces Black)
ruff format .
# Check and fix linting errors
ruff check . --fix