Classes#


Classes provide a means of bundling data and functionality together. Creating a new class creates a new type of object, allowing new instances of that type to be made. Each class instance can have attributes attached to it for maintaining its state. Class instances can also have methods (defined by its class) for modifying its state. (https://docs.python.org/3/tutorial/classes.html)

Create new class for describing students:

class Student():
    """Represents a student in a university database."""
    
    name = 'noName'

Create a new student <-> create new instance of the Student class:

theStudent = Student()
print( theStudent )
<__main__.Student object at 0x7f796c137770>

Access class data:

print( theStudent.name )
noName

Edit class data:

theStudent.name = 'Anna'
print( theStudent.name )
Anna

__init__ Function#

class Student():
    """Represents a student."""
    
#    name = 'noName'
#    specialization = "noSpecialization"
    
    def __init__(self, name='noName', specialization='noSpecialization'):
        """Initialize the Student object with the student's name and specialization."""
        
        self.name = name
        self.specialization = specialization
        
theStudent = Student('Anna','Chemistry')

print( theStudent )
print( theStudent.name )
print( theStudent.specialization )
<__main__.Student object at 0x7f796c1374d0>
Anna
Chemistry
?Student.__init__
theSecondStudent = Student('Robert', 'physics')

print( theSecondStudent )
print( theSecondStudent.name )
print( theSecondStudent.specialization )
<__main__.Student object at 0x7f796c3a2350>
Robert
physics

Add functionalities to the class:

class Student():
    """Represents a student."""

    #name = 'noName'
    #passed_courses = []
    
    def __init__(self, name='noName',passed_courses=None):
        """Initialize the Student class with the student's name."""
        
        self.name = name
        self.passed_courses = passed_courses if passed_courses is not None else []
        
    def addCourse(self, course):
        """Add a course to the list of passed courses."""
        
        self.passed_courses.append(course)

theStudent = Student('Joost')
print( theStudent.passed_courses)
theStudent.addCourse('Programming 1')

#theStudent.addCourse( 'Programming 2', 'Quantum Mechanics 1' )

print( theStudent.passed_courses)
[]
['Programming 1']
class Student():
    """Represents a student."""

    name = 'noName'
    passed_courses = []
    
    def __init__(self, name='noName',passed_courses=passed_courses):
        """Initialize the Student class with the student's name."""
        
        self.name = name
        self.passed_courses = passed_courses
        
    def addCourse(self, course):
        """Add a course to the list of passed courses."""
        
        self.passed_courses.append(course)

theStudent = Student('Joost')
print( theStudent.passed_courses)
theStudent.addCourse('Programming 1')

#theStudent.addCourse( 'Programming 2', 'Quantum Mechanics 1' )

print( theStudent.passed_courses)
[]
['Programming 1']

Warning

But be careful! Try to define default value of passed_courses outside of init and see what happens:

newStudent = Student('Emily')

#print(theStudent.passed_courses)
#theStudent.addCourse('Analytical mechanics')

print(newStudent.name )
print(newStudent.passed_courses )
Emily
['Programming 1']

Add functionalities to the class:

class Student():
    """Represents a student."""

    #name = 'noName'
    
    def __init__(self, name='noName'):
        """Initialize the student class with the student's name."""
        
        self.name = name
        self.passed_courses = []       # initially should be empty
        
    def addCourse(self, course):
        """Add a trick to the list of tricks."""
        
        self.passed_courses.append(course)
        
theStudent = Student('Andrea')
theStudent.addCourse('Quantum mechanics')
theStudent.addCourse('Electrodynamics')

anotherStudent = Student('Mary')
anotherStudent.addCourse('Neuroscience')

print( theStudent.name )
print( theStudent.passed_courses )

print( anotherStudent.name )
print( anotherStudent.passed_courses )
Andrea
['Quantum mechanics', 'Electrodynamics']
Mary
['Neuroscience']
class Student():
    """Represents a student."""

    #name = 'noName'
    
    def __init__(self, name='noName'):
        """Initialize the student class with the student's name and a list of passed courses."""
        
        self.name = name
        self.passed_courses = []       # initially tricks should be empty
        
    def addCourse(self, course):
        """Add a trick to the list of tricks."""
        
        self.passed_courses.append( course )
        
    def works(self):
        """Let the student work."""
        
        print(self.name+' submits and assignment')
        
Lucy = Student('Lucy')
Lucy.works()
print(Lucy)
Lucy submits and assignment
<__main__.Student object at 0x7f796c1374d0>

Magic Methods#

Python classes come with many predefined magic methods which are automatically called in certain situations. __init__(self, ...) is such a magic method which is automatically called upon initialization of the class instance.

There are many, many other magic methods automatically called upon:

  • construction and initialization

  • comparisons ( What should oneStudent == anotherStudent mean?)

  • applications of mathematical operations ( What should oneStudent + anotherStudent mean?)

See https://rszalski.github.io/magicmethods for a full list.

Use __str__ (magic method) to define a simple string which is used within the print(...) command:

class Student():
    """Represents a student."""

    #name = 'noName'
    
    def __init__(self, name='noName'):
        """Creates the student object with the student's name."""
        
        self.name = name
        
    def work(self):
        """Let the student work."""
        
        print(self.name+' submits an assignment')
        
    def __str__(self):
        """Create a string describing the class and return it."""
        
        return 'I\'m a student and my name is ' + self.name

        
Lars = Student('Lars')
print( Lars )

#Bailey.bark()
I'm a student and my name is Lars

Classes: Mathematical example#

Let us define a class which describes square diagonal matrices of the form

\[\begin{split} M = \begin{pmatrix} a & \\ & a \\ & & a \\ & & & \ddots \end{pmatrix} \quad \text{with} \quad M \in \mathbb{R}^{n \times n} \end{split}\]

and which implements addition and substraction operations.

class diagMatrix():
    """Represent diagonal matrices."""
    
    def __init__(self, value, dim):
        """Initialize the diagonal matrix.

        Keyword arguments:
            value -- diagonal values
            dim   -- dimension of the matrix
        """
        
        # sanity checks
        assert type(value) in [int, float], "value must be a number."
        assert isinstance(dim, int), "dim must be an integer."
        
        self.value = value
        self.dim = dim
        
    def __str__(self):
        """Create and return a simple ASCII reprensentation of the diagonal matrix."""
        
        myStr = ''
        for n in range(0, self.dim):
            for m in range(0, self.dim): 
                if(n == m):
                    myStr += str(self.value) + ' '
                else:
                    myStr += '0 '
            myStr += '\n'
        return myStr
    
diagMatrixA = diagMatrix(value=-2, dim=13)

print( diagMatrixA )
-2 0 0 0 0 0 0 0 0 0 0 0 0 
0 -2 0 0 0 0 0 0 0 0 0 0 0 
0 0 -2 0 0 0 0 0 0 0 0 0 0 
0 0 0 -2 0 0 0 0 0 0 0 0 0 
0 0 0 0 -2 0 0 0 0 0 0 0 0 
0 0 0 0 0 -2 0 0 0 0 0 0 0 
0 0 0 0 0 0 -2 0 0 0 0 0 0 
0 0 0 0 0 0 0 -2 0 0 0 0 0 
0 0 0 0 0 0 0 0 -2 0 0 0 0 
0 0 0 0 0 0 0 0 0 -2 0 0 0 
0 0 0 0 0 0 0 0 0 0 -2 0 0 
0 0 0 0 0 0 0 0 0 0 0 -2 0 
0 0 0 0 0 0 0 0 0 0 0 0 -2 
diagMatrixA = diagMatrix(-4.2, 3)
print( diagMatrixA )
-4.2 0 0 
0 -4.2 0 
0 0 -4.2 
diagMatrixA = diagMatrix("string", 3)
print( diagMatrixA )
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
Cell In[16], line 1
----> 1 diagMatrixA = diagMatrix("string", 3)
      2 print( diagMatrixA )

Cell In[14], line 13, in diagMatrix.__init__(self, value, dim)
      5 """Initialize the diagonal matrix.
      6 
      7 Keyword arguments:
      8     value -- diagonal values
      9     dim   -- dimension of the matrix
     10 """
     12 # sanity checks
---> 13 assert type(value) in [int, float], "value must be a number."
     14 assert isinstance(dim, int), "dim must be an integer."
     16 self.value = value

AssertionError: value must be a number.
diagMatrixA = diagMatrix(4, 3.2)
print( diagMatrixA )
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
Cell In[22], line 1
----> 1 diagMatrixA = diagMatrix(4, 3.2)
      2 print( diagMatrixA )

Cell In[20], line 14, in diagMatrix.__init__(self, value, dim)
     12 # sanity checks
     13 assert type(value) in [int, float], "value must be a number."
---> 14 assert isinstance(dim, int), "dim must be an integer."
     16 self.value = value
     17 self.dim = dim

AssertionError: dim must be an integer.
diagMatrixA = diagMatrix(4, 3)
print( diagMatrixA )

diagMatrixB = diagMatrix(2, 3)
print( diagMatrixB )
4 0 0 
0 4 0 
0 0 4 

2 0 0 
0 2 0 
0 0 2 
diagMatrixB = diagMatrixA + diagMatrixB
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[22], line 1
----> 1 diagMatrixB = diagMatrixA + diagMatrixB

TypeError: unsupported operand type(s) for +: 'diagMatrix' and 'diagMatrix'

Implement diagMatrixA + diagMatrixB via __add__(self, other).

class diagMatrix(diagMatrix):
    
    def __add__(self, other):
        """Add two diagonal matrices.

        Keyword arguments:
            self  -- first diagonal matrix
            other -- second diagonal matrix
        """
        
        # sanity checks
        assert self.dim == other.dim, "Matrices have different dimensions."
        
        newValue = self.value + other.value
        newDim = self.dim
        
        return diagMatrix(newValue, newDim)
    
diagMatrixA = diagMatrix(4, 3)
print( diagMatrixA )

diagMatrixB = diagMatrix(2, 3)
print( diagMatrixB )

diagMatrixC = diagMatrixA - diagMatrixB

print(diagMatrixC)
4 0 0 
0 4 0 
0 0 4 

2 0 0 
0 2 0 
0 0 2 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[6], line 25
     22 diagMatrixB = diagMatrix(2, 3)
     23 print( diagMatrixB )
---> 25 diagMatrixC = diagMatrixA - diagMatrixB
     27 print(diagMatrixC)

TypeError: unsupported operand type(s) for -: 'diagMatrix' and 'diagMatrix'

Note

Note the diagMatrix(diagMatrix) syntax. This is called inheritance: The new class diagMatrix(...) inherits the properties and methods from the “parent” class ...(diagMatrix) in the brackets.

class diagMatrix(diagMatrix):
    
    def __sub__(self, other):
        """Subtract two diagonal matrices.

        Keyword arguments:
            self  -- first diagonal matrix
            other -- second diagonal matrix
        """
        
        # sanity checks
        assert self.dim == other.dim, "Matrices have different dimensions."
        
        newValue = self.value - other.value
        newDim = self.dim
        
        return diagMatrix(newValue, newDim)
    
diagMatrixC = diagMatrix(5, 4)
print( diagMatrixC )

diagMatrixD = diagMatrix(3, 4)
print( diagMatrixD )
5 0 0 0 
0 5 0 0 
0 0 5 0 
0 0 0 5 

3 0 0 0 
0 3 0 0 
0 0 3 0 
0 0 0 3 
print(diagMatrixC + diagMatrixD)
8 0 0 0 
0 8 0 0 
0 0 8 0 
0 0 0 8 

Warning

diagMatrixA and diagMatrixB do not know about __sub__.

print(diagMatrixA - diagMatrixB)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[10], line 1
----> 1 print(diagMatrixA - diagMatrixB)

TypeError: unsupported operand type(s) for -: 'diagMatrix' and 'diagMatrix'