Step 42. Data Types in Depth — Four Baskets for Holding Values
Level 1 — Programming and the Computer’s Inner Workings | Difficulty ★★☆☆☆ | Estimated time: 3 hours
Prerequisites: Step 41 complete. You know Python installation, running VSCode, variables, and input()/print().
- What you need: the same workbench from Step 41 (Python + VSCode). Keep using the
py_practicefolder. - Caution: today’s exercise is 100% safe. It’s nothing but creating and running code files on your own computer.
Last time you learned how to attach a name tag to a single value: name = "Minsu". But real-world problems are never about "one." A connection log holds thousands of IPs, a class roster holds dozens of names, a server configuration holds dozens of items. Store those in individual variables and you get ip1, ip2, ip3… management collapses somewhere around the thousandth. That’s why programming has data structures — baskets that hold many values in one variable. Today you learn Python’s four representative baskets, plus string manipulation. The choice of "which basket do I use?" will later decide half of your scan-result organization and log-aggregation code.
1. Learning Objectives
By the end of this chapter, you will be able to:
- Create lists, retrieve by index, and add with
append - Explain the "end not included" rule of slicing
[start:end] - Split and search strings with
split,replace, andin - Add, modify, and look up dictionary items, and know the safety of
.get() - Remove duplicates with sets, and know the mutable/immutable distinction
- Look at a situation and pick the right basket among the four, with reasons
2. Background Knowledge — Today’s Tools and Concepts
Today’s Tools at a Glance
| Category | Details |
|---|---|
| Language/environment | Python 3.12 or later, VSCode (the Step 41 workbench) |
| Today’s commands/grammar | lists [], append, len, slicing; tuples (); dictionaries {}, .get(); sets set(); string split, replace, join, in |
| Concepts needed | index (counting from 0), mutable vs. immutable, nested data structures |
2-1. Lists — A Bookshelf with Order
A list is a basket with order. You make it with square brackets [].
ips = ["1.1.1.1", "8.8.8.8", "9.9.9.9"]
Each slot has a number, and computers count from 0. The first value is ips[0], the second is ips[1]. This number is called an index. It feels awkward at first, but your body will remember it soon.
2-2. Tuples — Sealed Lists
A tuple is a list made with parentheses () that cannot be changed once created. Use it when you need the promise "these values must not change." Today, just learn that it exists and what it looks like.
2-3. Dictionaries — A Name-Tag Dictionary
A dictionary is a collection of "tag: value" pairs made with curly braces {}.
host = {"ip": "192.168.0.23", "os": "ubuntu", "port": 22}
If a list finds things by "which slot?", a dictionary finds things by "which name tag?" Ask host["os"] and "ubuntu" comes out. Since you find things by name rather than position, it stays unconfusing even with many items, and it’s the most common choice for holding server info and configuration values. The name tag is called the key, and the value is called the value.
2-4. Sets — A No-Duplicates Roster
A set is a basket that doesn’t allow duplicates. Put the same value in twice and only one remains. It’s the tool for "extract only the distinct IPs" from a log.
2-5. Mutable and Immutable — Can It Be Changed?
One concept runs through all four baskets: once made, can the contents be changed?
- Mutable: lists, dictionaries, sets — changeable.
- Immutable: tuples, and strings — not changeable.
"Strings are immutable" means you can’t directly fix the second character of "abc". Instead, you make a new string and swap it in. This distinction later becomes the answer to "why does this work but that doesn’t?"
3. Follow Along
3-1. Making and Handling a List
Create a file basket.py and type along.
ips = ["1.1.1.1", "8.8.8.8"]
print(ips)
print(ips[0]) # first slot
print(ips[-1]) # last slot (counting from the back)
ips.append("9.9.9.9") # add to the end
print(ips)
print(len(ips)) # number of slots
['1.1.1.1', '8.8.8.8']
1.1.1.1
8.8.8.8
['1.1.1.1', '8.8.8.8', '9.9.9.9']
3
(Verified on 2026-09-09.)
How to read it: ips[0] is the first slot; ips[-1] is a handy expression meaning "the first slot from the back." .append() stacks one onto the end, and len() counts the slots. Read the dot (.) as "’s" — ips.append is "ips’s add function." These four operations (retrieve, add, count) are most of list usage.
3-2. Slicing — Tearing Off a Piece
ips = ["1.1.1.1", "8.8.8.8", "9.9.9.9", "114.114.114.114"]
print(ips[0:2])
print(ips[2:])
print(ips[:2])
print(ips[1:3])
print(ips[-2:])
['1.1.1.1', '8.8.8.8']
['9.9.9.9', '114.114.114.114']
['1.1.1.1', '8.8.8.8']
['8.8.8.8', '9.9.9.9']
['9.9.9.9', '114.114.114.114']
(Verified on 2026-09-09.)
How to read it: ips[0:2] means "from index 0 up to just before index 2." The end number is not included. It says 0:2 but only indexes 0 and 1 come out — today’s first trap. Leave one side of the colon empty and it means "from the beginning" or "to the end." Slicing is the basic skill for cutting out pieces like "just the last 10 lines" of a log.
3-3. String Manipulation — Text Works Like a List
A string is a sequence of characters, so list-like manipulation works on it.
log = "192.168.0.23 - - GET /admin HTTP/1.1"
print(log[0:13]) # cut the front part
print(log.split(" ")) # split on spaces into a list
print(log.replace("GET", "POST")) # a new string with a swap
print("admin" in log) # containment check
192.168.0.23
['192.168.0.23', '-', '-', 'GET', '/admin', 'HTTP/1.1']
192.168.0.23 - - POST /admin HTTP/1.1
True
(Verified on 2026-09-09. The output is shown exactly as produced, including the trailing space at the end of the first line.)
How to read it: split(" ") uses the space as a knife to split the string into a list. replace doesn’t fix the original — it returns a new string with the swap (because strings are immutable). in is a true/false question: "is it contained?" Much of the data you handle in security study is text logs, and these three techniques — split, find, replace — are the starting point of log analysis.
3-4. Dictionaries — Managing by Name Tag
host = {"ip": "192.168.0.23", "os": "ubuntu", "port": 22}
print(host["os"])
host["user"] = "lee" # add a new item
host["port"] = 2222 # modify an existing item
print(host)
print(host.get("location")) # safely ask about a missing key
print(host.get("location", "unknown")) # specify a fallback value if missing
ubuntu
{'ip': '192.168.0.23', 'os': 'ubuntu', 'port': 2222, 'user': 'lee'}
None
unknown
(Verified on 2026-09-09.)
How to read it: the point is that adding and modifying have the same shape (host["key"] = value). And if you ask directly about a missing key like host["location"], you get a KeyError — but asking with .get() returns None (a special value meaning "nothing") instead of an error. The second slot of .get(key, default) means "if it’s missing, use this instead." In real code, .get() is your seatbelt.
3-5. Removing Duplicates with a Set
visitors = ["1.1.1.1", "8.8.8.8", "1.1.1.1", "9.9.9.9", "8.8.8.8"]
unique = set(visitors)
print(unique)
print(len(unique))
{'8.8.8.8', '9.9.9.9', '1.1.1.1'}
3
(Verified on 2026-09-09.)
How to read it: five visits were actually three visitors. The moment you wrap with set(), duplicates evaporate. In the verified output the order looks different from insertion order — a set is a basket that never guaranteed order to begin with. It only promises "no duplicates."
3-6. Practice Picking Baskets
Read each situation and pick the right basket. Write the answer and the reason in your notebook.
- You want to record today’s visiting IPs in order.
- You want to store a server’s IP, OS, and open port together.
- You want to count deduplicated visitors.
- You want to store a pair of values that must never change, like a company’s latitude and longitude.
Answers: 1 — list (order matters), 2 — dictionary (found by name tag), 3 — set (deduplication), 4 — tuple (sealed). Saying the reason matters more than getting the answer right. The reason is exactly your basis for judgment in the field.
3-7. Nested Baskets — Baskets Inside Baskets
Real data comes in layers. If a "server list" is also one where each server is a "bundle of info," you store it like this:
servers = [
{"name": "web", "ip": "192.168.0.10", "ports": [80, 443]},
{"name": "db", "ip": "192.168.0.20", "ports": [3306]},
]
print(servers[0]["ip"])
print(servers[1]["ports"][0])
print(len(servers[0]["ports"]))
192.168.0.10
3306
2
(Verified on 2026-09-09.)
How to read it: a list containing dictionaries, each containing another list. Read from the left. servers[1] is the second server (a dictionary), ["ports"] is that server’s port list, [0] is the first value of that list. It’s like peeling shells one by one. The API responses, config files, and scan results you’ll meet later are all nested structures like this. Remember only the principle "one shell at a time, from the left," and deep structures won’t scare you.
Try it yourself: write the code that adds 5432 to
servers[1]["ports"]. Hint: approach via the same path as retrieval and attachappend. The answer isn’t in the section-5 checklist commentary — it’s right here:servers[1]["ports"].append(5432).
4. Missions & Exercises
Mission — A Student Score Manager
Create a file scores.py and assemble the following yourself.
- Store four students’ names and scores in a dictionary. Example:
{"Minsu": 85, "Jiyoung": 92, "Chris": 78, "Haneul": 95} - Receive one new student as input and add them (name and score; convert the score with
int()). - Print every student’s name and score, one per line. (Hint: looping
dictionary.items()with for gives you the tag and value together. If for still feels awkward, printing the whole dictionary is acceptable.) - Calculate and print the average score. (Hint: the score list is
list(dictionary.values()), the sum issum(), the count islen()) - Print the highest score. (Hint:
max(score_list))
When done, grade yourself. Check that the average is accurate to the decimal point and that the new student is included in the average.
Exercises
Question 1. Given ips = ["1.1.1.1", "8.8.8.8", "9.9.9.9"], what happens when you print ips[3], and why?
Question 2. Explain the difference between the results of ips[1:3] and ips[1:2] using the "end number not included" rule.
Question 3. When looking up a dictionary key that might not exist, why is host.get("location") recommended over host["location"]? What happens in each case when the key is missing?
Question 4. You want to compute "the number of distinct IPs that connected today" from a log. Which basket should you use, and why? What is the one thing you must give up when using a set?
5. Model Answers & Completion Criteria
Mission Model Answer
scores = {"Minsu": 85, "Jiyoung": 92, "Chris": 78, "Haneul": 95}
new_name = input("New student's name: ")
new_score = int(input("Score: ")) # input is text, so convert
scores[new_name] = new_score
print("=== All Scores ===")
for name, score in scores.items():
print(f"{name}: {score}")
values = list(scores.values())
print(f"Average: {sum(values) / len(values)}")
print(f"Highest score: {max(values)}")
Example run (entering new student Younghee, 88):
=== All Scores ===
Minsu: 85
Jiyoung: 92
Chris: 78
Haneul: 95
Younghee: 88
Average: 87.6
Highest score: 95
How to verify: ① Check that the new student is included in the list and the average. ② Verify the average by hand — (85+92+78+95+88) ÷ 5 = 87.6. ③ If you skip int() at score input, you get a TypeError at the sum() step — that error is itself the verification signal.
Exercise Answers
Answer 1. You get the error IndexError: list index out of range (message verified on 2026-09-09). Because indexes count from 0, the last slot of a three-slot list is ips[2], and ips[3] is a slot that doesn’t exist.
Answer 2. Both start at index 1, but the end number is not included, so ips[1:3] gives two items (indexes 1 and 2), while ips[1:2] gives only one (index 1). It’s convenient to remember that end - start is the number of pieces.
Answer 3. With host["location"], a missing key stops the program with a KeyError, but .get() returns None (or your specified default) instead of an error, so the program keeps going (verified on 2026-09-09: host.get("location") → None, host.get("location", "unknown") → unknown). For values that might be missing, .get() is safe.
Answer 4. Use a set. The "no duplicates" property — put the same value in twice and only one remains — is exactly the answer. len(set(visitors)) finishes it in one line (verified on 2026-09-09: five visits → 3). What you give up is order — a set doesn’t guarantee insertion order.
Completion Criteria Checklist
- [ ] I can explain list indexes (starting from 0) and
ips[-1](the last slot) - [ ] I can explain the slicing rule "end number not included" with an example
- [ ] I can use
append,len,split,replace, andjoin(" ".join(list)) - [ ] I can add, modify, and look up dictionary items, and I know the safety of
.get() - [ ] I can deduplicate with
set()and know that order is not guaranteed - [ ] I can pull values from nested structures with the "one shell at a time, from the left" rule
- [ ] I completed the mission (student score manager) and verified the average by hand
6. Common Pitfalls & Fixes
Wall 1. IndexError: list index out of range
Symptom (verified on 2026-09-09):
Traceback (most recent call last):
File "...s42_indexerror.py", line 2, in <module>
print(ips[5])
~~~^^^
IndexError: list index out of range
Cause: you pulled slot 5 from a list that only has two slots. Since counting starts at 0, the last of two slots is ips[1].
Fix: build the habit of checking the slot count with len(ips) before retrieving, and when you need "the last one," don’t count — use ips[-1].
Wall 2. I get a KeyError
Symptom: KeyError at host["lacation"].
Cause: two cases. The key truly doesn’t exist, or a typo (the example above is a typo of location). Dictionaries are strict about key spelling.
Fix: first check the actual key list with your eyes via print(host). For keys that might be missing, build the habit of asking with .get() from the start.
Wall 3. Printing a list shows brackets and quotes
Symptom: print(ips) comes out as ['1.1.1.1', '8.8.8.8']. You want it to look clean.
Cause: printing a list whole makes Python show the basket itself.
Fix: make "one string joined with spaces" via " ".join(ips) and print that. join is split in reverse. More refined one-per-line printing is learned at the next stage (loops).
Wall 4. Converting to a set scrambled the order
Symptom (verified on 2026-09-09): you put in {'1.1.1.1', '8.8.8.8', ...} but it comes out in a different order like {'8.8.8.8', '9.9.9.9', '1.1.1.1'}.
Cause: sets don’t guarantee order. It’s a basket that only promises "no duplicates."
Fix: if you need order, use a list; if you need both, get a sorted list with sorted(set(visitors)). Knowing each tool’s promise is skill.
7. Summary
Today’s Concepts
| Concept | One-line description |
|---|---|
List [] |
A basket with order — indexes start at 0 |
Tuple () |
A sealed list — once made, unchangeable |
Dictionary {} |
A collection of "key: value" pairs — found by name tag |
Set set() |
A no-duplicates basket — order not guaranteed |
| Mutable/immutable | The distinction of whether contents can change after creation |
| Index & slicing | Slot numbers (from 0) and cutting pieces with [start:end] (end excluded) |
| Nested structure | Baskets inside baskets — one shell at a time, from the left |
Today’s Grammar
| Grammar | What it does |
|---|---|
list[i] / list[-1] |
Get the i-th value / the last value |
list[a:b] |
A piece from a up to before b |
list.append(value) / len(list) |
Add to the end / number of slots |
string.split(" ") / " ".join(list) |
Split into a list / join into a string |
string.replace(old, new) / x in string |
A new swapped string / containment check |
dict[key] = value |
Add and modify (same shape) |
dict.get(key, default) |
Look up without error even if missing |
set(list) |
Remove duplicates |
Instincts More Important Than Grammar
Even the same problem grows three times longer if you pick the wrong basket. A hundred lines of list code manually checking for duplicates ends in one line with a set. After you finish writing code, ask just once: "is this the right basket?" This retrospective builds data-structure instinct fastest.
And today’s four baskets are the ingredients of log analysis. Splitting a log line (split), aggregating by IP (dictionary), counting unique visitors (set) — all are jobs for today’s tools. The subject of such analysis is always logs from your own system or legally provided data. ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime.
Once every box is checked, Step 42 is complete.