CptS 481 - Python Software Construction

Unit 20: Advanced Python

In [12]:
from IPython.display import HTML, Image
HTML(open("notes.css", "r").read())
Out[12]:

Here are some relatively new Python features you may find useful.

Assignment Expressions

In Python, the "=" is used for the assignment statement:

In [2]:
x = 2 + 2

but you want to assign a value and use it in an expression at the same time, you can't:

In [14]:
# uncomment to see exception
#if x = 2 + 2:
#    print(x)

The ":=" operator, however, lets you do this:

In [15]:
if x := 2 + 2:
    print(x)
4

(See if this allows you to define new variables within your spreadsheet!)

Type Annotations

Python does not require type declarations. However, it does permit type annotations. We touched on these when we talked about documentation:

In [18]:
%%file demos/d0_annotation/polar.py
"""
The ``polar`` Module
"""
from math import sin, cos, atan2, pi

__version__ = "1.1"

class Polar:
    """
    specifies a 2D position or velocity in polar coordinates

    This class represents a 2D position or velocity measured in polar
    coordinates and defined by its `distance` (or speed) and an angle
    (`bearing`) (in radians) from the positive y ('North') direction.

    We follow the conventions of navigation, not mathematics, so 0
    degrees is North and bearings increase in a clockwise fashion.
    """

    DEGREES_TO_RADIANS = pi / 180
    "multiplicative constant to convert degrees to radians"

    #: multiplicative constant to convert radians to degrees
    RADIANS_TO_DEGREES = 180 / pi

    def __init__(self,
                 distance: float = 0.0 ,
                 bearing: float = 0.0) -> None:
        """
        specifies a polar point by `distance` and `bearing`
        """
        self.distance = distance
        self.bearing = bearing
        
    def __add__(self, other: 'Polar') -> 'Polar':
        """
        returns the result of adding two Polar
        """
        (x0, y0) = self.rect()
        (x1, y1) = other.rect()
        dx = x1 + x0
        dy = y1 + y0
        mag = (dx**2 + dy**2)**0.5
        return Polar(mag, atan2(dy, dx) if mag > 0 else 0)

    def __repr__(self) -> str:
        """
        returns a 'lossless' string representation of a PolarCoordinate
        """
        return "Polar({}, {})".format(self.distance, self.bearing)

    def rect(self) -> (float, float):
        """
        returns the rectangular (Cartesian) coords as an (x, y) tuple
        """
        return (self.distance*sin(self.bearing),
                self.distance*cos(self.bearing))

if __name__ == '__main__':
    # test1: with a 3-4-5 right triangle
    a = Polar(3, 0)
    print('  a:', a)
    b = Polar(4, pi/2)
    print('  b:', b)
    print('a+b:', a+b)

    # test2: zero relative distance
    c = Polar(2, Polar.DEGREES_TO_RADIANS * 30)
    print('  c:', c)
    d = Polar(2, Polar.DEGREES_TO_RADIANS * 210)
    print('  d:', d)
    print('c+d:', c+d)
Overwriting demos/d0_annotation/polar.py

Note how we insert annotations into the code (more than the original).

In [19]:
%%sh
cd demos/d0_annotation
make clean # to force rebuild for demo purposes
make html
chromium _build/html/index.html
Removing everything under '_build'...
Running Sphinx v3.2.1
making output directory... done
building [mo]: targets for 0 po files that are out of date
building [html]: targets for 1 source files that are out of date
updating environment: [new config] 1 added, 0 changed, 0 removed
reading sources... [100%] index

looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
writing output... [100%] index

generating indices...  genindex py-modindexdone
writing additional pages...  searchdone
copying static files... ... done
copying extra files... done
dumping search index in English (code: en)... done
dumping object inventory... done
build succeeded.

The HTML pages are in _build/html.
(chrome:445676): dbind-WARNING **: 13:12:19.177: Couldn't register with accessibility bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
[445676:445805:1203/131238.523116:ERROR:udev_watcher.cc(97)] Failed to begin udev enumeration.

You can also add annotations to assignment statements:

In [20]:
x:float = 3.4**2

This might help code readability, but it doesn't affect documentation. So far, we've only used annotations for documentation, but lately Python developers have gone a little farther...

Type-Checked Python

Here's a talk on the subject from PyCon 2018. (Other videos recommended.)

Namedtuples

These are a subset of tuples whose members have names. They borrow from classes and tuples and take the place of some of what people use dictionaries for.

In [22]:
from collections import namedtuple

Student = namedtuple('Student', ['firstName', 'lastName', 'year'])
jcleese = Student('John', 'Cleese', 2020)
jcleese
Out[22]:
Student(firstName='John', lastName='Cleese', year=2020)
In [10]:
for elem in jcleese:
    print(elem)
John
Cleese
2020
In [24]:
jcleese.firstName
Out[24]:
'John'

Dataclasses

Here's another talk from PyCon 2018 on another useful tool for big data that takes namedtuples a step further.

Virtual Environments

Python virtual environments allow you to install multiple versions of Python and (for instance) experimental versions of modules in a way that isolates them from the rest of your system. It's like a container (e.g. "docker"), but not quite so isolated.

TBD

PyPy

PyPy provides a "just in time" compiler (not interpreter) for Python code. (Java has had one for many years.)

TBD

In [ ]: