Python Programming for Business Analytics

Chapter 5
Files, Exceptions and Object-Oriented Foundations

Diploma in Business Analytics
Python Programming for Business Analytics   •   Diploma

Files, Exceptions and Object-Oriented Foundations

Introduction

Business analytics rarely consists only of calculations performed on data already held in memory. Analysts retrieve sales transactions from CSV files, exchange structured data through JSON, read configuration files, generate reports, and preserve results for later use. Real applications also encounter missing files, invalid values, incomplete records, and unexpected system conditions. A reliable analytics program must therefore manage data storage, file paths, errors, and program structure deliberately.

This chapter introduces Python’s practical facilities for file handling and exceptions, then develops the foundations of object-oriented programming. The aim is not to use classes everywhere, but to understand when a class can make an analytics application clearer, safer, and easier to maintain.

Learning Objectives

By the end of this chapter, you should be able to:

  • open, read, write, and append text files safely;
  • process CSV and JSON data using appropriate Python libraries;
  • construct portable file paths with pathlib;
  • distinguish syntax, runtime, and logical errors;
  • use try, except, else, and finally appropriately;
  • define and use classes, objects, attributes, and methods;
  • explain instance, class, and static methods;
  • apply encapsulation and composition in a small analytics application; and
  • evaluate the benefits and limitations of object-oriented design.

1. Files as Persistent Data Sources

A variable exists only while a program is running unless its value is saved. A file provides persistent storage: data remains available after the program terminates. Files may contain plain text, tabular records, serialized objects, images, or other binary data.

A file-processing task normally has four stages:

  1. identify the correct path;
  2. open the file using an appropriate mode and encoding;
  3. read or write data; and
  4. close the resource reliably.

Python’s with statement is preferred because it closes the file even when an exception occurs.

from pathlib import Path

path = Path("data") / "sales.txt"

with path.open("r", encoding="utf-8") as file:
    contents = file.read()

print(contents)

The mode "r" reads an existing file. The mode "w" creates a new file or replaces an existing file. The mode "a" appends to an existing file, creating it if necessary. The mode "x" creates a new file and fails if the file already exists. Adding "b" selects binary mode, as in "rb" or "wb".

1.1 Reading Text Efficiently

read() returns the entire file as one string, whereas readline() reads one line and iteration processes the file one line at a time. For large files, iteration is usually more memory-efficient.

path = Path("data") / "notes.txt"

with path.open(encoding="utf-8") as file:
    for line in file:
        cleaned_line = line.strip()
        if cleaned_line:
            print(cleaned_line)

The encoding argument matters because text is represented using character encodings. UTF-8 is a widely supported default for modern data exchange, but an organisation may require another encoding.

1.2 Writing Text

report_path = Path("output") / "summary.txt"
report_path.parent.mkdir(parents=True, exist_ok=True)

with report_path.open("w", encoding="utf-8") as file:
    file.write("Monthly sales summary\n")
    file.write("Total revenue: 125000\n")

mkdir(parents=True, exist_ok=True) creates missing directories and avoids failure when the directory already exists. In production systems, output should normally be written to a controlled directory rather than a path supplied without validation by an untrusted user.

2. Portable Paths with pathlib

A hard-coded path such as "C:\\Users\\Ana\\sales.csv" is unsuitable for a shared application. Operating systems use different path conventions, and users store projects in different locations. pathlib.Path provides an object-oriented, platform-aware interface.

from pathlib import Path

project_root = Path.cwd()
input_path = project_root / "data" / "sales.csv"
output_path = project_root / "reports" / "monthly.txt"

print(input_path.exists())
print(input_path.suffix)
print(input_path.name)

Useful operations include exists(), is_file(), is_dir(), suffix, name, parent, glob(), and mkdir(). A path object represents a location; it does not guarantee that the location exists or that the user has permission to access it.

Relative paths are interpreted from the program’s current working directory, which may differ from the directory containing the script. For reproducible applications, establish paths explicitly, document the expected project structure, or obtain a trusted project root through configuration.

3. Common Structured File Formats

3.1 CSV Files

