Classes and Objects (2)
Contents
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__
# (optional) default values can also be provided
def __init__(self, bookTitle="", bookAuthor="", bookYear=0):
self._title = bookTitle
self._author = bookAuthor
self._year = int(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)
ps = Book("Parable of the Sower", "Octavia Butler", 1993)
# we can access (non-private) attributes directly using dot notation
# (but this is not the preferred way!)
ps._title
'Parable of the Sower'
# invoke the accessor method (preferred way to access attributes)
pp.getTitle()
'Pride and Prejudice'
emma.getAuthor()
'Jane Austen'
ps.getYear()
1993
# invoke the mutator methods
ps.setYear(1991)
# verify that our update to year attribute in the previous line worked
ps.getYear()
1991
# invoke Book methods on specify books
pp.numWordsInTitle()
3
emma.yearsSincePub(2022)
207
ps.yearsSincePub(2022)
31
ps.sameAuthorAs(emma)
False
emma.sameAuthorAs(pp)
True
# test our __str__ method bring printing a specific Book instance
print(ps)
'Parable of the Sower', by Octavia Butler, in 1991
Data Hiding via Attribute Types¶
When we create attributes of a class, we must decide what level of access “users” of the class should have. Some OOP languages strictly enforce these distinctions. Python uses the following special naming conventions to “signal the attribute type”:
Private (prefixed with
__
): these attributes should only be used inside of the class definition. These attributes are strictly private and essentially invisible from outside the class.Protected (prefixed with
_
): these attributes can be used from outside the class but only in subclasses.Public (no underscore prefix): these attributes can and should be freely used anywhere.
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)
Input In [19], in <cell line: 1>()
----> 1 a.__val
AttributeError: 'TestingAttributes' object has no attribute '__val'
String Representation of a Class¶
Printing objects is often useful for debugging. For built-in objects in Python, such as lists, dictionaries, etc, Python knows how to print the contents of these objects in a useful way. Unfortunately, this is not true for objects that we define in our own classes.
class TestingPrint():
__slots__ = ['_attr']
def __init__(self, value):
self._attr = value
test = TestingPrint("testing")
print(test)
<__main__.TestingPrint object at 0x105adea30>
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__ = ['_first', '_mid', '_last']
# since middle names are optional, we can define a default value
def __init__(self, firstName, lastName, middleName=''):
self._first = firstName
self._mid = middleName
self._last = lastName
# accessor methods for attributes
def getFirst(self):
return self._first
def getMiddle(self):
return self._mid
def getLast(self):
return self._last
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('Iris', 'Howley')
n2 = Name('Jeannie', 'Albrecht', 'Raye')
print(n1)
print(n2)
I. Howley
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. (It’s harder to see this in a script though.)
n1
<__main__.Name at 0x1059b09c0>
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)