Python Variables, Data Types, and Basic Operations

Week 2: Python Fundamentals - Core Building Blocks

Session Overview

Welcome to our exploration of Python's foundational elements! Today, we'll dive into variables, data types, and basic operations that form the building blocks of any Python program. Understanding these concepts thoroughly will give you a solid foundation for Python development and allow you to express your ideas clearly in code.

Understanding Variables in Python

Variables are named storage locations in computer memory. Think of them as labeled containers that hold data which can be changed during program execution.

Variable Assignment

In Python, you create a variable by assigning a value to a name:

# Basic variable assignment
name = "Alice"
age = 30
height = 5.7
is_student = True

Unlike many other programming languages, Python doesn't require you to declare a variable's type before using it. The type is determined automatically based on the value assigned.

Variable Naming Rules

Python Naming Conventions

Analogy: Variables as Labeled Boxes

Think of variables as labeled boxes in a storage room:

  • Each box has a name (the variable name) written on it
  • You can put things in the box (assign values)
  • You can replace the contents with something else (reassign values)
  • You can look inside to see what's there (retrieve values)
  • You can create new boxes or discard old ones as needed

This analogy helps understand why the same variable can hold different types of data at different times - you can put anything in a box!

Multiple Assignment

Python allows for assigning multiple variables at once:

# Assign same value to multiple variables
x = y = z = 0

# Assign multiple values to multiple variables
a, b, c = 1, 2, 3

# Swap values without a temporary variable
a, b = b, a

Variable Scope

The scope of a variable determines where in your code the variable can be accessed. Python has several scopes:

global_var = "I'm global"

def outer_function():
    outer_var = "I'm in the outer function"
    
    def inner_function():
        local_var = "I'm local"
        print(local_var)     # Accesses local variable
        print(outer_var)     # Accesses enclosing variable
        print(global_var)    # Accesses global variable
    
    inner_function()

Python Data Types

Python has several built-in data types to represent different kinds of values. Unlike statically-typed languages, Python is dynamically-typed, meaning a variable can change its type during program execution.

Numeric Types

Python provides three numeric types:

# Integer (whole numbers)
age = 30
count = -5
big_number = 10000000000  # Python integers have unlimited precision

# Float (decimal numbers)
height = 5.7
pi = 3.14159
scientific = 2.5e6  # Scientific notation (2.5 million)

# Complex (numbers with real and imaginary parts)
complex_num = 2 + 3j

String Type

Strings are sequences of characters, enclosed in single, double, or triple quotes:

# Single quotes
name = 'Alice'

# Double quotes
message = "Hello, World!"

# Triple quotes for multi-line strings
description = """This is a long description
that spans multiple lines,
which is easy to read in the code."""

# Strings are immutable (cannot be changed after creation)
name = 'Alice'
# This creates a new string, doesn't modify the original
new_name = name + ' Smith'

Boolean Type

Boolean values represent truth values, either True or False:

is_student = True
has_permission = False

# Comparison operations yield boolean results
is_adult = age >= 18
is_valid = name != ""

Sequence Types

Python has several sequence types that can hold collections of items:

# Lists - ordered, mutable collections
fruits = ['apple', 'banana', 'cherry']
mixed_list = [1, 'hello', True, 3.14]

# Tuples - ordered, immutable collections
coordinates = (10.5, 20.8)
person = ('Alice', 30, 'Engineer')

# Ranges - sequence of numbers
numbers = range(10)      # 0 to 9
even_numbers = range(0, 10, 2)  # 0, 2, 4, 6, 8

Mapping Type

Dictionaries store key-value pairs:

# Dictionary - unordered collection of key-value pairs
person = {
    'name': 'Alice',
    'age': 30,
    'profession': 'Engineer',
    'is_student': False
}

# Accessing values
print(person['name'])    # Alice

# Adding new key-value pairs
person['location'] = 'New York'

Set Types

Sets store unordered collections of unique items:

# Set - unordered collection of unique elements
fruits = {'apple', 'banana', 'cherry'}

# Adding and removing elements
fruits.add('orange')
fruits.remove('banana')

