Classes and Objects (2)¶
In today’s lecture, we will continue our discussion of object oriented programming in Python.
class Book:
"""This class represents a book with attributes title, author, and year"""
# attributes
# _ indicates that they are protected
__slots__ = ['_title', '_author', '_year']
# __init__ is automatically called when we create new Book objects
# we set the intial values of our attributes in __init__
def __init__(self, bookTitle, bookAuthor, bookYear):
self._title = bookTitle
self._author = bookAuthor
self._year = bookYear
# accessor (getter) methods
def getTitle(self):
return self._title
def getAuthor(self):
return self._author
def getYear(self):
return self._year
# mutator (setter) methods
def setTitle(self, bookTitle):
self._title = bookTitle
def setAuthor(self, bookAuthor):
self._author = bookAuthor
def setYear(self, bookYear):
self._year = int(bookYear)
# methods for manipulating Books
def numWordsInTitle(self):
"""Returns the number of words in title of book"""
return len(self._title.split())
def sameAuthorAs(self, otherBook):
"""Check if self and otherBook have same author"""
return self._author == otherBook.getAuthor()
def yearsSincePub(self, currentYear):
"""Returns the number of years since book was published"""
return currentYear - self._year
# __str__ is used to generate a meaningful string representation for Book objects
# __str__ is automatically called when we ask to print() a Book object
def __str__(self):
return "'{}', by {}, in {}".format(self._title, self._author, 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)
# we can access (non-private) attributes directly using dot notation
# (but this is not the preferred way!)
hp._title
"Harry Potter and the Sorcerer's Stone"
# invoke the accessor method (preferred way to access attributes)
pp.getTitle()
'Pride and Prejudice'
emma.getAuthor()
'Jane Austen'
hp.getYear()
1997
# invoke the mutator methods
hp.setYear(1997)
# verify that our update to year attribute in the previous line worked
hp.getYear()
1997
# invoke Book methods on specify books
hp.numWordsInTitle()
6
emma.yearsSincePub(2022)
207
hp.yearsSincePub(2022)
25
hp.sameAuthorAs(emma)
False
emma.sameAuthorAs(pp)
True
# test our __str__ method bring printing a specific Book instance
print(hp)
'Harry Potter and the Sorcerer's Stone', by J.K. Rowling, in 1997
In many instances, it is useful to specify default values for parameters. Python supports this in both method and function definitions. In this example, we specify default values for the parameters in init.
class Book2:
"""This class represents a book with attributes title, author, and year"""
# attributes
__slots__ = ['_title', '_author', '_year']
# this __init__ method specifies default values for the parameters
def __init__(self, bookTitle="", bookAuthor="", bookYear=0):
self._title = bookTitle
self._author = bookAuthor
self._year = bookYear
def getTitle(self):
return self._title
def getAuthor(self):
return self._author
def getYear(self):
return self._year
emptyBook = Book2()
emptyBook._title
''
emptyBook.getTitle()
''
Data Hiding via Attribute Types¶
When we create instance variables of a class, we must decide what level of access “users” of the class should have over the data and procedural attributes. Some OOP languages enforce these distinctions, Python uses a special naming convention to “signal the attribute type.”
Private (prefixed with
__
or_
): these attributes should only be used inside of the class definition. Among these, attributes with__
are strictly private and essentially invisible from outside. Attributes with_
can be used from outside but really should not be.Public (no underscore prefix): these attributes can and should be freely used.
class TestingAttributes():
__slots__ = ['__val', '_val', 'val']
def __init__(self):
self.__val = "I am strictly private."
self._val = "I am private but accessible from outside."
self.val = "I am public."
a = TestingAttributes()
a.val
'I am public.'
a._val
'I am private but accessible from outside.'
a.__val
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
/var/folders/md/kwd9nc_d2ns0hw9wsvdrnt2c0000gn/T/ipykernel_55943/133428337.py in <module>
----> 1 a.__val
AttributeError: 'TestingAttributes' object has no attribute '__val'
Another Example: Name
class¶
In this example, we create a Name class that represents names, including a first, middle, and last name. This scenario illustrates a good reason to specify default parameter values in __init__
. Also, note that we do not define mutator methods in this case since a person’s name cannot change (usually). Finally, we can choose how these names are printed in __str__
.
class Name:
"""Class to represent a person's name."""
__slots__ = ['_f', '_m', '_l']
# since middle names are optional, we can define a default value
def __init__(self, first, last, middle=''):
self._f = first
self._m = middle
self._l = last
# accessor methods for attributes
def getFirst(self):
return self._f
def getMiddle(self):
return self._m
def getLast(self):
return self._l
def __str__(self):
# if the person has a middle name
if len(self.getMiddle()):
return '{}. {}. {}'.format(self.getFirst()[0], self.getMiddle()[0], self.getLast())
else:
return '{}. {}'.format(self.getFirst()[0], self.getLast())
n1 = Name('Rohit', 'Bhattacharya')
n2 = Name('Jeannie', 'Albrecht', 'Raye')
print(n1)
print(n2)
R. Bhattacharya
J. R. Albrecht
Notice Even though we can print the object now, if you ask about the name object in interactive python, it still gives something that is not human readable.
n1
<__main__.Name at 0x1097f9ec0>
type(n2)
__main__.Name
Summary¶
Today we saw how Python supports data abstraction (separating the data and details of the implementation from the user) via :
Data hiding: via attribute naming conventions
Encapsulation: bundling together of data and methods that provide an interface to the data (accessor and mutator methods)