Comma-separated values (CSV) represent rows and columns as text. CSV is widely used in business systems, but it has important complications: fields may contain commas, quotation marks, or line breaks; delimiters may differ; and values have no inherent types.

The csv module handles quoting rules more safely than manually splitting strings.

import csv
from pathlib import Path

sales = []
with Path("data/sales.csv").open(newline="", encoding="utf-8") as file:
    reader = csv.DictReader(file)
    for row in reader:
        sales.append({
            "order_id": row["order_id"],
            "amount": float(row["amount"]),
            "region": row["region"].strip()
        })

DictReader uses the header row as dictionary keys. Notice that conversion of amount is explicit: CSV values are strings by default. A robust program should also validate missing columns, blank values, invalid numbers, and unexpected negative amounts where business rules prohibit them.

To write CSV:

rows = [
    {"region": "East", "revenue": 42000},
    {"region": "West", "revenue": 39000}
]

with Path("output/revenue.csv").open("w", newline="", encoding="utf-8") as file:
    writer = csv.DictWriter(file, fieldnames=["region", "revenue"])
    writer.writeheader()
    writer.writerows(rows)

The newline="" convention is recommended by Python’s CSV documentation to prevent unwanted blank lines on some platforms.

3.2 JSON Files

JSON represents objects, arrays, strings, numbers, Boolean values, and null values. It is convenient for configuration and hierarchical data.

import json
from pathlib import Path

config_path = Path("config.json")
with config_path.open(encoding="utf-8") as file:
    config = json.load(file)

threshold = config.get("alert_threshold", 0)

A Python dictionary or list can be written using json.dump():

settings = {"currency": "USD", "include_returns": True}

with Path("settings.json").open("w", encoding="utf-8") as file:
    json.dump(settings, file, indent=2)

JSON has no native date, decimal, or set type. Such values require a defined representation, such as an ISO-formatted date string. JSON loaded from an external source must not automatically be trusted merely because it is syntactically valid. Its values and structure still require validation.

FormatStrengthTypical analytics use
Plain textSimple and human-readableLogs and notes
CSVCompact tabular exchangeTransactions and exports
JSONNested, structured dataAPIs and configuration
Binary formatsEfficient or type-preservingLarge datasets and specialised systems

4. Errors and Exceptions

An error is a defect or condition that prevents a program from producing the intended result. Syntax errors prevent code from being parsed. Runtime errors occur while code executes. Logical errors allow execution but produce incorrect results.

An exception is an object representing an abnormal condition during execution. For example, opening a missing file raises FileNotFoundError, converting invalid text to an integer raises ValueError, and accessing a missing dictionary key with square brackets raises KeyError.

4.1 Handling Exceptions

from pathlib import Path

try:
    with Path("data/sales.csv").open(encoding="utf-8") as file:
        first_line = file.readline()
except FileNotFoundError:
    print("The sales file was not found.")
except PermissionError:
    print("The application does not have permission to read the file.")

Specific exceptions should normally be caught before broader exceptions. This preserves useful information and avoids hiding unrelated programming defects.

A complete structure is:

try:
    value = int(user_text)
except ValueError:
    print("Enter a whole number.")
else:
    print(f"Accepted value: {value}")
finally:
    print("Input attempt completed.")

The else block runs only when no exception occurs. The finally block runs whether or not an exception occurs and is appropriate for cleanup, although with is generally better for files.

4.2 Raising Exceptions Deliberately

A function should reject invalid data rather than silently returning a misleading result.

def convert_amount(text):
    try:
        amount = float(text)
    except ValueError as error:
        raise ValueError("Amount must be numeric") from error

    if amount < 0:
        raise ValueError("Amount cannot be negative")
    return amount

Exception chaining, using raise ... from error, preserves the original cause while presenting a clearer application-level message.

Do not use a bare except: unless there is a carefully justified reason. It catches system-exiting conditions as well as ordinary exceptions and can conceal serious defects. Logging a technical error and displaying a suitable user message are often better than printing an exception and continuing with corrupted data.

5. Validation and Defensive Analytics

File access and data validation solve different problems. A file may exist and be readable while containing malformed or semantically impossible data. Validation should address:

  • required columns and keys;
  • data types;
  • allowable ranges;
  • duplicate identifiers;
  • missing values;
  • date formats; and
  • relationships between fields.

