Key Word(s): Class methods, Static methods, Instance methods, Dunder methods
Last Time¶
- Classes
- Inheritance
- Super class initializers
- Interfaces
Today¶
- Special methods (the dunder methods)
- The
Python
Data Model - Class methods, static methods, instance methods
from IPython.display import HTML
The Python Data Model¶
Duck typing is used throughout Python
. Indeed it's what enables the "Python Data Model"
- All Python classes implicitly inherit from the root object class.
- The Pythonic way is to just document your interface and implement it.
- This usage of common interfaces is pervasive in dunder functions to comprise the
Python
data model.
Example: Printing with __repr__
and __str__
¶
- The way printing works is that Python wants classes to implement
__repr__
and__str__
methods. - It will use inheritance to give the built-in
object
s methods when these are not defined. - Any class can define
__repr__
and__str__
. - When an instance of such a class is interrogated with the
repr
orstr
function, then these underlying methods are called.
We'll see __repr__
here. If you define __repr__
you have made an object sensibly printable.
__repr__
¶
class Animal():
def __init__(self, name):
self.name = name
def __repr__(self):
class_name = type(self).__name__
return "{0!s}({1.name!r})".format(class_name, self)
r = Animal("David")
r
print(r)
repr(r)
- The return value of
__repr__
is in quotes. Why? - The expression returned by
__repr__
should be able to be fed into theeval
built-in.eval
accepts aPython
expression as a string.- The
Python
expression is then evaluated. - Convenient for debugging!
__repr__
returns thePython
code needed to rebuild our object.
eval(repr(r))
Now we see how r
was created!
Note¶
- There can be confusion about the difference between
__repr__
and__str__
. - Here is a great Stackoverflow discussion this issue: Difference between
__str__
and__repr__
?. - Use
__repr__
to show how an object is created --- this is useful for developers. - Use
__str__
to describe the object --- this is useful for users. - Note:
print()
first looks for__str__
and if that's not found it looks for__repr__
.
The pattern with dunder methods¶
There are functions without double-underscores that cause the methods with the double-underscores to be called
Thus repr(an_object)
will cause an_object.__repr__()
to be called.
In user-level code, you SHOULD NEVER see the latter. In library level code, you might see the latter. The definition of the class is considered library level code.
Example: Instance Equality via __eq__
¶
We can now ask and answer the question: What makes two objects equal?
To do this, we will add a new dunder method to the mix, the unimaginatively named (that's a good thing) __eq__
.
class Animal():
def __init__(self, name):
self.name = name
def __repr__(self):
class_name = type(self).__name__
return "{0!s}({1.name!r})".format(class_name, self)
def __eq__(self, other):
return self.name == other.name # two animals are equal if their names are equal
A = Animal("Tom")
B = Animal("Jane")
C = Animal("Tom")
There are three separate object identities, but we made two of them equal!
print(id(A), " ", id(B), " ", id(C))
print(A==B, " ", B==C, " ", A==C)
This is critical because it gives us a say in what equality means.
Python's power comes from the data model, composition, and delegation¶
The data model is used (from Fluent Python) to provide a:
description of the interfaces of the building blocks of the language itself, such as sequences, iterators, functions, classes....
Python's power comes from the data model, composition, and delegation¶
The data model is used (from Fluent Python) to provide a:
description of the interfaces of the building blocks of the language itself, such as sequences, iterators, functions, classes....
The special "dunder" methods we talk about are invoked by the Python
interpreter to perform basic operations.
For example, __getitem__
gets an item in a sequence. This is used to do something like a[3]
.
__len__
is used to say how long a sequence is. Its invoked by the len
built-in function.
A sequence, for example, must implement __len__
and __getitem__
. That's it.
The original reference for this data model is: https://docs.python.org/3/reference/datamodel.html.
Tuple¶
An example of a sequence in Python
is the tuple. Since a tuple is a sequence, it must support indexing and be able to tell us its length.
a = (1,2)
a[0] # indexing
len(a) # length
Great. That worked out nicely. Let's take a look at some "enhanced" tuples.
NamedTuples¶
collections.namedtuple
¶
- Produces subclasses of tuples
- The tuples are enhanced with field names and a class name.
Consider the example from Fluent Python (Example 1-1):
import collections
Card = collections.namedtuple('Cards', ['rank', 'suit'])
repr(Card)
my_card = Card('3', 'diamonds')
print(my_card)
print(type(my_card))
print(my_card.rank)
A Custom Sequence¶
Let's create a FrenchDeck
as an example of something that follows Python
's Sequence protocol. Remember, the sequence protocol requires implementation of two methods: __len__
and __getitem__
. That's it.
[Card(rank, suit) for suit in "spade diamond club heart".split() for rank in [str(n) for n in range(2,11)] + list('JKQA')]
class FrenchDeck:
ranks = [str(n) for n in range(2,11)] + list('JKQA')
suits = "spade diamond club heart".split()
def __init__(self):
# composition: there are items IN this class that constitute its structure
# delegation: the storage for this class is DELEGATED to this list below
self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks]
def __len__(self):
return len(self._cards)
def __getitem__(self, position):
return self._cards[position]
deck = FrenchDeck()
len(deck)
deck[0], deck[-1], deck[3]
deck[10:15]
- The
FrenchDeck
class supports the sequence protocol - As a result, we can use functions like
random.choice
directly on instances ofFrenchDeck
. - This is the power of interfaces and the data model.
from random import choice
choice(deck)
Building out our class: instances and classmethods¶
At this point, you should feel comfortable with classes, special methods, and the python
data model.
We will take a short excursion to enhance our classes using classmethods
. We will also see staticmethods
and regular instance
methods.
A Favorite Example¶
class ComplexClass():
def __init__(self, real, imaginary):
self.real = real
self.imaginary = imaginary
@classmethod
def make_complex(cls, real, imaginary):
return cls(real, imaginary)
def __repr__(self):
class_name = type(self).__name__
return "%s(real=%r, imaginary=%r)" % (class_name, self.real, self.imaginary)
def __eq__(self, other):
return (self.real == other.real) and (self.imaginary == other.imaginary)
c1 = ComplexClass(1,2)
c1
class ComplexClass():
def __init__(self, real, imaginary):
self.real = real
self.imaginary = imaginary
@classmethod
def make_complex(cls, real, imaginary):
return cls(real, imaginary)
def __repr__(self):
class_name = type(self).__name__
return "%s(real=%r, imaginary=%r)" % (class_name, self.real, self.imaginary)
def __eq__(self, other):
return (self.real == other.real) and (self.imaginary == other.imaginary)
make_complex
is a class method. See how its signature is different above. It is a factory to produce instances.
c2 = ComplexClass.make_complex(1,2)
c2
c1 == c2
The take-away¶
- A
classmethod
has access to the actual class, but not the instance of the class
Static Methods, Class Methods, Instance Methods¶
What's really going on under the hood here?
# From fluent python
class Demo():
@classmethod
def klassmeth(*args): # Class methods do not have to return an instance of the class
return args
@staticmethod
def statmeth(*args): # This is just a regular function
return args
def instmeth(*args): # This is a true blue instance method
return args
sm = Demo.statmeth(1,2)
print(type(sm))
sm
# From fluent python
class Demo():
@classmethod
def klassmeth(*args): # Class methods do not have to return an instance of the class
return args
@staticmethod
def statmeth(*args): # This is just a regular function
return args
def instmeth(*args): # This is a true blue instance method
return args
cm = Demo.klassmeth(1,2)
print(type(cm))
cm
ademo = Demo()
Demo.instmeth(ademo, 1,2)
ademo.instmeth(1,2)
cm = ademo.klassmeth(1,2)
sm = ademo.statmeth(1,2)
im = ademo.instmeth(1,2)
print(type(cm), type(sm), type(im))
cm
sm
im
Class variables and instance variables¶
class Demo2():
classvar = 1
ademo2 = Demo2()
print(Demo2.classvar, ademo2.classvar)
ademo2.classvar = 2 # Different from the classvar above
print(Demo2.classvar, ademo2.classvar)