Programming for Network Research

Erick Peirson, PhD | erick.peirson@asu.edu | @captbarberousse

Last updated 21 January, 2016

2. Objects and Types

We use all kinds of words to describe things in Python. We have strings (str), integers (int), lists (list), and functions (function). In the end, however, these are all objects. In fact, aside from syntax and operators (e.g. def, +, etc.), everything in Python is an object.

What sets different kinds of objects apart is their type. Every object has a type. You can find out the type of an object using the type function.


In [4]:
type("What am I?")


Out[4]:
str

Another word for type is "class". When I'm working with a particular string (str), for example, I'm working with an instance of the str class.


In [5]:
print "I am an instance of a string"
print str                # <-- This is the str class, or type.
print type(str)          # <-- The str type is an instance of class `type`.


I am an instance of a string
<type 'str'>
<type 'type'>

A function is also an object, for which there is a function type.


In [6]:
def myfunction():
    return True

type(myfunction)


Out[6]:
function

2.1. Be careful with your types!

Each type/class has different attributes, and different things that you can do with them. And many functions will work on instances of some types but not others. If you try to do something with an object of the wrong type, you'll usually get a TypeError.


In [7]:
'a'/5


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-c2b4e5d2d3e0> in <module>()
----> 1 'a'/5

TypeError: unsupported operand type(s) for /: 'str' and 'int'

In [8]:
sum('what is the sum of this string')


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-8-63c618b5e5d4> in <module>()
----> 1 sum('what is the sum of this string')

TypeError: unsupported operand type(s) for +: 'int' and 'str'

2.2. Instance methods.

Instances (objects) of different classes have different built-in methods (another word for a function). An instance method is like a super power: think about it like an intrinsic ability of the object.

We can use instance methods by writing the name of the object, and then a period (.), and then the name of the method. For example, we can change a str to lower case using the lowercase() method:


In [9]:
mystring = 'YYYYIKES'
print mystring.lower()    # lower() is an instance method for strings!


yyyyikes

Just like other functions, instance methods can accept arguments. For example, I can count the number of 'Y' characters in my string object using the count() method:


In [10]:
print mystring.count('Y')    # How many Y's are there in my string?


4

In IPython, you can get information about a function or method by writing everything up to the open parens, and then pressing shift + tab. For example, try writing the following in the code-cell below:

mystring.count(

and then press shift + tab on your keyboard. It should open a little box with information about the method.


In [11]:
mystring.count(


  File "<ipython-input-11-f6cd5492596c>", line 1
    mystring.count(
                   ^
SyntaxError: unexpected EOF while parsing

2.3. Make your own types!

It's easy to define your own types/classes in Python. Just use the class declaration.

Let's create a Car class. The Car class will define what attributes and methods an instance of a car will have.


In [24]:
class Car:
    """
    A small passenger automobile.
    """
    
    driver = None              # By default, there is no driver.
    number_of_passengers = 0   # By default, the car is empty.

Let's unpack the code-block above.

On the first line, I declare that I am about to define a new class (or type) called Car.

class Car:

I then define two attributes, and give them default values. When we create a new car, it will have these attributes, and those attributes will have the specified default values.

driver = None              # By default, there is no driver.
number_of_passengers = 0   # By default, the car is empty.

Let's try instantiating our Car class. In other words, let's create a new car.


In [13]:
mycar = Car()

Sure enough, mycar has an attribute called driver, and another attribute called number_of_passengers.


In [14]:
print mycar.driver
print mycar.number_of_passengers


None
0

You can change an attribute just like you would change a variable.


In [15]:
mycar.driver = 'John'
mycar.number_of_passengers += 5    # += means "increment by" -- in this case, we add 5 to the current value.

print mycar.driver
print mycar.number_of_passengers


John
5

Now let's give our Car class some methods. We'll redeclare the class, this time with methods.


In [23]:
class Car:
    """
    A small passenger automobile.
    """
    
    driver = None              # By default, there is no driver.
    number_of_passengers = 0   # By default, the car is empty.

    def honk(self):
        """
        What noise does a car make?
        """
        print 'beep beep!'
        
    def count_passengers(self):
        """
        Get the number of passengers in this car.
        """
        return self.number_of_passengers

This looks a lot like the function declarations that you saw earlier, but with two important differences:

  1. They are part of the class definition for Car, so they are indented one extra level.
  2. Their first (and, in this case, only) parameter is something called self.

self is how we refer to the instance of a class. When we create an instance of car, and then use that car instance's number_of_passengers method, the self in that function is the instance of the car that we're interacting with.

So when I use the count_passengers method, and it returns self.number_of_passengers, it is retrieving the number_of_passengers attribute from the current car instance.


In [17]:
mycar = Car()    # Putting the parens () after the class name instantiates the class.
mycar.driver = 'John'
mycar.number_of_passengers += 5

print mycar.count_passengers()


5

Pro tip: when defining a class, always include a docstring. That's the part with the triple open and close quotes and a descriptive message.

"""
I am a docstring. I describe what a class is all about.
"""

2.4. Changing the type of an object.

Sometimes it makes sense to transform an instance/object of one type into an instance/object of another type. For example, suppose I have a string that contains the character "5".

mystring = '5'

If I want to do math with that 5 (say, I want to add 3.4 to it), I'll run into trouble:


In [18]:
mystring = '5'

mystring + 3.4


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-18-76778f9159ef> in <module>()
      1 mystring = '5'
      2 
----> 3 mystring + 3.4

TypeError: cannot concatenate 'str' and 'float' objects

But all is not lost! If I transform it into an int first, I'm golden.


In [19]:
print type(mystring)    # mystring is a str
myint = int(mystring)   # But I can turn it into an int!

print type(myint)       # Yup, it's an int.
print myint + 3.4       # And now I can math!


<type 'str'>
<type 'int'>
8.4

2.5. Exploration

Frequently you will work with objects with which you are only partially familiar. Here are a few techniques that are helpful for learning about an object and its attributes and methods.

dir()

dir will attempt to display all of the attributes of an object.


In [20]:
print dir("what does a string know?")


['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_formatter_field_name_split', '_formatter_parser', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

Notice that this includes methods, like count, title, and upper.

The __dict__ attribute.

Many objects can be represented as dictionaries (we haven't talked about that yet). For now, the important thing is that access the __dict__ attribute of most objects will display a list of non-function attributes as well as their values.


In [25]:
myCar = Car()
myCar.driver = 'Fido'

print myCar.__dict__


{'driver': 'Fido'}

IPython: tab completion

In IPython, you can use tab completion! Just type the name of an object plus a period, and then press tab to see a list of attributes. You can start typing in part of an attribute name to limit the list.


In [ ]:
myCar.    # Put your cursor right after the dot, and press ``tab``.

Wrap-up

In this notebook, you learned the basics of Object Oriented Programming. We talked about classes (types) and instances (objects). We also talked about attributes and instance methods (functions that belong to instances of a class).