Matplotlib Basic Plots
This lesson is original documentation that covers the same general Python learning area as popular beginner tutorials. It does not copy third-party tutorial text or examples.
Learning Goal
Create simple line, bar, scatter, and histogram charts.
This topic belongs to 31. Python Data Science Intro. The goal is to understand the concept clearly, practice it with small examples, and know how it connects to practical Python work in automation, data, AI, and server scripts.
Concept Overview
Python favors readable code, explicit names, and small reusable pieces. When learning Matplotlib Basic Plots, focus on what values enter the program, what transformation happens, and what output or side effect is produced.
Good beginner Python habits:
- Use clear names instead of abbreviations.
- Keep examples small until the concept is understood.
- Print intermediate values while learning.
- Prefer simple control flow before clever one-liners.
- Validate input before trusting it.
Basic Example
def run_03_matplotlib_basic_plots_demo() -> None:
topic = "Matplotlib Basic Plots"
message = f"Practicing {topic}"
print(message)
run_03_matplotlib_basic_plots_demo()
Practical Example
from pathlib import Path
def write_learning_note(directory: Path, topic: str) -> Path:
directory.mkdir(parents=True, exist_ok=True)
note = directory / "python-notes.txt"
note.write_text(f"Today I practiced: {topic}\n", encoding="utf-8")
return note
path = write_learning_note(Path("practice-output"), "Matplotlib Basic Plots")
print(f"wrote {path}")
Syntax Notes
| Area | Guidance |
|---|---|
| Names | Use lowercase names with underscores for variables and functions |
| Blocks | Indentation defines code blocks |
| Values | Inspect types with type(value) while learning |
| Output | Use print() for learning and logging for real tools |
| Errors | Read tracebacks from bottom to top to find the direct failure |
Common Mistakes
| Mistake | Why It Happens | Better Habit |
|---|---|---|
| Mixing tabs and spaces | Editors may insert different whitespace | Configure the editor for 4 spaces |
| Reusing vague names | x and data hide meaning | Choose names that explain purpose |
| Ignoring errors | Tracebacks look noisy at first | Find the exception type and failing line |
| Copying without changing | The code runs but understanding stays shallow | Rewrite examples with your own values |
Practice Tasks
- Run the basic example and change the topic text.
- Add one more variable and print it.
- Wrap the logic in a function with a clear name.
- Add one invalid input and observe the error.
- Rewrite the example so it solves a small task from your own work.
Mini Quiz
- What input does the example receive?
- What output does it produce?
- Which line would you change first to adapt it?
- What error might happen if a path, value, or type is wrong?
Real-World Uses
- Automation scripts that process files or records
- Server checks that print clear status messages
- AI scripts that prepare prompts or validate model output
- Data tasks that transform values consistently
- CLI tools that accept arguments and report useful results
Next Step
Practice this concept in a new file, then combine it with the previous lesson. Small connected examples build stronger Python skill than isolated snippets.
Extended Examples
Example 1: Parameterized Validation
from dataclasses import dataclass
from typing import Optional
@dataclass(frozen=True)
class ValidationResult:
valid: bool
value: Optional[str] = None
error: Optional[str] = None
def validate_and_normalize(
value: str,
min_length: int = 1,
max_length: int = 100,
strip_whitespace: bool = True
) -> ValidationResult:
if strip_whitespace:
value = value.strip()
if not value:
return ValidationResult(valid=False, error="empty value after processing")
if len(value) < min_length:
return ValidationResult(valid=False, error=f"too short: {len(value)} < {min_length}")
if len(value) > max_length:
return ValidationResult(valid=False, error=f"too long: {len(value)} > {max_length}")
return ValidationResult(valid=True, value=value)
# Test different scenarios
test_cases = [" hello ", "a", "x" * 200, "", "validinput"]
for case in test_cases:
result = validate_and_normalize(case)
print(f"input={case!r} -> valid={result.valid}, error={result.error}")
Example 2: Structured Result with Context
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
class Status(Enum):
SUCCESS = "success"
FAILURE = "failure"
PARTIAL = "partial"
@dataclass(frozen=True)
class OperationResult:
status: Status
message: str
timestamp: datetime
details: dict
def is_success(self) -> bool:
return self.status == Status.SUCCESS
def to_dict(self) -> dict:
return {
"status": self.status.value,
"message": self.message,
"timestamp": self.timestamp.isoformat(),
"details": self.details,
}
def run_operation(value: str) -> OperationResult:
if not value or len(value) < 3:
return OperationResult(
status=Status.FAILURE,
message="value too short",
timestamp=datetime.now(),
details={"value_length": len(value) if value else 0}
)
return OperationResult(
status=Status.SUCCESS,
message="operation completed",
timestamp=datetime.now(),
details={"processed_length": len(value)}
)
result = run_operation("test")
print(result.to_dict())
Integration Patterns
Combining with argparse
import argparse
from dataclasses import dataclass
@dataclass(frozen=True)
class Config:
input_value: str
verbose: bool
output_file: str
def parse_args() -> Config:
parser = argparse.ArgumentParser(description="Process values with validation")
parser.add_argument("input_value", help="Value to process")
parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output")
parser.add_argument("-o", "--output", default="output.txt", help="Output file")
args = parser.parse_args()
return Config(
input_value=args.input_value,
verbose=args.verbose,
output_file=args.output
)
def main() -> int:
config = parse_args()
if config.verbose:
print(f"Processing: {config.input_value}")
print(f"Output to: {config.output_file}")
print(f"Result: {config.input_value.upper()}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Combining with logging
import logging
from dataclasses import dataclass
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s %(levelname)s %(message)s"
)
LOG = logging.getLogger(__name__)
@dataclass(frozen=True)
class LoggedResult:
topic: str
success: bool
detail: str
def process_with_logging(value: str) -> LoggedResult:
LOG.debug(f"Processing value: {value}")
if not value:
LOG.warning("Empty value received")
return LoggedResult(topic="process", success=False, detail="empty")
result = value.strip().upper()
LOG.info(f"Processed to: {result}")
return LoggedResult(topic="process", success=True, detail=result)
result = process_with_logging("hello")
LOG.debug(f"Final result: {result}")
Operational Considerations
When using this pattern in production environments:
- Environment Variables: Read configuration from environment variables with sensible defaults
- Exit Codes: Return non-zero exit codes when operations fail
- Error Messages: Write useful error messages to stderr, not stdout
- Dry Runs: Support
--dry-runflag for state-changing operations - Logging: Use structured logging with appropriate log levels
Production Snippet
import os
import sys
import logging
from pathlib import Path
LOGGING_LEVEL = os.environ.get("LOG_LEVEL", "INFO").upper()
logging.basicConfig(
level=getattr(logging, LOGGING_LEVEL, logging.INFO),
format="%(asctime)s %(levelname)s %(name)s: %(message)s"
)
LOG = logging.getLogger(__name__)
def get_working_directory() -> Path:
"""Get working directory with validation."""
cwd = os.environ.get("WORK_DIR")
if cwd:
path = Path(cwd)
if path.is_dir():
return path
LOG.warning(f"WORK_DIR not a directory: {cwd}")
return Path.cwd()
def main() -> int:
LOG.info("Starting application")
work_dir = get_working_directory()
LOG.info(f"Working directory: {work_dir}")
try:
# Main logic here
pass
except Exception as e:
LOG.error(f"Failed: {e}")
return 1
LOG.info("Completed successfully")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Extended Troubleshooting
| Problem | Likely Cause | Solution |
|---|---|---|
| Function not found | Import error or typo in name | Check imports and function definition |
| Type error on result | Wrong return type or None handling | Add type hints and handle None |
| File not found | Wrong path or permissions | Use absolute paths and check permissions |
| Tests fail | Edge cases not handled | Add more test cases and validation |
| Works locally, fails on server | Environment differences | Check Python version, paths, and dependencies |
Debugging Steps
- Run with
-vor--verboseflag - Print the input values and types
- Check the Python version:
python3 --version - Verify dependencies:
python3 -c "import module_name" - Run with dry-run mode if available
- Check logs for error messages
Additional Practice
- Modify the core pattern to accept multiple values
- Add a retry mechanism for failed operations
- Implement a config file loading pattern
- Add a simple progress indicator for long operations
- Create a CLI with subcommands using argparse
Field Notes
This pattern forms the foundation of reliable Python automation. The key principles:
- Explicit is better than implicit - Name variables and functions clearly
- Errors should never pass silently - Handle failures explicitly
- Readability counts - Code is read more than written
- Flat is better than nested - Avoid deep nesting
- Small is better - Many small functions are easier to test
Build confidence by applying this pattern to real automation tasks, then expand to handle more complex scenarios.
Summary
- Validate early, validate often
- Return structured results, not just values
- Log usefully without overwhelming
- Handle errors explicitly
- Test the failure paths, not just success