•
DAY3
OBJECT-ORIENTED PROGRAMMING AND
MODULES
Understanding Classes and Objects
Object-oriented programming (OOP) is a core concept in Python that
models real-world relationships. The primary mechanisms in OOP are
classes and objects. Mastering them enables you to create code that is well-
organized, modular, and reusable.
A class may be thought of as a blueprint or template for creating objects.
For example, we can have a User class that defines the common attributes
and behaviors of user objects in an application. To summarize, classes
encapsulate related data and functions into a single logical entity.
Classes are defined using the class keyword:
class User:
pass
This creates a new User class, which currently has nothing inside it. We can
add attributes and methods to enrich the class:
class User:
def init (self, name, email):
[Link] = name
[Link] = email
def log_in(self):
print([Link], 'is logged in')
The init () method is a unique constructor that runs whenever a new User
object is created. It initializes attributes like name and email. (self refers to
the current instance.)
We can also define other methods like log_in() which can then access and
operate on the object attributes using self. By doing this, you allow
encapsulating useful functionality that is tied to the User class specifically.
Now we can create User objects which are instances of this class:
userl = User('John Doe',' john@[Link] ')
user2 = User('Jane Doe',' jane@[Link] ')
print([Link]) # Prints 'John Doe'
print(user2,email) # Prints ' jane@[Link] '
userl.log__in() # Calls log__in() method
So objects encapsulate the class state (attributes) and behaviors (methods).
You can create multiple objects from a class, each with its own set of
attribute values.
Classes enable logical grouping of related data and functions. By doing this,
•
you can reduce complexity in large programs. The key principles are:
• Encapsulation: Bundling related attributes and methods into
classes.
• Abstraction: Exposing essential features and hiding
implementation details.
• Polymorphism: Methods behave differently based on class type.
• Inheritance: Child classes inherit attributes and behaviors from
parent classes.
For example, we can have a base Vehicle class with subclasses like Car,
Motorcycle, etc. The child classes inherit the general vehicle attributes
(engine, wheels) and override specific methods like num_wheels().
Inheritance enables code reuse across class hierarchies.
Defining classes gives clarity and structure to programs by conceptualizing
real entities using code. This aligns with thinking about discrete objects
with states and behaviors. Classes allow the modeling of unified concepts
central to the problem domain.
Some key benefits include:
• Modularity: Each class encapsulates a single concept.
• Reusability: Common logic can be defined once in a parent class.
• Pluggability: New classes can extend program functionality.
• Maintainability: Individual classes are easier to understand.
Let's look at an example model for a store with classes for Customer,
Product, ShoppingCart, and Order.
The Customer class represents registered customers with attributes like
name, shipping address, etc. The Product class models products with
properties like price, description, etc.
The ShoppingCart class tracks selected products and quantities as cart
items. Order represents a finalized order containing Customer details, cart
items, and total cost.
•
This demonstrates modeling domain concepts using classes. Key objects
and relationships are represented cleanly through encapsulation and
inheritance.
Proper decomposition into classes is crucial for large projects. Use
principles like minimalism (smaller classes), single responsibility (do one
thing), and open/closed (extend, not modify classes) when designing.
Object-oriented programming takes some practice but allows for building
reusable. robust and well-structured applications. The paradigm shift from
pure procedures is an investment well worth making.
In essence, classes are fundamental for modeling real-world entities in
code, encapsulating both state and behavior. Objects represent individual
entities, fostering modular and organized data representation. Through
composition and inheritance, developers create complex data structures.
Embracing object-oriented programming principles yields well-structured,
modular and manageable code.
Attributes, Methods, and the seH Keyword
Attributes and methods are key constructs that enable object-oriented
programming in Python. Combined with the self keyword, they provide a
powerful means of encapsulating data and behavior within class instances.
Mastering these concepts is essential for unlocking the benefits of OOP.
Attributes are data variables bound to a class instance. For example, a
Person class may have name and age attributes:
class Person:
def init(seH, name, age):
[Link] = name
•
[Link] = age
Here, name and age are attributes set via the initializer. We access them on
Person instances:
pl= Person("John", 30)
print([Link]) # Prints "John"
print([Link]) # Prints 30
Attributes store and expose data related to a specific object. They keep
important states private to the instance.
Methods are functions defined inside a class. They encapsulate behaviors
and actions:
class Person:
# ...
def greet(self):
print(f"Hello, my name is {[Link]}")
pl= Person("John", 30)
[Link]() # Calls greet method
Methods provide interfaces for interacting with objects. This code will
bundle data and related logic together.
The self keyword refers to the instance being invoked within the code -
methods access to attributes and other methods on the same object. Self
binds them together into an encapsulated unit.
Proper use of attributes, methods, and self promotes:
• Abstraction - Hide complexity behind interfaces
• Encapsulation - Group-related data and behavior
• Modularity - Break systems into logical objects
The correct use of this leads to code that is more modular, intuitive, and
reusable.
Mastering OOP is a milestone for any Python programmer. Classes power
codebases of any size. Keep these core principles in mind:
• Attributes for storing instance data
• Methods for encapsulating object behavior
• self for accessing other instance attributes/methods
With this foundational understanding, you can effectively utilize classes •
and objects to enhance your Python proficiency. They play a vital role in
managing complexity by facilitating clean abstraction and design principles.
Inheritance: Leveraging Existing Code
Inheritance is a fundamental object-oriented programming concept that
allows you to build relationships between classes and reuse code. When one
class inherits from another, it automatically gains access to all the attributes
and behaviors defined in the parent. This powerful mechanism allows you
to reduce program duplication by factoring common logic into superclass
base classes.
In Python, inheritance works by deriving subclasses from parent classes.
For example, we can define a Vehicle base class with shared attributes like
num_wheels and behaviors like drive():
class Vehicle:
def init(seH, num_wheels):
seH.num_wheels = num_wbeels
def drive(self):
print("Driving on {} wheels" .format(self.num_wheels))
Then "Car" and "Truck0 can inherit the common code from "Vehicle":
class Car(Vehicle):
pass
class 'Iruck(Vehicle):
pass
Now Car and Truck objects will initialize with num_wheels and respond to
drive() even without re-writing any code. We get code reuse through the
inheritance hierarchy.
You can override inherited methods by redefining them in the subclass. For
example, Truck could customize drive():
class Truck(Vehicle):
•
def drive(self):
print("Driving truck on{} wheels" .format(seH.num_wheels))
[Link]() will now print the truck-specific message - whereas
[Link]() still uses the original.
Inheritance promotes the reuse of code, but should be managed judiciously:
• Only subclass when extending behavior - otherwise use
composition
• Avoid deep inheritance hierarchies of more than 3-4 levels
• Limit subclassed methods using super() to augment vs override
Multiple inheritance allows a class to inherit from multiple parent classes,
gaining the attributes and methods of both:
class FlyingCar(Car, Plane):
pass
However, multiple inheritance can get messy, so use sparingly when the
relationship warrants it.
In Python, inheritance works by following the method resolution order
(MRO). MRO will then define the order to check parent classes when
resolving attributes and methods. Use mro to view:
[Link]
Output: (FlyingCar, Car, Plane, Vehicle, Object)
Knowing the MRO helps avoid ambiguous method overrides across
hierarchies.
Here are some tips for effectively using inheritance:
• Abstract common logic into base classes
• Inherit when you need to extend subclasses
• Use composition rather than inheritance when possible
• Leverage polymorphism by passing subclass objects
interchangeably
• Call super() when overriding methods in subclasses
• Avoid duplicating code through the inheritance hierarchy
To conclude. inheritance in Python allows you to:
•
•
•
•
•
Define Hierarchical relationships between classes
Leverage and extend existing logic in base classes
Avoid duplication by inheriting common attributes and behaviors
Customize inherited functionality by overriding methods
Enable polymorphic code by passing subclass instances
interchangeably
•
When developing larger object-oriented programs, strategic use of
inheritance facilitates code reuse and simplifies complexity management.
Identifying essential relationships between classes guides the creation of
efficient inheritance hierarchies and interfaces. Learn to recognize
duplicative logic that could be elevated into superclass abstractions. Before
you know it, you'll be inheriting like a pro!
Modules: Organizing and Reusing Code
Modules in Python allow you to organize code into reusable1 modular
packages. Instead of consolidating all your code into a single large script,
modules allow you to divide components into separate, self-contained files
that can be imported as required. Modules make code more maintainable,
shareable, and professionally structured.
Modules are simply Python .py files containing functions, classes, and
variable definitions. By convention, module names are short, lowercase,
and underscore-separated, for example, math_tools.py. You can then import
modules to access their contents in other scripts.
For example, math_tools.py may contain:
def add(x, y):
retumx + y
def multiply(x, y):
return x *y
To use these functions in another .py file:
import math_tools
math_tools.add(S, 2)
math_tools.multiply(3, 7)
Through doing this, you can import the module namespace and access
functions with dot notation. You can also assign shortcuts:
import math_tools as mt
[Link](S, 2)
•
Some key benefits of modules include:
• Organize related code into cohesive bundles
• Reuse logic across multiple scripts
• Only import what is necessary
• Namespace functions to avoid collisions
• Share modules across projects or teams
When structuring modules:
• Focus modules on discrete tasks or domains
• Use descriptive names like data_processing
• Limit inter-module dependencies where possible
• Add docstring comments explaining the usage
Now let's explore effective practices for importing modules:
• Put import statements at the top to convey dependencies
• Only import modules that are used to avoid bloat
• Use unique aliases like import pandas as pd to avoid name
collisions
• from module import function syntax pulls in just a specific
component
• Import built-in modules like sys and collections for extra tools
Thoughtful use of modules makes code more professional 1 shareable, and
maintainable. The skill of developing portable, reusable modules is a
hallmark of skilled Pythonistas.
Modules enable you to extend Python's capabilities to match your exact
needs. If the built-in modules do not have what you need, you can define
your own to fill gaps in functionalities, data types or utilities.
1
•
For example, you may create an audio_processing module if there are no
Python libraries that suit your audio analytics needs. Similarly, you may
make a hardware_drivers module to interface with custom hardware
devices. The domain-specific modules you create become part of your
unique Python ecosystem.
Here are some common examples of custom modules:
• Company-specific datastore interfaces like accounts_db
• Game physics and rendering engines
• Hardware driver modules like arduino_io
• Specific data algorithms like recommendation_engine
• Utility functions like image_processing
• Shared constant definitions like config
The process for creating custom modules follows the same principles:
• Organize related functions and classes into .py files
• Use descriptive names like audio_conversion.py
• Include docstrings and comments explaining the usage
• Import your module and call its functions
• Publish great modules to GitHub to share with the community
Your custom modules should be:
• Self-contained with unique utility
• Well-documented for ease of use
• Portable for reuse across projects
• Distributed for convenience via packaging
With custom modules, you wield the full power and extensibility of Python
for your specific use case. Modules enable clean, maintainable, and fluent
code architecture.
Exploring Python Standard Library
The Python standard library is a powerful collection of modules packed
with functionality to solve nearly any programming need. Mastering the