- Motivating Example
- Introduction
- Class Constructors
- Special Methods for Class Objects
- Class Inheritance
- Attribute Name Mangling
- Class Objects Example
- Practice Problems
Class Objects Motivating Example
Suppose we again place ourselves in the role of a geography teacher. It’s now the end of the school year, and we want to examine the grades of the students. The following table contains the grades for each student and each percentage of the final grade it represents.
Student | Homework Total (20%) | Project 1 (10%) | Midterm (20%) | Project 2 (10%) | Final (40%) |
---|---|---|---|---|---|
Mary | 85 | 90 | 88 | 100 | 90 |
Matthew | 80 | 95 | 70 | 90 | 70 |
Marie | 90 | 92 | 84 | 0 | 91 |
Manuel | 79 | 70 | 85 | 70 | 82 |
Malala | 100 | 95 | 100 | 98 | 99 |
Now what we want to do is figure out some summary statistics about each student in the class. To do this, we will use a new form of storing data and methods called a class object. With class objects, we can develop new and efficient algorithms for completing tasks.
Introduction to Python Objects
Throughout the past few tutorials, we’ve described various Python “things,” such a data types, structures, and functions as “objects.” We also introduced in the first tutorial that Python is an “object-oriented” scripting language. The subject of object-oriented programming is broad, and this tutorial will not attempt to cover its entirety. However, we will cover the basics of object-oriented programming as they relate to Python, and we’ll cover the class
constructor in detail within this tutorial.
Code More, Distract Less: Support Our Ad-Free Site
You might have noticed we removed ads from our site - we hope this enhances your learning experience. To help sustain this, please take a look at our Python Developer Kit and our comprehensive cheat sheets. Each purchase directly supports this site, ensuring we can continue to offer you quality, distraction-free tutorials.
Python Objects
Python objects are variables, classes, functions, and other “things” that maintain some pointer (where it is located), contain some data, and may contain internal procedures or methods. For example, let’s create a string object:
x = "string" # Object instantiation and pointer assignment to x
id("string"), id(x) # Verify that x points to the same object
> (3106053026736, 3106053026736)
When we use quotations, Python creates or “instantiates” the string object. If we want to locate where the object is in memory to use it, then we will need to create a “pointer” to the string object. We created the above pointer using the assignment operator =
to assign the variable x
as a pointer to the string object we created. We can tell that the x
variable points to the same string object using the id
function.
Object Attributes
An “attribute” of an object is some value or method that is attached to that object. In a sense, an attribute is a set of variables and functions that are defined within the “sandbox” of the object. When a function is an attribute of an object, it is called a “method.” Attributes of an object in Python are referenced using the object.attribute
convention.
We can look at all of the attributes associated with an object using the builtin dir()
function1.
dir(x)
> ['__add__',
> '__class__',
> '__contains__',
> '__delattr__',
> '__dir__',
> '__doc__',
> '__eq__',
> ... # Omitting further results
We can see that a string object has quite a number of attributes associated with it, predominantly methods. These methods operate on the values within the object, which is the primary reason they are considered separate from functions; they already “know” information about the object.
For example, if I wanted to make all the letters in the string we created uppercase, then I can call the upper
method :
x.upper
> <function str.upper>
x.upper()
> 'STRING'
As we mentioned previously in our tutorial on functions, its important to know that the method is referenced by x.upper
, and is called or “activated” by x.upper()
.
Attribute Naming Conventions
Many beginners to Python can be confused by the confusing naming convention used by Python for object attributes. In the above example on strings, the string object had attributes x.__dir__
, and x.upper
. Why were the underscores added to the dir
object and not the upper
object? The reason is due to Python’s convention for classifying attributes of an object.
The following table gives a summary of this convention for an example attribute atr
:
Name | Example | Reason |
---|---|---|
Single Leading Underscore | _atr | Declaring "private" variables, or internal use only |
Single Trailing Underscore | atr_ | Avoids conflict with builtin keywords |
Double Leading Underscore | __atr | Attribute name "mangling" |
Double Leading and Trailing Underscore | __atr__ | Special internal Python use |
Single leading underscores are used to declare that attributes within an object are “private” 2 and should not be used outside of the internal structure of the object. This type of declaration is used more in defining variables and functions within modules, which we will cover in a later tutorial.
Single trailing underscores are used to differentiate attribute names from other Python builtin names. For example, if I wanted to make a method named print
, but did not want to call the builtin Python function print
, then I could name the attribute print_
.
Double leading underscores indicate to Python that the referenced name should be “mangled” or changed using the class name. For example, an attribute named __atr
within an object can only be accessed outside of the object by object._ClassName__atr
. We will cover these in more detail later.
Double leading and trailing underscores are dedicated to Python internal functions and processing. Like we saw above, the dir
builtin function of Python works by accessing the object.__dir__
attribute. Because these attributes are primarily used by Python itself, they are rarely created by users.
Class Constructors
Now that we’ve talked about Python objects that already exist within Python, lets go over how we can create new objects for ourselves. We can define new class objects using the class
object constructor. The class
object constructor has the following format:
class [name]:
[internals]
Like if
statements and loops, indented lines (tab or 4 spaces) following the opening line are grouped within the constructor.
We can create a simple example class constructor with an attribute key
using the following:
class example:
key = "example string"
Note that objects attributes need not be setup in the class constructor, but can be dynamically added later. For example:
x = example()
x.addedvalue = 1000
print(x.addedvalue)
> 1000
Therefore we can store variables within class objects themselves.
Class Objects vs. Constructors
In the above example, we created a class constructor named example
. A common mistake for beginners to Python is to assume that the class constructor is the class object. This is not the case. A constructor forms the template of a class object, on which class objects are created or instantiated. For example, if we create an example
object by instantiating it and assigning it to the variable x
, we can reference and manipulate the key attribute:
class example:
key = "example string"
x = example() # Instantiate class object and assign to x
print(x.key)
> example string
x.key = "object string" # Changing the key attribute
print(x.key)
> object string
print(example.key) # Note how the object attribute did not change the constructor attribute
> example string
Knowing the difference is important, as different objects of the same parent class can take different values:
x = example()
y = example()
x.key = "One example"
y.key = "A different example"
print(x.key)
> One example
print(y.key)
> A different example
As you can see from the above example, the example
class created a class of object that contains the key
attribute. Instantiating the object on two different variables allows us to change the key on each variable.
Code More, Distract Less: Support Our Ad-Free Site
You might have noticed we removed ads from our site - we hope this enhances your learning experience. To help sustain this, please take a look at our Python Developer Kit and our comprehensive cheat sheets. Each purchase directly supports this site, ensuring we can continue to offer you quality, distraction-free tutorials.
Class Methods
We’ve previously mentioned that functions can be included as attributes of an object in what are called methods. We can do this simply by defining a function within the class constructor:
class example:
key = "example string"
def addup(self, a, b): # We will explain the "self" statement in a moment
return(a + b)
x = example() # object initialization
x.addup(3,2)
> 5
With a minor change in the input tuple, we’ve now recreated a function to sum two numbers as a method of an object. However, you may wonder why we would want to create this method instead of just creating a function. The secret lies within the self
variable in the method input tuple.
Whenever a method is defined within a class
object, the first input object will always be the self
variable3. We can imagine that the self
variable refers to the object itself. This means that any attribute defined as part of the object can be referenced within the object as self
. For example:
class example:
value = 3
def addup(self, a):
return(a + self.value) # We will now add a given value to the internal value
x = example() # object initialization
x.addup(2)
> 5
x.value = 8 # We can change the internal value variable using the x.value attribute
x.addup(2)
> 10
In other words, when we call self
within the class constructor, we are telling Python to use an attribute of the same object. Because we can instantiate the object on any variable (x
, y
, etc.) then self
provides a consistent way to reference the object from within the constructor.
Special Methods for Class Objects
In our introduction section we mentioned that Python has reserved special object methods of the format __method__
for use by its builtin functions. In this section, we’ll go over a few of these special methods, and how they can be used to develop effective objects. There are many more special methods than what is presented here, but those methods require more knowledge of Python mechanics that we will discuss in future tutorials.
__init__ Method
The __init__
method is a standard method that Python uses when instantiating (or initializing) and object. Suppose we instantiate the following class object x = example()
. When we call the constructor with example()
, Python will search the object for the __init__
method and automatically execute its contents with the instantiating input tuple. For example:
class example:
def __init__(self, input): # Don't forget to put self first!
self.key = input
print(example.key) # Check to see if the constructor has a key. This should fail.
> AttributeError: type object 'example' has no attribute 'key'
x = example("sample text") # Instantiate the class object and use the following input in __init__
print(x.key)
> sample text
The majority of class object usage involves __init__
, as it’s used to setup the object for later use.
Iterators: __iter__ and __next__
In our previous tutorial on functions, we discussed generator functions and their uses at length. We will expand on the concept of a generator into the more general iterator class of object. Generator functions are a shortcut way of defining an iterator; however if a more complex process of iteration is required, then we can create an iterator.
The __init__
method in an object will tell Python that the object is iterable and callable with the iter
function. For example, list objects possess an __iter__
method, and thus we can iterate over them using a for ... in ...
statement.
The __next__
method tells Python what actions to perform when the next iteration call is performed on the object. This method provides the same capabilities of a while
loop coupled with a yield
statement for generators.
Let’s start by making our own iterator to print out a list of strings.
class myIterator:
def __init__(self, input): # __init__ to read in an input list
self.listing = input
self.max = len(input) # start an index of
self.index = -1
def __iter__(self):
return(self) # This simply points the object back to itself when called
def __next__(self): # The essential iteration loop
self.index += 1 # Increment index by 1 on each loop
if self.index >= self.max: # If we've reached the end of the list of items
raise StopIteration # This will stop iteration after the list is exhausted
return(self.listing[self.index])
listing = ["The", "best", "of", "times."]
x = myIterator(listing)
for item in x:
print(item)
> The
> best
> of
> times.
Note in the above example, we maintained a condition within the __next__
method to raise an exception to halt execution of the iterator once all elements were exhausted. This is similar to the halt we added to a generator function to prevent infinite loops. Don’t worry for now about what the raise
statement entails; we will discuss exceptions in detail in a later tutorial.
__dir__ Method
We’ve previously mentioned the dir
function and how it examines an object for the __dir__
method to output a list of all available methods. We can setup this list ourselves within an object:
class example:
def __init__(self):
self.key = 1000
def printkey(self):
print(self.key)
def __dir__(self):
return(["__init__", "__dir__", "printkey"])
x = example()
dir(x)
> ['__dir__', '__init__', 'printkey']
Class Inheritance
Class objects support inheritance of attributes from other “parent” objects. When a class object is defined, a parent class or tuple of classes can be added by adding a call to the end of the class definition:
class exampleParent:
mainKey = 1000
class exampleChild(exampleParent):
childKey = 2
x = exampleChild() #Note we only have to invoke the parent class within the constructor
print(x.mainKey)
> 1000
print(x.childKey)
> 2
Notice how in the above example, the exampleChild
object did not itself define the mainKey
attribute, but rather it “inherited” the attribute from the parent class. Inheritance is especially useful in situations where multiple classes use the same methods. Instead of re-typing those methods within each class, you can define a parent class with common methods and have the child objects inherit them.
Attribute Name Mangling
In the introduction we saw that that the underscore naming convention __atr
informs Python to “mangle” the attribute name when the object is instantiated. This means that the attribute name will be converted to _ClassName__atr
for reference. For example,
class example:
def regularName(self):
self.key = 1
def __mangledName(self):
self.value = 1
x = example()
print(dir(x))
> [...'_example__mangledName', 'regularName'] # omitted other output
As you can see from the above, the the __mangledName
was converted to _example__mangledName
. Typically this form of name mangling is done to classify an object attribute as private. It is also used to prevent naming collisions between instances of classes.
Class Objects Example
Now that we’ve learned the basics of Python objects and the class object constructor, lets go back to our motivating example of the geography class. We want to create a way to store student attributes and perform calculations on this information.
Let’s assume that our data was input as the following list of dictionaries:
geographyClass = [{"Name":"Mary" ,"HW":85 ,"P1":90 ,"M":88 ,"P2":100 ,"F":90},
{"Name":"Matthew" ,"HW":80 ,"P1":95 ,"M":70 ,"P2":90 ,"F":70},
{"Name":"Marie" ,"HW":90 ,"P1":92 ,"M":84 ,"P2":0 ,"F":91},
{"Name":"Manuel" ,"HW":79 ,"P1":70 ,"M":85 ,"P2":70 ,"F":82},
{"Name":"Malala" ,"HW":100 ,"P1":95 ,"M":100 ,"P2":98 ,"F":99}]
We can create objects by establishing a geographyStudent
class that contains attributes for input from the dictionary:
class geographyStudent:
def __init__(self, input): #Initializing each grade attribute
self.name = input["Name"]
self.homework = input["HW"]
self.project1 = input["P1"]
self.midterm = input["M"]
self.project2 = input["P2"]
self.final = input["F"]
Now we can add the data to a list of geographyStudent
class objects.
students = [] # Define an empty list in which to place objects
for s in geographyClass:
students.append(geographyStudent(s)) # Instantiate an object for each student
print(students)
> [<__main__.geographyStudent at 0x190feca1d30>,
> <__main__.geographyStudent at 0x190feca1d68>,
> <__main__.geographyStudent at 0x190feca1da0>,
> <__main__.geographyStudent at 0x190feca1dd8>,
> <__main__.geographyStudent at 0x190feca1e10>]
print(students[0].name)
> Mary
Now that we have defined a geography student class constructor, we can add methods to it to create some summaries of the student’s grade. We can create the finalGrade
method to calculate the student’s final grade for the semester:
class geographyStudent:
def __init__(self, input): #Initializing each grade attribute
self.name = input["Name"]
self.homework = input["HW"]
self.project1 = input["P1"]
self.midterm = input["M"]
self.project2 = input["P2"]
self.final = input["F"]
def finalGrade(self):
self.grade = (self.homework*0.2
+ self.project1*0.1
+ self.midterm*0.2
+ self.project2*0.1
+ self.final*0.4)
return(self.grade)
students = []
grades = []
for s in geographyClass:
students.append(geographyStudent(s))
for s in students:
grades.append(s.finalGrade())
print(grades)
> [89.6, 76.5, 80.4, 79.6, 98.9]
Code More, Distract Less: Support Our Ad-Free Site
You might have noticed we removed ads from our site - we hope this enhances your learning experience. To help sustain this, please take a look at our Python Developer Kit and our comprehensive cheat sheets. Each purchase directly supports this site, ensuring we can continue to offer you quality, distraction-free tutorials.
Python Objects Practice Problems
-
Create class object that supports the properties of a Doubly Linked List. The main object should be the list, and each list element should be an object with
key
,next
, andprevious
attributes. UseNone
as a null object. Solution -
Suppose every student in the school we’ve mentioned above has an attribute
x.school = "Example High"
. Build a class object that defines this more general student attribute, and recreate thegeographyStudent
object from above that inherits it. Solution -
List all the methods associated with Boolean, Integer, and Float objects. Solution
-
Consider the following Python code:
class example:
key = "The best of times."
x = example
x.key = "The worst of times."
y = example()
print(y.key)
> The worst of times.
Why did y
, a new example
object, have the same key as x
despite seemingly only changing the attribute within x
?
Solution
- Add a method to the
geographyStudent
class in the above example to print “Pass” if the student received a total grade greater than or equal to 65, or “Fail” otherwise. Solution
Solutions
1. Below is one such implementation.
class DoublyLLElement:
def __init__(self):
self.key = None
self.next = None
self.prev = None
class DoublyLinkedList:
def __init__(self):
self.root = None
DLL = DoublyLinkedList()
x = DoublyLLElement()
DLL.root = x
x.key = "First Element"
print(DLL.root.key)
> 'First Element'
2. This example motivates the use of inheritance:
class Student:
school = "Example High"
class geographyStudent(Student): # This is where inheritance is invoked
def __init__(self, input): #Initializing each grade attribute
self.name = input["Name"]
self.homework = input["HW"]
self.project1 = input["P1"]
self.midterm = input["M"]
self.project2 = input["P2"]
self.final = input["F"]
def finalGrade(self):
self.grade = (self.homework*0.2
+ self.project1*0.1
+ self.midterm*0.2
+ self.project2*0.1
+ self.final*0.4)
return(self.grade)
students = []
for s in geographyClass: # Assumes the list of dictionaries in the above example exists
students.append(geographyStudent(s))
print(students[1].school)
> Example High
3. The lists themselves are too long to print here, but each one can be printed with:
print(dir(True)) # Boolean
print(dir(1)) # Integer
print(dir(1.0)) # Float
4. The reason is that the x
variable erroneously pointed to the class constructor, rather than an object created with the constructor. Because x
points to the constructor, the attribute key
in the constructor was changed, and the following instantiations of the class object contain the changed key
value by default. It’s important to remember the difference between class objects and class object constructors and how objects are instantiated.
5. We can simply add another method that utilizes the existing grade
method:
class geographyStudent(Student):
def __init__(self, input):
self.name = input["Name"]
self.homework = input["HW"]
self.project1 = input["P1"]
self.midterm = input["M"]
self.project2 = input["P2"]
self.final = input["F"]
def finalGrade(self):
self.grade = (self.homework*0.2
+ self.project1*0.1
+ self.midterm*0.2
+ self.project2*0.1
+ self.final*0.4)
return(self.grade)
def PassFail(self):
grade = self.finalGrade()
if grade >= 65.0:
return("Pass")
else:
return("Fail")
students = []
for s in geographyClass: # Assumes the list of dictionaries in the above example exists
students.append(geographyStudent(s))
print(students[1].finalGrade())
> 76.5
print(students[1].PassFail())
> Pass
Did you find this free tutorial helpful? Share this article with your friends, classmates, and coworkers on Facebook and Twitter! When you spread the word on social media, you’re helping us grow so we can continue to provide free tutorials like this one for years to come.
-
The
dir
function actually accesses an object’s__dir__
method to obtain the necessary information. If the__dir__
method is not defined within the object, then Python will attempt to gather what information it can. Note thatdir(x)
is equivalent tox.__dir__()
. ↩ -
“private” is used in quotes here because the attribute can still be accessed externally by outside user via
object._atr
. The “private” status means that Python will not automatically import the attribute, and stands as a caution sign to outside users rather than a locked door. ↩ -
The first object doesn’t have to be titled
self
; it can be any variable name consistent within the class object as long as it’s the first object in the input tuple. However, Python convention uses “self” as the object self-reference, and deviating from convention can cause confusion to later reviewers of the code. ↩