Python Standard Library Essentials: os, sys, math, random, datetime

You've built functions, organized code with modules, and written clean imports. But here's the thing, Python comes with a treasure chest of pre-built functionality that handles 80% of the tasks you'll encounter daily. We're talking about the Standard Library: that massive collection of modules ready to use without installing anything.
The problem? The Standard Library is enormous. Over 200 modules. Overwhelming, right?
In this article, we're going straight for the 20% that matters. We'll learn the five modules you'll actually use constantly: os, sys, math, random, and datetime. By the end, you'll handle file paths, process command-line arguments, do math operations, generate random data, and work with dates and times like a pro.
Let's dive in.
Table of Contents
- Why the Standard Library Matters
- os and sys: Your System Interface
- The os Module: Working with Files and Directories
- Getting Your Current Directory
- Listing Files in a Directory
- Checking if Paths Exist
- Working with Paths
- Creating and Removing Directories
- Environment Variables
- The sys Module: Arguments and System Info
- Command-Line Arguments
- Python Version and System Info
- Exiting Your Program
- Modifying Python's Path
- The math Module: Beyond Basic Arithmetic
- Constants
- Rounding and Absolute Values
- Powers and Roots
- Trigonometry
- The random Module: Generating Random Data
- Picking Random Items
- Shuffling a List
- Sampling Without Replacement
- Random Numbers
- Seeding for Reproducibility
- Random Number Best Practices
- The datetime Module: Dates, Times, and Timezones
- Creating Dates
- Creating Times
- Combining Date and Time
- Time Differences with timedelta
- Parsing Strings to Dates
- Converting Dates Back to Strings
- Timezone Awareness (Brief Preview)
- datetime Pitfalls
- Putting It Together: A Real-World Example
- A Modern Alternative: pathlib (Preview of Article 32)
- Where to Find the Docs
- Summary
- Putting It All Together: The Bigger Picture
Why the Standard Library Matters
Before we jump into the modules themselves, it's worth pausing to understand why the Standard Library is such a big deal, and why learning it changes how you code.
When you install Python, you are not just getting an interpreter. You are getting a battle-tested toolkit that took decades to build and refine. The Standard Library is the result of thousands of developers solving the same problems over and over, file manipulation, network requests, date calculations, cryptography, serialization, and then encoding those solutions into modules that ship with every Python installation on the planet. That is a significant inheritance.
The practical implication is this: before you reach for a third-party package from PyPI, you should ask whether the Standard Library already handles it. Nine times out of ten, the answer is yes. And the Standard Library wins in ways that matter in production: it has no version conflicts, no dependency chain, no installation step, and no licensing surprises. You just import it and go. For learners, this means you can write genuinely useful programs, scripts that interact with your file system, parse dates, generate test data, run as command-line tools, without a single pip install. That is a powerful position to be in from day one.
The five modules we are covering here are a carefully chosen core. Every professional Python developer uses os, sys, math, random, and datetime regularly. Master these, and you will have a foundation you can build anything on.
os and sys: Your System Interface
Let's be clear about something before we get into the code examples: os and sys are not abstract utilities. They are your direct interface to the operating environment your code runs in.
The os module is your bridge to the underlying operating system, the filesystem, processes, environment variables, and hardware abstractions that sit beneath Python. When your script needs to know where it is running, what files exist nearby, whether a directory needs to be created, or what environment variable holds a secret API key, os is the module you call. It abstracts away the differences between Windows, macOS, and Linux, which is essential if you ever want your code to run anywhere other than your own machine.
The sys module, by contrast, is your interface to the Python interpreter itself. It lets you inspect and influence how Python is running your code right now. What version of Python is active? What directories does it search for modules? What arguments did the user pass on the command line? How should the program terminate? All of these live in sys. Together, os and sys give you the environmental awareness that separates scripts that only work under perfect conditions from programs that are genuinely robust.
Think of os as your connection to the machine and sys as your connection to the Python process running on that machine. You need both.
The os Module: Working with Files and Directories
The os module is your gateway to interacting with the operating system. Need to list files? Check if a path exists? Create directories? The os module does that.
Getting Your Current Directory
Every Python script runs from somewhere. Let's see where:
import os
current_dir = os.getcwd()
print(current_dir)Output:
C:\Users\yourname\Projects
(On macOS/Linux, you'd see something like /Users/yourname/Projects.)
The os.getcwd() function returns your current working directory, where Python looks for files by default. This is crucial when you're opening files without specifying a full path. A surprising number of bugs come from code that assumes the working directory is where the script lives, when it is actually wherever the user launched the script from. Knowing os.getcwd() helps you avoid that trap entirely.
Listing Files in a Directory
Want to see what's in a folder?
import os
files = os.listdir('.')
print(files)Output:
['main.py', 'config.txt', 'data', 'utils.py', '.gitignore']
The argument '.' means "current directory." You can pass any path:
import os
files = os.listdir('C:/Users/yourname/Documents')
print(files)Notice that os.listdir() returns both files and subdirectories as strings, with no distinction between them. If you need to know which items are files and which are folders, you will want to follow up with os.path.isfile() or os.path.isdir(), which we will cover next.
Checking if Paths Exist
Before you try to open a file, you should check if it exists. Otherwise, you'll crash:
import os
path = 'myfile.txt'
if os.path.exists(path):
print("File found!")
else:
print("File not found.")You can also check specifically for files or directories:
import os
if os.path.isfile('myfile.txt'):
print("It's a file")
if os.path.isdir('myfolder'):
print("It's a directory")These checks are not just defensive programming, they are the foundation of any script that interacts with user-provided paths. If you are building a tool that accepts a file path as input, always validate before proceeding.
Working with Paths
Here's where things get tricky. File paths look different on Windows, macOS, and Linux:
- Windows:
C:\Users\name\Documents\file.txt - macOS/Linux:
/Users/name/Documents/file.txt
The backslash vs. forward slash nightmare. That's where os.path.join() saves your life:
import os
# This works on ANY operating system
path = os.path.join('Documents', 'Projects', 'myfile.txt')
print(path)Output (Windows):
Documents\Projects\myfile.txt
Output (macOS/Linux):
Documents/Projects/myfile.txt
Use os.path.join() every single time you build a path. It handles the slashes for you. This one habit will save you from cryptic path errors when your code gets deployed on a server that runs a different operating system than your development machine.
Creating and Removing Directories
Need to create a folder?
import os
os.mkdir('newfolder')
print("Folder created!")If the parent directory doesn't exist, you'll get an error. Use os.makedirs() instead to create nested directories:
import os
os.makedirs('data/raw/processed', exist_ok=True)
print("Nested directories created!")The exist_ok=True flag means "don't crash if the directory already exists." This is something you will want in almost every use case, without it, running your script twice would crash on the second run.
To remove a directory (it must be empty):
import os
os.rmdir('newfolder')
print("Folder removed!")Environment Variables
Your operating system stores configuration in environment variables. Want to know your username? Access an API key from your environment?
import os
username = os.getenv('USERNAME')
print(f"Hello, {username}!")Output:
Hello, alice!
On macOS/Linux, use USER instead of USERNAME. For cross-platform code:
import os
username = os.getenv('USERNAME') or os.getenv('USER')
print(f"Hello, {username}!")You can also set environment variables (though they only persist for your current script):
import os
os.environ['MY_VAR'] = 'some_value'
print(os.getenv('MY_VAR'))Output:
some_value
In production, environment variables are the standard way to pass secrets, database passwords, API tokens, configuration flags, into applications without hardcoding them in your source code. The pattern os.getenv('API_KEY') is something you will write thousands of times in your career.
The sys Module: Arguments and System Info
The sys module gives you hooks into Python itself and your system environment.
Command-Line Arguments
Remember we said everything Python runs from a command line (even if you don't see it)? Users can pass arguments to your script:
python script.py arg1 arg2 arg3To access those arguments in your code:
import sys
print(sys.argv)Output (if you ran python script.py hello world):
['script.py', 'hello', 'world']
Notice that sys.argv is a list. The first element is always the script name. The arguments come after:
import sys
if len(sys.argv) < 2:
print("Usage: python script.py <name>")
else:
name = sys.argv[1]
print(f"Hello, {name}!")Run it:
python script.py AliceOutput:
Hello, Alice!
This is how command-line tools work. You're building the foundation for real software. Every popular command-line utility you use, git, docker, npm, processes arguments through exactly this mechanism. Mastering sys.argv is how you start writing tools that other developers and users will actually want to run.
Python Version and System Info
Need to know which Python version is running?
import sys
print(sys.version)
print(sys.version_info)Output:
3.12.1 (main, Dec 7 2024, 09:00:00)
[GCC 12.2.0]
version_info(major=3, minor=12, micro=1, releaselevel='final', serial=0)
The sys.version_info tuple is useful for version-specific logic:
import sys
if sys.version_info.major < 3:
print("You're on Python 2! Upgrade!")
else:
print("You're on Python 3. Good!")This kind of check becomes important when you are maintaining code that needs to run on different Python versions, or when you are writing a library that other developers will use in their own environments.
Exiting Your Program
Want to stop execution immediately?
import sys
print("Starting process...")
if something_wrong:
print("Error detected. Exiting.")
sys.exit(1)
print("This won't run if we exited above")The argument to sys.exit() is the exit code:
0means success- Non-zero (usually
1) means an error occurred
This is important for automation and CI/CD pipelines, they check exit codes to know if your script succeeded. When you call sys.exit(1), you are communicating to the calling process, whether that is a shell script, a deployment pipeline, or another program, that something went wrong and it should take appropriate action.
Modifying Python's Path
Occasionally, you need to import a module from a non-standard location. You can add directories to Python's search path:
import sys
sys.path.append('/custom/module/path')
# Now Python will look in /custom/module/path for imports
import my_custom_moduleThe math Module: Beyond Basic Arithmetic
Python's built-in operators (+, -, *, /) handle everyday math. But for advanced calculations, you need math.
Constants
The math module provides mathematical constants:
import math
print(math.pi)
print(math.e)
print(math.tau) # 2 * piOutput:
3.141592653589793
2.718281828459045
6.283185307179586
These are useful for geometry and scientific calculations. Having math.pi available with full floating-point precision means you never need to hardcode an approximation like 3.14 and risk accumulated rounding errors in your calculations. The same applies to math.e, which is the base of natural logarithms and appears constantly in machine learning and statistical models.
Rounding and Absolute Values
import math
print(math.ceil(4.2)) # Round UP
print(math.floor(4.9)) # Round DOWN
print(math.fabs(-10)) # Absolute valueOutput:
5
4
10.0
Why not just use int() or round()? Because math functions are more precise and predictable for scientific work. The built-in round() uses banker's rounding, which can produce surprising results when you are building financial or scientific applications. math.ceil() and math.floor() are unambiguous and do exactly what they say.
Powers and Roots
import math
print(math.pow(2, 3)) # 2^3
print(math.sqrt(16)) # Square root of 16
print(math.log(100, 10)) # Log base 10 of 100
print(math.log(8, 2)) # Log base 2 of 8Output:
8.0
4.0
2.0
3.0
When you start working in AI and machine learning, you will encounter math.log constantly, loss functions, entropy calculations, and information-theoretic measures are all built on logarithms. Getting comfortable with this function now pays dividends later.
Trigonometry
import math
angle_degrees = 45
angle_radians = math.radians(angle_degrees)
print(math.sin(angle_radians))
print(math.cos(angle_radians))
print(math.tan(angle_radians))Output:
0.7071067811865476
0.7071067811865475
1.0
Yes, sine and cosine expect radians, not degrees. That's why math.radians() exists, to convert between them. This trips up beginners every time they use the trig functions directly with degree values and get seemingly nonsensical outputs. Always convert first.
The random Module: Generating Random Data
Need to pick a random item? Generate fake data? The random module is your friend.
Picking Random Items
import random
colors = ['red', 'green', 'blue', 'yellow']
chosen = random.choice(colors)
print(chosen)Output (example):
blue
Every time you run it, you get a different color. random.choice() picks one item from a list. This is one of those deceptively simple functions that shows up everywhere, games, simulations, data augmentation pipelines, randomized testing, A/B experiment setups.
Shuffling a List
import random
cards = ['A', 'K', 'Q', 'J', '10']
random.shuffle(cards)
print(cards)Output (example):
['10', 'A', 'Q', 'K', 'J']
Important: shuffle() modifies the list in place. It doesn't return a new list. This is a common source of bugs, if you write shuffled = random.shuffle(my_list), you will end up with None in shuffled and a mutated my_list. Always shuffle the list directly, then use it.
Sampling Without Replacement
Need to pick multiple items without repeats?
import random
players = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve']
winners = random.sample(players, 3)
print(winners)Output (example):
['Carol', 'Eve', 'Alice']
random.sample() picks items without replacement. No duplicates. This is different from calling choice() three times (which could repeat). When you need a subset of a population without repetition, drawing lottery numbers, selecting test cases, bootstrapping a dataset, random.sample() is the right tool.
Random Numbers
Generate integers:
import random
num = random.randint(1, 10)
print(num)Output (example):
7
Generate floats:
import random
num = random.random() # Between 0.0 and 1.0
print(num)Output (example):
0.5728263569779855
random.random() returns a float between 0.0 (inclusive) and 1.0 (exclusive). This is the building block for probability simulations, Monte Carlo methods, and any algorithm that needs a uniform random number between 0 and 1.
Seeding for Reproducibility
Here's the catch with randomness: it's not actually random. It's pseudorandom. Python uses a seed, a starting point, to generate "random" sequences. Same seed = same sequence.
This is useful for testing and debugging:
import random
random.seed(42)
print(random.randint(1, 100))
print(random.randint(1, 100))
# Reset to the same seed
random.seed(42)
print(random.randint(1, 100)) # Same as first call above
print(random.randint(1, 100)) # Same as second call aboveOutput:
82
74
82
74
Every time you set seed(42), you get the same sequence. This is gold for reproducible research and testing. In machine learning especially, seeding is considered essential practice, if your model training involves any randomness (and it almost always does), setting a seed means you can reproduce your results exactly, share them with colleagues, and debug failures. The number 42 is a community convention, but any integer works.
Random Number Best Practices
The random module is powerful, but there is one critically important limitation you need to know: never use random for security-sensitive tasks.
The reason is that random uses a deterministic pseudorandom number generator, the Mersenne Twister algorithm. It is excellent for simulations, games, and data shuffling, but it is not cryptographically secure. An attacker who observes enough outputs from random can potentially predict future outputs. For anything involving passwords, tokens, session IDs, or cryptographic keys, you must use Python's secrets module instead.
Here is the rule of thumb: if the random value will ever be seen by an adversary or used to make a security decision, use secrets. If it is for simulations, testing, games, or data science work, random is appropriate.
For reproducibility in data science and machine learning, always set a seed at the top of your script or notebook. Use a clearly named constant, RANDOM_SEED = 42 is the convention, and pass it wherever seeds are accepted. Libraries like NumPy and scikit-learn have their own random state, so you will need to seed those separately.
When generating random floats for probability distributions beyond uniform, look at random.gauss() for Gaussian distributions, random.expovariate() for exponential distributions, and random.betavariate() for beta distributions. These show up constantly in statistical modeling.
Finally, if you are generating large amounts of random data for machine learning, think random weight initialization, data augmentation, or Monte Carlo sampling, consider using NumPy's random module (numpy.random) instead. It operates on entire arrays at once and is dramatically faster than Python's random module for bulk generation.
The datetime Module: Dates, Times, and Timezones
Working with dates and times is trickier than it seems. Time zones, leap seconds, different calendar systems... the datetime module handles it.
Creating Dates
from datetime import date
today = date.today()
print(today)
birthday = date(1995, 3, 15)
print(birthday)Output:
2025-02-25
1995-03-15
The date.today() function gives you today's date. You can also create specific dates with date(year, month, day). Date objects are immutable, meaning you cannot modify them in place, any operation that produces a new date returns a new date object rather than changing the original. This makes them safe to pass around without worrying about accidental mutation.
Creating Times
from datetime import time
morning = time(8, 30, 0) # 8:30:00 AM
print(morning)
afternoon = time(14, 45) # 2:45 PM (seconds default to 0)
print(afternoon)Output:
08:30:00
14:45:00
Combining Date and Time
from datetime import datetime
now = datetime.now()
print(now)
specific = datetime(2025, 12, 25, 18, 30, 0)
print(specific)Output:
2025-02-25 14:32:10.123456
2025-12-25 18:30:00
datetime objects combine both date and time. They're what you'll use most often. When you are logging events, timestamping records, or comparing when things happened, datetime is your primary type. The vast majority of real-world datetime work uses datetime.now() as a starting point.
Time Differences with timedelta
Need to calculate durations or add/subtract time?
from datetime import datetime, timedelta
now = datetime.now()
tomorrow = now + timedelta(days=1)
last_week = now - timedelta(weeks=1)
print(f"Now: {now}")
print(f"Tomorrow: {tomorrow}")
print(f"Last week: {last_week}")Output:
Now: 2025-02-25 14:32:10.123456
Tomorrow: 2025-02-26 14:32:10.123456
Last week: 2025-02-18 14:32:10.123456
timedelta represents a duration. You can specify days, seconds, microseconds, milliseconds, minutes, hours, or weeks. It's incredibly flexible. You can also subtract two datetime objects from each other to get a timedelta back, which lets you calculate elapsed time between two events, such as how long a process ran or how many days remain until a deadline.
Parsing Strings to Dates
Often you'll receive dates as strings (from APIs, files, user input). Parse them:
from datetime import datetime
date_string = "2025-12-25 18:30:00"
parsed = datetime.strptime(date_string, "%Y-%m-%d %H:%M:%S")
print(parsed)
print(type(parsed))Output:
2025-12-25 18:30:00
<class 'datetime.datetime'>
The strptime() function converts a string to a datetime object using a format code. Here's what those codes mean:
%Y= 4-digit year%m= 2-digit month%d= 2-digit day%H= Hour (24-hour)%M= Minute%S= Second
If your date string looks different, adjust the format:
from datetime import datetime
date_string = "03/15/1995"
parsed = datetime.strptime(date_string, "%m/%d/%Y")
print(parsed)Output:
1995-03-15 00:00:00
The format string must match the input string exactly, even a difference in separator characters (- versus /) will raise a ValueError. When you are parsing dates from external data sources, wrapping strptime() in a try/except block is strongly recommended, because real-world date data is notoriously messy.
Converting Dates Back to Strings
from datetime import datetime
now = datetime.now()
formatted = now.strftime("%B %d, %Y at %I:%M %p")
print(formatted)Output:
February 25, 2025 at 02:32 PM
strftime() (string from time) does the opposite of strptime(). Use the same format codes to create readable strings. This is what you reach for when you need to display a datetime to a user or write it to a log file in a human-readable format.
Timezone Awareness (Brief Preview)
This is advanced, but worth knowing: by default, datetime objects are naive, they don't know about time zones. For now:
from datetime import datetime, timezone
# Naive (no timezone)
now_naive = datetime.now()
print(now_naive.tzinfo) # None
# Aware (has timezone)
now_aware = datetime.now(timezone.utc)
print(now_aware)
print(now_aware.tzinfo)Output:
None
2025-02-25 14:32:10.123456+00:00
<class 'datetime.timezone'>
If you're building apps that span time zones, use aware datetimes. For local scripts, naive usually works fine. However, note that mixing naive and aware datetimes in the same comparison will raise a TypeError, Python will refuse to compare them rather than silently doing the wrong thing. This is actually good behavior, because comparing local time to UTC without converting would produce incorrect results.
datetime Pitfalls
Working with dates and times is where a lot of bugs hide, even for experienced developers. Let us go through the traps so you can avoid them.
The first and most common pitfall is the naive versus aware datetime problem. When you call datetime.now(), you get a naive datetime, a timestamp with no timezone information attached. If you store this in a database, then later compare it to a timestamp from a different machine in a different timezone, you will get wrong answers. The fix is to always work in UTC internally. Use datetime.now(timezone.utc) instead of datetime.now(), store UTC timestamps, and only convert to local time when displaying to users.
The second pitfall is strptime format mismatches. The format string must match the input exactly. A date that looks like 2025-2-5 (no zero-padding) will not parse with %Y-%m-%d, it requires %Y-%-m-%-d on Linux or special handling on Windows. Always validate your format string against real data before shipping.
The third pitfall is mutable default arguments with datetime. This is a Python-level trap, but it stings hardest with datetime: never use a datetime as a default argument in a function definition. Because default values are evaluated once at function definition time, all calls to that function would share the same datetime object. Use None as the default and create the datetime inside the function body.
The fourth pitfall is year-end and month-end arithmetic. Adding one month to January 31 should give February 28 (or 29 in a leap year), but timedelta only works with absolute durations like days, not relative units like months. For calendar-aware arithmetic, look into the dateutil library from PyPI. It handles edge cases that timedelta cannot.
Putting It Together: A Real-World Example
Let's build a simple script that combines these modules. We'll create a file backup utility that copies files from today and logs what it did:
import os
import sys
from datetime import datetime
import random
# Get backup destination from command line
if len(sys.argv) < 2:
print("Usage: python backup.py <source_directory>")
sys.exit(1)
source_dir = sys.argv[1]
# Check if source exists
if not os.path.isdir(source_dir):
print(f"Error: {source_dir} doesn't exist")
sys.exit(1)
# Create backup directory with timestamp
backup_dir = os.path.join('backups', datetime.now().strftime('%Y-%m-%d_%H%M%S'))
os.makedirs(backup_dir, exist_ok=True)
# List and backup files
files = os.listdir(source_dir)
backed_up = 0
for filename in files:
source_file = os.path.join(source_dir, filename)
# Only backup regular files, not directories
if os.path.isfile(source_file):
# Copy using read/write (simple version)
try:
with open(source_file, 'r') as src:
content = src.read()
with open(os.path.join(backup_dir, filename), 'w') as dst:
dst.write(content)
backed_up += 1
except Exception as e:
print(f"Could not backup {filename}: {e}")
print(f"Backed up {backed_up} files to {backup_dir}")Run it:
python backup.py ./documentsOutput:
Backed up 5 files to backups/2025-02-25_143210
This script demonstrates:
sys.argvfor command-line argumentsos.path.isdir()andos.path.isfile()to check pathsdatetime.now().strftime()to create timestampsos.path.join()for cross-platform pathsos.makedirs()to create the backup directoryos.listdir()to iterate over files
Notice how the modules work together naturally. Each one handles a different layer of the problem: sys gives us the input, os manages the filesystem, and datetime creates the timestamp. This kind of composition, using small, focused modules together, is exactly how professional Python code is structured.
A Modern Alternative: pathlib (Preview of Article 32)
Before we wrap up, here's a heads-up: pathlib is the modern replacement for os.path. It's object-oriented and more elegant:
from pathlib import Path
# Create a Path object
current_dir = Path.cwd()
print(current_dir)
# Join paths (no more os.path.join!)
new_path = current_dir / 'documents' / 'myfile.txt'
print(new_path)
# Check if exists
if new_path.exists():
print("Found it!")
# List files
for file in current_dir.glob('*.py'):
print(file)pathlib is cleaner, but os is still widely used and frankly, you need to know it. Once you've mastered os, pathlib will feel intuitive. We'll dive deep in Article 32.
Where to Find the Docs
You don't need to memorize everything. Here's how to find the official documentation:
- Python Official Docs: https://docs.python.org/3/library/
- Quick Reference: Search "[module_name] python docs"
- In Your Terminal: Type
python -m pydoc os(or any module)
The standard library is vast, but these five modules, os, sys, math, random, datetime, handle the vast majority of practical tasks.
Summary
You now know:
- os: File paths, directories, environment variables, the OS interface
- sys: Command-line arguments, Python version, exit codes
- math: Constants, rounding, powers, trigonometry
- random: Choices, shuffles, random numbers, seeding
- datetime: Dates, times, parsing, formatting, durations
These modules are your daily bread. Master them, and you'll write confident, professional code. The Python standard library has your back.
Putting It All Together: The Bigger Picture
We have covered a lot of ground in this article, and it is worth stepping back to see the full picture before you move on.
The five modules you learned here are not isolated tools. They form a cohesive vocabulary for interacting with the real world from Python. Every time your program needs to know where it is running, it calls os. Every time it needs to accept input from the terminal or shut down gracefully, it calls sys. Every time it needs precise numerical computation, it calls math. Every time it needs to introduce controlled randomness into simulations, tests, or data generation, it calls random. And every time it needs to reason about when things happen, logging, scheduling, measuring duration, it calls datetime.
In the next article, you will build a CLI Expense Tracker that ties all of these concepts together in a working application. You will accept command-line arguments with sys, manage data files with os, format report dates with datetime, and use math for the arithmetic. When you sit down to write that project and find yourself reaching for these modules instinctively, that is how you will know the material from this article has truly stuck.
The Standard Library is one of Python's greatest gifts to developers. It is well-documented, well-tested, and available everywhere Python runs. Make it your habit to check whether the Standard Library handles something before looking for a third-party solution. You will write cleaner, more portable, and more maintainable code as a result.
Keep building.