# Python 3 - Functions, Modules, and Real Data: Making Python Do Useful Work

*Write reusable code, bring in tools from the Python ecosystem, call a real API, and read and save files.*

## Part 1: Aman Repeats Himself

Aman is happy. He can write loops, conditions, and lists. He writes a small program that greets three friends:

```python
print("Hello, Priya! Welcome to Chai Club.")
print("Hello, Rahul! Welcome to Chai Club.")
print("Hello, Neha! Welcome to Chai Club.")
```

Neha looks at it and says, "Now change 'Chai Club' to 'Code Club'."

Aman edits three lines. Neha says, "Now imagine 300 lines." Aman understands the problem. **Repeating code is slow to write and easy to break.**

"That," says Neha, "is why functions exist."

This article covers:

1. Functions: defining and calling
2. Parameters and arguments
3. Global vs local scope
4. Returning values
5. Modules and packages (external tools)
6. Importing modules and built-ins
7. `requirements.txt`
8. Working with APIs using `requests`
9. Working with data using `pandas` and `matplotlib`
10. Reading and saving files

![](https://cdn.hashnode.com/uploads/covers/68822b6ad516e7a593530cf9/5bdacaa3-99af-43b9-a590-477fe85d57e4.png align="center")

---

## Part 2: Functions (Defining and Calling)

A **function** is a named block of code that does one job. You write it once and use it many times.

### Defining a function

```python
def greet():
    print("Hello! Welcome to Chai Club.")
```

Read it:

- `def` → "define a function"
- `greet` → the name of the function
- `()` → brackets (empty for now)
- `:` → colon ends the line
- The indented block is the **body**: what the function does

Important: **defining a function does not run it.** Python just remembers it.

### Calling a function

To run it, write its name with brackets:

```python
greet()
greet()
greet()
```

Output:

```plaintext
Hello! Welcome to Chai Club.
Hello! Welcome to Chai Club.
Hello! Welcome to Chai Club.
```

Now, if Aman wants to change the message, he changes **one** line inside the function. Every call updates automatically.

### Naming rules

- Use lowercase and underscores: `send_email`, `calculate_total`
- Use a verb, because a function *does* something
- Define a function **before** you call it

### Docstrings

A short description inside triple quotes, right after the `def` line. VS Code shows it when you hover over the function.

```python
def greet():
    """Print a welcome message."""
    print("Hello! Welcome to Chai Club.")
```

### Practice

1. Write a function `say_hi()` that prints "Hi there!" and call it twice.
2. What happens if you write `def greet():` with a body but never write `greet()`?
3. Why do we use `def` and not just copy-paste the code?

---

## Part 3: Function Parameters and Arguments

Aman's `greet()` always says the same thing. He wants it to greet different people. For that, a function needs **input**.

### Parameters

A **parameter** is a variable in the function definition that receives input:

```python
def greet(name):
    print(f"Hello, {name}! Welcome to Chai Club.")
```

`name` is the parameter.

### Arguments

An **argument** is the actual value you pass when calling:

```python
greet("Priya")
greet("Rahul")
```

`"Priya"` and `"Rahul"` are arguments.

Simple way to remember: **parameter = the empty slot. Argument = the thing you put in the slot.**

![]( align="center")

### Multiple parameters

```python
def greet(name, club):
    print(f"Hello, {name}! Welcome to {club}.")

greet("Priya", "Chai Club")
greet("Rahul", "Code Club")
```

### Positional vs keyword arguments

**Positional**: order matters.

```python
greet("Priya", "Chai Club")   # name="Priya", club="Chai Club"
greet("Chai Club", "Priya")   # wrong! name="Chai Club"
```

**Keyword**: you name each one, so order does not matter.

```python
greet(club="Chai Club", name="Priya")   # works correctly
```

Keyword arguments make code easier to read when a function has many parameters.

### Default values

You can give a parameter a default. Then it becomes optional.

```python
def greet(name, club="Chai Club"):
    print(f"Hello, {name}! Welcome to {club}.")

greet("Priya")                 # uses default: Chai Club
greet("Rahul", "Code Club")    # overrides the default
```

Rule: parameters with defaults must come **after** parameters without defaults.

### Type hints (optional but helpful)

```python
def add(a: int, b: int) -> int:
    return a + b
```

This does not force types. It is a note for humans and for VS Code. In AI projects you will see this style a lot.

### Practice

1. Write `multiply(a, b)` that prints `a * b`. Call it with `3` and `4`.
2. Write `order(drink, size="medium")` that prints "One medium chai" (or whatever you pass). Call it with and without the size.
3. In `greet("Priya", "Chai Club")`, which word is the parameter and which is the argument?
4. What is wrong here?

   ```python
   def greet(club="Chai Club", name):
       ...
   ```

---

## Part 4: Global vs Local Variable Scope

Aman writes this and gets confused:

```python
def make_coffee():
    cups = 2
    print(cups)

make_coffee()   # prints 2
print(cups)     # NameError: name 'cups' is not defined
```

Why is `cups` not found? Because of **scope**.

### What is scope?

Scope is the area of the program where a variable exists.

- A variable created **inside** a function is **local**. It exists only inside that function. When the function ends, it is gone.
- A variable created **outside** all functions is **global**. It exists everywhere in the file.

```python
club = "Chai Club"      # global

def greet():
    name = "Priya"      # local
    print(name, club)   # can read both

greet()
print(club)   # fine
print(name)   # NameError
```

![](https://cdn.hashnode.com/uploads/covers/68822b6ad516e7a593530cf9/5547c668-7389-483c-9633-e0d7b7fd49fd.png align="center")

### Reading vs changing a global

A function can **read** a global variable. But if it tries to **assign** to it, Python creates a new local variable instead:

```python
count = 0

def increase():
    count = count + 1   # UnboundLocalError!

increase()
```

Python sees `count = ...` inside the function and decides `count` is local. But then `count + 1` reads a local that does not exist yet. Error.

### The `global` keyword

If you really want to change a global inside a function:

```python
count = 0

def increase():
    global count
    count = count + 1

increase()
print(count)   # 1
```

### Neha's advice

"Avoid `global`. It makes code hard to understand. Instead, give the function input and get output back with `return`. That is the next topic."

### Practice

1. What does this print?

   ```python
   x = 10
   def show():
       x = 5
       print(x)
   show()
   print(x)
   ```
2. Why does this fail?

   ```python
   def add_item():
       items.append("chai")
   add_item()
   ```

   (Hint: is `items` defined anywhere?)
3. Is a variable created inside a function available after the function finishes?

---

## Part 5: Returning Values from Functions

So far, Aman's functions only `print`. But printing just shows text on the screen. It does not give the result back to the program.

### return

`return` sends a value out of the function.

```python
def add(a, b):
    return a + b

result = add(3, 4)
print(result)   # 7
```

Now the result is stored in `result`. Aman can use it later: save it, pass it to another function, do more maths.

### print vs return

This is the most common beginner confusion.

| `print()` | `return` |
| --- | --- |
| Shows something on screen | Gives a value back to the caller |
| For humans to see | For the program to use |
| Function still returns `None` | Function ends here |

```python
def add_print(a, b):
    print(a + b)

def add_return(a, b):
    return a + b

x = add_print(3, 4)    # shows 7, but x is None
y = add_return(3, 4)   # shows nothing, but y is 7
```

### return ends the function

Any code after `return` does not run:

```python
def check(n):
    if n > 0:
        return "positive"
    return "not positive"
```

### Returning multiple values

```python
def min_max(numbers):
    return min(numbers), max(numbers)

low, high = min_max([4, 9, 1, 7])
print(low, high)   # 1 9
```

Python packs them into a tuple and unpacks them for you.

### Functions that return nothing

If there is no `return`, the function returns `None` automatically.

### Chaining functions

This is where functions become powerful:

```python
def clean(text):
    return text.strip().lower()

def is_valid_email(email):
    return "@" in email and "." in email

raw = "  Aman@Example.com  "
email = clean(raw)
print(is_valid_email(email))   # True
```

Small functions, each doing one job, combined together. This is how real programs are built.

![](https://cdn.hashnode.com/uploads/covers/68822b6ad516e7a593530cf9/058df3d5-0e71-4c25-9d62-d1833c81254c.png align="center")

### Practice

1. Write `square(n)` that returns `n * n`. Store `square(6)` in a variable and print it.
2. What is `x` after `x = print("hi")`?
3. Write `divide(a, b)` that returns the quotient and the remainder as two values.
4. What does this return?

   ```python
   def f():
       return 1
       return 2
   ```

---

## Part 6: External Tools (Modules and Packages)

Aman now writes many functions. His file is getting long. And he wonders: "Do I have to write *everything* myself? Like, code to download a web page?"

"No," says Neha. "That is the best part of Python. Other people already wrote it. You just import it."

### Three words

- **Module**: a single `.py` file with functions and variables inside. Your own `hello.py` is a module.
- **Package**: a folder of modules. Big libraries like `pandas` are packages.
- **Library**: a general word for a module or package that you use.

### Three sources

1. **Built-in / standard library**: comes with Python. `math`, `os`, `random`, `json`, `datetime`.
2. **Third-party packages**: you install with `pip`. `requests`, `pandas`, `openai`.
3. **Your own modules**: files you write in your project.

![](https://cdn.hashnode.com/uploads/covers/68822b6ad516e7a593530cf9/9832d61d-2549-4638-bc04-cedca08f5571.png align="center")

---

## Part 7: Importing Modules and Built-ins

### import module

```python
import math

print(math.sqrt(25))   # 5.0
print(math.pi)         # 3.141592653589793
```

You use `module.name` to reach things inside.

### from module import name

```python
from math import sqrt, pi

print(sqrt(25))   # 5.0
print(pi)
```

Now you use the name directly. Good when you need only a few things.

### import module as nickname

```python
import pandas as pd
import matplotlib.pyplot as plt
```

Very common in data and AI code.

### from module import \* (avoid)

```python
from math import *
```

This imports everything. It looks convenient, but you lose track of where names come from. Neha says: "Don't."

### Useful standard library modules

```python
import random
print(random.randint(1, 6))        # random dice roll
print(random.choice(["chai", "coffee"]))

import datetime
print(datetime.date.today())       # 2026-10-30

import os
print(os.getcwd())                 # current folder
print(os.listdir("."))             # files in current folder

import json
data = {"name": "Aman", "age": 25}
text = json.dumps(data)            # dict → JSON string
back = json.loads(text)            # JSON string → dict
```

### Importing your own module

Aman creates `helpers.py` in his project:

```python
# helpers.py
def clean(text):
    return text.strip().lower()
```

And in `main.py`:

```python
from helpers import clean

print(clean("  HELLO  "))   # hello
```

Both files must be in the same folder (or Python must know where to look).

---

## Part 8: Import Methods Summary

| Syntax | How you use it | When |
| --- | --- | --- |
| `import math` | `math.sqrt(4)` | Default, clear |
| `from math import sqrt` | `sqrt(4)` | A few specific names |
| `import pandas as pd` | `pd.read_csv()` | Long names, common nicknames |
| `from math import *` | `sqrt(4)` | Avoid |

Also:

- Put all imports at the **top** of the file.
- Standard order: built-ins first, third-party second, your own modules last.

---

## Part 9: Installing Packages and requirements.txt

Aman's project now needs `requests`, `pandas`, and `matplotlib`. He installs them (with `.venv` active):

```bash
pip install requests pandas matplotlib
```

### The problem

Neha asks, "If I clone your project tomorrow, how do I know which packages to install?"

### The solution: requirements.txt

A simple text file listing the packages:

```plaintext
requests==2.31.0
pandas==2.2.2
matplotlib==3.9.0
```

### Create it

```bash
pip freeze > requirements.txt
```

`pip freeze` prints all installed packages with exact versions. `>` saves that output to the file.

### Install from it

```bash
pip install -r requirements.txt
```

`-r` means "read from this file". One command installs everything.

### Pin versions or not?

- `requests==2.31.0` → exactly this version. Safe and repeatable.
- `requests>=2.31.0` → this or newer. More flexible, but may break one day.
- `requests` → any version. Risky for real projects.

For now, `pip freeze` with exact versions is fine.

Neha's rule: **Every project folder has a** `requirements.txt`**. Always commit it.**

### Practice

1. Create `requirements.txt` for your project with `pip freeze`.
2. What does the `-r` in `pip install -r requirements.txt` mean?
3. Rewrite using the "nickname" style: `import matplotlib.pyplot` → `?`
4. What is the difference between a module and a package?

---

## Part 10: Working with APIs (requests Example)

This is where Python stops being a toy. Aman will fetch real data from the internet.

### What is an API?

An **API** (Application Programming Interface) is a way for programs to talk to each other. A web API is a URL that returns data (usually JSON) instead of a web page.

Example: a weather service, a currency rate service, an AI model service.

### A simple GET request

Aman uses the free JSONPlaceholder test API:

```python
import requests

url = "https://jsonplaceholder.typicode.com/posts/1"
response = requests.get(url)

print(response.status_code)   # 200
print(response.json())
```

Output:

```plaintext
200
{'userId': 1, 'id': 1, 'title': 'sunt aut facere ...', 'body': 'quia et suscipit ...'}
```

### Reading the pieces

- `requests.get(url)` → send a GET request
- `response.status_code` → `200` = OK, `404` = not found, `500` = server error
- `response.json()` → convert the JSON text into a Python **dictionary**

Now it is just a dictionary:

```python
data = response.json()
print(data["title"])
print(data["userId"])
```

![](https://cdn.hashnode.com/uploads/covers/68822b6ad516e7a593530cf9/968dbbf5-22be-4012-851d-e1759ebec937.png align="center")

### Passing parameters

Many APIs accept query parameters (`?key=value`):

```python
params = {"userId": 1}
response = requests.get("https://jsonplaceholder.typicode.com/posts", params=params)
posts = response.json()
print(len(posts))   # 10
```

### Looping over results

```python
for post in posts:
    print(post["id"], post["title"])
```

### Always check the status

```python
response = requests.get(url)

if response.status_code == 200:
    data = response.json()
else:
    print("Something went wrong:", response.status_code)
```

### Wrap it in a function

```python
def get_post(post_id):
    url = f"https://jsonplaceholder.typicode.com/posts/{post_id}"
    response = requests.get(url)
    if response.status_code == 200:
        return response.json()
    return None

post = get_post(5)
print(post["title"])
```

Everything from this article in one place: a function, a parameter, an f-string, an `if`, a `return`, and an imported package.

### Practice

1. Fetch `https://jsonplaceholder.typicode.com/users/1` and print the user's name and email.
2. What does status code `404` mean?
3. What does `response.json()` return — a string or a dictionary?
4. Write `get_user(user_id)` that returns the user dictionary or `None`.

---

## Part 11: Working with Data (pandas and matplotlib)

Aman's next task: analyse some sales data. For this, two famous packages:

- **pandas** → tables of data (called DataFrames)
- **matplotlib** → charts

### A DataFrame

```python
import pandas as pd

data = {
    "drink": ["chai", "coffee", "lassi", "juice"],
    "price": [10, 20, 30, 25],
    "sold": [120, 80, 40, 60]
}

df = pd.DataFrame(data)
print(df)
```

Output:

```plaintext
    drink  price  sold
0    chai     10   120
1  coffee     20    80
2   lassi     30    40
3   juice     25    60
```

A DataFrame is like a spreadsheet in Python. `df` is the usual name.

### Looking at data

```python
df.head()        # first 5 rows
df.shape         # (4, 3) → 4 rows, 3 columns
df.columns       # column names
df.describe()    # quick statistics
df.info()        # types and missing values
```

### Selecting

```python
df["price"]               # one column
df[["drink", "sold"]]     # several columns
df[df["price"] > 15]      # rows where price > 15
```

### Creating a new column

```python
df["revenue"] = df["price"] * df["sold"]
print(df)
```

```plaintext
    drink  price  sold  revenue
0    chai     10   120     1200
1  coffee     20    80     1600
2   lassi     30    40     1200
3   juice     25    60     1500
```

### Simple statistics

```python
print(df["revenue"].sum())    # 5500
print(df["price"].mean())     # 21.25
print(df.sort_values("revenue", ascending=False))
```

### A chart with matplotlib

```python
import matplotlib.pyplot as plt

plt.bar(df["drink"], df["revenue"])
plt.title("Revenue by drink")
plt.xlabel("Drink")
plt.ylabel("Revenue")
plt.show()
```

A bar chart window appears. In Jupyter or VS Code interactive mode, it shows inline.

![](https://cdn.hashnode.com/uploads/covers/68822b6ad516e7a593530cf9/b1ebe4e0-4394-4491-80b4-1ab6da298254.png align="center")

---

## Part 12: Reading and Saving Data Files

Data usually lives in files. Aman must know how to read and write them.

### Plain text files

Writing:

```python
with open("notes.txt", "w") as f:
    f.write("Chai is good.\n")
    f.write("Code is fun.\n")
```

Reading:

```python
with open("notes.txt", "r") as f:
    content = f.read()
print(content)
```

Read line by line:

```python
with open("notes.txt") as f:
    for line in f:
        print(line.strip())
```

### Modes

| Mode | Meaning |
| --- | --- |
| `"r"` | read (default) |
| `"w"` | write — **deletes** existing content |
| `"a"` | append — add to the end |

### Why `with`?

`with open(...) as f:` opens the file and **closes it automatically** when the block ends. Without `with`, you must call `f.close()` yourself, and people forget. Always use `with`.

### JSON files

Perfect for dictionaries and lists.

```python
import json

data = {"name": "Aman", "drinks": ["chai", "coffee"]}

# save
with open("data.json", "w") as f:
    json.dump(data, f, indent=2)

# load
with open("data.json") as f:
    loaded = json.load(f)
print(loaded["drinks"])
```

Note: `json.dump` / `json.load` work with files. `json.dumps` / `json.loads` work with strings (the `s` means string).

### CSV files with pandas

```python
# save
df.to_csv("sales.csv", index=False)

# load
df2 = pd.read_csv("sales.csv")
print(df2.head())
```

`index=False` stops pandas from saving the row numbers as a column.

pandas can also read Excel (`pd.read_excel`), JSON (`pd.read_json`), and many more.

### File paths

Files are saved **relative to the folder where you run Python**, which is usually your project folder in VS Code. Keep data in a `data/` subfolder:

```python
df.to_csv("data/sales.csv", index=False)
```

(We will go deeper into paths in the next article.)

![](https://cdn.hashnode.com/uploads/covers/68822b6ad516e7a593530cf9/27f570d3-582c-451e-b88f-b8b71700b41f.png align="center")

### Practice

1. Write three lines to `todo.txt`, then read and print them.
2. What happens to the old content when you open a file with `"w"`?
3. Save the dictionary `{"city": "Ahmedabad", "temp": 34}` to `weather.json` and load it back.
4. Create a DataFrame with two columns, save it as CSV, and read it back.
5. What is the difference between `json.dump` and `json.dumps`?

---

## Part 13: Final Quiz

Try without looking back. Answers at the end.

**Q1.** What is the difference between a parameter and an argument?

**Q2.** What does this print?

```python
def greet(name, club="Chai Club"):
    return f"{name} → {club}"

print(greet("Priya"))
print(greet("Rahul", club="Code Club"))
```

**Q3.** What is `result` after this?

```python
def show():
    print("hi")

result = show()
```

**Q4.** What does this print?

```python
total = 100

def spend():
    total = 20
    return total

print(spend())
print(total)
```

**Q5.** Which import style should you avoid, and why?

**Q6.** What command creates `requirements.txt` from your current environment?

**Q7.** In `requests.get(url)`, what does `response.json()` give you?

**Q8.** What does `df[df["price"] > 15]` do?

**Q9.** Why should you use `with open(...)` instead of `open(...)` alone?

**Q10.** Write a function `average(numbers)` that returns the mean of a list.

---

### Answers

**Part 2:** 1.

```python
def say_hi():
    print("Hi there!")
say_hi()
say_hi()
```

2. Nothing. The function is defined but never runs.
3. One place to change, less code, fewer bugs, and the name explains what the code does.

**Part 3:**

1. `def multiply(a, b): print(a * b)` → `multiply(3, 4)` prints `12`.

```python
def order(drink, size="medium"):
    print(f"One {size} {drink}")
order("chai")            # One medium chai
order("chai", "large")   # One large chai
```

- In the definition `greet(name, club)`, `name` and `club` are parameters. `"Priya"` and `"Chai Club"` are arguments.
- A parameter with a default (`club`) comes before one without (`name`). Python gives a `SyntaxError`. Swap the order.

**Part 4:**

1. `5` then `10`. The inner `x` is local; the outer `x` is unchanged.
2. `items` is never defined anywhere, so `NameError`. (If `items = []` existed globally, `.append` would work because that modifies the list, not reassigns the name.)
3. No. Local variables disappear when the function ends.

**Part 5:**

1. `def square(n): return n * n` → `s = square(6)` → `36`.
2. `None`. `print` returns nothing.
3. `def divide(a, b): return a // b, a % b`
4. `1`. The second `return` never runs.

**Part 9:**

1. `pip freeze > requirements.txt`
2. Read the package list from the given file.
3. `import matplotlib.pyplot as plt`
4. A module is one `.py` file. A package is a folder of modules.

**Part 10:** 1.

```python
r = requests.get("https://jsonplaceholder.typicode.com/users/1")
user = r.json()
print(user["name"], user["email"])
```

2. Not found — the URL does not exist on the server.
3. A dictionary (or a list, depending on the JSON).

```python
def get_user(user_id):
    r = requests.get(f"https://jsonplaceholder.typicode.com/users/{user_id}")
    if r.status_code == 200:
        return r.json()
    return None
```

**Part 12:**

1. Use `"w"` mode and `f.write()`, then open again with `"r"` and `f.read()`.
2. It is deleted. The file starts empty.
3. `json.dump(data, f)` to save; `json.load(f)` to load.
4. `df.to_csv("file.csv", index=False)` then `pd.read_csv("file.csv")`.
5. `json.dump` writes to a file. `json.dumps` returns a string.

**Final Quiz:**

1. A parameter is the placeholder name in the function definition. An argument is the real value passed when calling.
2. `Priya → Chai Club` and `Rahul → Code Club`.
3. `None`. The function prints but does not return.
4. `20` then `100`. The function's `total` is local.
5. `from module import *`. You cannot tell where names come from, and names can overwrite each other.
6. `pip freeze > requirements.txt`
7. The response body converted from JSON into a Python dictionary or list.
8. Returns only the rows where the `price` column is greater than 15.
9. `with` closes the file automatically, even if an error happens.

```python
def average(numbers):
    return sum(numbers) / len(numbers)
```

---

## Epilogue

Aman looks at his project folder. There is `main.py`, `helpers.py`, `requirements.txt`, and a `data/` folder with a CSV inside. His code calls a real API, turns the result into a table, draws a chart, and saves everything to a file.

Three articles ago, he could print "Hello". Now he can fetch, process, and store real data.

Neha says, "Notice what changed. You stopped writing *everything* yourself. You write small functions, and you borrow the rest from the Python ecosystem. That is how AI engineers work. The model is just one more package you import."
