from datetime import date
date<class 'datetime.date'>
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!
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.
We can use a method to create an instance of this class to read in a date such as .fromisoformat()!
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.
The class has defined attributes we can access such as .day or .year.
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!
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)
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.
name = str, for instance)__init__(). Here we’ll take in two arguments, name and unity ID.Now we can initiate an instance of a student object!
We can access the attributes defined above!
We can add other attributes using the @property decorator.
Now we should have an attribute that returns a tuple of length two with the first and last name.
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]
[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 studentLet’s try it out!
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!