Sign In Start Free Trial
Account

Add to playlist

Create a Playlist

Modal Close icon
You need to login to use this feature.
  • Book Overview & Buying Python 3 Object-Oriented Programming
  • Table Of Contents Toc
Python 3 Object-Oriented Programming

Python 3 Object-Oriented Programming - Third Edition

By : Dusty Phillips
4.3 (30)
close
close
Python 3 Object-Oriented Programming

Python 3 Object-Oriented Programming

4.3 (30)
By: Dusty Phillips

Overview of this book

Object-oriented programming (OOP) is a popular design paradigm in which data and behaviors are encapsulated in such a way that they can be manipulated together. This third edition of Python 3 Object-Oriented Programming fully explains classes, data encapsulation, and exceptions with an emphasis on when you can use each principle to develop well-designed software. Starting with a detailed analysis of object-oriented programming, you will use the Python programming language to clearly grasp key concepts from the object-oriented paradigm. You will learn how to create maintainable applications by studying higher level design patterns. The book will show you the complexities of string and file manipulation, and how Python distinguishes between binary and textual data. Not one, but two very powerful automated testing systems, unittest and pytest, will be introduced in this book. You'll get a comprehensive introduction to Python's concurrent programming ecosystem. By the end of the book, you will have thoroughly learned object-oriented principles using Python syntax and be able to create robust and reliable programs confidently.
Table of Contents (15 chapters)
close
close

Case study

Let's try to tie everything we've learned together with a larger example. We'll be developing an automated grading system for programming assignments, similar to that employed at Dataquest or Coursera. The system will need to provide a simple class-based interface for course writers to create their assignments and should give a useful error message if it does not fulfill that interface. The writers need to be able to supply their lesson content and to write custom answer checking code to make sure their students got the answer right. It will also be nice for them to have access to the students' names to make the content seem a little friendlier.

The grader itself will need to keep track of which assignment the student is currently working on. A student might make several attempts at an assignment before they get it right. We want to keep track of the number of attempts so the course authors can improve the content of the more difficult lessons.

Let's start by defining the interface that the course authors will need to use. Ideally, it will require the course authors to write a minimal amount of extra code besides their lesson content and answer checking code. Here is the simplest class I could come up with:

class IntroToPython:
def lesson(self):
return f"""
Hello {self.student}. define two variables,
an integer named a with value 1
and a string named b with value 'hello'

"""

def check(self, code):
return code == "a = 1\nb = 'hello'"

Admittedly, that particular course author may be a little naive in how they do their answer checking. If you haven't seen the f""" syntax before, we'll cover it in detail in the Chapter 8Strings and Serialization.

We can start with an abstract base class that defines this interface, as follows:

class Assignment(metaclass=abc.ABCMeta):
@abc.abstractmethod
def lesson(self, student):
pass

@abc.abstractmethod
def check(self, code):
pass

@classmethod
def __subclasshook__(cls, C):
if cls is Assignment:
attrs = set(dir(C))
if set(cls.__abstractmethods__) <= attrs:
return True

return NotImplemented