For example, a transaction record may satisfy the type requirement that quantity is an integer but still violate the business rule that quantity must be greater than zero. Validation rules should be documented because they affect reported results and business decisions.

A useful design separates file input, validation, transformation, and analysis. This separation makes tests more focused and allows the analysis function to operate on clean Python objects rather than file-specific details.

6. Object-Oriented Foundations

Object-oriented programming (OOP) organises software around objects that combine data and behaviour. A class is a blueprint; an object, or instance, is a concrete value created from that class.

class Sale:
    def __init__(self, order_id, amount, region):
        self.order_id = order_id
        self.amount = amount
        self.region = region

    def is_large(self, threshold):
        return self.amount >= threshold

sale = Sale("A104", 2500.0, "East")
print(sale.region)
print(sale.is_large(2000))

__init__ initialises an instance. self refers to the particular object on which a method operates. Attributes hold state, while methods express operations associated with that state.

6.1 Encapsulation and Invariants

Encapsulation groups related data and operations and controls how state is changed. Python does not enforce private attributes in the same way as some languages, but a leading underscore communicates that an attribute is intended for internal use.

class InventoryItem:
    def __init__(self, sku, quantity):
        self.sku = sku
        self._quantity = 0
        self.quantity = quantity

    @property
    def quantity(self):
        return self._quantity

    @quantity.setter
    def quantity(self, value):
        if not isinstance(value, int) or value < 0:
            raise ValueError("Quantity must be a non-negative integer")
        self._quantity = value

The rule that quantity cannot be negative is an invariant: a condition that should remain true for every valid object. Centralising the rule prevents different parts of an application from applying inconsistent checks.

6.2 Class and Static Methods

An instance method receives self. A class method receives cls and is often used as an alternative constructor. A static method receives neither automatically and is suitable for a utility logically related to the class but not dependent on instance or class state.

class Sale:
    tax_rate = 0.2

    def __init__(self, amount):
        self.amount = amount

    @classmethod
    def from_text(cls, text):
        return cls(float(text))

    @staticmethod
    def valid_amount(amount):
        return amount >= 0

tax_rate is a class attribute shared by instances unless an instance overrides it. Shared mutable class attributes require care because changing them can affect every object.

6.3 Composition Rather Than Unnecessary Inheritance

Composition means building a class from objects of other classes. An AnalyticsReport may contain a collection of Sale objects and a ReportFormatter. This often produces clearer designs than a deep inheritance hierarchy.

Inheritance expresses an “is a” relationship, such as OnlineSale being a specialised Sale. It should be used when the subtype genuinely satisfies the behaviour expected of the parent. In business analytics, composition is frequently preferable because data pipelines change independently of reporting, storage, and validation components.

7. An Integrated Analytics Example

The following illustrative design reads CSV records, converts them into Sale objects, validates amounts, and calculates a total. It separates responsibilities without introducing unnecessary complexity.

import csv
from pathlib import Path

class Sale:
    def __init__(self, order_id, amount, region):
        if not order_id:
            raise ValueError("Order ID is required")
        if amount < 0:
            raise ValueError("Amount cannot be negative")
        self.order_id = order_id
        self.amount = amount
        self.region = region

    @classmethod
    def from_row(cls, row):
        return cls(
            row["order_id"].strip(),
            float(row["amount"]),
            row["region"].strip()
        )

class SalesRepository:
    def __init__(self, path):
        self.path = Path(path)

    def load(self):
        records = []
        with self.path.open(newline="", encoding="utf-8") as file:
            reader = csv.DictReader(file)
            required = {"order_id", "amount", "region"}
            if not required.issubset(reader.fieldnames or set()):
                raise ValueError("CSV is missing required columns")
            for row_number, row in enumerate(reader, start=2):
                try:
                    records.append(Sale.from_row(row))
                except (KeyError, TypeError, ValueError) as error:
                    raise ValueError(
                        f"Invalid record on row {row_number}"
                    ) from error
        return records

class SalesSummary:
    def total_revenue(self, sales):
        return sum(sale.amount for sale in sales)

