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.
Important: For solutions to these assignments once they have been released, see the main website
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 (
Objects introduce additional complexity as we can mutate their attributes. In
the functional data abstraction
tree, to 'change' the values, we needed to
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 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
>>> 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 ...>
Q1: Bad bank account
Implement the class
BadBankAccount, which is a subclass of
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
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.balanceself.balance = self.balance - amount return self.balance"*** YOUR CODE HERE ***"@property def overdrawn(self): return self.balance < 0
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>