See https://edemaine.github.io/coffeescript-for-python/ for a better-formatted version of this document. {: .github-only}
CoffeeScript is a programming language whose syntax is clearly designed to match much of Python (with additional inspirations from Perl, ECMAScript, Ruby, etc.). But most documentation for learning CoffeeScript assumes knowledge of JavaScript (which CoffeeScript compiles to), which is far messier.
This guide attempts to teach CoffeeScript to someone fluent in just Python (such as the typical MIT sophomore), showing the slight tweaks needed to convert Python code into CoffeeScript code. The goal is to make it easy for someone to learn CoffeeScript as a second language after Python, without first learning JavaScript, thereby enabling a Python programmer to also make cool web applications (for example). This should also make it easier to later learn JavaScript as a third language (as the CoffeeScript compiler and documentation provides countless examples relating the two).
This guide is still a work-in-progress, and is not yet complete. Feel free to submit an issue for anything you find lacking or confusing.
Both Python and CoffeeScript are great languages. The main reason to prefer CoffeeScript is that it compiles to JavaScript, resulting in several advantages:
- It can run in any web browser, making it easy to distribute your software for people to play with: just embed it in a web page, and anyone can run it on their desktop computer or smartphone. (This feature is also especially important for web development.) It can also run stand-alone/from a command line (like Python) via Node, which is now the basis for many web servers as part of a complete "web application".
- When running in a browser, you gain access to many powerful GUI features in the browser, notably HTML/CSS (e.g., buttons), SVG (vector 2D graphics), Canvas (raster 2D graphics), and WebGL (3D graphics). This makes it really easy to do complex interactive graphics.
- It is much faster: Node runs typical CoffeeScript 2-5x faster than the equivalent Python (though this gap narrows if you use PyPy). This performance boost is thanks to extensive optimization, such as just-in-time compilation, because of the intense interest in web applications. Low-level access to typed arrays, WebWorker threads, etc. can make Coffee/JavaScript even faster. For example, this π calculation benchmark shows that well-tuned JavaScript code is around 5x faster than NumPy, 100x faster than CPython, and only around 2x slower than highly optimized C.
An alternative would be to learn a language like RapydScript which is more similar to Python but still compiles directly to JavaScript (like CoffeeScript does).
Both Python and CoffeeScript share many features:
- Code block nesting is based on indentation.
- Variables don't need to be declared; you just assign a value.
- Everything is an object and has a type, but you don't need to declare the types of variables.
- Imperative programming plus strong functional programming support.
- Interpreted language with just-in-time optimization and interactive REPL
- Self-resizing arrays and dictionaries are powerful native types.
- Numbers,
strings,
regular expressions,
slicing,
comparisons with chaining,
and
/or
/not
,if
,while
,for...in
, list comprehensions, generator functions andyield
, asynchronous functions andawait
, exceptions, and many other features are all very similar.
They also have some major differences (some better for Python and some better for CoffeeScript):
- CoffeeScript requires less punctuation, and relies even more on indentation:
- no colons to start code blocks,
- optional braces around dictionary literals,
- optional quotes around dictionary keys,
- optional commas between list items, and
- optional parentheses in function calls.
- Variables have different scope: every variable in
CoffeeScript is as if it was declared
nonlocal
in Python. This makes it easier to access variables in enclosing scopes, but also easier to accidentally re-use variables. - The typing systems differ: CoffeeScript uses prototype object orientation, while Python uses multiple-inheritance object orientation. The main practical difference is that multiple inheritance is not supported by CoffeeScript. In principle, it's also easier to create "classes" in CoffeeScript because every object can act as a class.
lambda
-style inline functions can be multiple lines in CoffeeScript, making mixed imperative/functional programming even easier. On the other hand, CoffeeScript functions do not support keyword arguments.- CoffeeScript's REPL has a different interface for multiline inputs: press CTRL-V before the first newline, and press CTRL-V again when done (instead of a blank line).
- The built-in types differ in many small ways, e.g., their method names differ. But for the most part, there is a one-to-one mapping.
- CoffeeScript has more helpful syntax for a lot of important features,
but also misses a few features:
- There is just one
Number
type corresponding to Python'sfloat
. There are no (big) integers, complex numbers, or rationals. - String interpolation,
regular expressions, and
the equivalent of
range
have built-in syntax (instead of relying on methods/libraries/functions). - There are two slicing operators depending on whether you want to include the final index or not.
- All comparisons are shallow;
no built-in deep comparison support.
Truthiness is similarly shallow; e.g.,
[]
is considered true. unless
alternative toif not
;switch
alternative to longif
/then
/else
chains; andif
can come at the end of a line;- Multiline
if
s,while
, andfor
loops are expressions instead of statements, so single statements span multiple lines with full indentation support. (while
andfor
loops helpfully accumulate the list of final values.) - Three types
of
for
loops, including cleaner syntax for Python'sfor i, x in enumerate(...)
andfor key, value in d.items()
. - Exceptional behavior generally doesn't raise exceptions
like it does in Python.
For example,
d[key]
returnsundefined
whenkey not in d
(instead of raisingKeyError
);1/0
returnsInfinity
(instead of raisingZeroDivisionError
); and function calls with incorrect number of arguments work fine (with missing arguments set toundefined
and extra arguments discarded, instead of raisingTypeError
). - No dictionary comprehensions or generator expressions.
- No operator overloading via
__special_methods__
. No metaclasses.
- There is just one
Given the large similarites, a natural question is whether it's possible to automatically convert Python to CoffeeScript. We've started exploring that question in python2coffee. By contrast, this guide aims to teach you CoffeeScript so you can write code in CoffeeScript in the first place.
At a high level, if you remove all the colons at the end of lines from Python code, you end up with equivalent CoffeeScript code. There are many smaller differences, though, stemming in particular from different built-in types (a consequence of JavaScript). This section aims to document these differences by way of side-by-side examples.
The analog of Python's print
is CoffeeScript's
console.log
.
Like Python 3, it is just a regular function.
Similarly, assert
's analog is console.assert
(also a regular function, unlike Python).
Python | CoffeeScript |
---|---|
# Python 2
print "Hello world", 1+2
# Python 3
print("Hello world", 1+2)
#or
print ("Hello world", 1+2) |
console.log "Hello world", 1+2
#or
console.log('Hello world', 1+2)
#INVALID: no space allowed between function and argument paren
#console.log ('Hello world', 1+2) |
assert even % 2 == 0
assert even % 2 == 0, 'even numbers divisible by 2' |
console.assert even % 2 == 0
console.assert even % 2 == 0, 'even numbers divisible by 2' |
Python and CoffeeScript comments are generally the same:
Python | CoffeeScript |
---|---|
# This line is a comment
x = 5 # set x to five |
# This line is a comment
x = 5 # set x to five |
CoffeeScript also supports block comments (similar to triple-quoted strings in Python), which you should be careful not to trigger by accident:
Python | CoffeeScript |
---|---|
# Some comments
# Some more comments
# Even more comments |
###
Some comments
Some more comments
Even more comments
### |
### This line is a comment |
## This line is a comment |
CoffeeScript string notion is very similar
syntax to Python, except in how triple-quoted strings deal with indentation.
In addition, strings enclosed with "..."
or """..."""
have built-in string
interpolation similar to Python 3.6's
f-strings.
(If you want something like Python's %
operator, try the
sprintf-js package.)
The resulting JavaScript String
type
has many similar methods to Python str
, though often with different names.
Python | CoffeeScript |
---|---|
"Hello {}, your age is {}".format(name, age)
'Hello {}, your age is {}'.format(name, age)
"Hello {}, your age is {}".format(name,
thisYear - birthYear)
#or
f'Hello {name}, your age is {thisYear - birthYear}' |
"Hello #{name}, your age is #{age}"
#INVALID: 'Hello #{name}, your age is #{age}'
# #{...} is allowed only in ""s, not ''s
"Hello #{name}, your age is #{thisYear - birthYear}" |
s = '''\
hello
world'''
# 'hello\nworld'
s = '''
hello
world
'''
# '\nhello\nworld\n' |
s = '''
hello
world
'''
# 'hello\nworld' -- common indentation removed
s = '''
hello
world
'''
# '\nhello\nworld\n' |
'\033' # 3-digit octal
'\x1b' # 2-digit hex
'\u001b' # 4-digit hex |
# octal forbidden in CoffeeScript
'\x1b' # 2-digit hex
'\u001b' # 4-digit hex |
'\a' # bell
'\b' # backspace
'\f' # formfeed
'\n' # linefeed
'\r' # carriage return
'\t' # tab
'\v' # vertical tab |
'\x07' # bell
'\b' # backspace
'\f' # formfeed
'\n' # linefeed
'\r' # carriage return
'\t' # tab
'\v' # vertical tab |
'y' in s
'y' not in s
s.startswith('y')
s.endswith('?')
s.find('hi')
s.find('hi', start)
s.rfind('hi')
s.rfind('hi', start)
s.find('hi') >= 0
s.replace('hi', 'bye')
s.replace('hi', 'bye', 1)
s.lower()
s.upper()
s.strip()
s.lstrip()
s.rstrip()
s.split()
s.split(',')
a = s.split(',', 2)
', '.join(array)
s.count('hi') |
'y' in s
'y' not in s
s.startsWith('y')
s.endsWith('?')
s.indexOf 'hi' # also supports RegExp
s.indexOf 'hi', start
s.lastIndexOf 'hi' # also supports RegExp
s.lastIndexOf 'hi', start
s.includes 'hi'
s.replace /hi/g, 'bye'
s.replace 'hi', 'bye'
s.toLowerCase()
s.toUpperCase()
s.trim() # no argument allowed
s.trimStart() # no argument allowed
s.trimEnd() # no argument allowed
s.split /\s+/
s.split ','
a = s.split ','; a[2..] = [a[2..].join ',']
array.join(', ')
(s.match /hi/g).length |
s1 + s2 + s3 |
s1 + s2 + s3
#or
s1.concat s2, s3 |
s * 5 |
s.repeat 5 |
len(s) |
s.length |
ord(s)
chr(27) |
s.charCodeAt()
String.fromCharCode 27 |
isinstance(s, str) |
typeof s == 'string' |
str(x) |
x.toString() |
See also string slicing.
JavaScript has just one
Number
type
corresponding to Python's float
(IEEE double precision).
There are no built-in integers, big integers, complex numbers, or rationals.
(But BigInts are coming!)
Python | CoffeeScript |
---|---|
6.0001
7.0
7e9 |
6.0001
7
7e9 |
0b11111111
0o377
255
0xff |
0b11111111
0o377
255
0xff |
int('ff', 16) |
parseInt 'ff', 16 |
float('7e9') |
parseFloat '7e9' |
str(n)
bin(n)
oct(n)
hex(n) |
n.toString()
n.toString(2)
n.toString(8)
n.toString(16) |
str(n)
bin(n)
oct(n)
hex(n) |
n.toString()
n.toString(2)
n.toString(8)
n.toString(16) |
isinstance(n, (int, float)) |
typeof s == 'number' |
(-b + (b**2 - 4*a*c)**0.5) / (2*a) |
(-b + (b**2 - 4*a*c)**0.5) / (2*a) |
x // y # integer division
x % y # mod in [0, y) |
x // y # integer division
x %% y # mod in [0, y) |
(~a | b) & (c ^ d) << n |
(~a | b) & (c ^ d) << n |
math.e
math.pi
math.tau
math.inf
math.nan |
Math.E
Math.PI
2*Math.PI
Infinity
NaN |
round(x)
math.trunc(x)
math.floor(x)
math.ceil(x)
math.sqrt(x)
abs(x)
math.log(x)
math.log(x, base)
math.log1p(x)
math.log2(x)
math.log10(x)
Math.exp(x)
Math.expm1(x)
math.degrees(x)
math.radians(x)
math.cos(x)
math.sin(x)
math.tan(x)
math.acos(x)
math.asin(x)
math.atan(x)
math.atan2(y, x)
math.hypot(x, y) |
Math.round x
Math.trunc x
Math.floor x
Math.ceil x
Math.sqrt x
Math.abs x
Math.log x
(Math.log x) / Math.log base
Math.log1p x
Math.log2 x
Math.log10 x
Math.exp x
Math.expm1 x
x * 180 / Math.PI
x * Math.PI / 180
Math.cos x
Math.sin x
Math.tan x
Math.acos x
Math.asin x
Math.atan x
Math.atan2 y, x
Math.hypot x, y # or more args |
CoffeeScript functions
automatically return the last expression (if they are
not aborted with an explicit return
), making the final return
optional.
All arguments default to undefined
unless otherwise specified.
Defaults are evaluated during the function call (unlike Python which evalutes
them at function definition time).
Python | CoffeeScript |
---|---|
def rectangle(x, y = None):
if y is None:
y = x
return x * y |
rectangle = (x, y = x) -> x * y |
def f(x, add = 1):
y = x + add
return y*y |
f = (x, add = 1) ->
y = x + add
y*y |
add1 = lambda x: x+1
add = lambda x, y=1: x+y
zero = lambda: 0 |
add1 = (x) -> x+1
add = (x, y=1) -> x+y
zero = -> 0 |
def callback(x):
print('x is', x)
return f(x)
compute('go', callback) |
compute 'go', (x) ->
console.log 'x is', x
f(x) |
In CoffeeScript (like Perl and Ruby), the parentheses in function calls
are allowed but optional;
when omitted, they implicitly extend to the end of the line.
(Similar behavior is possible in IPython via the
%autocall
magic.)
Parentheses are still required for zero-argument function calls and when
an argument list ends before the end of the line.
In CoffeeScript (unlike Python), there can be no space between the function
name and the parentheses.
Python | CoffeeScript |
---|---|
f(5)
#or
f (5)
f(5, 10)
#or
f (5, 10) |
f(5)
#or
f 5
# f (5) is technically valid but only by luck
f(5, 10)
#or
f 5, 10
# f (5, 10) is INVALID: no space allowed between function and argument paren |
add(5, add1(add1(zero()))) |
add 5, add1 add1 zero() |
add(add1(1), 2) |
add add1(1), 2
#or
add (add1 1), 2 |
CoffeeScript functions do not support keyword arguments. The typical workaround is to use an object for an argument.
Python | CoffeeScript |
---|---|
f(add = 2, x = 10) |
f(10, 2)
# no keyword arguments in function calls |
def g(x, **options):
f(x, **options) |
g = (x, options) ->
f(x, options.add) |
CoffeeScript allows calling a function with a variable number of arguments,
and getting back all remaining arguments, with
splats (...
):
Python | CoffeeScript |
---|---|
x = [1, 2, 3]
print(*x)
#or
apply(print, x) |
x = [1, 2, 3]
console.log ...x
#or
console.log.apply console, x |
def add(first, *rest):
for arg in rest:
first += rest
return first |
add = (first, ...rest) ->
for arg in rest
first += arg
first |
Instead of ...x
, you can also write x...
. If you're using CoffeeScript v1,
you need to use the latter form.
CoffeeScript has no variable declarations like Python's
global
and
nonlocal
.
CoffeeScript's only behavior
is the equivalent of Python's nonlocal
:
a variable is local to a function if it is assigned in that function
and it is not assigned in any higher scope.
An exception is that function arguments are always local to the function,
with together with CoffeeScript's do
makes it simple to explicitly request
Python's default behavior.
Python | CoffeeScript |
---|---|
def f(x):
def next():
nonlocal x
x += 1
return x
return [next(), next()] |
f = (x) ->
next = ->
x += 1 # implicitly return x
[next(), next()] |
def f(x):
def g(x):
return -x
return g(x+1) |
f = (x) ->
g = (x) -> -x
g x+1 |
def delay(bits):
out = []
for bit in bits:
def g(bit = bit): # save current bit
return bit
out.append(g)
return out |
delay = (bits) ->
for bit in bits
do (bit) -> # force locally scoped bit
-> bit |
def recurse(x, stack = []):
for item in x:
stack.append(item)
recurse(item)
stack.pop()
recurse(root) |
stack = []
recurse = (x) ->
for item in x
stack.push item
recurse item
stack.pop()
recurse root |
CoffeeScript if
s
are similar to Python's, except that elif
is spelled else if
.
In addition, CoffeeScript offers unless
as a more intuitive if not
,
and allows a one-line suffixed if
(and unless
).
Python | CoffeeScript |
---|---|
if x:
y = 1
else:
y = 2 |
if x
y = 1
else
y = 2 |
if error:
return |
if error
return
#or
return if error |
if not ok:
continue |
unless ok
continue
#or
continue unless ok |
y = 1 if x else 2 |
y = if x then 1 else 2
#or
y =
if x
1
else
2 |
if x:
y = 1
elif z:
y = 2
else:
y = 3 |
if x
y = 1
else if z
y = 2
else
y = 3 |
y = 1 if x else (2 if z else 3) |
y =
if x
1
else if z
2
else
3 |
Unlike Python, CoffeeScript offers a
switch
expression
as an alternative to if
/then
/else
.
switch
is especially concise when branching on the same value.
There's also a one-line form for switch
cases, when...then
.
Python | CoffeeScript |
---|---|
if x:
y = 1
elif z:
y = 2
else:
y = 3 |
switch
when x
y = 1
when z
y = 2
else
y = 3 |
y = 1 if x else (2 if z else 3) |
y =
switch
when x
1
when z
2
else
3 |
if x == 0:
print('zero')
elif x == 1 or x == 2 or x == 3:
print('small')
elif x in ['hello', 'world']:
print('hi')
else:
print('unknown') |
switch x
when 0
console.log 'zero'
when 1, 2, 3
console.log 'small'
when 'hello', 'world'
console.log 'hi'
else
console.log 'unknown' |
def string(x):
if x == 0:
return 'zero'
elif x == 1 or x == 2 or x == 3:
return 'small'
elif x in ['hello', 'world']:
return 'hi'
else:
return 'unknown' |
string = (x) ->
switch x
when 0 then 'zero'
when 1, 2, 3 then 'small'
when 'hello', 'world' then 'hi'
else 'unknown' |
CoffeeScript while
loops
are roughly identical to Python's, including support for continue
and break
.
Like unless
, until
is shorthand for while not
.
In addition, loop
is shorthand for while true
.
Like if
and unless
, while
and until
have a one-line suffix form.
In addition, while
and until
loops are expressions, returning an array
of the final values.
Python | CoffeeScript |
---|---|
while this or that:
... |
while this or that
... |
while items:
items.pop() |
items.pop() while items.length |
reversed = []
while items:
reversed.append(items.pop()) |
reversed =
while items.length
items.pop() |
while not done:
... |
until done
... |
while True:
...
if done:
break |
loop
...
break if done |
line = getNextLine()
while line:
if not line.strip():
continue
...
line = getNextLine() |
while (line = getNextLine())
continue unless line.trim()
... |
CoffeeScript for...in
loops are
similar to Python's, including support for continue
and break
,
plus a concise notation for for...in enumerate(...)
.
In addition, for
loops are expressions
(like while
and until
loops),
returning an array of the final values.
Python | CoffeeScript |
---|---|
for x in [1, 2, 3]:
y += x |
for x in [1, 2, 3]
y += x |
for i, x in enumerate([1, 2, 3]):
y += x * i |
for x, i in [1, 2, 3]
y += x * i |
out = []
for x in [1, 2, 3]:
out.append(x * x)
#or
out = [x * x for x in [1, 2, 3]] |
out =
for x in [1, 2, 3]
x * x
#or
out = (x * x for x in [1, 2, 3]) |
for x in range(10):
y += x |
for x in [0...10]
y += x |
for x in range(5, 10):
y += x |
for x in [5...10]
y += x |
for x in range(5, 10, 2):
y += x |
for x in [5...10] by 2
y += x |
See also comprehensions.
CoffeeScript array comprehensions
are similar to Python's list comprehensions, but written with parentheses
instead of brackets and with when
instead of if
.
Unlike Python, they are just a one-line inverted form of a regular for
loop
(symmetric to the one-line inverted if
),
and can also be written in the non-inverted multiline form.
Python | CoffeeScript |
---|---|
y = [f(i) for i in x]
#or
y = list(map(f, x)) |
y = (f i for i in x)
#or
y =
for i in x
f i
#or
y = x.map f |
y = [f(i) for i in x if condition(i)]
#or
y = list(filter(condition, map(f, x))) |
y = (f i for i in x when condition i)
#or
y =
for i in x when condition i
f(i)
#or
y =
for i in x
continue unless condition i
f(i) |
z = [[f(i,j) for j in y] for i in x] |
y = (f i, j for j in y for i in x)
#or
y = ((f i, j for j in y) for i in x)
#or
y =
for i in x
for j in y
f i, j |
z = [f(i,j) for i in x for j in y] |
y = [].concat ...(f i, j for j in y for i in x)
#or
y = [].concat ...(
for i in x
for j in y
f i, j
) |
CoffeeScript lacks dictionary/object comprehensions and generator expressions, though it does have generator functions, which can be used to simulate generator expressions:
Python | CoffeeScript |
---|---|
it = (f(i) for i in x)
for item in it:
... |
it = do -> yield f(i) for i in x
for item from it
... |
Most Python comparison/Boolean operators have
the same name in CoffeeScript
(in addition to offering C-style names).
CoffeeScript also supports
chained comparisons
just like Python.
One key difference is that ==
and !=
are shallow comparisons, not deep:
they act like Python's ==
/!=
only for numbers and strings,
and act like Python's is
/isnt
for all other objects.
Another important difference is that []
and {}
are considered true
in CoffeeScript, so you need to check nonemptyness differently.
Python | CoffeeScript |
---|---|
True
False |
true
false |
1+2 == 3 # True
1 < 2 < 3 # True |
1+2 == 3 # true
1 < 2 < 3 # true |
x == 5 and not (y < 5 or done) |
x == 5 and not (y < 5 or done) |
b = bool(object) |
b = Boolean object
#or
b = not not object
#or
b = !!object |
if items: # check for nonempty list
process(items) |
if items.length # check for nonempty list
process items |
x = [1, 2, 3]
y = [1, 2, 3]
# pointer comparison
x is x # True
x is y # False
# deep comparison
x == x # True
x == y # True |
x = [1, 2, 3]
y = [1, 2, 3]
# pointer comparison
x == x # true
x == y # false
# deep comparison
_ = require 'underscore' #or lodash
_.isEqual x, x # true
_.isEqual x, y # true |
CoffeeScript arrays include
notation similar to Python list
s, as well as an indentation-based notion
that avoids the need for commas.
The resulting JavaScript Array
type
has many similar methods to Python list
, though often with different names.
Python | CoffeeScript |
---|---|
x = [1, 2, 3] |
x = [1, 2, 3]
#or
x = [
1
2
3
] |
3 in x
3 not in x |
3 in x
3 not in x |
len(x) |
x.length |
x = []
x.append(5)
x.append(10) |
x = []
x.push 5, 10 |
x = [1, 2, 3]
y = [4, 5, 6]
x.extend(y) |
x = [1, 2, 3]
y = [4, 5, 6]
x.push ...y |
x + y + z |
x.concat y, z
#or
[...x, ...y, ...z] |
last = x.pop()
first = x.pop(0) |
last = x.pop()
first = x.shift() |
x.reverse() |
x.reverse() |
# Lexical sort by string value
# e.g. [1, 10, 2]
x.sort(key = lambda item: str(item)) |
# Lexical sort by string value
# e.g. [1, 10, 2]
x.sort() |
# Sort by numeric value
# e.g. [1, 2, 10]
x.sort(key = lambda item: float(item)) |
# Sort by numeric value
# e.g. [1, 2, 10]
x.sort (x,y) -> x-y
# or, especially for stable sort:
_ = require 'underscore' #or lodash
x = _.sortBy x |
min(x)
max(x)
min(a, b)
max(a, b) |
Math.min ...x
Math.max ...x
Math.min a, b
Math.max a, b |
try:
i = x.index(a)
except ValueError:
i = -1 |
i = x.indexOf a |
try:
i = x.index(a, 5)
except ValueError:
i = -1 |
i = x.indexOf a, 5 |
CoffeeScript destructuring assignment is a nice tool for extracting parts of arrays:
Python | CoffeeScript |
---|---|
a, b = b, a |
[a, b] = [b, a] |
head, *tail = [1, 2, 3] |
[head, ...tail] = [1, 2, 3] |
See also array slicing.
CoffeeScript has no analog to Python's tuple
type, but the same effect of
"an unchangable list" can be obtained via Object.freeze
:
Python | CoffeeScript |
---|---|
x = (1, 2) |
x = Object.freeze [1, 2] |
Python has two key/value mechanisms: hasattr
/getattr
/setattr
for object attributes and __contains__
/__getitem__
/__setitem__
for dictionaries. CoffeeScript has no such asymmetry, making regular
Object
s a fine substitute for dictionaries.
Python | CoffeeScript |
---|---|
d = {1: 2, 'hello': 'world'} |
d = {1: 2, hello: 'world'}
#or
d =
1: 2
hello: 'world' |
d = dict((i, i**2) for i in range(10)) |
d = Object.fromEntries([i, i**2] for i in [0...10])
#or
d = Object.fromEntries(
for i in [0...10]
[i, i**2]
) |
d.get(key) |
d[key] |
d.get('hello') |
d.hello
#or
d['hello'] |
d.set('hello', 'bye') |
d.hello = 'bye'
#or
d['hello'] = 'bye' |
del d[key] |
delete d[key] |
key in d |
key of d |
for key in d:
f(key) |
for key of d
f key
# Safer version, in case Object has added properties:
for own key of d
f key |
for key, value in d.items():
... |
for key, value of d
... |
list(d.keys())
list(d.values())
list(d.items()) |
Object.keys d
Object.values d
Object.entries d |
len(d) |
Object.keys(d).length |
if d: # nonempty dict?
process(items) |
if Object.keys(d).length # nonempty Object?
process d |
d.setdefault(key, value) |
d[key] ?= value |
d.setdefault(key, []).append(value) |
(d[key] ?= []).push value |
CoffeeScript destructuring assignment is a nice tool for extracting parts of objects:
Python | CoffeeScript |
---|---|
a = d['a']
b = d['b']
c = d['c']
c_x = c['x'] |
{a, b, c: {x}} = d |
head, *tail = [1, 2, 3] |
[head, ...tail] = [1, 2, 3] |
An important limitation of Object
s as key/value stores is that all keys
are mapped to strings: the reference d[x]
is equivalent to d[x.toString()]
.
One weird consequence is that d[42]
and d['42']
refer to the same item.
On the plus side, it gives you a limited way to override key equality testing
(like Python's __hash__
and __eq__
), by using/overriding the
toString
method:
Python | CoffeeScript |
---|---|
d[1,2] = 'a' |
d[[1,2]] = 'a'
# equivalent to d['1,2'] = 'a' |
# d has keys of the form (int, int)
for x, y in d:
...
for (x, y), value in d.items():
... |
# d has keys of the form 'int,int'
for key of d
[x, y] = key.split ','
...
for key, value of d
[x, y] = key.split ','
... |
class Pair:
def __init__(self, x, y):
self.x = x
self.y = y
def __hash__(self):
return hash((self.x, self.y))
def __eq__(a, b):
return a.x == b.x and a.y == b.y
d[Pair(1,2)] = 'a' |
class Pair
constructor: (@x, @y) ->
toString: -> "#{@x},#{@y}"
d[new Pair 1, 2] = 'a'
# equivalent to d['1,2'] = 'a' |
While CoffeeScript Object
s are a good substitute for dictionaries,
they have
a few limitations,
most notably, that all keys in Object
s get mapped to strings.
An alternative is the built-in Map
type, which allows arbitrary object keys,
and distinguishes numbers (42
) from their string equivalents ('42'
).
Unlike Python's dict
, however, Map
s offer no way to override the hash
function or equality testing (Python's __hash__
and __eq__
): objects are
treated as identical keys
if they match according to
CoffeeScript's ==
operator, so
numbers and strings are compared by value, while all other
objects are compared according to reference identity
(like Python's is
).
(Also, unlike ==
, NaN
is treated equal to itself
and -0
and +0
are treated equal.)
Map
's syntax is also uglier than regular Object
s.
Python | CoffeeScript |
---|---|
d = {1: 2, 'hello': 'world'} |
d = new Map [
[1, 2]
['hello', 'world']
] |
len(d) |
d.size |
d.get(key) |
d.get key |
d[key] = value |
d.set key, value |
del d[key] |
d.delete key |
key in d |
d.has key |
for key, value in d.items():
... |
for [key, value] from d
... |
d.keys()
d.values()
d.items() |
d.keys()
d.values()
d.entries() |
d.setdefault(key, value) |
d.set key, value unless d.has key |
CoffeeScript offers a Set
class similar to
CoffeeScript's Map.
It shares the limitation of testing equality according to
CoffeeScript's ==
operator, so
numbers and strings are compared by value, while all other
objects are compared according to reference identity
(like Python's is
).
If you instead want "render as identical strings" semantics,
use regular CoffeeScript Objects
(with constant values, e.g., true
).
Python | CoffeeScript |
---|---|
x = set() |
x = new Set |
x = {1, 2, 3} |
x = new Set [1, 2, 3] |
# x is a set
5 in x
x.add(5)
x.discard(7) |
# x is a set
x.has 5
x.add 5
x.delete 7
|
# x is a set
if x:
print len(x), 'elements'
else:
print 'empty' |
# x is a Set
if x.size
console.log x.size, 'elements'
else
console.log 'empty' |
# x is a set
for item in x:
print item |
# x is a Set
for item from x
console.log item |
iter(x) |
x.values() |
CoffeeScript slicing features two
notations for the range from i
to j
: i...j
excludes j
like
Python's i:j
, while i..j
includes j
.
Python | CoffeeScript |
---|---|
list(range(7, 10))
# [7, 8, 9] |
[7...10]
#or
[7..9] |
list(range(5))
# [0, 1, 2, 3, 4] |
[0...5]
#or
[0..4]
# [...5] and [..4] are invalid |
list(range(0, 10, 2))
# [0, 2, 4, 6, 8] |
(i for i in [0...10] by 2)
# [0...10] by 2 is invalid |
# list not generated (in Python 3)
for i in range(9999):
... |
# list not generated
for i in [0...9999]
... |
# x is str or list
x[7:10] # 7, 8, 9 |
# x is String or Array
x[7...10] # 7, 8, 9
#or
x[7..9] # 7, 8, 9 |
# x is list
x[7:10] = ['a', 'b']
x.insert(0, 'c')
x.insert(7, 'd')
del x[7]
del x[7:10]
x.clear()
y = x.copy() |
# x is Array
x[7...10] = ['a', 'b']
x.unshift 'c'
x[7...7] = ['d']
x[7...7] = []
x[7...10] = []
x[..] = []
y = x[..] |
# x is str or list
x[:] # shallow copy |
# x is String or Array
x[..] # shallow copy |
# x is str or list
x[:-1] |
# x is String or Array
x[...-1] |
Note that negative numbers behave like Python in slices,
but negative numbers behave differently when simply getting an item:
x[-1]
is equivalent to x['-1']
and will typically return undefined
;
to access the last element, use x[x.length-1]
.
Python has one "null" value, None
.
CoffeeScript has two, undefined
and null
.
Essentially, undefined
is the default initial value for all variables
(a notion absent from Python), while null
is an explicit null value.
Python | CoffeeScript |
---|---|
x = None |
# x is automatically undefined
# explicit setting:
x = undefined
# alternate None-like value:
x = null |
CoffeeScript defines an
existential ?
operator,
both a unary form to test for undefined
or null
, and a binary form
to provide alternate (default) values in case of undefined
or null
:
Python | CoffeeScript |
---|---|
if x is not None:
... |
if x?
...
#equivalent to:
if x != undefined and x != null
... |
y = 5 if x is None else x |
y = x ? 5 |
CoffeeScript also defines
many conditional operators
that apply the operator only when the left-hand side isn't
undefined
or null
(and otherwise leaves it alone):
Python | CoffeeScript |
---|---|
try:
x
except UnboundLocalError:
x = 5 |
x ?= 5 |
# d is a dictionary
d.setdefault(key, value) |
# d is an object
d[key] ?= value |
d[key] if d is not None else d |
d?[key] |
if callback is not None:
callback(value) |
callback?(value)
#or
callback? value |
if x is not None and hasattr(x, 'set') and x.set is not None:
x.set(5) |
x?.set?(5)
#or
x?.set? 5 |
CoffeeScript has built-in Perl-like /.../
syntax for regular expressions,
and a triple-slash version ///...///
for multiline regular expressions
that ignores whitespace.
Python | CoffeeScript |
---|---|
r = re.compile(r'^Hello (\w+)',
re.IGNORECASE | re.MULTILINE) |
r = /^Hello (\w+)/im |
r = re.compile(r'<p>.*?</p>', re.DOTALL) |
# ECMAScript 2018 / Node 9+
r = /<p>.*?<\/p>/s
# Older
r = /<p>[^]*?<\/p>/ |
r = re.compile(r'[(\[]*(\d+)/(\d+)/(\d+)[)\]]*') |
r = /[(\[]*(\d+)\/(\d+)\/(\d+)[)\]]*/
#or
r = ///
[(\[]* # leading brackets
(\d+) / (\d+) / (\d+) # y/m/d
[)\]]* # closing brackets
/// |
def bracket(word):
return re.compile(r'^[(\[]*' + word + r'[)\]]*') |
bracket = (word) ->
new RegExp "^[(\\[]*#{word}[)\\]]*"
#or
bracket = (word) ->
/// ^[(\\[]* #{word} [)\\]]* /// |
match = r.search(string)
match.group(0) # whole match
match.group(1) # first group
match.start() # start index
match.string # input string |
match = r.exec string
match[0] # whole match
match[1] # first group
match.index # start index
match.input # input string |
if r.search(string):
... |
if r.test string
... |
for match in re.finditer(r'(pattern)', string):
match.group(0) # whole match
match.group(1) # first group
match.start() # start index
match.string # input string |
r = /(pattern)/g # keeps lastIndex
while (match = r.exec string)?
match[0] # whole match
match[1] # first group
match.index # start index
match.input # input string
# r.exec can be re-used on other strings |
matches = re.findall(r'pattern', string) |
matches = string.match /pattern/g
# Array of full matches, ignoring all groups |
out = re.sub(r'pattern', repl, string) |
out = string.replace /pattern/g, repl |
out = re.sub(r'pattern', repl, string, 1) |
out = string.replace /pattern/, repl |
out = re.sub(r'(pattern)', r'$(\1) \g<0>', string) |
out = string.replace /(pattern)/g, '$$($1) $&' |
def replacer(match):
all = match.group(0)
group1 = match.group(1)
index = match.start()
string = match.string
...
out = re.sub(r'(pattern)', replacer, string) |
out = string.replace /(pattern)/g,
(all, group1, index, string) ->
# (unneeded arguments can be omitted)
... |
out = re.split(r'\s*,\s*', string) |
out = string.split /\s*,\s*/ |
out = re.split(r'\s*(,)\s*', string) |
out = string.split /\s*(,)\s*/ |
out = re.split(r'\s*,\s*', string)[:limit] |
out = string.split /\s*,\s*/, limit |
JavaScript regular expression syntax and usage is roughly the same as Python, with some exceptions:
- JavaScript doesn't support
(?P<...>...)
,(?<=...)
,(?<!...)
,(?#...)
,(?(...)...|...)
,\A
, or\Z
in regular expressions. - In JavaScript,
/
needs to be escaped as\/
within/.../
. Also, spaces right next to the surrounding/
s can confuse the parser, so you may need to write them as[ ]
(for example). - JavaScript doesn't support flag-changing syntax:
instead of
(?i)
and(?m)
, use/.../i
and/.../m
. - JavaScript doesn't support flags
re.ASCII
,re.DEBUG
, orre.LOCALE
. (///...///
is the analog ofre.VERBOSE
.) - JavaScript's
\d
matches just[0-9]
, and\w
matches just[a-zA-Z_]
, instead of the Unicode notions matched by Python. However,\s
matches all Unicode space characters like Python. - JavaScript replacement patterns need to use
$...
instead of\...
(and$50
instead of\g<50>
), and thus need to have$
escaped as$$
. The full-match replacer\g<0>
should instead be$&
. Additional replacement features are$`
, which expands to the portion of the string before the match, and$'
, which expands to the portion of the string after the match.
CoffeeScript classes behave similar
to Python classes, but are internally implemented with prototypes, so do
not support multiple inheritence. CoffeeScript provides @
as a helpful
alias for this
(the analog of self
in Python), and @foo
as an alias
for @.foo
.
Python | CoffeeScript |
---|---|
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def translate(self, dx, dy):
self.x += dx
self.y += dy
def __str__(self):
return "({}, {})".format(self.x, self.y) |
class Point
constructor: (@x, @y) ->
translate: (dx, dy) ->
@x += dx
@y += dy
null # otherwise, would return @y
toString: ->
"(#{@x}, #{@y})" |
p = Point(1, 2)
p.translate(3, 4)
print(p) |
p = new Point 1, 2
p.translate 3, 4
console.log "#{p}"
#or
console.log p.toString()
# Note: console.log p will not call toString() |
trans = p.translate
trans(5, 6) |
trans = p.translate
# trans 5, 6 will use this = global scope
trans.call p, 5, 6
#or
trans = p.translate.bind p
trans 5, 6
#or
trans = (...args) -> p.translate ...args
trans 5, 6 |
isinstance(p, Point) # True
isinstance(p, object) # True in Python 3 |
p instanceof Point # true
p instanceof Object # true |
class PPoint(Point):
dim = 2
@classmethod
def parse(Class, string):
return Class(*[float(word)
for word in string.split(",")]) |
class PPoint extends Point
@dim: 2
@parse: (string) ->
# @ = this is the class in an @method
new @ ...(parseFloat word \
for word in string.split ",") |
print(PPoint.dim)
PPoint.slope = lambda self: self.y / self.x |
console.log PPoint::dim
PPoint::slope = -> @y / @x |
Within methods, use =>
in place of ->
to construct a function with
the same value of this
(@
) as the method creating it.
Python | CoffeeScript |
---|---|
class Accumulator:
def __init__(self):
self.value = 0
def adder(self):
def add(x):
self.value += x
return add |
class Accumulator
constructor: ->
@value = 0
adder: ->
(x) => @value += x |
Exceptions are far less important in CoffeeScript than Python because
most invalid operations return special values like NaN
or undefined
instead of raising an exception.
Python | CoffeeScript |
---|---|
try:
x = a/b
except ZeroDivisionError:
if a > 0:
x = math.inf
elif a < 0:
x = -math.inf
else:
x = math.nan |
x = a/b |
try:
x = d[key]
except KeyError:
x = None
#or
x = d.get(key) |
x = d[key] |
# f either needs 1 argument
# or needs 0 arguments
try:
f(argument)
except TypeError:
f() |
# f will ignore excess arguments
f argument |
# f either needs 1 argument
# or needs 0 arguments
try:
f()
except TypeError:
f(None) |
# Missing f argument defaults to undefined
f() |
But exceptions still exist, and can be captured in a similar way:
CoffeeScript try
is similar to Python's,
except there is no exception type matching.
Python | CoffeeScript |
---|---|
try:
f()
except Exception as e:
...
finally:
cleanup() |
try
f()
catch e
...
finally
cleanup() |
try:
f()
except ErrorType1 as e:
...
except ErrorType2 as e:
...
except Exception as e:
... |
try
f()
catch e
if e instanceof ErrorType1
...
else if e instanceof ErrorType1
...
else
... |
raise RuntimeError('Oops!') |
throw new Error 'Oops!' |
See JavaScript's built-in Error types.
CoffeeScript generator functions
are roughly identical to Python's: any yield
statement makes a function
a generator function, and both support yield from
.
The main difference is in the resulting
Generator object,
which has a different next()
interface and does not support send()
.
Most important is that looping over generators (and iterators in general)
requires for...from
instead of for...in
in CoffeeScript.
Python | CoffeeScript |
---|---|
def positive_ints():
n = 0
while True:
n += 1
yield n |
positive_ints = ->
n = 0
loop
n += 1
yield n |
for i in positive_ints():
... |
for i from positive_ints()
... |
def concat(iter1, iter2):
yield from iter1
yield from iter2 |
concat = (iter1, iter2) ->
yield from iter1
yield from iter2 |
L = [1, 2]
it = iter(L)
one = it.next()
two = it.next() |
L = [1, 2]
it = L[Symbol.iterator]()
one = it.next().value
two = it.next().value |
try:
it.next()
done = False
except StopIteration:
done = True |
done = it.next().done |
CoffeeScript async functions
are similar to Python's
(which coevolved to be similar to JavaScript's),
except that there's no need to declare a function async
; like
generator functions, any function with an await
keyword is automatically async
.
Python | CoffeeScript |
---|---|
async def process(db):
data = await db.read('query')
return f(data) |
process = (db) ->
data = await db.read 'query'
f data |
async def fast():
return 'done' |
fast = ->
await 'done' |
async def slow():
print('hello')
await asyncio.sleep(1)
print('world') |
sleep = (seconds) ->
new Promise (resolve) ->
setTimeout resolve, seconds * 1000
slow = ->
console.log 'hello'
await sleep 1
console.log 'world' |
To install CoffeeScript on your machine, first
install NodeJS.
(LTS = Long Term Support is a good choice.)
Or install NodeJS with a package manager.
This will install (at least) two commands, node
and npm
.
Then run the following command:
npm install --global coffeescript
You should then have a command coffee
that runs the interactive interpreter,
similar to python
. The main difference is, if you want to input a multiline
command, you need to press CTRL-V before the first newline, and press CTRL-V
again when you're done (instead of indicating completion with a blank line).
You can also compile a CoffeeScript file filename.coffee
into a
JavaScript file filename.js
via
coffee -c filename.coffee
Like Python, you can split up your code into multiple files, and add
dependencies between them.
In a Node environment, the analog of import
is require
:
Python | CoffeeScript |
---|---|
import helper # load ./helper.py |
helper = require './helper' # load ./helper.coffee or ./helper.js
#or
helper = require './helper.coffee' |
from helper import data, scheme |
{data, scheme} = require './helper' |
A key difference is that modules need to explicitly "export" variables
that they want to expose to other modules by attaching them to the
exports
object; local variables are private.
Python | CoffeeScript |
---|---|
# helper.py
reps = 5
data = 'stuff' * reps
scheme = lambda: 'http'
del reps # hide variable from outside |
# helper.coffee
reps = 5
exports.data = 'stuff'.repeat reps
exports.scheme = -> 'http' |
Here is how to detect whether you are the "main" module (being executed
directly by coffee
or node
):
Python | CoffeeScript |
---|---|
if __name__ == '__main__':
... |
if require.main == module
... |
The analog of PyPI (Python Package Index)
is NPM (Node Package Manager).
The analog of command-line tool pip
is npm
(or an alternative called yarn
).
Unlike PyPI, NPM packages are usually installed locally to each project, which makes it easy for different projects to use different versions of the same package. To get started, run
npm init
This will ask questions for the creation of a stub package.json
file.
Then you can install packages local to your project using npm install
.
For example, to install the
underscore
package
(written by the same author as CoffeeScript), run
npm install underscore
This will install the package in node_packages/underscore
,
and change package.json
to note which version you depend on.
You can use a package installed in this way via
_ = require 'underscore'
It's also easy to create your own packages and publish them for others to use.
This document is by Erik Demaine, with helpful input from several others. The top image is based on the CoffeeScript logo and this free Python clipart.