Python Magic...Methods



Scott Overholser



https://github.com/eigenholser/python-magic-methods

Terminology

  • "Dunder" is used to reference "double underscore" names.

  • E.g. __init__() is called "dunder init".

Magic Methods

  • Special methods with reserved names.

  • Beautiful, intuitive, and standard ways of performing basic operations.

  • Define meaning for operators so that we can use them on our own classes like they were built in types.

Python Built-in Types


In [ ]:
methods = []
for item in dir(2):
    if item.startswith('__') and item.endswith('__'):
        methods.append(item)
print(methods)

Examples


In [ ]:
(2).__str__()

In [ ]:
str(2)

In [ ]:
(2).__pow__(3)

In [ ]:
2 ** 3

What about our own custom objects?

  • We can add magic methods to make our own objects behave like built-in types.

  • Why would we do that?

  • Expressiveness!

  • It is Zen.

The Zen of Python


In [ ]:
import this

Initialize our notebook


In [ ]:
from great_circle import (
    CAN, JFK, LAX, SLC, 
    Point, Distance, 
    MagicPoint, MagicDistance)

I asked Google Maps for the GPS coordinates of these airports


In [ ]:
CAN, JFK, LAX, SLC

Object Construction

  • The __init__(self, [args...]) magic method.

In [ ]:
# Initialize some non-magic objects
slc1 = Point(SLC)
slc2 = Point(SLC)
lax = Point(LAX)
jfk = Point(JFK)
can = Point(CAN)

# Initialize some objects with magic methods
m_slc1 = MagicPoint(SLC)
m_slc2 = MagicPoint(SLC)
m_lax = MagicPoint(LAX)
m_jfk = MagicPoint(JFK)
m_can = MagicPoint(CAN)

Object Equality

  • The __eq__(self, other) magic method.

In [ ]:
# Both p1 and p2 were instantiated using SLC coordinates.
slc1.coordinates(), slc2.coordinates()

So they're equal...right?


In [ ]:
slc1 == slc2

Um...wrong.

This is why!

The identity operator is returns true only if the id() function on both objects are equal.


In [ ]:
hex(id(slc1)), hex(id(slc2))

In [ ]:
slc1 is slc2

How should we define equality?

  • If latitude and longitude of both points are equal then the points are equal.
  • More generally, if the attributes of both objects are equal, the objects are equal.

In [ ]:
slc1.latitude == slc2.latitude and slc1.longitude == slc2.longitude

How expressive is that!?

Not very...

We could define a method on our object...


In [ ]:
def is_equal(self, p):
    """
    Test for equality with point p.
    """
    return self.latitude == p.latitude and self.longitude == p.longitude

In [ ]:
slc1.is_equal(slc2)
  • Still cumbersome.

Now let's try it with Magic!

  • The __eq__() magic method is defined.

In [ ]:
m_slc1 == m_slc2

Big Kermit Thee Frog Yaaay!

What's the difference?

  • Equality is still defined the same.
  • We still define a method on our object to implement the equality test.
  • Python makes an implicit call to our method. This is the secret sauce...the magic!
  • Python requires our method have the name __eq__(), take a single argument, and return a boolean.

Pick up the pace

Calculating distance between points

  • Intuitively, the distance between two points is the difference.
  • This implies subtraction.
  • The __sub__(self, other) magic method.

Old and busted

  • Create a method to compute distance from the instance to supplied Point instance.

In [ ]:
slc1.calculate_distance(lax)

In [ ]:
jfk.calculate_distance(slc1)
  • Still cumbersome.

New hotness

  • The __sub__() magic method.
  • Takes a single argument and returns whatever you want.

In [ ]:
# __sub__() returns MagicDistance instance.
dist_lax_slc = m_slc1 - m_lax
dist_jfk_slc = m_slc1 - m_jfk
type(dist_lax_slc)

In [ ]:
# Jumping ahead a bit to representation of objects...
print(dist_lax_slc)
print(dist_jfk_slc)

Representation of objects

  • The __repr__(self) magic method.
  • The __str__(self) magic method.
  • The __format__(self, formatstr) magic method.

__repr__(self)


In [ ]:
# Point
slc1

In [ ]:
# MagicPoint
m_slc1

In [ ]:
# MagicDistance
dist_lax_slc

__str__(self)


In [ ]:
# Point
str(slc1)

In [ ]:
# MagicPoint
str(m_slc1)

In [ ]:
# MagicDistance
str(dist_lax_slc)

__format__(self, formatstr)

Old and busted


In [ ]:
# Point
"SLC coordinates: {}.".format(slc1)
  • That didn't work very well.

In [ ]:
# Point
"SLC coordinates: {}.".format(slc1.coordinates())
  • That's better, but cumbersome.

New hotness


In [ ]:
# MagicPoint
"SLC coordinates: {}.".format(m_slc1)
  • Perfect. But wait, there's more!

In [ ]:
# MagicPoint
"SLC coordinates: {:.4f}.".format(m_slc1)
  • Specifying a floating point format results in radians.

More new hotness


In [ ]:
# MagicDistance
"Distance from LAX to SLC is {} nautical miles.".format(dist_lax_slc)

In [ ]:
# MagicDistance
"Distance from JFK to SLC is {} nautical miles.".format(dist_jfk_slc)

About distance

  • __eq__()

  • __lt__(), __le__()

  • __gt__(), __ge__()

  • Let's take a quick look at these methods in the code.

  • Nothing but magic from here.

Just a sampling


In [ ]:
dist_jfk_slc == dist_lax_slc

In [ ]:
dist_jfk_slc > dist_lax_slc

In [ ]:
dist_lax_slc >= dist_lax_slc

Calling an object like a function

  • __call__(self, [args...])

In [ ]:
dist_lax_slc(slc1, jfk)
  • This is a very contrived example.

Object Destruction

  • The __del__(self) magic method.
  • Called when the object is garbage collected.

In [ ]:
# No magic methods.
slc1 = None

In [ ]:
# Magic methods.
m_can = None