Python Modules and Packages | import, pip, and Virtual Environments Explained
이 글의 핵심
Hands-on guide to Python modules and packages: import patterns, pip, standard library highlights, virtual environments, and project layout.
Introduction
“Split code into modules”
Modules and packages are how you structure code and reuse it across projects.
1. What is a module?
Definition
A module is a .py file containing Python code. You can put functions, classes, and variables in a module and import them elsewhere.
Why use modules?
- Reuse the same code in many places
- Namespaces to avoid name clashes
- Maintenance by splitting features into files
Creating a module
Example: a small math utilities module
# math_utils.py (module file)
"""
Math utility functions.
"""
def add(a, b):
"""Return a + b."""
return a + b
def multiply(a, b):
"""Return a * b."""
return a * b
def power(base, exponent):
"""Return base ** exponent."""
return base ** exponent
# Module-level constants
PI = 3.14159
E = 2.71828
# Runs only when this file is executed directly
if __name__ == '__main__':
print("math_utils self-test")
print(f"2 + 3 = {add(2, 3)}")
print(f"2 * 3 = {multiply(2, 3)}")
Using a module
# main.py (imports the module)
import math_utils
# Call functions on the module
result1 = math_utils.add(3, 5)
print(result1) # 8
result2 = math_utils.multiply(4, 7)
print(result2) # 28
# Module constants
print(math_utils.PI) # 3.14159
# Circle area
radius = 5
area = math_utils.PI * math_utils.power(radius, 2)
print(f"Area of circle with radius {radius}: {area}") # 78.53975
How __name__ works
# math_utils.py
print(f"__name__ = {__name__}")
if __name__ == '__main__':
print("This module was run as the main program")
else:
print("This module was imported")
# When you run `python math_utils.py`:
# __name__ = __main__
# This module was run as the main program
# When you `import math_utils`:
# __name__ = math_utils
# This module was imported
Module search path
Python looks for modules in this order:
import sys
for path in sys.path:
print(path)
# Typical order:
# 1. Directory of the script you run
# 2. Entries from PYTHONPATH
# 3. Standard library directories
# 4. site-packages (third-party installs from pip)
# Add a path at runtime
sys.path.append('/custom/path')
2. import syntax
Four common patterns
There are four main ways to import. Each has trade-offs—pick what fits the situation.
1) Import the whole module
import math
print(math.sqrt(16)) # 4.0
print(math.pi) # 3.141592653589793
print(math.factorial(5)) # 120
# Pros:
# - Clear provenance (math.sqrt comes from math)
# - Fewer name collisions
# Cons:
# - More typing (prefix every call)
When to use
- You call several functions from the module
- You want readers to see where names come from
2) Import specific names
from math import sqrt, pi, factorial
print(sqrt(16)) # 4.0 (no math. prefix)
print(pi)
print(factorial(5))
# Pros:
# - Shorter call sites
# - Import only what you need
# Cons:
# - Less obvious where names come from
# - Easier to shadow or clash names
Name clash example
from math import sqrt
from numpy import sqrt # shadows math.sqrt!
print(sqrt(16)) # numpy.sqrt behavior (may differ from math)
# Fix: use aliases
from math import sqrt as math_sqrt
from numpy import sqrt as np_sqrt
print(math_sqrt(16))
print(np_sqrt(16))
When to use
- You use a few names often and the context is obvious
3) Aliases
import math as m
import numpy as np
import pandas as pd
print(m.sqrt(16))
print(np.array([1, 2, 3]))
print(pd.DataFrame({'a': [1, 2]}))
# Pros:
# - Shorter names for long modules
# - Keeps namespace + less typing
# - Community conventions (np, pd) improve readability
# Cons:
# - Non-standard aliases confuse readers
Common aliases
import numpy as np # ✅ idiomatic
import pandas as pd # ✅ idiomatic
import matplotlib.pyplot as plt
import tensorflow as tf
import numpy as n # ❌ non-standard
import pandas as p # ❌ confusing
When to use
- Long module names used many times
- Libraries with a de-facto short name (numpy → np)
4) Star import (discouraged)
from math import *
print(sqrt(16))
print(pi)
# Pros:
# - Very short at call sites
# Cons:
# - Unclear origin of names
# - High collision risk
# - Harder code review
# - Conflicts with PEP 8 guidance
Problem scenario
from math import *
from statistics import *
# Both may define mean — which one wins?
result = mean([1, 2, 3, 4, 5])
When to use
- Almost never in production (maybe in a quick REPL session)
Comparison table
| Style | Syntax | Example | Pros | Cons | Typical use |
|---|---|---|---|---|---|
| Whole module | import math | math.sqrt(16) | Clear | Verbose | Many calls |
| From import | from math import sqrt | sqrt(16) | Short | Less clear | Few names |
| Alias | import math as m | m.sqrt(16) | Clear + short | Must remember alias | Long names |
| Star | from math import * | sqrt(16) | Shortest | Dangerous | Avoid |
Relative vs absolute imports
Absolute import: full path from the project/package root.
# Layout:
# myproject/
# ├── main.py
# └── mypackage/
# ├── __init__.py
# ├── utils.py
# └── core/
# └── engine.py
# In mypackage/core/engine.py
from mypackage.utils import helper # absolute
Relative import: relative to the current package.
# In mypackage/core/engine.py
from ..utils import helper # parent package
from . import config # same package
# . = current directory
# .. = parent
# ... = grandparent (rare)
Trade-offs
Absolute:
✅ Clear and stable
❌ Renaming the top-level package touches many imports
Relative:
✅ Easier when moving subpackages
❌ Fails if you run a module as a plain script (python engine.py)
Guideline: prefer absolute imports; use relative imports inside a package when it helps.
3. Packages
What is a package?
A package is a directory of modules. Traditionally an __init__.py file marks the directory as a package (and can run initialization code).
Why packages?
- Hierarchy for related modules
- Namespaces to avoid flat name clashes
- Distribution as a single installable unit
Example layout
myproject/
├── main.py
├── requirements.txt
└── mypackage/
├── __init__.py
├── module1.py
├── module2.py
└── subpackage/
├── __init__.py
└── module3.py
# Python 3.3+ can treat directories as namespace packages without __init__.py,
# but an explicit __init__.py is still common for regular packages.
Building a package
Step 1: create modules
# mypackage/math_ops.py
def add(a, b):
"""Addition."""
return a + b
def subtract(a, b):
"""Subtraction."""
return a - b
# mypackage/string_ops.py
def reverse(text):
"""Reverse a string."""
return text[::-1]
def capitalize_words(text):
"""Title-case each word."""
return ' '.join(word.capitalize() for word in text.split())
Step 2: __init__.py
# mypackage/__init__.py
"""
mypackage: small utility library.
"""
from .math_ops import add, subtract
from .string_ops import reverse, capitalize_words
__version__ = '1.0.0'
# __all__ controls `from mypackage import *`
__all__ = ['add', 'subtract', 'reverse', 'capitalize_words']
# Optional side effect on import
print(f"mypackage v{__version__} loaded")
Step 3: use the package
# main.py
import mypackage
print(mypackage.add(3, 5))
print(mypackage.reverse("Hello"))
from mypackage.math_ops import add
from mypackage.string_ops import reverse
print(add(10, 20))
print(reverse("Python"))
from mypackage import add, reverse
print(add(1, 2))
Roles of __init__.py
# 1) Empty file: marks directory as a package
# mypackage/__init__.py
# (empty)
# 2) Re-export symbols for shorter imports
# from .math_ops import add
# enables: from mypackage import add
# 3) Package initialization: logging, settings
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.info("package init")
# 4) __all__ lists the public API
__all__ = ['add', 'subtract']
Subpackages
myproject/
├── main.py
└── mypackage/
├── __init__.py
├── math_ops.py
├── string_ops.py
└── advanced/
├── __init__.py
├── statistics.py
└── algorithms.py
Using a subpackage
# mypackage/advanced/statistics.py
def mean(numbers):
"""Arithmetic mean."""
return sum(numbers) / len(numbers)
def median(numbers):
"""Median value."""
sorted_nums = sorted(numbers)
n = len(sorted_nums)
mid = n // 2
return sorted_nums[mid] if n % 2 == 1 else (sorted_nums[mid-1] + sorted_nums[mid]) / 2
# main.py
from mypackage.advanced.statistics import mean, median
data = [1, 2, 3, 4, 5]
print(f"mean: {mean(data)}")
print(f"median: {median(data)}")
4. The standard library
What is it?
The standard library ships with Python. No extra install—just import.
Frequently used modules
1) os — OS services
import os
print(os.getcwd())
os.chdir('/path/to/directory')
os.mkdir("new_folder")
os.makedirs("parent/child/grandchild")
print(os.path.exists('file.txt'))
print(os.path.isfile('file.txt'))
print(os.path.isdir('folder'))
path = os.path.join('folder', 'subfolder', 'file.txt')
print(os.environ.get('PATH'))
os.environ['MY_VAR'] = 'value'
files = os.listdir('.')
print(files)
2) datetime — dates and times
from datetime import datetime, timedelta, date, time
now = datetime.now()
print(now)
formatted = now.strftime("%Y-%m-%d %H:%M:%S")
print(formatted)
tomorrow = now + timedelta(days=1)
next_week = now + timedelta(weeks=1)
two_hours_ago = now - timedelta(hours=2)
print(f"Tomorrow: {tomorrow.strftime('%Y-%m-%d')}")
print(f"Next week: {next_week.strftime('%Y-%m-%d')}")
date_str = "2026-12-31 23:59:59"
parsed = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")
print(parsed)
today = date.today()
print(today)
current_time = time(14, 30, 0)
print(current_time)
3) random — random numbers
import random
print(random.randint(1, 10))
print(random.randrange(0, 10, 2))
print(random.random())
print(random.uniform(1.0, 10.0))
colors = ['red', 'blue', 'green', 'yellow']
print(random.choice(colors))
print(random.choices(colors, k=3))
print(random.sample(colors, k=2))
numbers = [1, 2, 3, 4, 5]
random.shuffle(numbers)
print(numbers)
random.seed(42)
print(random.randint(1, 100))
4) json — JSON
import json
data = {"name": "Alice", "age": 25, "hobbies": ["reading", "movies"]}
json_str = json.dumps(data, ensure_ascii=False, indent=2)
print(json_str)
parsed = json.loads(json_str)
print(parsed['name'])
print(type(parsed))
with open('data.json', 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
with open('data.json', 'r', encoding='utf-8') as f:
loaded = json.load(f)
print(loaded)
5) sys — interpreter and runtime
import sys
print(sys.version)
# python script.py arg1 arg2
print(sys.argv)
print(sys.path)
# sys.exit(0) # normal exit
# sys.exit(1) # error exit
6) pathlib — object-oriented paths
from pathlib import Path
current = Path.cwd()
print(current)
file_path = Path('folder') / 'subfolder' / 'file.txt'
print(file_path)
if file_path.exists():
print("exists")
file_path.write_text("Hello, World!", encoding='utf-8')
content = file_path.read_text(encoding='utf-8')
print(content)
print(file_path.name)
print(file_path.stem)
print(file_path.suffix)
print(file_path.parent)
for file in Path('.').glob('*.py'):
print(file)
5. pip and package management
What is pip?
pip installs and manages third-party packages from PyPI (the Python Package Index).
Common pip commands
pip install requests
# pip will:
# 1. Find the package on PyPI
# 2. Download a release
# 3. Install dependencies
# 4. Place files under site-packages
pip install requests==2.28.0
pip install "requests>=2.28.0"
pip install "requests>=2.28.0,<3.0.0"
pip install --upgrade requests
pip install -U requests
pip show requests
pip uninstall requests
pip uninstall -y requests
pip list
pip list --outdated
pip freeze > requirements.txt
pip install -r requirements.txt
requirements.txt format
# Pin exact versions (common in production)
requests==2.28.0
flask==2.3.0
pandas==2.0.0
# Minimum version
numpy>=1.24.0
# Range
django>=4.0.0,<5.0.0
# Web stack
flask==2.3.0
# Data stack
pandas==2.0.0
numpy==1.24.0
# Dev-only (often a separate file)
# requirements-dev.txt
pytest==7.3.0
black==23.3.0
flake8==6.0.0
Advanced pip usage
pip install -e .
# Editable install: changes in source are visible without reinstall
pip install git+https://github.com/user/repo.git
pip install git+https://github.com/user/repo.git@branch_name
pip install package-1.0.0-py3-none-any.whl
pip install requests flask pandas
pip install --no-cache-dir requests
pip install --user requests
pip troubleshooting
pip install --upgrade pip
pip install --trusted-host pypi.org --trusted-host files.pythonhosted.org requests
pip install --user requests
pip install --force-reinstall requests
pip check
6. Virtual environments (basics)
Using venv
python -m venv myenv
# Windows
myenv\Scripts\activate
# macOS / Linux
source myenv/bin/activate
pip install requests
deactivate
7. Virtual environments (in depth)
How venv works
python -m venv myenv
# Common names: venv, .venv, env
# Layout:
# myenv/
# ├── Scripts/ # Windows
# ├── bin/ # Unix
# ├── Lib/
# └── pyvenv.cfg
# Windows PowerShell
myenv\Scripts\Activate.ps1
# Windows CMD
myenv\Scripts\activate.bat
# macOS / Linux
source myenv/bin/activate
# Prompt shows (myenv) when active
which python # Unix
where python # Windows
# Points at the interpreter inside the environment
pip install requests flask pandas
pip list
pip freeze > requirements.txt
deactivate
Typical workflow
mkdir myproject
cd myproject
python -m venv venv
venv\Scripts\activate # Windows
pip install flask sqlalchemy pytest
pip freeze > requirements.txt
echo "venv/" >> .gitignore
git add .
git commit -m "Initial commit"
git clone <repo>
cd myproject
python -m venv venv
venv\Scripts\activate
pip install -r requirements.txt
Global vs virtual environment
| Global install | Virtual environment | |
|---|---|---|
| Location | Python install dir | Project directory |
| Isolation | Shared across projects | Per project |
| Version conflicts | Possible | Avoided |
| Recommended for | System tools | Application work |
8. Practical examples
Example 1: project skeleton
myproject/
├── venv/
├── src/
│ ├── __init__.py
│ ├── main.py
│ ├── config.py
│ ├── utils/
│ │ ├── __init__.py
│ │ ├── file_utils.py
│ │ └── string_utils.py
│ └── models/
│ ├── __init__.py
│ └── user.py
├── tests/
│ ├── __init__.py
│ ├── test_utils.py
│ └── test_models.py
├── docs/
│ └── README.md
├── .gitignore
├── requirements.txt
├── requirements-dev.txt
├── setup.py
└── README.md
Example 2: simple web scraper
web_scraper/
├── venv/
├── scraper/
│ ├── __init__.py
│ ├── main.py
│ ├── parser.py
│ └── storage.py
├── requirements.txt
└── README.md
# scraper/parser.py
import requests
from bs4 import BeautifulSoup
def fetch_page(url):
"""Download HTML for a URL."""
response = requests.get(url, timeout=10)
response.raise_for_status()
return response.text
def parse_titles(html):
"""Extract heading text from HTML."""
soup = BeautifulSoup(html, 'html.parser')
titles = [h2.text.strip() for h2 in soup.find_all('h2')]
return titles
# scraper/storage.py
import json
from pathlib import Path
def save_to_json(data, filename):
"""Write data to a JSON file."""
with open(filename, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
def load_from_json(filename):
"""Load JSON if the file exists."""
if not Path(filename).exists():
return None
with open(filename, 'r', encoding='utf-8') as f:
return json.load(f)
# scraper/__init__.py
"""
Web scraper package.
"""
from .parser import fetch_page, parse_titles
from .storage import save_to_json, load_from_json
__version__ = '1.0.0'
__all__ = ['fetch_page', 'parse_titles', 'save_to_json', 'load_from_json']
# scraper/main.py
from scraper import fetch_page, parse_titles, save_to_json
def main():
url = 'https://example.com'
print(f"Fetching: {url}")
html = fetch_page(url)
print("Parsing titles...")
titles = parse_titles(html)
save_to_json({'titles': titles}, 'output.json')
print(f"Saved {len(titles)} titles")
if __name__ == '__main__':
main()
requests==2.31.0
beautifulsoup4==4.12.0
python -m venv venv
venv\Scripts\activate
pip install -r requirements.txt
python -m scraper.main
9. Fixing import problems
Problem 1: ModuleNotFoundError
# ModuleNotFoundError: No module named 'mymodule'
# Cause 1: package not installed
# Fix: pip install mymodule
# Cause 2: path not on sys.path
import sys
print(sys.path)
sys.path.append('/path/to/module')
# Cause 3: wrong environment
import sys
print(sys.prefix)
Problem 2: Circular imports
# module_a.py
from module_b import func_b
def func_a():
return func_b()
# module_b.py
from module_a import func_a # circular!
# Fix 1: lazy import inside a function
def func_a():
from module_b import func_b
return func_b()
# Fix 2: shared module
# common.py with shared helpers
# Fix 3: refactor dependencies (best long-term)
Problem 3: Relative import errors
# ImportError: attempted relative import with no known parent package
# Cause: running a file inside a package directly
# python mypackage/module.py # bad
# Fix: run as a module
# python -m mypackage.module
# Or use absolute imports:
# from mypackage.utils import helper
Problem 4: ImportError vs ModuleNotFoundError
# ModuleNotFoundError: module missing entirely
# ImportError: module exists but the name does not
# from math import nonexistent_function
import math
print(dir(math))
10. Practical tips
1) Import order (PEP 8 style)
# Standard library
import os
import sys
from datetime import datetime
# Third party
import requests
import numpy as np
from flask import Flask
# Local application
from mypackage import utils
from mypackage.models import User
2) Optional dependencies
try:
import numpy as np
HAS_NUMPY = True
except ImportError:
HAS_NUMPY = False
print("NumPy not installed; using pure Python fallback.")
def process_data(data):
if HAS_NUMPY:
return np.array(data).mean()
return sum(data) / len(data)
3) Lazy imports
def process_image(image_path):
from PIL import Image # import only when needed
img = Image.open(image_path)
return img.resize((100, 100))
4) __all__
def public_func():
"""Public API."""
pass
def _private_func():
"""Internal by convention (_ prefix)."""
pass
__all__ = ['public_func']
# from mymodule import * # only public_func
5) Reloading during development
import importlib
import mymodule
importlib.reload(mymodule)
# Avoid in production; debugging only
11. Exercises
Exercise 1: small utility package
Create this layout and use it:
utils/
├── __init__.py
├── math_utils.py # add, multiply
└── text_utils.py # reverse, count_words
Exercise 2: requirements.txt
Write requirements.txt with:
- requests (2.31.0)
- flask (>=2.3.0,<3.0.0)
- pandas (latest)
Exercise 3: standard library
Use datetime and random to:
- Print the date 7 days from today
- Draw 10 unique random integers from 1–100
Answers
# utils/math_utils.py
def add(a, b):
return a + b
def multiply(a, b):
return a * b
# utils/text_utils.py
def reverse(text):
return text[::-1]
def count_words(text):
return len(text.split())
# utils/__init__.py
from .math_utils import add, multiply
from .text_utils import reverse, count_words
__all__ = ['add', 'multiply', 'reverse', 'count_words']
# main.py
from utils import add, reverse
print(add(3, 5))
print(reverse("Hello"))
requests==2.31.0
flask>=2.3.0,<3.0.0
pandas
from datetime import datetime, timedelta
import random
today = datetime.now()
after_7_days = today + timedelta(days=7)
print(after_7_days.strftime("%Y-%m-%d"))
random_numbers = random.sample(range(1, 101), k=10)
print(random_numbers)
Summary
Key takeaways
- Module: a
.pyfile; import to reuse code. - Package: directory of modules;
__init__.pyfor regular packages. - import styles: whole module, from-import, alias, star (avoid star).
- Standard library:
os,datetime,random,json,sys,pathlib, … - pip: install from PyPI; pin versions for reproducibility.
- Virtual environments: isolate dependencies per project (
venv). - requirements.txt: record dependencies for installs.
After you master the module system
- Reuse code more confidently
- Structure larger projects cleanly
- Collaborate with clearer boundaries
- Ship installs that match your environment
Next steps
- Classes and objects — OOP in Python
- Exception handling
- File handling