Miscellaneous

There is a lot of interesting stuff in the Python language which we don’t have time to cover entirely. In this section, we’ll sample some topics which may be useful to know about.

%pylab inline
Populating the interactive namespace from numpy and matplotlib

Ternary Operator

Consider the computation of an absolute value x = abs(a).

If we were to write the expression using if/else statements, we would have

x = 0
if (a > 0):
    x = a
else:
    x = -a

There are a few things that are not very satisfactory about the if/else statements. First is that we have to initialize the variable x in the appropriate scope. The second issue is that it is much more verbose. An alternative is

x = a
if not (a > 0):
    x = -a

This is less verbose, and doesn’t waste the initial assignment of x. However, it might be a bit confusing to read the code, since x is assigned, but perhaps immediately re-assigned.

The ternary operator solves these issues with a compact, readable syntax

x = a if a > 0 else -a

This expression is easy to read in plain English: “x is a if a is greater than zero, otherwise x is -a

If you’re familiar with C, you’ve probably already seen the ternary operator ?. As an example, the above translates to

int x = (a > 0) ? a : -a;

Hash Tables & Dictionaries

You can read a good overview here.

Hash Functions

A hash function is a map from some type to an integer. Not all classes are “hashable”, particularly if data is mutable. Built-in hashable classes can be hashed using hash, or the __hash__() method

x = 1
print(x.__hash__())
print(hash(x))
1
1
x = 1.1
print(x.__hash__())
print(hash(x))
230584300921369601
230584300921369601

The hash function of an integer is the identiy. For non-integer types, the function can be more complex.

print(hash(1 + 4j))
print(hash("hello"))
4000013
-167849732849390864

Hash functions are typically “one-way” functions, meaning that you can’t compute the input from the output. This makes hash functions very useful for things like checking passwords without storing the password itself, or checking that source code has not been altered (why websites often provide MD5 sums with downloads).

Dictionaries

Python dictionaries are implemented as a hash table, which uses hash functions to quickly map keys to values.

__slots__ Magic

When defining a class, we might specify instance attributes

class MyComplex():
    def __init__(self, re, im):
        self.re = re
        self.im = im
        
    def __repr__(self):
        return f"{self.re} + {self.im}i"
        
x = MyComplex(1,2)
x
1 + 2i

However, we can add attributes arbitarily

x.attr = 5

By defautlt, instance atrributes and their values are stored in a dict, which you can access using __dict__

x.__dict__
{'re': 1, 'im': 2, 'attr': 5}

However, if we’re creating many instances of “small” objects, this might not be very efficient because of the (slight) dictionary overhead compared to data structures like a list or tuple.

The way around this is to use __slots__, which allows us to specify what attributes are allowed in an instance

class MyComplex2():
    __slots__ = ('re', 'im')
    def __init__(self, re, im):
        self.re = re
        self.im = im
    
    def __repr__(self):
        return f"{self.re} + {self.im}i"
        
x = MyComplex2(1,2)
x
1 + 2i

Now we can’t set arbitrary attributes

x.attr = 5
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-9-5577da64b395> in <module>
----> 1 x.attr = 5

AttributeError: 'MyComplex2' object has no attribute 'attr'
n = 200000

a = []
for i in range(n):
    a.append(MyComplex(1,2))
    
b = []
for i in range(n):
    b.append(MyComplex2(1,2))
sum([x.re for x in a ])
sum([x.re for x in b])


%timeit sum([x.re for x in a ])

%timeit sum([x.re for x in b])
22.6 ms ± 1.01 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
20.6 ms ± 791 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

We see the version of the data structure that uses __slots__ is a bit faster to access.

Exceptions

try, except, final keywords, as well as exceptions

Coroutines

See this slide deck

Type Hints

Type Hints are not enforced by the interpreter, but can be useful for documentation, and code analysis libraries

Other

b'abc' # byte string - ascii only
b'abc'