Effective Python 66 - 70

Click here for the first post, which contains the context of this series.

Item #66: Consider contextlib and with statements for reusable try/finally behavior.

  • The with statement allows you to reuse logic from try/finally blocks and reduce visual noise.
  • The contextlib built-in module provides a contextmanager decorator that makes it easy to use your own functions in with statements.
  • The value yielded by context managers is supplied to the as part of the with statement. It is useful for letting your code directly access the cause of a special context.

Item #67: Use datetime instead of time for local clocks.

  • Avoid using the time module for translating between different time zones.
  • Use the datetime built-in module along with the pytz community module to reliably convert between times in different time zones.
  • Always represent time in UTC and do conversions to local time as the very final step before presentation.

Item #68: Make pickle reliable with copyreg.

  • The pickle built-in module is useful only for serializing and deserializing objects between trusted programs.
  • Deserializing previously pickled objects may break if the classes involved have changed over time (e.g., attributes have been added or removed).
  • Use the copyreg built-in module with pickle to ensure backward compatibility for serialized objects.

Item #69: Use decimal when precision is paramount.

  • Python has built-in types and classes in modules that can represent practically every type of numerical value.
  • The Decimal class is ideal for situations that require high precision and control over rounding behavior, such as computations of monetary values.
  • Pass str instances to the Decimal constructor instead of float instances if it's important to compute exact answers and not floating point approximations.

Item #70: Profile before optimizing.

  • It's important to profile Python programs before optimizing because the sources of slowdowns are often obscure.
  • Use the cProfile module instead of the profile module because it provides more accurate profiling information. The Profile object's runcall method provides everything you need to profile a tree of function calls in isolation.
  • The Stats object lets you select and print the subset of profiling information you need to see to understand your program's performance.

Munkres §52: The Fundamental Group


Claim: If $A$ is star convex, then $A$ is simply connected.

Proof: $A$ is clearly path-connected. Let $a\in A$ be the star point, let $\alpha$ and $\beta$ be two loops in $A$, and define $F:I\times I\to A$ by
$$(x,t)\mapsto\begin{cases}(1-2t)\alpha(x)+2ta&t\leq1/2\\2(1-t)a+(2t-1)\beta(x)&t>1/2\end{cases}.$$
Then $F$ is a path homotopy between $\alpha$ and $\beta$, implying that $\pi_1(A,a)=0$.
$$\tag*{$\blacksquare$}$$
Claim: If $\gamma=\alpha*\beta$, then $\widehat\gamma=\widehat\beta\circ\widehat\alpha$.

Proof:
$$\begin{align}\widehat\gamma([f])&=[\overline{\alpha*\beta}]*[f]*[\alpha*\beta]\\&=[\overline\beta*\overline\alpha]*[f]*[\alpha*\beta]\\&=[\overline\beta]*[\overline\alpha]*[f]*[\alpha]*[\beta]\\&=[\overline\beta]*\widehat\alpha([f])*[\beta]\\&=\widehat\beta(\widehat\alpha([f]))\\&=(\widehat\beta\circ\widehat\alpha)([f]).\tag*{$\blacksquare$}\end{align}$$
Claim: $\pi_1(X,x_0)$ is abelian if and only if $\widehat\alpha=\widehat\beta$ for all paths $\alpha,\beta$ from $x_0$ to $x_1$, where $X$ is path-connected.

Proof: Suppose that $\pi_1(X,x_0)$ is abelian and recall that $\pi_1(X,x_1)$ is isomorphic to it. Then
$$\begin{align}\widehat\alpha([f])&=[\overline{\alpha}]*[f]*[\alpha]\\&=[\overline{\alpha}]*[f]*[\beta]*[\overline\beta*\alpha]\\&=[\overline\beta*\alpha]*[\overline{\alpha}]*[f]*[\beta]\\&=[\overline\beta]*[f]*[\beta]\\&=\widehat\beta([f]).\end{align}$$
Conversely, suppose that $\widehat\alpha=\widehat\beta$ for all paths $\alpha,\beta$ from $x_0$ to $x_1$, let $\alpha$ be a path from $x_0$ to $x_1$, let $f$ and $g$ be loops based at $x_0$, and note that $\gamma:=f*\alpha$ is a path from $x_0$ to $x_1$. Then
$$\begin{align}\widehat\gamma([g])&=[\overline\gamma]*[g]*[\gamma]\\&=[\overline{f*\alpha}]*[g]*[f*\alpha]\\&=[\overline\alpha*\overline f]*[g]*[f*\alpha]\\&=[\overline\alpha]*[\overline f*g*f]*[\alpha]\\&=[\overline\alpha]*[g]*[\alpha]\\&=\widehat\alpha([g])\end{align}$$
implies that $[\overline f*g*f]=[g]$, which in turn implies that $[g]*[f]=[f]*[g]$.
$$\tag*{$\blacksquare$}$$
Claim: If $a\in A\subset X$ and $r$ is a retraction of $X$ onto $A$, then
$$r_*:\pi_1(X,a)\to\pi_1(A,a)$$
is surjective.

Proof:
$$r_*\circ\iota_*=(r\circ\iota)_*=\mathrm{id}_{\pi_1(A,a)},$$
where $\iota:A\hookrightarrow X$, implies that $r$ has a right inverse, which in turn implies that it is surjective.
$$\tag*{$\blacksquare$}$$
Claim: If $a\in A\subset\mathbb R^n$, $y\in Y$, $h:\pi_1(A,a)\to\pi_1(Y,y)$, and $h$ is extendable to a continuous $\widetilde h:\mathbb R^n\to Y$, then $h_*$ is trivial.

Proof:
$$h=\widetilde h\circ\iota\implies h_*=\widetilde h_*\circ\iota_*,$$
where $\iota:A\hookrightarrow\mathbb R^n$. However, the domain of $\widetilde h_*$ is $\pi_1(\mathbb R^n,a)=0$.
$$\tag*{$\blacksquare$}$$
Claim: If $X$ is path-connected, $h:X\to Y$ is continuous, $h(x_0)=y_0$, $h(x_1)=y_1$, $\alpha$ is a path from $x_0$ to $x_1$, and $\beta=h\circ\alpha$, then
$$\widehat\beta\circ(h_{x_0})_*=(h_{x_1})_*\circ\widehat\alpha.$$
This is equivalent to saying that $h_*$ is independent of base point up to isomorphism.

Proof:
$$\begin{align}\widehat\beta\circ(h_{x_0})_*([f])&=[\overline\beta]*(h_{x_0})_*([f])*[\beta]\\&=[h\circ\overline\alpha]*[h\circ f]*[h\circ\alpha]\\&=(h_{x_1})_*([\overline\alpha]*[f]*[\alpha])\\&=(h_{x_1})_*\circ\widehat\alpha.\end{align}$$
$$\tag*{$\blacksquare$}$$
Let $G$ be a topological group with operation $\cdot$ and identity $x_0$, let $\Omega(G,x_0)$ be the set of loops in $G$ based at $x_0$, and let
$$(f\otimes g)(s):=f(s)\cdot g(s)$$
for all $f,g\in\Omega(G,x_0)$.

Note that $\Omega(G,x_0)$ equipped with $\otimes$ is a group with identity $x_0(s)=x_0$.

Claim$\otimes$ induces a group operation $\otimes$ on $\pi_1(G,x_0)$.

Proof: Let $[f]\otimes[g]:=[f\otimes g]$ and note that it is well-defined since $(s,t)\mapsto F(s,t)\cdot G(s,t)$ is a homotopy between $s\mapsto F(s,0)\otimes G(s,0)$ and $s\mapsto F(s,1)\otimes G(s,1)$.
$$\tag*{$\blacksquare$}$$
Claim: $*$ and $\otimes$ on $\pi_1(G,x_0)$ are the same.

