Object oriented Programming

Object oriented Programming (OOP) is a programming system that is based around objects, which can contain both data and useful code that manipulate that data. So the main idea behind OOP is to have both data and the methods that manipulate it within objects, allowing for more organized, accessible and reusable code.

An object (such as lists, functions, strings, …) is an instance of a class; a fundamental building block of Python. So the most important concept in Python is the class: an object’s data type that bundles data and functionality together.

Core Python classes are:

  • Integers
  • Floats
  • Strings
  • Booleans
  • Lists
  • Dictionaries
  • Tuples
  • Sets
  • Frozensets
  • Functions
  • Ranges
  • None
  • Custom-defined

Methods & Attributes

Method is a function that belongs to a class and typically performs an action or operation. A sample would be .replace().

Attribute is a value associated with an object or class which is referenced by name using dot notation. A sample would be .shape or .columns.

Dot notation is how to access the methods and attributes that belong to an instance of a class (to an object).

A simpler way of thinking about the distinction between attributes and methods is to remember that attributes are characteristics of the object, while methods are actions or operations.

Data Types & Classes

Data Type is an attribute that describes a piece of data based on its values, its programming language, or the operations it can perform. Types are categories that show the kind of data a variable can take and a function can return or take as its arguments. Additionally, a data type specifies the range of possible values and actions that may be performed on them. 

Programming languages usually have the following built-in types: integer, float, Boolean and strings.

Class, on the other hand, serves as a blueprint for creating objects, which are instances of the class. Each class defines a set of attributes and methods that the objects created from the class can have.

In other words, it describes the structure and behavior that objects of the class should have. The structural part consists of attributes: features that all the objects of the class will have. Behavior refers to the methods we can invoke on the objects.

These two concepts in Python 3

I was checking these terms in stack-overflow and came across the following post. The reason why these two are easily to be mixed with each other might be this: 

“Python had both types and classes. Types were built-in objects defined in C; classes were what you built when using a class statement. The two were named differently because you couldn’t mix these; classes could not extend types. 

This difference was artificial, a limitation in the language implementation. Starting with Python 2.2, the developers of Python have slowly moved towards unifying the two concepts, with the difference all but gone in Python 3. Built-in types are now also labeled classes, and you can extend them at will.”

More on Object-oriented Programming

The above mentioned concepts would make more sense with a deeper understanding of OOP. Although the following terms are way out of my knowledge and beyond the scope of this post, I’d keep them here as they can serve as a starting point for my future research and studies.

Classes are fundamental to Object-oriented Programming, a programming paradigm that emphasizes the organization of code around objects and their interactions. OOP provides a way to model real-world entities, their attributes, and their behaviors in software code. 

Object-oriented programming principles include encapsulation, inheritance, polymorphism, and abstraction. These principles facilitate code modularity, reusability, and maintainability. These concepts require advance level of knowledge but here are some basic notes:

Hierarchy and Inheritance 

Types may have hierarchical relationships, such as integer types inheriting from numeric types. However, inheritance is incidental to types. In contrast, it’s the key concept when it comes to classes. They can inherit properties and behavior from other classes (a sample will be given below), which enables code reuse and establishes hierarchical relationships between them.

Static vs. Dynamic 

Types are often determined at compile-time in statically typed languages. In contrast, classes are typically determined and instantiated at runtime.

Storage 

There’s a difference between storing built-in values such as integers and objects instantiated from our classes. For example, built-in types are stored on the stack (in Java) or directly in memory (in Python), whereas user-defined objects created from classes are stored on the heap in both Java and Python. 

In Python, a class definition is stored as a Python code object, which is created when we define a class. The code object contains the bytecode instructions and other metadata associated with the class.

Benefits and Usage 

Types enforce constraints on the values that variables can hold, which ensures data integrity. Furthermore, statically-typed languages can use compile-time checking to detect type errors and inconsistencies before the code is executed. Additionally, types enable efficient memory allocation and data representation, as well as enhance code readability

