Elisp Cheatsheet for Python Programmers
Table of Contents
- 1. Guiding Principles
- 2. Collections
- 2.1. Comparison Functions
- 2.2. Sequence Types
- 2.2.1. Non-Mutating Python Sequence to Elisp List Translations
- 2.2.2. Non-Mutating Python Sequence to Elisp Vector Translations
- 2.2.3. Non-Mutating Python Sequence to Elisp Sequence Translations
- 2.2.4. Mutating Python Sequence to Elisp List Translations
- 2.2.5. Mutating Python Sequence to Elisp Vector Translations
- 2.2.6. Mutating Python Sequence to Elisp Sequence Translations
- 2.3. Map Types
- 2.4. Iteration
- 2.5. Python String to Elisp String
- 3. File I/O
- 4. License
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.
Version: 0.4.0
Author: Charles Y. Choi
1. Guiding Principles
- This document uses Python 3.13 and Emacs Lisp 30.2+ APIs from built-in packages.
- 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, plist).
- The Emacs packages
seq.elandmap.eldo the heavy lifting here for sequence and map types respectively.
- This document aims to provide guidance for a proficient Python programmer to implement Elisp code using familiar programming abstractions without surprise.
- Performance is at best a tertiary concern.
2. Collections
2.1. Comparison Functions
In Elisp, the comparison function used to disambiguate elements in a sequence-type collection or keys in a map-type collection is significant. When in doubt, it is better to specify the comparison function than trust (hope) that the default comparison function will behave to developer intent. Info (elisp) Equality Predicates details the built-in comparison functions.
That said, here is some general guidance. Use the comparison function:
=to compare numberseqto compare object identity.eqlto compare numbers but also take into account type. For example comparing an integer to its numerically equivalent float will returnnil.equalto compare if the objects have equal components.
| Python | Elisp | Notes |
|---|---|---|
is |
eq |
|
== |
equal |
Use Elisp equal for string comparison. |
Depending on the type of object to compare, you may have to resort to writing a custom comparison function.
2.2. Sequence Types
There are three basic sequence types in Python: list, tuple, and range. This document will cover only Elisp translation of Python list and tuple types and subsequent reference to Python sequences should be understood to not include range. For Elisp sequence types, this document will cover list and vector types.
The Elisp list type is a tree data structure with a linked-list style abstraction for accessing its nodes. Elisp also offers a general purpose array type called a vector.
The following legend describes types for variables used in the translations.
s: sequence x: any t: sequence n: integer i: integer j: integer cmp: comparison function
2.2.1. Non-Mutating Python Sequence to Elisp List Translations
Elisp list specific translations.
| Python Sequence | Elisp List | Notes |
|---|---|---|
s = [], s = list(), s = (n1, n2, …) |
(setq s ()), (setq s nil) |
nil has extra meaning in Elisp as it represents an empty list. |
list(range(0, n)) |
(number-sequence 0 (1- n)) |
|
s * n or n * s |
(cl-loop repeat n append s), (apply #'append (make-list n s)) |
cl-loop needs (require 'cl-lib). |
x in s |
(member x s) |
member can be used if cmp is equal. |
x not in s |
(not (member x s)) |
member can be used if cmp is equal. |
s + t |
(seq-concatenate 'list s t), (append s t) |
|
s[0] |
(car s) |
|
s[-1] |
(car (last s)) |
2.2.2. Non-Mutating Python Sequence to Elisp Vector Translations
Elisp vector specific translations.
| Python Sequence | Elisp Vector | Notes |
|---|---|---|
s = [] |
(setq s (vector)) |
|
s + t |
(seq-concatenate 'vector s t), (vconcat s t) |
|
s[i] |
(aref s i) |
2.2.3. Non-Mutating Python Sequence to Elisp Sequence Translations
These translations work on either Elisp list or vector types.
| Python Sequence | Elisp Sequence (List or Vector) | Notes |
|---|---|---|
x in s |
(seq-contains-p s x #'cmp) |
Make sure cmp will compare the element type! |
x not in s |
(not (seq-contains-p s x #'cmp)) |
Make sure cmp will compare the element type! |
map(lambda a: a * 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:] |
(seq-subseq s i) |
|
s[i:j:k] |
||
len(s) |
(seq-length s), (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) (cmp x a)) s) |
|
s[0] |
(seq-first s) |
|
s[-1] |
(car (last s)) |
|
s[-n] |
(seq-first (seq-subseq s -n)) |
|
if not s: |
(seq-empty-p s) |
2.2.4. Mutating Python Sequence to Elisp List Translations
Elisp analogs to the Python list methods to handle insertion, appending, and updating are left to the developer to implement. Arguably, the omission of these functions is reluctance on the part of Emacs Core to make the trade-off design decisions required to implement them.
The following Elisp translations will mutate the original input s.
| Python Sequence | Elisp List | Notes |
|---|---|---|
s.append(x) |
(setq s (nreverse (cons x (reverse s)))) |
This implementation is slow if s is large. |
s.clear() |
(setq s ()), (setq s nil) |
|
s.extend(t) |
(setq s (append s t)) |
|
s *=n |
(setq s (cl-loop repeat n append s)) |
cl-loop needs (require 'cl-lib). |
s.push(x) |
(push x s) |
|
s.pop() |
(pop s) |
|
s.insert(0, x) |
(push s x) |
|
s.insert(i, x) |
(setq s (append (seq-subseq s 0 i) (cons x (seq-subseq s i)))) |
2.2.5. Mutating Python Sequence to Elisp Vector Translations
| Python Sequence | Elisp Vector | Notes |
|---|---|---|
s[i] = x |
(aset s i x) |
|
s.clear() |
(setq s (vector)) |
|
s.remove(x) |
(remove x s) |
2.2.6. Mutating Python Sequence to Elisp Sequence Translations
These translations work on either Elisp list or vector types.
| Python Sequence | Elisp Sequence (List or Vector) | Notes |
|---|---|---|
s[i] = x |
(setf (seq-elt s i) x) |
|
s[i:j] = t |
||
del s[i:j] |
(setq s (append (seq-subseq s 0 i) (seq-subseq s j))) |
|
del s[i] |
(setq s (seq-remove-at-position s i)) | |
s[i:j:k] = t |
||
del s[i:j:k] |
||
s.copy() |
(seq-copy s) |
|
s.extend(t) |
(setq s (append s t)) |
|
s *=n |
(setq s (cl-loop for _ from 1 to n nconc (seq-copy s))) |
|
s.remove(x) |
(setq s (seq-remove (lambda (a) (cmp x a)) s)), (setq s (remove x s)) |
Note Elisp translation presumes only one instance of x is in s, as seq-remove will remove all instances of x whereas in Python s.remove(x) will only remove the first instance. |
s.reverse() |
(setq s (seq-reverse s)), (setq s (reverse s)), (setq s (nreverse s)) |
nreverse may destructively mutate s. |
2.3. Map Types
Emacs Lisp provides several implementations of a map type:
- Association list (alist)
- Property list (plist)
- Hash Table (hash-table)
Association and property lists are lightweight map types in that they use list structure to emulate a map type. An alist or plist should not be considered isomorphic to a Python dictionary as they can have degenerate keys. This contrasts sharply with a Python dictionary, where keys are always unique.
2.3.1. Python Dictionary to Elisp Association List (alist)
An association list (alist) is a list with structure ((key1 . value1) (key2 . value2) …).
Conventional Elisp wisdom arguing for alist usage over a hash-table 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 (described below).
d: dictionary/alist k: key v: value
| Python | Elisp | Notes |
|---|---|---|
d = dict(), d = {} |
(setq d (list)) |
|
list(d) |
(map-keys d) |
|
len(d) |
(map-length d) |
|
d[k] |
(map-elt d k) |
|
d[k] = v, k ∉ d.keys() |
(setq d (map-insert d k v)) |
k is not in d. map-insert will not check uniqueness. |
d[k] = v, k ∈ d.keys() |
(map-put! d k v) |
k is in d. Note that map-put! will mutate d. |
del d[k] |
(setq d (map-delete d k)) |
Type-specific behavior is dependent on key type. 😞 |
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) |
Can be emulated using map-elt and map-delete. |
|
d.popitem() |
||
reversed(d) |
(reverse (map-keys d)) |
|
d.values() |
(map-values d) |
2.3.2. Python Dictionary to Elisp Property List (plist)
A property list (plist) is a list with structure (key1 value1 key2 value2 …). Most all the map- commands used to operate on an alist also apply to a plist with the exception of initialization. Whereas an alist can be initialized to be empty (nil), a plist must be initialized with a key, value pair.
A common convention is to name plist keys with a ‘:’ prefix character (e.g. :name). Note the key type is an Elisp symbol, not a string.
d: dictionary/plist k: key v: value
| Python | Elisp |
|---|---|
d = dict([(k, v)]), d = {k : v} |
(setq d '(k v)) |
d[k] |
(plist-get d k) |
d[k] = v |
(plist-put d k v) |
k in d |
(plist-member d k) |
2.3.3. 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 cmp: comparison function
| Python | Elisp | Notes |
|---|---|---|
d = dict(), d = {} |
(setq d (make-hash-table :test #'cmp)) |
If :test is omitted, default cmp is eql. |
list(d) |
(map-keys d) |
|
len(d) |
(map-length d) |
|
d[k] |
(map-elt d k) |
|
d[k] = v |
(map-put! d k v) |
|
del d[k] |
(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() |
(clrhash d) |
|
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) |
Can be emulated using map-elt and map-delete. |
|
d.popitem() |
||
reversed(d) |
||
d.values() |
(map-values d) |
|
(map-insert d k v) |
Like map-put! but does not mutate d. |
2.4. Iteration
Elisp provides many different functions to iterate through a sequence or map. The following sections show some common Python iteration examples with their Elisp equivalents.
2.4.1. Sequence
Basic Python for loop running the function f(i) for every element in range(n). No results are returned.
for i in range(n):
f(i)
Elisp equivalent using mapc which does not accumulate results.
(mapc #'f (number-sequence 0 (1- n))) ; f is a function with a single argument
Python map function on an iterable.
map(f range(n)) # f is a function with a single argument
Elisp equivalent using mapcar.
(mapcar #'f (number-sequence 0 (1- n)))
Python filter example of odd numbers only from 0 to 9 inclusive.
filter(lambda x: x % 2, range(10))
Elisp seq-filter equivalent. Note that in Elisp, a 0 value does not coerce to a false value when compared like in Python.
(seq-filter (lambda (x)
(if (eq 0 (mod x 2))
nil
t))
(number-sequence 0 9))
Python reduce example.
from functools import reduce reduce(lambda x,y: x+y, range(10))
Elisp seq-reduce equivalent.
(seq-reduce (lambda (x y)
(+ x y))
(number-sequence 0 9) 0)
2.4.2. Map
Python iteration through dictionary d using for loop.
for k,v in d.items():
f(k,v)
Elisp equivalent using map-do. No results are accumulated.
(map-do #'f d)
Python dictionary iteration example, saving results in a list.
results = []
for k,v in d.items():
results.append(f(k, v))
Elisp equivalent, where the returned result is a list of each result of f applied to a key, value pair of d.
(map-apply #'f d)
2.5. Python String to Elisp String
s: string a: string b: string c: string sep: separator string strs: list of strings i: integer j: integer
| Python | Elisp | Notes |
|---|---|---|
"" |
(make-string 0 ? ), "" |
|
s[i:] |
(substring s i) |
|
s[i:j] |
(substring s i j) |
|
s[:j] |
(substring s nil j) |
|
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) |
||
s1 == s2 |
(string-equal s1 s2), (string= s1 s2) |
2.5.1. Regular Expressions
Python and Elisp use different styles of regular expressions (regexps). This document offers no translation of Python regexp style to Elisp. However what is offered is mention of regexp-related calls by both.
p: pattern r: replacement s: string
| Python | Elisp | Notes |
|---|---|---|
re.sub(p, r, s) |
(replace-regexp-in-string p r s) |
|
re.match(p, s) |
(string-match p s) |
re.match and string-match have different APIs that accomplish the same thing. See Simple Match Data (GNU Emacs Lisp Reference Manual) for extracting group data from a found match. |
3. File I/O
The in-memory representation of a file in Emacs is a buffer, whose closest analog in a general purpose language like Python is a file handle. A common pattern is to read the contents of a file into list of strings, each string separated by a newline (“\n”).
Here is an example of this in Python.
def read_file_lines(filename):
with open(filename, "r") as infile:
lines = infile.readlines()
return lines
for line in read_file_lines(filename):
print(line.rstrip('\n'))
Here is an Elisp equivalent.
(defun read-file-lines (filename)
"Load FILENAME into a buffer and read each line."
(with-temp-buffer
;; Insert the contents of the file into the temporary buffer
(insert-file-contents filename)
;; Move to the beginning of the buffer
(goto-char (point-min))
;; Initialize an empty list to hold the lines
(let ((lines '()))
;; Loop until the end of the buffer is reached
(while (not (eobp))
;; Read the current line
(let ((line (string-trim-right (thing-at-point 'line t))))
;; Add the line to the list
(push line lines))
;; Move to the next line
(forward-line 1))
;; Return the lines in the correct order
(nreverse lines))))
;; Example usage:
(let ((lines (read-file-lines "somefile.log")))
(dolist (line lines)
(message "%s" line)))
Writing an Elisp list to a file is illustrated in the following example.
(defun write-strings-to-file (strings filename)
"Write a list of STRINGS to FILENAME, one string per line."
(with-temp-file filename
;; Iterate over each string in the list
(dolist (str strings)
;; Insert the string followed by a newline character
(insert str "\n"))))
;; Example usage:
(let ((my-strings (read-file-lines "somefile.log"))
(file-path "some-other-file.log"))
(write-strings-to-file my-strings file-path))
Although the above examples work as advertised, conventional Elisp wisdom frowns upon pipeline style processing of collections arguing that:
- Elisp has been optimized to work in-place with buffer contents and that transformations should be made directly to the buffer content.
- Pipeline style processing of collections is slow. If you are going to process a large log file, using Elisp is not the right tool for the job.
It is left to the reader whether to heed this guidance.
4. License
Copyright 2025-2026 Charles Y. Choi. This work is openly licensed via CC BY 4.0.