# Set operations
basket1 = {'apple', 'orange', 'banana'}
basket2 = {'pear', 'banana', 'kiwi'}
all_fruits = basket1 | basket2  # Union
common_fruits = basket1 & basket2  # Intersection

None Type

Python has a special None value that represents the absence of a value:

# None represents the absence of a value
result = None

def function_without_return():
    pass  # Do something but don't return a value

value = function_without_return()  # value will be None

Analogy: Data Types as Different Container Types

Think of different data types as different kinds of containers:

  • Integers and Floats: Like measuring cups for different measurements (whole vs. fractional)
  • Strings: Like letter beads strung together on a necklace
  • Lists: Like a drawer organizer with ordered compartments
  • Tuples: Like a sealed package with multiple items inside
  • Dictionaries: Like a filing cabinet where each file (value) has a label (key)
  • Sets: Like a collection of unique stamps

Just as you wouldn't store soup in a file folder, each data type in Python is optimized for certain operations and ways of organizing data.

Checking Types

You can check the type of a variable using the type() function:

x = 42
y = "Hello"
z = [1, 2, 3]

print(type(x))  # 
print(type(y))  # 
print(type(z))  # 

Type Conversion

Python provides functions to convert between types:

# Converting between types
age_str = "30"
age_int = int(age_str)    # String to integer
age_float = float(age_int)  # Integer to float
age_str_again = str(age_int)  # Integer to string

# Converting collections
my_list = [1, 2, 3, 2, 1]
my_tuple = tuple(my_list)  # List to tuple
my_set = set(my_list)      # List to set (duplicates removed)
my_list_again = list(my_set)  # Set to list

Basic Operations in Python

Arithmetic Operations

Python supports all standard arithmetic operations:

# Addition
sum = 5 + 3  # 8

# Subtraction
difference = 10 - 4  # 6

# Multiplication
product = 6 * 7  # 42

# Division (always returns float)
quotient = 12 / 4  # 3.0

# Integer Division (floors the result)
int_quotient = 13 // 5  # 2

# Modulus (remainder)
remainder = 13 % 5  # 3

# Exponentiation
power = 2 ** 3  # 8

# Combining operations with order of precedence
result = 10 + 3 * 2  # 16 (not 26, because * has higher precedence than +)
result_with_parens = (10 + 3) * 2  # 26

String Operations

Strings support concatenation, repetition, and indexing:

# Concatenation
first_name = "John"
last_name = "Doe"
full_name = first_name + " " + last_name  # "John Doe"

# String repetition
stars = "*" * 10  # "**********"

# Indexing (0-based)
first_letter = full_name[0]  # "J"
last_letter = full_name[-1]  # "e"

# Slicing
substring = full_name[5:8]  # "Doe"
first_five = full_name[:5]  # "John "
from_sixth = full_name[5:]  # "Doe"

# Length
name_length = len(full_name)  # 8

# String methods
uppercase = full_name.upper()  # "JOHN DOE"
lowercase = full_name.lower()  # "john doe"
replaced = full_name.replace("John", "Jane")  # "Jane Doe"

Boolean Operations

Python has logical operations for combining boolean values:

# AND operation (True if both operands are True)
result = True and False  # False

# OR operation (True if at least one operand is True)
result = True or False  # True

# NOT operation (inverts the boolean value)
result = not True  # False

# Combining operations
complex_logic = (True or False) and not (False and True)  # True

Comparison Operations

Comparison operations compare values and return boolean results:

# Equal
is_equal = 5 == 5  # True

# Not equal
is_not_equal = 5 != 10  # True

# Greater than
is_greater = 10 > 5  # True

# Less than
is_less = 5 < 10  # True

# Greater than or equal
is_greater_equal = 10 >= 10  # True

# Less than or equal
is_less_equal = 5 <= 5  # True

# Chained comparisons
is_in_range = 1 < 5 < 10  # True (equivalent to 1 < 5 and 5 < 10)

Identity and Membership Operations

Python provides operations to check identity and membership:

# Identity operations (check if objects are the same object in memory)
a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(a is b)  # False (different objects with the same values)
print(a is c)  # True (same object)
print(a is not b)  # True

# Membership operations (check if a value is in a sequence)
fruits = ['apple', 'banana', 'cherry']

