Unit 2: Data Types and OOP Concepts

CAP776 — Programming In Python 6 min read

I. Orientation — Python’s Data and Object Model

Python is a dynamically typed, object-oriented programming language in which every value is an object. A data type determines a value’s possible contents, supported operations, and behavior; object-oriented programming organizes related data and behavior into reusable classes and objects.

  • Dynamic typing: A variable is bound to an object without an explicit type declaration; x = 10 binds x to an integer, while x = "ten" can later bind it to a string.
  • Object identity, type, and value: Every object has an identity, a type, and a value. The functions id(x), type(x), and expressions involving x reveal these properties.
  • Mutability:
    • Mutable objects: Lists, dictionaries, and sets can change after creation.
    • Immutable objects: Strings, tuples, and range objects cannot change after creation.
  • Ordered collections: Strings, lists, tuples, dictionaries, and ranges preserve a defined order; dictionary order follows insertion order.
  • Unordered collections: Sets do not support positional indexing because their elements are organized by hashing rather than sequence position.
  • Zero-based indexing: The first element of a sequence has index 0; negative indices count backward, so -1 identifies the last element.
  • Iteration: Collection and range objects are iterable and can be processed with for, membership operators, comprehensions, and functions such as len().
  • Class-based organization: A class defines attributes and methods, while an object is a particular instance created from that class.

II. Strings — Immutable Text Sequences

A. Strings

A string is an immutable sequence of Unicode characters used to represent and manipulate textual data.

  • Creation: Strings are enclosed in matching single, double, or triple quotation marks.
PYTHON
name = "Python"
message = 'Data Types'
paragraph = """Text can
span lines."""
  • Indexing and slicing: For s = "Python", s[0] is "P", s[-1] is "n", and s[1:4] is "yth". A slice follows s[start:stop:step], excluding stop.
  • Immutability: s[0] = "J" raises TypeError; a changed string must be created, such as "J" + s[1:].
  • Operators: + concatenates, * repeats, and in tests membership. Thus, "Py" in "Python" evaluates to True.
  • Common methods:
    • Case and spacing: upper(), lower(), title(), and strip().
    • Search and replacement: find(), count(), and replace().
    • Splitting and joining: "a,b".split(",") produces ["a", "b"]; "-".join(["a", "b"]) produces "a-b".
  • Formatting: An f-string embeds expressions clearly.
PYTHON
language = "Python"
version = 3
text = f"{language} version {version}"
  • Escape sequences: \n represents a newline and \t a tab; raw strings such as r"C:\new" treat backslashes literally.

III. Lists — Mutable Ordered Collections

A. Lists

A list is a mutable, ordered sequence that can store duplicate values and objects of different types.

  • Creation: Square brackets or list() create lists; numbers = [10, 20, 30] contains three integer references.
  • Access: Lists support indexing, slicing, and nesting. In matrix = [[1, 2], [3, 4]], matrix[1][0] is 3.
  • Mutation: An indexed element or slice can be replaced; numbers[1] = 25 changes the list to [10, 25, 30].
  • Common methods:
    • Adding: append(x) adds one object, extend(iterable) adds several elements, and insert(i, x) adds at index i.
    • Removing: remove(x) removes the first matching value, pop(i) removes and returns an indexed value, and clear() removes all elements.
    • Organization: sort() changes the list in place, while reverse() reverses its order.
  • Copying and aliasing: b = a makes both names refer to one list; b = a.copy() creates a shallow copy.
  • List comprehensions: A concise expression can construct a transformed or filtered list.
PYTHON
squares = [n * n for n in range(1, 6) if n % 2 != 0]
# Result: [1, 9, 25]

Here, n is each generated integer, and the condition retains odd values.

IV. Tuples — Fixed Ordered Collections

A. Tuples

A tuple is an immutable, ordered sequence suited to fixed records and values that should not be reassigned.

  • Creation: Parentheses are conventional: point = (4, 7). A one-element tuple requires a comma, as in single = (4,).
  • Immutability: point[0] = 5 raises TypeError, although a mutable object stored inside a tuple may itself change.
  • Operations: Tuples support indexing, slicing, concatenation, repetition, membership, count(), and index().
  • Packing and unpacking: record = ("Asha", 20) packs two values; name, age = record assigns them separately.
  • Multiple return values: A function can return a tuple, which callers may unpack.
PYTHON
def minimum_maximum(values):
    return min(values), max(values)

lowest, highest = minimum_maximum([4, 1, 9])
  • List contrast: Tuples provide structural immutability and can be dictionary keys when all their contents are hashable; lists cannot be dictionary keys.

V. Dictionaries — Key–Value Mappings

A. Dictionaries

A dictionary is a mutable mapping that associates unique, hashable keys with arbitrary values.

  • Creation: student = {"name": "Ravi", "marks": 82} maps two string keys to values.
  • Key rules: Keys must be hashable, so strings, numbers, and suitable tuples are allowed; lists and sets are not. Reassigning an existing key replaces its value.
  • Access: student["name"] returns "Ravi" but raises KeyError for a missing key; student.get("grade", "NA") safely supplies "NA".
  • Modification: student["marks"] = 90 updates a value, while student["city"] = "Pune" inserts a pair.
  • Common methods: keys(), values(), and items() provide dynamic views; update() merges entries, and pop(key) removes and returns a value.
  • Iteration: Iterating directly processes keys; paired iteration uses items().