This ABC defines the two required abstract methods and provides the magic __subclasshook__ method to allow a class to be perceived as a subclass without having to explicitly extend it (I usually just copy and paste this code. It isn't worth memorizing.)

We can confirm that the IntroToPython class fulfills this interface using issubclass(IntroToPython, Assignment), which should return True. Of course, we can explicitly extend the Assignment class if we prefer, as seen in this second assignment:

class Statistics(Assignment):
def lesson(self):
return (
"Good work so far, "
+ self.student
+ ". Now calculate the average of the numbers "
+ " 1, 5, 18, -3 and assign to a variable named 'avg'"
)

def check(self, code):
import statistics

code = "import statistics\n" + code

local_vars = {}
global_vars = {}
exec(code, global_vars, local_vars)

return local_vars.get("avg") == statistics.mean([1, 5, 18, -3])

This course author, unfortunately, is also rather naive. The exec call will execute the student's code right inside the grading system, giving them access to the entire system. Obviously, the first thing they will do is hack the system to make their grades 100%. They probably think that's easier than doing the assignments correctly!

Next, we'll create a class that manages how many attempts the student has made at a given assignment:

class AssignmentGrader:
def __init__(self, student, AssignmentClass):
self.assignment = AssignmentClass()
self.assignment.student = student
self.attempts = 0
self.correct_attempts = 0

def check(self, code):
self.attempts += 1
result = self.assignment.check(code)
if result:
self.correct_attempts += 1

return result

def lesson(self):
return self.assignment.lesson()

This class uses composition instead of inheritance. At first glance, it would make sense for these methods to exist on the Assignment superclass. That would eliminate the annoying lesson method, which just proxies through to the same method on the assignment object. It would certainly be possible to put all this logic directly on the Assignment abstract base class, or even to have the ABC inherit from this AssignmentGrader class. In fact, I would normally recommend that, but in this case, it would force all course authors to explicitly extend the class, which violates our request that content authoring be as simple as possible.

Finally, we can start to put together the Grader class, which is responsible for managing which assignments are available and which one each student is currently working on. The most interesting part is the register method:

import uuid

class Grader:
def __init__(self):
self.student_graders = {}
self.assignment_classes = {}

def register(self, assignment_class):
if not issubclass(assignment_class, Assignment):
raise RuntimeError(
"Your class does not have the right methods"
)

id = uuid.uuid4()
self.assignment_classes[id] = assignment_class
return id

This code block includes the initializer, which includes two dictionaries we'll discuss in a minute. The register method is a bit complex, so we'll dissect it thoroughly.

The first odd thing is the parameter this method accepts: assignment_class. This parameter is intended to be an actual class, not an instance of the class. Remember, classes are objects, too, and can be passed around like other classes. Given the IntroToPython class we defined earlier, we might register it without instantiating it, as follows:

from grader import Grader
from lessons import IntroToPython, Statistics

grader = Grader()
itp_id = grader.register(IntroToPython)

The method first checks whether that class is a subclass of the Assignment class. Of course, we implemented a custom __subclasshook__ method, so this includes classes that do not explicitly subclass Assignment. The naming is, perhaps, a bit deceitful! If it doesn't have the two required methods, it raises an exception. Exceptions are a topic we'll cover in detail in the next chapter; for now, just assume that it makes the program get angry and quit.

Then, we generate a random identifier to represent that specific assignment. We store the assignment_class in a dictionary indexed by that ID, and return the ID so that the calling code can look that assignment up in the future. Presumably, another object would then place that ID in a course syllabus of some sort so students do the assignments in order, but we won't be doing that for this part of the project.

The uuid function returns a specially formatted string called a universally unique identifier, also known as a globally unique identifier. It essentially represents an extremely large random number that is almost, but not quite, impossible to conflict with another similarly generated identifier. It is a great, quick, and clean way to create an arbitrary ID to keep track of items.

Next up, we have the start_assignment function, which allows a student to start working on an assignment given the ID of that assignment. All it does is construct an instance of the AssignmentGrader class we defined earlier and plop it in a dictionary stored on the Grader class, as follows:

    def start_assignment(self, student, id):
self.student_graders[student] = AssignmentGrader(
student, self.assignment_classes[id]
)

After that, we write a couple of proxy methods that get the lesson or check the code for whatever assignment the student is currently working on:

    def get_lesson(self, student):
assignment = self.student_graders[student]
return assignment.lesson()

def check_assignment(self, student, code):
assignment = self.student_graders[student]
return assignment.check(code)

Finally, we create a method that gives a summary of a student's current assignment progress. It looks up the assignment object and creates a formatted string with all the information we have about that student:


def assignment_summary(self, student):
grader = self.student_graders[student]
return f"""
{student}'s attempts at {grader.assignment.__class__.__name__}:

attempts: {grader.attempts}
correct: {grader.correct_attempts}

passed: {grader.correct_attempts > 0}
"""

And that's it. You'll notice that this case study does not use a ton of inheritance, which may seem a bit odd given the topic of the chapter, but duck typing is very prevalent. It is quite common for Python programs to be designed with inheritance that gets simplified into more versatile constructs as it is iterated on. As another example, I originally defined the AssignmentGrader as an inheritance relationship, but realized halfway through that it would be better to use composition, for the reasons outlined previously.

Here's a bit of test code that shows all these objects connected together:

grader = Grader()
itp_id = grader.register(IntroToPython)
stat_id = grader.register(Statistics)

grader.start_assignment("Tammy", itp_id)
print("Tammy's Lesson:", grader.get_lesson("Tammy"))
print(
"Tammy's check:",
grader.check_assignment("Tammy", "a = 1 ; b = 'hello'"),
)
print(
"Tammy's other check:",
grader.check_assignment("Tammy", "a = 1\nb = 'hello'"),
)

print(grader.assignment_summary("Tammy"))

grader.start_assignment("Tammy", stat_id)
print("Tammy's Lesson:", grader.get_lesson("Tammy"))
print("Tammy's check:", grader.check_assignment("Tammy", "avg=5.25"))
print(
"Tammy's other check:",
grader.check_assignment(
"Tammy", "avg = statistics.mean([1, 5, 18, -3])"
),
)

print(grader.assignment_summary("Tammy"))
CONTINUE READING
83
Tech Concepts
36
Programming languages
73
Tech Tools
Icon Unlimited access to the largest independent learning library in tech of over 8,000 expert-authored tech books and videos.
Icon Innovative learning tools, including AI book assistants, code context explainers, and text-to-speech.
Icon 50+ new titles added per month and exclusive early access to books as they are being written.
Python 3 Object-Oriented Programming
notes
bookmark Notes and Bookmarks search Search in title playlist Add to playlist font-size Font size

Change the font size

margin-width Margin width

Change margin width

day-mode Day/Sepia/Night Modes

Change background colour

Close icon Search
Country selected

Close icon Your notes and bookmarks

Confirmation

Modal Close icon
claim successful

Buy this book with your credits?

Modal Close icon
Are you sure you want to buy this book with one of your credits?
Close
YES, BUY

Submit Your Feedback

Modal Close icon
Modal Close icon
Modal Close icon