Python Classes & Objects: Object-Oriented AI

Master Python classes and objects, the foundation of object-oriented programming crucial for building modular AI and machine learning applications. Learn to create blueprints for your data.

6.2 Python Classes and Objects

Python is a powerful object-oriented programming language where everything, from basic data types like integers and strings to complex user-defined structures, is treated as an object. This object-oriented approach promotes clean, modular, and reusable code.

What is a Class in Python?

A class in Python serves as a blueprint or template for creating objects. It encapsulates attributes (data) and methods (functions) that define the behavior and state of the objects created from it.

For instance, a Smartphone class could have attributes like brand, RAM, and storage, and methods like make_call() or send_message().

How to Define a Class in Python

You use the class keyword to define a class. The general syntax is:

class ClassName:
    """Optional documentation string describing the class."""
    # class body: attributes (variables) and methods (functions)

Example: A Basic Python Class

Here's an example of an Employee class:

class Employee:
    """Base class for all employees."""
    empCount = 0  # Class variable: shared across all instances of the class

    def __init__(self, name, salary):
        """
        Constructor method to initialize instance attributes.
        """
        self.name = name       # Instance variable: unique to each object
        self.salary = salary   # Instance variable: unique to each object
        Employee.empCount += 1

    def show_count(self):
        """Displays the total number of employees."""
        print(f"Total Employees: {Employee.empCount}")

    def show_details(self):
        """Displays the details of an employee."""
        print(f"Name: {self.name} | Salary: {self.salary}")

Explanation of the Employee class:

  • empCount = 0: This is a class variable. It's shared among all instances of the Employee class. When a new employee is created, this count is incremented.
  • __init__(self, name, salary): This is the constructor method. It's automatically called when you create a new object (instance) of the class.
    • self refers to the instance of the class being created.
    • self.name = name and self.salary = salary are instance variables. Each object created from the class will have its own unique name and salary.
    • Employee.empCount += 1 increments the class variable empCount for every new employee object.
  • show_count(self): A method that accesses and prints the class variable empCount.
  • show_details(self): A method that accesses and prints the instance variables name and salary for a specific employee object.

Creating Objects (Instances) from a Class

Once a class is defined, you can create objects (instances) from it by calling the class name as if it were a function.

# Create instances of the Employee class
emp1 = Employee("Alice", 3000)
emp2 = Employee("Bob", 4000)

# Call methods on the objects
emp1.show_details()  # Output: Name: Alice | Salary: 3000
emp2.show_details()  # Output: Name: Bob | Salary: 4000
emp1.show_count()    # Output: Total Employees: 2

Working with Object Attributes

Python allows for dynamic manipulation of object attributes: you can add, modify, or delete attributes of an object after it has been created.

emp1 = Employee("Alice", 3000)

# Add a new attribute dynamically
emp1.age = 25
print(f"Alice's age: {emp1.age}") # Output: Alice's age: 25

# Modify an existing attribute
emp1.salary = 3500
print(f"Alice's new salary: {emp1.salary}") # Output: Alice's new salary: 3500

# Delete an attribute
del emp1.age
# print(emp1.age) # This would now raise an AttributeError

Using Built-in Functions to Manage Attributes

Python provides several built-in functions to interact with object attributes:

  • getattr(obj, attr_name[, default]): Retrieves the value of an attribute. If the attribute doesn't exist, it returns default if provided, otherwise raises an AttributeError.
  • setattr(obj, attr_name, value): Sets or creates an attribute on an object.
  • hasattr(obj, attr_name): Checks if an object has a specified attribute, returning True or False.
  • delattr(obj, attr_name): Deletes a specified attribute from an object.
emp1 = Employee("Alice", 3000)

# Example using getattr with a default value
print(getattr(emp1, 'department', 'HR')) # Output: HR (since 'department' doesn't exist)

# Example using setattr to add an attribute
setattr(emp1, 'department', 'Sales')
print(f"Alice's department: {getattr(emp1, 'department')}") # Output: Alice's department: Sales

# Example using hasattr
print(hasattr(emp1, 'age')) # Output: False (if 'age' was deleted or never added)

# Example using delattr
if hasattr(emp1, 'department'):
    delattr(emp1, 'department')
    print(hasattr(emp1, 'department')) # Output: False

Python Built-in Class Attributes

Every class in Python has several special attributes that provide metadata about the class itself.

AttributeDescription
__dict__Dictionary containing the class's namespace.
__doc__String containing the class's documentation (docstring).
__name__String containing the class's name.
__module__String containing the name of the module where the class is defined.
__bases__Tuple containing the base classes of the current class (for inheritance).
# Accessing built-in class attributes
print(f"Docstring: {Employee.__doc__}")      # Output: Docstring: Base class for all employees.
print(f"Class Name: {Employee.__name__}")    # Output: Class Name: Employee
print(f"Module: {Employee.__module__}")      # Output: Module: __main__ (if run as a script)
print(f"Base Classes: {Employee.__bases__}")  # Output: Base Classes: (<class 'object'>,)
# print(f"Dictionary: {Employee.__dict__}") # This will print a large dictionary of class attributes and methods