Since a class is a type, it has all the benefits but offers some additional ones: 

  • Classes enable code reuse through inheritance. 
  • They facilitate the implementation of object-oriented design principles, such as encapsulation, abstraction, and polymorphism. (not in detail but a little more information will be given below)
  • Classes facilitate code organization by encapsulating related data and behavior into cohesive units.

Putting some of these in action and giving a sample would help us to understand these concepts better:

How classes work in Python

  • Defining a Class: We create a class using the class keyword. A class can have attributes (variables) and methods (functions) that define its behavior.
class Cat:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def meow(self):
        return f"{self.name} says murr!"

In this example, Cat is a class with an initializer method (__init__) that sets up the name and age attributes, and a method meow that returns a string. 

  • Creating an Instance: We create an instance (or object) of the class by calling the class as if it were a function.
my_cat = Cat("Simba", 3)

Here, my_cat is an instance of the Cat class with the name “Simba” and age 3.

  • Accessing Attributes and Methods: Once we have an instance, we can access its attributes and methods using dot notation.
print(my_cat.name)      # Output: Simba
print(my_cat.meow())    # Output: Simba says murr!
  • Inheritance: Python supports inheritance, allowing us to create a new class based on an existing class. This promotes code reuse and helps create a hierarchical relationship between classes.
class Bengal(Cat):
    def meow(self):
        return f"{self.name} says hisss!"

Bengal is a subclass of Cat, and it overrides the meow method.

  • Encapsulation: Classes in Python support encapsulation by bundling data (attributes) and methods that operate on the data within a single unit. This helps to manage complexity by hiding the internal state and requiring interaction through methods.
  • Polymorphism: Python classes allow for polymorphism, meaning that we can use a common interface to handle different types of objects, often through method overriding or method overloading.

The following is a  little more complicated sample that I encountered in Google’s Advanced Data Analytics certificate program:
class Spaceship:
   # Class attribute
   tractor_beam = 'off'

   # Instance attributes
   def __init__(self, name, kind):
       self.name = name
       self.kind = kind
       self.speed = None

  # Instance methods
   def warp(self, warp):
       self.speed = warp
       print(f'Warp {warp}, engage!')

   def tractor(self):
       if self.tractor_beam == 'off':
           self.tractor_beam = 'on'
           print('Tractor beam on.')
       else:
           self.tractor_beam = 'off'
           print('Tractor beam off')

A class is like a blueprint for all things that share characteristics and behaviors. In this case, the class is Spaceship. There can be all different kinds of spaceships. They can have different names and different purposes. 

Whenever we create an object of a given class, we’re creating an instance of that class. This is also known as instantiating the class. In the code above, every time we instantiate an object of the Spaceship class it will start with its tractor beam set to off. The tractor beam is a class attribute. All instances of the Spaceship class have one. 

There are also instance attributes. These are attributes that we can assign when we instantiate the object.

# Create an instance of the Spaceship class (i.e. "instantiate")
ship = Spaceship('Mockingbird','rescue frigate')

# Check ship's name
print(ship.name)

# Check what kind of ship it is
print(ship.kind)

# Check tractor beam status
print(ship.tractor_beam)
Mockingbird
rescue frigate
off

The next block of code uses the warp() method to set the warp speed to seven. Then it checks the current speed of the ship using the speed attribute.

# Set warp speed
ship.warp(7)

# Check speed
ship.speed
Warp 7, engage!
7

This final block of code uses the tractor() method to toggle the tractor beam. Then it checks the current status of the tractor beam using the tractor_beam attribute.

# Toggle tractor beam
ship.tractor()

# Check tractor beam status
print(ship.tractor_beam)
Tractor beam on.
on


Once again, I can’t go through each of these concepts in depth. To conclude:

Understanding classes and object-oriented programming is crucial for building scalable and maintainable code in Python. Classes help in organizing code, modeling real-world entities, and creating reusable components.

In