0% found this document useful (0 votes)
48 views45 pages

Module 4 Copy 1

This document covers Python programming concepts including modules, random number generation, and object-oriented programming. It explains how to create and use modules, the importance of namespaces, and provides examples of using the random and time modules for generating random numbers and measuring execution time. Additionally, it discusses the math module for mathematical functions and constants, and emphasizes the significance of repeatability in random number generation for debugging and testing.

Uploaded by

kayalvizhi
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
48 views45 pages

Module 4 Copy 1

This document covers Python programming concepts including modules, random number generation, and object-oriented programming. It explains how to create and use modules, the importance of namespaces, and provides examples of using the random and time modules for generating random numbers and measuring execution time. Additionally, it discusses the math module for mathematical functions and constants, and emphasizes the significance of repeatability in random number generation for debugging and testing.

Uploaded by

kayalvizhi
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

PYTHON PROGRAMMING

1BPLC105B
Module-4
Modules: Random numbers, the time module, the math module, creating your own modules,
Namespaces,
Scope and lookup rules, Attributes and the dot Operator, Three import statement
variants. Mutable versus immutable and aliasing
Object oriented programming: Classes and Objects — The Basics, Attributes, Adding methods to
our class,
Instances as arguments and parameters, Converting an instance to a string, Instances as return
values.
Text Book Chapters: 8.1-8.8, 9.1, 11.1

4.1 Python Modules and the random Module


A module in Python is a file that contains Python code such as functions, variables, and classes, which can be
reused in other Python programs. Python comes with many built-in modules as part of its standard library,
which help programmers perform common tasks easily. Examples of such modules are the turtle module (used
for graphics) and the string module (used for string operations). Python also provides a help system that allows
users to explore all available standard modules and understand how to use them.

Random Numbers in Python (random Module)


In many programs, we need random numbers. Some common uses of random numbers are:

• Simulating dice throws, coin tosses, or lottery games

• Shuffling playing cards

• Generating random positions in games (e.g., enemy spaceships)

• Simulating natural events like rainfall

• Supporting security features such as encryption

Python provides the random module to handle all such tasks.

Creating a Random Number Generator


import random
rng = [Link]()
Here, rng acts like a black box that generates random values.

Generating Random Integers (randrange)


dice_throw = [Link](1, 7)

• This generates a random integer from 1 to 6.

• The lower limit is included, and the upper limit is excluded.

• All values have equal probability (uniform distribution).

You can also specify a step value:


random_odd = [Link](1, 100, 2)

• This generates a random odd number less than 100.

Generating Random Floating-Point Numbers (random)


delay_in_seconds = [Link]() * 5.0

• [Link]() returns a floating-point number in the range [0.0, 1.0).

• This means 0.0 is possible, but 1.0 is not included.

• Multiplying by 5.0 scales the value to the range [0.0, 5.0).

• These values are also uniformly distributed.

Other Distributions
Apart from uniform distribution, the random module can generate values following other distributions, such as:

• Normal (bell-shaped) distribution, useful for modeling rainfall, medical data, or scientific
measurements.
Shuffling a List (shuffle)
cards = list(range(52))
[Link](cards)

• This represents a deck of 52 cards.

• The shuffle() method randomly rearranges the elements of the list.

• Note: shuffle() works only on lists, not directly on range objects.


4.1.1 Repeatability and Testing (Python Random Numbers)
Random number generators in Python are not truly random. They are based on a deterministic algorithm,
meaning that if the same starting conditions are used, the generator will produce the same sequence of numbers
every time. Because of this behavior, they are called pseudo-random number generators.
Seed Value
A random number generator starts with a seed value.

• The seed determines the initial state of the generator.

• Every time a random number is generated, the internal state (seed) is updated.

• Future random numbers depend on the current state of the generator.

Why Repeatability Is Important


Repeatability is especially useful for:

• Debugging programs

• Writing unit tests

• Verifying program behavior

When testing, we want a program to behave the same way every time it runs. Random behavior can make
debugging difficult, so Python allows us to fix the seed.
Creating a Generator with a Fixed Seed
import random
drng = [Link](123)

• Here, 123 is the explicit seed value.

• Every time this program is run, drng will generate exactly the same sequence of random numbers. •

This ensures repeatable and predictable results.

Default Behavior (Without a Seed)


If no seed is provided:
[Link]()

• Python usually uses the current system time as the seed.

• This results in different random sequences each time the program runs.

Important Practical Note

• Fixed seed → useful for testing and debugging


• Variable seed (time-based) → useful for games and real-world simulations

For example:

• In testing: fixed seed is desirable

• In games: fixed shuffling would make the game boring and predictable

4.1.2 Picking Balls from Bags, Throwing Dice, Shuffling Cards


This section explains how Python’s random module can be used to:

• Generate random numbers

• Handle duplicates vs non-duplicates

• Model real-world probability situations such as dice throws, lotteries, and card shuffling

Case 1: Random Numbers With Duplicates (With Replacement)


