Creating a Class and Methods

Published

2026-02-27

We’ve implicitly been using different Classes during the course. We’ve created Objects from these classes, used their methods, and investigated their attributes. Understanding these at a basic level is really useful to fully understand how to extend certain programs - especially as we get into Spark. So let’s dive a little deeper into python programming!

What is a Class?

Classes provide a means of bundling data and functionality together.

Essentially, we can use a class to create a new type of object with its own attributes and methods. We’ve used tons of classes and objects created from them in the course so far. As an example, we can use the datetime module to deal with dates.

from datetime import date
date
<class 'datetime.date'>

We can use a method to create an instance of this class to read in a date such as .fromisoformat()!

my_date = date.fromisoformat("2000-01-02")
my_date
datetime.date(2000, 1, 2)

Or we can obtain today’s date (as of this page’s creation) using the today() method on an already created instance of the class.

my_date.today()
datetime.date(2026, 2, 27)

The class has defined attributes we can access such as .day or .year.

my_date.day
2
my_date.year
2000

In some instances, creating our own classes with defined methods and attributes can be very useful! More often, we may need to dig into the docs a bit to fully understand something in Spark. Either way, understanding how classes are created is important. Let’s learn the basics!

Investigating a Class

It is difficult to investigate built-in classes as they are generally defined in C, but we can checkout how the datetime.date class is created!

(Selected output given below:)

class date:
    """Concrete date type.

    Constructors:

    __new__()
    fromtimestamp()
    today()
    fromordinal()

    Operators:

    __repr__, __str__
    __eq__, __le__, __lt__, __ge__, __gt__, __hash__
    __add__, __radd__, __sub__ (add/radd only with timedelta arg)

    Methods:

    timetuple()
    toordinal()
    weekday()
    isoweekday(), isocalendar(), isoformat()
    ctime()
    strftime()

    Properties (readonly):
    year, month, day
    """

We can see that we start with class name_of_class:. This is how we’ll create a new class. Next we see a docstring like we’ve used when writing functions.

    def __new__(cls, year, month=None, day=None):
        """Constructor.
        Arguments:
        year, month, day (required, base 1)
        """
        ...
        year, month, day = _check_date_fields(year, month, day)
        self = object.__new__(cls)
        self._year = year
        self._month = month
        self._day = day
        self._hashcode = -1
        return self

An __new__ ‘dunder method’ (double underscore) exists to create an instance of the class! This is used when we execute date(). Many classes also have an __init__(self) that initializes the instance.

You can see it creates a number of different attributes associated with the object. However, these are ‘protected’ attributes. We define standard attributes after the class definition or using a special decorator, @property.

   # Read-only field accessors
    @property
    def year(self):
        """year (1-9999)"""
        return self._year

    @property
    def month(self):
        """month (1-12)"""
        return self._month

These are defined similar to how we create a function but, of course, we’ll call our attributes without () on the end (my_date.year)!

To create a method we define functions within the class (tabbed in still). Here the @classmethod decorator creates a method that works on the class (but not on an instance of the class):

    @classmethod
    def fromisoformat(cls, date_string):
        """Construct a date from a string in ISO 8601 format."""
        if not isinstance(date_string, str):
            raise TypeError('fromisoformat: argument must be str')

        if len(date_string) not in (7, 8, 10):
            raise ValueError(f'Invalid isoformat string: {date_string!r}')

        try:
            return cls(*_parse_isoformat_date(date_string))
        except Exception:
            raise ValueError(f'Invalid isoformat string: {date_string!r}')
  • This works on the class itself, we can create a method to work on an instance of the class by defining a function without using the @classmethod decorator.

  • For instance, the .replace() method allows a replacement of date field.

    def replace(self, year=None, month=None, day=None):
        """Return a new date with new values for the specified fields."""
        if year is None:
            year = self._year
        if month is None:
            month = self._month
        if day is None:
            day = self._day
        return type(self)(year, month, day)
  • Ok, we have the basics, let’s get to it!

Creating a Class with Attributes

Let’s create a class for students in this course. For each student we want to have a number of pieces of information and functionality. We can start with the class keyword as we saw above. Class objects support two kinds of operations: attribute references and instantiation.

  • We can add attribute references by simply defining a name and a value or a data type (name = str, for instance)
  • To set an initial state to the object, we can use __init__(). Here we’ll take in two arguments, name and unity ID.
