In [17]:
# huge topic. this is the basics
# classes "encapsulate" state and behavior
# in other words variables and functions (called methods).
# objects or instances of a class can use these variables
# to store per-object state, and the methods can access that
# per-object state.

# classes form a hierarchy.
# each class is a "subclass" of one other class, which is
# called its "superclass". The base class--one with no superclass,
# is called "object".
# (there is an old style of defining classes in Python 2 which I
# will not cover as it is generally considered bad practice to use it)

# by convention, class names are in capitalized camel case

# Here is the simplest class:

class EmptyClass(object):
    pass

In [18]:
# to make an instance, call the class's "constructor". it is named after
# the class. The default constructor does nothing, so it works with our
# EmptyClass:

EmptyClass()


Out[18]:
<__main__.EmptyClass at 0x7fbcc02ac290>

In [19]:
# let's create a class with a method. All methods accept at least one argument;
# the first argument is called "self" and refers to whatever instance the method
# is called on.

class SimpleClass(object):
    def hello(self):
        return 'Hello'

# when you call the method, instead of passing an instance to it, you dereference it
# from an expression returning the instance, and you don't have to pass "self" to it.
o = SimpleClass()
o.hello()


Out[19]:
'Hello'

In [20]:
# a method that takes arguments takes them after "self"

class SimpleClass(object):
    def hello(self, who='world'):
        return 'Hello, %s.' % who
    
o = SimpleClass()
o.hello('everyone')


Out[20]:
'Hello, everyone.'

In [21]:
# "self" can have properties set on it, which are per-instance

a = SimpleClass()
b = SimpleClass()
a.name = 'Joe'

class Thing(object):
    pass

o = Thing()

o.something = 3
o.bar = 8
o


Out[21]:
<__main__.Thing at 0x7fbcc02ac910>

In [22]:
# only classes can have their attributes set, and not, say, dicts

d = { 'one': 1, 'two': 2 }

d.name = 'Joe'


---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-22-c996a83897e1> in <module>()
      3 d = { 'one': 1, 'two': 2 }
      4 
----> 5 d.name = 'Joe'

AttributeError: 'dict' object has no attribute 'name'

In [23]:
# inside methods, properties belong to "self"

class SimpleClass(object):
    def hello(self):
        return 'Hello, %s.' % self.name
    
joe = SimpleClass()
bethany = SimpleClass()

joe.name = 'Joe'
bethany.name = 'Bethany'

joe.hello(), bethany.hello()


Out[23]:
('Hello, Joe.', 'Hello, Bethany.')

In [24]:
# methods can set properties

class SimpleClass(object):
    def hello(self):
        msg = 'Hello, %s.' % self.name
        self.name = 'person I already said hello to'
        return msg
    
joe = SimpleClass()
joe.name = 'Joe'
joe.hello()


Out[24]:
'Hello, Joe.'

In [25]:
joe.hello()


Out[25]:
'Hello, person I already said hello to.'

In [26]:
# each class has a special method called __init__ that is called when you construct an instance

class SimpleClass(object):
    def __init__(self, name):
        self.name = name
    def hello(self):
        return 'Hello, %s.' % self.name

joe = SimpleClass('Joe')
joe.hello()


Out[26]:
'Hello, Joe.'

In [27]:
# methods can call each other using "self"

class SimpleClass(object):
    def __init__(self, name):
        self.name = name
    def hello(self):
        return 'Hello, %s.' % self.name
    def say_hello(self):
        print self.hello()
        return 5

SimpleClass('Joe').say_hello()


Hello, Joe.
Out[27]:
5

In [28]:
# you can "subclass" a class. the subclass will "inherit" methods from its "superclass"
# and you can "override" methods.

class SimpleSubclass(SimpleClass):
    def hello(self):
        return 'Oh hai %s.' % self.name

SimpleSubclass('Joe').say_hello()


Oh hai Joe.
Out[28]:
5

In [29]:
# be careful when overriding __init__, as the superclass's __init__ is not automatically called

class SimpleSubclass(SimpleClass):
    def __init__(self, name, greeting):
        self.greeting = greeting
    def hello(self):
        return '%s, %s.' % (self.greeting, self.name)
    
# this will not work

SimpleSubclass('Joe', 'Hi there').say_hello()


---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-29-40c2b7aaf9e3> in <module>()
      9 # this will not work
     10 
---> 11 SimpleSubclass('Joe', 'Hi there').say_hello()

<ipython-input-27-7865f9e4fe3d> in say_hello(self)
      7         return 'Hello, %s.' % self.name
      8     def say_hello(self):
----> 9         print self.hello()
     10         return 5
     11 

<ipython-input-29-40c2b7aaf9e3> in hello(self)
      5         self.greeting = greeting
      6     def hello(self):
----> 7         return '%s, %s.' % (self.greeting, self.name)
      8 
      9 # this will not work

AttributeError: 'SimpleSubclass' object has no attribute 'name'

In [30]:
# there is a very specific idiom that must be used to call a superclass constructor

class SimpleSubclass(SimpleClass):
    def __init__(self, name, greeting):
        super(SimpleSubclass, self).__init__(name)
        self.greeting = greeting
    def hello(self):
        return '%s, %s.' % (self.greeting, self.name)
    
SimpleSubclass('Joe', 'Greetings').say_hello()


Greetings, Joe.
Out[30]:
5

In [31]:
# you can also call any other superclass method the same way

class SimpleSubclass(SimpleClass):
    def __init__(self, name, greeting):
        super(SimpleSubclass, self).__init__(name)
        self.greeting = greeting
    def hello(self):
        return '%s, %s.' % (self.greeting, self.name)
    def say_hello(self):
        super(SimpleSubclass, self).say_hello()
        return 7
    
SimpleSubclass('Joe', 'Greetings').say_hello()


Greetings, Joe.
Out[31]:
7

In [32]:
# remember, once we create a class we can set its properties and call its methods
# and it will keep track of its properties as they change

o = SimpleSubclass('Joe','Greetings')
o.say_hello()


Greetings, Joe.
Out[32]:
7

In [33]:
o.name = 'everyone'
o.say_hello()


Greetings, everyone.
Out[33]:
7

In [34]:
o.greeting = 'Howdy'
o.say_hello()


Howdy, everyone.
Out[34]:
7

In [35]:
# you can set properties that are never used in any methods and that works too:

o.foobar = 3
o.say_hello()


Howdy, everyone.
Out[35]:
7

In [36]:
o.foobar


Out[36]:
3