This notebook was put together by Morgan Fouesneau for UW's [Astro 599](http://www.astro.washington.edu/users/vanderplas/Astr599/) course. Source and license info is on [GitHub](https://github.com/jakevdp/2013_fall_ASTR599/).

Object-oriented programming: Principles in 1h by Examples

-- M. Fouesneau [ morgan.fouesneau@astro.washington.edu ]

Astro599 ( http://www.astro.washington.edu/users/vanderplas/Astr599/schedule )

Prezi presentation is available at: http://bit.ly/18FylbC


In [1]:
# make sure pylab runs inline when using the notebook
%pylab inline  
import numpy as np
import pylab as plt


Populating the interactive namespace from numpy and matplotlib

In [2]:
def ezrc(fontSize=22., lineWidth=2., labelSize=None, tickmajorsize=10, tickminorsize=5):
    """
    slides - Define params to make pretty fig for slides
    """
    from pylab import rc, rcParams
    if labelSize is None:
        labelSize = fontSize + 5
    rc('figure', figsize=(9, 7))
    #rc('figure', figsize=(8, 6))
    rc('lines', linewidth=lineWidth)
    rcParams['grid.linewidth'] = lineWidth
    rc('font', size=fontSize, family='serif', weight='small')
    rc('axes', linewidth=lineWidth, labelsize=labelSize)
    rc('legend', borderpad=0.1, markerscale=1., fancybox=False)
    #rc('text', usetex=True)
    rc('image', aspect='auto')
    #rc('ps', useafm=True, fonttype=3)
    rcParams['xtick.major.size'] = tickmajorsize
    rcParams['xtick.minor.size'] = tickminorsize
    rcParams['ytick.major.size'] = tickmajorsize
    rcParams['ytick.minor.size'] = tickminorsize
    
ezrc(20) # making figures cleaner for slides

In python All is objects

Text is object


In [3]:
s = 'some text'

In [4]:
s.upper()


Out[4]:
'SOME TEXT'

Modules are objects


In [5]:
import numpy as np
np


Out[5]:
<module 'numpy' from '/Users/jakevdp/anaconda/python.app/Contents/lib/python2.7/site-packages/numpy/__init__.pyc'>

In [6]:
type(np)


Out[6]:
module

In [7]:
np.__name__


Out[7]:
'numpy'

In [8]:
np.__class__


Out[8]:
module

In [9]:
print np.__dict__.keys()   # we'll see __dict__ later


['disp', 'union1d', 'all', 'issubsctype', 'savez', 'atleast_2d', 'restoredot', 'ptp', 'unicode_', 'ix_', 'mirr', 'blackman', 'FLOATING_POINT_SUPPORT', 'busdaycalendar', 'pkgload', 'void', 'unicode0', 'ERR_RAISE', 'void0', 'tri', 'diag_indices', 'array_equal', 'fmod', 'True_', 'indices', 'loads', 'round', 'set_numeric_ops', 'pmt', '_mat', 'cosh', 'object0', 'rate', 'FPE_OVERFLOW', 'index_exp', 'append', 'compat', 'nanargmax', 'hstack', 'typename', 'diag', 'rollaxis', 'ERR_WARN', 'polyfit', 'version', 'memmap', 'nan_to_num', 'complex64', 'fmax', 'spacing', 'sinh', '__git_revision__', 'PackageLoader', 'sinc', 'trunc', 'vstack', 'ERR_PRINT', 'asscalar', 'less_equal', 'BUFSIZE', 'object_', 'divide', 'csingle', 'dtype', 'unsignedinteger', 'fastCopyAndTranspose', 'bitwise_and', 'uintc', 'select', 'deg2rad', 'bytes_', 'eye', 'kron', 'newbuffer', 'negative', 'busday_offset', 'mintypecode', 'MAXDIMS', 'sort', 'einsum', 'uint0', 'zeros_like', 'int_asbuffer', 'uint8', 'chararray', 'linspace', 'resize', 'uint64', 'ma', 'true_divide', 'Inf', 'finfo', 'triu_indices', 'infty', 'add_newdoc', 'seterrcall', 'logical_or', 'minimum', 'WRAP', 'tan', 'absolute', 'array_repr', 'get_array_wrap', 'polymul', 'tile', 'array_str', 'setdiff1d', 'sin', 'longlong', 'product', 'int16', 'str_', 'mat', 'fv', 'max', 'asanyarray', 'uint', 'npv', 'logaddexp', 'flatnonzero', 'short', 'correlate', 'fromstring', 'left_shift', 'searchsorted', 'int64', 'may_share_memory', 'dsplit', 'intersect1d', 'can_cast', 'ppmt', 'show_config', 'cumsum', 'roots', 'outer', 'CLIP', 'fix', 'busday_count', 'timedelta64', 'degrees', 'choose', 'FPE_INVALID', 'recfromcsv', 'fill_diagonal', 'empty_like', 'logaddexp2', 'greater', 'histogram2d', 'polyint', 'rank', 'datetime64', 'complexfloating', 'ndindex', 'ctypeslib', 'PZERO', 'isfortran', 'asfarray', 'radians', 'fliplr', 'alen', 'recarray', 'modf', 'mean', 'square', 'ogrid', 'nanargmin', 'r_', 'diag_indices_from', 'hanning', 's_', 'allclose', 'extract', 'float16', 'ulonglong', 'matrix', 'asarray', 'poly1d', 'promote_types', 'rec', 'datetime_as_string', 'uint32', 'math', 'log2', '__builtins__', 'cumproduct', 'diagonal', 'atleast_1d', 'meshgrid', 'transpose', 'column_stack', 'put', 'byte', 'remainder', 'row_stack', 'expm1', 'nper', 'ndfromtxt', 'place', 'DataSource', 'newaxis', 'arccos', 'signedinteger', 'ndim', 'rint', 'number', 'arctan2', 'little_endian', 'ldexp', 'lookfor', 'array', 'vsplit', 'common_type', 'size', 'logical_xor', 'geterrcall', 'sometrue', 'exp2', 'bool8', 'msort', 'alltrue', 'zeros', 'False_', '__NUMPY_SETUP__', 'nansum', 'bool_', 'inexact', 'broadcast', 'copyto', 'amin', 'arctanh', 'typecodes', 'rot90', 'savetxt', 'sign', 'sctypes', 'std', 'not_equal', 'fromfunction', 'tril_indices_from', '__config__', 'double', 'require', 'typeNA', 'str', 'getbuffer', 'abs', 'clip', 'savez_compressed', 'frompyfunc', 'triu_indices_from', 'conjugate', 'alterdot', 'asfortranarray', 'binary_repr', 'angle', 'lib', 'min', 'unwrap', 'apply_over_axes', 'ERR_LOG', 'right_shift', 'take', 'get_numarray_include', 'trace', 'warnings', 'any', 'who', 'compress', 'histogramdd', 'issubclass_', 'multiply', 'mask_indices', 'amax', 'logical_not', 'average', 'nan', 'nbytes', 'exp', 'result_type', 'dot', 'maximum_sctype', 'longfloat', 'random', 'setxor1d', 'copy', 'FPE_UNDERFLOW', 'frexp', 'errstate', 'nanmin', 'swapaxes', 'SHIFT_OVERFLOW', 'complex256', 'fft', 'digitize', '__file__', 'NZERO', 'ceil', 'ones', 'add_newdoc_ufunc', 'deprecate', 'median', 'geterr', 'convolve', 'isreal', 'where', 'isfinite', 'SHIFT_UNDERFLOW', 'MachAr', 'argmax', 'testing', 'deprecate_with_doc', 'polyder', 'rad2deg', 'isnan', '__all__', 'irr', 'sctypeDict', 'NINF', 'min_scalar_type', 'count_nonzero', 'sort_complex', 'nested_iters', 'concatenate', 'ERR_DEFAULT2', 'vdot', 'bincount', 'copysign', 'array2string', 'corrcoef', 'fromregex', 'vectorize', 'set_printoptions', 'trim_zeros', 'unravel_index', 'cos', 'float64', 'arccosh', 'ushort', 'equal', 'cumprod', 'float_', 'vander', 'geterrobj', 'load', 'fromiter', 'poly', 'bitwise_or', 'polynomial', 'diff', 'iterable', 'array_split', 'get_include', 'pv', 'tensordot', 'piecewise', 'invert', 'UFUNC_PYVALS_NAME', 'SHIFT_INVALID', 'ubyte', 'c_', 'flexible', 'pi', '__doc__', 'empty', 'find_common_type', 'isposinf', 'arcsin', 'sctypeNA', 'imag', 'sctype2char', 'singlecomplex', 'SHIFT_DIVIDEBYZERO', 'matrixlib', 'apply_along_axis', 'reciprocal', 'tanh', 'dstack', 'cov', 'cast', 'logspace', 'packbits', 'issctype', 'mgrid', 'longdouble', 'signbit', 'conj', 'asmatrix', 'inf', 'flatiter', 'bitwise_xor', 'fabs', 'generic', 'reshape', 'NaN', 'cross', 'sqrt', '__package__', 'longcomplex', 'complex', 'pad', 'split', 'floor_divide', '__version__', 'format_parser', 'nextafter', 'polyval', 'flipud', 'i0', 'iscomplexobj', 'mafromtxt', 'bartlett', 'polydiv', 'identity', 'safe_eval', 'greater_equal', 'floor', 'trapz', 'PINF', 'recfromtxt', 'add_newdocs', 'RankWarning', 'ascontiguousarray', 'less', 'putmask', 'UFUNC_BUFSIZE_DEFAULT', 'unicode', 'half', 'NAN', 'typeDict', '__path__', 'shape', 'setbufsize', 'cfloat', 'RAISE', 'isscalar', 'character', 'bench', 'source', 'add', 'uint16', 'bool', 'ufunc', 'save', 'ravel', 'float32', 'real', 'int32', 'tril_indices', 'around', 'lexsort', 'complex_', 'ComplexWarning', 'ipmt', '_import_tools', 'atleast_3d', 'isneginf', 'integer', 'unique', 'mod', 'insert', 'bitwise_not', 'getbufsize', 'array_equiv', 'get_printoptions', 'asarray_chkfinite', 'in1d', 'interp', 'hypot', 'logical_and', 'arange', 'diagflat', 'float128', 'byte_bounds', 'nonzero', 'kaiser', 'polysub', 'fromfile', 'prod', 'nanmax', 'core', 'object', 'seterrobj', 'power', 'nditer', 'percentile', 'FPE_DIVIDEBYZERO', '__name__', 'subtract', 'frombuffer', 'iscomplex', 'add_docstring', 'argsort', 'fmin', 'ones_like', 'is_busday', 'arcsinh', 'intc', 'float', 'ndenumerate', 'intp', 'unpackbits', 'Infinity', 'log', 'cdouble', 'complex128', 'long', 'round_', 'broadcast_arrays', 'inner', 'var', 'int_', 'log10', 'uintp', 'linalg', 'histogram', 'issubdtype', 'int0', 'squeeze', 'int8', 'info', 'seterr', 'argmin', 'genfromtxt', 'maximum', 'record', 'obj2sctype', 'clongdouble', 'sum', 'isrealobj', 'log1p', 'delete', 'tril', 'int', 'ediff1d', 'char', 'single', 'loadtxt', 'ScalarType', 'triu', 'floating', 'expand_dims', 'Tester', 'polyadd', 'ERR_IGNORE', 'emath', 'arctan', 'bmat', 'isclose', 'ERR_DEFAULT', 'test', 'roll', 'string0', 'compare_chararrays', 'iinfo', 'real_if_close', 'repeat', 'hamming', 'ALLOW_THREADS', 'ravel_multi_index', 'string_', 'isinf', 'ndarray', 'e', 'ERR_CALL', 'datetime_data', 'clongfloat', 'hsplit', 'gradient', 'base_repr', 'argwhere', 'set_string_function']

Matplotlib of course


In [10]:
import pylab as plt
plt.plot(range(10))


Out[10]:
[<matplotlib.lines.Line2D at 0x105ee1d10>]

[<matplotlib.lines.Line2D at 0x30c3e90>]

plot returns an instance of a Line2D object


In [11]:
import pylab as plt
r = []
r.append( plt.plot(range(10)) )
r.append( plt.xlabel('measured') )
r.append( plt.ylabel('calculated') )
r.append( plt.title('Measured vs. calculated') )
r.append( plt.grid(True) )
print r


[[<matplotlib.lines.Line2D object at 0x10615e750>], <matplotlib.text.Text object at 0x102302910>, <matplotlib.text.Text object at 0x106148e50>, <matplotlib.text.Text object at 0x106151810>, None]

In [12]:
import pylab as plt
# ------- make a figure ----------------------------
plt.plot(range(10))
plt.grid(True)

# ------- access objects and change them ------------
ax = plt.gca()   # gca == get current axis
line = ax.lines[0]  
line.set_marker('o')
line.set_markersize(20)
line.set_linewidth(2)
plt.setp(line, color='g')  # set property

#change the figure style
plt.tick_params(which='both', width=2)
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
for k, sk in ax.spines.items():
    if k in ('top', 'right'):
        sk.set_color('None')     
    else:
        sk.set_position(('outward', 20))
        
# add labels
plt.xlabel('measured') 
plt.ylabel('calculated') 
plt.title('Measured vs. calculated')


Out[12]:
<matplotlib.text.Text at 0x106191390>

Let's make a class Vector

The simplest form of class definition looks like this:


In [13]:
class DummyClass:
    pass

Class objects support two kinds of operations: attribute references and methods.

Attribute references use the standard syntax used for all attribute references in Python: obj.name.

So, a class definition looked like this:


In [14]:
class DummyClass:
    """A simple example class"""
    i = 12345
    def f(self):
        return 'hello world'

then DummyClass.i and DummyClass.f are valid attribute references, returning an integer and a function object, respectively.

First exercise, make a vector class:

attributes

  • x, y, z: 3d coordinates

methods

  • sum
  • scalar product
  • etc

When a class defines an __init__() method, class instantiation automatically invokes __init__() for the newly-created class instance. So in this example, a new, initialized instance can be obtained by:


In [15]:
class Vector(object):
    def __init__(self):
        pass

Note: Often, the first argument of a method is called self. This is nothing more than a convention: the name self has absolutely no special meaning to Python.

Note, however, that by not following the convention your code may be less readable to other Python programmers, and it is also conceivable that a class browser program might be written that relies upon such a convention.


In [16]:
class Vector(object):   
    def __init__(self, x,y,z): 
        """ constructor """
        pass
        
    def sum(self, p):       
        """ addition: p1+p2 """
        pass
    
    def scalarProd(self,p):
        """ A.B = sum(a[k].b[k]) """
        pass

A solution:


In [ ]:
# your solution here

In [17]:
x = Vector(1, 0, 0)
y = Vector(0, 1, 0)
z = Vector(0, 0, 1)

print "x.y = ", x.scalarProd(y)

print x


x.y =  None
<__main__.Vector object at 0x106165ad0>

object.__repr__(self)

Called by the repr() built-in function and by string conversions (reverse quotes) to compute the "official" string representation of an object.

object.__str__(self)

Called by the str() built-in function and by the print statement to compute the "informal" string representation of an object.

http://docs.python.org/2/reference/datamodel.html


In [ ]:
class Vector(object):   
    
    # your previous solution
    
    def __str__(self):
        # your solution here

In [ ]:
x = Vector(1, 0, 0)
y = Vector(0, 1, 0)
z = Vector(0, 0, 1)

print "x.y = ", x.scalarProd(y)
print x

What about checking that $x^2 + y^2 + 2\cdot x \cdot y = (x + y) ^ 2$


In [ ]:
x.scalarProd(x) + y.scalarProd(y)
x.scalarProd(x) + y.scalarProd(y) + 2 * x.scalarProd(y) == x.sum(y).scalarProd(x.sum(y))

In [ ]:
x ** 2 + y ** 2 + 2 * x * y == (x + y) ** 2

Implement common operators __add__, __sub__, __mul__, ... see http://docs.python.org/2/reference/datamodel.html


In [ ]:
# put the new version of your class here

In [ ]:
x = Vector(1, 0, 0)
y = Vector(0, 1, 0)
z = Vector(0, 0, 1)

xy = x * y
print "x.y = ", xy
print x
x * x + y * y + 2 * x * y == (x + y) ** 2

Bonus exercise

Generalize the class vector and all operations to N-dimensions (tip: you can use numpy arrays)


In [ ]:
#code here

A Teaser about Decorators

This section is a very quick introduction to a very pythonic coding process: decorations

A decorator is just a callable object that takes a function as an argument and returns a replacement function. We’ll start simply and work our way up to useful decorators. In other words: a function that writes a function of a function

Look carefully through our decorator example below. We defined a function named outer that has a single parameter some_func. Inside outer we define an nested function named inner.

Since Python 2.4, you can wrap a function in a decorator by pre-pending the function definition with a decorator name and the @ symbol. In the code samples below above we decorated our function using this syntax, however it’s important to recognize that this is no different than simply replacing the original variable with the return from the wrapper function- Python just adds some syntactic sugar to make what is going on very explicit.


In [18]:
def outer(some_func):
    def inner():
        print "before some_func"
        ret = some_func() # 1
        return ret + 1
    return inner

@outer
def foo():
    return 1

# transparently equivalent to
# foo = outer(foo)

foo()


before some_func
Out[18]:
2

Examples of 2 decorators:

  • timing a function (which also implements the context manager usage)
  • memoization feature: caching output of a function according to its call arguments.

In [19]:
import collections
import functools
from functools import wraps
import time, math, sys

class timeit(object):
	""" Time a block of your code.
    Decorator and context manager
	"""
	def __init__(self, f=None, verbose=True, text=None):
		self.f = f
		if not self.f is None:
			if type(self.f) != str:
				functools.update_wrapper(self, f)
				self.text = self.__name__
			else:
				self.text = f
		else:
			self.text = text or ''
		self.verbose = verbose

	def __enter__(self):
		print "Timing %s" % (self.text)
		self.start = time.time()

	def __exit__(self, exc_type, exc_val, exc_tb):
		self.stop = time.time()
		print self.time

	def __pretty_print(self, t):
		units = [u"s", u"ms",u'us',"ns"]
		scaling = [1, 1e3, 1e6, 1e9]
		if t > 0.0 and t < 1000.0:
			order = min(-int(math.floor(math.log10(t)) // 3), 3)
		elif best >= 1000.0:
			order = 0
		else:
			order = 3
		return "%s Execution time: %.3g %s" % (self.text, t * scaling[order], units[order])

	@property
	def time(self):
		return self.__pretty_print(self.stop-self.start)

	def __call__(self, *args, **kwargs):
		self.start = time.time()
		r = self.f(*args, **kwargs)
		self.stop = time.time()
		if self.verbose:
			print self.time
		return r

In [20]:
class memoize(dict):
	'''Decorator that caches a function's return
    value each time it is called. If called 
    later with the same arguments, the cached 
    value is returned (not reevaluated).
	'''
	def __init__(self, func):
		self.func = func
		functools.update_wrapper(self, func)

	def __getitem__(self, *key):
		return dict.__getitem__(self, key)

	def __missing__(self, key):
		ret = self[key] = self.func(*key)
		return ret

	__call__ = __getitem__

	def __repr__(self):
		'''Return the function's docstring.'''
		return self.func.__doc__

In [21]:
@timeit
@memoize
def f(*args, **kwargs):
    import time
    time.sleep(5)
    return (args, kwargs)

In [22]:
f(10)
f(10)


f Execution time: 5 s
f Execution time: 5.01 us
Out[22]:
((10,), {})

Implementing the IMF as an object

Inheritance

Of course, a language feature would not be worthy of the name “class” without supporting inheritance. The syntax for a derived class definition looks like this:


In [ ]:
class IMF(object):
    mass = [0.1, 120.]

    
class Salpeter(IMF):
    pass

In [ ]:
f = Salpeter()
print f.mass

Execution of a derived class definition proceeds the same as for a base class. When the class object is constructed, the base class is remembered. This is used for resolving attribute references: if a requested attribute is not found in the class, the search proceeds to look in the base class. This rule is applied recursively if the base class itself is derived from some other class.

There’s nothing special about instantiation of derived classes: Salpeter() creates a new instance of the class. Method references are resolved as follows: the corresponding class attribute is searched, ascending up the chain of parent classes if necessary, and the method reference is valid if this yields a function object.

Derived classes may override methods of their base classes.

An overriding method in a derived class may in fact want to extend rather than simply replace the base class method of the same name. There is a simple way to call the base class method directly: just call IMF.methodname(self, *args, **kwargs). It is common to call the parent constructor in the derived class constructor:

def Salpeter(IMF):
    def __init__(self, *args, **kwargs):
        IMF.__init__(self, *args, **kwargs)

Note that the above example is equivalent to doing nothing special and thus can be omitted (it is the implicit definition from inheritance)

Homework: fully implement an IMF concept.

due Monday, October 14th

Similarly to last week assignment

  • Create a new notebook or a copy of the current one, and call it HW2.ipynb,

  • complete this part, embed resulting figures in the notebook. Comment your code: each function/method must have at least one line of description.

  • The assignment should be turned in by submitting a pull request to the github repository: jakevdp/ASTR599_homework. (see Astro599 website for help)

The requirements are:

  • Computing the expected number of stars per mass bin
  • Computing the mass enclosed in a given mass range
  • Being able to draw random masses from an IMF
  • What is the average mass predicted by an IMF?

Below is a template of IMF class. Feel free to adapt.


In [ ]:
class IMF(object):
    """
    IMF object class
    let you define an IMF as multiple power-laws.

    attributes:
        norm:    norm of the function
    """

    def __init__(self, *args, **kwargs):
        """
        """
        pass

    def get_enclosed_mass(self, Mmin, Mmax):
        """Get the enclosed mass over a given mass range.
        """
        pass

    def get_enclosed_Nstar(self, Mmin, Mmax):
        """Get the enclosed dN over a given mass range
        """
        pass

    def get_avg_mass(self, Mmin, Mmax):
        """ get the avg mass over a given range """
        pass

    def getValue(self, m):
        """ returns the value of the normalized IMF at a given mass m:
            note: m can be an iterable
        """
        pass

    def random(self, N, massMin=None, massMax=None):
        """ Draw samples from this distribution
        Samples are distributed over the interval [massMin, massMax]
        Interval is truncated to the IMF range definition if it extents beyond it. 
        (taken as is otherwise)
        """
        pass
    
    def __call__(self, m=None):
        return self.getValue(m)

Plot at least a couple of mass functions on the same figure


In [ ]:
#your code here

Draw a random sample of N masses from one mass function and show that the sample follows the desired distribution


In [ ]:
#code here