Session Eight: Object Oriented Programming 2

Object Oriented Programming continued...

Announcements

Review & Questions

Homework

Code review – let’s take a look.

Homework Notes:

**kwargs will always define a kwargs dict: it just may be empty.

And there is no need to check if it’s empty before trying to loop through it.

if self.attributes != {}:
    for key, value in self.attributes.items():
        self.atts += ' {}="{}"'.format(key, value)

no need for != {} – an empty dict is “Falsey”

but no need for that check at all. If the dict (or ist, or tuple) is empty, then the loop is a do-nothing operation:

  • notes on Duck Typing: HTML Renderer Exercise and code review
  • anyone stuck that wants to work through your code?

Lightning Talks

Deana Holmer
Brennen Bounds
tbd
tbd
tbd

Framing

Multiple Inheritance

Multiple inheritance: Inheriting from more than one class.

Simply provide more than one parent.

class Combined(Parent1, Parent2, Parent3):
    def __init__(self, something, something else):
        # some custom initialization here.
        Parent1.__init__(self, ......)
        Parent2.__init__(self, ......)
        Parent3.__init__(self, ......)
        # possibly more custom initialization

Calls to the parent class __init__ are optional and case dependent.

Purpose

What was the purpose behind inheritance?

Code reuse.

What is the purpose behind multiple inheritance?

Code reuse.

What wasn’t the purpose of inheritance?

Building massive class hierarchies for their own sake.

What isn’t the purpose of multiple inheritance?

Building massive class hierarchies for their own sake.

Python’s Multiple Inheritance Model

Cooperative Multiple Inheritance

Emphasis on cooperative!

  • Play by the rules and everybody benefits (parents, descendants).
  • Play by the rules and nobody gets hurt (yourself, mostly).
  • We’re all adults here.

What could go wrong?

The Diamond Problem

With Python “new style” classes everything is descended from ‘object’. Thus, the moment you invoke multiple inheritance you have the diamond problem.

https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem

super()

super(): use it to call a superclass method, rather than explicitly calling the unbound method on the superclass.

instead of:

class A(B):
    def __init__(self, *args, **kwargs)
        B.__init__(self, *argw, **kwargs)
        ...

You can do:

class A(B):
    def __init__(self, *args, **kwargs)
        super().__init__(*argw, **kwargs)
        ...

MRO: Method Resolution Order

class Combined(Super1, Super2, Super3)

Attributes are located bottom-to-top, left-to-right

  • Is it an instance attribute ?
  • Is it a class attribute ?
  • Is it a superclass attribute ?
    • Is it an attribute of the left-most superclass?
    • Is it an attribute of the next superclass?
    • and so on up the hierarchy...
  • Is it a super-superclass attribute ?
  • ... also left to right ...

http://python-history.blogspot.com/2010/06/method-resolution-order.html

Super’s Superpowers

It works out – dynamically at runtime – which classes are in the delegation order.

Do not be afraid. And be very afraid.

Dependency Injection

Super() is the right way to do dependency injection.

https://en.wikipedia.org/wiki/Dependency_injection

Compare with Monkey Patching as done in other languages.

https://en.wikipedia.org/wiki/Monkey_patch

Argument Passing

Remember that super does not only delegate to your superclass, it delegates to any class in the MRO.

Therefore you must be prepared to call any other class’s method in the hierarchy and be prepared to be called from any other class’s method.

The general rule is to pass all arguments you received on to the super function. If classes can take differing arguments, accept *args and **kwargs.

Two seminal articles

“Super Considered Harmful” – James Knight

https://fuhm.net/super-harmful/

“Super() considered super!” – Raymond Hettinger

http://rhettinger.wordpress.com/2011/05/26/super-considered-super/

(Both worth reading....)

Composition

Composition vs Inheritance

Composition does virtually the same thing as multiple inheritance, in the sense that it allows your class to reuse the functionality of other classes.

With inheritance you are thinking in terms of ‘is-a’ relationships.

With composition you are thinking in terms of ‘has-a’ relationships.

Composition is more explicit than inheritance and it avoids the complexity of super(). It of course also gains nothing from super()’s superpowers.

An example

class Other(object):

    def __init__(self):
        print("Other __init__()")


class MyComposedClass(object):
""" I inherit from object """

    def __init__(self):
        self.other = Other()  # I contain Other()

Remember: ‘has-a’ not ‘is-a’

Properties

https://en.wikipedia.org/wiki/Property_%28programming%29#Python

Attributes are clear and concise

One of the strengths of Python is lack of clutter.

In [5]: class C:
        def __init__(self):
                self.x = 5
In [6]: c = C()
In [7]: c.x
Out[7]: 5
In [8]: c.x = 8
In [9]: c.x
Out[9]: 8

And we want to maintain this clarity.

Getter and Setters

But what if you need to add behavior later?

  • do some calculation
  • check data validity
  • keep things in sync
In [5]: class C:
   ...:     def __init__(self):
   ...:         self.x = 5
   ...:     def get_x(self):
   ...:         return self.x
   ...:     def set_x(self, x):
   ...:         self.x = x
   ...:
In [6]: c = C()
In [7]: c.get_x()
Out[7]: 5
In [8]: c.set_x(8)
In [9]: c.get_x()
Out[9]: 8

This is verbose – Java?

Properties

class C:
    _x = None
    @property
    def x(self):
        return self._x
    @x.setter
    def x(self, value):
        self._x = value

In [28]: c = C()
In [30]: c.x = 5
In [31]: print(c.x)
5

Now the interface is like simple attribute access!

Decorators

What’s up with the “@” symbols?

Those are “decorations” it is a syntax for wrapping functions up with something special.

We will cover decorators in detail in another part of the program, but for now just copy the syntax.

@property
def x(self):

means: make a property called x with this as the “getter”.

@x.setter
def x(self, value):

means: make the “setter” of the ‘x’ property this new function

Read Only Attributes

You do not need to define a setter. If you don’t, you get a “read only” attribute:

In [11]: class D():
   ....:     def __init__(self, x=5):
   ....:         self._x = 5
   ....:     @property
   ....:     def getx(self):
   ....:     """I am read only"""
   ....:         return self._x
   ....:
In [12]: d = D()
In [13]: d.x
Out[13]: 5
In [14]: d.x = 6
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-14-c83386d97be3> in <module>()
----> 1 d.x = 6
AttributeError: can't set attribute

Deleters

If you want to do something special when a property is deleted, you can define a deleter as well:

In [11]: class D():
   ....:     def __init__(self, x=5):
   ....:         self._x = 5
   ....:     @property
   ....:     def x(self):
   ....:         return self._x
   ....:     @x.deleter
   ....:     def x(self):
   ....:         del self._x

If you leave this out, the property can’t be deleted, which is usually what you want.

[demo: properties_example.py]

LAB: Properties, class methods, special methods

Let’s use some of this to build a nice class to represent a Circle.

For now, Let’s do steps 1-4 of:

Circle Class Excercise

Review framing questions

Homework

Complete the Circle class

Complete the Sparse Array class

Refactor mailroom to use classes.

Readings

Python 3 Object Oriented Programming: Dusty Phillips

(Dusty is a local boy and co-founder of PuPPy)