Study Guide: Objects

Instructions

This is the companion guide to Quiz 6 with links to past lectures, assignments, and handouts, as well as isomorphic quiz problems and additional practice problems to assist you in learning the concepts.

Assignments

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, attributes, and methods.

Attributes

Attributes are the name-value bindings that describe the data 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 property 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 ***"
@property 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>