class student:
    course = '554'
    department = 'st'
    
    def __init__(self, name: str, unity: str):
        self.full_name = name
        self.unity_id = unity

Now we can initiate an instance of a student object!

x = student("Justin Post", "jbpost2")

We can access the attributes defined above!

x.course
'554'
x.full_name
'Justin Post'
x.unity_id
'jbpost2'

We can add other attributes using the @property decorator.

class student:
    course = '554'
    department = 'st'
    
    def __init__(self, name: str, unity: str):
        self.full_name = name
        self.unity_id = unity
    
    @property
    def split_name(self) -> tuple[str, str]:
        """Return the first name and last name"""
        first, last = self.full_name.split(" ")
        return first, last

Now we should have an attribute that returns a tuple of length two with the first and last name.

x = student("Justin Post", "jbpost2")
print(x.split_name)
('Justin', 'Post')

Adding Methods to a Class

We can easily add our own methods. Let’s add a grades attribute and a method to add grades at creation time. To do this, we use the @classmethod decorator.

class student:
    course = '554'
    department = 'st'
    grades = None
    
    def __init__(self, name: str, unity: str):
        self.full_name = name
        self.unity_id = unity
    
    @property
    def split_name(self) -> tuple[str, str]:
        """Return the first name and last name"""
        first, last = self.full_name.split(" ")
        return first, last

    @classmethod
    def with_grades(self, name: str, unity: str, grades: list):
        student = self(name, unity)
        student.grades = grades
        return student
      
x = student.with_grades("Justin Post", "jbpost2", [3, 10, 40])
print(x.grades)
[3, 10, 40]

Sweet! Let’s also add a method to take an already created instance and add grades to it! To create this kind of method, we just define a new function without the decorator.

class student:
    course = '554'
    department = 'st'
    grades = None
    
    def __init__(self, name: str, unity: str):
        self.full_name = name
        self.unity_id = unity
    
    @property
    def split_name(self) -> tuple[str, str]:
        """Return the first name and last name"""
        first, last = self.full_name.split(" ")
        return first, last
    
    @classmethod
    def with_grades(self, name: str, unity: str, grades: list):
        student = self(name, unity)
        student.grades = grades
        return student
      
    def add_grade(self, grade: list):
        student = self
        if student.grades is None:
            student.grades = grade
        else:
            student = student.grades.extend(grade)
        return student

x = student.with_grades("Justin Post", "jbpost2", [3, 10, 40])
print(x.grades)
[3, 10, 40]
x.add_grade([12, 11])
print(x.grades)
[3, 10, 40, 12, 11]

Awesome! Last thing for our purposes, often an __str__ method is added, which notes how to print the object. Let’s use an F-string to print out some info.

class student:
    course = '554'
    department = 'st'
    grades = None
    
    def __init__(self, name: str, unity: str):
        self.full_name = name
        self.unity_id = unity
    
    def __str__(self):
        if self.grades is None:
            return f"Student(name={self.full_name}, unity_id={self.unity_id})"
        else:
            return f"Student(name={self.full_name}, unity_id={self.unity_id}, grades={self.grades})"
      
    @property
    def split_name(self) -> tuple[str, str]:
        """Return the first name and last name"""
        first, last = self.full_name.split(" ")
        return first, last
    
    @classmethod
    def with_grades(self, name: str, unity: str, grades: list):
        student = self(name, unity)
        student.grades = grades
        return student
      
    def add_grade(self, grade: list):
        student = self
        if student.grades is None:
            student.grades = grade
        else:
            student = student.grades.extend(grade)
        return student

Let’s try it out!

x = student("Justin Post", "jbpost2")
print(x)
Student(name=Justin Post, unity_id=jbpost2)
x = student.with_grades("Justin Post", "jbpost2", [3, 10, 40])
x.add_grade([12, 11])
print(x)
Student(name=Justin Post, unity_id=jbpost2, grades=[3, 10, 40, 12, 11])

Recap

Creating a class can be useful if you are going to create many objects of the same type. We can add our own attributes and methods!

Note: A python script with the class above is available here if you want to run the code. Next we look at how to execute python scripts from the command line!

Use the table of contents on the left or the arrows at the bottom of this page to navigate to the next learning material!