repository = SalesRepository("data/sales.csv")
try:
    sales = repository.load()
    summary = SalesSummary()
    print(summary.total_revenue(sales))
except (FileNotFoundError, PermissionError, ValueError) as error:
    print(f"Could not process sales data: {error}")

This design demonstrates several professional practices: paths are represented by Path; files are closed automatically; records are validated at construction; row-level failures retain context; and summary logic does not need to know how the file was stored.

8. Case Study: Regional Sales Reporting

Illustrative case. A retail company receives a daily CSV export from four regional branches. The existing script manually splits lines, assumes every amount is valid, and writes reports into a path that works only on the developer’s computer. Occasionally, one malformed row causes the entire report to fail without a useful explanation.

Problem

The company needs a repeatable process that is portable across computers, identifies invalid records, and produces trustworthy regional totals.

Analysis

Manual string splitting can misinterpret quoted commas. Hard-coded paths reduce portability. Processing all values as strings can produce incorrect arithmetic, while catching every exception can conceal programming defects. The application needs explicit format handling, validation, and controlled error reporting.

Approach and Solution

The redesigned process uses Path for locations, csv.DictReader for parsing, a Sale class for valid domain records, and targeted exception handling. Each row is converted inside a try block so its row number can be reported. Valid records are analysed; invalid records are either rejected according to policy or placed in a separate error report. Output directories are created deliberately, and the file encoding is specified.

Outcome

The process becomes portable and auditable. Analysts can distinguish a missing input file from a malformed row. The summary calculation operates on validated objects, reducing the risk that formatting details contaminate business logic.

Learning Points

Reliable analytics depends on more than a correct formula. Input contracts, validation rules, error handling, and resource management determine whether results can be trusted. Classes are useful here because a Sale represents a meaningful business concept with enforceable rules; a class would be less useful for a one-line transformation with no state or behaviour.

9. Limitations, Governance and Best Practices

Object-oriented design can improve maintainability, but it also introduces abstraction, indirection, and additional code. A small script may be clearer with functions and dictionaries. Classes should be introduced when they clarify a domain concept, protect invariants, manage related behaviour, or support testing and reuse.

File-based workflows also have limitations. CSV lacks strong typing and schema enforcement. Large files may exceed available memory if loaded entirely. Concurrent processes can overwrite outputs, and partial writes can leave incomplete reports. For critical systems, use temporary files followed by an atomic replacement strategy, maintain backups, and record processing metadata.

Security and governance are essential. Do not execute code or deserialize untrusted content merely because it arrives in a file. Restrict file permissions, validate paths, protect confidential customer data, and avoid placing personal data in logs. Retention, access control, auditability, and applicable privacy law should shape the design of analytics pipelines.

Best practices include:

  • use with for resource management;
  • specify encoding for text files;
  • use pathlib rather than platform-specific string manipulation;
  • validate structure and meaning, not merely file existence;
  • catch specific exceptions and preserve useful context;
  • avoid silently ignoring malformed records;
  • separate input, validation, transformation, and analysis;
  • write tests for both valid and invalid inputs; and
  • document assumptions about schemas, paths, units, and business rules.

Chapter Summary

Files provide persistent storage for analytics inputs, outputs, configurations, and logs. Python’s context managers ensure that resources are closed reliably. pathlib supports portable path construction, while csv and json provide safer format-specific processing than manual string operations. Because external data is imperfect, programs must distinguish syntax, runtime, and logical errors, handle specific exceptions, validate business rules, and raise informative errors when necessary.

Object-oriented programming combines state and behaviour in objects. Classes are valuable when they represent domain concepts, enforce invariants, or separate responsibilities. Instance methods operate on objects, class methods provide class-aware alternatives such as constructors, and static methods provide related utilities. Composition is often more flexible than unnecessary inheritance. In analytics applications, a design that separates file access, domain validation, and analysis is more testable and maintainable than a single large procedure.

Key Terms

File: A persistent named storage resource.

Path: A representation of a file or directory location.

Encoding: A rule for representing characters as bytes.

CSV: A text format for tabular, delimiter-separated data.

