Python Oop
Python Oop
1 Contents
• Overview 2
• OOP Review 3
• Special Methods 5
• Exercises 6
• Solutions 7
2 Overview
So imagine now you want to write a program with consumers, who can
1
• data, such as cash on hand
• methods, such as buy or work that affect this data
3 OOP Review
As discussed an earlier lecture, in the OOP paradigm, data and functions are bundled to-
gether into “objects”.
An example is a Python list, which not only stores data but also knows how to sort itself, etc.
In [1]: x = [1, 5, 4]
x.sort()
x
Out[1]: [1, 4, 5]
As we now know, sort is a function that is “part of” the list object — and hence called a
method.
If we want to make our own types of objects we need to use class definitions.
A class definition is a blueprint for a particular class of objects (e.g., lists, strings or complex
numbers).
It describes
2
In Python, the data and methods of an object are collectively referred to as attributes.
Attributes are accessed via “dotted attribute notation”
• object_name.data
• object_name.method_name()
In the example
In [2]: x = [1, 5, 4]
x.sort()
x.__class__
Out[2]: list
• x is an object or instance, created from the definition for Python lists, but with its own
particular data.
• x.sort() and x.__class__ are two attributes of x.
• dir(x) can be used to view all the attributes of x.
OOP is useful for the same reason that abstraction is useful: for recognizing and exploiting
the common structure.
For example,
• a Markov chain consists of a set of states and a collection of transition probabilities for
moving across states
• a general equilibrium theory consists of a commodity space, preferences, technologies,
and an equilibrium definition
• a game consists of a list of players, lists of actions available to each player, player pay-
offs as functions of all players’ actions, and a timing protocol
These are all abstractions that collect together “objects” of the same “type”.
Recognizing common structure allows us to employ common tools.
In economic theory, this might be a proposition that applies to all games of a certain type.
In Python, this might be a method that’s useful for all Markov chains (e.g., simulate).
When we use OOP, the simulate method is conveniently bundled together with the Markov
chain object.
3
4.1 Example: A Consumer Class
Admittedly a little contrived, this example of a class helps us internalize some new syntax.
Here’s one implementation
This class defines instance data wealth and three methods: __init__, earn and spend
• wealth is instance data because each consumer we create (each instance of the
Consumer class) will have its own separate wealth data.
The ideas behind the earn and spend methods were discussed above.
Both of these act on the instance data wealth.
The __init__ method is a constructor method.
Whenever we create an instance of the class, this method will be called automatically.
Calling __init__ sets up a “namespace” to hold the instance data — more on this soon.
We’ll also discuss the role of self just below.
Usage
Here’s an example of usage
4
Out[4]: 5
In [5]: c1.earn(15)
c1.spend(100)
Insufficent funds
We can of course create multiple instances each with its own data
In [6]: c1 = Consumer(10)
c2 = Consumer(12)
c2.spend(4)
c2.wealth
Out[6]: 8
In [7]: c1.wealth
Out[7]: 10
In [8]: c1.__dict__
In [9]: c2.__dict__
Out[9]: {'wealth': 8}
When we access or set attributes we’re actually just modifying the dictionary maintained by
the instance.
Self
If you look at the Consumer class definition again you’ll see the word self throughout the
code.
The rules with self are that
– e.g., the earn method references self.wealth rather than just wealth
• Any method defined within the class should have self as its first argument
There are no examples of the last rule in the preceding code but we will see some shortly.
Details
In this section, we look at some more formal details related to classes and self
5
• You might wish to skip to the next section on first pass of this lecture.
• You can return to these details after you’ve familiarized yourself with more examples.
Methods actually live inside a class object formed when the interpreter reads the class defini-
tion
{'__module__': '__main__', '__init__': <function Consumer.__init__ at 0x104a307b8>, 'earn': <function Consumer.earn at 0x104
Note how the three methods __init__, earn and spend are stored in the class object.
Consider the following code
In [11]: c1 = Consumer(10)
c1.earn(10)
c1.wealth
Out[11]: 20
When you call earn via c1.earn(10) the interpreter passes the instance c1 and the argument
10 to Consumer.earn.
In fact, the following are equivalent
• c1.earn(10)
• Consumer.earn(c1, 10)
In the function call Consumer.earn(c1, 10) note that c1 is the first argument.
Recall that in the definition of the earn method, self is the first parameter
The end result is that self is bound to the instance c1 inside the function call.
That’s why the statement self.wealth += y inside earn ends up modifying c1.wealth.
For our next example, let’s write a simple class to implement the Solow growth model.
The Solow growth model is a neoclassical growth model where the amount of capital stock
per capita 𝑘𝑡 evolves according to the rule
𝑠𝑧𝑘𝑡𝛼 + (1 − 𝛿)𝑘𝑡
𝑘𝑡+1 = (1)
1+𝑛
Here
6
• 𝑠 is an exogenously given savings rate
• 𝑧 is a productivity parameter
• 𝛼 is capital’s share of income
• 𝑛 is the population growth rate
• 𝛿 is the depreciation rate
The steady state of the model is the 𝑘 that solves Eq. (1) when 𝑘𝑡+1 = 𝑘𝑡 = 𝑘.
Here’s a class that implements this model.
Some points of interest in the code are
• An instance maintains a record of its current capital stock in the variable self.k.
– Notice how inside update the reference to the local method h is self.h.
"""
def __init__(self, n=0.05, # population growth rate
s=0.25, # savings rate
�=0.1, # depreciation rate
�=0.3, # share of labor
z=2.0, # productivity
k=1.0): # current capital stock
def h(self):
"Evaluate the h function"
# Unpack parameters (get rid of self to simplify notation)
n, s, �, �, z = self.n, self.s, self.�, self.�, self.z
# Apply the update rule
return (s * z * self.k**� + (1 - �) * self.k) / (1 + n)
def update(self):
"Update the current state (i.e., the capital stock)."
self.k = self.h()
def steady_state(self):
"Compute the steady state value of capital."
# Unpack parameters (get rid of self to simplify notation)
n, s, �, �, z = self.n, self.s, self.�, self.�, self.z
# Compute and return steady state
return ((s * z) / (n + �))**(1 / (1 - �))
7
Here’s a little program that uses the class to compute time series from two different initial
conditions.
The common steady state is also plotted for comparison
s1 = Solow()
s2 = Solow(k=8.0)
T =
fig, ax = plt.subplots(figsize=(9, 6))
ax.legend()
plt.show()
Next, let’s write a class for a simple one good market where agents are price takers.
The market consists of the following objects:
8
Here
The class provides methods to compute various values of interest, including competitive equi-
librium price and quantity, tax revenue raised, consumer surplus and producer surplus.
Here’s our implementation
class Market:
"""
self.ad, self.bd, self.az, self.bz, self.tax = ad, bd, az, bz, tax
if ad < az:
raise ValueError('Insufficient demand.')
def price(self):
"Return equilibrium price"
return (self.ad - self.az + self.bz * self.tax) / (self.bd + self.bz)
def quantity(self):
"Compute equilibrium quantity"
return self.ad - self.bd * self.price()
def consumer_surp(self):
"Compute consumer surplus"
# == Compute area under inverse demand function == #
integrand = lambda x: (self.ad / self.bd) - (1 / self.bd) * x
area, error = quad(integrand, 0, self.quantity())
return area - self.price() * self.quantity()
def producer_surp(self):
"Compute producer surplus"
# == Compute area above inverse supply curve, excluding tax == #
integrand = lambda x: -(self.az / self.bz) + (1 / self.bz) * x
area, error = quad(integrand, 0, self.quantity())
return (self.price() - self.tax) * self.quantity() - area
def taxrev(self):
"Compute tax revenue"
return self.tax * self.quantity()
9
equilibrium price = 18.5
Here’s a short program that uses this class to plot an inverse demand curve together with in-
verse supply curves with and without taxes
q_max = m.quantity() * 2
q_grid = np.linspace(0.0, q_max, 100)
pd = m.inverse_demand(q_grid)
ps = m.inverse_supply(q_grid)
psno = m.inverse_supply_no_tax(q_grid)
fig, ax = plt.subplots()
ax.plot(q_grid, pd, lw=2, alpha=0.6, label='demand')
ax.plot(q_grid, ps, lw=2, alpha=0.6, label='supply')
ax.plot(q_grid, psno, '--k', lw=2, alpha=0.6, label='supply without tax')
ax.set_xlabel('quantity', fontsize=14)
ax.set_xlim(0, q_max)
ax.set_ylabel('price', fontsize=14)
ax.legend(loc='lower right', frameon=False, fontsize=14)
plt.show()
10
• computes dead weight loss from the imposition of the tax
Out[20]: 1.125
Let’s look at one more example, related to chaotic dynamics in nonlinear systems.
One simple transition rule that can generate complex dynamics is the logistic map
Let’s write a class for generating time series from this model.
Here’s one implementation
def update(self):
"Apply the map to update state."
self.x = self.r * self.x *(1 - self.x)
11
This piece of code plots a longer trajectory
fig, ax = plt.subplots()
ax.set_xlabel('$t$', fontsize=14)
ax.set_ylabel('$x_t$', fontsize=14)
x = ch.generate_sequence(ts_length)
ax.plot(range(ts_length), x, 'bo-', alpha=0.5, lw=2, label='$x_t$')
plt.show()
ax.set_xlabel('$r$', fontsize=16)
plt.show()
12
On the horizontal axis is the parameter 𝑟 in Eq. (2).
The vertical axis is the state space [0, 1].
For each 𝑟 we compute a long time series and then plot the tail (the last 50 points).
The tail of the sequence shows us where the trajectory concentrates after settling down to
some kind of steady state, if a steady state exists.
Whether it settles down, and the character of the steady state to which it does settle down,
depend on the value of 𝑟.
For 𝑟 between about 2.5 and 3, the time series settles into a single fixed point plotted on the
vertical axis.
For 𝑟 between about 3 and 3.45, the time series settles down to oscillating between the two
values plotted on the vertical axis.
For 𝑟 a little bit higher than 3.45, the time series settles down to oscillating among the four
values plotted on the vertical axis.
Notice that there is no value of 𝑟 that leads to a steady state oscillating among three values.
5 Special Methods
Python provides special methods with which some neat tricks can be performed.
For example, recall that lists and tuples have a notion of length and that this length can be
queried via the len function
13
Out[25]: 2
If you want to provide a return value for the len function when applied to your user-defined
object, use the __len__ special method
def __len__(self):
return 42
Now we get
In [27]: f = Foo()
len(f)
Out[27]: 42
In [29]: f = Foo()
f(8) # Exactly equivalent to f.__call__(8)
Out[29]: 50
6 Exercises
6.1 Exercise 1
The empirical cumulative distribution function (ecdf) corresponding to a sample {𝑋𝑖 }𝑛𝑖=1 is
defined as
1 𝑛
𝐹𝑛 (𝑥) ∶= ∑ 1{𝑋𝑖 ≤ 𝑥} (𝑥 ∈ R) (3)
𝑛 𝑖=1
Here 1{𝑋𝑖 ≤ 𝑥} is an indicator function (one if 𝑋𝑖 ≤ 𝑥 and zero otherwise) and hence 𝐹𝑛 (𝑥)
is the fraction of the sample that falls below 𝑥.
The Glivenko–Cantelli Theorem states that, provided that the sample is IID, the ecdf 𝐹𝑛 con-
verges to the true distribution function 𝐹 .
Implement 𝐹𝑛 as a class called ECDF, where
14
• A given sample {𝑋𝑖 }𝑛𝑖=1 are the instance data, stored as self.observations.
• The class implements a __call__ method that returns 𝐹𝑛 (𝑥) for any 𝑥.
6.2 Exercise 2
𝑁
𝑝(𝑥) = 𝑎0 + 𝑎1 𝑥 + 𝑎2 𝑥2 + ⋯ 𝑎𝑁 𝑥𝑁 = ∑ 𝑎𝑛 𝑥𝑛 (𝑥 ∈ R) (4)
𝑛=0
The instance data for the class Polynomial will be the coefficients (in the case of Eq. (4), the
numbers 𝑎0 , … , 𝑎𝑁 ).
Provide methods that
7 Solutions
7.1 Exercise 1
In [30]: class ECDF:
15
In [31]: # == test == #
print(F(0.5))
0.6
0.509
7.2 Exercise 2
In [32]: class Polynomial:
def differentiate(self):
"Reset self.coefficients to those of p' instead of p."
new_coefficients = []
for i, a in enumerate(self.coefficients):
new_coefficients.append(i * a)
# Remove the first element, which is zero
del new_coefficients[0]
# And reset coefficients data to new values
self.coefficients = new_coefficients
return new_coefficients
References
16