Author: Spencer Lyon
So far in your python training you have been introduced to many different ideas and concepts. You have probably heard the term “object” a few times, without really understanding what it means. This being true, the code you have written up to this point has been object based – you have passed around objects in your scripts, used them in expressions, called their methods, ect. In order for your code to be truly object-oriented you need to start to defining your own classes.
This lab will teach you the basics of OOP and get you some hands-on experience using them in your code.
The term object oriented programming encapsulates a new way of thinking about your code. Instead of having your programs simply being a list of instructions to be executed in sequential order when your script is run, OOP takes a more abstract approach that focuses more on the relationship between the different parts of your code and which tasks should be performed by each part.
At this point, you might be wondering why you need to learn a whole new way to think about programming. Hasn’t what you have been doing all along good enough? The answer is ... kind of. What you have been doing up to this point, writing out a list of instructions for the interpreter to execute, is known as procedural programming. It is the foundation of modern computer science. For this reason old, powerful languages like C and Fortran, which do not support OOP by themselves, are classified as procedural programming languages. However, there has been a clear shift in mainstream programming toward newer languages that do support OOP. Some examples are Java, C++, Python (surprise!), Ruby, C#, Objective-C, and many more. So why are more and more programmers using OOP in their code? I can think of 2 (there are definitely more) distinct advantages an OOP approach has over a standard procedural approach.
Code re-usability: This is probably the biggest plus for OOP. With OOP you define objects that have certain properties. If you then realize that you need an object with very similar features, you can simply subclass the original object and save your self from writing all that code over again. Although copy/paste is a plausible form of code reuse, it quickly becomes difficult to manage with larger, more complicated projects. As an example, say you wanted to code an OLG model. One way to do this from and OOP persepective might be to define the following classes with respective properties:
- Agent:
- age property that specifies the agent’s age.
- utility method (function) that returns the utility for a given state of the economy and a given choice of consumption and labor supply.
- consume method that evaluates the optimal amount of food to consume given a current state of the economy – uses the utility method.
- work method that returns the optimal amount of labor to supply given the current state of the economy – uses the utility method.
- Firm:
- produce method that returns how much of a good is produced given the state of the economy
- captial property that specifies the aggregate capital stock in the current period
- labor property that specifies the total supply of labor in the current period
- Economy:
- period property that keeps track of the time period
- agents list that contains a list of all current agents
- firms list that contains a list of all current firms
- shocks dictionary of pandas.Series objects that specify the exogenous shocks over time
- endog_state property of a pandas.DataFrame containing the endogenous state data over time.
- gather_choices method that sends each object in the the agents and firms lists the current data and receives the choice variables.
You could then create multiple instances of the Agent class to represent agents of different ages in your economy. You would also need at least one Firm instance to represent the other half of the market and a single Economy instance to keep everything organized (you could also have methods in the Economy class that would gather consumption, labor supply and production results from each of the other instances in the economy).
Unite data and functionality: An object is really just a generic container in which you can place data and different methods that act on that data. You have already seen this benefit in the BSplineBasis class you wrote earlier. Had you tried to do this without object oriented programming, every function that makes up the spline (generating knots, defining basis functions, evaluating or plotting the basis functions, ect.) would have had to have additional arguments that specify the details of the spline (things like degree, number of knots, the knot vector or list of basis functions, ect.). Even in this small example you can see how this could get out of hand with a bigger problem.
Python is, at its core, an object oriented programming language. Everything you use or variable you define in python is an object. You can verify this using the type function. I will demonstrate below.
In [11]: type(42)
Out[11]: int
In [12]: type(1.2)
Out[12]: float
In [13]: type('foo')
Out[13]: str
In [14]: type([1, 3, 4])
Out[14]: list
In [15]: type({1:3, 2:4})
Out[15]: dict
In [16]: type(('b', [3], 4))
Out[16]: tuple
In [17]: type(np.array([1, 3, 4]))
Out[17]: numpy.ndarray
In [18]: type(pd.Series([1, 2]))
Out[18]: pandas.core.series.Series
Every object in python has characteristics that are broken into two main categories: 1. [data] attributes, and 2. methods. Attributes are the specific pieces of data that make one object unique from another. Methods are python functions that act on that data. As an example consider a python list:
In [19]: a = [1, 2, 'string', 2.3]
The data for this list are the items in the list. In this example they are the int 1, the int 2, the str ‘string’, and the float 2.3. Lets take a look at what methods are available to a list:
In [20]: not_special = lambda x: not x.startswith('__')
In [21]: filter(not_special, dir(a))
Out[21]:
['append',
'count',
'extend',
'index',
'insert',
'pop',
'remove',
'reverse',
'sort']
Calling any of these methods will apply that python function to the list and alter it somehow:
In [22]: a
Out[22]: [1, 2, 'string', 2.3]
# reverse order of data
In [23]: a.reverse()
In [24]: a
Out[24]: [2.3, 'string', 2, 1]
# return the last item in the list and remove it from the list
In [25]: a.pop()
Out[25]: 1
# see, it's gone!
In [26]: a
Out[26]: [2.3, 'string', 2]
The class keyword is used to define new types of objects in python. A class definition will describe what kind of data it stores and what functions are available for acting on this data (functions defined within classes are referred to as methods). On their own, class definitions are not useful, just like defining a function without calling it is not useful. To make use of a class, you need to create an object of that class or type. These are known as instances of the class. It is most common for each instance of a class to have its own unique data, while the methods are common across instances.
Throughout the rest of this lab we will focus on how to create and use new objects we define.
As noted above, new classes are defined using the class keyword. Imagine I wanted to create a class that represented a die. I would begin like this:
1 2 | class Die(object):
pass
|
I will break this down one word at a time:
After having told python that I will be defining a new class, I need to tell it what should happen when a new instance of this class is created. This happens in the __init__ method (the two leading and trailing underscores are important and we will talk more about them shortly). This is the function that is called when we are creating a new class. For the Die, it might looks something like this:
1 2 3 4 | class Die(object):
def __init__(self, sides=6, dots=None):
self.nsides = sides
self.dots = dots
|
Note
In all the examples in this lab I will omit docstrings and I may, at times, number lines in a non PEP8 compliant way as I have done above. I am doing this just for instructional purposes. When actually writing code that will be executed I wouldn’t do this.
I will break this down one line at a time:
At this point, I could create an instance of the Die class, but it wouldn’t be able to do much. Before we do that I will add one more method: roll. As I am sure you can guess, this method will simulate the rolling of the die and update the dots parameter of the object.
1 2 3 4 5 6 7 8 9 | from random import randint
class Die(object):
def __init__(self, sides=6, dots=None):
self.nsides = sides
self.dots = dots
def roll(self):
self.dots = randint(1, self.nsides)
|
Explanation by line:
I am now going to create a new instance of my Die class and roll it a few times.
In [27]: d = Die(6) # [1]
In [28]: d.nsides # [2]
Out[28]: 6
In [29]: d.dots # [3]
In [30]: d.roll() # [4]
In [31]: d.dots # [5]
Out[31]: 1
In [32]: d.roll() # [6]
In [33]: d.dots # [7]
Out[33]: 6
In [34]: d # [8]
Out[34]: <__main__.Die at 0x111a79610>
Explanation by line:
Notice that when I am defining the class or calling a method that I treat the self parameter to the methods as if is isn’t there. I tell the __init__ methods the value of its sides argument by passing it as the first argument to Die.
In a python class having a method name start and end with two underscores tells python that it is a special or magic method. These methods are usually used to allow access to object’s data or functions via python keywords or operators. This is a bit abstract and I best understood by example. Below is a table of many of the most common special methods:
Method Name | Description | Usage |
---|---|---|
__repr__ | Returns the string printed to the screen when an object is not assigned a name | d |
__str__ | Called when str or print is called on the object | str(d) or print(d) |
__sub__ | Called when using - | d - d |
__ge__ | Called when using `` >`` | d1 > d2 |
__getitem__ | Called when trying to index object | d1[2] |
__cmp__ | Implements the full suite of comparison operators | d > d or d == d1, ect. |
__rsub__ | Called when using -= | d -= 1 |
__len__ | Called when using len() | len(d) |
__call__ | Called when using d() | d() |
I have barely scratched the surface of the special methods in python. For example, in addition to __sub__ there are the __mul__, __div__, __add__ methods that implement other basic mathematical operations. Another example is that in addition to __ge__, there are 5 other comparison operator methods to be used when calling <, <=, >=, ==, and !=. See the official Python docs on special methods and this great site on Operator Overloading for more in-depth examples.
Let’s add a few of these to the Die class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | class Die(object):
def __init__(self, sides=6, dots=None):
self.nsides = sides
self.dots = dots
if self.dots is None:
self.roll()
def __repr__(self):
return str(self.dots)
def __str__(self):
return self.__repr__()
def __unicode__(self):
return self.__repr__()
def __lt__(self, other):
return self.dots < other.dots
def __le__(self, other):
return self.dots <= other.dots
def __eq__(self, other):
return self.dots == other.dots
def __ne__(self, other):
return self.dots != other.dots
def __gt__(self, other):
return self.dots > other.dots
def __ge__(self, other):
return self.dots >= other.dots
def roll(self):
# Make sure we roll between 1 and nsides
self.dots = randint(1, self.nsides)
return self.dots
|
I will now test some of these out:
In [35]: d1 = Die(6, 5)
In [36]: d2 = Die(6, 4)
In [37]: d1
Out[37]: 5
In [38]: d2
Out[38]: 4
In [39]: d1 > d2
Out[39]: True
In [40]: d2 <= d1
Out[40]: True
In [41]: d1 == d2
Out[41]: False
Another term in object-oriented programming is subclass. This term means that one class is a child, or inherits from another class. When this happens, all methods and data that are part of the first, super, or parent class become part of the subclass. There is the added benefit, however that you can override the default behavior of the parent class.
As an example, suppose we wanted to create a new class called StandardDie. This class would be a subclass of the Die class we wrote earlier, but we would only allow it to have 6 sides. Suppose also that we wanted to update the printing system for the class and give it some very basic ASCII art representing the face of the die. I have done this below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | class StandardDie(Die):
def __init__(self, dots=1):
super(StandardDie, self).__init__(sides=6, dots=dots)
self.dots = dots
self.roll()
def __repr__(self):
if self.dots == 1:
msg = "\n x\n"
elif self.dots == 2:
msg = "x\n\n x"
elif self.dots == 3:
msg = "x\n x\n x"
elif self.dots == 4:
msg = "x x\n\nx x"
elif self.dots == 5:
msg = "x x\n x\nx x"
elif self.dots == 6:
msg = "x x\nx x\nx x\n"
else:
msg = ""
return msg
|
We will test to make sure the StandardDie class has all the comparison operators and roll function that were only implemented in the Die class, but also make sure that the new printing system took hold:
In [42]: d1 = Die(6, dots=4)
In [43]: d2 = StandardDie(4) # remember only dots is a parameter here
In [44]: d1
Out[44]: 4
In [45]: d2
Out[45]:
x x
x x
In [46]: d1 > d2
Out[46]: False
In [47]: d1 == d2
Out[47]: True
In [48]: d2.roll()
Out[48]: 4
In [49]: d2
Out[49]:
x x
x x
In [50]: def check_inheritance(x):
....: return isinstance(x, Die), isinstance(x, StandardDie)
....:
# Should be True, False
In [51]: check_inheritance(d1)
Out[51]: (True, False)
# Both True
In [52]: check_inheritance(d2)
Out[52]: (True, True)
I have taken this dice example and extended it to create the game Yhatzee. You can download the file here, but I will include the other pieces here for completeness.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 | from random import choice, randint, shuffle
from string import digits
import re
import numpy as np
import pandas as pd
_parse_selection = re.compile('(\d)')
def _random_name():
return 'Player' + ''.join(choice(digits) for i in range(2))
# Score card constants
_top_index = ['Ones', 'Twos', 'Threes', 'Fours', 'Fives', 'Sixes',
'Bonus']
_bot_index = ['ThreeOfKind', 'FourOfKind', 'FullHouse', 'SmStraight',
'LgStraight', 'Yhatzee', 'Chance', 'Bonus']
_arrs = [['Top'] * len(_top_index) + ['Bottom'] * len(_bot_index) + ['Total'],
_top_index + _bot_index + ['']]
_ind = pd.MultiIndex.from_arrays(_arrs)
class Die(object):
def __init__(self, sides=6, dots=None):
self.nsides = sides
self.dots = dots
if self.dots is None:
self.roll()
def __repr__(self):
return str(self.dots)
def __str__(self):
return self.__repr__()
def __unicode__(self):
return self.__repr__()
def __lt__(self, other):
return self.dots < other.dots
def __le__(self, other):
return self.dots <= other.dots
def __eq__(self, other):
return self.dots == other.dots
def __ne__(self, other):
return self.dots != other.dots
def __gt__(self, other):
return self.dots > other.dots
def __ge__(self, other):
return self.dots >= other.dots
def roll(self):
# Make sure we roll between 1 and nsides
self.dots = randint(1, self.nsides)
return self.dots
class StandardDie(Die):
def __init__(self, dots=1):
super(StandardDie, self).__init__(sides=6, dots=dots)
self.dots = dots
self.roll()
def __repr__(self):
if self.dots == 1:
msg = "\n x\n"
elif self.dots == 2:
msg = "x\n\n x"
elif self.dots == 3:
msg = "x\n x\n x"
elif self.dots == 4:
msg = "x x\n\nx x"
elif self.dots == 5:
msg = "x x\n x\nx x"
elif self.dots == 6:
msg = "x x\nx x\nx x\n"
else:
msg = ""
return msg
class Dice(object):
def __init__(self, numdice=5, sides=6, die_class=Die):
self.nsides = sides
self.ndice = numdice
self.dice = [die_class(6) for i in xrange(numdice)]
@property
def dots(self):
return [i.dots for i in self.dice]
def __repr__(self):
msg = 'The dice are:\n\n'
msg += str('\n' +'-'*8 + '\n').join([repr(i) for i in self.dice])
msg += '\n\n'
msg += 'index: value\n'
msg += '\n'.join([' %i: %i' % (i, j) for i, j in enumerate(self.dots)])
return msg
def __str__(self):
return self.__repr__()
def __unicode__(self):
return self.__repr__()
def __len__(self):
return len(self.mdots)
def __getitem__(self, index):
return self.dice[index]
def __setitem__(self, index, value):
self.dice[index].dots = value
def __iter__(self):
return iter(self.dice)
def roll(self, index='all'):
if index == 'all':
for i in self.dice:
i.roll()
elif isinstance(index, int):
# Just roll 1
self.dice[index].roll()
elif isinstance(index, list):
# Roll all in the index list
for i in index:
self.dice[i].roll()
class YhatzeeScoreCard(object):
def __init__(self, players=None):
"""
Score card for Yhatzee.
Parameters
----------
players : iterable of strings
A list, tuple, array, ect. containing Player Instances
"""
if players is None:
# Just make 2 random names
players = [_random_name() for i in xrange(2)]
# TODO: Should we use a DataFrame here or just roll our own class?
self.card = pd.DataFrame(np.zeros((len(_ind), len(players))),
index=_ind, columns=players)
else:
# Use the Players' score_card as columns
self.card = pd.DataFrame([i.score_card for i in players]).T
def __repr__(self):
# Just use pandas
return repr(self.card)
def __str__(self):
return self.__repr__()
def __unicode__(self):
return self.__repr__()
def update(self, players):
self.card = pd.DataFrame([i.score_card for i in players]).T
class YhatzeeScorer(object):
def __init__(self, dice):
# make a series out of the dice
self.dice = dice
self.d_Series = pd.Series(dice.dots)
self.v_counts = pd.DataFrame(self.d_Series.value_counts(),
index=range(1, 7))
self.v_counts = self.v_counts.fillna(0)
def score_numeric(self, num):
"""Computes numeric score for num (top of scorecard)"""
vc = self.v_counts.copy()
vc[0] = vc[0].values * vc.index.values
return int(vc.ix[num])
def _score_3_of_kind(self):
"""Tries to compute score of 3 of a kind"""
if int(self.v_counts.max()) < 3:
return 0
else:
return self.d_Series.sum()
def _score_4_of_kind(self):
"""Tries to compute score of 4 of a kind"""
if int(self.v_counts.max()) < 4:
return 0
else:
return self.d_Series.sum()
def _score_full_house(self):
"""Tries to compute score of full_house"""
sort_counts = self.v_counts.sort(0, ascending=0)
if sort_counts.iloc[0] == 3 and sort_counts.iloc[1] == 2:
return 25
else:
return 0
def _score_sm_straight(self):
"""Tries to compute score of small straight"""
t1 = np.array([1, 2, 3, 4])
t2 = np.array([2, 3, 4, 5])
t3 = np.array([3, 4, 5, 6])
n_zero = self.v_counts[0].nonzero()[0] + 1
if n_zero.size > 3:
c1 = np.array(n_zero == t1).all()
c2 = np.array(n_zero == t2).all()
c3 = np.array(n_zero == t3).all()
if c1 or c2 or c3:
return 30
return 0
def _score_lg_straight(self):
"""Tries to compute score of large straight"""
t1 = np.array([1, 2, 3, 4, 5])
t2 = np.array([2, 3, 4, 5, 6])
n_zero = self.v_counts[0].nonzero()[0] + 1
if n_zero.size == 5:
if all(n_zero == t1) or all(n_zero == t2):
return 40
return 0
def _score_yhatzee(self):
"""Tries to compute score of yhatzee"""
if int(self.v_counts.max()) == 5:
return 50
else:
return 0
def _score_bonus(self):
"""Tries to compute score of 3 of a kind"""
pass
def _score_chance(self):
return self.d_Series.sum()
def score_special(self):
"""Uses the '_' methods to score the bottom"""
pass
def score_all(self, c_name=0):
# Blank score_card to fill in
temp_sc = pd.DataFrame([0]*16, index=_ind, columns=[c_name])
temp_sc = temp_sc.drop(('Top', 'Bonus'))
temp_sc = temp_sc.drop(('Total', ''))
for i in range(6):
temp_sc.ix['Top', _top_index[i]] = self.score_numeric(i + 1)
temp_sc.ix['Bottom', 'ThreeOfKind'] = self._score_3_of_kind()
temp_sc.ix['Bottom', 'FourOfKind'] = self._score_4_of_kind()
temp_sc.ix['Bottom', 'FullHouse'] = self._score_full_house()
temp_sc.ix['Bottom', 'SmStraight'] = self._score_sm_straight()
temp_sc.ix['Bottom', 'LgStraight'] = self._score_lg_straight()
temp_sc.ix['Bottom', 'Yhatzee'] = self._score_yhatzee()
temp_sc.ix['Bottom', 'Chance'] = self._score_chance()
return temp_sc
class Player(object):
def __init__(self, name=None):
if name is None:
name = _random_name()
self.name = name
self.score_card = pd.Series(np.zeros(len(_ind)), index=_ind, name=name)
self.have_chosen = set()
#### Operators to see how this Player's score compares to others
def __lt__(self, other):
return self.score < other.score
def __le__(self, other):
return self.score <= other.score
def __eq__(self, other):
return self.score == other.score
def __ne__(self, other):
return self.score != other.score
def __gt__(self, other):
return self.score > other.score
def __ge__(self, other):
return self.score >= other.score
### Let's print it real good!
def __repr__(self):
# Just use pandas, they print pretty good
return repr(self.score_card)
def __str__(self):
return self.__repr__()
def __unicode__(self):
return unicode(self.__repr__())
def _prep_score_chooser(self, dice):
# Get the scorer
scorer = YhatzeeScorer(dice)
# Have him score everything
df = scorer.score_all()
df = df.rename_axis({0:'new'})
df['existing'] = self.score_card
df['index'] = range(df.shape[0])
df = df.reindex(columns=['existing', 'new', 'index'])
return df
def _get_ind_choice(self, df):
print(df)
# Let user choose which score to keep
ind = raw_input('Choose index of score you want to keep: ')
ind = self._verify_ind_choice(ind)
if ind is None:
ind = self._get_ind_choice(df)
return ind
def _verify_ind_choice(self, ind):
try:
ind = int(ind)
return ind
except:
print("Couldn't convert your choice to an integer, try again")
return None
def choose_score(self, dice):
df = self._prep_score_chooser(dice)
success = False
while not success:
ind = self._get_ind_choice(df)
i_ind = df[df['index'] == ind].index
if self.score_card.ix[i_ind] == 0 and not ind in self.have_chosen:
# good to go
try:
self.score_card.ix[i_ind] = df[df['index'] == ind]['new']
success = True
self.have_chosen.add(ind)
except IndexError:
print("Invalid selection. Must be in [0, 13]")
else:
print("Can't replace previous score (even if it was 0), "
+ "make selection again")
success = False
class YhatzeeManager(object):
def __init__(self, players, score_card, scorer):
self.players = players
self.score_card = score_card
self.scorer = scorer
# Keep track of what round we are on (only 13 number in yhatzee)
self.round = 0
# Get some dice for the game
self.dice = Dice(5, 6, StandardDie)
# Randomize turn order
shuffle(self.players)
def _get_die_selection(self):
try:
play_str = raw_input('Give index of which dice to re-roll '+
'(numbers separated by commas or spaces or ' +
'type (c) to check score_card): ')
to_roll = [int(i) for i in _parse_selection.findall(play_str)]
if play_str == 'c':
print(self.score_card)
self._get_die_selection()
except SyntaxError:
# Player doesn't want to
to_roll = None
return to_roll
def _do_roll(self, n_roll, index='all'):
dice = self.dice
# Roll the dice
dice.roll(index)
# Show them and get selection
print(dice)
if n_roll < 3:
# only on first or second roll
rollers = self._get_die_selection()
else:
rollers = None
return rollers
def _do_turn(self, player):
dice = self.dice
print('\n\n%s it is your turn' % player.name)
n_rolls = 1
rollers = self._do_roll(n_rolls)
if rollers is None:
player.choose_score(dice)
else:
n_rolls = 2
rollers = self._do_roll(n_rolls, rollers)
if rollers is None:
player.choose_score(dice)
else:
n_rolls = 3
self._do_roll(n_rolls, rollers)
player.choose_score(dice)
self.score_card.update(self.players)
def _check_bonuses(self):
bonuses = (self.score_card.card.xs('Top').drop('Bonus').sum() >= 63)
self.score_card.card.ix['Top', 'Bonus'] = bonuses * 35
def play_game(self):
while self.round < 13:
for p in self.players:
self._do_turn(p)
self._check_bonuses()
self.round += 1
print('\n\n\n')
print('=' * 78)
print('=' * 78)
print('After round %i the score is: ' % (self.round))
self.score_card.card.iloc[-1] = self.score_card.card.sum()
print(self.score_card)
winner = self.score_card.card.sum().idxmax()
print('\n\n')
print('!' * 78)
print('!' * 78)
print('And the winner is...' + '\n'*5)
print(winner)
def run_test_game():
the_dice = Dice(5, 6, StandardDie)
p1 = Player('Spencer')
p2 = Player('Chase')
score_card = YhatzeeScoreCard([p1, p2])
scorer = YhatzeeScorer(the_dice)
manager = YhatzeeManager([p1, p2], score_card, scorer)
manager.play_game()
|
Todo
Using the provided yhatzee game as an example, create the dice game farkle. You can find the Farkle rules at the linked website. A lot of the logic for prompting players and controlling the game is shown in the yhatzee example.
Note
For this problem and the ones to follow you may need to search the web for more guidance and examples of OOP in practice. I have only included this one example by design, as I feel that at this point in your programming careers you should learn to be comfortable searching for answers on your own. We are, of course, willing to help, but we encourage you to do as much as you can with your fellow boot-campers and the internet.
As you learned during an economics lecture earlier in bootcamp, there are many different distributions. Many of these distributions are related in a tree-like manner. Below is an image of a distribution tree for the skewed generalized T distribution family:
The pdf for the SGT (top of the tree) is given below:
where \(\beta(a, b)\) is the beta function of \(a\) and \(b\).
Todo
Implement the entire SGT from the diagram. For each distribution you need to have attributes representing the support and, when possible, first two moments (mean and standard deviation) of the distribution. You must also have methods for the pdf of the distribution and for the cdf. Note that often the cdf has no closed-form representation. In cases such as these, approximate the cdf numerically using an integration routine like that found in scipy.integrate.quad. Get the limits of integration using the information on the support you included as an attribute for the class.
Hint: By far the easiest way to do this problem is to come up with a good implementation for the distribution at the top of the tree, in this case the SGT. You can then work your way down the tree by subclassing the more general distributions and pinning parameters down, similar to how I subclassed Die when I created StandardDie.
Hint2: You will find the function scipy.special.beta useful.
Disclaimer: This problem comes from a working paper by Christian Baker, Jeremy Bejarano, Rick Evans, Ken Judd, and Kerk Phillips (they might be good people to ask for help with understanding the problem).
In the working paper, they develop a sales tax model that features households and the government (partial equilibrium because they don’t compute the production side of the market). In this section of the lab, we will ask you to implement the household’s problem using the object oriented concepts you have learned in the lab.
Let the economy be characterized by \(I\) different consumption goods \(c_i\), where \(i=1, 2,...I\). Define aggregate consumption \(C\) by the constant elasticity of substitution (CES) aggregator:
\[C \equiv \left(\sum_{i=1}^I \alpha_i(c_i - \bar{c}_i)^{\frac{\eta - 1}{\eta}} \right) ^{\frac{\eta}{\eta - 1}}\]
where \(\eta \ge 1\) is the elasticity of substitution among all of the consumption goods, \(\alpha_i \in [0, 1]\) is the weight on the consumption of each type of good with \(\sum_i \alpha_i = 1\), and \(\bar{c}_i \ge 0\) is a minimum level of consumption for each type of good. The constant relative risk aversion (CRRA) utility function for the individual with CES preferences over consumption is the following:
where the aggregate consumption of an individual \(C\) is defined above and \(\gamma\) is the coefficient of relative risk aversion.
Let the price of consumption good \(i\) be determined by the competitive equilibrium assumption of marginal cost pricing with the additional sales tax levied on good \(i\). If \(mc_i\) is the marginal cost of producing good \(i\) and \(\tau_i\) is the sales tax rate on good \(i\), then competitive equilibrium implies that he price of each consumptino good \(p_i\) is given by
We can normalize the marginal cost of each good to unity \(mc_i = 1\) for all \(i\) by simply changing the units of each good. So the price of each good can be simplified to its normalized version.
Given a nominal wage of \(w\), the household budget constraint is:
The household’s problem is to choose a consumption basket \(\{c_i\}_{i=1}^I\) to maximize utility subject to this budget constraing. In this example, we will allow consumers to be heterogeneous in terms of their elasticity of substitution \(\eta\) among different consumption goods and in terms of income \(w\). So \(\theta = (\eta, w) \in \Theta = [1, \infty) \times (0, \infty)\). Let the joint distribution over consumer types in the economy be \(\Gamma(\eta, w) = \Gamma(\eta) \Gamma(w)\), where \(\eta \sim ([\eta_{min}, \eta_{max}])\) and \(w \sim GB2(a, b, p, q)\).
now we can write the consumers optimization problem in terms of vectors of variables,
where \(\boldsymbol{c} = \{c_i\}_{i=1}^I\) and \(\tau = \{\tau_i\}_{i=1}^I\). If the budget constraint binds and \(c_i \ge \bar{c}_i\) for all \(i\), the solution to the objective function can be summarized by \(I - 1\) Euler equations:
where \(w\) and all the \(tau_i\) are introduced into this equation because good \(c_I\) is substituted out of the utility function using the budget constraint. The solution to this problem is individual consumption functions \(c_i(\eta, w, \tau)\) that are functions of consumer types (\((\eta, w)\)) and tax rates \(\tau\). This household has equilibrium utility \(u\left(\boldsymbol{c}(\eta, w, \tau) \right)\) and total taxes paid of \(r(\eta, w, \tau) = \sum_{i=1}^I \tau_i c_i(\eta, w, \tau)\).
However, it is also the case that both the budget constraing and \(c_i \ge \bar{c}_i\) are not satisfied for some sales tax schedules \(\tau\).
Todo
In order to simplify this problem, we will permit you to assume that \(I=3\). Now write a python class for the agents in this model where there are subclasses for each agent (i.e. there should be a subclass for each individual). The inputs should be a wage, elasticity of substitution (\(\eta\)), and a tax schedule. Each of these subclasses should be able to return the following: the values for consumption, capital, amount payed in taxes, and utility. The class should also be able to return the both the steady state and current values of government revenue and overall welfare (utility).
Todo
Given a discrete support for \(w\) and \(\eta\), a tax schedule \(\tau\) do some really cool stuff. Your wage and elasticty values will respectively be \((.15, .85, 1.5)\) and \((2, 2, 3)\) where each value corresponds with one of the agents. Impose two different tax regimes: first is a constant tax rate of .35 and the other is a tax bracket where \(\tau = .5\) if \(w>1\) and \(\tau = .2\) if \(w \leq 1\). For each of these cases create a table comparing each agents’ utility, overall welfare, and government revenue (you can sum their utility and tax revenue to get overall welfare and government revenue). Which tax regime do you think is better and why.