Python Fundamentals#
Information |
Details |
---|---|
Learning Objectives |
• Write Python programs using core language features |
Prerequisites |
Basic programming experience (MATLAB preferred) |
Estimated Time |
2 hours |
Topics |
Variables, data types, conditional statements, loops, functions, simple classes, error handling |
Introduction#
Python has become a dominant programming language in engineering and scientific computing. This lesson teaches Python fundamentals with a focus on helping MATLAB users transition smoothly. We’ll start with basic Python concepts and gradually introduce engineering applications.
It is our goal that, by the end of this lesson, you will be comfortable writing Python programs for engineering calculations. But just like learning other programming languages, practice is key.
For MATLAB Users
This lesson includes explicit comparisons to MATLAB throughout. Look for the “MATLAB Comparison” boxes to understand key differences and similarities.
MATLAB to Python Quick Reference#
If you’re coming from MATLAB, here are the critical differences you need to know:
Concept |
MATLAB |
Python |
Key Difference |
---|---|---|---|
Indexing |
1-based |
0-based |
First element is |
Code blocks |
|
Indentation |
Python uses indentation, not |
Arrays |
Everything is matrix |
Lists vs NumPy |
Use lists for general data, NumPy for math |
Comments |
|
|
Python uses |
Power |
|
|
|
Output |
Semicolon suppresses |
No semicolon needed |
Python doesn’t auto-display results |
Division |
|
|
|
Critical Warning: Zero-Based Indexing
The single biggest source of errors for MATLAB users is forgetting that Python uses 0-based indexing. The first element of a list is list[0]
, not list[1]
.
Getting Started with Simple Calculations#
Let’s start with a basic calculation that you might do in any engineering context. This will introduce Python’s syntax for variables, operations, and output.
Basic Arithmetic#
# Calculate electrical power
voltage = 120 # Volts
current = 10 # Amperes
power = voltage * current # Watts
print(f"Power: {power} W")
Power: 1200 W
Notice that:
Python doesn’t require variable type declarations
Comments use
#
instead of MATLAB’s%
We must explicitly
print()
to see output (no automatic display for assignment statements)
Using the Math Module#
import math
# Calculate power in AC circuit
voltage_rms = 120
current_rms = 10
phase_angle_deg = 30 # degrees
# Convert to radians and calculate
phase_angle_rad = math.radians(phase_angle_deg)
real_power = voltage_rms * current_rms * math.cos(phase_angle_rad)
print(f"Real power: {real_power:.1f} W")
print(f"Power factor: {math.cos(phase_angle_rad):.3f}")
Real power: 1039.2 W
Power factor: 0.866
Tip: F-strings for Formatting
Python’s f-strings (the f"..."
syntax) are the most readable way to format output. Use {variable:.Nf}
to display N decimal places.
Variables and Data Types#
Python provides several built-in data types for different kinds of information. Let’s explore the most common ones you’ll use in engineering calculations.
Numbers: Integers and Floats#
Python distinguishes between integers (whole numbers) and floating-point numbers (decimals).
# Integer values
num_components = 5
rated_voltage = 230 # V
# Floating-point values
resistance = 10.5 # Ohms
temperature = 25.3 # Celsius
# Check types
print(f"Type of num_components: {type(num_components)}")
print(f"Type of resistance: {type(resistance)}")
Type of num_components: <class 'int'>
Type of resistance: <class 'float'>
Type Conversion#
# Converting between types
voltage_string = "240"
voltage_int = int(voltage_string)
voltage_float = float(voltage_string)
print(f"String: {voltage_string} (type: {type(voltage_string)})")
print(f"Integer: {voltage_int} (type: {type(voltage_int)})")
print(f"Float: {voltage_float} (type: {type(voltage_float)})")
String: 240 (type: <class 'str'>)
Integer: 240 (type: <class 'int'>)
Float: 240.0 (type: <class 'float'>)
MATLAB Comparison: Variable Types
Unlike MATLAB where everything is a matrix by default, Python has distinct types for integers, floats, and strings. Python is dynamically typed - you don’t declare types, but they matter for operations.
Strings for Labels and Names#
# String variables
component_name = "Resistor_R1"
unit_label = "kW"
# String operations
full_label = component_name + " (10" + unit_label + ")"
print(full_label)
Resistor_R1 (10kW)
# String methods
equipment_id = "GEN_001_ACTIVE"
print(f"Lowercase: {equipment_id.lower()}")
print(f"Contains 'GEN': {'GEN' in equipment_id}")
print(f"First 7 characters: {equipment_id[:7]}")
Lowercase: gen_001_active
Contains 'GEN': True
First 7 characters: GEN_001
Lists: Collections of Values#
Lists are Python’s most versatile data structure for storing collections.
# Creating lists
measurements = [23.5, 24.1, 23.8, 24.3, 23.9] # Temperature readings
components = ["R1", "R2", "C1", "L1"] # Component names
print(f"Measurements: {measurements}")
print(f"Number of measurements: {len(measurements)}")
Measurements: [23.5, 24.1, 23.8, 24.3, 23.9]
Number of measurements: 5
List Operations#
# Basic operations
avg_temp = sum(measurements) / len(measurements)
max_temp = max(measurements)
min_temp = min(measurements)
print(f"Average: {avg_temp:.2f}°C")
print(f"Range: {min_temp}°C to {max_temp}°C")
Average: 23.92°C
Range: 23.5°C to 24.3°C
# Accessing elements (remember: 0-based indexing!)
print(f"First measurement: {measurements[0]}")
print(f"Last measurement: {measurements[-1]}")
print(f"Middle three: {measurements[1:4]}")
First measurement: 23.5
Last measurement: 23.9
Middle three: [24.1, 23.8, 24.3]
Warning: Zero-Based Indexing
Remember: Python uses 0-based indexing. The first element is at index 0, not 1. Negative indices count from the end: -1 is the last element.
Modifying Lists#
# Adding elements
measurements.append(24.5) # Add to end
print(f"After append: {measurements}")
# Removing elements
measurements.pop() # Remove last
print(f"After pop: {measurements}")
After append: [23.5, 24.1, 23.8, 24.3, 23.9, 24.5]
After pop: [23.5, 24.1, 23.8, 24.3, 23.9]
Dictionaries: Named Values#
Dictionaries store data with named keys, similar to MATLAB structures.
# Creating a dictionary
circuit = {
"voltage": 12,
"current": 2.5,
"resistance": 4.8,
"power": 30
}
print(f"Voltage: {circuit['voltage']} V")
print(f"Current: {circuit['current']} A")
Voltage: 12 V
Current: 2.5 A
# Adding and modifying values
circuit["frequency"] = 60 # Add new key
circuit["power"] = circuit["voltage"] * circuit["current"] # Update
print(f"Updated power: {circuit['power']} W")
print(f"All keys: {list(circuit.keys())}")
Updated power: 30.0 W
All keys: ['voltage', 'current', 'resistance', 'power', 'frequency']
MATLAB Comparison: Dictionaries vs Structures
Python dictionaries are like MATLAB structures but use bracket notation (dict['key']
) instead of dot notation (struct.field
). Keys must be strings (or other immutable types).
Exercise 1: Working with Data Types#
Create a dictionary called component
to represent an electrical component with these properties:
Key
"name"
with value"R1"
Key
"resistance"
with value47
(ohms)Key
"tolerance"
with value5
(%)Key
"temp_coefficients"
with value[0.002, 0.003, 0.0025]
Calculate the resistance range based on tolerance:
Store the nominal resistance in a variable
r
Store the tolerance percentage in a variable
tol
Calculate
r_min
as the minimum resistance (r × (1 - tol/100))Calculate
r_max
as the maximum resistance (r × (1 + tol/100))Print the component name and resistance range
Finally, calculate the average temperature coefficient from the list and print it with 4 decimal places.
Hint
Access dictionary values using component["key_name"]
. Use sum()
and len()
to calculate the average of a list.
Conditional Statements and Loops#
Programs need to make decisions and repeat operations. Python provides several constructs for program flow control.
If-Elif-Else Statements#
Conditional statements allow different code to run based on conditions.
Important: Indentation
Python uses indentation to define code blocks. Always use 4 spaces for each indentation level. Most editors handle this automatically.
# Simple voltage check
voltage = 118
if voltage < 110:
print("Voltage too low")
elif voltage > 125:
print("Voltage too high")
else:
print("Voltage within acceptable range")
Voltage within acceptable range
Multiple Conditions#
# Temperature and humidity check
temperature = 28
humidity = 65
if temperature > 30 and humidity > 70:
print("High temperature and humidity - cooling required")
elif temperature > 30 or humidity > 70:
print("Either temperature or humidity high - monitor closely")
else:
print("Operating conditions normal")
Operating conditions normal
MATLAB Comparison: if-elif-else
Python uses elif
instead of MATLAB’s elseif
. Conditions must end with a colon (:
), and the code block must be indented. No end
statement is needed.
For Loops#
For loops iterate over sequences (lists, ranges, etc.).
# Loop through a list
resistances = [10, 22, 47, 100, 220]
print("Resistance values:")
for r in resistances:
conductance = 1 / r
print(f" R = {r} Ω, G = {conductance:.4f} S")
Resistance values:
R = 10 Ω, G = 0.1000 S
R = 22 Ω, G = 0.0455 S
R = 47 Ω, G = 0.0213 S
R = 100 Ω, G = 0.0100 S
R = 220 Ω, G = 0.0045 S
Using range() for Counting#
# Generate sequence of numbers
print("Counting from 0 to 4:")
for i in range(5):
print(f" Index {i}")
Counting from 0 to 4:
Index 0
Index 1
Index 2
Index 3
Index 4
# Using range with start, stop, step
print("\nVoltage steps from 100V to 130V:")
for v in range(100, 131, 10):
print(f" {v} V")
Voltage steps from 100V to 130V:
100 V
110 V
120 V
130 V
While Loops#
While loops continue as long as a condition is true.
# Iterative calculation
value = 100
iterations = 0
print("Halving until below 10:")
while value > 10:
value = value / 2
iterations += 1
print(f" Iteration {iterations}: value = {value:.2f}")
print(f"Final value: {value:.2f} after {iterations} iterations")
Halving until below 10:
Iteration 1: value = 50.00
Iteration 2: value = 25.00
Iteration 3: value = 12.50
Iteration 4: value = 6.25
Final value: 6.25 after 4 iterations
Tip: enumerate() and zip()
Use enumerate()
when you need both index and value. Use zip()
to iterate over multiple lists together.
# Using enumerate for index and value
components = ["R1", "R2", "C1"]
for i, name in enumerate(components):
print(f"Component {i}: {name}")
Component 0: R1
Component 1: R2
Component 2: C1
# Using zip to combine lists
names = ["R1", "R2", "R3"]
values = [10, 22, 47]
for name, value in zip(names, values):
print(f"{name}: {value} Ω")
R1: 10 Ω
R2: 22 Ω
R3: 47 Ω
Exercise 2: Temperature Monitoring#
Given a list of hourly temperature readings, write code that counts how many readings exceed 25°C and identifies the hours when temperature was highest.
Use this data: temps = [22, 23, 24, 26, 28, 27, 26, 24, 23, 22]
(representing hours 0-9)
Hint
Use a for loop with enumerate to get both index (hour) and temperature. Keep track of the maximum temperature and its hour.
Functions#
Functions allow you to organize code into reusable blocks. They make programs more modular and easier to test.
Defining Functions#
Best Practice: Docstrings
Always include a docstring (triple-quoted string) after the function definition to describe what it does.
def calculate_power(voltage, current):
"""
Calculate electrical power.
Parameters:
-----------
voltage: Voltage in volts
current: Current in amperes
Returns:
--------
Power in watts
"""
power = voltage * current
return power
# Using the function
p = calculate_power(120, 2.5)
print(f"Power: {p} W")
Power: 300.0 W
Functions with Default Parameters#
def calculate_resistance(voltage, current, safety_factor=1.0):
"""
Calculate resistance using Ohm's law.
Parameters:
-----------
voltage: Voltage in volts
current: Current in amperes
safety_factor: Optional multiplier (default 1.0)
"""
if current == 0:
return float('inf') # Infinite resistance
resistance = (voltage / current) * safety_factor
return resistance
# Using with and without default parameter
r1 = calculate_resistance(12, 0.5) # Uses default safety_factor=1.0
r2 = calculate_resistance(12, 0.5, safety_factor=1.2)
print(f"Standard: {r1} Ω")
print(f"With safety factor: {r2} Ω")
Standard: 24.0 Ω
With safety factor: 28.799999999999997 Ω
Returning Multiple Values#
def analyze_measurements(data):
"""
Calculate statistics for a list of measurements.
Returns:
--------
(mean, minimum, maximum)
"""
mean_val = sum(data) / len(data)
min_val = min(data)
max_val = max(data)
return mean_val, min_val, max_val
# Get multiple return values
readings = [23.5, 24.1, 23.8, 24.3, 23.9]
avg, min_r, max_r = analyze_measurements(readings)
print(f"Average: {avg:.2f}")
print(f"Range: {min_r} to {max_r}")
Average: 23.92
Range: 23.5 to 24.3
MATLAB Comparison: Functions
Python functions use def
instead of function
. They don’t need an end
statement. Multiple return values are automatically packed into a tuple.
Exercise 3: Temperature Conversion Function#
Write a function that converts temperature between Celsius and Fahrenheit. The function should take a temperature value and a unit (‘C’ or ‘F’), and return the converted temperature.
Formulas: F = C × 9/5 + 32, C = (F - 32) × 5/9
Hint
Use an if-elif statement to check the unit parameter. Consider what to return if an invalid unit is provided.
Error Handling#
Real programs need to handle unexpected inputs and errors gracefully. Python provides exception handling mechanisms for this.
Important: When to Use Try-Except
Use try-except for exceptional conditions, not normal program flow. It’s better to check conditions explicitly when possible.
Basic Exception Handling#
def safe_divide(a, b):
"""
Safely divide two numbers.
"""
try:
result = a / b
return result
except ZeroDivisionError:
print("Error: Division by zero")
return None
# Test with different inputs
print(f"10 / 2 = {safe_divide(10, 2)}")
print(f"10 / 0 = {safe_divide(10, 0)}")
10 / 2 = 5.0
Error: Division by zero
10 / 0 = None
Input Validation#
def calculate_current(voltage, resistance):
"""
Calculate current using Ohm's law with validation.
"""
# Validate inputs
if resistance <= 0:
raise ValueError("Resistance must be positive")
if voltage < 0:
raise ValueError("Voltage cannot be negative")
return voltage / resistance
# Test with various inputs
test_cases = [(12, 4), (12, 0), (-5, 10)]
for v, r in test_cases:
try:
i = calculate_current(v, r)
print(f"V={v}, R={r}: I = {i:.2f} A")
except ValueError as e:
print(f"V={v}, R={r}: Error - {e}")
V=12, R=4: I = 3.00 A
V=12, R=0: Error - Resistance must be positive
V=-5, R=10: Error - Voltage cannot be negative
Warning: Specific Exceptions
Always catch specific exception types (like ValueError
or ZeroDivisionError
) rather than using bare except:
clauses. This helps identify real errors.
Handling Missing Data#
def process_sensor_data(data_dict):
"""
Process sensor data with missing value handling.
"""
# Required fields
required = ['temperature', 'pressure']
# Check for missing fields
for field in required:
if field not in data_dict:
print(f"Warning: Missing {field}, using default")
# Set defaults
if field == 'temperature':
data_dict[field] = 25.0
elif field == 'pressure':
data_dict[field] = 101.3
return data_dict
# Test with incomplete data
sensor1 = {'temperature': 23.5, 'pressure': 102.1}
sensor2 = {'temperature': 24.0} # Missing pressure
print("Sensor 1:", process_sensor_data(sensor1))
print("Sensor 2:", process_sensor_data(sensor2))
Sensor 1: {'temperature': 23.5, 'pressure': 102.1}
Warning: Missing pressure, using default
Sensor 2: {'temperature': 24.0, 'pressure': 101.3}
Introduction to Classes#
Classes allow you to bundle data and functions together into objects. This is useful when you want to model real-world entities with both properties and behaviors.
What is a Class?
A class is like a blueprint. When you create a class, you’re defining what properties and behaviors objects of that type should have. An “instance” is a specific object created from that blueprint.
Simple Sensor Class#
class Sensor:
"""A simple sensor class."""
def __init__(self, name, unit):
"""Initialize the sensor."""
self.name = name
self.unit = unit
self.readings = []
def add_reading(self, value):
"""Add a new reading."""
self.readings.append(value)
def get_average(self):
"""Calculate average of readings."""
if self.readings:
return sum(self.readings) / len(self.readings)
return 0
def get_latest(self):
"""Get the most recent reading."""
if self.readings:
return self.readings[-1]
return None
Using the Sensor Class#
# Create sensor instances
temp_sensor = Sensor("Temperature", "°C")
pressure_sensor = Sensor("Pressure", "kPa")
# Add readings
temp_sensor.add_reading(23.5)
temp_sensor.add_reading(24.1)
temp_sensor.add_reading(23.8)
# Use methods
print(f"{temp_sensor.name} sensor:")
print(f" Latest: {temp_sensor.get_latest()}{temp_sensor.unit}")
print(f" Average: {temp_sensor.get_average():.2f}{temp_sensor.unit}")
Temperature sensor:
Latest: 23.8°C
Average: 23.80°C
Important: The self Parameter
Every method in a class must have self
as its first parameter. This refers to the specific instance. Python passes it automatically when you call methods.
Equipment Class with Status#
class Equipment:
"""Represents a piece of equipment."""
def __init__(self, equipment_id, rated_power):
"""Initialize equipment."""
self.id = equipment_id
self.rated_power = rated_power # in kW
self.is_running = False
self.current_power = 0
self.hours_run = 0
def start(self):
"""Start the equipment."""
if not self.is_running:
self.is_running = True
self.current_power = self.rated_power * 0.8 # 80% load
print(f"{self.id} started")
else:
print(f"{self.id} already running")
def stop(self):
"""Stop the equipment."""
if self.is_running:
self.is_running = False
self.current_power = 0
print(f"{self.id} stopped")
else:
print(f"{self.id} already stopped")
def run_hours(self, hours):
"""Log running hours."""
if self.is_running:
self.hours_run += hours
energy = self.current_power * hours
return energy
return 0
def get_status(self):
"""Get equipment status."""
status = "Running" if self.is_running else "Stopped"
return f"{self.id}: {status}, {self.current_power:.1f}/{self.rated_power} kW, {self.hours_run} hours total"
# Create and use equipment
pump = Equipment("PUMP_01", 50)
fan = Equipment("FAN_01", 25)
# Operate equipment
pump.start()
fan.start()
# Run for some hours
energy_pump = pump.run_hours(4)
energy_fan = fan.run_hours(4)
print(f"\nEnergy consumed:")
print(f" Pump: {energy_pump} kWh")
print(f" Fan: {energy_fan} kWh")
# Check status
print(f"\nStatus:")
print(f" {pump.get_status()}")
print(f" {fan.get_status()}")
PUMP_01 started
FAN_01 started
Energy consumed:
Pump: 160.0 kWh
Fan: 80.0 kWh
Status:
PUMP_01: Running, 40.0/50 kW, 4 hours total
FAN_01: Running, 20.0/25 kW, 4 hours total
MATLAB Comparison: Classes
Python classes are simpler than MATLAB classes. The __init__
method is like MATLAB’s constructor. No need for separate property or method blocks - everything is defined together.
Data Logger Class#
class DataLogger:
"""Logs and analyzes data points."""
def __init__(self, logger_name, max_points=100):
"""Initialize logger."""
self.name = logger_name
self.max_points = max_points
self.data = []
self.timestamps = []
def log(self, value, timestamp=None):
"""Log a data point."""
# Add the value
self.data.append(value)
self.timestamps.append(timestamp)
# Keep only max_points most recent
if len(self.data) > self.max_points:
self.data.pop(0)
self.timestamps.pop(0)
def get_statistics(self):
"""Get statistics of logged data."""
if not self.data:
return None
return {
'count': len(self.data),
'mean': sum(self.data) / len(self.data),
'min': min(self.data),
'max': max(self.data)
}
def clear(self):
"""Clear all logged data."""
self.data = []
self.timestamps = []
# Use the data logger
voltage_logger = DataLogger("Voltage Monitor", max_points=10)
# Log some readings
readings = [118, 119, 120, 122, 121, 119, 118, 120, 121, 119]
for v in readings:
voltage_logger.log(v)
# Get statistics
stats = voltage_logger.get_statistics()
print(stats)
{'count': 10, 'mean': 119.7, 'min': 118, 'max': 122}
Final Exercise: Create a Simple Calculator Class#
Create a Calculator
class with the following specifications:
Initialization:
Define an
__init__
method that initializesself.value
to 0
Methods to implement:
add(self, x)
: Add x to the current value, update self.value, and return the new valuesubtract(self, x)
: Subtract x from the current value, update self.value, and return the new valuemultiply(self, x)
: Multiply the current value by x, update self.value, and return the new valuedivide(self, x)
: Divide the current value by x, update self.value, and return the new valueCheck if x is not zero before dividing
If x is zero, print “Error: Division by zero” and don’t modify the value
Always return self.value
clear(self)
: Reset self.value to 0 and return 0get_value(self)
: Return the current value without modifying it
Each method should include a docstring describing what it does.
Hint
All operation methods follow the pattern: modify self.value
using the appropriate operator (+=, -=, *=, /=) and then return self.value
. The divide method needs an if statement to handle division by zero.
# Your class should work with the following test code
calc = Calculator()
print(f"Initial: {calc.get_value()}")
print(f"Add 10: {calc.add(10)}")
print(f"Multiply by 3: {calc.multiply(3)}")
print(f"Subtract 5: {calc.subtract(5)}")
print(f"Divide by 5: {calc.divide(5)}")
print(f"Clear: {calc.clear()}")
Initial: 0
Add 10: 10
Multiply by 3: 30
Subtract 5: 25
Divide by 5: 5.0
Clear: 0
Summary#
You’ve learned Python fundamentals that form the foundation for engineering programming:
Data Types: Numbers, strings, lists, and dictionaries for storing different kinds of information.
Program Flow: Conditional statements (if-elif-else) and loops (for, while) for controlling program execution.
Functions: Creating reusable code blocks with parameters and return values.
Error Handling: Using try-except blocks and validation to handle errors gracefully.
Classes: Organizing related data and functions into objects.
MATLAB Transitions: Key differences like zero-based indexing, indentation for blocks, and Python’s simpler syntax.
These fundamentals prepare you for more advanced topics like NumPy for numerical computing and specialized libraries for power system analysis.
Next Steps
Practice these concepts by:
Writing functions for common engineering calculations
Creating classes to model system components
Reading data from files and processing it
Building small programs that combine multiple concepts