Function Explanation
import random
def make_random_ints(num, lower_bound, upper_bound):
""”
Generate a list containing num random ints between lower_bound
and upper_bound. upper_bound is an open bound.

"""
rng = [Link]()
result = []
for i in range(num):
[Link]([Link](lower_bound, upper_bound))
return result
Example Output
make_random_ints(5, 1, 13)
# Output: [8, 1, 8, 5, 6]
Key Observations

• Duplicates are allowed (e.g., 8 appears twice).

• This behavior is expected in scenarios like:

o Throwing a die multiple times


o Tossing a coin

• In statistics, this is called sampling with replacement:

o After drawing a value, it is “put back,” so it can appear again.

Case 2: Random Numbers Without Duplicates (Without Replacement – Small


Range) If duplicates are not allowed, the previous algorithm is incorrect.

Correct Approach: Shuffle and Slice


xs = list(range(1, 13)) # Numbers 1 to 12 (months)
rng = [Link]()
[Link](xs)
result = xs[:5]
Explanation

• First, create a list of all possible values.

• Shuffle the list randomly.

• Take only the first n elements.

• This guarantees no duplicates.

Real-World Example

• Picking lottery numbers

• Drawing cards from a deck

• Selecting unique months

In statistics, this is called sampling without replacement.

Problem with Shuffle-and-Slice for Large Ranges


If the range is very large, this method becomes inefficient.
Example:

• Pick 5 numbers between 1 and 10,000,000 (without duplicates) • Creating and

shuffling a list of 10 million numbers is a performance disaster.


Case 3: Random Numbers Without Duplicates (Large Range – Efficient
Method) Improved Algorithm

import random
def make_random_ints_no_dups(num, lower_bound,
upper_bound): """

Generate a list containing num random ints between


lower_bound and upper_bound. upper_bound is an open bound.
The result list cannot contain duplicates.

