Note
Class
A class combines( and abstracts) data and functions.
A object is an instantiation of class.
You can think a class as a blue print for a house ,so you just know the structure of this house and how to build it ,you can not really live in it . And the object is the house itself.
Class Statement
class < name >:
< suite >
Assignment & def statement in < suite > create attributes of the class (not names in frames).
Object Construction
When a class is called:
- A new instance of that class is created;
- The _init_ method of the class is called with the new object as its first argument (named self), along with any additional arguments provided in the call expression.
- _init_ is called a constructor.
- We can think self argument as the object we create . That is , self is the name that is going to refer to an instance of the class.
- The _init_ method should return None, or it will occur the TypeError. Even if we put or call a function with the return value in the _init_ method, when we call _init_() ,it will return None .
Object Identity:
- Every object that is an instance of a user-defined class has a unique identity
- We often use “is” and “is not” to test if two expression evaluate to the same project
- Binding an object to a new name using assignment does not create a new object .
- if we assign c = a , then we test " c is a" ,we will get True.
Method
Method are defined in the suite of a class statement. We use def statement to create a method. These def statements create function objects as always , but their names are bound as attributes of the class.
Invoking method & Dot expressions
All invoked methods has access to the object via self parameter , and so they can all access and manipulate the object’s state.
# simple class
class Account:
interest = 0.02 # a class attribute
# self is always a name for an object which is an instance of this account class
def __init__(self,account_holder):
self.name = account_holder # instance attribute
self.balance = 0 # instance attribute
def deposit(self,amount):
self.balance = self.balance + amount
return self.balance
def withdraw(self,amount):
if amount > self.balance:
return 'Insufficient Funds'
else:
self.balance = self.balance - amount
return self.balance
tom_account = Account('Tom')
getattr(tom_account,'balance')
# 0
hasattr(tom_account,'deposit')
# True
tom_account.interest
# 0.02
Dot Notation :
- Dot notation automatically supplies the first argument to a method.
- type(Account.deposit) is a class function with two arguments
- type(tom_account.deposit) is a class method with one argument
- Object + function = Method
- self is always a name for an object which is an instance of this account class
- Every time we use self within one of these methods or attributes, we are referring to a particular account
- Every time we are referring to self.something and assigning to it, it is a form of mutation. Actually what we’re changing is self , is the account object . So you may see many methods in classes do not have the return statement .When we use such method without return , we just wanna to change some values . When the method is called , the self.something in that method will be changed automatically in that class.
- Objects receive message via dot notation.
- Dot notation access attributes of the instance or its class.
< expression > . < name >- Evaluates to the value of the attribute looked up by < name > in the object that is the value of the < expression >
- Expression can be any valid python expression
Accessing attributes
Using getattr , we can look up an attribute using a string . getattr and dot expressions look up a name in the same way.
Looking up an attribute name in an object may return : one of its instance attributes or one of the attributes of its class.
the order of finding an attribute/ a value : instance --> class --> base class
Looking up Attributes by name
__< expression > . < name > __
To evaluate a dot expression :
- Evaluate the < expression > to the left of the dot, which yields the object of the dot expression
- < name > is matched against the instance attributes of that object ; if an attribute with that name exists ,its value is returned
- if not ,< name > is looked up in that class ,which yields a class attribute value
- That value is returned unless it is a function ,in which case a bound method is returned instead
Methods and Functions
A method is an attribute.
Bound Methods ,which couple together a function and the object on which that method will be invoked.
Object + Function = Bound Method
The object always is passed in the first argument of the method , that is, the self argument.
Class attributes
Class attributes are “shared” across all instances of a class because they are attributes of the class ,not the instance .
While in the above example interest is an attribute of class Account and if we call tom_account.interest we will get 0.02 , the interest is not part of the instance that was somehow copied from the class. If we override the interest value in the tom instance ,it won’t change the interest value in the class.
All the attributes in the _init_ method are instance attribute
create class attribute:
- build it in the class < suite > outside the all the method of the class
- using assignment outside the class . For example , just assign Account.interest = 0.03 .If the class already has the interest attribute , this assignment will change the value of attribute interest .If the class Account do not have such attribute ,it will create new one.
Attribute Assignment
Assignment statements with a dot expression on their left-hand side affect attributes for the object of that dot expression:
- If the object is an instance ,then assignment sets an instance attribute
- If the object is a class ,then assignment sets a class attribute.
- <object>.<attribute> = <expression> , attribute assignment statement adds or modifies the attribute of the object.
Inheritance
Inheritance is a method for relating classes together .
A common use: two similar classes differ in their degree of specialization.
The specialized class may have the same attributes as the general class, along with som special-case behavior.
class <name> (< base class >):
< suite>
The subclass may override certain inherited attributes.Using inheritance ,we implement a subclass by specifying its differences from the base class.
Looking up Attribute Names on Classes
Base class attribute are not copied into subclass.
To look up a name in a class :
- If it names an attribute in the class ,return the attribute value
- Otherwise , look up the name in the base class ,if there is one
# example
class A:
z = -1
def f(self,x):
return B(x-1)
class B(A):
n = 4
def __init__(self,y):
if y:
self.z = self.f(y)
else:
self.z = C(y+1)
class C(B):
def f(self,x):
return x
# we assign :
a = A() # empty instance
b = B(1) # assign 1 to the y of __init__() in B
b.n = 5 # only change the n-value of b instance
# we need to figure out
print(C(2).n)
print(C(2).z)
print(C.z == a.z)
print(B.z)
print(b.z)
print(b.z.z)
print(b.z.z.z)
print(b.z.z.z.z)
Firstly ,we can write down all the things in the class A B C:
-
class A
- z : -1
- f : -----> func f(self, x)
-
class B inherits from A
- n = 4
- _init_ : -----> func _init_(self, y)
-
class C inherits from B
- f : -----> func f(self, x)
Secondly , we need to figure out the assignments:
- f : -----> func f(self, x)
-
a = A()
- a is an instance of A
- Since there is no _init_ method within the class A definition, there is no instance attributes that are set .
- This a instance is just a blank slate
-
b = B(1) & b.n = 5
- calling B() introduces a new instance of class B ,and we assign is to b in the global frame.
- the _init_ method for B is called automatically with self bound of the instance b and y bound to the 1
- Now z is set ,although it seems a list bit complicate. z is in the b instance
- Then the attribute assignment b.n = 5 means that we add an attribute to the instance b, which has no effect on B class or its base class instead it’s just a change to this particular instance b.
Then, the questions:
-
C(2).n = ?
- class C does not have _init_ method , so we find in its base class B, and there is one then the 2 will be passed to the y-value of the _init_ method in class B.
- Now we know that y is assigned to 2, so the statement if y is True, then self.z = self.f(2). At this time self is the instance of class C, so we find func-f in class C , and finally we can know that self.z = 2 . Then we are going to add z attribute to the C instance .So C(2).z = 2
- After we create an instance C(2), we can see that there is no n in the instance and class C , so we lookup in its base class B, the we find n = 4 , so C(2).n = 4
-
C.z ?= a.z
- It is simple to find a.z. First we look up in the instance a , there is no z.Then we find it in the class A , and there exists z = -1 ,so a.z=-1
- Then we need to find C.z .First we’d like to find it in the class C , but there is no z in it . Then we find it in it base class B , no z in B. Finally we find it in class A , because A is the base class of B, then we find there exists z = -1 ,so C.z=-1
- Notice that C is not an instance , it is just the class C.
-
b.z
- b = B(1) , b has an instance attribute called z .When b instance was created we passed in 1 as the value for y, self is bound to the instance itself . Then we checked that if y is True , so we got self.z = self.f(1).
- Since Neither instance b or class B has method f, so we find f in the base class A .
- Then we passed in 1 as the value of x , self is bound to the instance b itself, we got the return B(0) which creates a new instance of B . so b.z is the instance B(0).
- Now we have a new instance B(0) ,which will automatically invoke the _init_ method and pass the 0 to y. Then we check if y is False ,now self.z = C(1) . The self is the instance B(0) , and self.z will create a new instance C(1) . b.z.z is the instance C(1)
- Since neither class C or instance C(1) has the _init_ method , so we look up in the base class B . Then we bind self to C(1) , 1 is passed to y . Checking that if y is True , we can get self.z = f(1) .Notice that now the self is Instance C(1) ,so we will first check whether instance C(1) has the function f . Then we pass in 1 as the value of x , and get the return value 1 . so at this time , in the instance C(1) , self.z = 1, which means b.z.z.z = 1
- If we continue to figure out b.z.z.z.z , we will get the error .Since int type does not have the attribute of z.
Designing for Inheritance
- Do not repeat your self; Using existing Implementations ;
- Attributes that have been overridden are still accessible via class objects
- Look up attribute on instances whenever possible
# inheritance example for above class Account
class CheckingAccount(Account):
""" A bank account that charges for withdrawals"""
withdraw_fee = 1 # class attribute
interest = 0.01 # class attribute
def withdraw(self,amount):
return Account.withdraw(self,amount + self.withdraw_fee)
# Account.withdraw() is the attribute look-up on the base class (class Account)
# we can see we only change the amount value from amount to amount + self.withdraw_fee which allows for specialized account
Inheritance and Composition
OOP shines when we adopt the metaphor.
- Inheritance is best for representing is-a relationships.
- E.g., a checking account is a specific type of account
- so, CheckingAccount inherits from Account
- Composition is best for representing has a relationships
- E.g., a bank has a collection of bank accounts it manages
- So ,A bank has a list of accounts as an attribute
# example for Composition
class Bank:
""" A bank *has* accounts
>>> bank = Bank()
>>> john = bank.open_account('John',10)
>>> jack = bank.open_account('Jack',5,CheckingAccount)
>>> john.interest
0.02
>>> Jack.interest
0.01
>>> bank.pay_interest()
>>> john.balance
10.2
"""
def __init__(self):
self.accounts = []
def open_account(self, holder, amount, kind=Account):
account = kind(holder)
account.deposit(amount)
self.accounts.append(account)
return account
def pay_interest(self):
for a in self.accounts:
# a is an Account/CheckingAccount instance
a.deposit(a.balance*a.interest)
def too_big_to_fail(self):
return len(self.accounts)>1
Multiple Inheritance
A class may inherit from multiple base class in Python. But it will make program complicated
# example
# single inheritance
class SavingsAccount(Account):
deposit_fee = 2
def deposit(self,amount):
return Account.deposit(self,amount-self.deposit_fee)
# Multiple Inheritance
class AsSeenOnTVAccount(CheckingAccount, SavingsAccount):
"""
if CheckingAccount and SavingsAccount have the same attribute or method ,first check CheckingAccount .
>>> such_a_deal = AsSeenOnTVAccount("John")
>>> such_a_deal.balance # instance attribute
1
>>> such_a_deal.deposit(20) # SavingsAccount Method
19
>>> such_a_deal.withdraw(5) # CheckingAccount Method
13
"""
def __init(self,account_holder):
self.holder = account_holder
self.balance = 1 # a free dollar
Using super()
We do not need to pass self when we use super() notation.
super() is not referring to the Account class it’s like referring to a version of self as if Account where it is class instead of CheckingAccount where it’s class.
class CheckingAccount(Account):
""" A bank account that charges for withdrawals"""
withdraw_fee = 1 # class attribute
interest = 0.01 # class attribute
def withdraw(self,amount):
# return Account.withdraw(self,amount + self.withdraw_fee)
# alternatively :
return super().withdraw(amount + self.withdraw_fee)
Hw04
class VendingMachine
class VendingMachine:
"""A vending machine that vends some product for some price.
Creat a class call VendingMachine that represents a vending machine for some product.A VendingMachine object returns strings describing its iteractions.
Fill in the VendingMachine class add attribute and methods asa appropriate, such that its behavior matched the following doctests
some useful string syntax
>>> ten,twenty,thirty = 10 ,'twenty',[30]
>>> '{0} plus {1} is {2}'.format(ten,twenty,thirty)
'10 plus twenty is [30]'
>>> v = VendingMachine('candy', 10)
>>> v.vend()
'Inventory empty. Restocking required.'
>>> v.add_funds(15)
'Inventory empty. Restocking required. Here is your $15.'
>>> v.restock(2)
'Current candy stock: 2'
>>> v.vend()
'You must add $10 more funds.'
>>> v.add_funds(7)
'Current balance: $7'
>>> v.vend()
'You must add $3 more funds.'
>>> v.add_funds(5)
'Current balance: $12'
>>> v.vend()
'Here is your candy and $2 change.'
>>> v.add_funds(10)
'Current balance: $10'
>>> v.vend()
'Here is your candy.'
>>> v.add_funds(15)
'Inventory empty. Restocking required. Here is your $15.'
>>> w = VendingMachine('soda', 2)
>>> w.restock(3)
'Current soda stock: 3'
>>> w.restock(3)
'Current soda stock: 6'
>>> w.add_funds(2)
'Current balance: $2'
>>> w.vend()
'Here is your soda.'
"""
"*** YOUR CODE HERE ***"
def __init__(self,name,price,stock = 0,money = 0):
self.name = name
self.price = price
self.stock = stock
self.money = money
def vend(self):
if not self.stock:
return 'Inventory empty. Restocking required.'
elif self.money < self.price:
gap = self.price - self.money
return 'You must add ${} more funds.'.format(gap)
elif self.money >= self.price:
change = self.money - self.price
self.money = 0
self.stock -=1
if change:
return 'Here is your {0} and ${1} change.'.format(self.name,change)
else:
return 'Here is your {}.'.format(self.name)
def add_funds(self,add_money):
if not self.stock:
return 'Inventory empty. Restocking required. Here is your ${}.'.format(add_money)
else:
self.money = self.money + add_money
return 'Current balance: ${}'.format(self.money)
def restock(self,add_stock):
self.stock = self.stock + add_stock
return 'Current {0} stock: {1}'.format(self.name,self.stock)
class Mint
class Mint:
"""A mint creates coins by stamping on years.
The update method sets the mint's stamp to Mint.current_year.
complete the Mint and Coin classes so that the coins created by a mint have the correct year and worth.
* each Mint Instance has a year stamp,The update method sets the year stamp to the current_year class attribute of the Mint class
* The create method takes a subclass of Coin and returns an instance of that class stamped with the mint's year(which may be different from Mint.current_year if it has not been updated)
* A Coin's worth method returns the cents value of the coin plus one extra cent for each year of age beyond 50. A coin's age can be determined by substracting the coin's year from the current_year class attribute of the Mint class.
>>> mint = Mint()
>>> mint.year
2020
>>> dime = mint.create(Dime)
>>> dime.year
2020
>>> Mint.current_year = 2100 # Time passes
>>> nickel = mint.create(Nickel)
>>> nickel.year # The mint has not updated its stamp yet
2020
>>> nickel.worth() # 5 cents + (80 - 50 years)
35
>>> mint.update() # The mint's year is updated to 2100
>>> Mint.current_year = 2175 # More time passes
>>> mint.create(Dime).worth() # 10 cents + (75 - 50 years)
35
>>> Mint().create(Dime).worth() # A new mint has the current year
10
>>> dime.worth() # 10 cents + (155 - 50 years)
115
>>> Dime.cents = 20 # Upgrade all dimes!
>>> dime.worth() # 20 cents + (155 - 50 years)
125
"""
current_year = 2020
def __init__(self):
self.update()
def create(self, kind):
"*** YOUR CODE HERE ***"
# kind is a subclass of coin , Nickel and Dime do not have __init__ so they will inherit the class Coin's method
return kind(self.year)
def update(self):
"*** YOUR CODE HERE ***"
self.year = Mint.current_year
class Coin:
def __init__(self, year):
self.year = year
def worth(self):
"*** YOUR CODE HERE ***"
if self.year< Mint.current_year
return self.cents + Mint.current_year - self.year - 50
else:
return self.cents
class Nickel(Coin):
cents = 5
class Dime(Coin):
cents = 10