JSON: A structured text format for objects, arrays, and primitive values.

Context manager: A construct that manages setup and cleanup around a block.

Exception: An object representing an abnormal execution condition.

Validation: Checking that data satisfies structural and business requirements.

try statement: A statement used to monitor code that may raise exceptions.

except clause: A handler for a specified exception type.

Class: A blueprint defining data and behaviour for objects.

Object: A concrete instance of a class.

Attribute: Data associated with an object or class.

Method: A function defined within a class.

Encapsulation: Combining data and operations while controlling access to state.

Invariant: A condition that must remain true for a valid object.

Composition: Constructing an object from other collaborating objects.

Inheritance: Creating a specialised class from a parent class.

Repository: A component responsible for obtaining or storing domain data.

Serialization: Representing data in a form suitable for storage or transmission.

Review Questions

Short Answer Questions

  1. Why is a with statement preferred when opening a file?
  1. What is the difference between a relative path and an absolute path?
  1. Why must values loaded from CSV usually be converted explicitly?
  1. Distinguish a runtime error from a logical error.
  1. What roles do self and __init__ play in a Python class?

Long Answer Questions

  1. Explain how a robust application should read, validate, and process a CSV file. Include potential failure conditions.
  1. Compare CSV and JSON as data-exchange formats for business analytics.
  1. Explain try, except, else, and finally, and describe when each is appropriate.
  1. Discuss encapsulation, invariants, and properties using an inventory or sales example.
  1. Explain why composition may be preferable to inheritance in an analytics application.

Analytical / Application Questions

  1. Design a processing strategy for a sales file containing missing columns, invalid amounts, duplicate order IDs, and blank regions. State which problems should stop the pipeline and which could be reported for later correction.
  1. A script works on a developer’s computer but fails on a colleague’s computer because its file paths are hard-coded. Redesign the path and output strategy and justify your decisions.
  1. Evaluate whether a class is justified for a simple function that converts a list of transaction dictionaries into a total revenue value. Explain your decision using cohesion, state, validation, and maintainability.

Multiple Choice Questions with Answers

Question 1

Which file mode appends data to an existing text file?

A. "r"

B. "w"

C. "a"

D. "x"

Correct Answer: C. "a"

Question 2

Which Python tool provides a platform-aware representation of file paths?

A. pathlib.Path

B. csv.Reader

C. json.Object

D. os.TextFile

Correct Answer: A. pathlib.Path

Question 3

What type is normally returned for a CSV field read by csv.DictReader before conversion?

A. Integer

B. String

C. Boolean

D. Date

Correct Answer: B. String

Question 4

Which exception commonly indicates that a requested file does not exist?

A. KeyError

B. TypeError

C. FileNotFoundError

D. IndexError

Correct Answer: C. FileNotFoundError

Question 5

What is the main purpose of a finally block?

A. To run cleanup code regardless of whether an exception occurs

B. To define a new class

C. To convert CSV values to JSON

D. To suppress every programming error

Correct Answer: A. To run cleanup code regardless of whether an exception occurs

Question 6

In a class method, which name conventionally refers to the class itself?

A. self

B. this

C. class

D. cls

Correct Answer: D. cls

Question 7

Which concept describes a rule that a valid object must continue to satisfy?

A. Delimiter

B. Invariant

C. Encoding

D. Serialization

Correct Answer: B. Invariant

Question 8

Why should a program avoid a bare except: in ordinary error handling?

A. It cannot catch any exception

B. It catches only file errors

C. It can conceal unrelated and serious conditions

D. It automatically closes all files

Correct Answer: C. It can conceal unrelated and serious conditions

Question 9

Which design separates file access from business analysis most directly?

A. Putting all operations in one long function

B. Using a repository component for data access

C. Ignoring malformed records silently

D. Hard-coding every output path

Correct Answer: B. Using a repository component for data access

Question 10

Which statement about JSON is correct?

A. It automatically preserves every Python type

B. It is limited to flat tables

C. It supports nested objects and arrays

D. It cannot represent Boolean values

Correct Answer: C. It supports nested objects and arrays

Python Programming for Business Analytics
Chapter 5 of 14