mirror of
https://github.com/kickingvegas/elisp-for-python.git
synced 2025-04-11 18:10:47 -05:00
248 lines
22 KiB
Org Mode
248 lines
22 KiB
Org Mode
* 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 [[https://github.com/kickingvegas/elisp-for-python/issues][feedback]] is encouraged.
|
||
|
||
** Table of Contents :TOC_3:
|
||
- [[#elisp-cheatsheet-for-python-programmers][Elisp Cheatsheet for Python Programmers]]
|
||
- [[#guiding-principles][Guiding Principles]]
|
||
- [[#python-list-to-elisp-list][Python List to Elisp List]]
|
||
- [[#python-tuple-to-elisp-vector][Python Tuple to Elisp Vector]]
|
||
- [[#python-dictionary][Python Dictionary]]
|
||
- [[#to-elisp-hash-table][To Elisp Hash Table]]
|
||
- [[#to-elisp-association-list-alist][To Elisp Association List (alist)]]
|
||
- [[#to-elisp-property-list-plist][To Elisp Property List (plist)]]
|
||
- [[#python-string-to-elisp-string][Python String to Elisp String]]
|
||
|
||
** 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 [[https://en.wikipedia.org/wiki/Polymorphism_(computer_science)#:~:text=A%20function%20that%20can%20evaluate,which%20such%20specializations%20are%20made.][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~ and ~map.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.
|
||
|
||
#+begin_example
|
||
s: list
|
||
x: any
|
||
t: list
|
||
n: integer
|
||
i: integer
|
||
j: integer
|
||
#+end_example
|
||
|
||
❗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.
|
||
|
||
#+begin_example
|
||
s: tuple/vector
|
||
x: any
|
||
t: tuple/vector
|
||
n: integer
|
||
i: integer
|
||
j: integer
|
||
#+end_example
|
||
|
||
| 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.
|
||
|
||
#+begin_example
|
||
d: dictionary/hash-table
|
||
k: key
|
||
v: value
|
||
#+end_example
|
||
|
||
| | | | <20> |
|
||
| 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
|
||
#+begin_src elisp :lexical no
|
||
(map-do f d) ; return nil
|
||
(map-apply f d) ; return results of f applied to each element of d as a list
|
||
#+end_src
|
||
|
||
*** 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*.
|
||
|
||
#+begin_example
|
||
d: dictionary/alist
|
||
k: key
|
||
v: value
|
||
#+end_example
|
||
|
||
|
||
| 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
|
||
|
||
#+begin_example
|
||
s: string
|
||
a: string
|
||
b: string
|
||
c: string
|
||
sep: separator string
|
||
strs: list of strings
|
||
#+end_example
|
||
|
||
|
||
| 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)~ | | |
|