print('banana' in fruits)  # True
print('orange' in fruits)  # False
print('orange' not in fruits)  # True

Compound Assignment Operations

Python provides shortcuts for updating variables:

# Addition assignment
x = 10
x += 5  # Equivalent to x = x + 5, x is now 15

# Subtraction assignment
x -= 3  # x is now 12

# Multiplication assignment
x *= 2  # x is now 24

# Division assignment
x /= 4  # x is now 6.0

# Integer division assignment
x //= 2  # x is now 3.0

# Modulus assignment
y = 10
y %= 3  # y is now 1

# Exponentiation assignment
y **= 3  # y is now 1 (1^3 = 1)

Analogy: Operations as Kitchen Tools

Think of Python operations as different kitchen tools:

  • Arithmetic operations are like basic kitchen tools (knife for cutting/subtraction, mixer for combining/addition)
  • String operations are like tools for working with dough (rolling for extending, cookie cutters for slicing)
  • Boolean operations are like sieves and strainers that filter based on conditions
  • Comparison operations are like measuring tools that tell you relationships between ingredients
  • Compound operations are like all-in-one appliances that perform multiple steps in one action

Just as a good chef knows which tool to use when, a good programmer chooses the right operations for the task at hand.

String Formatting

Python offers several ways to format strings by inserting values into string templates:

F-strings (Python 3.6+)

F-strings provide a concise and readable way to embed expressions inside string literals:

name = "Alice"
age = 30

# Basic f-string
greeting = f"Hello, {name}! You are {age} years old."

# Expressions in f-strings
greeting = f"Hello, {name.upper()}! Next year, you'll be {age + 1}."

# Formatting specifiers
pi = 3.14159265359
formatted = f"Pi to 2 decimal places: {pi:.2f}"  # "Pi to 2 decimal places: 3.14"

# Alignment and padding
for i in range(1, 4):
    print(f"{i:3} squared is {i**2:3}")
# Output:
#   1 squared is   1
#   2 squared is   4
#   3 squared is   9

str.format() Method

The format() method is another way to format strings:

# Basic formatting
greeting = "Hello, {}! You are {} years old.".format(name, age)

# Positional arguments
greeting = "Hello, {0}! You are {1} years old. Nice to meet you, {0}!".format(name, age)

# Named arguments
greeting = "Hello, {name}! You are {age} years old.".format(name=name, age=age)

# Formatting specifiers
formatted = "Pi to 2 decimal places: {:.2f}".format(pi)  # "Pi to 2 decimal places: 3.14"

% Operator (Older Style)

The % operator is an older way to format strings, still found in legacy code:

# Basic formatting
greeting = "Hello, %s! You are %d years old." % (name, age)

# Formatting specifiers
formatted = "Pi to 2 decimal places: %.2f" % pi  # "Pi to 2 decimal places: 3.14"

While all these methods work, f-strings are recommended for new code due to their readability and performance.

Operations on Collections

List Operations

Lists support many operations for adding, removing, and modifying elements:

fruits = ['apple', 'banana', 'cherry']

# Adding elements
fruits.append('orange')  # ['apple', 'banana', 'cherry', 'orange']
fruits.insert(1, 'blueberry')  # ['apple', 'blueberry', 'banana', 'cherry', 'orange']
fruits.extend(['grape', 'kiwi'])  # ['apple', 'blueberry', 'banana', 'cherry', 'orange', 'grape', 'kiwi']

# Removing elements
removed = fruits.pop()  # Removes and returns 'kiwi'
fruits.pop(1)  # Removes element at index 1 ('blueberry')
fruits.remove('banana')  # Removes the first occurrence of 'banana'

# Finding elements
index = fruits.index('cherry')  # Gets the index of 'cherry'
count = fruits.count('apple')  # Counts how many 'apple' items are in the list

# Sorting
fruits.sort()  # Sorts the list in place
fruits.sort(reverse=True)  # Sorts in descending order
sorted_fruits = sorted(fruits)  # Returns a new sorted list, leaving the original unchanged

# Reversing
fruits.reverse()  # Reverses the list in place
reversed_fruits = list(reversed(fruits))  # Returns a new reversed list