Proof: Note that
$$\begin{align}[f]\otimes[g]&=[f*e_{x_0}]\otimes[e_{x_0}*g]\\&=[(f*e_{x_0})\otimes(e_{x_0}*g)]\\&=[f*g]\\&=[f]*[g]\end{align}$$
because
$$\begin{align}((f*e_{x_0})\otimes(e_{x_0}*g))(s)&=(f*e_{x_0})(s)\cdot(e_{x_0}*g)(s)\\&=\begin{cases}f(s)&s\leq1/2\\g(s)&s>1/2\\\end{cases}\\&=(f*g)(s).\end{align}$$
$$\tag*{$\blacksquare$}$$
Claim: $\pi_1(G,x_0)$ is abelian.

Proof:
$$\begin{align}[f]*[g]&=[f]\otimes[g]\\&=[e_{x_0}*f]\otimes[g*e_{x_0}]\\&=[(e_{x_0}*f)\otimes(g*e_{x_0})]\\&=[g*f]\\&=[g]*[f].\end{align}$$
$$\tag*{$\blacksquare$}$$

Examples of Banach Algebras

Let
$$\ell^\infty(\Omega):=\{f:\Omega\to\mathbb C:\|f\|_\infty<\infty\},$$
where $\Omega$ is a set and
$$\|f\|_\infty:=\sup_{\omega\in \Omega}|f(\omega)|.$$
Claim: $\ell^\infty(\Omega)$ is a unital Banach algebra.
 
Proof: It is clear that that $\ell^\infty(\Omega)$ is unital and an algebra. Let $(f_n)_n$ be Cauchy, let $\epsilon>0$, let $N$ be such that
$$\|f_n-f_m\|_\infty=\sup_{\omega\in\Omega}|f_n(\omega)-f_m(\omega)|<\frac\epsilon2$$
for all $n,m\geq N$, define $f:\Omega\to\mathbb C$ by $\omega\mapsto\lim_nf_n(\omega)$, and note that $f$ is well-defined since $(f_n(\omega))_n$ is Cauchy for all $\omega\in\Omega$. Then
$$\lim_m|f_n(\omega)-f_m(\omega)|=|f_n(\omega)-\lim_mf_m(\omega)|=|f_n(\omega)-f(\omega)|\leq\frac\epsilon2<\epsilon$$
for all $\omega\in\Omega$, meaning that $\|f_n-f\|_\infty<\epsilon$ for all $n\geq N$. Finally,
$$\|f\|_\infty=\|f-f_n+f_n\|_\infty\leq\|f_n-f\|_\infty+\|f_n\|_\infty<\infty.\tag*{$\blacksquare$}$$
Let
$$C_b(\Omega):=\{f\in\ell^\infty(\Omega):f\text{ is continuous}\},$$
where $\Omega$ is a topological space.

Claim: $C_b(\Omega)$ is a unital Banach algebra.

Proof: We show that it is closed: recall that convergence with respect to $\|\cdot\|_\infty$ is equivalent to uniform convergence and that uniformly convergent sequences of continuous functions converge to continuous functions.
$$\tag*{$\blacksquare$}$$
Let
$$C_0(\Omega):=\{f\in C_b(\Omega):f\text{ vanishes at infinity}\},$$
where $\Omega$ is locally compact and Hausdorff.

Recall that $f$ vanishes at infinity if for all $\epsilon>0$, there is $K$ compact such that $|f(\omega)|<\epsilon$ for all $\omega\in K^c$.

Claim: $C_0(\Omega)$ is a Banach algebra.

Proof: We show that it is closed: let $n$ be such that $\|f_n-f\|_\infty<\epsilon/2$ and let $K$ compact be such that $|f_n(\omega)|<\epsilon/2$ for all $\omega\in K^c$. Then
$$|f(\omega)|\leq|f_n(\omega)-f(\omega)|+|f_n(\omega)|<\|f_n-f\|_\infty+\frac\epsilon2<\epsilon$$
for all $\omega\in K^c$.
$$\tag*{$\blacksquare$}$$
$C_0(\Omega)$ is one of the most important examples of a Banach algebra in C*-algebra theory, and it is unital if and only if $\Omega$ is compact, in which case
$$C(\Omega)=C_b(\Omega)=C_0(\Omega).$$
Let $L^\infty(\Omega,\mu)$ be the set of classes of essentially bounded, complex-valued, measurable functions on $\Omega$, where $(\Omega,\mu)$ is a measure space, equipped with the essential supremum norm.

Claim: $L^\infty(\Omega,\mu)$ is a unital Banach algebra.

Let $B_\infty(\Omega):=\{f\in\ell^\infty(\Omega):f\text{ is measurable}\}$, where $\Omega$ is measurable.

Claim: $B_\infty(\Omega)$ is a unital Banach algebra.

Proof: Recall that a point-wise convergent sequence of measurable functions converges to a measurable function.
$$\tag*{$\blacksquare$}$$
Let $A$ be the set of continuous, complex-valued functions on the closed unit disc $D\subset\mathbb C$ that are holomorphic on the interior $D^{\mathrm{o}}$ of $D$.

Claim: $A$ is a unital Banach algebra called the disc algebra.

Proof. If $(f_n)_n$ converges to $f$ with respect to $\|\cdot\|_\infty$, then it converges uniformly and $f$ is thus continuous. Moreover,
$$0=\lim_n\oint_\gamma f_n(z)\,\text{d}z=\oint_\gamma\lim_nf_n(z)\,\text{d}z=\oint_\gamma f(z)\,\text{d}z$$
for all closed, piece-wise $C^1$ curves $\gamma$ on $D^{\mathrm{o}}$. Therefore, by Morera's theorem, $f$ is holomorphic on $D^{\mathrm{o}}$.
$$\tag*{$\blacksquare$}$$





Effective Python 61 - 65

Click here for the first post, which contains the context of this series.

Item #61: Know how to port threaded I/O to asyncio.

  • Python provides asynchronous versions of for loops, with statements, generators, comprehensions, and library helper functions that can be used as drop-in replacements in coroutines.
  • The asyncio built-in module makes it straightforward to port existing code that uses threads and blocking I/O over to coroutines and asynchronous I/O.

Item #62: Mix threads and coroutines to ease the transition to asyncio.

  • The awaitable run_in_executor method of the asyncio event loop enables coroutines to run synchronous functions in ThreadPoolExecutor pools. This facilitates top-down migrations to asyncio.
  • The run_until_complete method of the asyncio event loop enables synchronous code to run a coroutine until it finishes. The asyncio.run_coroutine_threadsafe function provides the same functionality across thread boundaries. Together these help with bottom-up migrations to asyncio.

Item #63: Avoid blocking the asyncio event loop to maximize responsiveness.

  • Making system calls in coroutines—including blocking I/O and starting threads—can reduce program responsiveness and increase the perception of latency.
  • Pass the debug=True parameter to asyncio.run in order to detect when certain coroutines are preventing the event loop from reacting quickly.

Item #64: Consider concurrent.futures for true parallelism.

  • Moving CPU bottlenecks to C-extension modules can be an effective way to improve performance while maximizing your investment in Python code. However, doing so has a high cost and may introduce bugs.
  • The multiprocessing module provides powerful tools that can parallelize certain types of Python computation with minimal effort.
  • The power of multiprocessing is best accessed through the concurrent.futures built-in module and its simple ProcessPoolExecutor class.
  • Avoid the advanced (and complicated) parts of the multiprocessing module until you've exhausted all other options.

Item #65: Take advantage of each block in try, except, else, and finally.

  • The try/finally compound statement lets you run cleanup code regardless of whether exceptions were raised in the try block.
  • The else block helps you minimize the amount of code in try blocks and visually distinguish the success case from the try/except blocks.
  • An else block can be used to perform additional actions after a successful try block but before common cleanup in a finally block.
import json
try:
    my_file = open('my_file.json')
except FileNotFoundError as e:
    print(e)
else:
    try:
        data = json.loads(my_file.read())
    except json.decoder.JSONDecodeError as e:
        print(e)
    else:
        # Work with data here.
        pass
    finally:
        my_file.close()