Syntax Rosetta & memory foundations
Map ES6+ onto Python primitives, then unlearn the parts that look identical but are not: nullity, truthiness, identity, and hashability.
You can read Python already. That is the problem. The syntax is close enough that you will translate it in your head and be wrong about four things per file. This module fixes the four.
Nullity
JavaScript ships two empty values. Python ships one.
| JavaScript | Python |
|---|---|
null, deliberately empty | None |
undefined, never assigned | None, or an AttributeError/KeyError |
x ?? fallback | x if x is not None else fallback |
obj?.key | d.get("key"), or attempt it and catch |
None is a singleton, so is None is both the idiomatic and the correct test. == None works by
accident until someone defines __eq__, and then it stops working.
Truthiness
This is the divergence that silently changes behaviour when you port code.
In JavaScript, if (arr) asks does this array exist. In Python, if xs: asks does this list
have anything in it. Ported directly, that check flips meaning. When you meant existence, say
if xs is not None:.
Equality and identity
== compares values through __eq__. is compares object identity, it is Object.is, not
===. Reserve it for None, True and False.
Strings and slicing
Two traps live here.
| JavaScript | Python | Note |
|---|---|---|
`${name} is ${age}` | f"{name} is {age}" | interpolated at evaluation, not deferred |
s.replace("-", "+") | s.replace("-", "+", 1) | Python replaces all by default |
s.replaceAll("-", "+") | s.replace("-", "+") | |
arr.at(-1) | arr[-1] | |
arr.slice(1, 3) | arr[1:3] | |
[...arr].reverse() | arr[::-1] |
EAFP over LBYL
JavaScript style is Look Before You Leap. Guard with ?., then act. Python style is Easier to
Ask Forgiveness than Permission. Act, then catch. A try block that raises nothing costs
essentially nothing, so the guard buys you no performance and one more branch to keep in sync.
# LBYL, reads like JavaScript, and races if the dict is shared
if "key" in config and config["key"] is not None:
use(config["key"])
# EAFP, idiomatic
try:
use(config["key"])
except KeyError:
use_default()Hashability
Dict keys and set members must be hashable, which in practice means immutable. This has no
JavaScript analogue, a Map accepts any object as a key, by reference.
Comprehensions
.map().filter() chains become one comprehension. Same for dicts and sets. Swap the brackets for
parentheses and you get a lazy generator instead of a materialised list, the equivalent of a
function*, and what you want when the sequence is large.
Unpacking
| JavaScript | Python |
|---|---|
const [a, ...rest] = xs | a, *rest = xs |
const { x, y } = point | x, y = point["x"], point["y"] |
{ ...obj, k: v } | {**obj, "k": v} or obj | {"k": v} |
[...a, ...b] | [*a, *b] or a + b |
Kata
Two bugs, both of which look correct to a JavaScript reader.
Why the default argument breaks
def accumulate(item, into=[]) evaluates [] once, when the def statement runs, not on
each call. Every invocation shares that one list. JavaScript evaluates default parameters per
call, which is why the same shape is safe there. The Python idiom is into=None plus an if into is None: into = [] guard on the first line.