"""
result = []
rng = [Link]()
for i in range(num):
while True:
candidate = [Link](lower_bound, upper_bound)
if candidate not in result:
break
[Link](candidate)
return result
Example Output
xs = make_random_ints_no_dups(5, 1, 10000000)
print(xs)
# [3344629, 1735163, 9433892, 1081511, 4923270]
Why This Works
• Random numbers are generated one by one.

• A number is accepted only if it is not already in the list.

• Efficient when:

o The number of required values is small

o The range is very large

The Pitfall (Very Important for Exams)


Problem Case
xs = make_random_ints_no_dups(10, 1, 6)
What Will Happen?

• Numbers possible: 1, 2, 3, 4, 5 → only 5 unique values

• Requested numbers: 10

• The program will enter an infinite loop

• Eventually, it will never terminate

Why?
Because it is impossible to generate more unique numbers than the range allows.

4.2 The time Module


As programs become larger and algorithms more complex, an important question
arises: Is our code efficient?
One practical way to answer this is by measuring execution time—that is, how long a piece of code takes to
run. Python provides the time module for this purpose.
The clock() Function (as per the given text)
The [Link]() function returns a floating-point value representing the number of seconds elapsed since the
program started running.
Basic Timing Strategy

1. Call clock() before the code you want to measure → store in t0

2. Execute the code

3. Call clock() after execution → store in t1

4. Compute elapsed time as:


5. t1 − t0
This difference tells us how fast the code ran.

Example: Comparing Two Ways to Sum a List


Python has a built-in sum() function. We can also write our own summation logic. This example compares the
performance of both approaches.

User-Defined Sum Function


import time
def do_my_sum(xs):
sum = 0
for v in xs:
sum += v
return sum

Test Setup
sz = 10000000 # 10 million elements
testdata = range(sz)

Timing the User-Defined Function


t0 = [Link]()
my_result = do_my_sum(testdata)
t1 = [Link]()
print("my_result = {0} (time taken = {1:.4f} seconds)"
.format(my_result, t1 - t0))

Timing the Built-in sum() Function


t2 = [Link]()
their_result = sum(testdata)
t3 = [Link]()
print("their_result = {0} (time taken = {1:.4f} seconds)"
.format(their_result, t3 - t2))

Sample Output
my_result = 49999995000000 (time taken = 1.5567 seconds)
their_result = 49999995000000 (time taken = 0.9897 seconds)

Analysis of Results

• Both methods produce the same correct result

• The built-in sum() function is faster

• The user-defined function is approximately 57% slower


• Built-in functions are optimized at a lower level, making them more efficient

Despite this, summing 10 million numbers in under a second using the built-in function is very
efficient. Important Note (Modern Python)

In newer versions of Python, [Link]() is deprecated. Functions like:

• time.perf_counter()

• time.process_time()

4.3 The math Module


The math module in Python provides mathematical functions and constants similar to those found on a scientific
calculator. It is commonly used for trigonometry, logarithms, square roots, and other mathematical
computations.
To use the math module, it must first be imported:
import math
Mathematical Constants
The math module provides important mathematical constants:
[Link]

• Value of π (pi)

• Output: 3.141592653589793

math.e

• Value of e, the base of natural logarithms

• Output: 2.718281828459045

Common Mathematical Functions


Square Root
[Link](2.0)

• Returns the square root of 2

• Output: 1.4142135623730951

Trigonometric Functions and Angle Measurement


In Python (and most programming languages), angles are measured in radians, not
degrees. Converting Degrees to Radians

[Link](90)
• Converts 90 degrees into radians

• Output: 1.5707963267948966

Sine Function
[Link]([Link](90))

• Calculates sine of 90 degrees

• Output: 1.0

Inverse Trigonometric Function


[Link](1.0) * 2

• asin(1.0) returns π/2

• Multiplying by 2 gives π

• Output: 3.141592653589793

Radians vs Degrees

• Radians are the standard unit for angles in programming. •

Python provides:

o [Link]() → degrees to radians

o [Link]() → radians to degrees

Important Conceptual Difference


math Module vs random and turtle Modules

• In random and turtle, we:

o Create objects

o Call methods on those objects

o Objects have state

▪ Example:

▪ Turtle has position, color, direction

▪ Random generator has a seed

• In the math module:


o Functions are pure functions

o They do not depend on any state or history

o The same input always gives the same output

o Example:

▪ [Link](2.0) will always return the same value

Therefore, math functions are not methods of an object. They are simply standalone functions grouped inside
the math module.

4.4 Creating Your Own Modules


In Python, we can create our own modules by simply saving a Python script with a .py file extension. Any file
that contains Python code (functions, variables, etc.) and is saved with .py automatically becomes a module.
Example: Creating a Custom Module
Suppose we create a file named [Link] with the following code:
def remove_at(pos, seq):
return seq[:pos] + seq[pos+1:]
What This Function Does

• Removes the element at position pos from a sequence

• Works with strings, lists, or any sequence that supports slicing

Using the Custom Module


Once the file is saved, we can use it in another Python program or in the Python interpreter by importing
it. import seqtools

Example Usage
s = "A string!"
seqtools.remove_at(4, s)
Output
'A sting!'

• The character at index 4 (the letter r) is removed

• The function works exactly like a built-in utility

Important Rules About Importing Modules

• Do not include .py in the import statement


import seqtools
import [Link]

• Python automatically looks for files ending in .py

• The module file must be:

o In the same directory, or

o In a directory listed in Python’s module search path

Why Use Modules?

Using modules helps in:

• Breaking large programs into smaller, manageable parts

• Reusing code across multiple programs

• Organizing related functions together

• Improving readability and maintenance

• A module is created by saving code in a .py file

• Functions inside a module can be accessed using:

• module_name.function_name()

• The .py extension is not written during import

• Modules support code reuse and better program structure

4.5 Namespaces
A namespace is a collection of identifiers (names) such as variables, functions, and objects that belong to a
module, function, or class. Namespaces help organize code and prevent name conflicts.
Generally, a namespace contains related items, for example:

• All mathematical functions in the math module

• All random-related functions in the random module

Why Namespaces Are Important


Namespaces allow:

• The same identifier name to be used in different places

• Multiple programmers to work on the same project without naming collisions


• Better organization and readability of large programs

Each module has its own namespace, so identical names in different modules do not interfere with each other.

Example: Module Namespaces


[Link]
question = "What is the meaning of Life, the Universe, and Everything?"
answer = 42
[Link]
question = "What is your quest?"
answer = "To seek the holy grail."
Using Both Modules Together
import module1
import module2
print([Link])
print([Link])
print([Link])
print([Link])
Output
What is the meaning of Life, the Universe, and Everything?
What is your quest?
42
To seek the holy grail.
Explanation

• Both modules define question and answer

• No conflict occurs because:

o [Link] and [Link] exist in different namespaces

Function Namespaces
Functions also create their own namespaces.
Example
def f():
n=7
print("printing n inside of f:", n)
def g():
n = 42
print("printing n inside of g:", n)
n = 11
print("printing n before calling f:", n)
f()
print("printing n after calling f:", n)
g()
print("printing n after calling g:", n)
Output
printing n before calling f: 11
printing n inside of f: 7
printing n after calling f: 11
printing n inside of g: 42
printing n after calling g: 11
Explanation

• There are three different variables named n

• Each n exists in a different namespace:

o Global namespace

o Function f() namespace

o Function g() namespace

• They do not collide, even though the names are the same

This is similar to having multiple people named “Bruce” — the name is the same, but the individuals are
different.

Relationship Between Namespaces, Files, and Modules


Python follows a one-to-one mapping:

• One file → One module → One namespace

Example:

• File name: [Link]

• Module name: math


• Namespace name: math

So when you write:


import math
[Link](4)
You are accessing sqrt inside the math namespace.

Important Conceptual Clarification

• Files and directories:

o Concerned with where code is stored on the computer

• Modules and namespaces:

o Concerned with how code is organized logically

In Python, these concepts appear tightly connected, but this is not true in all languages.

Key Warning (Very Important for Understanding)

If you rename a file in Python:

• The module name changes

• All import statements must be updated

• All references to that namespace must also change

Other programming languages (like C#) may:

• Allow one module across multiple files

• Allow multiple namespaces in one file

• Share one namespace across many files

Therefore, do not confuse file structure with namespaces, even though Python blends

them. 4.6 Scope and Lookup Rules

The scope of an identifier (variable, function name, etc.) is the region of the program where that identifier can
be accessed or used.
Python determines which identifier to use by following well-defined scope and lookup rules.

The Three Important Scopes in Python


1. Local Scope
• Identifiers declared inside a function

• Stored in the function’s namespace

• Each function has its own local namespace

• Exists only during function execution

2. Global Scope

• Identifiers declared at the top level of a module (file)

• Accessible throughout the module

• Shared by all functions in that module

3. Built-in Scope

• Identifiers provided by Python itself

• Examples: range, min, len, print

• Available without importing anything

Inspecting Scopes in Python


Python provides built-in functions to examine scopes:

• locals() → shows local namespace

• globals() → shows global namespace

• dir() → shows names in a namespace

Scope Lookup Precedence Rules


When Python encounters an identifier, it searches scopes in this order:
1. Local scope
2. Global scope
3. Built-in scope
The innermost scope always takes precedence.

Example 1: Hiding a Built-in Name


def range(n):
return 123 * n
print(range(10))
Output
1230
Explanation

• Python finds range in the global scope

• This hides the built-in range

• Therefore, Python calls the user-defined function, not the built-in one Redefining built-in names like
range or min is bad practice and should be avoided because it causes confusion.

Example 2: Local vs Global Variables


n = 10
m=3
def f(n):
m=7
return 2 * n + m
print(f(5), n, m)
Output
17 10 3

Step-by-Step Explanation

• n = 10 and m = 3 are in the global namespace

• When f(5) is called:

o A new local n is created with value 5

o A new local m is created with value 7

• Inside the function:

• 2 * 5 + 7 = 17

• After the function returns:

o Global n and m remain unchanged


o The local variables are destroyed

Scope of Identifiers in the Example

• n on line 1 (global): Visible on lines 1, 2, 6, and 7

o Hidden on lines 3, 4, and 5 by the local n

• f:

o Created by def

o Exists in the global namespace

o Can be called anywhere in the module after its definition

4.7 Attributes and the Dot Operator


In Python, variables defined inside a module are called attributes of that module. Similarly, objects (such as
modules, functions, and classes) can also have attributes.
Attributes in Python
Module Attributes

• Any variable or function defined inside a module becomes an attribute of that module. •

These attributes belong to the module’s namespace.

Example:
# [Link]
question = "What is the meaning of life?"
Here, question is an attribute of module1.
Object Attributes
Objects can also have attributes. Examples include:

• __doc__ → documentation string of an object

• __annotations__ → type annotations of a function

These attributes store information related to the object.

The Dot Operator (.)


The dot operator is used to access attributes of a module or object.
Accessing Module Attributes
[Link]
[Link]
Even though both modules contain an attribute named question, there is no conflict because each belongs to a
different module namespace.

Accessing Functions Using the Dot Operator


Modules can contain functions as well as variables, and both are accessed using the dot
operator. Example:

seqtools.remove_at

• seqtools → module name

• remove_at → function inside the module

This expression refers to the remove_at function defined in the seqtools module.

Fully Qualified Names


When a name includes the module (or object) it belongs to, it is called a fully qualified name.
Examples:

• [Link]

• [Link]

• seqtools.remove_at

Using fully qualified names:

• Clearly specifies which attribute is being referred to

• Avoids ambiguity when multiple modules have attributes with the same name

4.8 Three Import Statement Variants


Python provides three main ways to import modules or names from modules into the current namespace. Each
method affects how names are accessed and where they are placed.
Method 1: Import the Entire Module (Recommended)
import math
x = [Link](10)
Explanation

• Only the name math is added to the current namespace.

• Functions and constants inside the module must be accessed using dot notation.

• Example:
o [Link](10)

o [Link]

Advantages

• Clear and explicit

• Avoids name conflicts

• Easy to understand which module a function comes from

This is the preferred and safest method.

Method 2: Import Specific Names from a Module


from math import cos, sin, sqrt
x = sqrt(10)
Explanation

• The names cos, sin, and sqrt are imported directly into the current namespace. •

The module name math is not imported.

• Using [Link](10) will cause an error.

Advantages

• Less typing

• Convenient when using a few functions frequently

Disadvantages

• Possible name clashes

• Less clear where a function originated

Method 3: Import All Names from a Module


from math import *
x = sqrt(10)
Explanation

• All public identifiers from the module are imported into the current namespace. •

Functions can be used without qualification.

Disadvantages (Important for Exams)


• High risk of name conflicts

• Makes code harder to read and debug

• Not recommended in professional code

Using an Alias (Shorthand Import)


import math as m
[Link]
Explanation

• The module is imported with an alias (m)

• Reduces typing while maintaining clarity

• Commonly used in practice

Importing Inside a Function (Local Import)


def area(radius):
import math
return [Link] * radius * radius
x = [Link](10) # Error
Explanation

• math is imported inside the function

• It exists only in the local namespace of area

• It is not available globally

• Therefore, [Link](10) outside the function causes an error Import Style

Namespace Impact Qualification Needed Recommended import math Adds

math Yes ([Link]) Yes from math import sqrt Adds sqrt No Sometimes from

math import * Adds all names No No import math as m Adds m Yes ([Link]) Yes.

Introduction to Python Data Types


You have already encountered Python’s core data types:

• bool
• int

• float

• string

• tuple

• list

• dictionary

In this section, the focus is mainly on lists and tuples, and the concepts of mutability, immutability, and
aliasing. New data types such as sets and frozensets are also introduced later.

4.9 Mutable versus Immutable


Mutable Data Types
A mutable data type is one whose contents can be changed after creation.
Common mutable types:

• List

• Dictionary

Example: Mutability of Lists


my_list = [2, 4, 5, 3, 6, 1]
my_list[0] = 9
print(my_list)
Output
[9, 4, 5, 3, 6, 1]
Here, the value at index 0 is successfully changed, proving that lists are mutable.

Immutable Data Types


An immutable data type is one whose contents cannot be changed after
creation. Common immutable types:

• Tuple

• String

Example: Immutability of Tuples


my_tuple = (2, 5, 3, 1)
my_tuple[0] = 9

Result
TypeError: 'tuple' object does not support item assignment
This error occurs because tuples do not allow modification.

Aliasing
Aliasing occurs when two or more variables refer to the same object in memory. This is common with
mutable objects like lists.
Example: Aliasing with Lists
list_one = [1, 2, 3, 4, 6]
list_two = list_one
list_two[-1] = 5
print(list_one)
Output
[1, 2, 3, 4, 5]
Even though only list_two was modified, list_one also changed. This happens because both variables point to
the same list.

Memory Address Check Using id()


You can verify aliasing using the built-in id() function.
id(list_one) == id(list_two)
Output
True
This confirms both variables reference the same memory location.

Avoiding Aliasing with Copying


To avoid aliasing, create a copy of the list.
Shallow Copy Using Slicing
list_one = [1, 2, 3, 4, 6]
list_two = list_one[:]
id(list_one) == id(list_two)
Output
False
Now the lists are stored at different memory locations.
list_two[-1] = 5
print(list_two)
print(list_one)
Output
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 6]
Changes to one list do not affect the other.

Limitation: Nested Lists


Shallow copying does not work correctly for nested lists, because inner lists are still
shared. Example:

a = [[1, 2], [3, 4]]


b = a[:]
b[0][0] = 9
Both a and b will reflect the change.
To handle this properly, Python provides the copy module, which supports deep copying.

4.10 Classes and Objects — the Basics


4.10.1 Object-Oriented Programming
Python is an object-oriented programming (OOP) language, which means it provides features that allow
programs to be designed using objects and classes.

What Is Object-Oriented Programming (OOP)?


Object-oriented programming is a programming paradigm that focuses on:

• Objects rather than just functions

• Combining data and functionality into a single unit

OOP originated in the 1960s, but it became widely adopted in the mid-1980s as software systems grew larger and
more complex. The main goal of OOP is to:

• Manage complex software systems

• Make programs easier to understand, maintain, and modify

• Improve code reuse and scalability


Procedural Programming vs Object-Oriented Programming

Procedural Programming

• Focuses on functions or procedures

• Data and functions are separate

• Functions operate on external data

Example idea:

• Write functions

• Pass data to functions

• Data is not tightly bound to behavior

Object-Oriented Programming

• Focuses on objects

• An object contains:

o Data (attributes)

o Functionality (methods)

• Data and behavior are bundled together

This approach more closely models real-world systems.

Objects in Python (Already Encountered)


Even before formally learning OOP, you have already used objects in Python, such

as: • Turtle objects (graphics and movement)

• String objects (text with built-in methods)

• Random number generator objects

Each of these:

• Stores data internally

• Provides methods to operate on that data


Real-World Analogy
In OOP:

• Each object represents a real-world entity or concept

• The data represents properties

• The functions (methods) represent actions

Example (conceptual):

• A car object:
o Data: speed, color, fuel

o Methods: start(), stop(), accelerate()

This mirrors how real-world objects behave.

Why Object-Oriented Programming Is Important


OOP helps to:

• Organize large programs into manageable parts

• Reduce complexity

• Improve code readability

• Make programs easier to extend and modify

• Support teamwork by allowing multiple developers to work on different objects

4.10.2 User-Defined Compound Data Types


Python already provides many built-in classes such as int, float, str, and Turtle. In object-oriented programming,
we can also define our own classes to represent real-world or mathematical concepts. These are called user
defined compound data types.

The Concept of a Point


In mathematics, a point in two dimensions is represented by two numbers:

• x → horizontal position

• y → vertical position

Examples:

• (0, 0) → origin
• (x, y) → x units to the right and y units up

Common operations on points include:

• Finding the distance from the origin

• Finding the distance between two points

• Computing the midpoint of two points

• Checking whether a point lies inside a shape

To support such operations cleanly, we want to group the x and y values together.

Why Not Just Use a Tuple?


A quick solution is to use a tuple like (x, y). While this works for simple cases, it has limitations:

• No clear names for components (x, y)

• Harder to attach behaviors (methods) to the data A better and more flexible approach is to define a new class.

Defining a Point Class


class Point:
""" Point class represents and manipulates x,y coords. """

def __init__(self):
""" Create a new point at the origin """
self.x = 0
self.y = 0

Understanding the Class Definition


1. class Point:

• Defines a new class named Point

• A class is a blueprint for creating objects

2. Docstring

""" Point class represents and manipulates x,y coords. """ • Describes what the class does

• Used by help tools and documentation systems


3. The __init__ Method

def __init__(self):

• Called the initializer

• Automatically runs whenever a new object is created • Used to set initial values of attributes

4. The self Parameter

• Refers to the newly created object

• Used to create and access attributes

• self.x and self.y belong to the specific object being created

Creating (Instantiating) Point Objects


p = Point()
q = Point()
• p and q are two different Point objects

• Each has its own x and y attributes

print(p.x, p.y, q.x, q.y)


Output
0000
This happens because:

• During initialization, each object’s x and y are set to 0

Objects Are Independent


Although p and q are created from the same class:

• They are stored at different memory locations

• Changing p.x does not affect q.x

Constructors and Instantiation

• A function like Point() or Turtle() that creates objects is called a constructor •

Every class automatically provides a constructor with the same name as the class •

The process of:


1. Creating a new object
2. Initializing its attributes
is called instantiation

Class as a Factory (Important Analogy)

• A class is like a factory

• An object is a product made by the factory

• Each time the constructor is called:

o A new object is created

o The __init__ method sets default values

The class itself is not an object—it only contains the instructions for creating objects.

4.10.3 Attributes

Like real-world objects, object instances in Python have attributes and


methods. Attributes represent the data or state of an object.
Modifying Attributes Using Dot Notation
Attributes of an object instance can be modified using the dot (.) operator.
p.x = 3
p.y = 4

• Here, p refers to a Point object

• x and y are attributes of that object

• Their values are updated to 3 and 4

Namespaces and Attributes

• Each instance has its own namespace

• Modules also have namespaces

• Accessing attributes from a module or an object uses the same syntax

In this case, we are accessing data attributes from an object instance.

Conceptual State of the Object


After the assignments:

• p refers to a Point object

• The object contains:

o x→3

o y→4

• Each attribute refers to a numeric value

Accessing Attribute Values


Attributes can be accessed using the same dot notation:
print(p.y)
Output
4
Assigning an attribute value to a variable:
x = p.x
print(x)
Output
3

Understanding p.x
The expression:
p.x
means:
“Go to the object that p refers to, and retrieve the value of its attribute x.”
There is no conflict between:

• x (a variable in the global namespace)

• p.x (an attribute in the object’s namespace)

The dot notation ensures unambiguous access.

Using Attributes in Expressions


Attributes can be used in any Python expression.
Example 1: Formatted Output
print("(x={0},y={1})".format(p.x, p.y))
Output
(x=3, y=4)

Example 2: Calculation Using Attributes


distance_squared_from_origin = p.x * p.x + p.y * p.y

• Calculation:

• 3² + 4² = 9 + 16 = 25

• Result:

• distance_squared_from_origin = 25

4.10.4 Improving Our Initializer


Earlier, creating a point at a specific position required multiple lines of code:
p = Point()
p.x = 7
p.y = 6
This works, but it is not convenient and makes object creation less clean.

Making the Initializer More Flexible


We can improve this by adding parameters to the __init__ method so that values can be passed at the time of
object creation.

Improved Point Class

class Point:
""" Point class represents and manipulates x,y coords. """
def __init__(self, x=0, y=0):

""" Create a new point at x, y """


self.x = x
self.y = y

What Changed?

• The __init__ method now accepts two parameters: x and y • Both parameters have default values of 0
• This allows us to:

o Create points at any location

o Still create the origin (0, 0) easily

Using the Improved Class


p = Point(4, 2)
q = Point(6, 3)
r = Point() # origin
print(p.x, q.y, r.x)
Output
430
Explanation

• p is created at (4, 2)

• q is created at (6, 3)

• r uses default values and represents (0, 0)

Advantages of This Approach

• Object creation becomes shorter and clearer

• Attributes are initialized immediately

• Reduces the risk of forgetting to set attributes

• Makes the class easier and safer to use

Important Technical Note (For Understanding)

Strictly speaking:

• The __init__ method does not create the object

• The object is created first

• __init__ only initializes it with default or provided values

However:

• In practice, creation and initialization happen together


• Tools and editors show the __init__ docstring as help when calling the class constructor

• Therefore, the docstring is written to guide the user of the class

4.10.5 Adding Other Methods to Our Class

The real power of using a class (like Point) instead of a simple tuple (x, y) becomes clear when we start adding
methods. A class allows us to group data and the operations that make sense for that data in one place.

Why Methods Are Important


A tuple such as (6, 7) could represent many things:

• A point (x, y)

• A date (day, month)

• Any other paired data

However:

• Calculating distance from origin makes sense only for a point

• It does not make sense for (day, month) data

By using a class, we ensure that:

• Only appropriate operations are allowed

• Data and related behavior stay together

• Each object maintains its own state

What Is a Method?

• A method is like a function

• It is defined inside a class

• It is called on an object (instance)

• It is accessed using dot notation

Example:
p.distance_from_origin()
This is similar in style to:
[Link](90)

Adding a Method to the Point Class


Let’s add a method called distance_from_origin to compute the distance of a point from (0,
0).

class Point:

""" Create a new Point, at coordinates x, y """

def __init__(self, x=0, y=0):


""" Create a new point at x, y """
self.x = x
self.y = y

def distance_from_origin(self):
""" Compute my distance from the origin """
return ((self.x ** 2) + (self.y ** 2)) ** 0.5

Using the Method


Example 1
p = Point(3, 4)
p.distance_from_origin()
Output
5.0

Example 2
q = Point(5, 12)
q.distance_from_origin()
Output
13.0

Example 3 (Origin)
r = Point()
r.distance_from_origin()
Output

0.0

Understanding self in Methods

• The first parameter of every method refers to the current object

• By convention, this parameter is named self

• self.x and self.y refer to the attributes of the object that called the method

Important detail:

• When calling p.distance_from_origin(), we do not pass p explicitly

• Python automatically passes the object as the self argument

This happens behind the scenes.

Organizational Power of Classes


Using classes provides:

• Better program structure

• Logical grouping of data and operations

• Improved readability and maintainability

• Clear modeling of real-world concepts

Each instance of the class:

• Has its own attributes

• Can call the same methods

• Behaves independently

4.10.6 Instances as Arguments and Parameters


In Python, objects (instances) can be passed to functions just like any other value. This is a common and
powerful feature of object-oriented programming.

Passing Objects to Functions


We have already seen this concept in earlier examples, such as passing a turtle object to a function so that the
function can control or manipulate that turtle.
When an object is passed to a function:

• The function receives a reference to the object

• The object itself is not copied

Important Concept: Aliasing

Variables in Python store references to objects, not the objects


themselves. When an object is passed as an argument:

• The parameter inside the function becomes an alias

• Both the caller and the function refer to the same object

• There is still only one object in memory

This is important because:

• Changes made to the object inside the function can affect the original object

Example Using the Point Class


Function Definition
def print_point(pt):
print("({0}, {1})".format(pt.x, pt.y))
Explanation

• pt is a parameter that refers to a Point object

• The function accesses the object’s attributes using dot notation

• The output format is controlled by the function

Calling the Function


p = Point(3, 4)
print_point(p)
Output
(3, 4)
What Happens Internally
• p holds a reference to a Point object

• When p is passed to print_point, the parameter pt refers to the same object •

No new object is created

• This is why pt.x and pt.y correctly access the attributes of p

Why This Is Useful


Passing instances as arguments allows:
• Functions to operate on different objects dynamically

• Code reuse

• Cleaner and more modular programs

• Separation of responsibilities (functions handle operations, objects store state)

4.10.7 Converting an Instance to a String


When working with classes and objects, it is not good practice to write separate “chatterbox” functions that print
object details directly. A better object-oriented approach is to let each object know how to represent itself as a
string.

Initial (Less Preferred) Approach


We may first think of adding a method like this:
class Point:
# ...
def to_string(self):
return "({0}, {1})".format(self.x, self.y)
Usage:
p = Point(3, 4)
print(p.to_string())
Output
(3, 4)
This works, but it is not ideal.

Why This Is Not Ideal


Python already provides:

• A built-in str() function

• Automatic string conversion inside print()

However, without special handling, Python does not know how we want our object to look as a
string. str(p)

print(p)
Output
<__main__.Point object at 0x01F9AA10>
This default representation is not user-friendly.

The Pythonic Solution: __str__


Python allows us to define a special method called __str__.
If a class defines __str__, Python will:
• Automatically use it when str(object) is called

• Automatically use it when print(object) is called

Improved Point Class with __str__


class Point:
# ...

def __str__(self):
return "({0}, {1})".format(self.x, self.y)

Resulting Behavior
p = Point(3, 4)
str(p)
print(p)
Output
(3, 4)
(3, 4)
Now:

• str(p) returns a meaningful string


• print(p) displays the same clean representation

Why __str__ Is Better Than to_string

• Integrates with Python’s built-in mechanisms

• Works automatically with print()

• Produces cleaner and more readable output

• Follows object-oriented design principles

4.10.8 Instances as Return Values


In Python, functions and methods can return object instances, just like they can return numbers or strings.
This is a powerful feature of object-oriented programming, allowing objects to be created, processed, and
returned dynamically.

Returning an Instance from a Function

Consider the task of finding the midpoint between two


points.

Function Definition

def midpoint(p1, p2):


""" Return the midpoint of points p1 and p2 """
mx = (p1.x + p2.x) / 2
my = (p1.y + p2.y) / 2
return Point(mx, my)
Explanation

• The function receives two Point objects

• It calculates the midpoint coordinates

• It creates and returns a new Point object

Using the Function


p = Point(3, 4)
q = Point(5, 12)
r = midpoint(p, q)
print(r)
Output
(4.0, 8.0)
The returned value r is a Point instance, not a tuple or list.

Returning an Instance from a Method


Instead of a standalone function, we can make this behavior part of the Point
class. Method Definition

class Point:
# ...
def halfway(self, target):
""" Return the halfway point between myself and the target """
mx = (self.x + target.x) / 2

my = (self.y + target.y) / 2
return Point(mx, my)

Explanation

• self refers to the calling object

• target is another Point object

• The method returns a new Point instance

Using the Method


p = Point(3, 4)
q = Point(5, 12)
r = [Link](q)
print(r)
Output
(4.0, 8.0)

Function vs Method (Conceptual Difference)

Function Method

Operates on objects passed as arguments Operates on the calling object


Defined outside a class Defined inside a class

midpoint(p, q) [Link](q)

Both approaches return new objects, but the method approach is more object-oriented.

Composability of Calls
Object creation and method calls can be combined without assigning intermediate
variables. Example

print(Point(3, 4).halfway(Point(5, 12)))


Output
(4.0, 8.0)
This works because:

• Point(3, 4) creates an object

• .halfway(Point(5, 12)) returns a new object

• print() displays it using __str__

4.10.9 A Change of Perspective


This section explains an important conceptual shift that occurs when moving from procedural programming to
object-oriented programming (OOP).

Procedural Perspective (Function-Centered)


In procedural programming, the function is the active agent.
Example:

print_time(current_time)
This style suggests:
“Hey function print_time, here is an object. Do something with

it.” • Functions act on data

• Data is passive

• Responsibility lies with the function

Object-Oriented Perspective (Object-Centered)


In object-oriented programming, the object is the active
agent. Example:

current_time.print_time()
This style suggests:
“Hey object current_time, please print yourself.”

• Objects contain both data and behavior

• Responsibility lies with the object

• Methods belong to the object they operate on

Example from Turtle Graphics


In early turtle examples, we already used this object-oriented
style: [Link](100)

This means:
“Hey turtle tess, move yourself forward by 100 units.”
The turtle object:

• Knows its own position and direction

• Updates its own state

• Controls its own behavior

Why This Change in Perspective Is Useful: At first, this shift may seem like a stylistic change, but it has real
benefits:

• Makes programs more flexible

• Improves code reuse

• Makes large programs easier to maintain

• Reduces dependency between unrelated functions and data

By shifting responsibility from functions to objects:

• Functions become simpler

• Objects manage their own behavior and state

Real-World Analogy (Very Important)


In real life:

• A microwave oven has a cook() method

• We do not have a separate cook(microwave) function

Similarly:

• A cellphone has methods to:

o Send an SMS

o Switch to silent mode

• The behavior is part of the object itself

Object-oriented programming mirrors this real-world organization.

Why OOP Fits Human Thinking Better


Humans naturally think in terms of objects:

• Objects have properties

• Objects perform actions

OOP matches this mental model by:

• Binding functionality tightly to the object

• Making programs easier to understand and reason about

• of code

4.10.10 Objects Can Have State


One of the most important ideas in object-oriented programming is that objects can have state. The state of
an object is the collection of data (attributes) that describes the object at a particular moment in time.
This state can change over time as methods are called on the object.

Object State Explained

An object’s state is stored in its attributes, and methods are used to:

• Read the state

• Modify the state


Example 1: Turtle Object
A turtle object has a state that includes:

• Position (x, y)

• Heading (direction)

• Color

• Shape

How Methods Affect State

• left(90) → changes the turtle’s heading

• forward(100) → changes the turtle’s position

• color("red") → changes the turtle’s color

Each method updates some part of the turtle’s internal state.

Example 2: Bank Account Object


A bank account object is another good real-world example.
Possible State of a Bank Account

• Current balance

• Transaction log (history of deposits and payments)

Possible Methods

• get_balance() → checks the current balance

• deposit(amount) → increases the balance

• withdraw(amount, description) → decreases the balance and records the

transaction • show_transactions() → displays the transaction history

Each method:

• Uses the existing state

• Updates the state appropriately

Why State Is Important


Objects become powerful because:
• They remember information over time

• Each object maintains its own independent state

• Behavior depends on the object’s current state

For example:

• Two turtle objects can move differently because they have different positions

• Two bank accounts can have different balances even though they use the same class

Key Concept

• Attributes = state

• Methods = behavior

• Methods change or use the state of the object

• The object remains consistent and self-contained


Real-World Analogy
Just like real-world objects:

• A mobile phone has state (battery level, silent mode, network)

• Actions like calling or switching to silent change the phone’s state

OOP mirrors this real-world behavior closely.

You might also like