Python Basics: Core Syntax
A comprehensive guide to Python's fundamental building blocks
Table of Contents
1. Variables and Data Types
Variable Basics
Variables in Python are names that refer to objects in memory. Python uses dynamic typing, meaning variable types are determined at runtime.
Variable naming rules and conventions
- Must start with a letter or underscore
- Can contain letters, numbers, and underscores
- Case-sensitive
- Use snake_case by convention (e.g.,
my_variable
) - Avoid Python keywords (like
if
,for
, etc.)
# Valid variable names
age = 25
user_name = "Alice"
_is_admin = False
MAX_RETRIES = 3
Dynamic typing vs static typing
Python uses duck typing ("if it walks like a duck and quacks like a duck, it must be a duck") where type checking happens at runtime.
x = 10 # x is an integer
x = "hello" # now x is a string
x = [1, 2] # now x is a list
type() and isinstance()
Use these functions to check types:
print(type(42)) # <class 'int'>
print(isinstance(3.14, float)) # True
Variable scope
Python has three scopes:
- Local: Inside the current function
- Nonlocal: In enclosing functions (but not global)
- Global: At the module level
x = "global" # Global scope
def outer():
x = "nonlocal"
def inner():
nonlocal x # Refers to x in outer()
x = "changed"
inner()
print(x) # "changed"
outer()
print(x) # "global"
Primitive Data Types
Python's built-in primitive types:
Type | Description | Example |
---|---|---|
int |
Integer of unlimited size | 42 , -7 , 0 |
float |
Floating-point number | 3.14 , -0.001 |
bool |
Boolean value | True , False |
str |
Unicode string | "hello" , 'world' |
complex |
Complex number | 3+4j |
NoneType |
Represents absence of value | None |
Composite Data Types
Python's built-in container types:
Type | Mutable | Ordered | Description | Example |
---|---|---|---|---|
list |
Yes | Yes | Ordered sequence of items | [1, 2, 3] |
tuple |
No | Yes | Immutable sequence | (1, 2, 3) |
dict |
Yes | No (Python 3.7+: insertion order) | Key-value mapping | {"a": 1, "b": 2} |
set |
Yes | No | Unordered collection of unique items | {1, 2, 3} |
frozenset |
No | No | Immutable set | frozenset({1, 2, 3}) |
Advanced Topics
Immutable vs Mutable Objects
Immutable objects (int, float, str, tuple, frozenset) cannot be changed after creation. Mutable objects (list, dict, set) can be modified.
# Immutable example
x = "hello"
y = x # Both point to same string
x += " world" # Creates new string, x now points to it
print(y) # "hello" (unchanged)
# Mutable example
a = [1, 2]
b = a # Both reference same list
a.append(3) # Modifies the list in place
print(b) # [1, 2, 3] (changed)
Memory Management
Use id()
to get memory address and sys.getsizeof()
to check object size:
import sys
x = 42
print(id(x)) # Memory address
print(sys.getsizeof(x)) # Size in bytes
Type Hints (PEP 484)
Python supports optional type annotations:
def greet(name: str) -> str:
return f"Hello, {name}"
# Variable annotations
count: int = 0
Note: Type hints don't enforce types at runtime but are useful for documentation and static type checkers like mypy.
2. Operators and Expressions
Arithmetic Operators
Operator | Description | Example |
---|---|---|
+ |
Addition | 3 + 2 → 5 |
- |
Subtraction | 5 - 2 → 3 |
* |
Multiplication | 3 * 4 → 12 |
/ |
Division (float) | 10 / 3 → 3.333... |
// |
Floor division | 10 // 3 → 3 |
% |
Modulus (remainder) | 10 % 3 → 1 |
** |
Exponentiation | 2 ** 3 → 8 |
Augmented Assignment
Combine operation and assignment:
x = 5
x += 3 # Equivalent to x = x + 3
x *= 2 # Equivalent to x = x * 2
Comparison Operators
Operator | Description | Example |
---|---|---|
== |
Equal to | 5 == 5 → True |
!= |
Not equal to | 5 != 3 → True |
> |
Greater than | 5 > 3 → True |
< |
Less than | 5 < 3 → False |
>= |
Greater than or equal to | 5 >= 5 → True |
<= |
Less than or equal to | 5 <= 3 → False |
Chained Comparisons
Python allows chaining comparison operators:
# Traditional way
if x > 1 and x < 10:
print("Between 1 and 10")
# Chained comparison
if 1 < x < 10:
print("Between 1 and 10")
Logical Operators
Operator | Description | Example |
---|---|---|
and |
Both conditions must be true | True and False → False |
or |
At least one condition must be true | True or False → True |
not |
Negates the condition | not True → False |
Truthy vs Falsy Values
In Python, the following evaluate to False
:
None
False
- Zero of any numeric type:
0
,0.0
,0j
- Empty sequences:
""
,()
,[]
- Empty mappings:
{}
- Objects with
__bool__()
returningFalse
or__len__()
returning0
if not []:
print("Empty list is falsy")
Bitwise Operators
Operator | Description | Example |
---|---|---|
& |
Bitwise AND | 5 & 3 → 1 |
| |
Bitwise OR | 5 | 3 → 7 |
^ |
Bitwise XOR | 5 ^ 3 → 6 |
~ |
Bitwise NOT | ~5 → -6 |
<< |
Left shift | 5 << 1 → 10 |
>> |
Right shift | 5 >> 1 → 2 |
Membership & Identity Operators
Operator | Description | Example |
---|---|---|
in |
Membership test | "a" in ["a", "b"] → True |
not in |
Negative membership test | "c" not in ["a", "b"] → True |
is |
Identity test (same object) | x is y |
is not |
Negative identity test | x is not y |
Important: is
checks for object identity (same memory address), while ==
checks for equality (same value).
Operator Overloading
Python allows operators to have different meanings for user-defined classes using special methods:
Operator | Method |
---|---|
+ |
__add__ |
- |
__sub__ |
* |
__mul__ |
== |
__eq__ |
< |
__lt__ |
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
3. Conditions and Loops
Conditional Statements
if, elif, else
age = 18
if age < 13:
print("Child")
elif age < 18:
print("Teenager")
else:
print("Adult")
Ternary Operator
Compact conditional expression:
# Traditional if-else
if x > 0:
result = "positive"
else:
result = "non-positive"
# Ternary equivalent
result = "positive" if x > 0 else "non-positive"
match-case (Python 3.10+)
Structural pattern matching:
def handle_command(command):
match command.split():
case ["quit"]:
print("Quitting...")
case ["go", direction]:
print(f"Going {direction}")
case ["drop", *items]:
print(f"Dropping {', '.join(items)}")
case _:
print("Unknown command")
Loops
for loops
Iterate over sequences or any iterable:
# Iterating through a list
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
# Using range()
for i in range(5): # 0 to 4
print(i)
# With dictionary
person = {"name": "Alice", "age": 25}
for key, value in person.items():
print(f"{key}: {value}")
while loops
Execute while condition is true:
count = 0
while count < 5:
print(count)
count += 1
Loop Control
Statement | Description |
---|---|
break |
Exit the loop immediately |
continue |
Skip to next iteration |
pass |
Do nothing (placeholder) |
for num in range(10):
if num % 2 == 0:
continue # Skip even numbers
if num > 7:
break # Stop after 7
print(num)
else clause in loops
The else
block executes if the loop completes normally (no break
):
for n in range(2, 10):
for x in range(2, n):
if n % x == 0:
print(f"{n} equals {x} * {n//x}")
break
else:
print(f"{n} is a prime number")
Iteration Tools
Function | Description | Example |
---|---|---|
enumerate() |
Get index and value | for i, val in enumerate(lst): |
zip() |
Iterate multiple sequences | for a, b in zip(lst1, lst2): |
reversed() |
Reverse iteration | for x in reversed(lst): |
sorted() |
Iterate in sorted order | for x in sorted(lst): |
Comprehensions
List comprehensions
# Traditional approach
squares = []
for x in range(10):
squares.append(x**2)
# List comprehension
squares = [x**2 for x in range(10)]
# With condition
even_squares = [x**2 for x in range(10) if x % 2 == 0]
Dict comprehensions
# Create dictionary of squares
squares = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
Set comprehensions
unique_lengths = {len(x) for x in ["apple", "banana", "cherry"]}
# {5, 6}
Generator expressions
Memory-efficient alternative to list comprehensions:
# List comprehension (creates list)
sum_of_squares = sum([x**2 for x in range(1000000)])
# Generator expression (no list created)
sum_of_squares = sum(x**2 for x in range(1000000))
4. Type Casting
Implicit vs Explicit Casting
Python performs implicit type conversion in some cases:
# Integer + float → float
result = 3 + 4.5 # 7.5 (float)
# Boolean + integer → integer
result = True + 5 # 6 (True becomes 1)
Explicit casting uses constructor functions:
int("42") # 42 (string to integer)
float("3.14") # 3.14 (string to float)
str(100) # "100" (integer to string)
bool(0) # False (integer to boolean)
Common Conversions
Conversion | Example |
---|---|
String to number | int("42") , float("3.14") |
Number to string | str(100) |
Sequence conversions | tuple([1, 2, 3]) , list("hello") |
Dict to list | list({"a": 1, "b": 2}.items()) |
Edge Cases & Pitfalls
Handling ValueError
try:
num = int("abc")
except ValueError:
print("Invalid integer")
Lossy conversions
pi = 3.14159
int(pi) # 3 (decimal part truncated)
Boolean casting rules
All values are truthy except:
None
False
- Zero numeric values:
0
,0.0
,0j
- Empty sequences/collections:
""
,()
,[]
,{}
,set()
5. Exception Handling
Basic Try-Except
try:
result = 10 / 0
except ZeroDivisionError:
print("Cannot divide by zero")
else:
print("Division successful")
finally:
print("This always executes")
Common Built-in Exceptions
Exception | Description |
---|---|
ValueError |
Invalid value (e.g., int("abc") ) |
TypeError |
Invalid operation for type (e.g., "a" + 1 ) |
IndexError |
Sequence index out of range |
KeyError |
Dictionary key not found |
ZeroDivisionError |
Division by zero |
FileNotFoundError |
File doesn't exist |
Advanced Topics
Custom Exceptions
class InvalidEmailError(Exception):
"""Raised when email format is invalid"""
pass
def validate_email(email):
if "@" not in email:
raise InvalidEmailError("Email must contain @")
try:
validate_email("user.example.com")
except InvalidEmailError as e:
print(f"Error: {e}")
Exception Chaining
try:
open("nonexistent.txt")
except FileNotFoundError as e:
raise RuntimeError("Failed to open file") from e
assert Statements
For debugging and internal consistency checks:
def calculate_average(numbers):
assert len(numbers) > 0, "List cannot be empty"
return sum(numbers) / len(numbers)
Context Managers (with blocks)
# Automatically closes file after block
with open("file.txt") as f:
content = f.read()
Logging & Debugging
logging module
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
try:
1 / 0
except ZeroDivisionError:
logger.exception("Division by zero error")
traceback module
import traceback
try:
1 / 0
except:
traceback.print_exc() # Prints full traceback
6. Input/Output Basics
Console I/O
print() function
print("Hello", "World", sep=", ") # Hello, World
print("No newline", end="") # Prints without newline
input() function
name = input("Enter your name: ")
print(f"Hello, {name}")
File I/O
Opening files
Mode | Description |
---|---|
'r' |
Read (default) |
'w' |
Write (truncates existing) |
'a' |
Append |
'x' |
Exclusive creation (fails if exists) |
'b' |
Binary mode |
't' |
Text mode (default) |
'+' |
Updating (read/write) |
Reading files
# Using context manager (recommended)
with open("file.txt", "r") as f:
content = f.read() # Read entire file
lines = f.readlines() # Read as list of lines
line = f.readline() # Read single line
# Iterate line by line (memory efficient)
for line in f:
print(line.strip())
Writing files
with open("output.txt", "w") as f:
f.write("Line 1\n")
f.write("Line 2\n")
f.writelines(["Line 3\n", "Line 4\n"])
7. String Formatting
Methods
f-strings (Python 3.6+)
name = "Alice"
age = 25
print(f"My name is {name} and I'm {age} years old")
# Expressions inside f-strings
print(f"Next year I'll be {age + 1}")
.format() method
"Hello, {}. You are {} years old".format("Alice", 25)
"Coordinates: {latitude}, {longitude}".format(latitude="37.24N", longitude="115.81W")
% formatting (legacy)
"Hello, %s. You are %d years old" % ("Alice", 25)
Advanced Formatting
Format specifiers
# Number formatting
print(f"Pi: {3.1415926535:.2f}") # Pi: 3.14
# Alignment
print(f"{"left":<10}") # left
print(f"{"right":>10}") # right
print(f"{"center":^10}") # center
# Number bases
print(f"255 in hex: {255:#x}") # 0xff
print(f"255 in binary: {255:b}") # 11111111
Multiline strings
long_text = """This is a
multi-line string
that spans several lines"""
# With f-strings
name = "Alice"
message = f"""Dear {name},
Thank you for your order.
We'll ship it soon."""
8. Docstrings & Comments
Docstrings
Multi-line documentation strings (following PEP 257):
def calculate_area(width, height):
"""Calculate the area of a rectangle.
Args:
width (float): The width of the rectangle
height (float): The height of the rectangle
Returns:
float: The area of the rectangle
"""
return width * height
Accessing docstrings
print(calculate_area.__doc__)
help(calculate_area)
Comments
Single-line comments start with #
:
# This is a single-line comment
x = 5 # Inline comment
PEP 8 Guidelines:
- Use docstrings for public modules, functions, classes, and methods
- Keep comments up-to-date when code changes
- Comments should explain why, not what (code should be self-documenting)
- Use complete sentences in docstrings
9. Python's Execution Model
Script vs. Module
The special __name__
variable determines how a file is being used:
# my_module.py
def main():
print("This is the main function")
if __name__ == "__main__":
# Executed when run directly
main()
else:
# Executed when imported
print("Module imported")
Bytecode Compilation
Python compiles source code (.py
) to bytecode (.pyc
) for faster execution.
Namespaces and Scope
Python uses namespaces to organize variables:
- Local: Inside the current function
- Enclosing: For nested functions
- Global: At the module level
- Built-in: Python's built-in names
x = "global"
def outer():
x = "outer"
def inner():
nonlocal x # Refers to x in outer()
x = "inner"
inner()
print(x) # "inner"
outer()
print(x) # "global"