PythonCheatSheet
PythonCheatSheet copied to clipboard
Quick reference to a tremendously accessible high-level language ---executable pseudocode!
Created 2020-03-01 Sun 22:58
#+OPTIONS: toc:nil d:nil #+OPTIONS: toc:nil d:nil #+TITLE: PythonCheatSheet #+AUTHOR: [[https://alhassy.github.io/][Musa Al-hassy]] #+export_file_name: README.org
Quick reference to a tremendously accessible high-level language ---executable pseudocode!
The listing sheet, as PDF, can be found [[https://alhassy.github.io/PythonCheatSheet/CheatSheet.pdf][here]], or as a [[https://alhassy.github.io/PythonCheatSheet/CheatSheet_Portrait.pdf][single column portrait]], while below is an unruly html rendition.
This reference sheet is built from a [[https://github.com/alhassy/CheatSheet][CheatSheets with Org-mode]] system.
#+html:

#+toc: headlines 2 #+macro: blurb Quick reference to a tremendously accessible high-level language ---executable pseudocode!
#+latex_header: \usepackage{titling,parskip} #+latex_header: \usepackage{eufrak} % for mathfrak fonts #+latex_header: \usepackage{multicol,xparse,newunicodechar}
#+latex_header: \usepackage{etoolbox}
#+latex_header: \newif\iflandscape #+latex_header: \landscapetrue
#+latex_header_extra: \iflandscape \usepackage[landscape, margin=0.5in]{geometry} \else \usepackage[margin=0.5in]{geometry} \fi
#+latex_header: \def\cheatsheetcols{2} #+latex_header: \AfterEndPreamble{\begin{multicols}{\cheatsheetcols}} #+latex_header: \AtEndDocument{ \end{multicols} }
#+latex_header: \let\multicolmulticols\multicols #+latex_header: \let\endmulticolmulticols\endmulticols #+latex_header: \RenewDocumentEnvironment{multicols}{mO{}}{\ifnum#1=1 #2 \def\columnbreak{} \else \multicolmulticols{#1}[#2] \fi}{\ifnum#1=1 \else \endmulticolmulticols\fi}
#+latex_header: \def\maketitle{} #+latex: \fontsize{9}{10}\selectfont
#+latex_header: \def\cheatsheeturl{}
#+latex_header: \usepackage[dvipsnames]{xcolor} % named colours #+latex: \definecolor{grey}{rgb}{0.5,0.5,0.5}
#+latex_header: \usepackage{color} #+latex_header: \definecolor{darkgreen}{rgb}{0.0, 0.3, 0.1} #+latex_header: \definecolor{darkblue}{rgb}{0.0, 0.1, 0.3} #+latex_header: \hypersetup{colorlinks,linkcolor=darkblue,citecolor=darkblue,urlcolor=darkgreen}
#+latex_header: \setlength{\parindent}{0pt}
#+latex_header: \def\cheatsheetitemsep{-0.5em} #+latex_header: \let\olditem\item #+latex_header_extra: \def\item{\vspace{\cheatsheetitemsep}\olditem}
#+latex_header: \usepackage{CheatSheet/UnicodeSymbols}
#+latex_header: \makeatletter #+latex_header: \AtBeginEnvironment{minted}{\dontdofcolorbox} #+latex_header: \def\dontdofcolorbox{\renewcommand\fcolorbox[4][]{##4}} #+latex_header: \makeatother
#+latex_header: \RequirePackage{fancyvrb} #+latex_header: \DefineVerbatimEnvironment{verbatim}{Verbatim}{fontsize=\scriptsize}
#+latex_header: \usemintedstyle{xcode}
#+latex_header: \def\cheatsheeturl{https://github.com/alhassy/PythonCheatSheet}
#+latex_header: \def\cheatsheetcols{2} #+latex_header: \landscapetrue #+latex_header: \def\cheatsheetitemsep{-0.5em}
#+latex_header: \newunicodechar{𝑻}{\ensuremath{T}} #+latex_header: \newunicodechar{⊕}{\ensuremath{\oplus}} #+latex_header: \newunicodechar{≈}{\ensuremath{\approx}} #+latex_header: \newunicodechar{𝓍}{\ensuremath{\mathit{x}}} #+latex_header: \newunicodechar{𝓌}{\ensuremath{\mathit{w}}} #+latex_header: \newunicodechar{𝓎}{\ensuremath{\mathit{y}}}
#+latex_header: \newunicodechar{α}{\ensuremath{\alpha}} #+latex_header: \newunicodechar{𝓈}{\ensuremath{\mathit{s}}}
#+begin_quote
- [[#hello-world][Hello, World!]]
- [[#this-is-how-we-do-it][This is how we do it]]
- [[#exploring-built-in-modules-with-dir-and-help][Exploring built-in modules with ~dir~ and ~help~]]
- [[#arithmetic][Arithmetic]]
- [[#conditionals][Conditionals]]
- [[#boolean-connectives][Boolean connectives]]
- [[#relational-operations][Relational operations]]
- [[#if-expressions-and-statements][If expressions and statements]]
- [[#iterables][Iterables]]
- [[#comprehensions][Comprehensions]]
- [[#generators][Generators]]
- [[#unpacking-operation][Unpacking operation]]
- [[#loops][Loops]]
- [[#forwhile-via-iternext][For/while via iter/next]]
- [[#methods-on-iterables][Methods on Iterables]]
- [[#sum-is-monoidal][Sum is monoidal]]
- [[#methods-for-sequences-only][Methods for sequences only]]
- [[#sequences-are-ordered][Sequences are Ordered]]
- [[#tuples][Tuples]]
- [[#strings][Strings]]
- [[#string-comprehensions][String Comprehensions]]
- [[#string-methods][String methods]]
- [[#f-strings-formatted-string-literals][f-strings: Formatted String Literals]]
- [[#lists][Lists]]
- [[#sets][Sets]]
- [[#dictionaries][Dictionaries]]
- [[#dictionaries-implement-hierarchical-tree-structures-and-case-statements][Dictionaries implement hierarchical tree structures and case statements]]
- [[#dictionary-methods][Dictionary Methods]]
- [[#splicing][Splicing]]
- [[#functions][Functions]]
- [[#functions-have-attributes][Functions have attributes]]
- [[#on-the-nature-of-λ][On the nature of λ]]
- [[#default--keyword-argument-values][Default & keyword argument values]]
- [[#dictionary-arguments][Dictionary arguments]]
- [[#type-annotations][Type Annotations]]
- [[#currying][Currying]]
- [[#decorators][Decorators]]
- [[#object-oriented-programming][Object-Oriented Programming]]
- [[#person-empty-class][Person: Empty Class]]
- [[#a-more-complex-person-class][A More Complex ~Person~ Class]]
- [[#making-people][Making People]]
- [[#syntax-overloading-dunder-methods][Syntax Overloading: Dunder Methods]]
- [[#extension-methods][Extension Methods]]
- [[#inheritance][Inheritance]]
- [[#decorators-and-classes][Decorators and Classes]]
- [[#curry-decorator][Curry Decorator]]
- [[#named-expressions][Named Expressions]]
- [[#modules][Modules]]
- [[#reads][Reads]] #+end_quote
#+latex: \vspace{-1em}
- Hello, World!
#+latex: \hspace{-1.3em} ⇒ Python is [[https://docs.python.org/3/reference/datamodel.html#objects-values-and-types][object oriented]] and dynamically type checked.
⇒ With [[Syntax Overloading: Dunder Methods][dunder methods]], every /syntactic construct/ extends to user-defined datatypes, classes! ---Including loops, comprehensions, and even function call notation!
⇒ Children block fragments are realised by consistent indentation, usually 4 spaces. No annoying semicolons or braces.
⇒ ~τ(x)~ to try to coerce =x= into a =τ= type element, crashes if conversion is not possible. Usual types: src_python[:exports code]{bool, str, list, tuple, int, float, dict, set}. Use src_python[:exports code]{type(x)} to get the type of an object =x=.
⇒ Identifier names are case sensitive; some unicode such as =“α”= is okay but not ~“⇒”~.
⇒ If ~obj~ is an instance of type ~τ~, then we may invoke an instance method ~f~ in two ways: ~obj.f()~ or ~τ.f(obj)~. The latter hints at why src_python[:exports code]{“self”} is the usual name of the first argument to instance methods. The former ~τ.f~ is the name proper.
⇒ Function and class definitions are the only way to introduce new, local, scope.
⇒ src_python[:exports code]{del x} deletes the object =x=, thereby removing the name =x= from scope.
⇒ src_python[:exports code]{print(x, end = e)} outputs =x= as a string followed by =e=; =end= is optional and defaults to a newline. =print(x₁, …, xₙ)= prints a tuple without parentheses or commas.
⇒ The src_python[:exports code]{NoneType} has only one value, src_python[:exports code]{None}. It's used as the return type of functions that only perform a side-effect, like printing to the screen. Use src_python[:exports code]{type(None)} to refer to it.
** This is how we do it
Everything here works using Python3. #+begin_src python import sys assert '3.8.1' == sys.version.split(' ')[0] #+end_src
#+results:
We'll use ~assert y == f(x)~ to show that the output of ~f(x)~ is ~y~.
- Assertions are essentially “machine checked comments”.
** Exploring [[https://docs.python.org/3/library/][built-in modules]] with ~dir~ and ~help~
#+latex: \vspace{0.5em}
| *Explore [[https://docs.python.org/3/library/][built-in modules]] with ~dir~ and ~help~ * |
| ~dir(M)~ | List of string names of all elements in module ~M~ | | ~help(M.f)~ | Documentation string of function ~f~ in module ~M~ |
#+begin_parallel #+begin_src python import re for member in sorted (dir(re)): if "find" in member: print (help ("re." + member)) #+end_src #+latex: \columnbreak ⇒ Print alphabetically all regular expression utilities that mention ~find~.
#+latex: \vspace{1.5em} ~help~ can be called directly on a name; no need for quotes. #+end_parallel
#+latex: \vspace{-1.5em}
- Arithmetic
#+latex: \hspace{-1.4em} Besides the usual operators =+, *, **, /, //, %, abs=, declare src_python[:exports code]{from math import *} to obtain =sqrt, loq10, factorial, …= ---use ~dir~ to learn more, as mentioned above.
- Augmented assignments: ~x ⊕= y ≡ x = x ⊕ y~ for any operator ~⊕~.
- Floating point numbers are numbers with a decimal point.
- =**= for exponentiation and ~%~ for the remainder after division.
- =//=, floor division, discards the fractional part, whereas =/= keeps it.
- Numeric addition and /sequence/ catenation are both denoted by ~+~.
- However: ~1 + 'a' ⇒ error!~.
#+begin_parallel #+begin_src python
Readability!
‘_’ in numeric literals is ignored
assert 1000000 == 1_000_000
assert 1.2 == float("1.2") assert -1 == int(float('-1.6'))
float('a')
⇒ Crashes: 'a' is not a number
#+end_src #+latex: \columnbreak #+begin_src python
Scientific notation: 𝓍e𝓎 ≈ 𝓍 * (10 ** 𝓎)
assert 250e-2 == 2.5 == 1 + 2 * 3 / 4.0
from math import * # See below on imports assert 2 == sqrt(4) assert -inf < 123 < +inf #+end_src #+end_parallel
#+latex: \vspace{-1.5em}
- Conditionals
#+begin_parallel #+latex: \hspace{-1.3em} Booleans are a subtype (subclass) of integers, consisting of two values: =True= and =False=.
#+latex: \vspace{1em} \hspace{-1.3em} Consequently, we freely get [[https://en.wikipedia.org/wiki/Iverson_bracket][Iverson brackets]].
#+latex: \columnbreak #+begin_src python assert True == 1 and False == 0 assert issubclass(bool, int) #+end_src
#+latex: \vspace{1em} #+begin_src python abs(x) ≈ x * (x > 0) - x * (x < 0) #+end_src #+end_parallel
#+latex: \vspace{-1em} | /Every “empty” collection is considered false! Non-empty values are truthy!/ |
- src_python[:exports code]{bool(x)} ⇒ Interpret object =x= as either true or false.
- E.g. 0, =None=, and empty tuples/lists/strings/dictionaries are falsey.
#+begin_parallel
#+begin_src python assert (False == bool(0) == bool("") == bool(None) == bool(()) == bool([]) == bool({})) #+end_src
#+results:
#+latex: \columnbreak
In Boolean contexts: #+latex: \vspace{1em} | “x is empty” | ≡ | =not bool(x)= | | ~len(e) != 0~ | ≡ | ~bool(e)~ | | ~bool(e)~ | ≡ | ~e~ | | ~x != 0~ | ≡ | ~x~ |
#+latex: \vspace{0.5em} User-defined types need to implement dunder methods [[https://docs.python.org/3.8/reference/datamodel.html#object.bool][ =bool= ]] or [[https://docs.python.org/3.8/reference/datamodel.html#object.len][ ~len~ ]]. #+end_parallel
#+latex: \vspace{-0.5em}
** Boolean connectives
Usual infix operations src_python[:exports code]{and, or, not} for /control flow/ whereas ~&, |~ are for Booleans only.
- src_python[:exports code]{None or 4 ≈ 4} but ~None | 4~ crashes due to a type error.
| =s₁ and ⋯ and sₙ= | ⇒ | Do ~sₙ~ only if all ~sᵢ~ “succeed” | | ~s₁ or ⋯ or sₙ~ | ⇒ | Do ~sₙ~ only if all ~sᵢ~ “fail” |
-
src_python[:exports code]{x = y or z} ⇒ assign ~x~ to be ~y~ if ~y~ is “non-empty” otherwise assign it ~z~.
-
Precedence: src_python[:exports code]{A and not B or C ≈ (A and (not B)) or C}.
** Relational operations
Value equality ~==~, discrepancy ~!=~; Chained comparisons are conjunctive; e.g., | ~x < y <= z~ | ≡ | ~x < y and y <= z~ | | ~p == q == r~ | ≡ | ~p == q and q == r~ |
** If expressions and statements
#+begin_parallel /If-expressions/ /must/ have an =else= clause, but /if-statements/ need not =else= nor =elif= clauses; =“else if”= is invalid.
#+latex: \vspace{1em} Expressions bind more tightly than statements; whence usually no need to parenthesise if-expressions.
#+latex: \columnbreak #+begin_src python
If-expression
expr₁ if condition else expr₂
If-statement
if condition₁: action₁ elif condition₂: action₂ elif condition₃: action₃ else : default_action #+end_src #+end_parallel
#+latex: \vspace{-1.5em}
- Iterables
#+latex: \hspace{-1.3em} An /iterable/ is an object which can return its members one at a time; this includes the (finite and ordered) /sequence types/ ---lists, strings, tuples--- and non-sequence types ---generators, sets, dictionaries. An iterable is any class implementing =iter= and =next=; an example is shown later.
- Zero-based indexing, ~x[i]~, applies to sequence types only.
- We must have src_python[:exports code]{-len(x) < i < len(x)} and src_python[:exports code]{xs[-i] ≈ xs[len(x) - i]}.
We shall cover the general iterable interface, then cover lists, strings, tuples, etc.
** Comprehensions
Comprehensions provide a concise way to create iterables; they consist of brackets ---() for generators, [] for lists, {} for sets and dictionaries--- containing an expression followed by a =for= clause, then zero or more =for= or =if= clauses.
#+begin_center src_python[:exports code]{(f(x) for x in xs if p(x))} #+end_center #+latex: \vspace{-0.5em} ⇒ A new iterable obtained by applying ~f~ to the elements of ~xs~ that satisfy ~p~ ⇐
E.g., the following prints a list of distinct pairs. #+latex: \vspace{-0.5em} #+begin_src python print ([(x, y) for x in [1,2,3] for y in (3,1,4) if x != y]) #+end_src
#+begin_parallel #+begin_src python from itertools import count
evens = (2 * x for x in count())
First 5 even naturals
for _, x in zip(range(5), evens): print (x) #+end_src #+latex: \columnbreak
Generators are “sequences whose elements are generated when needed”; i.e., are /lazy lists/.
#+latex: \vspace{1em} If [,] are used in defining =evens=, the program will take forever to make a list out of the infinitly many even numbers! #+end_parallel
Comprehensions are known as monadic do-notation in Haskell and Linq syntax in C#. ** Generators Generators are functions which act as a lazy streams of data: Once a ~yield~ is encountered, control-flow goes back to the caller and the function's state is persisted until another value is required.
#+begin_parallel
#+begin_src python
Infinite list of even numbers
def evens(): i = 0; while True: yield i i += 2 #+end_src #+latex: \columnbreak #+begin_src python xs = evens() print (next (xs)) # ⇒ 0 print (next (xs)) # ⇒ 2 print (next (xs)) # ⇒ 4
Print first 5 even numbers
for _, x in zip(range(5),evens()): print x #+end_src #+end_parallel
#+latex: \vspace{-1em} Notice that =evens= is just ~count(0, 2)~ from the [[https://docs.python.org/2/library/itertools.html#module-itertools][itertools module]].
** Unpacking operation #+latex: \vspace{0.5em} | Unpacking operation |
-
Iterables are “unpacked” with =*= and dictionaries are “unpacked” with =**=.
-
Unpacking /syntactically/ removes the outermost parenthesis ()/[]/{}.
-
E.g., if =f= needs 3 arguments, then =f(*[x₁, x₂, x₃]) ≈ f(x₁, x₂, x₃)=.
-
E.g., printing a number of rows: ~print(*rows, sep = '\n')~.
-
E.g., [[https://www.python.org/dev/peps/pep-0448/][coercing iterable ~it~:]] #+begin_center #+begin_src python set(it) ≈ {*it}, list(it) ≈ [*it], tuple(it) ≈ (*it,) #+end_src #+end_center
[[https://www.python.org/dev/peps/pep-3132/][Iterable unpacking syntax]] may also be used for assignments, where ~*~ yields lists.
| ~x, *y, z = it~ | ≡ | ~x = it[0]; z = it[-1]; y = list(it[1:len(it)-1])~ | | | ⇒ | ~[x] + ys + [z] = list(it)~ |
E.g., ~head , *tail = xs~ to split a sequence.
In particular, since tuples only need parenthesis within expressions, we may write ~x , y = e₁, e₂~ thereby obtaining simultaneous assignment.
E.g., ~x, y = y , x~ to swap two values.
** Loops
#+begin_parallel Loops let us /iterate over/ iterables!
#+latex: \vspace{1.3em} ⇒ =break= exists a loop early; =continue= skips the current loop iteration.
#+latex: \vspace{1.3em} ⇒ Loops may be followed by an ~else:~ clause, which is executed /only/ if the loop terminated by its condition failing ---not due to a =break=!
#+latex: \columnbreak
#+begin_src python
for-loop over a set
for x in {2, 3, 4}: print (x) else: print ("for-loop is done")
Looping over characters with indices
for i, x in enumerate('abc'): print (f"{i} goes to {x}")
“while loop” over a tuple
i, xs = 0, (2, 3, 4) while i < len(xs): print (xs[i]) i += 1 #+end_src #+end_parallel
** For/while via iter/next Any user-defined class implementing ~iter~ and ~next~ can use loop syntax.
#+begin_src python for x in xs: f(x) ≈ it = iter(xs); while True: try: f(next(it)) except StopIteration: break #+end_src
- ~iter(x)~ ⇒ Get an iterable for object =x=.
- ~next(it)~ ⇒ Get the current element and advance the iterable =it= to its next
state.
- Raise [[https://docs.python.org/3.6/library/exceptions.html#StopIteration][StopIteration]] exception when there are no more elements.
** Methods on Iterables
#+latex: \vspace{1em} | Methods on Iterables |
-
src_python[:exports code]{len} gives the length of (finite) iterables
- =len ((1, 2))= ⇒ 2; the extra parentheses make it clear we're giving /one tuple argument/, not /two integer arguments/.
-
src_python[:exports code]{x in xs} ⇒ check whether value =x= is a member of =xs=
- src_python[:exports code]{x in y ≡ any(x == e for e in y)}, provided ~y~ is a finite iterable.
- src_python[:exports code]{x in y ≡ y.contains(x)}, provided =y='s class defines the method.
- src_python[:exports code]{x not in y ≡ not x in y}
-
src_python[:exports code]{range(start, stop, step)} ⇒ An iterator of integers from =start= up to =stop-1=, skipping every other =step-1= number.
- Associated forms: =range(stop)= and =range(start, stop)=.
-
src_python[:exports code]{reversed(xs)} returns a reversed iterator for ~xs~; likewise src_python[:exports code]{sorted(xs)}.
-
src_python[:exports code]{enumerate(xs) ≈ zip(xs, range(len(xs)))}
- Pair elements with their indices.
-
src_python[:exports code]{zip(xs₁, …, xsₙ)} is the iterator of tuples ~(x₁, …, xₙ)~ where ~xᵢ~ is from ~xsᵢ~.
- Useful for looping over multiple iterables at the same time.
- src_python[:exports code]{zip(xs, ys) ≈ ((x, y) for x in xs for y in ys)}
- src_python[:exports code]{xs₁ , …, xsₙ = zip(𝓍𝓈)} ⇒ “unzip” =𝓍𝓈=, an iterable of tuples, into a tuple of
(abstract) iterables ~xsᵢ~, using the unpacking operation ==.
#+begin_src python
xs , τ = [ {1,2} , [3, 4] ] , list
assert τ(map(tuple, xs)) == τ(zip(*(zip(*xs)))) == [(1,2) , (3,4)]
I claim the first “==” above is true for any xs with:
assert len({len(x) for x in xs}) == 1 #+end_src
-
src_python[:exports code]{map(f, xs₁, …, xₙ)} is the iterable of values ~f x₁ … xₙ~ where ~xᵢ~ is from ~xsᵢ~.
- This is also known as /zip with f/, since it generalises the built-in ~zip~.
- src_python[:exports code]{zip(xs, ys) ≈ map(lambda x, y: (x, y), xs, ys)}
- src_python[:exports code]{map(f, xs) ≈ (f(x) for x in xs)}
-
src_python[:exports code]{filter(p, xs) ≈ (x for x in xs if p(x))}
-
src_python[:exports code]{reduce(⊕, [x₀, …, xₙ], e) ≈ e ⊕ x₀ ⊕ ⋯ ⊕ eₙ}; the initial value ~e~ may be omitted if the list is non-empty.
#+begin_src python from functools import reduce
assert 'ABC' == reduce(lambda x, y: x + chr(ord(y) - 32), 'abc', '') #+end_src
These are all instances of src_python[:exports code]{reduce}:
- src_python[:exports code]{sum, min/max, any/all} ---remember “empty” values
are falsey!
#+begin_src python
Sum of first 10 evens
assert 90 == (sum(2*i for i in range(10))) #+end_src - Use ~prod~ from the ~numpy~ module for the product of elements in an iterable.
- src_python[:exports code]{sum, min/max, any/all} ---remember “empty” values
are falsey!
#+begin_src python
** Sum is monoidal #+latex: \vspace{1em} | Flattening |
Since, src_python[:exports code]{sum(xs, e = 0) ≈ e + xs[0] + ⋯ + xs[len(xs)-1]} We can use =sum= as a generic “list of τ → τ” operation by providing a value for =e=. E.g., lists of lists are catenated via:
#+begin_src python assert [1, 2, 3, 4] == sum([[1], [2, 3], [4]], []) assert (1, 2, 3, 4) == sum([(1,), (2, 3), (4,)], ())
List of numbers where each number is repeated as many times as its value
assert [1, 2, 2, 3, 3, 3, 4, 4, 4, 4] == sum([i * [i] for i in range(5)], []) #+end_src
** Methods for sequences only #+latex: \columnbreak
| Methods for sequences only |
#+begin_parallel Numeric addition and /sequence/ catenation are both denoted by ~+~; however: ~x + y~ crashes when src_python[:exports code]{type(x) != type(y)}. #+latex: \columnbreak #+begin_src python assert 'hi' == 'h' + 'i' assert (1, 2, 3, 4) == (1, 2) + (3, 4) assert [1, 2, 3, 4] == [1, 2] + [3, 4] #+end_src #+end_parallel
#+latex: \vspace{-0.7em} #+begin_parallel Multiplication is iterated addition; not just for numbers, but for all /sequence types/! #+latex: \columnbreak #+begin_src python assert "hi" * 2 == 2 * "hi" == "hihi" assert (1,2) * 3 == (1, 2, 1, 2, 1, 2) assert [1] * 3 == [1, 1, 1] #+end_src #+end_parallel
#+latex: \vspace{-0.7em} #+begin_parallel =xs.index(ys)= returns the first index in =xs= where =ys= occurs, or a src_python[:exports code]{ValueError} if it's not present. #+latex: \columnbreak #+begin_src python assert 1 == "abc".index('bc') assert 0 == (1, 2, 3).index(1) assert 1 == ['h', 'i'].index('i') #+end_src #+end_parallel
#+latex: \vspace{-0.7em} #+begin_parallel =xs.count(ys)= returns the number of times =ys= occurs as an element/substring of =xs=. #+latex: \columnbreak #+begin_src python assert 1 == "abc".count('ab') assert 0 == [1, 2, 3].count('ab') assert 1 == [1, 2, 3].count(2) assert 0 == [1, 2, 3].count([2, 3]) assert 1 == [1, [2, 3]].count([2, 3]) #+end_src #+end_parallel #+latex: \vspace{-0.5em}
** Sequences are Ordered #+latex: \vspace{0.3em} | Sequences are Ordered |
#+latex: \vspace{-0.3em} Sequences of the same type are compared lexicographically: Where ~k = min(n, m)~, ~[x₀, …, xₙ] < [y₀, …, yₘ] ≡ x₀ < y₀ or ⋯ or xₖ < yₖ~ ---recalling that Python's ~or~ is lazy; i.e., later arguments are checked only if earlier arguments fail to be true. Equality is component-wise. #+latex: \vspace{-0.5em} #+begin_src python assert [2, {}] != [3] # ⇒ Different lengths! assert [2, {}] < [3] # ⇒ True since 2 < 3. assert (1, 'b', [2, {}]) < (1, 'b', [3]) #+end_src
#+latex: \vspace{-1em}
- Tuples
#+latex: \hspace{-1.3em} A /tuple/ consists of a number of values separated by commas ---parenthesis are only required when the tuples appear in complex expressions.
#+begin_parallel Tuples are immutable; no setters.
#+latex: \vspace{2em} But we can access then alter /mutable components/ of a tuple; e.g., we can alter the list component of =t= ⇒
#+latex: \vspace{1em} Getter is usual indexing, ~xs[i]~.
#+latex: \vspace{3em} Convert ~x~ to a tuple with ~tuple(x)~.
#+latex: \vspace{2.5em} Iverson brackets again!
#+latex: \columnbreak #+begin_src python
Heterogeneous tuples
t = 1, 'b', [3], (4, 5)
Alter mutable component
t[2][0] = 33 assert t == (1, 'b', [33], (4, 5))
empty_tuple = () # ⇒ () singleton_tuple = 'one', # ⇒ ('one',)
Note the trailing comma!
assert (('3', '4') == tuple(['3', '4']) == tuple('34'))
(a, b)[c] ≈ a if c else b # Eager! #+end_src #+end_parallel
#+latex: \vspace{-1.5em} Simultaneous assignment is really just tuple unpacking on the left and tuple packing on the right. #+latex: \vspace{-1em}
- Strings
#+latex: \hspace{-1.3em} Strings are both ~"~-enclosed and ='=-enclosed literals; the former easily allows us to include apostrophes, but otherwise they are the same.
-
There is no separate character type; a character is simply a string of size one.
- src_python[:exports code]{assert 'hello' == 'he' + 'l' + 'lo' == 'he' 'l' 'lo'}
- String literals separated by a space are automatically catenated.
-
String characters can be accessed with [], but cannot be updated since strings are immutable. E.g., src_python[:exports code]{assert 'i' == 'hi'[1]}.
-
src_python[:exports code]{str(x)} returns a (pretty-printed) string representation of an object.
** String Comprehensions
String comprehensions are formed by joining all the strings in the resulting iterable ---we may join using any separator, but the empty string is common. #+begin_src python assert '5 ≤ 25 ≤ 125' == (' ≤ '.join(str(5 ** i) for i in [1, 2, 3])) #+end_src
- =s.join(xs).split(s) ≈ xs=
- ~xs.split(s)~ ⇒ split string =xs= into a list every time =s= is encountered
** String methods
Useful string operations: | ~s.startswith(⋯)~ | ~s.endswith(⋯)~ | | ~s.upper()~ | ~s.lower()~ |
- src_python[:exports code]{ord/chr} to convert between characters and integers.
- src_python[:exports code]{input(x)} asks user for input with optional prompt ~x~.
- E.g., src_python[:exports code]{i = int(input("Enter int: "))} ⇒ gets an integer from user
** f-strings: Formatted String Literals
f-strings are string literals that have an =f= before the starting quote and may contain curly braces surrounding expressions that should be replaced by their values.
#+begin_src python name, age = "Abbas", 33.1 print(f"{name} is {age:.2f} years {'young' if age > 50 else 'old'}!")
⇒ Abbas is 33.10 years old!
#+end_src
F-strings are expressions that are evaluated at runtime, and are generally faster than traditional formatted strings ---which Python also supports.
The brace syntax is ~{expression:width.precision}~, only the first is mandatory and the last is either ~𝓃f~ or ~𝓃e~ to denote 𝓃-many decimal points or scientific notation, respectively.
#+latex: \vspace{-1em}
- Lists
#+begin_parallel #+latex: \hspace{-1.3em} Python supports zero-indexed heterogeneous lists.
#+latex: \vspace{1em}
#+latex: \hspace{-1.3em} Like all sequence types, we access values with indices ~xs[0]~ and modify them in the same way. Above ~xs[12]~ yields an out of range error.
#+latex: \columnbreak #+begin_src python
Making lists
xs = [] xs.append(1) xs.append([2, 'a']) xs.append('b')
or:
xs = [1, [2, 'a'], 'b'] #+end_src #+end_parallel
#+latex: \vspace{-0.5em} Besides all of the iterable methods above, for lists we have:
- src_python[:exports code]{list(cs)} ⇒ turns a string/tuple into the list of its characters/components
- ~xs.remove(x)~ ⇒ remove the first item from the list whose value is ~x~.
- ~xs.index(x)~ ⇒ get first index where ~x~ occurs, or error if it's not there.
- ~xs.pop(i)~ ≈ ~(x := xs[i], xs := xs[:i] + xs[i+1:])[0]~
- [[Named Expressions]] are covered below; if ~i~ is omitted, it defaults to ~len(xs)-1~.
- Lists are thus stacks with interface ~append/pop~.
- For a list-like container with fast appends and [[https://docs.python.org/3/tutorial/datastructures.html#using-lists-as-queues][pops on either end]], see the [[https://docs.python.org/3/library/collections.html#module-collections][deque collection]] type.
#+latex: \vspace{-1em}
- Sets
#+latex: \vspace{-0.5em} #+begin_parallel #+latex: \hspace{-1.3em} src_python[:exports code]{set(xs)} to transform a sequence into a set, which is a list without repetitions.
#+latex: \vspace{1em}\hspace{-1.3em} Useful methods ~a.𝓂(b)~ where 𝓂 is =intersection, union, difference, symmetric_difference=.
#+latex: \columnbreak #+begin_src python
Two ways to make sets; no duplicates!
assert {1, 2, 3} == set([3, 1, 1, 2])
Set comprehension
{x for x in 'abracadabra' if x not in 'abc'}
⇒ {'d', 'r'}
#+end_src #+end_parallel
#+latex: \vspace{-1.5em}
- Dictionaries
#+latex: \hspace{-1.5em} Note that ={}= denotes the empty dictionary, not the empty set.
A /dictionary/ is like a list but indexed by user-chosen /keys/, which are members of any immutable type. /It's really a set of “key:value” pairs./
E.g., a dictionary of numbers along with their squares can be written explicitly (below left) or using a comprehension (below right). #+begin_src python assert {2: 4, 4: 16, 6: 36} == {x: x**2 for x in (2, 4, 6)} #+end_src
** Dictionaries implement hierarchical tree structures and case statements #+begin_parallel
| Hierarchical Tree Structures |
#+latex: \vspace{0.5em} #+begin_src python you = { "kid1": "Alice" , "kid2": { "kid1": "Bobert" , "kid2": "Mary" } } #+end_src #+latex: \columnbreak | “Case Statements” |
#+latex: \vspace{0.5em} #+begin_src python i, default = 'k' , "Dec" x = { 'a': "Jan" , 'k': "Feb" , 'p': "Mar" }.get(i, default) assert x == 'Feb' #+end_src #+end_parallel
#+latex: \vspace{-1em} Alternatively: Start with =you = {}= then later add key-value pairs: =you[key] = value=. #+begin_src python assert 'Bobert' == you["child2"]['child1'] # access via indices del you['child2']['child2'] # Remove a key and its value assert 'Mary' not in you['child2'].values() #+end_src
** Dictionary Methods #+latex: \vspace{1em}
-
src_python[:exports code]{list(d)} ⇒ list of keys in dictionary ~d~.
-
~d.keys(), d.values()~ ⇒ get an iterable of the keys or the values.
-
src_python[:exports code]{k in d} ⇒ Check if key ~k~ is in dictionary ~d~.
-
src_python[:exports code]{del d[k]} ⇒ Remove the key-value pair at key ~k~ from dictionary ~d~.
-
~d[k] = v~ ⇒ Add a new key-value pair to ~d~, or update the value at key ~k~ if there is one.
-
src_python[:exports code]{dict(xs)} ⇒ Get a dictionary from a list of key-value tuples.
When the keys are strings, we can specify pairs using keyword arguments: #+latex: \newline src_python[:exports code]{dict(me = 12, you = 41, them = 98)}.
Conversely, ~d.items()~ gives a list of key-value pairs; which is useful to have when looping over dictionaries.
In dictionary literals, later values will always override earlier ones: #+begin_src python assert dict(x = 2) == {'x':1, 'x':2} #+end_src
Dictionary update: ~d = {**d, key₁:value₁, …, keyₙ:valueₙ}~.
- Splicing
#+latex: \hspace{-1.3em} =xs[start:stop:step]= ≈ the subsequence of =xs= from =start= to =stop-1= skipping every =step-1= element. All are optional, with =start, stop=, and =step= defaulting to =0, len(xs)=, and =1=; respectively.
- The start is always included and the end always excluded.
- =start= may be negative: -𝓃 means the 𝓃-th item from the end.
- All slice operations return a new sequence containing the requested elements.
- One colon variant: =xs[start:stop]=, both =start= and =stop= being optional.
- /Slicing applies to sequence types only/ ---i.e., types implementing ~getitem~.
#+begin_parallel #+begin_src python :session splicing xs = [11, 22, 33, 44, 55]
assert xs[3:-5] == [] assert xs[3:7] == [44, 55] assert ( xs[3:77] == xs[3: min(77, len(xs))] == xs[3:5]) #+end_src
#+latex: \columnbreak #+begin_src python :session splicing assert "ola" == "hola"[1:] assert (3, 2, 1) == (1, 2, 3)[::-1]
assert xs[-1::] == [55]
n, N = 10, len(xs) assert xs[-n::] == xs[max(0, N - n)::] #+end_src #+end_parallel
#+latex: \vspace{-1em}
#+begin_parallel /Useful functions via splicing/
#+latex: \vspace{1em} | ~xs[:𝓃]~ | ⇒ | take first 𝓃 items | | ~xs[0]~ | ⇒ | head of ~xs~ | | ~xs[𝓃:]~ | ⇒ | drop first 𝓃 items | | ~xs[1:]~ | ⇒ | tail of ~xs~ | | ~xs[-1]~ | ⇒ | last element of ~xs~ | | ~xs[::𝓀]~ | ⇒ | get every 𝓀-th value | | ~𝓃 * [x]~ | ⇒ | the list consisting of | | | | ~x~ repeated 𝓃-times |
#+latex: \columnbreak /Splice laws/ #+latex: \vspace{1em} | ~xs[:]~ | ≈ | ~xs~ | | ~xs[::]~ | ≈ | ~xs~ | | ~xs[0:len(xs)]~ | ≈ | ~xs~ | | ~xs[::-1]~ | ≈ | ~reversed(xs)~ | | ~xs[:𝓃] + xs[𝓃:]~ | ≈ | ~xs~ | | ~len(xs[𝓂:𝓃])~ | ≈ | ~𝓃 - 𝓂~ |
#+latex: \vspace{2em} | | ~xs[𝓂:𝓃] = ys~ | | ≡ | ~xs = xs[:𝓂] + ys + xs[𝓃:]~ | #+end_parallel
Assignment to slices is possible, resulting in sequences with possibly different sizes.
#+begin_src python xs = list(range(10)) # ⇒ xs ≈ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] xs[3:7] = ['a', 'b'] # ⇒ xs ≈ [0, 1, 2, 'a', 'b', 7, 8, 9] #+end_src
Other operations via splicing:
- ~0 == s.find(s[::-1])~ ⇒ string ~s~ is a palindrome
- src_python[:exports code]{inits xs ≈ [xs[0:i] for i in range(1 + len(xs))]}
- src_python[:exports code]{segs xs ≈ [xs[i:j] for i in range(len(xs)) for j in range(i, len(xs))]}
#+latex: \columnbreak
- Functions
#+latex: \hspace{-1.3em} /Functions are first-class citizens/: Python has one namespace for functions and variables, and so there is no special syntax to pass functions around or to use them anywhere, such as storing them in lists.
-
src_python[:exports code]{return} clauses are optional; if there are none, a function returns src_python[:exports code]{None}.
-
Function application always requires parentheses, even when there are no arguments.
-
Any object ~x~ can be treated like a function, and use the ~x(⋯)~ application syntax, if it implements the ~call~ method: ~x(⋯) ≈ x.call(⋯)~. The src_python[:exports code]{callable} predicate indicates whether an object is callable or not.
-
Functions, and classes, can be nested without any special syntax; the nested functions are just new local values that happen to be functions. Indeed, nested functions can be done with src_python[:exports code]{def} or with assignment and src_python[:exports code]{lambda}.
-
Functions can receive a variable number of arguments using ~*~.
#+begin_src python :results output :session compose def compose(*fs): """Given many functions f₀,…,fₙ return a new one: λ x. f₀(⋯(fₙ(x))⋯)""" def seq(x): seq.parts = [f.name for f in fs] for f in reversed(fs): x = f(x) return x return seq
print (help(compose)) # ⇒ Shows the docstring with function type compose.doc = "Dynamically changing docstrings!"
Apply the “compose” function;
first define two argument functions in two ways.
g = lambda x: x + 1 def f(x): print(x)
h = compose(f, g, int)
h('3') # ⇒ Prints 4
print(h.parts) # ⇒ ['f', '
Redefine “f” from being a function to being an integer.
f = 3
f(1) # ⇒ Error: “f” is not a function anymore!
#+end_src
#+results:
: Help on function compose in module main:
:
: compose(*fs)
: Given f₀,…,fₙ return λ x ↦ f₀(⋯(fₙ(x))⋯)
:
: None
: 4
: ['f', '
Note that ~compose()~ is just the identity function ~lambda x∶ x~. ** Functions have attributes
The first statement of a function body can optionally be a ‘docstring’, a string enclosed in three double quotes. You can easily query such documentation with ~help(functionName)~. In particular, ~f.code.co_argcount~ to obtain the number of arguments ~f~ accepts.
That [[https://www.python.org/dev/peps/pep-0232/][functions have attributes]] ---state that could alter their behaviour--- is not at all unexpected: Functions are objects; Python objects have attributes like ~doc~ and can have arbitrary attributes (dynamically) attached to them.
** On the nature of λ
A src_python[:exports code]{lambda} is a single line expression; you are prohibited from writing statements like src_python[:exports code]{return}, but the semantics is to do the src_python[:exports code]{return}.
src_python[:exports code]{lambda args: (x₀ := e₀, …, xₙ := eₙ)[k]} is a way to perform ~n~-many stateful operations and return the value of the ~k~-th one. See ~pop~ above for lists; [[Named Expressions]] are covered below.
For fresh name ~x~, a let-clause /“let x = e in ⋯”/ can be simulated with ~x = e; …; del x~. However, in combination with [[Named Expressions]], lambda's ensure a new local name: src_python[:exports code]{(lambda x = e: ⋯)()}.
** Default & keyword argument values #+latex: \vspace{0.7em} | Default & keyword argument values are possible |
#+begin_src python :session kwds def go(a, b=1, c='two'): """Required 'a', optional 'b' and 'c'""" print(a, b, c) #+end_src Keyword arguments must follow positional arguments; order of keyword arguments (even required ones) is not important.
- Keywords cannot be repeated. #+begin_src python :session kwds go('a') # ⇒ a 1 two ;; only required, positional go(a='a') # ⇒ a 1 two ;; only required, keyword go('a', c='c') # ⇒ a 1 c ;; out of order, keyword based go('a', 'b') # ⇒ a b two ;; positional based go(c='c', a='a') # ⇒ a 1 c ;; very out of order #+end_src
** Dictionary arguments #+latex: \vspace{0.7em} | Dictionary arguments |
After the /required/ positional arguments, we can have an arbitrary number of optional/ positional arguments (a tuple) with the syntax ~*args~, after that we may have an arbitrary number of /optional/ keyword-arguments (a dictionary) with the syntax ~**args~.
The reverse situation is when arguments are already in a list or tuple but need to be unpacked for a function call requiring separate positional arguments. Recall, from above, that we do so using the ~*~ operator; likewise ~**~ is used to unpack dictionaries.
- E.g., if =f= needs 3 arguments, then =f(*[x₁, x₂, x₃]) ≈ f(x₁, x₂, x₃)=.
#+begin_src python def go(a, *more, this='∞', **kwds): print (a) for m in more: print(m) print (this) for k in kwds: print(f'{k} ↦ {kwds[k]}') return kwds['neato'] if 'neato' in kwds else -1
Elementary usage
go(0) # ⇒ 0 ∞ go(0, 1, 2, 3) # ⇒ 0 1 2 3 ∞ go(0, 1, 2, this = 8, three = 3) # ⇒ 0 1 2 8 three ↦ 3 go(0, 1, 2, three=3, four = 4) # ⇒ 0 1 2 ∞ three ↦ 3 four ↦ 4
Using “**”
args = {'three': 3, 'four': 4} go(0, 1, 2, **args) # ⇒ 0 1 2 ∞ three ↦ 3 four ↦ 4
Making use of a return value
assert 5 == go (0, neato = 5) #+end_src
** Type Annotations #+latex: \vspace{1em} | Type Annotations |
We can [[https://www.python.org/dev/peps/pep-3107/][annotate functions]] by expressions ---these are essentially useful comments, and not enforced at all--- e.g., to provide [[https://www.python.org/dev/peps/pep-0484/][type hints]]. They're useful to document to human readers the intended types, or used by third-party tools. #+begin_src python
A function taking two ints and returning a bool
def f(x:int, y : str = 'neat') -> bool: return str(x) # Clearly not enforced!
print (f('hi')) # ⇒ hi; Typing clearly not enforced
print(f.annotations) # ⇒ Dictionary of annotations #+end_src
** Currying
Currying: Fixing some arguments ahead of time.
#+begin_parallel #+latex: \vspace{0em} #+begin_src python partial(f, v₀, …, vₙ) ≈ lambda x₀, …, xₘ: f(v₀, …, vₙ, x₀, …, xₘ) #+end_src
#+latex: \columnbreak #+begin_src python from functools import partial
multiply = lambda x, y, z: z * y + x twice = partial(multiply, 0, 2) assert 10 == twice(5) #+end_src
#+results: #+end_parallel
Using decorators and classes, we can make an ‘improved’ partial application mechanism ---see the final page.
** Decorators
#+begin_parallel Decorators allow us to modify functions in orthogonal ways, such as printing values when debugging, without messing with the core logic of the function. #+latex: \vspace{1em} E.g., to do preprocessing before and after a function call ---e.g., =typed= below for this standard template. #+latex: \vspace{1em} Decorators are just functions that alter functions, and so they can return anything such as an integer thereby transforming a function name into an integer variable. #+latex: \ifnum\cheatsheetcols=1 \columnbreak \else \columnbreak \fi #+begin_src python @decorator_2 @decorator_1 def fun(args): ⋯
≈
def fun(args): ⋯ fun = decorator_2(decorator_1(fun)) #+end_src
#+latex: \vspace{1em} The [[https://www.python.org/dev/peps/pep-0318/#current-syntax][decoration syntax]] ~@ d f~ is a convenient syntax that emphasises code acting on code. #+end_parallel
Decorators can be helpful for functions we did not write, but we wish to [[https://en.wikipedia.org/wiki/Advice_(programming)][advise]] their behaviour; e.g., ~math.factorial = my_decorator(math.factorial)~ to make the standard library's ~factorial~ work in new ways.
When decorating, we may use [[https://stackoverflow.com/a/36908/3550444][*args and **kwargs]] in the inner wrapper function so that it will accept an arbitrary number of positional and keyword arguments. See =typed= below, whose inner function accepts any number of arguments and passes them on to the function it decorates.
We can also use decorators to add a bit of type checking at runtime: #+begin_src python :session learning import functools
“typed” makes decorators; “typed(s₁, …, sₙ, t)” is an actual decorator.
def typed(*types): ,*inTys, outT = types def decorator(fun): @functools.wraps(fun) def new(*args, **kwdargs): # (1) Preprocessing stage if any(type(𝓌 := arg) != ty for (arg, ty) in zip(args, inTys)): nom = fun.name raise TypeError (f"{nom}: Wrong input type for {𝓌!r}.") # (2) Call original function result = fun(*args, **kwdargs) # Not checking keyword args # (3) Postprocessing stage if type(result) != outT: raise TypeError ("Wrong output type!") return result return new return decorator #+end_src
#+results:
After being decorated, function attributes such as ~name~ and ~doc~ refer to the decorator's resulting function. In order to have it's attributes preserved, we copy them over using [[https://docs.python.org/3/library/functools.html#functools.wraps][ [email protected]~ decorator]] ---or by declaring ~functools.update_wrapper(newFun, oldFun)~.
#+begin_src python :session learning
doit : str × list × bool → NoneType
@typed(str, list, bool, type(None)) def doit(x, y, z = False, *more): print ((ord(x) + sum(y)) * z, *more) #+end_src Notice we only typecheck as many positions as given, and the output; other arguments are not typechecked. #+begin_src python :session learning
⇒ TypeError: doit: Wrong input type for 'bye'!
doit('a', [1, 2], 'bye')
⇒ 100 n i ;; typechecking succeeds
doit('a', [1, 2], True, 'n', 'i')
⇒ 0; Works with defaults too ;-)
doit(x, y)
⇒ 194; Backdoor: No typechecking on keyword arguments!
doit('a', z = 2, y = {}) #+end_src
The implementation above matches the ~typed~ specification, but the one below does not and so always crashes.
#+begin_src python :session learning
This always crashes since
the result is not a string.
@typed(int, str) def always_crashes(x): return 2 + x #+end_src
Note that ~typed~ could instead /enforce/ type annotations, as shown before, at run time ;-)
An easier way to define a family of decorators is to define a [[https://github.com/micheles/decorator][decorator-making-decorator]]!
#+latex: \vspace{-1em}
- Object-Oriented Programming
#+latex: \hspace{-1.3em} /Classes/ bundle up data and functions into a single entity; /Objects/ are just values, or /instances/, of class types. That is, a class is a record-type and an object is a tuple value.
-
A Python /object/ is just like a real-world object: It's an entity which has attributes ---/a thing which has features/.
-
We /classify/ objects according to the features they share.
-
A /class/ specifies properties and behaviour, an implementation of which is called an /object/. Think class is a cookie cutter, and an actual cookie is an object.
-
Classes are also known as “bundled up data”, structures, and records.
They let us treat a collection of data, including methods, as one semantic entity. E.g., rather than speak of name-age-address tuples, we might call them person objects.
Rather than “acting on” tuples of data, an object “knows how to act”; we shift from ~doit(x₁, …, xₙ)~ to ~𝓍.doit()~. We /abstract away/ the =n=-many details into 1 self-contained idea.
** Person: Empty Class #+latex: \vspace{0.5em} | What can we learn from an empty class? |
#+begin_parallel #+begin_src python :session empty-person class Person: """An example, empty, class.""" pass #+end_src
#+latex: \vspace{1em} src_python[:exports code]{pass} is the “do nothing” statement. It's useful for writing empty functions/classes that will be filled in later or for explicitly indicating do-nothing cases in complex conditionals.
#+latex: \vspace{0.5em}\hrule\vspace{0.5em} #+begin_src python :session empty-person
We defined a new type!
assert isinstance(Person, type)
View information of the class
print (help(Person))
Or use: Person.name,
Person.doc, Person.dict
Let's make a Person object
jasim = Person() assert Person == type(jasim) assert isinstance(jasim, Person) #+end_src
#+latex: \vfill\hrule\vspace{0.5em} Instance (reference) equality is compared with src_python[:exports code]{is}. #+latex: \vspace{0.3em} #+begin_center src_python[:exports code]{x is y ≡ id(x) == id(y)} #+end_center ~id(x)~ is a unique number identifying object ~x~; usually its address in memory. #+latex: \vspace{0.5em} #+begin_src python :session empty-person jason = jasim qasim = Person () assert jason is jasim and jasim is jason assert qasim is not jasim #+end_src
#+latex: \vspace{0.5em}\hrule\vspace{0.5em} #+begin_src python :session empty-person
Check attributes exist before use
assert not hasattr(jasim, 'work')
Dynamically add new (instance) attributes
jasim.work = 'farmer' jasim.nick = 'jay'
Delete a property
del jasim.nick
View all attribute-values of an object
print(jasim.dict) # {'work': 'farmer'} #+end_src #+latex: \vspace{0.7em}\hrule\vspace{0.5em} #+end_parallel
| /Look at that, classes are just fancy dictionary types!/ | The converse is also true: src_python[:exports code]{class X: a = 1 ≈ X = type('X', (object,), dict(a = 1))} ** A More Complex ~Person~ Class
#+latex: \vspace{0.5em} | Let's add more features! |
src_python[:exports code]{# [0]} An =init= method is called whenever a new object is created via =Person(name, age)=. It /constructs/ the object by /initialising/ its necessary features.
src_python[:exports code]{# [1]} The argument src_python[:exports code]{self} refers to the object instance being created and src_python[:exports code]{self.x = y} is the creation of an attribute =x= with value =y= for the newly created object instance. Compare src_python[:exports code]{self} with =jasim= above and src_python[:exports code]{self.work} with =jasim.work=. It is convention to use the name src_python[:exports code]{self} to refer to the current instance, you can use whatever you want but it must be the first argument.
src_python[:exports code]{# [2]} Each =Person= instance has their own =name= and =work= features, but they universally share the =Person.__world= feature. Attributes starting with two underscores are /private/; they can only be altered within the definition of the class. Names starting with no underscores are /public/ and can be accessed and altered using dot-notation. Names starting with one underscore are /protected/; they can only be used and altered by children classes.
#+begin_src python :session OOP class Person: __world = 0 # [2]
def __init__(self, name, work): # [0]
self.name = name
self.work = work
Person.__world += 1
def speak(me): # [1] Note, not using “self”
print (f"I, {me.name}, have the world at my feet!")
# Implementing __str__ allows our class to be coerced as string
# and, in particular, to be printed.
def __str__(self):
return (f"In a world of {Person.__world} people, "
f"{self.name} works at {self.work}")
# [3] Any class implementing methods __eq__ or __lt__
# can use syntactic sugar == or <, respectively.
def __eq__(self, other):
return self.work == other.work
# We can loop over this class by defining __iter__,
# to setup iteration, and __next__ to obtain subsequent elements.
def __iter__(self):
self.x = -1
return self
def __next__(self):
self.x += 1
if self.x < len(self.name): return self.name[self.x]
else: raise StopIteration
#+end_src
#+results:
** Making People #+latex: \vspace{1em} | Making People |
#+begin_src python :session OOP jason = Person('Jasim', "the old farm") kathy = Person('Kalthum', "Jasim's farm") print(kathy) # ⇒ In a world of 2 people, Kalthum works at Jasim's farm
Two ways to use instance methods
jason.speak() # ⇒ I, Jasim, have the world at my feet! Person.speak(jason) #+end_src
The following code /creates a new public feature that happens to have the same name as the private one/. This has no influence on the private feature of the same name! See src_python[:exports code]{# [2]} above. #+begin_src python :session OOP Person.__world = -10
Check that our world still has two people:
print(jason) # ⇒ In a world of 2 people, Jasim works at the old farm #+end_src
** Syntax Overloading: Dunder Methods
#+latex: \vspace{1em} | Syntax Overloading: Dunder Methods |
src_python[:exports code]{# [3]} Even though =jasim= and =kathy= are distinct people, in a dystopian world where people are unique up to contribution, they are considered “the same”. #+begin_src python :session OOP kathy.work = "the old farm" assert jason is not kathy assert jason == kathy #+end_src
We can use any Python syntactic construct for new types by implementing the dunder ---“d”ouble “under”score--- methods that they invoke. This way new types become indistinguishable from built-in types. E.g., implementing ~call~ makes an object behave like a function whereas implementing ~iter~ and ~next~ make it iterable ---possibly also implementing ~getitem~ to use the slicing syntax ~obj[start:stop]~ to get a ‘subsegment’ of an instance. Implementing ~eq~ and ~lt~ lets us use ~==, <~ which are enough to get ~<=, >~ if we decorate the class by the [[https://docs.python.org/2/library/functools.html#functools.total_ordering][ [email protected]_ordering~ decorator]]. [[https://docs.python.org/3/reference/datamodel.html#object.rsub][Reflected operators]] ~rℴ𝓅~ are used for arguments of different types: ~x ⊕ y ≈ y.r⊕(x)~ if ~x.⊕(y)~ is not implemented.
#+begin_src python :session OOP
Loop over the “jason” object; which just loops over the name's letters.
for e in jason: print (e) # ⇒ J \n a \n s \n i \n m
Other iterable methods all apply.
print(list(enumerate(jason)) # ⇒ [(0, 'J'), (1, 'a'), (2, 's'), …] #+end_src
One should not have attributes named such as ~attribute~; the dunder naming convention is for the Python implementation team.
- [[https://rszalski.github.io/magicmethods/][Here]] is a list of possible dunder methods.
- ~add~ so we can use ~+~ to merge instances ---then use ~sum~ to ‘add’ a list of elements.
- Note: ~𝒽(x) ≈ x.𝒽~ for 𝒽: src_python[:exports code]{len, iter, next, bool, str}.
** Extension Methods #+latex: \vspace{0.5em} | Extension Methods |
#+latex: \vspace{-0.5em} #+begin_src python :session OOP
“speak” is a public name, so we can assign to it:
(1) Alter it for “jason” only
jason.speak = lambda: print(f"{jason.name}: Hola!")
(2) Alter it for ALL Person instances
Person.speak = lambda p: print(f"{p.name}: Salam!") jason.speak() # ⇒ Jasim: Hola! kathy.speak() # ⇒ Kalthum: Salam! #+end_src
Notice how =speak()= above was altered. In general, we can “mix-in new methods” either at the class level or at the instance level in the same way.
#+begin_parallel #+begin_src python
New complex method
def speak(self): ⋯
Add it at the class level
Person.speak = speak
Remove “speak” from
the current scope
del speak #+end_src #+latex: \columnbreak This ability to extend classes with new functions does not work with the builtin types like src_python[:exports code]{str} and src_python[:exports code]{int}; neither at the class level nor at the instance level. If we want to inject functionality, we can simply make an empty class like the first incarnation of ~Person~ above. An example, ~PartiallyAppliedFunction~, for altering how function calls work is shown on the right column ⇒ #+end_parallel
** Inheritance #+latex: \vspace{1em} | Inheritance |
A class may /inherit/ the features of another class; this is essentially automatic copy-pasting of code. This gives rise to /polymorphism/, the ability to “use the same function on different objects”: If class ~A~ has method ~f()~, and classes ~B~ and ~C~ are children of ~A~, then we can call ~f~ on ~B~- and on ~C~-instances; moreover ~B~ and ~C~ might redefine ~f~, thereby ‘overloading’ the name, to specialise it further.
#+begin_src python :session OOP class Teacher(Person): # Overriding the inherited init method. def init(self, name, subject): super().init(name, f'the university teaching {subject}') self.subject = subject
assert isinstance(Teacher, type) assert issubclass(Teacher, Person) assert issubclass(Person, object)
The greatest-grandparent of all classes is called “object”.
moe = Teacher('Ali', 'Logic') assert isinstance(moe, Teacher) # By construction. assert isinstance(moe, Person) # By inheritance. print(moe)
⇒ In a world of 3 people, Ali works at the university teaching Logic
#+end_src
- Decorators and Classes
Since ~@C f~ stands for ~f = C(f)~, we can decorate via /classes/ ~C~ whose ~init~ method takes a function. Then ~@C f~ will be a class! If the class implements ~call~ then we can continue to treat ~@C f~ as if it were a (stateful) function.
In turn, we can also decorate class methods in the usual way. E.g., when a method =𝓍(self)= is decorated [[https://docs.python.org/library/functions.html#property][ ~@property~ ]], we may attach logic to its setter ~obj.𝓍 = ⋯~ and to its getter ~obj.𝓍~!
We can decorate an entire class ~C~ as usual; =@dec C= still behaves as update via function application: =C = dec(C)=. This is one way to change the definition of a class dynamically.
- E.g., to implement design patterns like the singleton pattern.
A class decorator is a function from classes to classes; if we apply a function decorator, then only the class' constructor is decorated ---which makes sense, since the constructor and class share the same name.
** Curry Decorator
#+latex: \ifnum\cheatsheetcols=1 \else \columnbreak \vspace{-1em} \fi | Example: Currying via Class Decoration |
#+latex: \vspace{-0.5em} Goal: We want to apply functions in many ways, such as ~f(x₁, …, xₙ)~ and ~f(x₁, …, xᵢ)(xᵢ₊₁, …, xₙ)~; i.e., all the calls on the right below are equivalent. #+latex: \vspace{-0.5em}
#+begin_parallel #+begin_src python :session curry @curry def doit(x, y, z): print('got', x, y, z) #+end_src #+latex: \columnbreak #+begin_src python :session curry doit(1)(2)(3) doit(1, 2)(3) doit(1)(2, 3) doit(1, 2, 3) doit(1, 2, 3, 666, '∞') # Ignore extra args #+end_src #+end_parallel
#+latex: \vspace{-1.5em} The simplest thing to do is to transform src_python[:exports code]{f = lambda x₁, …, xₙ: body} into src_python[:exports code]{nestLambdas(f, [], f.code.co_argcount) = lambda x₁: …: lambda xₙ: body}. #+begin_src python :session curry def nestLambdas (func, args, remaining): if remaining == 0: return func(*args) else: return lambda x: nestLambdas(func, args + [x], remaining - 1) #+end_src
#+results:
#+latex: \vspace{-0.5em}
However, the calls shift from ~f(v₁, …, vₖ)~ to ~f(v₁)(v₂)⋯(vₖ)~; so we need to change what it means to call a function. As already mentioned, we cannot extend built-in classes, so we'll make a wrapper to slightly alter what it means to call a function on a smaller than necessary amount of arguments.
#+latex: \vspace{-0.5em} #+begin_src python :session curry class PartiallyAppliedFunction():
def __init__(self, func):
self.value = nestLambdas(func, [], func.__code__.co_argcount)
def __mul__ (self, other):
return PartiallyAppliedFunction(lambda x: self(other(x)))
apply = lambda self, other: other * self if callable(other) else self(other)
def __rshift__(self, other): return self.apply(other)
def __rrshift__(self, other): return self.apply(other)
def __call__(self, *args):
value = self.value
for a in args:
if callable(value):
value = value(a)
return PartiallyAppliedFunction(value) if (callable(value)) else value
curry = PartiallyAppliedFunction # Shorter convenience name #+end_src
#+results:
The above invocation styles, for ~doit~, now all work ^_^
Multiplication now denotes function composition and the [[https://docs.python.org/3/reference/datamodel.html#object.rsub][(‘r’eflected) ‘r’ight-shift]] denotes forward-composition/application: | ~(g * f(v₁, …, vₘ))(x₁, …, xₙ) = g(f (v₁, …, vₘ, x₁))(x₂, …, xₙ)~ | #+latex: \vspace{-0.5em}
#+begin_parallel #+begin_src python :session curry @curry def f(x, y, z): return x + y + z
@curry def g(x, y): return [x] * y #+end_src
#+results:
#+latex: \columnbreak #+begin_src python :session curry assert( (g * f(3, 1))(9, 4) == (f(3, 1) >> g)(9, 4) == [13, 13, 13, 13])
assert ( ['a', 'a', 'b'] == 2 >> g('a') >> curry(lambda x: x + ['b'])) #+end_src
#+results: #+end_parallel
- Named Expressions
The value of a [[https://www.python.org/dev/peps/pep-0572/][“walrus”]] expression ~x := e~ is the value of ~e~, but it also introduces the name ~x~ into scope. The name =x= must be an atomic identifier; e.g., not an unpacked pattern or indexing; moreover =x= cannot be a ~for~-bound name.
#+begin_parallel “subexpression reuse”
#+begin_src python f(e, e) ≈ f(x := e, x) #+end_src
#+latex: \vspace{1em} “if-let” #+latex: \vspace{0.5em} #+begin_src python x = e; if p(x): f(x) ≈ if p(x := e): f(x) #+end_src
#+latex: \vspace{0.5em} This can be useful to capture the value of a /truthy/ item so as to use it in the body: #+latex: \vspace{0.5em} #+begin_src python if e: x = e; f(x) ≈ if (x := e): f(x) #+end_src
#+latex: \vspace{0.5em} “while-let” #+latex: \vspace{0.5em} #+begin_src python while True: x = input(); if p(x): break; f(x) ≈ while p(x := input()): f(x) #+end_src
#+latex: \vspace{1em} “witness/counterexample capture” #+latex: \vspace{0.5em} #+begin_src python if any(p(witness := x) for x in xs): print(f"{witness} satisfies p")
if not all(p(witness := x) for x in xs): print(f"{witness} falsifies p") #+end_src #+end_parallel
#+latex: \vspace{-1em} “Stateful Comprehensions” #+begin_src python partial sums of xs ≈ [sum(xs[: i + 1]) for i in range(len(xs))] ≈ (total := 0, [total := total + x for x in xs])[1] #+end_src
Walrus introduces new names, what if we wanted to check if a name already exists? #+begin_src python
alter x if it's defined else, use 7 as default value
x = x + 2 if 'x' in vars() else 7 #+end_src
#+latex: \vspace{-1em}
#+latex: \ifnum\cheatsheetcols=1 \else \vfill \fi
- Modules
#+latex: \hspace{-1.3em} ⇒ Each Python file ~myfile.py~ determines a module whose contents can be used in other files, which declare src_python[:exports code]{import myfile}, in the form ~myfile.component~.
⇒ To use a function ~f~ from ~myfile~ without qualifying it each time, we may use the ~from~ import declaration: src_python[:exports code]{from myfile import f}.
⇒ Moreover, src_python[:exports code]{from myfile import *} brings into scope all contents of ~myfile~ and so no qualification is necessary.
⇒ To use a smaller qualifier, or have conditional imports that alias the imported modules with the same qualifier, we may use src_python[:exports code]{import thefile as newNameHere}.
⇒ A Python package is a directory of files ---i.e., Python modules--- with a (possibly empty) file named ~init.py~ to declare the directory as a package.
⇒ If ~P~ is a package and ~M~ is a module in it, then we can use src_python[:exports code]{import P.M} or src_python[:exports code]{from P import M}, with the same behaviour as for modules. The init file can mark some modules as private and not for use by other packages.
#+latex: \vspace{-0.5em}
#+latex: \ifnum\cheatsheetcols=1 \else \columnbreak \fi
#+latex: \ifnum\cheatsheetcols=1 \else \vfill \fi
- Reads
- [[https://dbader.org/][Dan Bader's Python Tutorials]] ---bite-sized lessons
- Likewise: [[https://www.w3schools.com/python/python_intro.asp][w3schools Python Tutorial]]
- [[https://www.learnpython.org/][www.learnpython.org]] ---an interactive and tremendously accessible tutorial
- [[https://docs.python.org/3/tutorial/index.html][The Python Tutorial]] ---really good introduction from [[https://www.python.org/][python.org]]
- https://realpython.com/ ---real-world Python tutorials
- [[https://norvig.com/python-lisp.html][Python for Lisp Programmers]]
- [[https://github.com/jupyter/jupyter/wiki/A-gallery-of-interesting-Jupyter-Notebooks][A gallery of interesting Jupyter Notebooks]] ---interactive, ‘live’, Python tutorials
- [[https://runestone.academy/runestone/books/published/thinkcspy/index.html][How to think like a computer scientist]] ---Python tutorial that covers turtle graphics as well as drag-and-drop interactive coding and interactive quizzes along the way to check your understanding; there are also videos too!
- [[https://github.com/dbrattli/OSlash][Monads in Python]] ---Colourful Python [[https://github.com/dbrattli/OSlash#tutorials][tutorials]] converted from Haskell
- [[http://norvig.com/21-days.html][Teach Yourself Programming in Ten Years]]