PYTHON
for key, value in student.items():
    print(key, value)
  • Dictionary comprehension: {n: n * n for n in range(1, 4)} produces {1: 1, 2: 4, 3: 9}.
  • Use cases: Dictionaries model named records, frequency tables, configurations, caches, and fast key-based lookup.

VI. Sets — Collections of Unique Elements

A. Sets

A set is a mutable collection of unique, hashable elements designed for membership testing and mathematical set operations.

  • Creation: {1, 2, 3} creates a set, but an empty set requires set() because {} creates an empty dictionary.
  • Uniqueness: {1, 1, 2} becomes {1, 2}; duplicate values are automatically eliminated.
  • No positional access: Sets are not indexed or sliced, and their displayed iteration order should not be treated as a fixed sequence order.
  • Modification: add(x) inserts one element, update(iterable) inserts several, discard(x) removes without error, and remove(x) raises KeyError if absent.
  • Set operations: For sets A and B:
    • Union: A | B contains elements in either set.
    • Intersection: A & B contains common elements.
    • Difference: A - B contains elements only in A.
    • Symmetric difference: A ^ B contains elements in exactly one set.
  • Relationships: A <= B tests whether A is a subset; A.isdisjoint(B) tests whether no elements are shared.
  • Immutable form: frozenset() creates an immutable, hashable set suitable for use as a dictionary key or another set’s element.

VII. Range — Arithmetic Integer Sequences

A. Range

A range is an immutable, memory-efficient sequence of integers commonly used to control iteration.

  • Forms: range(stop), range(start, stop), and range(start, stop, step) define arithmetic sequences in which stop is excluded.
  • Symbol meanings:
    • start: First generated integer; the default is 0.
    • stop: Exclusive boundary.
    • step: Difference between consecutive integers; the default is 1 and it cannot be zero.
  • Examples: range(2, 8, 2) represents 2, 4, 6; range(5, 0, -1) represents 5, 4, 3, 2, 1.
  • Memory efficiency: A range stores its arithmetic definition rather than materializing every integer. list(range(4)) explicitly creates [0, 1, 2, 3].
  • Sequence behavior: Ranges support len(), indexing, slicing, membership testing, and equality comparisons.
  • Loop use:
PYTHON
for index in range(3):
    print(index)

This iterates with index equal to 0, 1, and 2.

VIII. OOP Features — Class-Based Program Design

A. OOP features

Object-oriented programming models a system through interacting objects that combine state with behavior.

  • Class: A blueprint defining attributes and methods; class Account: introduces a new class.
  • Object: An instance of a class; a = Account() constructs an Account object.
  • Constructor initialization: __init__ initializes instance state, while self refers to the current instance.
PYTHON
class Account:
    bank = "ABC"

    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount
  • Attributes: bank is a class attribute shared through the class; owner and balance are instance attributes.
  • Methods: a.deposit(100) invokes behavior with a supplied as self.
  • Abstraction: A class exposes useful operations while hiding implementation details; callers use deposit() without managing the assignment directly.
  • Polymorphism: Different classes can respond to the same method name, such as multiple shapes implementing area().
  • Benefits: OOP supports modularity, reuse, maintainability, and direct modeling of entities with related data and operations.

IX. Encapsulation — Controlled Access to State

A. Encapsulation

Encapsulation bundles data and methods within a class and controls how an object’s internal state is accessed or modified.

  • Public members: self.name is accessible normally and forms part of the object’s public interface.
  • Non-public convention: _balance signals that a member is intended for internal use, although Python does not enforce this restriction.
  • Name mangling: __pin is transformed to a class-qualified name such as _Account__pin, reducing accidental access but not providing absolute privacy.
  • Properties: property enables validated, method-controlled access using attribute syntax.
PYTHON
class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("Below absolute zero")
        self._celsius = value
  • Invariant protection: The setter ensures _celsius never falls below -273.15, preserving a valid object state.
  • Interface stability: Internal representation can change while clients continue using object.celsius.
  • Limitation: Python follows a “consenting adults” philosophy; encapsulation relies on interfaces, naming conventions, and careful design rather than strict access keywords.

X. Inheritance — Extending Existing Classes

A. Inheritance

Inheritance creates a new class from an existing class, allowing behavior and attributes to be reused, specialized, or replaced.

  • Terminology: The existing class is the base or parent class; the derived class is the subclass or child class.
  • Basic syntax: class Dog(Animal): makes Dog inherit accessible behavior from Animal.
  • Method overriding: A subclass can redefine an inherited method to provide specialized polymorphic behavior.
  • Parent initialization: super() accesses the parent implementation without naming the parent class directly.
PYTHON
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "sound"

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed

    def speak(self):
        return "bark"
  • Inherited state: A Dog receives name through Animal.__init__ and adds its own breed attribute.
  • Type relationship: isinstance(Dog("Max", "Beagle"), Animal) is True, expressing that a dog “is an” animal.
  • Forms: Python supports single, multilevel, hierarchical, and multiple inheritance. In multiple inheritance, the method resolution order determines search order and is available through ClassName.mro().
  • Design constraint: Inheritance is appropriate for a genuine “is-a” relationship; composition is preferable when one object merely “has-a” collaborating object.