{ "metadata": { "name": "" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Classes and Objects\n", "\n", "**Adapted from materials by Tommy Guy and Anthony Scopatz**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Object Orientation\n", "\n", "**Object oriented programming** is a way of thinking about and defining how different pieces of software and ideas work together. In objected oriented programming, there are two main interfaces: **classes** and **objects**. \n", "\n", "* **Classes** are types of things such as int, float, person, or square.\n", "* **Objects** are **instances** of those types such a 1, 42.0, me, and a square with side length 2.\n", "\n", "\n", "Unlike functional or procedural paradigms, there are three main features that classes provide.\n", "\n", "* **Encapsulation:** Classes are containers that may have any kind of other programming element living in them: variables, functions, and even other classes. In Python, members of a class are known as **attributes** for normal variables and **methods** for functions.\n", "\n", "\n", "* **Inheritence:** A class may automatically gain all of the attributes and methods from another class it is related to. The new class is called a **subclass** or sometimes a **subtype**. Multiple levels of inheritance set up a **class heirarchy**. For example:\n", "\n", " - `Shape` is a class with an `area` attribute.\n", " - `Rectangle` is a subclass of `Shape`.\n", " - `Square` is a subclass of `Rectangle` (which also makes it a subclass of `Shape`).\n", "\n", "\n", "* **Polymorphism:** Subclasses may override methods and attributes of their parents in a way that is suitable to them. For example:.\n", "\n", " - `Shape` is a class with an `area` method.\n", " - `Square` is a subclass of `Shape` that computes area by $x^2$.\n", " - `Circle` is a subclass of `Shape` that computes area by $\\pi r^2$\n", "\n", "\n", "If this seems more complicated than writing functions and calling them in sequence that is because it is! However, obeject orientation enables authors to cleanly separate out ideas into independent classes and potentially produce more efficient code overall. It is also good to know because in many languages - Python included - it is the way that you modify the type system. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Basic Classes\n", "\n", "Object oriented programming revolves around the creation and\n", "manipulation of objects that have attributes and can do things. They can\n", "be as simple as a coordinate with x and y values or as complicated as a\n", "dynamic webpage framework. Here is the code for making a very simple class \n", "that sets an attribute. " ] }, { "cell_type": "code", "collapsed": false, "input": [ "class MyClass(object):\n", " def me(self, name):\n", " self.name = name" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "my_object = MyClass()" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the object oriented terminology above:\n", "\n", "- `MyClass` - a user defined type.\n", "- `my_object` - a `MyClass` instance.\n", "- `me()` - a `MyClass` method (member function)\n", "- `self` - a reference to the object that is used to define method. This must be the first agument of any method.\n", "- `name` - an attribute (member variable) of `MyClass`.\n", "- `object` - a special class that should be the parent of all classes.\n", "\n", "You *write* a class and you *create* and object." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can check the type of the object we've created." ] }, { "cell_type": "code", "collapsed": false, "input": [ "type(my_object)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can directly set and access the name attribute." ] }, { "cell_type": "code", "collapsed": false, "input": [ "my_object.name = 'Rachel'\n", "print my_object.name" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can call the function to set the name attribute." ] }, { "cell_type": "code", "collapsed": false, "input": [ "my_object.me('Azalee')\n", "print my_object.name" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Hands on Example**\n", "\n", "Write an Atom class with mass and velocity attributes and an energy() method." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Atom class with mass and velocity attributes and an energy method.\n", "\n", "class Atom(object):\n", " \n", " # Setup function takes mass in amu and velocity in m/s\n", " def properties(self, m, v):\n", " self.mass = m * 1.66053892e-27 # convert mass from amu to kg\n", " self.velocity = v\n", " \n", " # Return kinetic energy in Joules\n", " def energy(self):\n", " self.energy = 0.5 * self.mass * self.velocity**2\n", " return self.energy\n" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's try it out!" ] }, { "cell_type": "code", "collapsed": false, "input": [ "atom = Atom()\n", "print \"atom is of type\", type(atom)\n", "\n", "atom.properties(12,2e7)\n", "print \"\\nMass in kg\", atom.mass\n", "print \"\\nVelocity in m/s\", atom.velocity\n", "print \"\\nEnergy in Joules\", atom.energy()" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Programatic Attribute Access\n", "\n", "Python provides three built-in functions, `getattr()`, `setattr()`, and `delattr()` to programticaly access an object's members. These all take the object and the string name of the attribute to be accessed. This is instead of using dot-access." ] }, { "cell_type": "code", "collapsed": false, "input": [ "class A(object):\n", " a = 1" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "avar = A()\n", "getattr(avar, 'a')" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "setattr(avar, 'q', 'mon')\n", "print avar.q" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##Constructors\n", "\n", "Usually you want to create an object with a set of initial or default values for\n", "things. Perhaps an object needs certain information to be created. For\n", "this you write a **constructor**. In python, constructors are just methods\n", "with the special name ``__init__()``:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class Person(object):\n", " def __init__(self):\n", " self.name = \"Rachel\"" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "person = Person()\n", "print person.name" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Constructors may take arguements just like any other method or function." ] }, { "cell_type": "code", "collapsed": false, "input": [ "class Person(object):\n", " def __init__(self, name, title=\"The Best\"):\n", " self.name = name\n", " self.title = title" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "rachel = Person(\"Rachel\")\n", "print rachel.name, rachel.title" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "azalee = Person(\"Azalee\", \"The Greatest\")\n", "print azalee.name, azalee.title" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here is a more complex and realisitic example of a matrix class:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Matrix defines a real, 2-d matrix.\n", "\n", "class Matrix(object):\n", " # I am a matrix of real numbers\n", "\n", " def __init__(self, h, w):\n", " self._nrows = h\n", " self._ncols = w\n", " self._data = [0] * (self._nrows * self._ncols)\n", "\n", " def __str__(self):\n", " return \"Matrix: \" + str(self._nrows) + \" by \" + str(self._ncols)\n", "\n", " # reinitialize to zeros\n", " def reinit(self):\n", " self._data = [0] * (self._nrows * self._ncols)\n", " \n", " def setnrows(self, w):\n", " self._nrows = w\n", " self.reinit()\n", "\n", " def getnrows(self):\n", " return self._nrows\n", "\n", " def getncols(self):\n", " return self._ncols\n", "\n", " def setncols(self, h):\n", " self._ncols = h\n", " self.reinit()\n", "\n", " def setvalue(self, i, j, value):\n", " if i < self._nrows and j < self._ncols:\n", " self._data[i * self._nrows + j] = value\n", " else:\n", " raise Exception(\"Out of range\")\n", "\n", " def multiply(self, other):\n", " # Perform matrix multiplication and return a new matrix.\n", " # The new matrix is on the left.\n", " result = Matrix(self._nrows, other.getncols())\n", " # Do multiplication...\n", " return result\n", "\n", " def inv(self):\n", " # Invert matrix \n", " if self._ncols != self._nrows:\n", " raise Exception(\"Only square matrices are invertible\")\n", " inverted = Matrix(self._ncols, self._nrows)\n", " inverted.setncols(self._ncols)\n", " inverted.setnrows(self._ncols)\n", " return inverted" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Interface vs. Implementation\n", "\n", "Users shouldn't have to know *how* your program works in order to use\n", "it.\n", "\n", "The interface is a **contract** saying what a class knows how to do. The\n", "code above defines matrix multiplication, which means that\n", "`mat1.multiply(mat2)` should always return the right answer. It turns out\n", "there are many ways to multiply matrices, and there are whole PhDs\n", "written on performing efficient matrix inversion. The implementation is\n", "the way in which the contract is carried out." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Subclassing\n", "\n", "If you want a to create a class that behaves mostly like another class,\n", "you should not have to copy code. What you do is **subclass** and change the\n", "things that need changing. When we created classes we were already\n", "subclassing the built in python class \"object.\"\n", "\n", "For example, let's say you want to write a sparse matrix class, which\n", "means that you don't explicitly store zero elements. You can create a\n", "subclass of the Matrix class that redefines the matrix operations." ] }, { "cell_type": "code", "collapsed": false, "input": [ "class SparseMatrix(Matrix):\n", " # I am a matrix of real numbers\n", "\n", " def __str__(self):\n", " return \"SparseMatrix: \" + str(self._nrows) + \" by \" + str(self._ncols)\n", "\n", " def reinit(self):\n", " self._data = {}\n", "\n", " def setValue(self, i, j, value):\n", " self._data[(i,j)] = value\n", "\n", " def multiply(self, other):\n", " # Perform matrix multiplication and return a new matrix.\n", " # The new matrix is on the left.\n", " result = SparseMatrix(self._nrows, other.getncols())\n", " # Do multiplication...\n", " return result\n", "\n", " def inv(self):\n", " # Invert matrix\n", " if self._nrows != self._rcols: \n", " raise Exception(\"Only square matrices are invertible\")\n", " inverted = SparseMatrix(self._ncols, self._nrows)\n" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The SparseMatrix object is a Matrix but some methods are defined in the *superclass* Matrix. You can see this by looking at the dir of the SparseMatrix and noting that it gets attributes from Matrix." ] }, { "cell_type": "code", "collapsed": false, "input": [ "dir(SparseMatrix) " ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A more minimal and more abstact version of inheritence may be seen here:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "class A(object):\n", " a = 1\n", "\n", "class B(A):\n", " b = 2\n", " \n", "class C(B):\n", " b = 42\n", " c = 3\n", "\n", "x = C()\n", "print x.a, x.b, x.c" ], "language": "python", "metadata": {}, "outputs": [] } ], "metadata": {} } ] }