In today's lecture we will introduces user-defined types in Python called classes and formalize the notion of objects.
Python supports many different kinds of data
1234
(int
), 3.14
(float
), "Hello"
(str
)[1, 5, 7, 11, 13]
(list
), {"CA": "California", "MA": "Massachusetts"}
(dict
) Each of these is an object, and every object has:
An object is an instance of a type
1234
is an instance of an int
"hello"
is an instance of a str
Different between Jupyter Notebook and interactive Python. The outputs to type
queries appear different between Jupyter notebook and interactive python, which displays the word class
. For example:
>>> type(1234)
<class 'int'>
>>>
type(1234)
type("hello")
type([1, 5, 7, 11, 13])
type({"CA": "California", "MA": "Massachusetts"})
type(range(5))
def greeting():
print("Hello")
type(greeting)
We can create our own type by defining our own class. Creating a class involves: defining the class name and defining its attributes. Using the class involves: creating new instances (objects) and doing operations on the instances.
Let us start by defining a simple class Book
.
class Book:
"""This class represents a book"""
pass
Creating an instance of the class. We can create an instance (or an object) of the class Book
as follows.
b1 = Book()
b1
type(b1)
Each instance is different. If we create a new instance b2
of class Book
, it is a different object than b1
.
b2 = Book()
b2 == b1
Data attributes (a Pythonic term) or instance variables store data that "belong" to the instances of the class.
For example, an instance of a class Book
must have associated information about it, such as name
for the name of the book, author
for name of the author, etc. Such information is associated with all instances of the class by defining them as the data attributes in the class definition.
We can assign attributes directly to an instance of the class (outside the class definition) but this is not recommended and should not be done. Let us take an example of the not the proper approach and then show describe the approach that should be used instead.
b1.name = "Emma"
b1.author = "Jane Austen"
b1.name
b2.name # will this work?
b2.name = "Harry Potter"
b2.name
b2.author = "J.K. Rowling"
b2.author
Methods or procedural attributes are functions that are defined as part of the class definition and describe how to interact with the class objects. For example, we now how we can modify a list object L
by using methods like append
, extend
,etc.
How are these methods called? We call the append method on the list object L
, by writing L.append(4)
. Methods of the classes we define are called in a similar way.
L = list()
L.extend([1,2,3])
L
L.append(4)
L
First Method. Lets define a toy class with a simple method that prints "Hello" and see how we can call the method on an instance of the class.
class A:
"""Class to test the use of methods"""
def greeting(self):
print("Hello")
greeting()
a = A()
a.greeting()
Lets try to understand what the purpose of the parameter self by using the python function id()
.
Recall that id(obj)
displays the unique identifier to the object. You can think of this number as the address in memory this object is stored.
num = 42
id(num)
listA = listB = [1,2,3]
id(listA) == id(listB)
Let us rewrite class A
to print the id
of self.
class A:
"""Class to test the use of methods"""
def greeting(self):
print("Hello, object with ID", id(self))
obj = A()
obj.greeting()
id(obj)
A.greeting(obj)
L = [1,2,3,4]
list.append(L, 5)
L
Summary. Methods differs from a function in two aspects:
The following two calls are equivalent: A.greeting(obj)
and obj.greeting()
.
While Python allows you to assign attributes to instances of a class on the fly (and outside the class), it is not the proper way to do so.
The data attributes (instance variables) should be part of the class definition. We can achieve this by the Python's special method __init__
.
__init__
Special method that lets us define how to create an instance of a class. In particular, lets us initialize some data attributes of the class.
class TestInit:
"""This class will test when __init__ is called"""
def __init__(self):
print("__init__ is called")
obj = TestInit()
Thus, the special method __init__
is called automatically when we create a new instance of the class.
Let us use it now to properly initialize our class Book with attributes name
and author
.
class Book:
"""This class represents a book"""
def __init__(self, name=None, author=None):
self.name = name
self.author = author
emma = Book('Emma', 'Jane Austen')
emma.name
emma.author
hp = Book('Harry Potter')
hp.name
print(hp.author)
While Python allows you to assign attributes to instances of a class on the fly outside the class, this functionality is not ideal.
class Book:
"""This class represents a book"""
def __init__(self, name=None, author=None):
self.name = name
self.author = author
b = Book('Emma', 'Jane Austen')
b.year = 1815
b.year
__dict__
__dict__
on the fly and there are no predetermined set of keysb.__dict__
b.ref = 9999777
b.__dict__
__slots__
¶__slots__
provide a clean way to work around this space consumption problem: instead of having a dynamic dict, slots provide a static structure which prohibits addition of attributes class Book:
"""This class represents a book"""
__slots__ = ['name', 'author']
def __init__(self, name=None, author=None):
self.name = name
self.author = author
b = Book('Emma', 'Jane Austen')
b.name
b.author
b.__dict__
b.year = 1815
We could define other methods as part of the class definition.
For example, we define the following methods:
numWordsName
that returns the number of words in the name of the book sameAuthorAs
that takes another book object as parameter and checks if the two books have the same author or not. yearSincePub
that takes in the current year and returns the number of years since the book was published.class Book:
"""This class represents a book with
attributes name, author and year"""
__slots__ = ['name', 'author','year'] #attributes
def __init__(self, name=None, author=None, year=None):
self.name = name
self.author = author
self.year = year
def numWordsName(self):
"""Returns the number of words in name of book"""
return len(self.name.split())
def sameAuthorAs(self, other):
"""Check if self and other have same author"""
if self.author == other.author:
return True
return False
def yearsSincePub(self, currentYear):
"""Returns the number of years since book was published"""
return currentYear - self.year
# creating book objects:
pp = Book('Pride and Prejudice', 'Jane Austen', 1813)
emma = Book('Emma', 'Jane Austen', 1815)
hp = Book("Harry Potter and the Sorcerer's Stone", "J.K. Rowling", 1997)
pp.sameAuthorAs(emma)
pp.sameAuthorAs(hp)
hp.numWordsName()
emma.yearsSincePub(2020)
hp.yearsSincePub(2020)