Neural Sync Active
Week 11: Python Modules & CLI Arguments
Registry Synced
Week 11: Python Modules & CLI Arguments
4077 words
20 min read
2026-09-12
Reading compass
Now · TL;DR
Programming in Python — Week 11: Python Modules & CLI Arguments
The official IITM syllabus lists Week 11: Module System.
This week is about organizing Python code so that useful functions, variables, and classes can be reused across files instead of rewriting everything in one program.
TL;DR
A Python file can act as a module.
Example:
textmath_utils.py main.py
math_utils.py:pythondef square(x): return x * x
main.py:pythonimport math_utils print(math_utils.square(5))
The main ideas are:
- modules
import- module namespaces
from ... import ...- aliases with
as - standard-library modules
- your own modules
__name__if __name__ == "__main__":- packages/directories conceptually
- avoiding name conflicts
- separating reusable code from program-specific code
Core mental model:
1. Why modules?
Suppose one program contains:
pythondef mean(...): ... def median(...): ... def variance(...): ...
Later another program needs the same functions.
Without modules, you might copy-paste them.
That creates problems:
- duplicate code
- inconsistent fixes
- harder maintenance
- larger files
- poor organization
Modules let you write reusable code once.
2. Basic module
Suppose file:
textmymath.py
contains:
pythondef square(x): return x ** 2 def cube(x): return x ** 3
Another file can import it.
3. import module
pythonimport mymath
Now use:
pythonmymath.square(4)
Result:
And:
pythonmymath.cube(3)
gives:
4. Why prefix with module name?
After:
pythonimport mymath
Python keeps definitions associated with the module namespace.
So:
pythonmymath.square
means:
the namesquarebelonging to modulemymath.
This helps prevent collisions between similarly named functions.
5. Namespace
A namespace is conceptually a mapping:
Inside module
mymath:textsquare → function cube → function
So:
pythonmymath.square
looks up
square in mymath's namespace.6. Name collision example
Suppose:
pythondef square(x): return "local"
and module also has:
pythonmymath.square
Then:
pythonsquare(5)
and:
pythonmymath.square(5)
refer to different names.
Module qualification makes the distinction explicit.
7. from module import name
Instead of:
pythonimport mymath
you may write:
pythonfrom mymath import square
Now call:
pythonsquare(5)
without module prefix.
8. Difference between the two styles
Style 1
pythonimport mymath mymath.square(5)
Style 2
pythonfrom mymath import square square(5)
Both can access the same function.
The first makes origin clearer.
9. Import several names
pythonfrom mymath import square, cube
Then:
pythonsquare(4) cube(3)
10. Aliasing with as
You can rename imported modules:
pythonimport math as m
Then:
pythonm.sqrt(25)
returns:
11. Why aliases?
Aliases can:
- shorten long names
- follow conventions
- avoid collisions
Example:
pythonimport numpy as np
is a widely used convention, though NumPy appears formally in Week 12.
12. Alias imported function
pythonfrom mymath import square as sq
Then:
pythonsq(5)
returns:
13. Standard library
Python includes many modules in its standard library.
Examples include modules for:
- mathematics
- random values
- filesystem operations
- dates/times
- statistics
- collections
These can be imported without writing them yourself.
14. math module
Example:
pythonimport math
Then:
pythonmath.sqrt(16) math.floor(3.8) math.ceil(3.2)
15. Constants from modules
pythonimport math print(math.pi)
A module can contain:
- functions
- constants
- classes
- other objects
Not only functions.
16. Import specific mathematical names
pythonfrom math import sqrt, pi
Then:
pythonprint(sqrt(25)) print(pi)
17. random module intuition
A standard module may provide reusable functionality such as random number generation.
Example:
pythonimport random
Then functions are accessed through:
pythonrandom....
Exact APIs are secondary to the module-system idea:
functionality can live in another namespace and be imported.
18. Your own module
Suppose:
textgeometry.py
contains:
pythonpi = 3.14159 def circle_area(r): return pi * r * r
Another file:
pythonimport geometry print(geometry.circle_area(2))
19. Import executes module code
This point matters.
Suppose
demo.py contains:pythonprint("Module loaded") x = 10
Then:
pythonimport demo
executes the module's top-level code.
So:
textModule loaded
may print during import.
20. Definitions are executed too, but bodies wait
When Python sees:
pythondef f(): print("inside")
during module loading, it creates the function object.
It does not execute:
pythonprint("inside")
until:
pythonf()
is called.
21. Top-level statements
These run during import:
pythonx = 10 print("hello")
A function body:
pythondef f(): print("inside")
does not run simply because the module was imported.
22. Why module side effects matter
Suppose importing a helper module unexpectedly:
- prints output
- modifies files
- asks for input
- starts long computations
That makes reuse inconvenient.
Reusable modules usually keep top-level behavior controlled.
23. __name__
Every Python module has a special variable:
python__name__
Its value depends on how the file is being used.
24. File run directly
If a Python file is executed as the main program:
python__name__
is conventionally:
python"__main__"
25. File imported as module
If:
texthelper.py
is imported:
pythonimport helper
then inside
helper.py, __name__ is typically:python"helper"
rather than
"__main__".26. Main guard
A common pattern:
pythonif __name__ == "__main__": ...
This means:
run this block only when this file is executed directly.
27. Example
geometry.py:pythondef square(x): return x * x if __name__ == "__main__": print(square(5))
If run directly:
text25
prints.
If imported:
pythonimport geometry
the test block does not execute.
28. Why main guard is useful
It lets one file serve two roles:
- reusable module
- executable/testable program
Reusable definitions stay available without unwanted top-level execution.
29. Conceptual structure
pythondef helper1(): ... def helper2(): ... def main(): ... if __name__ == "__main__": main()
This cleanly separates:
- definitions
- application entry point
30. Module variables
Suppose:
python## settings.py tax_rate = 0.18
Another file:
pythonimport settings price = 100 final = price * (1 + settings.tax_rate)
A module can act as a place for shared constants/configuration.
31. Module state
A module can also contain mutable state.
Example:
pythoncount = 0 def increment(): global count count += 1
Imported modules are objects with their own namespace/state.
At an introductory level, avoid unnecessary shared mutable module state unless the design needs it.
32. Reusing functions
Suppose:
textstats_utils.py
contains:
pythondef mean(values): return sum(values) / len(values)
Now many programs can use:
pythonimport stats_utils avg = stats_utils.mean(data)
This is code reuse without copy-paste.
33. Separation of concerns
A good design might use:
textinput_utils.py statistics.py reporting.py main.py
Each module has one broad responsibility.
This reduces one huge file into understandable components.
34. Module boundaries as abstraction
Suppose
statistics.py exposes:pythonmean(values)
The caller need not care how mean is implemented internally.
This connects back to Computational Thinking's abstraction ideas:
35. Importing implementation details
Even though Python may allow access to many module names, good code should depend primarily on the module's intended public interface.
This reduces coupling.
36. Names beginning with underscore
Conventionally:
python_helper() _internal_value
suggests:
intended for internal module use.
This is mainly a convention, not strong privacy enforcement.
37. from module import *
Python allows:
pythonfrom mymodule import *
This imports many public names directly.
But it is often discouraged because it can:
- pollute namespace
- create name collisions
- hide where names came from
38. Why explicit imports are clearer
Compare:
pythonfrom math import * print(sqrt(25))
with:
pythonimport math print(math.sqrt(25))
The second makes the source of
sqrt obvious.39. Name shadowing
Suppose:
pythonfrom math import sqrt
then later:
pythonsqrt = 5
Now:
pythonsqrt(25)
fails because
sqrt refers to integer 5.Assignment can shadow imported names.
40. Module alias avoids collisions
Instead:
pythonimport math
then:
pythonsqrt = 5 print(math.sqrt(25))
still works because module namespace keeps
math.sqrt separate.41. Import order conceptually
Python must locate the requested module.
For your own module:
pythonimport helper
Python needs to find an appropriate
helper.py or package/module on its import search path.For basic course exercises, files may simply reside in the same working directory.
42. File name matters
If module file is:
textgeometry.py
typically:
pythonimport geometry
uses the filename without
.py.Not:
pythonimport geometry.py
43. Avoid naming your files after standard modules
Suppose you create:
textmath.py
and then write:
pythonimport math
Python may import your local file instead of the standard library module.
This can cause confusing errors.
44. Circular imports intuition
Suppose:
textA.py imports B.py B.py imports A.py
This creates a circular dependency.
Such designs can produce partially initialized modules and confusing behavior.
At this level, the main lesson is:
modules should have clear dependency direction.
45. Dependency graph
You can visualize imports as:
textmain ├── input_utils ├── statistics └── reporting
Prefer understandable dependency structure rather than everything importing everything else.
46. Package intuition
A package groups related modules in a directory structure.
Conceptually:
textproject/ analytics/ stats.py cleaning.py plotting.py
Then code can conceptually import related modules from the package.
Exact packaging details can vary; the key idea is hierarchical organization beyond a single module.
47. Module vs package
Module
Usually one Python file:
textstats.py
Package
Collection/directory of related modules:
textanalytics/ stats.py cleaning.py
48. Dot notation for packages
Conceptually:
pythonimport package.module
Then:
pythonpackage.module.function()
The dots represent nested namespaces.
49. Reusing across programs
Suppose three programs need:
pythonvalidate_mark(x)
Instead of copying function three times:
textprogram1.py program2.py program3.py
put it once in:
textvalidation.py
and import it.
50. Maintenance benefit
If validation rule changes from:
to another rule, update one function in one module.
Every program using that module can benefit from the corrected implementation.
This is a major software-engineering advantage.
51. Testing modules independently
A reusable module can be tested independently from the application that uses it.
For example:
pythonif __name__ == "__main__": print(mean([1, 2, 3]))
or through separate tests.
This isolates bugs.
52. Import only what you need
Compare:
pythonimport math
with:
pythonfrom math import sqrt
The first imports module namespace.
The second places
sqrt directly in current namespace.Choose based on clarity and usage.
53. Module-qualified call
pythonmath.sqrt(16)
communicates two things:
- operation:
sqrt - source:
math
This extra qualification can improve readability.
54. Function imported directly
pythonsqrt(16)
is shorter but context may be less obvious.
Neither form is universally superior.
55. Alias conventions
Some libraries have conventional aliases, such as:
pythonimport numpy as np import pandas as pd
These conventions reduce verbosity while remaining recognizable.
Week 12 will make those particularly relevant.
56. Multiple imports
Possible:
pythonimport math import random
or:
pythonimport math, random
The first style is often clearer and easier to maintain.
57. Import placement
Imports are commonly placed near the top of a Python file.
Example:
pythonimport math import statistics def main(): ...
This makes dependencies visible immediately.
58. Import inside function
Python also allows:
pythondef f(): import math return math.sqrt(25)
But imports are commonly top-level unless there is a reason to localize them.
59. Standard-library reuse
Using an existing tested module is often preferable to rewriting functionality manually.
Example:
Instead of implementing square root yourself, use:
pythonmath.sqrt(...)
when appropriate.
This is abstraction and reuse.
60. Module documentation intuition
A module should make clear:
- what it provides
- what arguments functions expect
- what values functions return
- any assumptions
This makes it usable without reading every implementation line.
61. Docstrings
A function/module may contain documentation strings:
pythondef mean(values): """Return arithmetic mean of non-empty values.""" return sum(values) / len(values)
Docstrings are associated with the object and can be inspected by tools.
62. Module-level docstring
At top of file:
python"""Utility functions for numerical analysis."""
This documents the module as a whole.
63. Public interface intuition
Suppose module exposes:
pythonmean median mode
A user of the module should conceptually think:
textinputs → function → output
without needing implementation details.
That is a software abstraction boundary.
64. Import and mutable objects
Suppose module defines:
pythonvalues = []
and another file imports module:
pythonimport helper helper.values.append(5)
The module-level list is mutable shared state.
Other code accessing
helper.values sees the mutation.This can be useful, but can also make reasoning harder.
65. Prefer functions over uncontrolled global state
Instead of exposing:
pythoncounter = 0
and letting everyone modify it, cleaner design may provide:
pythondef increment(): ...
This preserves a more controlled interface.
The same encapsulation idea appears again.
66. Import caching intuition
Within one running Python process, importing the same module repeatedly does not normally execute its top-level code from scratch every single time.
Python keeps track of loaded modules.
The key practical lesson:
do not design module correctness around repeated-import side effects.
67. Example import side effect
demo.py:pythonprint("loaded")
Main:
pythonimport demo import demo
You should not assume
"loaded" prints twice merely because two import statements appear.68. __name__ trace
helper.py:pythonprint(__name__)
Run directly
Conceptually prints:
text__main__
Imported using
pythonimport helper
conceptually prints:
texthelper
This explains the main-guard pattern.
69. Main function pattern
pythondef main(): print("Program starts here") if __name__ == "__main__": main()
This makes program entry explicit.
70. Why define main()?
Instead of placing lots of logic at top-level,
main():- organizes application flow
- reduces import side effects
- makes testing easier
- separates definitions from execution
71. Example two-file program
utils.py:pythondef is_even(n): return n % 2 == 0
main.py:pythonimport utils n = int(input()) if utils.is_even(n): print("Even") else: print("Odd")
72. Execution flow
When
main.py reaches:pythonimport utils
Python loads
utils.Then:
pythonutils.is_even
becomes available.
Later:
pythonutils.is_even(n)
calls that function.
73. Function arguments still behave normally
The fact that a function came from a module does not change basic function semantics.
pythonutils.is_even(n)
passes
n as an argument exactly like a locally defined function call.74. Module-local variables
Suppose
utils.py:pythonthreshold = 50 def passed(mark): return mark >= threshold
Inside
passed, threshold is looked up in the module's global namespace.Another module can use:
pythonutils.threshold
if exposed.
75. Global means module-global here
In Python, a
global variable inside a module is generally global to that module's namespace, not magically shared as an unqualified name everywhere.For example:
pythonutils.threshold
is distinct from a separate:
pythonthreshold
in
main.py.76. Same variable name in two modules
A.py:pythonx = 10
B.py:pythonx = 20
Main:
pythonimport A import B print(A.x) print(B.x)
outputs:
text10 20
Namespaces keep them separate.
77. from ... import ... can create conflict
pythonfrom A import x from B import x
After the second import, unqualified:
pythonx
refers to whichever binding currently occupies that name—typically the later imported one.
This is why qualified module names can be safer.
78. Alias to disambiguate
pythonfrom A import x as ax from B import x as bx
Now:
pythonax bx
remain distinct.
79. Import errors
If Python cannot find a requested module:
pythonimport does_not_exist
you get an import-related error such as:
textModuleNotFoundError
This is different from a logical error inside your code.
80. Import succeeds but attribute absent
Suppose:
pythonimport math
but you ask for:
pythonmath.nonexistent
The module exists, but that attribute does not.
This is a different failure from the module itself being missing.
Common mistake: include
.pyWrong:
pythonimport helper.py
Normal module import:
pythonimport helper
Common mistake: forget module prefix
After:
pythonimport math
wrong:
pythonsqrt(25)
unless
sqrt was separately imported.Correct:
pythonmath.sqrt(25)
Common mistake: use prefix after direct import
After:
pythonfrom math import sqrt
normally call:
pythonsqrt(25)
Not:
pythonmath.sqrt(25)
unless
math itself was also imported.Common mistake: wildcard imports
pythonfrom module import *
may silently overwrite names and obscure origins.
Prefer explicit imports for predictable code.
Common mistake: execute program on import
Suppose module ends with:
pythonmain()
Then importing it runs the program.
Better:
pythonif __name__ == "__main__": main()
when execution should happen only directly.
Common mistake: duplicate code instead of extracting module
If several programs contain identical helper functions, that is a sign reusable logic could be placed into a common module.
Common mistake: giant utility module
The opposite extreme is one file containing hundreds of unrelated helpers.
Good modularization groups related functionality coherently.
Common mistake: circular dependency
Avoid architectures like:
textA imports B B imports A
when possible.
Prefer a dependency structure such as:
textA ──> shared B ──> shared
where common functionality moves into a third module.
Common mistake: shadow standard modules
Files named:
textmath.py random.py statistics.py
can interfere with intended standard-library imports.
Choose project filenames carefully.
Common mistake: shadow imported name
pythonimport math math = 10
Now:
pythonmath.sqrt(4)
fails because
math no longer refers to the module.91. Trace example
helper.py:pythonx = 10 def add(a, b): return a + b
main.py:pythonimport helper y = helper.add(helper.x, 5) print(y)
Trace:
texthelper.x = 10 helper.add(10, 5) → 15
Output:
92. Trace with alias
pythonimport helper as h print(h.add(2, 3))
h refers to the module object.Then:
pythonh.add
retrieves its function.
Output:
93. Trace direct import
pythonfrom helper import add print(add(2, 3))
The name
add is placed directly in the current module's namespace.No
helper. prefix is needed.94. Main-guard trace
demo.py:pythondef f(): print("F") print("A") if __name__ == "__main__": print("B")
Run directly
Output:
textA B
Import as module
Top-level:
pythonprint("A")
still executes.
But main-guard body does not.
Output:
textA
95. Why definitions alone are safer
If reusable module mostly contains:
pythondef ... class ... CONSTANT = ...
then importing it has predictable behavior.
Heavy application logic belongs behind explicit function calls or a main guard.
96. Modules and files are different concepts
Week 9–10 files:
textdata.txt
are generally data.
Week 11 modules:
textutils.py
contain Python code.
Both are files at the operating-system level, but their roles differ.
97. Module system as code organization
Think:
textDATA ORGANIZATION lists / dicts / files CODE ORGANIZATION functions / modules / packages
Functions organize instructions inside a file.
Modules organize functions/classes/constants across files.
Packages organize modules into larger groups.
98. Hierarchy of abstraction
textindividual statements ↓ functions ↓ modules ↓ packages ↓ applications
Each level groups lower-level details into more manageable units.
99. Week 10 → Week 11 transition
Week 10 asked:
How should data move between persistent files and Python programs?
Week 11 asks:
How should the Python program itself be organized?
Conceptually:
100. Compact module cheat sheet
python## import module import math math.sqrt(25)
python## import specific name from math import sqrt sqrt(25)
python## alias module import math as m m.sqrt(25)
python## alias imported name from math import sqrt as sq sq(25)
python## main guard if __name__ == "__main__": main()
101. Import-style comparison
| Syntax | Call style | Namespace clarity |
|---|---|---|
import math | math.sqrt() | High |
import math as m | m.sqrt() | High, shorter |
from math import sqrt | sqrt() | Short |
from math import * | sqrt() | Low; collision-prone |
102. Module-design checklist
When deciding what belongs in a module, ask:
- Is this functionality reusable?
- Are these functions conceptually related?
- Can callers use them through a clean interface?
- Does importing the module avoid unnecessary side effects?
- Are names clear?
- Are dependencies understandable?
- Could this code be tested separately?
- Am I duplicating functionality somewhere else?
103. What you should actually master
By the end of Python Week 11, you should be able to:
- Explain what a Python module is.
- Import a module with
import. - Access module attributes with dot notation.
- Use
from ... import .... - Use aliases with
as. - Explain what a namespace is.
- Understand how modules reduce name collisions.
- Use standard-library modules conceptually.
- Create and import your own module.
- Understand that top-level module code executes during import.
- Distinguish function definition from function execution.
- Explain
__name__. - Explain
__name__ == "__main__". - Use a main guard to avoid unwanted import-time execution.
- Understand module-level constants/state.
- Distinguish modules from packages.
- Understand basic dependency structure.
- Avoid wildcard-import/name-shadowing problems.
- Recognize circular-import problems conceptually.
- Understand modules as reusable abstraction boundaries.
If these are comfortable, Programming in Python Week 11 is under control.
Quick Recall & Flashcards
Test your active recall before proceeding to practice problems. Format:
Question :: Answer.- Week 11 Diagnostic: What is the purpose of the
if __name__ == '__main__':idiom in Python scripts? :: Answer: B —__name__is'__main__'only when the script is run directly. When imported into another script,__name__is set to the module name. - Week 11 Diagnostic: If a script is run as
python script.py data.csv 100, what is the value ofsys.argv[1]? :: Answer: B —sys.argv[0]is the script name'script.py';sys.argv[1]is the first CLI argument'data.csv'.
Practice & Self-Check
Attempt these diagnostic questions to verify your operational understanding.
1
Question 1
What is the purpose of the
if __name__ == '__main__': idiom in Python scripts?MCQ
3 Unit Assessment
A. To check if the Python version is up to date
B. To execute code only when the file is run directly, and not when it is imported as a module
C. To define a class constructor
D. To grant root permissions
2
Question 2
If a script is run as
python script.py data.csv 100, what is the value of sys.argv[1]?MCQ
3 Unit Assessment
A. 'script.py'
B. 'data.csv'
C. '100'
D. 'python'