Skip to content

Cheat Sheet: Clojure

gilch edited this page Dec 12, 2025 · 67 revisions

This is a cheat sheet, not a tutorial. It's meant to refresh your memory or help you find search terms for the docs, for those familiar with Clojure. Not all alternatives are macros. Not all macros are listed, only those that correspond to Clojure features.

For a more Clojure-like experience, also check out toolz (core functions), pyrsistent (persistent data structures), and Garden of EDN.

The equivalent of doc is help. Metaprograms live in a separate _macro_ namespace, so try (help _macro_.foo) for macros or (help _macro_.foo\#) for tags.

Clojure Literals Alternatives
123/123N 123 (type int)
1.23 1.23 (type float)
1.23M M#1.23_ (type decimal.Decimal)
2/3 Q#2/3 (type fractions.Fraction)
nil None (Python's null), () (empty tuple)
true/false True/False
##Inf M#inf (type decimal.Decimal), math..inf, bn##float inf, .#(float 'inf), 1e999 (overflow)
##-Inf M#-inf, .#(neg math..inf), bn##float|-inf|, .#(float "-inf"), -1e999 (underflow)
##NaN M#nan, math..nan, bn##float nan, .#(float 'nan), |(1e999-1e999)| (indeterminate form)
'foo/foo 'foo (symbol, type str)/foo (identifier)
namespace/foo namespace..foo (import), foo#foo (alias tag)
:foo :foo (control word; type str). Prefer symbols for a mapping's keys.
:namespace/foo, ::foo/foo, ::foo Use symbols instead: 'namespace..foo, 'foo#foo (alias), `foo (template quote)*.
"a string" "a string" (type str)
\c "c". (See Special Reader Syntax table.)
Clojure Collections Alternatives
'(1 2 3) '(1 2 3) (type tuple[int, ...])
'(a "foo bar") '(a |foo bar|) (type tuple[str, ...])
`(~a ~@b 3) `(,a ,@b 3)
[a b c] (@ a b c) (type list, mutable), (|| a b c ||) (type tuple)
`[~a ~@b c] (@ a :* b 'c)
#{1 2 3} (# 1 2 3) (type set, mutable), (en#frozenset 1 2 3) (immutable)
{:foo 1, :bar 2} (dict : foo 1 bar 2)
{:foo 2, 'spam "eggs"} (% :foo 2 'spam "eggs") (type dict, mutable)
#::{:foo 2, :_/bar a, :baz/baz 6} Use symbols instead: .#`(% 'foo 2 ','bar ,a 'baz..baz 6) (injected template)*
#:foo{:foo 2, :_/bar a, :baz/baz 6} No support, but metaprogram is only a few lines.** Just spell out or use alias tags.
{[1 2] 3, #{1 2} 3} (% (|| 1 2 ||) 3 (en#frozenset 1 2) 3)
{{1 2 3 4} 5} Python has no hashable mapping type, but implementation is only a few lines.***

*Recall Python root __name__ is __main__, regardless of filename.

**example namespaced dict macro...
(defmacro ns% (ns : :* kvs)
  my#(progn p=XY#(ands (H#is_symbol Y) (H#reader.is_qualifiable Y))
            p=X#(eq X `',(mk#Mock : __eq__ my.p))
            ks=(map X#(if-else (my.p X) `',|f"{ns}..{X[1]}"| X) |kvs[::2]|)
            `(% ,@chain#(zip my.ks |kvs[1::2]| : strict True))))

usage examples:

#> (define a 4)
#> (ns% foo (|| 'no-ns) a  "no-ns" 'a  'got-ns 2  'spam 'eggs  'ns..other "d"  3 4)
{'noQzH_ns': 4, 'no-ns': 'a', 'foo..gotQzH_ns': 2, 'foo..spam': 'eggs', 'ns..other': 'd', 3: 4}
#> (ns% foo 'bar 4  'nested (ns% spam 'eggs 1  'bacon 2)  'normal (% 'foo 2))
{'foo..bar': 4, 'foo..nested': {'spam..eggs': 1, 'spam..bacon': 2}, 'foo..normal': {'foo': 2}}
#> (ns% foo 'bar 4  :** (ns% spam 'eggs 1  'bacon 2)  :** (% 'foo 2))
{'foo..bar': 4, 'spam..eggs': 1, 'spam..bacon': 2, 'foo': 2}
***hashable mapping...

(This version uses hash(proxy), which requires Python 3.12+.)

(let (cls (type '_HashableDict `(,dict)
           (dict : __slots__ ()  __hash__ X#(-> X .items frozenset hash))))
  (defun frozendict(: kvs ()  :/ :?  :** kwargs)
    (types..MappingProxyType (cls kvs : :** kwargs))))

Python dicts remember insertion order, but this doesn't affect equality, hence the hash function must use frozenset rather than tuple.

Because it's immutable, the hash result could be memoized for better time performance, but for comparison, CPython didn't cache tuple hashes until version 3.15 due to questionable benefits for the cost.

This implementation is not pickleable. Dill can serialize them, but when it restores them, they're no longer hashable. If you can add dependencies, instead try immutables or pyrsistent libraries for persistent, serializable, and hashable mappings.

Clojure Callable* Literals Alternatives
('foo m) !##'foo m
(:foo m) !##:foo m
({'x 1, 'y 2} k) (.get (% 'x 1 'y 2) k), (-> (% 'x 1 'y 2) !#k), (op#getitem (% 'x 1 'y 2) k)
(#{1 2} x) (op#contains (# 1 2) x)
([:a :b] i) (-> (@ :a :b) !#i), (op#getitem (@ :a :b) i)
(map 'foo ms) (map &#!#'foo ms)
(map :foo ms) (map &#!#:foo ms)
(map {'x 1} xs) (map @##'get(% 'x 1) xs)
(map #{1 2} xs) (map X#|X in {1,2}| xs), (map X#(op#contains (# 1 2) X) xs), (map @##'__contains__(# 1 2) xs)**
(map [:a :b] xs) (map X#|[':a',':b'][X]| xs), (map X#(-> (@ :a :b) !#X) xs), (map @##'__getitem__(@ :a :b) xs)**

*You can implement callable types using the __call__() magic method.

**easily shortened...
(define HAS '__contains__)
(define GET '__getitem__)

(map @##HAS(# 1 2) xs)
(map @##GET(@ :a :b) xs)
Clojure Special Forms Alternatives
def define
if if-else/when
do progn
let* let*from (does simple destructuring and self-shadowing),
letfn my#. Maybe type.
quote quote
var proxy#. See also: defvar, contextvars.
fn lambda, fun
loop/recur loop-from/recur-from
set! set! (items), set@ (attributes)
throw throw. See also: throw-from.
try engarde (prelude)
monitor‑enter / monitor‑exit Maybe enter (prelude) combined with the threading module and locks. Not really used in "user"-level Clojure.
Clojure Macros Alternatives
->/->> ->/-<>>
. (.bar self...) (methods), foo.bar (attribute from identifier), @##' (attribute lookup tag), getattr (builtin function)
.. foo.bar.baz (from identifier) or -> with @#', like (-> foo @#'bar.baz)
alias alias
amap map and array.
and ands. Python's truthiness is different from Clojure's.
areduce /#, /XY#, ft#reduce
as-> -<>> with :<>. Maybe combined with my# or X# for repeats.
assert assure. See also: avow.
binding binding (see defvar, contextvars). Maybe unittest.mock.patch (like with-redefs).
bound-fn/bound-fn* Probably (.run (cxv#copy_context)-.
case case
comment Useless. Try ()_#(-) or similar. Trivial to implement yourself.
cond cond
cond->/cond->> Maybe my#(progn- with whens (imperative style).
condp my# on a cond
declare (define foo None), probably. Python mostly doesn't need it, but it affects template quotes, which mostly work anyway.
definline "Experimental" in Clojure. Define a macro (defmacro) and a function (defun) in terms of the macro, with the same name.
definterface abc., probably. Python is not Java.
defmacro defmacro
defmulti/defmethods deftypeonce/defuns, probably. Maybe :@##ft#singledispatch, which is not properly reloadable (but neither are Clojure multimethods).
defn defun
defn- defun and prepend an underscore to the name. Python mostly doesn't enforce privacy, but this is the convention.
defonce defonce
defprotocol abc., probably. Python's not Java.
defrecord deftupleonce
deftype deftypeonce
delay (ft#cache O#-). (Call it to force/deref/@.)
deref (.get *foo*), if *foo* is a context variable.
doseq any-map
dosync No STM in Python (ClojureScript either, really). Maybe in-memory sqlite3.
dotimes any-map on a range
doto doto
extend-protocol Not applicable; duck typing.
extend-type Maybe register (if using @singledispatch). Python classes are mutable.
for Ensue, probably. Also the iterator builtins and itertools (and chain#). Maybe with my#. Maybe mix on a Python comprehension.
future concurrent.futures.
gen-class types..new_class. (See also: exec, type, deftypeonce, @dataclass.)
gen-interface abc., probably. Python's not Java.
if-let my# on if-else or when
if-not unless
if-some my# on if-else or when, with an is not None check or something.
import define on a fully qualified name. Prefer aliases.
io! Not applicable. No STM, but see sqlite3.
lazy-cat chain, chain#.
lazy-seq Ensue (prelude). See also: tee.
let destruct->*. See also: let. (Yes, let is a macro in Clojure. It macroexpands in terms of let*.)
locking enter (prelude) with threading. locks.
memfn No need. Python's not Java.
ns prelude, alias. Need something to set _macro_, rather than *ns*.
or ors. Python's truthiness is different from Clojure's.
proxy, proxy-super type, probably. Usually not required, due to duck typing (ClojureScript either).
pvalues concurrent.futures..map
refer define on a fully qualified name. E.g., (define _macro_.foo bar.._macro_.foo)
refer-clojure prelude, or custom variants.
reify type, probably. Usually not required, due to duck typing.
require importlib., module handles, define, alias.
some->/some->> my# on ands
sync No STM, but see sqlite3.
time time#, timeit..timeit
vswap! Not applicable. (Everything's on the heap in Python.)
when when
when-first Iterators are mutable/stateful, but consider i#tee for lookahead or i#chain to put it back. Empty collections are already falsey.
when-let my# on when
when-not unless
when-some my# on when, with an is not None check or something.
while takewhile, dropwhile, loop-from, Ensue (prelude).
*destructure example...

Clojure's destructuring syntax has a variety of features:

(let [[a b & cs :as abcs] "abcdef" ; rest and :as
      [d e f] "d" ; nil extras
      [g h i] "ghi" ; simple positional
      ;; key lookup, syms shorthand, nil key, :or default, :as
      {j 'j, :syms [k l], m 'm, n 'n, :or {n "N?"}, :as jkls} {'j "J", 'k "K", 'l "L"}]
  (println a b cs abcs d e f g h i j k l m n jkls))

Clojure also has :strs and :keys for other map key types, but these all read as type str in Lissp.

Lissp's destruct-> has similar capabilities:

(destruct-> (@ "abcdef" "d" "ghi" (% 'j "J"  'k "K"  'l "L"))
            pos#((iter (next a  next b  || cs)  || abcs) ; remaining iterator, and all
                 (iter (next d  (next ()) e  (next None) f)) ; () or None extras
                 pos#(g h i) ; positional shorthand
                 ;; key, !s# shorthand, None key, .get default, all
                 (!#'j j  || !s#(k l)  (.get 'm) m  (.get 'n "N?") n  || jkls))
  (print a b (list cs) abcs d e f g h i j k l m n jkls))

There's also a @s# helper to destructure by attribute, analogous to !#s. Note that the bindings aren't valid until the body. For nested bindings, use nested destruct-> forms or use let*from instead:

(let*from ((abcs) (@ "abcdef") ; whole thing
           (a b : :* cs) abcs ; rest
           (: d ()  e ()  f ()) "d" ; defaults
           (g h i) "ghi" ; simple positional
           (jkls) (@ (% 'j "J"  'k "K"  'l "L")) ; whole thing
           ;; dict to iterator with defaults
           (j k l m n) (map (lambda (k : _or (% 'n "N?"))
                              (.get jkls k (.get _or k)))
                            '(j k l m n)))
  (print a b cs abcs d e f g h i j k l m n jkls))

let*from only unpacks from iterables, but expressions can convert data to that form. Unpacking nested data requires additional layers, but each layer can use the previous layer's bindings. destruct-> can more easily handle deeply nested data.

Clojure with- Macros Alternatives (See enter from prelude)
with-bindings (.run (cxv#copy_context)...
with-in-str with mk#patch on sys.stdin, replaced with an io..StringIO.
with-local-vars my#
with-open with contextlib..closing
with-out-str with contextlib..redirect_stdout
with-precision with decimal..localcontext
with-redefs with mk#patch
Clojure Special Reader Syntax Alternatives
#inst inst#
#uuid uuid#
*data-readers* _macro_
*default-data-reader-fn* __getattr__ (In _macro_'s class.)
#? Not applicable, but .#(cond- could be similar.
#?@ Not applicable, but .#` and ,@(cond- could be similar.
' '
\x "x". Use a length-1 string instead (also works in ClojureScript).
\space "\N{space}" (Any Unicode name, not case sensitive.)
\uABCD/\o52 "\uABCD"/"\52"
; ; Beware these can be arguments for tags.
@ (.get-) for context vars. No STM types.
^ __annotations__ (for classes and functions, maybe with typing..Annotated). attach (only namespace types). weakref..WeakKeyDictionary (Only weakref hashable types. Try Pyrsistent for more hashable collections.) col#UserString, col#UserDict, and col#UserList (namespace alternatives for common non-namespace types). Make a subclass with type (for other non-namespace types).
#{-} (#-)
#"regex" re..compile#|regex|. Compiles as a pickle, so no better than run time, but re. functions also allow string arguments and it caches their compiled patterns automatically.
#' proxy#. See also: defvar, contextvars.
#(-) %# or Xᵢ# from the macro tutorial. See also, O#, X#, XY#, XYZ#, XYZW#, en#X#/!##.
#_ _#
` `
~ , (Use an extra space or comment for grouping instead.)
~@ ,@
foo# (auto gensym) $#foo. Unlike Clojure, these also work in an unquote context.

For alternatives to Clojure functions, you need the toolz library and/or a Python standard library reference.

Clone this wiki locally