# Styling
from IPython.core.display import HTML
def css_styling():
sheet = './css/custom.css'
styles = open(sheet, "r").read()
return HTML(styles)
css_styling()
Basics Revision
Tuples, mutability
Dictionaries
Classes
__init__
, self
https://ngcm.github.io/summer-academy-2016-basics/basics_B.zip
If you are following this course and do not know how to obtain the above requirements, see Setup Instructions.
# Run this cell before trying examples
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
For
loopsappend
'lists = [10, 12, 14, 16, 18]
print(lists[0]) # Index starts at 0
print(lists[-1]) # Last index at -1
print(lists[0:3]) # Slicing: exclusive of end value
# i.e. get i=(0, 1, .. n-1)
print(lists[3:]) # "slice from i=3 to end"
10 18 [10, 12, 14] [16, 18]
# List construction example
a = []
print(a)
a.append('Hello world')
print(a)
[] ['Hello world']
a.extend([1, 2, 3, 4])
print(a)
a.remove(1) # Remove value 1 from a
print(a)
a.pop(0)
print(a)
['Hello world', 1, 2, 3, 4] ['Hello world', 2, 3, 4] [2, 3, 4]
# All methods
# a. # Tab complete behaviour?
var
is user defined) -
For var in iterable:powers = [0, 1, 2, 3]
for power in powers:
value = 10 ** (power)
print("10 to the power of {} is {}".format(power, value))
10 to the power of 0 is 1 10 to the power of 1 is 10 10 to the power of 2 is 100 10 to the power of 3 is 1000
# Better to use Pythons built in 'range' here:
for i in range(4):
print("10 to the power of {} is {}".format(i, 10**i))
10 to the power of 0 is 1 10 to the power of 1 is 10 10 to the power of 2 is 100 10 to the power of 3 is 1000
x = [i**2 for i in range(10)]
y = [i*10 for i in range(10)]
print(x)
print(y)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
#### How would you create a list from zero to 100 in increments of 5 in one line
def
followed by function name and parameters in a parenthesisreturn output_var(s)
ends the functiondef square_root(x):
"""Useful docstring: Calculates and returns square root of x"""
i = x ** 0.5
return i
x = 10
y = square_root(x)
print('The square root of {} is {}'.format(x, y))
The square root of 10 is 3.1622776601683795
# We can set a default value to the function
def square_root(x=20):
i = x ** 0.5
return i
print(square_root())
4.47213595499958
# Loops, functions and appending
mylist = []
for i in range(1,5):
mylist.append(square_root(i))
print(mylist)
[1.0, 1.4142135623730951, 1.7320508075688772, 2.0]
def update_integer(i):
# attempt to update i (integers) are immutable
i += 1
def update_list_end(arglist):
arglist[-1] = 50 # Lists are mutable: updates args directly!
a = 1
update_integer(a)
print(a)
mylist = [0, 1, 2, 3, 4]
update_list_end(mylist)
print(mylist)
1 [0, 1, 2, 3, 50]
Note above that there is no return
statement required: implicitly this function will return the Python builtin-in value None
.
np.array
import numpy as np
# basic usage: arange, linspace, array ops
x = np.linspace(0, 10, 11) # use 11 points
print(x)
y = np.arange(0, 10, 1) # use step size of 1
print(y)
print('The average of x is', np.average(x))
[ 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.] [0 1 2 3 4 5 6 7 8 9] The average of x is 5.0
M1 = np.array([[2,3],[6,3]])
M2 = np.array([[5,6],[2,9]])
print('M1:')
print(M1)
print('M2:')
print(M2)
M1: [[2 3] [6 3]] M2: [[5 6] [2 9]]
M3 = M1 * M2 # Element-wise multiplication
print(M3, '\n')
M4 = np.dot(M1, M2) # Matrix multiplication
print(M4)
[[10 18] [12 27]] [[16 39] [36 63]]
# Given array [0, np.pi/2., np.pi, 3*np.pi/4.] what would you
# expect passing it to np.sin ????
# live coding show some numpy functions.
x = np.linspace(0, 2*np.pi)
y = np.sin(x)
fig = plt.figure(figsize=(12, 5))
ax = fig.add_subplot(111)
ax.plot(x, y,'o-')
ax.margins(0.1)
ax.set_title('2D plot')
ax.set_xlabel('$x$')
ax.set_ylabel(r'$sin(x)$')
<matplotlib.text.Text at 0x110ccf6a0>
xtick_values = np.linspace(0, 2*np.pi, 5)
xtick_labels = ['$0$', r'$\frac{\pi}{2}$', r'$\pi$', r'$\frac{3\pi}{2}$',
r'$2\pi$']
fig = plt.figure(figsize=(12, 5))
ax = fig.add_subplot(111); ax.plot(x, y,'-o')
ax.set_title('2D plot')
ax.margins(0.1)
ax.set_xlabel('$x$'); ax.set_ylabel(r'$sin(x)$')
ax.set_xticks(xtick_values)
ax.set_xticklabels(xtick_labels, fontsize=25);
x = np.linspace(-1, 1, 101)
y = np.linspace(-1, 1, 101)
X, Y = np.meshgrid(x, y)
Z = np.sin(X + Y)**2
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(12, 5))
ax = fig.add_subplot(111, projection='3d')
surf = ax.plot_surface(X, Y, Z)
ax.set_xlabel(r'$X$')
ax.set_ylabel(r'$Y$')
ax.set_zlabel(r'$Z$')
plt.show()
# Live coding Bay....
# Create a 'Name, Age' Tuple using bracket notation
my_tuple = ('Dave', 42)
print(type(my_tuple))
print(my_tuple)
<class 'tuple'> ('Dave', 42)
# Create Tuple using bracket-less notation
my_tuple2 = 'Bob', 24
print(type(my_tuple2))
print(my_tuple2)
<class 'tuple'> ('Bob', 24)
# Tuple indexing
my_tuple = ('Dave', 42)
print(my_tuple[0])
print(my_tuple[1])
Dave 42
# Could make a list of tuples:
tups = [('Dave', 42), ('Bob', '24')]
# ... and then iterate over it
for tup in tups:
print("{} is {} years old".format(tup[0], tup[1]))
Dave is 42 years old Bob is 24 years old
# Store multiple variables using tuples:
my_tuple = 'Dave', 42
a, b = my_tuple
print('a = {}'.format(a))
print('b = {}'.format(b))
a = Dave b = 42
# Swap Variables using tuples:
b, a = a, b
print('a = {}'.format(a))
print('b = {}'.format(b))
a = 42 b = Dave
# extending or overwriting contents
my_tuple = 'Dave', 42
# my_tuple[0] = 'Steve' # Will give an error
# Sequences: Stick with a list
seq = [] # tuples have no append method, so need a list []
for i in range(10):
seq.append(i**2)
print(seq)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# Or a numpy array:
print(np.arange(10)**2)
[ 0 1 4 9 16 25 36 49 64 81]
# Create a tuple of lists 'a' - can you change the values in
# the lists?
# Live coding Bay....
key
: value
pairsdict
keyword# Using the dict function:
fruit = [('apples', 2), ('bananas', 5), ('pears', 10)]
price_table = dict(fruit)
print(price_table)
{'apples': 2, 'pears': 10, 'bananas': 5}
# Short hand (Arguably neater)
price_table = {'apples': 2, 'pears': 10, 'bananas': 5}
print(price_table)
{'apples': 2, 'pears': 10, 'bananas': 5}
Note: notice that the consistent order on printing of the dictionaries, even though the inputs are reordered. The ordering of hash tables is well defined, but not in a human-intuitive sense. We should therefore treat the data as if it was unordered.
price_table = {'apples': 2, 'bananas': 5, 'pears': 10}
akey = 'apples'
print("The price of {} is {}p".format(akey, price_table[akey]))
The price of apples is 2p
# Iterating over the dictionary will iterate over its keys
price_table = {'apples': 2, 'bananas': 5, 'pears': 10}
for key in price_table:
print("{} cost {}p".format(key, price_table[key]))
apples cost 2p pears cost 10p bananas cost 5p
# Or use the items method:
for key, val in price_table.items():
print("{} cost {}p".format(key, val))
apples cost 2p pears cost 10p bananas cost 5p
# I don't like pears, so let's buy apples and bananas
shopping_list = [('apples', 50), ('bananas', 20)]
total = 0
for item, quantity in shopping_list:
price = price_table[item]
print('Adding {} {} at {}p each'.format(quantity, item, price))
total += price * quantity
print(total)
Adding 50 apples at 2p each Adding 20 bananas at 5p each 200
# Hoping for ordered data:
alpha_num = {'a': 0, 'b': 1, 'c': 2}
for i, key in enumerate(alpha_num.keys()):
print("{} has a value of {}".format(key, i)) # This is wrong
b has a value of 0 c has a value of 1 a has a value of 2
mydict = {'a':1, 'b':2, 'c':3}
def myFunc(a,b,c):
return a*2, b*2, c*2
myFunc(**mydict)
(2, 4, 6)
# Live coding Bay....
"Object-oriented programming (OOP) refers to a type of computer programming in which programmers define not only the data type of a data structure, but also the types of operations (functions) that can be applied to the data structure."
Source: Webopedia
C++
and Python designed for OOPC
and Fortran
Inheritance
Encapsulation
Abstraction
Polymorphism
Note: Encapsulation is sometimes also used in OOP to describe the grouping of data with methods. It is however more common for texts to use it to describe the hiding of data as will be done here.
Useful explanations of these concepts for Python can also be found here
with open('data/structured_data.txt', 'w') as f:
f.write('#Name Height Weight\n')
f.write('John 180 80.5\n')
f.write('Paul 172 75.1\n')
f.write('George 185 78.6\n')
f.write('Ringo 170 76.5\n')
# Notice that the argument is a list of tuples
dt = np.dtype([('Name', np.str_, 16), ('Height', np.int32),
('Weight', np.float64)])
data = np.loadtxt('data/structured_data.txt', dtype=dt)
print(data)
[("b'John'", 180, 80.5) ("b'Paul'", 172, 75.1) ("b'George'", 185, 78.6) ("b'Ringo'", 170, 76.5)]
print(data['Name'])
print("{} has weight {}".format(data[0]['Name'], data[0]['Weight']))
["b'John'" "b'Paul'" "b'George'" "b'Ringo'"] b'John' has weight 80.5
# Live coding Bay....
dtypes
¶# Numpy arrays are classes
import numpy as np
a = np.array([0, 1, 6, 8, 12])
print(a.__class__)
print(type(a))
<class 'numpy.ndarray'> <class 'numpy.ndarray'>
# We want to operate on the array: try numpy cumulative sum function
print(np.cumsum(a))
[ 0 1 7 15 27]
# np.cumsum('helloworld') # Should we expect this to work?
We only know what a cumulative sum means for a narrow scope of data types
Group them together with an object!
# cumsum is a method belonging to a
a.cumsum()
array([ 0, 1, 7, 15, 27])
class ClassName(object)
class Greeter(object):
def hello(self): # Method (more on 'self' later)
print("Hello World")
agreeter = Greeter() # 'Instantiate' the class
print(agreeter)
# agreeter. # Tab complete?
<__main__.Greeter object at 0x111e57f28>
There's a few things here which I haven't introduced, but all will become clear in the remainder of this workshop.
# Note that we don't pass an argument to hello!
agreeter.hello()
Hello World
self
¶__init__
class methodself
= instanceNote: Passing of self
is done implicitly in other languages e.g. C++ and Java, and proponents of those languages may argue that this is better. "Explicit is better than implicit" is simply the python way.
Class
is like a type__init__
is not technically Construction (see: C++)__new__
'constructs' the instance before __init__
__init__
then initialises the contentMore info: The Constructor creates the instance, and the Initialiser Initialises its contents. Most languages e.g. C++ refer to these interchangably and perform these steps together, however the new style classes in Python splits the process.
The difference is quite fine, and for most purposes we do not need to redefine the behaviour of __new__
. This is discussed in several Stack Overflow threads, e.g.
class A(object):
def __init__(self):
print("Hello")
a_instance = A()
print(type(a_instance))
Hello <class '__main__.A'>
self.attribute = value
class Container(object):
"""Simple container which stores an array as an instance attribute
and an instance method"""
def __init__(self, N):
self.data = np.linspace(0, 1, N)
def plot(self):
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(self.data, 'bx')
mydata = Container(11) # 11 is passed as 'N' to __init__
print(mydata.__dict__) # __dict__ is where the attr: value
# pairs are stored!
mydata.plot()
{'data': array([ 0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ])}
# Code solution here:
class Container(object):
data = np.linspace(0, 1, 5) # class attribute
def __init__(self):
pass
a, b = Container(), Container()
print(a.data)
print(b.data)
[ 0. 0.25 0.5 0.75 1. ] [ 0. 0.25 0.5 0.75 1. ]
a.data = 0 # Creates INSTANCE attribute
Container.data = 100 # Overwrites CLASS attribute
print(a.data)
print(b.data)
0 100
Note: There's a couple of things going on in this example which are worth elaborating on. By specifying ClassName.attribute
, in this case Container.data = 100
we've overwritten the value of data
that EVERY instance of the Container
class will access. Hence printing b.data
gives the expected result.
By setting a.data
at the same time, we have set an instance attribute, which is given priority and called first even though we overwrote the class attribute after assigning this.
This could create a hard to track bug. To avoid it:
instance.attr
unless you really know what you're doing (even then, it's probably better and more readable to make it an instance attribute)For a really in depth explanation of class vs instance attributes, see either of the following links:
class Container(object):
def __init__(self, N):
self.data = np.linspace(0, 1, N)
def print_data(self):
print(self.data)
a = Container(11)
a.print_data() # <<< This is better
Container.print_data(a)
[ 0. 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1. ] [ 0. 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1. ]
_
' or '__
'Single underscore
Double underscore
instance._ClassName__Attribute
class Fruit(object):
def __init__(self):
self._hasjuice = True
def juice(self):
if not self.isfull(): raise ValueError('No juice!')
self._hasjuice = False
def isfull(self):
return self._hasjuice
orange = Fruit()
print(orange.isfull())
orange.juice()
print(orange.isfull())
True False
# orange. # tab completion behaviour?
# orange._ # tab completion behaviour now?
orange._hasjuice = True # bad!
orange.isfull()
True
class Fruit(object):
def __init__(self):
self.__hasjuice = True
def juice(self):
if not self.isfull(): raise ValueError('No juice!')
self.__hasjuice = False
def isfull(self):
return self.__hasjuice
apple = Fruit()
# apple._ # tab completion behaviour?
apple.juice()
apple._Fruit__hasjuice = False # Definitely bad!
apple.isfull()
False
Note: This behaviour can be over used in Python. Programmers from C++ or Java backgrounds may want to make all data hidden or private
and access the data with 'getter' or 'setter' functions, however it's generally accepted by Python programmers that getters and setters are unnecessary. The Pythonista phrase is "we are all consenting adults here", meaning you should trust the programmer to interact with your classes and they should trust you to document/indicate which parts of the data not to touch unless they know what they're doing (hence the underscore convention). See the top answer on this Stack Overflow thread.
For an entertaining view of encapsulation, see this blog
# Live coding Bay....
Child
/Derived
class inherits from Parent
/Base
class Parent(object):
# Note the base __init__ is overridden in
# Child class
def __init__(self):
pass
def double(self):
return self.data*2
class Child(Parent):
def __init__(self, data):
self.data = data
achild = Child(np.array([0, 1, 5, 10]))
achild.double()
array([ 0, 2, 10, 20])
super
¶class Plottable(object):
def __init__(self, data):
self.data = data
def plot(self, ax):
ax.plot(self.data)
class SinWave(Plottable):
def __init__(self):
super().__init__(
np.sin(np.linspace(0, np.pi*2, 101)))
class CosWave(Plottable):
def __init__(self):
super().__init__(
np.cos(np.linspace(0, np.pi*2, 101)))
fig = plt.figure()
ax = fig.add_subplot(111)
mysin = SinWave(); mycos = CosWave()
mysin.plot(ax); mycos.plot(ax)
Notes:
We didn't need any arguments to super
here as Python 3 allows this
super
requires additional arguments in Python 2, e.g.
super(Class, self).method(args...)
If you were wondering why we should use super().method
instead of BaseClass.method
, other than the convenience of renaming classes, it relates to multiple inheritance which is beyond the scope of this course. If you need to write programs with multiple inheritance (and there are strong arguments against this), you may want to look at this blog for advanced use of super
.
object
& builtin types come with many of these__init__
!dir(object)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
__lt__
:
__str__
All magics can be overridden
__str__
and __lt__
magic methods¶class Wave(object):
def __init__(self, freq):
self.freq = freq
self._data = np.sin(np.linspace(0, np.pi, 101)
* np.pi*2 * freq)
def __str__(self):
"""RETURNS the string for printing"""
return "Wave frequency: {}".format(self.freq)
def __lt__(self, wave2):
return self.freq < wave2.freq
wav_low = Wave(10)
wav_high = Wave(50) # A high frequency wave
print(wav_high)
wav_low < wav_high
Wave frequency: 50
True
# Live coding Bay....
Note: Magic methods are very briefly introduced here. For an extensive overview of magic methods for Python classes, view Rafe Kettlers blog
object
in Python 3object
class OldSyntax:
pass
class NewSyntax(object): # This means 'inherit from object'
pass
print(type(OldSyntax)) # Would give <type 'classobj'>
# in Python 2
print(type(NewSyntax))
<class 'type'> <class 'type'>
object
in Py3Notes: There are other differences affecting classes which we have not included, such as metaclasses and iterator behaviour, but here is a link to a more complete comparison:
Read
the packages you use dailyP.R.Chambers@soton.ac.uk