Introduction to Python
Python is a high-level, interpreted programming language known for its readability, simplicity, and versatility. Created by Guido van Rossum and first released in 1991, Python has grown to become one of the world's most popular programming languages, especially in web development, data science, machine learning, and automation.
Python's philosophy emphasizes code readability, with its notable use of significant whitespace (indentation) to delimit code blocks rather than curly braces or keywords. This enforced indentation creates visually clean code that is easy to understand and maintain.
Python's design philosophy is captured in the "Zen of Python" (accessible by typing import this in the Python interpreter):
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Readability counts.
This philosophy guides how Python code is written and structured, emphasizing clarity and readability over brevity or cleverness.
Think of Python as a versatile toolbox with well-organized, clearly labeled tools. Even if you're new to carpentry (programming), you can quickly find and use what you need without specialized knowledge.
Basic Syntax Elements
Python's syntax is designed to be clean and readable. Here are the fundamental elements:
Statements and Whitespace
Unlike many languages that use semicolons to terminate statements and braces to group code blocks, Python uses:
- Newlines to separate statements (though semicolons can be used to place multiple statements on one line)
- Indentation (typically 4 spaces) to define code blocks
# Examples of Python statements and indentation
name = "Alice" # A simple assignment statement
# A conditional block using indentation
if name == "Alice":
print("Hello, Alice!") # Indented block
print("Welcome back.") # Still in the indented block
else:
print("Hello, stranger!") # Different indented block
# This line is outside the conditional block
print("End of program.")
Comments
Python supports single-line comments beginning with the # character, and multi-line comments using triple quotes:
# This is a single-line comment
"""
This is a multi-line comment or docstring.
It can span multiple lines and is often used
for documentation.
"""
Variables and Assignment
Python uses a simple assignment operator = to assign values to variables. Variables don't need to be declared with types; Python determines the type automatically based on the assigned value:
# Variable assignment
name = "Bob" # A string
age = 25 # An integer
height = 1.85 # A floating-point number
is_student = True # A boolean
# Multiple assignment
x, y, z = 1, 2, 3
# Value swapping (without a temporary variable)
a, b = 5, 10
a, b = b, a # a is now 10, b is now 5
Python Naming Conventions
- Variables and functions: lowercase_with_underscores (snake_case)
- Classes: CapitalizedWords (PascalCase)
- Constants: ALL_CAPS_WITH_UNDERSCORES
- Private attributes: _single_leading_underscore (suggestion of privacy)
- Special attributes: __double_leading_underscore (name mangling in classes)
These conventions aren't enforced by the interpreter but are widely followed as part of Python's culture of readability.
Python's syntax is like well-structured writing with clear paragraphs and headings. The indentation visually reinforces the code's logical structure, making it easier to understand the flow and relationships between different parts.
Built-in Data Types
Python comes with several built-in data types that are immediately available for use:
Numeric Types
- int: Integer values (e.g., 42, -7, 0)
- float: Floating-point values (e.g., 3.14, -2.5, 1.0)
- complex: Complex numbers (e.g., 1+2j, -3.14j)
# Numeric type examples
a = 42 # int
b = 3.14159 # float
c = 2 + 3j # complex
# Type conversion
float_a = float(a) # 42.0
int_b = int(b) # 3 (truncates, doesn't round)
abs_c = abs(c) # 3.605551275463989 (magnitude)
Boolean Type
The bool type has only two values: True and False. Note the capitalization, which is different from many other languages.
# Boolean examples
is_valid = True
has_error = False
# Boolean conversion
print(bool(0)) # False
print(bool(42)) # True
print(bool("")) # False
print(bool("Hi")) # True
print(bool([])) # False
print(bool([1,2])) # True
Sequence Types
- str: String - immutable sequence of characters
- list: List - mutable sequence of items
- tuple: Tuple - immutable sequence of items
- range: Range - immutable sequence of numbers
# String examples
greeting = "Hello, World!"
name = 'Alice'
multiline = """This is a
multiline string."""
# List examples (mutable)
fruits = ["apple", "banana", "cherry"]
mixed = [1, "hello", 3.14, True]
fruits.append("orange") # Add an item
fruits[0] = "pear" # Modify an item
# Tuple examples (immutable)
coordinates = (10, 20)
person = ("Bob", 25, "Engineer")
# coordinates[0] = 5 # This would raise an error
# Range example
numbers = range(1, 10, 2) # 1, 3, 5, 7, 9
for num in numbers:
print(num)
Mapping Type
- dict: Dictionary - mutable key-value pairs
# Dictionary examples
person = {
"name": "Charlie",
"age": 30,
"profession": "Developer"
}
# Accessing dictionary values
print(person["name"]) # Charlie
person["age"] = 31 # Update a value
person["location"] = "NYC" # Add a new key-value pair
# Using get() with a default value
email = person.get("email", "Not provided") # "Not provided"
Set Types
- set: Set - mutable collection of unique items
- frozenset: Frozen set - immutable collection of unique items
# Set examples
colors = {"red", "green", "blue"}
colors.add("yellow") # Add an item
colors.add("red") # Adding duplicate has no effect
# Set operations
primary = {"red", "yellow", "blue"}
secondary = {"green", "purple", "orange"}
all_colors = primary | secondary # Union
common = primary & secondary # Intersection
difference = primary - secondary # Difference
None Type
Python has a special None type to represent the absence of a value or a null value:
# None example
result = None
def function_without_return():
print("This function doesn't return anything")
x = function_without_return() # x will be None
Python's built-in data types are like different types of containers in a kitchen. Strings are like recipe cards with text, lists are like adjustable spice racks where you can add or remove items, tuples are like sealed food packages that can't be changed once created, and dictionaries are like labeled storage bins where you can quickly find items by their names.
Type Checking and Conversion
Python is dynamically typed, meaning variable types are determined at runtime. However, you can check a variable's type and convert between types as needed:
Checking Types
# Using the type() function
name = "David"
age = 40
print(type(name)) # <class 'str'>
print(type(age)) # <class 'int'>
# Using isinstance() for type checking
print(isinstance(name, str)) # True
print(isinstance(age, float)) # False
print(isinstance(age, (int, float))) # True - is either int or float
Type Conversion (Casting)
# Converting between types
age_str = "42"
age_int = int(age_str) # Convert string to integer
age_float = float(age_int) # Convert integer to float
back_to_str = str(age_float) # Convert float to string
# List/tuple/set conversion
my_list = [1, 2, 3, 2, 1]
my_tuple = tuple(my_list) # (1, 2, 3, 2, 1)
my_set = set(my_list) # {1, 2, 3} - duplicates removed
# Dictionary conversion
items = [("a", 1), ("b", 2)]
my_dict = dict(items) # {'a': 1, 'b': 2}
It's important to note that not all conversions are possible, and attempting an invalid conversion will result in a ValueError or TypeError.
Type conversion in Python is like currency exchange. Just as you can convert dollars to euros (with some rules and potential loss of precision), you can convert between data types, but you need to be aware of the conversion rules and potential data loss.
String Operations and Formatting
Strings are one of the most commonly used data types in Python, and the language provides extensive capabilities for working with them:
String Concatenation and Repetition
# String concatenation with +
first_name = "John"
last_name = "Doe"
full_name = first_name + " " + last_name # "John Doe"
# String repetition with *
divider = "-" * 20 # "--------------------"
String Indexing and Slicing
# Indexing (0-based)
text = "Python"
first_char = text[0] # 'P'
last_char = text[-1] # 'n'
# Slicing [start:end:step]
substring = text[1:4] # 'yth' (from index 1 to 3)
reversed_text = text[::-1] # 'nohtyP' (reverse)
every_other = text[::2] # 'Pto' (every 2nd character)
Common String Methods
message = " Hello, World! "
# Case methods
print(message.upper()) # " HELLO, WORLD! "
print(message.lower()) # " hello, world! "
print(message.capitalize()) # " hello, world! "
print(message.title()) # " Hello, World! "
# Whitespace methods
print(message.strip()) # "Hello, World!"
print(message.lstrip()) # "Hello, World! "
print(message.rstrip()) # " Hello, World!"
# Search methods
print(message.find("World")) # 9
print("World" in message) # True
print(message.count("l")) # 3
# Replace method
print(message.replace("World", "Python")) # " Hello, Python! "
# Split and join
words = message.strip().split(", ") # ["Hello", "World!"]
new_message = ", ".join(words) # "Hello, World!"
String Formatting
Python offers several ways to format strings:
Format Method
# Using the format() method
name = "Eve"
age = 28
message = "My name is {} and I am {} years old.".format(name, age)
# With named placeholders
message = "My name is {name} and I am {age} years old.".format(name=name, age=age)
# With positional index
message = "My name is {0} and I am {1} years old.".format(name, age)
F-Strings (Formatted String Literals)
# Using f-strings (Python 3.6+)
name = "Frank"
age = 35
message = f"My name is {name} and I am {age} years old."
# With expressions
message = f"In 5 years, {name} will be {age + 5} years old."
# With formatting specifications
pi = 3.14159265359
formatted = f"Pi rounded to 2 decimal places: {pi:.2f}" # 3.14
Old-style % Formatting (still in use)
# Old-style formatting (similar to C printf)
name = "Grace"
age = 42
message = "My name is %s and I am %d years old." % (name, age)
String operations in Python are like text editing tools. You can cut (slice), glue (concatenate), find and replace text, adjust formatting (case), and even create templates with placeholders (formatting) – all the tools you'd expect in a modern word processor.
Container Operations
Python's container types (lists, tuples, dictionaries, sets) share many common operations:
Length and Membership
# Length with len()
fruits = ["apple", "banana", "cherry"]
print(len(fruits)) # 3
# Membership testing with in
print("apple" in fruits) # True
print("mango" in fruits) # False
Iteration
# Iterating over a list
for fruit in fruits:
print(fruit)
# Iterating with index
for i, fruit in enumerate(fruits):
print(f"{i}: {fruit}")
# Iterating over a dictionary
person = {"name": "Helen", "age": 24, "city": "Boston"}
for key in person:
print(f"{key}: {person[key]}")
# Iterating over key-value pairs
for key, value in person.items():
print(f"{key}: {value}")
Common List Operations
numbers = [1, 2, 3, 4, 5]
# Adding elements
numbers.append(6) # [1, 2, 3, 4, 5, 6]
numbers.insert(0, 0) # [0, 1, 2, 3, 4, 5, 6]
numbers.extend([7, 8]) # [0, 1, 2, 3, 4, 5, 6, 7, 8]
# Removing elements
numbers.remove(0) # [1, 2, 3, 4, 5, 6, 7, 8]
popped = numbers.pop() # popped = 8, numbers = [1, 2, 3, 4, 5, 6, 7]
popped_index = numbers.pop(0) # popped_index = 1, numbers = [2, 3, 4, 5, 6, 7]
# Finding elements
index = numbers.index(5) # 3
count = numbers.count(2) # 1
# Sorting and reversing
numbers.sort() # [2, 3, 4, 5, 6, 7]
numbers.sort(reverse=True) # [7, 6, 5, 4, 3, 2]
numbers.reverse() # [2, 3, 4, 5, 6, 7]
Common Dictionary Operations
person = {"name": "Ian", "age": 31}
# Adding or updating entries
person["city"] = "Chicago" # Add new key-value pair
person["age"] = 32 # Update existing value
# Getting values
name = person["name"] # Direct access (raises KeyError if not found)
city = person.get("city") # Using get() method
email = person.get("email", "Not available") # With default value
# Removing entries
age = person.pop("age") # Remove and return the value
del person["city"] # Remove without returning
# Other operations
keys = list(person.keys()) # Get all keys
values = list(person.values()) # Get all values
items = list(person.items()) # Get all key-value pairs
person.update({"email": "ian@example.com", "phone": "555-1234"}) # Bulk update
These container operations provide the building blocks for data manipulation in Python. They're like the common tools used across different types of storage systems – ways to add, remove, find, and organize items regardless of whether they're in a list, a dictionary, or another container type.
List and Dictionary Comprehensions
Python offers concise ways to create lists and dictionaries using comprehensions:
List Comprehensions
# Creating a list of squares using a loop
squares = []
for x in range(10):
squares.append(x ** 2)
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# The same with a list comprehension
squares = [x ** 2 for x in range(10)]
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# List comprehension with a condition
even_squares = [x ** 2 for x in range(10) if x % 2 == 0]
print(even_squares) # [0, 4, 16, 36, 64]
# Nested list comprehension
matrix = [[i * j for j in range(1, 4)] for i in range(1, 4)]
print(matrix) # [[1, 2, 3], [2, 4, 6], [3, 6, 9]]
Dictionary Comprehensions
# Creating a dictionary with a loop
square_dict = {}
for x in range(5):
square_dict[x] = x ** 2
print(square_dict) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# The same with a dictionary comprehension
square_dict = {x: x ** 2 for x in range(5)}
print(square_dict) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# Dictionary comprehension with a condition
even_square_dict = {x: x ** 2 for x in range(10) if x % 2 == 0}
print(even_square_dict) # {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}
# Creating a dictionary from two lists
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
name_age_dict = {name: age for name, age in zip(names, ages)}
print(name_age_dict) # {'Alice': 25, 'Bob': 30, 'Charlie': 35}
Set Comprehensions
# Creating a set with a loop
squares_set = set()
for x in range(5):
squares_set.add(x ** 2)
print(squares_set) # {0, 1, 4, 9, 16}
# The same with a set comprehension
squares_set = {x ** 2 for x in range(5)}
print(squares_set) # {0, 1, 4, 9, 16}
# Get unique letters from a string
text = "hello world"
letters = {char for char in text if char.isalpha()}
print(letters) # {'e', 'h', 'd', 'l', 'o', 'r', 'w'}
Comprehensions are a powerful and concise way to create collections. They're like specialized manufacturing machines that can quickly produce a collection of items according to your specifications, often replacing several lines of more verbose loop code with a single, readable line.
Advanced Data Types
In addition to the basic data types, Python includes several more specialized types in the standard library:
Named Tuples
Named tuples are like tuples but with field names, making your code more readable:
from collections import namedtuple
# Define a named tuple type
Point = namedtuple('Point', ['x', 'y'])
# Create instances
p1 = Point(1, 2)
p2 = Point(x=3, y=4)
# Access by name or position
print(p1.x, p1.y) # 1 2
print(p1[0], p1[1]) # 1 2
# Immutable like regular tuples
# p1.x = 5 # This would raise an AttributeError
Default Dictionaries
Default dictionaries automatically create missing keys with a default value:
from collections import defaultdict
# Create a defaultdict with int as default factory
word_counts = defaultdict(int)
# Count words in a text
text = "apple banana apple cherry banana apple"
for word in text.split():
word_counts[word] += 1 # No KeyError for new words
print(dict(word_counts)) # {'apple': 3, 'banana': 2, 'cherry': 1}
# defaultdict with list as default factory
grouped_names = defaultdict(list)
people = [("Alice", 25), ("Bob", 25), ("Charlie", 30), ("David", 30)]
for name, age in people:
grouped_names[age].append(name)
print(dict(grouped_names)) # {25: ['Alice', 'Bob'], 30: ['Charlie', 'David']}
Ordered Dictionaries
Ordered dictionaries maintain the insertion order of keys (note: in Python 3.7+, regular dictionaries also maintain order):
from collections import OrderedDict
# Create an ordered dictionary
od = OrderedDict()
od['first'] = 1
od['second'] = 2
od['third'] = 3
# Items are retrieved in insertion order
for key, value in od.items():
print(key, value)
Counter
Counter is a specialized dictionary for counting hashable objects:
from collections import Counter
# Count elements in a list
fruits = ['apple', 'orange', 'apple', 'banana', 'orange', 'apple']
fruit_counter = Counter(fruits)
print(fruit_counter) # Counter({'apple': 3, 'orange': 2, 'banana': 1})
# Most common elements
print(fruit_counter.most_common(2)) # [('apple', 3), ('orange', 2)]
# Count characters in a string
char_counter = Counter("Mississippi")
print(char_counter) # Counter({'i': 4, 's': 4, 'p': 2, 'M': 1})
These advanced data types are like specialized tools designed for specific tasks. While the basic data types are like general-purpose tools that can handle many jobs, these specialized types are optimized for particular scenarios, making your code more efficient and readable when dealing with those specific tasks.
Practice Activities
Activity 1: Data Type Explorer
Create a Python script that:
- Defines variables of each basic data type (int, float, string, list, tuple, dictionary, set)
- Prints the type and value of each variable
- Attempts to convert each type to every other type and handles any errors
- Demonstrates at least three operations specific to each data type
Activity 2: String Manipulator
Write a program that:
- Takes a user input string
- Counts the number of vowels and consonants
- Reverses the string
- Creates an acronym from the first letter of each word
- Checks if the string is a palindrome (reads the same forwards and backwards)
Activity 3: Data Processing Challenge
Given this data structure:
data = [
{"name": "Alice", "age": 25, "city": "New York"},
{"name": "Bob", "age": 30, "city": "Chicago"},
{"name": "Charlie", "age": 35, "city": "New York"},
{"name": "David", "age": 25, "city": "Boston"},
{"name": "Eve", "age": 30, "city": "Chicago"}
]
Write code to:
- Find all people from a specific city
- Calculate the average age
- Group people by age
- Sort people by name
- Create a new list with just names and cities using a list comprehension
Key Takeaways
- Python uses significant whitespace (indentation) to define code blocks, promoting clean, readable code.
- Python's built-in data types include numeric types (int, float), strings, containers (list, tuple, dict, set), and more.
- Python is dynamically typed, meaning variables can change types, and type checking is done at runtime.
- Strings in Python are versatile, with powerful methods for manipulation and multiple formatting options.
- Lists, dictionaries, sets, and tuples each have specific use cases and operations for different data organization needs.
- List, dictionary, and set comprehensions provide concise ways to create collections based on existing sequences.
- The standard library includes advanced data types like namedtuples, defaultdict, OrderedDict, and Counter for specialized use cases.
In our next lecture, we'll explore control structures in Python, including conditional statements, loops, and functions, which will allow us to add logic and reusable behavior to our programs.