Dictionary Operations

Dictionaries store key-value pairs and provide efficient lookups:

person = {'name': 'Alice', 'age': 30}

# Adding or updating entries
person['location'] = 'New York'  # Adds a new key-value pair
person['age'] = 31  # Updates an existing value

# Accessing values
name = person['name']  # Gets the value for key 'name'
# Safely get a value with a default if the key doesn't exist
location = person.get('job', 'Unknown')  # Returns 'Unknown' if 'job' key doesn't exist

# Removing entries
removed_age = person.pop('age')  # Removes the key and returns its value
person.popitem()  # Removes and returns the last inserted key-value pair

# Checking for keys
has_name = 'name' in person  # Checks if 'name' is a key in the dictionary

# Getting keys, values, and items
keys = list(person.keys())  # Gets all keys
values = list(person.values())  # Gets all values
items = list(person.items())  # Gets all key-value pairs as tuples

# Merging dictionaries
person.update({'job': 'Engineer', 'email': 'alice@example.com'})  # Adds/updates from another dict

Set Operations

Sets provide mathematical set operations:

set1 = {1, 2, 3, 4, 5}
set2 = {4, 5, 6, 7, 8}

# Adding and removing elements
set1.add(6)  # Adds an element
set1.remove(1)  # Removes an element (raises error if not found)
set1.discard(10)  # Removes an element if present (no error if not found)
popped = set1.pop()  # Removes and returns an arbitrary element

# Set operations
union = set1 | set2  # Elements in either set
intersection = set1 & set2  # Elements in both sets
difference = set1 - set2  # Elements in set1 but not in set2
symmetric_difference = set1 ^ set2  # Elements in either set but not both

# Checking membership
is_in_set = 3 in set1

# Checking subset/superset relationships
is_subset = set1 <= set2  # Is set1 a subset of set2?
is_superset = set1 >= set2  # Is set1 a superset of set2?

Advanced Topics

List Comprehensions

List comprehensions provide a concise way to create lists:

# Create a list of squares
squares = [x**2 for x in range(10)]  # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# Filtering with a condition
even_squares = [x**2 for x in range(10) if x % 2 == 0]  # [0, 4, 16, 36, 64]

# Nested loops
coordinates = [(x, y) for x in range(3) for y in range(2)]
# [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]

Dictionary Comprehensions

Similar to list comprehensions, but for creating dictionaries:

# Create a dictionary of squares
squares_dict = {x: x**2 for x in range(5)}  # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# Filtering with a condition
even_squares_dict = {x: x**2 for x in range(10) if x % 2 == 0}
# {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}

# Transforming keys and values
name_lengths = {name: len(name) for name in ['Alice', 'Bob', 'Charlie']}
# {'Alice': 5, 'Bob': 3, 'Charlie': 7}

Set Comprehensions

Create sets with a similar syntax:

# Create a set of squares
squares_set = {x**2 for x in range(10)}  # {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}

# Filtering with a condition
even_squares_set = {x**2 for x in range(10) if x % 2 == 0}
# {0, 4, 16, 36, 64}

Unpacking

Unpacking allows you to assign multiple variables from collections:

# Unpacking a list
numbers = [1, 2, 3]
a, b, c = numbers  # a=1, b=2, c=3

# Unpacking with * to collect remaining items
first, *rest = [1, 2, 3, 4, 5]  # first=1, rest=[2, 3, 4, 5]
*beginning, last = [1, 2, 3, 4, 5]  # beginning=[1, 2, 3, 4], last=5
first, *middle, last = [1, 2, 3, 4, 5]  # first=1, middle=[2, 3, 4], last=5

# Unpacking dictionaries with ** (in function calls)
person = {'name': 'Alice', 'age': 30}
print("%(name)s is %(age)d years old." % person)  # "Alice is 30 years old."

Practical Examples

Temperature Converter

def celsius_to_fahrenheit(celsius):
    """Convert Celsius to Fahrenheit."""
    return (celsius * 9/5) + 32

def fahrenheit_to_celsius(fahrenheit):
    """Convert Fahrenheit to Celsius."""
    return (fahrenheit - 32) * 5/9

