Code › codeit-ai-sprint

Python File I/O and OOP Fundamentals

Notes on Python sequences, file I/O, duplicate removal using sets, and OOP fundamentals covered in the second class of Codeit Sprint Main Course.

The second class of the Codeit Sprint Main Course focused on fundamental Python syntax. We explored sequence types like strings and tuples, file I/O, removing duplicates using set, and Object-Oriented Programming (OOP). This post covers the highlights of the lecture and the concepts I wanted to organize in more detail.


Variables Persist in Colab

While practicing in Colab during class, I ran into an error because variables remained in memory. In a Jupyter Notebook environment, cells are not just executed once from top to bottom; you can run middle cells multiple times or execute them out of order. As a result, variables, dictionaries, or lists created in previous cells persist in the kernel memory, which can affect subsequent executions.

When this happens, you need to restart the runtime or explicitly initialize the required variables.


Strings and Tuples Are Sequences Too

In Python, a string is also a sequence type. Looking into it further, a sequence is an abstract class explicitly defined within Python that supports indexing, slicing, the in operator, len(), and for loops. The official Python documentation also describes lists and strings as sequence types, which is why you can access individual characters in a string by index or slice specific segments.

text = "Python"

print(text[0])   # P
print(text[1:4]) # yth

Tuples are sequence types as well. They hold multiple values in order, similar to lists, but their elements cannot be changed once created. The official documentation defines tuples as immutable, distinguishing them from mutable lists. In other words, while you can modify values in a list, you cannot replace a value at a specific index in a tuple with a different one.

point = (3, 5)
# point[0] = 10  # TypeError

Tuple unpacking allows you to pack multiple values into a single tuple and then distribute them across multiple variables.

point = (3, 5)
x, y = point

File I/O and the with Statement

In today’s practice, we wrote code to read a vocabulary list from a file. The crucial takeaway here was that files must always be closed after being opened. Failing to close a file can keep operating system resources tied up. Furthermore, during write operations, data might remain stuck in the memory buffer without actually being saved to the physical file.

In Python, you can handle this safely using the with statement. The file is utilized inside the with block, and it is automatically cleaned up and closed once execution leaves the block.

with open("vocabulary.txt", "r", encoding="utf-8") as f:
    for line in f:
        print(line.strip())

The official Python documentation strongly recommends using the with keyword when handling files. It ensures that the file is properly closed when the block ends, even if an exception is raised mid-execution. It leaves much less room for error compared to manually calling close().

Regarding encoding, running the code without specifying an encoding worked fine in Colab, but it threw errors when solving problems on the Codeit platform. Because default encodings vary depending on the operating system or execution environment, reading or writing files that contain Unicode characters without specifying the encoding parameter can lead to crashes.

To look a bit deeper into how encoding works: Unicode characters are converted into unique numbers called code points from a standardized table. These code points must then be written to a file as bytes that a computer can read. The rules that determine how these code points are split into byte chunks for saving or loading are defined by standards like UTF-8 or UTF-16.

UTF-8 uses 1 byte for alphabets, numbers, and special characters, 3 bytes for languages like Korean, Chinese, and Arabic, and 4 bytes for emojis. On the other hand, UTF-16 uses 2 bytes for alphabets, numbers, Korean, and Chinese, and 4 bytes for emojis. Ultimately, if these rules do not match between writing and reading, proper file I/O becomes impossible, resulting in a decoding error.

Colab likely didn’t throw an error because its system default was already configured to UTF-8, which is the modern standard. In contrast, on Codeit, an error occurred because the system default encoding of their internal server did not match the actual encoding of the pre-provided vocabulary.txt file.

Now, let’s move on to the practical exercise. The core logic for the advanced vocabulary quiz application followed this workflow:

import random

vocab = {}

with open("vocabulary.txt", "r", encoding="utf-8") as f:
    for line in f:
        eng, kor = line.strip().split(":")
        vocab[eng] = kor

keys = list(vocab.keys())

while True:
    eng = keys[random.randint(0, len(keys) - 1)]
    answer = input(f"{vocab[eng]}: ")

    if answer == "q":
        break

    if answer == eng:
        print("Correct.")
    else:
        print(f"The answer is {eng}")

Here, line.strip().split(":") was used to strip trailing whitespace (like newline characters) from the end of the line, and then split the string into English and Korean words based on the colon. If you call split() without any arguments, it splits by whitespace by default—which includes regular spaces as well as newlines (\n) and tabs (\t). Therefore, when processing strings read from a file, it is vital to understand how strip() and split() evaluate boundaries.


Sets are Great for Removing Duplicates, But Not for Sorting

While working on a practice problem to remove duplicate items from a list, I became curious about how Python’s set behaves under the hood.

nums = [1, 2, 3, 2, 4, 1, 5, 3]
num_set = set(nums)
num_list = list(num_set)
print(num_list)

Converting a list into a set successfully filters out duplicates. However, a set is not a sorted data structure. While it might look sorted here because we are only dealing with small positive integers, this ordering guarantee completely breaks down when negative numbers or strings are introduced.

In Python’s hash table implementation, negative numbers have a very large offset added to their hash values when inserted into the hash table. This often pushes negative numbers behind positive ones. Consequently, the official Python documentation defines a set as an unordered collection of unique elements. You should never rely on the order of a list that was converted back from a set; you must sort it explicitly.

If a problem specifically requires a sorted list as the output, you should apply sorted() at the end.

nums = [1, 2, 3, 2, 4, 1, 5, 3]
result = sorted(set(nums))
print(result)

self, Instance Variables, and Class Variables

Finally, we dipped our toes into Object-Oriented Programming. I wanted to log a quick summary of what self represents, alongside the differences between instance variables and class variables.

In Python, the first parameter of an instance method is conventionally named self. Although you don’t manually pass an argument to self when calling a method, the object instance that invoked the method is automatically passed as the first argument behind the scenes.

class User:
    def __init__(self, name):
        self.name = name

    def greet(self):
        print(f"Hello, {self.name}")

user = User("Sean")
user.greet()

In this context, self.name is an instance variable belonging exclusively to each individual object. The string name is stored inside that specific user instance. Conversely, a class variable is attached directly to the class definition itself, making it shared and accessible across all instances.

class User:
    count = 0

    def __init__(self, name):
        self.name = name
        User.count += 1

In this example, name varies for each instance, but count belongs to the User class as a whole. Coming from a C++ background, a class variable serves a similar purpose to a static member variable. However, because Python allows attributes to be attached dynamically and executes method resolution on a runtime object model, equating it completely to C++ member function calls can be misleading. Since this topic is quite deep, I plan to dedicate a separate, detailed post to it.

There are also methods that do not take self as a parameter. The most common example is a static method. You can use them when a function doesn’t need to read or modify the state of an object (instance) or the class, but you still want to keep it scoped inside the class namespace.

class Math:
    @staticmethod
    def add(a, b):
        return a + b

In this case, add does not receive self. It doesn’t read or modify any state; it is simply a regular function cleanly grouped under the class namespace.


Wrapping Up the Second Class

Today’s class was a broad overview of Python syntax. We covered a massive amount of ground all at once—strings, tuples, sets, file I/O, and OOP. While programming languages share a lot of common ground, what really caught my attention today was digging into how things actually operate under the hood.

Revisiting foundational syntax makes me realize that once you start asking why a feature behaves the way it does, there is an incredible amount of depth to discover. In particular, writing a dedicated post comparing the behavioral nuances of instance variables, class variables, and static methods between Python and C++ seems like a great next step.