22 KiB
Elisp Cheatsheet for Python Programmers
This document is for readers who are familiar with the Python programming language and wish to apply that knowledge to writing Emacs Lisp (Elisp). It is intended to be a “cheat sheet”/quick reference and should not be considered a primary source for either Python or Emacs APIs.
❗ This is a work in progress. Constructive feedback is encouraged.
Table of Contents TOC_3
Guiding Principles
- This document uses Python 3.12 and Emacs Lisp 29.1+ APIs.
- Only Emacs 29.1+ built-in packages are used.
-
Python code translated to Elisp emphasizes using generic (aka polymorphic) functions.
- This lowers the cognitive load of working with different Elisp sequence types (list, vector) and map types (hash-table, alist).
- The Emacs packages
seq.el
andmap.el
do the heavy lifting here for sequence and map types respectively.
- Achieving correct and unsurprising behavior using conventional programming abstractions for sequence and map types is the primary concern.
- Performance is at best a tertiary concern.
Python List to Elisp List
The Elisp list type is best understood as a linked-list data structure. Elisp analogs to the Python list methods insert
and append
are relatively expensive to do and as such is frowned upon.
s: list x: any t: list n: integer i: integer j: integer
❗All Elisp translations will not mutate the original input unless noted.
Python | Elisp Generic | Elisp Type-Specific | Notes |
---|---|---|---|
s = [] , s = list() |
(setq s (list)) |
||
x in s |
(seq-contains-p s x) |
||
x not in s |
(not (seq-contains-p s x)) |
||
s + t |
(seq-concatenate 'list s t) |
(append s t) |
|
s * n or n * s |
(seq-map (lambda (a) (* n a)) s) |
||
s[i] |
(seq-elt s i) |
||
s[i:j] |
(seq-subseq s i j) |
||
s[i:j:k] |
|||
len(s) |
(seq-length s) |
||
min(s) |
(seq-min s) |
Elements of s must be orderable. |
|
max(s) |
(seq-max s) |
Elements of s must be orderable. |
|
s.index(x) |
(seq-position s x) |
||
s.count(x) |
(seq-count (lambda (a) (= x a)) s) |
||
s[0] |
(seq-first s) |
||
s[-n] |
(seq-first (seq-subseq s -n)) |
||
if not s: |
(seq-empty-p s) |
||
s[i] = x |
|||
s[i:j] = t |
|||
del s[i:j] |
|||
del s[i] |
(seq-remove-at-position s i) |
||
s[i:j:k] = t |
|||
del s[i:j:k] |
|||
s.append(x) |
|||
s.clear() |
(setq s (list)) |
||
s.copy() |
(seq-copy s) |
||
s.extend(t) |
(append s t) |
||
s *=n |
(seq-map (lambda (a) (* n a)) s) |
||
s.insert(i, x) |
|||
s.pop() |
(pop s) |
s is mutated. |
|
s.insert(0, x) |
(push s x) |
s is mutated. |
|
s.remove(x) |
(seq-remove (lambda (a) (= x a)) s) |
||
s.reverse() |
(reverse s), (nreverse s) |
nreverse will destructively mutate s . |
Python Tuple to Elisp Vector
The closest Elisp analog to a Python tuple is a vector. They both model immutable sequences.
s: tuple/vector x: any t: tuple/vector n: integer i: integer j: integer
Python | Elisp | Notes |
---|---|---|
s = (1, 2, 3) , s = tuple(range(3)) |
(setq s (vector 1 2 3)) , (setq s [1 2 3]) |
|
x in s |
(seq-contains-p s x) |
|
x not in s |
(not (seq-contains-p s x)) |
|
s + t |
(seq-concatenate 'vector s t) |
|
s * n or n * s |
(seq-into (seq-map (lambda (a) (* n a)) s) 'vector) |
|
s[i] |
(seq-elt s i) |
|
s[i:j] |
(seq-subseq s i j) |
|
s[i:j:k] |
||
len(s) |
(seq-length s) |
|
min(s) |
(seq-min s) |
Elements of s can be ordered. |
max(s) |
(seq-max s) |
Elements of s can be ordered. |
s.index(x) |
(seq-position s x) |
|
s.count(x) |
(seq-count (lambda (a) (= x a)) s) |
|
s[0] |
(seq-first s) |
|
s[-n] |
(seq-first (seq-subseq s -n)) |
|
if not s: |
(seq-empty-p s) |
Python Dictionary
To Elisp Hash Table
The Elisp hash-table
is the most straightforward analog to a Python dictionary. That said, there are gotchas, particularly around hash-table
creation. If the keys are of type string, then the key comparison should be set to the function equal
via the :test
slot. If :test
is omitted the default function eql
is used which compares numbers.
d: dictionary/hash-table k: key v: value
Python | Elisp Generic | Elisp Type-Specific | Notes |
---|---|---|---|
d = dict() , d = {} |
(setq d (make-hash-table :test #'equal)) |
If :test is omitted, key comparison is default eql that is tuned for number comparison. |
|
list(d) |
(map-keys d) |
(hash-table-keys d) |
|
len(d) |
(map-length d) |
(length d) |
|
d[k] |
(map-elt d k) |
(gethash k d) |
|
d[k] = v |
(map-put! d k v) |
(puthash k v d) |
|
del d[k] |
(map-delete d k) |
(remhash k d) |
|
k in d |
(map-contains-key d k) |
||
k not in d |
(not (map-contains-key d k)) |
||
iter(d) |
|||
d.clear() |
(setq d (list)) |
||
d.copy() |
(map-copy d) |
||
d.get(k) |
(map-elt d k) |
||
d.items() |
(map-pairs d) |
||
d.keys() |
(map-keys d) |
||
d.pop(k) |
|||
d.popitem() |
|||
reversed(d) |
|||
d.values() |
(map-values d) |
||
(map-insert d k v) |
Like map-put! but does not mutate d . |
Looping
(map-do f d) ; return nil
(map-apply f d) ; return results of f applied to each element of d as a list
To Elisp Association List (alist)
An alist is a convention to construct a basic list such that key-value semantics can be applied to it. An alist is allowed to possess degenerate keys (that is, keys are not necessarily unique!). This is because in truth, an alist is still a list with no actual enforcement of how values are stored in it. IMHO Elisp alists are an abomination, albeit a pragmatic one. Conventional Elisp wisdom arguing for alist usage boils down to convenient serialization and the notion that in practice, alist sizes are small enough to not merit the overhead of using hash-tables.
Regardless, my guidance is to exercise caution when translating Python dictionary code to an Elisp alist.
d: dictionary/alist k: key v: value
Python | Elisp Generic | Elisp Type-Specific | Notes |
---|---|---|---|
d = dict() , d = {} |
(setq d (list)) |
||
list(d) |
(map-keys d) |
||
len(d) |
(map-length d) |
(length d) |
|
d[k] |
(map-elt d k) |
||
d[k] = v |
(map-put! d k v) |
This only works if d is not nil. To initialize use (push '(k . v) d) . |
|
del d[k] |
(setq d (map-delete d k)) |
||
k in d |
(map-contains-key d k) |
||
k not in d |
(not (map-contains-key d k)) |
||
iter(d) |
|||
d.clear() |
(setq d (list)) |
||
d.copy() |
(map-copy d) |
||
d.get(k) |
(map-elt d k) |
||
d.items() |
(map-pairs d) |
||
d.keys() |
(map-keys d) |
||
d.pop(k) |
|||
d.popitem() |
|||
reversed(d) |
|||
d.values() |
(map-values d) |
||
(map-insert d k v) |
Like map-put! but does not mutate d . Also does not check uniqueness. |
To Elisp Property List (plist)
TBD
Python String to Elisp String
s: string a: string b: string c: string sep: separator string strs: list of strings
Python | Elisp | Notes |
---|---|---|
"" |
(make-string 0 ? ) |
|
a + b + c |
(concat a b c) |
|
s.strip() |
(string-clean-whitespace s) |
|
s.capitalize() |
(capitalize s) |
|
s.casefold() |
||
s.center(width) |
||
s.count(sub) |
||
s.encode(encoding) |
||
s.endswith(suffix) |
(string-suffix-p suffix s) |
|
s.expandtabs(tabsize) |
||
s.find(sub) |
(string-search sub s) |
|
s.format(*args, **kwargs) |
(format fmt args…) |
|
s.index(sub) |
(string-search sub s) |
|
s.isalnum() |
(string-match "^[[:alnum:]]*$" s) |
|
s.isalpha() |
(string-match "^[[:alpha:]]*$" s) |
|
s.isascii() |
(string-match "^[[:ascii:]]*$" s) |
|
s.isdecimal() |
||
s.isdigit() |
(string-match "^[[:digit:]]*$" s) |
|
s.islower() |
(string-match "^[[:lower:]]*$" s) |
case-fold-search must be nil. |
s.isnumeric() |
||
s.isprintable() |
(string-match "^[[:print:]]*$" s) |
|
s.isspace() |
(string-match "^[[:space:]]*$" s) |
|
s.istitle() |
||
s.isupper() |
(string-match "^[[:upper:]]*$" s) |
case-fold-search must be nil. |
sep.join(strs) |
(string-join strs sep) |
|
s.ljust(width) |
||
s.lower() |
(downcase s) |
|
s.lstrip() |
(string-trim-left s) |
|
s.removeprefix(prefix) |
(string-remove-prefix prefix s) |
|
s.removesuffix(suffix) |
(string-remove-suffix suffix s) |
|
s.replace(old, new, count=-1) |
(string-replace old new s) |
|
s.rfind(sub) |
||
s.rindex(sub) |
||
s.rjust(width) |
||
s.rsplit(sep) |
||
s.rstrip() |
(string-trim-right s) |
|
s.split(sep) |
(split-string s sep) |
|
s.splitlines() |
(string-lines s) |
|
s.startswith(prefix) |
(string-prefix-p prefix s) |
|
s.strip() |
(string-trim s) |
|
s.swapcase() |
||
s.title() |
(upcase-initials s) |
|
s.upper() |
(upcase s) |
|
s.zfill(width) |