# Using the functions
celsius = 25
fahrenheit = celsius_to_fahrenheit(celsius)
print(f"{celsius}°C is {fahrenheit:.1f}°F")

fahrenheit = 98.6
celsius = fahrenheit_to_celsius(fahrenheit)
print(f"{fahrenheit}°F is {celsius:.1f}°C")

Contact List Manager

contacts = {}

def add_contact(name, phone, email=None):
    """Add a contact to the contacts dictionary."""
    contacts[name] = {'phone': phone, 'email': email}
    print(f"Added contact: {name}")

def display_contact(name):
    """Display a contact's information."""
    if name in contacts:
        contact = contacts[name]
        print(f"Name: {name}")
        print(f"Phone: {contact['phone']}")
        if contact['email']:
            print(f"Email: {contact['email']}")
    else:
        print(f"No contact found with name: {name}")

def list_contacts():
    """List all contacts."""
    if contacts:
        print("Contacts:")
        for name in sorted(contacts.keys()):
            print(f"- {name}: {contacts[name]['phone']}")
    else:
        print("No contacts saved.")

# Using the functions
add_contact("Alice", "555-1234", "alice@example.com")
add_contact("Bob", "555-5678")
list_contacts()
display_contact("Alice")
display_contact("Charlie")  # Not found

Word Frequency Counter

def count_word_frequency(text):
    """Count the frequency of each word in a text."""
    # Convert to lowercase and split into words
    words = text.lower().split()
    
    # Remove punctuation from words
    cleaned_words = []
    for word in words:
        cleaned_word = word.strip('.,!?:;()"\'')
        if cleaned_word:  # Skip empty strings
            cleaned_words.append(cleaned_word)
    
    # Count word frequencies
    frequencies = {}
    for word in cleaned_words:
        if word in frequencies:
            frequencies[word] += 1
        else:
            frequencies[word] = 1
    
    return frequencies

def display_word_frequencies(frequencies):
    """Display word frequencies sorted by most common."""
    # Sort by frequency (descending) and then by word (ascending)
    sorted_frequencies = sorted(frequencies.items(), 
                               key=lambda x: (-x[1], x[0]))
    
    print("Word frequencies:")
    for word, count in sorted_frequencies:
        print(f"{word}: {count}")

# Example text
text = """
Python is a programming language that lets you work quickly
and integrate systems more effectively. Python is easy to learn,
yet powerful programming language. Its efficient high-level data structures
and a simple but effective approach to object-oriented programming.
"""

frequencies = count_word_frequency(text)
display_word_frequencies(frequencies)

Practice Exercises

Exercise 1: Basic Operations

  1. Create variables for your name, age, and favorite number
  2. Calculate and store your age in days
  3. Create a string that says "My name is [your name], I am [your age] years old, and my favorite number is [your favorite number]"
  4. Print the length of your name
  5. Check if your age is greater than your favorite number and store the result in a boolean variable

Exercise 2: Collections

  1. Create a list of 5 fruits
  2. Create a dictionary with information about yourself (name, age, city, etc.)
  3. Create a set of 3 hobbies
  4. Add a new fruit to your list and a new hobby to your set
  5. Update your age in the dictionary
  6. Print the second fruit in your list
  7. Check if a specific fruit is in your list

Exercise 3: BMI Calculator

Create a Body Mass Index (BMI) calculator program that:

  1. Asks the user for their weight in kilograms and height in meters
  2. Calculates the BMI using the formula: BMI = weight / (height * height)
  3. Displays the BMI value and a message based on the following categories:
    • Below 18.5: Underweight
    • 18.5 - 24.9: Normal weight
    • 25 - 29.9: Overweight
    • 30 and above: Obesity

Wrapping Up and Next Steps

Today we've covered the foundational building blocks of Python programming: variables, data types, and basic operations. These concepts form the basis for all Python programming, and mastering them is essential for your journey as a Python developer.

Key Takeaways

Where to Go from Here

In our next session, we'll build on these fundamentals as we explore control structures in Python - the tools that allow us to make decisions and repeat actions in our programs. We'll cover conditional statements, loops, and more advanced flow control techniques.

Additional Resources