Package Structure
What is a Package?
A package is a directory containing Python modules and a special __init__.py file.
my_package/
├── __init__.py # Makes directory a package
├── module1.py # Module
├── module2.py # Another module
└── subpackage/ # Sub-package
├── __init__.py
└── module3.py
init.py
# my_package/__init__.py
# Can be empty (marks directory as package)
# Or export specific names
from .module1 import MyClass
from .module2 import my_function
# Control what 'from my_package import *' imports
__all__ = ['MyClass', 'my_function']
Relative vs Absolute Imports
# Relative imports (from same package)
from . import module1 # Same directory
from .subpackage import module3 # Sub-directory
from .. import other_package # Parent directory
# Absolute imports (recommended)
from my_package import module1
from my_package.subpackage import module3
# Best practice: Use absolute imports
# They're clearer and less prone to errors
Package Discovery
# Find packages in project
import pkg_resources
for dist in pkg_resources.working_set:
print(dist.key, dist.version)
# Or using importlib
import importlib
import importlib.metadata
for dist in importlib.metadata.distributions():
print(dist.metadata['Name'], dist.version)
pip Package Manager
Installing Packages
# Basic install
pip install requests
# Specific version
pip install requests==2.28.0
# Version constraints
pip install 'requests>=2.28,<3.0'
# Install from requirements.txt
pip install -r requirements.txt
# Install in development mode
pip install -e .
Managing Packages
# List installed packages
pip list
# Show package info
pip show requests
# Check for outdated packages
pip list --outdated
# Upgrade package
pip install --upgrade requests
# Uninstall
pip uninstall requests
Requirements Files
# Generate requirements.txt
pip freeze > requirements.txt
# Or using pipreqs (more accurate)
pipreqs . --force
# Install from requirements.txt
pip install -r requirements.txt
# Development requirements
pip install -r requirements-dev.txt
requirements.txt Example
# requirements.txt
requests==2.28.0
flask>=2.0,<3.0
python-dotenv==1.0.0
pytest>=7.0 # Comment: testing
# Install with:
# pip install -r requirements.txt
Package Indices
# PyPI (default)
pip install requests
# Private index
pip install --index-url https://pypi.mycompany.com/simple/ mypackage
# Multiple indices
pip install --extra-index-url https://pypi.mycompany.com/simple/ requests
Common pip Commands
# Search (deprecated, use pypi.org)
pip search requests
# Download without installing
pip download requests -d ./packages
# Install from local file
pip install ./requests-2.28.0.tar.gz
# Install from Git
pip install git+https://github.com/user/repo.git
# Install from specific branch
pip install git+https://github.com/user/repo.git@main
Creating Packages
Package Structure
my_package/
├── src/
│ └── my_package/
│ ├── __init__.py
│ ├── core.py
│ └── utils.py
├── tests/
│ ├── __init__.py
│ └── test_core.py
├── pyproject.toml
├── README.md
└── LICENSE
pyproject.toml (Modern)
# pyproject.toml
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "my-package"
version = "0.1.0"
description = "A short description"
readme = "README.md"
license = {text = "MIT"}
requires-python = ">=3.8"
dependencies = [
"requests>=2.28",
"click>=8.0",
]
[project.optional-dependencies]
development = [
"pytest>=7.0",
"black>=22.0",
"mypy>=0.900",
]
[project.scripts]
my-cli = "my_package.cli:main"
setup.py (Legacy)
# setup.py
from setuptools import setup, find_packages
setup(
name='my-package',
version='0.1.0',
packages=find_packages(where='src'),
package_dir={'': 'src'},
install_requires=[
'requests>=2.28',
'click>=8.0',
],
extras_require={
'dev': ['pytest>=7.0', 'black>=22.0'],
},
)
Building and Publishing
# Install build tools
pip install build twine
# Build package
python -m build
# Check package
twine check dist/*
# Upload to PyPI
twine upload dist/*
# Or test on TestPyPI first
twine upload --repository testpypi dist/*
Versioning
# Semantic Versioning: MAJOR.MINOR.PATCH
# 1.0.0 -> 1.0.1 (bug fix)
# 1.0.1 -> 1.1.0 (new feature)
# 1.1.0 -> 2.0.0 (breaking change)
# In pyproject.toml
[project]
version = "1.2.3"
# Or dynamic versioning
[tool.setuptools_scm]
Best Practices
# ✅ Do:
# - Use pyproject.toml (modern)
# - Pin exact versions in requirements.txt
# - Use virtual environments
# - Write tests
# - Document your package
# - Follow PEP 8
# ❌ Don't:
# - Use setup.py alone (legacy)
# - Commit .pyc files
# - Include sensitive data
# - Use wildcard imports
# - Ignore dependency conflicts