Checking an Object’s Type

The type() function is used to determine the class (type) of any object.

print(type(42))                # Output: <class 'int'>
print(type(3.14))              # Output: <class 'float'>
print(type("Python"))          # Output: <class 'str'>
print(type([1, 2, 3]))         # Output: <class 'list'>
print(type({'x': 1, 'y': 2}))  # Output: <class 'dict'>

emp1 = Employee("Alice", 3000)
print(type(emp1))              # Output: <class '__main__.Employee'>

Memory Management and Garbage Collection

Python employs automatic memory management and garbage collection. When an object no longer has any references pointing to it, Python's garbage collector automatically reclaims the memory occupied by that object.

# Example of reference counting and garbage collection
a = 10      # 'a' references the integer object 10
b = a       # 'b' now also references the integer object 10
c = [b]     # 'c' references a list containing a reference to 10

del a       # Reference from 'a' to 10 is removed
del b       # Reference from 'b' to 10 is removed

# The integer object 10 is now only referenced by the list in 'c'.
# If 'c' is also deleted or its reference to 10 is removed, the object 10 might be garbage collected.

c[0] = 0    # The list now references 0, breaking the link to the original 10
c = None    # The list object itself is no longer referenced
# The integer object 10 is now unreferenced and eligible for garbage collection.

Using Destructors with __del__()

You can define a special method, __del__(), within a class to perform custom cleanup actions when an object is about to be destroyed (garbage collected).

class Resource:
    def __init__(self, name):
        self.name = name
        print(f"Resource '{self.name}' created.")

    def __del__(self):
        """Destructor method for custom cleanup."""
        print(f"Resource '{self.name}' is being released (garbage collected).")

# Create an object
r1 = Resource("FileHandle")

# The __del__ method is called automatically when the object is no longer referenced.
# This can happen when the object goes out of scope or is explicitly deleted.
del r1
# Output: Resource 'FileHandle' is being released (garbage collected).

Note: While __del__ can be useful, relying on it for critical cleanup can be tricky due to the non-deterministic nature of garbage collection. For robust resource management, context managers (with statement) are often preferred.

Data Hiding in Python using Name Mangling

Python doesn't have strict private/public access modifiers like some other languages. However, it provides a mechanism called name mangling to simulate data hiding. By prefixing an attribute name with double underscores (__), Python modifies the attribute's name internally, making it harder to access directly from outside the class.

class Counter:
    def __init__(self):
        self.__hidden_count = 0  # Name mangling: __hidden_count becomes _Counter__hidden_count

    def increment(self):
        self.__hidden_count += 1
        print(f"Incremented to: {self.__hidden_count}")

# Create an instance
c = Counter()
c.increment() # Output: Incremented to: 1
c.increment() # Output: Incremented to: 2

# Direct access from outside the class will raise an AttributeError
# try:
#     print(c.__hidden_count)
# except AttributeError as e:
#     print(e) # Output: 'Counter' object has no attribute '__hidden_count'

# Accessing the "mangled" attribute (not recommended for regular use)
print(f"Accessing mangled attribute: {c._Counter__hidden_count}") # Output: Accessing mangled attribute: 2

Key takeaway: Using double underscores (__) in front of a variable name within a class is a convention to indicate that the attribute is intended for internal use and to prevent accidental modification or access from outside the class.


SEO Keywords

  • Python classes tutorial
  • Python object creation
  • Python instance vs class variables
  • Python object attributes
  • Python built-in class attributes
  • Python getattr, setattr
  • Python type checking
  • Python memory management
  • Python destructors example (__del__)
  • Python name mangling

Interview Questions

  1. What is a class in Python and how is it different from an object?
    • A class is a blueprint or template, while an object is an instance created from that blueprint.
  2. How do class variables and instance variables differ? Give examples.
    • Class variables are shared among all instances (e.g., Employee.empCount), while instance variables are unique to each object (e.g., emp1.name).
  3. Explain the use of the __init__() method in Python.
    • It's the constructor method, automatically called when an object is created to initialize its attributes.
  4. How can you dynamically add, modify, or delete object attributes in Python?
    • Using direct assignment (obj.attr = value), modification (obj.attr = new_value), or the delattr() function and del obj.attr syntax.
  5. What are getattr(), setattr(), hasattr(), and delattr() used for?
    • They are built-in functions for dynamically accessing, setting, checking for, and deleting object attributes, respectively.
  6. What is the purpose of built-in class attributes like __dict__, __doc__, and __module__?
    • They provide metadata about the class itself, such as its namespace, documentation, and origin module.
  7. How does Python handle memory management and garbage collection?
    • Python uses automatic garbage collection, which reclaims memory from objects that are no longer referenced.
  8. What is the role of the __del__() method in Python?
    • It's a destructor method that can be defined to perform cleanup actions just before an object is garbage collected.
  9. How do you check the type of an object in Python?
    • Using the built-in type() function.
  10. What is name mangling in Python and how does it help in data hiding?
    • Name mangling (prefixing attributes with __) internally renames attributes to a less predictable format (_ClassName__attributeName), making them difficult to access directly from outside the class, thus simulating private members.