Python Classes

Python is a multi-paradigm programming language but under the hood is object oriented. You can define classes with methods and you can instantiate objects like in Java and C++. A class is similar to a cookie cutter. Using one cutter you can make many cookies. Similarly, by defining a class you can make many objects having the same properties.

What is a class?

A class is a named block of code that contains variables and function declarations. The variables are called "properties" or "attributes". The functions declared inside a class are called "methods". A method can return a result like a function but not all methods have results.

We can use a class like a code template to create objects during runtime. Each object is dynamically allocated in memory and it has its own namespace. An object can be a named variable or can be an element into a collection.

Objects are also called "instances" of a class. You can initiate an object using an "object constructor" that is a special method defined in a class, called __init__. It could be called "constructor" but in Python there is a convention to name all private functions with double underscore prefix.

Example:


# define a class
class Dog:
    kind = "canine" # class variable
    def __init__(self, name):
        self.name = name #instance variable
        self.tricks = [] #instance variable
    def add_trick(self, trick):
        self.tricks.append(trick)

# instance of a class
e = Dog('Buddy') #first instance
d = Dog('Fido')  #second instance

# call methods using objects e and d
e.add_trick('roll over') 
d.add_trick('play dead') 

# access public property "tricks"
print(e.name, e.tricks) 
print(d.name, d.tricks)

Output:


Buddy ['roll over']
Fido ['play dead']

Notes:

  1. The __init__ function is the constructor. This has 2 parameters: self and name. When a new object is created from the class Dog, only second parameter is required. First parameter "self" represents the current object.
  2. When you create a new object you use the class name with arguments, but the arguments are received by the __init__ function not by the class. That may be confusing at first since the class has its own parameters representing parent classes.

Caution:

Many developers have difficulty understanding the difference between class attributes and object attributes. Any variable defined in local scope of the class is shared between all objects of the class. Object attributes must use "self" prefix. Both kinds of attributes can be modified in the constructor, but class attributes are shared between objects. That is the difference.

Practice: Run the class example locally, create a third Dog instance, and verify that each object has its own tricks list.

Inheritance

A class can inherit from another class called "base class".

Example:


#all classes have a common super-class: "object"
class Animal(object):
    def __init__(self, kind): 
        self.kind = kind

class Dog(Animal):
    def __init__(self, name):
        super().__init__("canine")
        self.name   = name
        self.tricks =[]
    def add_trick(self, trick):
        self.tricks.append(trick)

Observe: in the constructor of "Dog" we call the super(), this is the super class and __init__() that is the constructor for the base class. The object "Dog" will have all attributes of "Animal" plus some more specific to "Dog" and defined in __init__() constructor.

If the parameter of the class is missing the class will automatically be derived from the most base class, called the "root" class in other languages. This is the "object" class.

Record

Sometimes a developer wants a structure to hold some data. You can do this with a collection but one neat trick is to use a class with attributes. This kind of class does not require an instance. It can be used as an object:


# a simple class
class record: pass

# define attributes
record.type    = "url"
record.domain  = "https://sagecode.org"
record.purpose = "Research Laboratory"

# print attributes
print(record.domain)

# instantiation
test = record();
print(test.domain);

Note:there is no point to instantiate a class used as record. All instances will have same attributes as the class. These are called class attributes. You can not add new attributes for an object but you can for the class.

Switch Emulation

Classic C-style switch syntax does not exist in Python. Starting with Python 3.10, the language now has a match statement for structural pattern matching, which is the modern built-in alternative you should study first. The example below shows an older emulation technique based on a class, a helper function, and a while loop. It is historically interesting, but it is not the preferred modern approach.


#define a value holder class
class switch(object):
    value = None
    def __new__(class_, value):
        class_.value = value
        return True

# function to verify matching case
# => True or False
def case(*args):
    iterator = (arg == switch.value for arg in args)
    return any(iterator)

# Switch example:
for n in range(0,10):
  print(n,":", end="", flush=True)
  while switch(n):
    if case(0):
        print ("n is zero;")
        break
    if case(1, 4, 9):
        print ("n is a perfect square;")
    if case(2, 4, 6, 8):
        print ("n is an even number;")
    if case(2, 3, 5, 7):
        print ("n is a prime number;")
        break
    break    
  pass # end switch
pass # end for

Practice: Run the switch-emulation example locally, then replace it with a match example to compare the two styles.

This example demonstrates how developers used to emulate switch-like behavior before structural pattern matching existed in the language. The trick is to use significant names that "extend" the language. We exploit the fact that "switch" and "case" are not reserved keywords in Python, otherwise this would not work. In modern Python, use match for readable branch selection and keep techniques like this as background knowledge.