Study Guide: Objects
Instructions
This is a study guide with links to past lectures, assignments, and handouts, as well as additional practice problems to assist you in learning the concepts.
Assignments
Important: For solutions to these assignments once they have been released, see the main website
Handouts
Lectures
Readings
Guides
Objects
We saw previously that sequence types like lists and dictionaries could combine values together in a single unit with data abstraction, but the way we accessed that data required writing additional constructors and selectors.
Objects and classes are a more convenient way to create data abstractions.
Object-oriented programming is a programming paradigm that combines
representation (data) together with behavior (functions). We can design a tree
structure both using functional data abstraction (tree
) as well as
object-based data abstraction (Tree
).
Objects introduce additional complexity as we can mutate their attributes. In
the functional data abstraction tree
, to 'change' the values, we needed to
call the tree
constructor and create a new tree with the desired values. With
Tree
objects, we can now modify the tree without creating new trees.
The Python documentation has a very thorough tutorial on Classes to help
understand how Python evaluates class
statements, data attributes, and methods.
Attributes
Attributes are the name-value bindings that describe the data and functions contained in an object, just like how name-value bindings in a frame describe the data contained in a frame. Attributes are accessed using dot notation.
>>> object.attribute
value
Dot notation can also be chained arbitrarily.
>>> object.attribute
another object
>>> object.attribute.attribute
value
And they are likewise assigned using the same dot notation.
>>> object.attribute = 0
>>> object.attribute
0
Class attributes are shared by all the instance of a class. They can be accessed from both the class as well as individual instances.
>>> class A:
... attribute = 0
...
>>> A.attribute
0
>>> a = A()
>>> a.attribute
0
We can also define functions in a class, and they are just functions.
>>> class A:
... def abs(x):
... if x > 0:
... return x
... elif x == 0:
... return 0
... else:
... return -x
...
>>> A.abs(-1)
1
>>> A.abs(0)
0
>>> A.abs(1)
1
Instance attributes are specific to each instance of a class.
>>> class A:
... def __init__(self):
... self.attribute = 1
...
>>> a = A()
>>> a.attribute
1
If a class attribute and instance attribute have the same name, then the instance attribute will hide the class attribute. The instance can access only the instance attribute, although the class can still access the class attribute.
>>> class A:
... attribute = 0
... def __init__(self, count):
... self.attribute = count
...
>>> A.attribute
0
>>> a = A(1)
>>> a.attribute
1
>>> A.attribute
0
Methods are instance attributes derived from class functions. A method can
be invoked through Class.function(object, *args)
or object.function(*args)
.
>>> class A:
... def __init__(self, name):
... self.name = name
... def excite(self, item):
... return self.name + ' just won a new ' + item + '!'
...
>>> a = A('John')
>>> A.excite(a, 'laptop')
'John just won a new laptop!'
>>> a.excite('car')
'John just won a new car!'
Python implements this through an object called a bound method which passes in the instance as the first argument.
>>> A.excite
<function A.excite at 0x...>
>>> a.excite
<bound method A.excite of ...>
Practice Problems
Easy
Q1: Bad bank account
Implement the class BadBankAccount
, which is a subclass of Account
.
BadBankAccount
allows the account holder to withdraw more money than they
have (i.e. overdraw). Once overdrawn, BadBankAccount
prevents the account
holder from withdrawing money again until the account has a positive balance.
You should also implement the property method overdrawn
, which returns
a boolean that tells whether or not an account is currently locked due to being
overdrawn.
class BadBankAccount(Account):
""" A subclass of bank account that allows an account holder to overdraw
once, and then prevents them from withdrawing more money. You should also
implement the method overdrawn, which allows an account holder to
check if they are overdrawn.
>>> harold_account = BadBankAccount('Harold')
>>> harold_account.deposit(100) # depositing my paycheck for the week
100
>>> harold_account.withdraw(101) # buying dinner
-1
>>> harold_account.overdrawn()
True
>>> harold_account.withdraw(100000)
You have overdrawn, please add more money!
-1
>>> harold_account.deposit(10)
9
>>> harold_account.overdrawn()
False
"""
def withdraw(self, amount):
"""Decrease the account balance by amount and return the
new balance.
"""
"*** YOUR CODE HERE ***"
if self.overdrawn():
print('You have overdrawn, please add more money!')
return self.balance self.balance = self.balance - amount
return self.balance
"*** YOUR CODE HERE ***"
def overdrawn(self):
return self.balance < 0
Medium
Q2: I Choose You!
Predict the result of evaluating the following calls in the interpreter. Then try them out yourself!
>>> class Pokemon:
... hp = 100
... damage = 25
... def __init__(self, name):
... self.name = name
... def attack(self, other):
... other.hp -= self.damage
... def __repr__(self):
... return self.name
...
>>> pika = Pokemon('Pikachu')
>>> pika.hp
______100
>>> pika.damage
______25
>>> pika.trainer = 'John'
>>> pika.trainer
______'John'
>>> Pokemon.trainer
______AttributeError: type object 'Pokemon' has no attribute 'trainer'
>>> bulba = Pokemon('Bulbasaur')
>>> bulba.hp -= pika.damage
>>> Pokemon.hp
______100
>>> pika.hp, bulba.hp
______(100, 75)
>>> pika.attack(bulba)
>>> pika.hp, bulba.hp
______(100, 50)
>>> Pokemon.attack(pika, bulba)
>>> pika.hp, bulba.hp
______(100, 25)
>>> pika.make_noise = lambda self: print('Pika pika!')
>>> pika.make_noise()
______TypeError: <lambda>() missing 1 required positional argument: 'self'
>>> pika.make_noise
______<function <lambda> at 0x...>
>>> pika.attack
______<bound method Pokemon.attack of Pikachu>
>>> pika.make_noise(pika)
______Pika pika!
>>> pika.make_noise('What?')
______Pika pika!
>>> pika.attack = lambda self, other: print("It doesn't affect " + other.name)
>>> pika.attack(bulba)
______TypeError: <lambda>() missing 1 required positional argument: 'other'
>>> pika.attack(pika, bulba)
______It doesn't affect Bulbasaur
>>> pika.attack
______<function <lambda> at 0x...>
>>> pika.attack = bulba.attack
>>> pika.attack(bulba)
>>> pika.hp, bulba.hp
______(100, 0)
>>> pika.attack
______<bound method Pokemon.attack of Bulbasaur>