Complete Guide from Beginner to Professional Level (2025 Edition)
Understanding the processing nature and core structure of Python code is essential.
# Python Execution Flow:
# 1. Source Code (my_script.py) ->
# 2. Compiler ->
# 3. Bytecode (my_script.pyc) ->
# 4. Python Virtual Machine (PVM) ->
# 5. Machine Code Execution
# The variable 'score' is a name referring to the int object 95.
score = 95
# The variable 'msg' refers to the string object "Done".
msg = "Done"
# Expression: 10 + 5. It evaluates to the value 15.
result = 10 + 5
# Expression: score > 90. It evaluates to the Boolean value True.
is_high = score > 90
# Assignment Statement: Executes the action of binding a name to a value.
x = 10 * 2
# Conditional Statement: Executes a block of code based on a condition.
if x > 10:
print("X is large") # A print statement
A standard 64-bit integer can hold up to 263-1. Python handles integers far larger than this limit.
# This massive integer is handled seamlessly by Python's arbitrary precision.
large_number = 2**1000
print(f"Size in bytes: {large_number.bit_length() // 8}")
# Note: The actual Python object overhead is significantly larger than the value data itself.
Mastering basic I/O and strictly adhering to syntax rules are the first steps in programming.
print() and input()print() Function: Used to display output to the standard output device (console or terminal). It can take multiple arguments, which it prints separated by a space by default.
Syntax and Arguments: print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)
# Basic output
print("Hello", "World!") # Output: Hello World! (sep=' ' is default)
# Custom separator and end character
print("A", "B", "C", sep="|", end="---")
print("D")
# Output: A|B|C---D
# Printing variables and f-strings (formatted string literals)
name = "Mir Ali"
print(f"The user's name is {name}.")
input() Function: Used to get input from the user via the command line. It always returns the data as a string (str), which often requires explicit type casting.
# Prompt user and store input as a string
user_name = input("Enter your name: ")
print(f"Welcome, {user_name}!")
# Required type casting for numerical input
age_str = input("Enter your age: ") # Returns "30"
age_int = int(age_str) # Casts "30" to 30 (int)
print(f"In 10 years, you will be {age_int + 10}.")
IndentationError.
# Correct Indentation
if True:
print("Inside block") # Indented by 4 spaces
if 1 == 1:
print("Nested block") # Indented by 8 spaces
print("Outside block")
# Incorrect (leads to IndentationError)
# if True:
# print("Missing indentation")
myVariable, Myvariable, and myvariable are three distinct identifiers. Keywords (like True, False, if, for) must be written exactly as defined.
int x;). The type is inferred at runtime based on the value assigned, and the type of the variable can change upon re-assignment.
asyncio.
for, if, while, class, def, import, True, False, None) as variable or function names. Using them will result in a SyntaxError.
() – Parentheses (Grouping)** – Exponentiation+x, -x, ~x – Unary operations (Positive, Negative, Bitwise NOT)* , / , // , % – Multiplication, division, floor division, modulo+ , - – Addition, subtraction<< , >> – Bitwise shifts (Left Shift, Right Shift)& – Bitwise AND^ – Bitwise XOR| – Bitwise OR==, !=, <, <=, >, >=, is, is not, in, not in – Comparisons, identity, membershipnot – Logical NOT (Negation)and – Logical ANDor – Logical ORExample:
# Precedence ensures that ** is calculated before * and /, and they are calculated before +
result = 2 + 3 * 4 ** 2 / (5 - 1)
# 1. (5 - 1) -> 4 (Parentheses)
# 2. 4 ** 2 -> 16 (Exponentiation)
# 3. 3 * 16 -> 48 (Multiplication/Division - left to right)
# 4. 48 / 4 -> 12.0 (Multiplication/Division - left to right)
# 5. 2 + 12.0 -> 14.0 (Addition/Subtraction)
print(result) # Output: 14.0
The bool type represents truth values (True or False).
not True is False.Every object in Python has an implicit boolean value (Truthiness). Objects that evaluate to False are called "Falsy" values:
0, 0.0, 0j."" (empty string), () (empty tuple), [] (empty list), {} (empty dict/set).1, -10), and non-empty strings/lists (e.g., "False", [None]).
# Truthiness in action:
if "hello":
print("String is Truthy") # Executes, because "hello" is True
# Short-circuiting with 'and' and 'or'
result_and = 0 and "data" # 0 is Falsy, so it returns 0 (short-circuits)
result_or = 1 or "data" # 1 is Truthy, so it returns 1 (short-circuits)
print(f"AND result: {result_and}") # Output: 0
print(f"OR result: {result_or}") # Output: 1
Python has dynamic typing (you don't declare the type), but variables still hold typed objects.
3+5j).True (1) / False (0).[1, "a", True].(1, "a", True).{key: value}.None, representing the absence of a value.Mutability determines if an object can change its state after creation. Changes to immutable objects result in a new memory address (new object).
Example: Mutability in Action
x = 10 # int (immutable)
y = [1, 2, 3] # list (mutable)
# List modification (in place)
y.append(4) # modifies the list without changing its ID (usually)
print(y) # [1, 2, 3, 4]
# Tuple modification (requires new object)
a = (1, 2)
# a[0] = 5 # ERROR: This would raise a TypeError (immutable)
[-1] is the last item) and slicing (e.g., [1:4])..append(item): Adds a single item to the end of the list..insert(index, item): Inserts an item at a specified position without replacing the element already there..extend(iterable): Appends all elements from another iterable (list, tuple, etc.) to the end of the list..pop([index]): Removes and returns the item at a given index (or the last one if index is omitted)..remove(value): Removes the first occurrence of the specified value. Raises ValueError if the value is not found.del list[index]: A general statement for deleting an item at a specific index or a slice..clear(): Removes all items from the list..sort() (sorts in place), .reverse(), .count(value)..count(value): Returns the number of times a specified value occurs..index(value): Searches for the specified value and returns its position.a, b = (1, 2)).my_dict[key] raises a KeyError.my_dict[new_key] = new_value adds a new key-value pair or updates an existing one..update(other_dict): Merges another dictionary or iterable into the current one, updating existing keys and adding new ones..pop(key, [default]): Removes the specified key and returns its value. If the key is not found, it raises KeyError unless a default value is provided..popitem(): Removes and returns an arbitrary key/value pair (or the last one in Python 3.7+).del my_dict[key]: A statement for deleting a specific key-value pair. Raises KeyError if the key is not found..clear(): Removes all items from the dictionary..get(key, default) (safe retrieval), .keys(), .values(), .items().Slicing is a powerful syntax used for extracting a part (a subsequence) of an ordered collection using indices. It always returns a new collection of the same type (a new list, a new tuple, or a new string).
General Syntax: [start:stop:step]
Example: List and Tuple Slicing
data_list = [10, 20, 30, 40, 50, 60]
data_tuple = (1, 2, 3, 4, 5, 6, 7)
my_string = "Cybersecurity"
# Basic Slicing: [1:4] gets elements from index 1 up to (but not including) 4
print(f"List [1:4]: {data_list[1:4]}") # Output: [20, 30, 40]
print(f"Tuple [3:]: {data_tuple[3:]}") # Output: (4, 5, 6, 7) (from index 3 to end)
print(f"String [:5]: {my_string[:5]}") # Output: 'Cyber' (from start up to index 5)
# Negative Indices: count from the end
print(f"List [-3:]: {data_list[-3:]}") # Output: [40, 50, 60] (last 3 elements)
# Step/Stride: [::2] takes every second element
print(f"List [::2]: {data_list[::2]}") # Output: [10, 30, 50]
# Reversing a Collection (using a step of -1)
print(f"Tuple Reversed: {data_tuple[::-1]}") # Output: (7, 6, 5, 4, 3, 2, 1)
# Slicing Assignment (List only)
# Assigning a new list to a slice (this mutates the list)
data_list[1:3] = [25, 35] # Replace [20, 30] with [25, 35]
print(f"List After Slice Assignment: {data_list}") # Output: [10, 25, 35, 40, 50, 60]
List Manipulation
my_list = ['A', 'B', 'C', 'D']
print(f"Initial List: {my_list}")
# 1. ADDING
my_list.append('E') # Appends a single item
my_list.insert(1, 'X') # Inserts 'X' at index 1
my_list.extend(['F', 'G'])# Extends with an iterable
print(f"After Add: {my_list}") # Output: ['A', 'X', 'B', 'C', 'D', 'E', 'F', 'G']
# 2. REMOVING / DELETING
value_removed = my_list.pop(2) # Removes and returns item at index 2 ('B')
print(f"Popped Item: {value_removed}")
my_list.remove('D') # Removes the value 'D' (first occurrence)
del my_list[4] # Deletes item at index 4 ('F')
del my_list[2:4] # Deletes a slice ('C', 'E')
print(f"After Remove/Delete: {my_list}") # Output: ['A', 'X', 'G']
Tuple Manipulation (New Object Creation)
my_tuple = (1, 2, 3)
print(f"Initial Tuple: {my_tuple}")
# 1. ADDING (Must create a new tuple)
new_tuple = my_tuple + (4, 5) # Concatenation creates a new tuple (1, 2, 3, 4, 5)
print(f"After 'Add' (New Object): {new_tuple}")
# 2. REMOVING (Must create a new tuple without the desired element)
# Example: Create a new tuple without element 2
index_to_remove = 1
removed_tuple = my_tuple[:index_to_remove] + my_tuple[index_to_remove + 1:]
print(f"After 'Remove' (New Object): {removed_tuple}") # Output: (1, 3)
# 3. DELETING the tuple variable itself
# del my_tuple # This removes the 'my_tuple' name from memory
Dictionary Manipulation
my_dict = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
print(f"Initial Dict: {my_dict}")
# 1. ADDING / UPDATING
my_dict['e'] = 50 # Add new key 'e'
my_dict['a'] = 15 # Update existing key 'a'
my_dict.update({'f': 60, 'g': 70}) # Add multiple items via update
print(f"After Add/Update: {my_dict}") # Output: {'a': 15, 'b': 20, 'c': 30, 'd': 40, 'e': 50, 'f': 60, 'g': 70}
# 2. REMOVING / DELETING
popped_val = my_dict.pop('c') # Removes 'c' key and returns its value (30)
print(f"Popped Value: {popped_val}")
del my_dict['b'] # Deletes the key 'b'
print(f"After Remove/Delete: {my_dict}") # Output: {'a': 15, 'd': 40, 'e': 50, 'f': 60, 'g': 70}
Statements that dictate the sequence of code execution.
if / elif / else: Conditional execution blocks.for item in iterable:: Iterates over the elements of a sequence.while condition:: Repeats a block of code as long as the condition is True.break: Exits the nearest enclosing loop immediately.continue: Skips the rest of the code in the current loop iteration and moves to the next.pass: Does nothing. Used as a placeholder where a statement is syntactically required.match-case (Python 3.10+): Structural pattern matching.Example: Loop Control Keywords
# Loop from 1 to 9
for i in range(1, 10):
if i % 2 == 0:
continue # Skip even numbers (2, 4, 6, 8)
if i == 7:
break # Stop iteration when 7 is reached
print(i) # Prints 1, 3, 5
# Example of match-case (Python 3.10+)
status_code = 404
match status_code:
case 200:
print("OK")
case 404:
print("Not Found")
case _: # Default case
print("Unknown")
Functions are callable units that encapsulate a specific task.
def)Used for standard, multi-line, named functions. Supports documentation and type hinting.
def greet(name: str, age: int = 25) -> str:
"""
Return a greeting message. Uses type hints for clarity.
:param name: The person's name (str).
:param age: The person's age (int, default is 25).
:return: A formatted greeting string (str).
"""
return f"Hello {name}, you are {age} years old."
# Calling the function with and without the optional argument
message1 = greet("Mir Ali Shahidi", 39)
message2 = greet("A student")
print(message1) # Output: Hello Mir Ali Shahidi, you are 39 years old.
print(message2) # Output: Hello A student, you are 25 years old.
lambda)Used for small, single-expression functions. Cannot contain multiple statements.
# Lambda is restricted to a single expression
square = lambda x: x ** 2
print(f"5 squared is: {square(5)}") # Output: 5 squared is: 25
# Used often for sorting based on a complex key
points = [(1, 2), (0, 5), (3, 1)]
# Sort by the second element (index 1) of the tuple
sorted_by_y = sorted(points, key=lambda p: p[1])
print(f"Sorted by Y-axis: {sorted_by_y}") # Output: [(3, 1), (1, 2), (0, 5)]
The LEGB Rule defines the order Python looks for names (variables, functions, etc.) in a nested structure:
def or lambda).global keyword.print, len, range).
global_var = 10 # G (Global)
def outer_func():
enclosing_var = 20 # E (Enclosing)
def inner_func():
local_var = 30 # L (Local)
# Accessing variables in LEGB order
print(f"L: {local_var}")
print(f"E: {enclosing_var}")
print(f"G: {global_var}")
# Built-in is len()
print(f"B: {len([1, 2])}")
inner_func()
outer_func()
A programming paradigm centered around classes and objects.
Example: Class, Object, Attributes, and Methods
class Person:
species = "Homo sapiens" # Class attribute (shared by all instances)
def __init__(self, name: str, age: int):
"""Constructor: Initializes a new object with instance attributes."""
self.name = name # Instance attribute
self.age = age
def introduce(self):
"""Instance method: operates on the object's data (self.name, self.age)."""
return f"Hi, I'm {self.name} and I am {self.age}. We are all {self.species}."
# Creating an object (Instance)
p = Person("Alice", 30)
print(p.introduce())
print(Person.species) # Accessing Class Attribute
_private_ variables).
class Hacker(Person):
def __init__(self, name, age, skill):
super().__init__(name, age) # Call parent constructor
self.skill = skill
def hack(self):
return f"{self.name} is performing ethical hacking."
# h = Hacker("Mirali", 39, "Cybersecurity")
+ operator works for numbers (addition) and strings (concatenation).Methods surrounded by double underscores (e.g., __init__) are called Dunder Methods or Magic Methods. They define how objects interact with Python's core language features (operators, functions, built-ins).
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
# Defines behavior for the + operator (operator overloading)
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
# Defines the official string representation (used by repr() or debugging)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
# Defines behavior for the len() built-in function
def __len__(self):
return 2 # A vector always has x and y components
v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1 + v2 # Calls v1.__add__(v2)
print(v3) # Output: Vector(4, 6) (Calls __repr__)
print(len(v3)) # Output: 2 (Calls __len__)
self as the first argument, and operates on instance attributes.@classmethod): Takes the class itself (conventionally named cls) as the first argument. It can modify the class state (class attributes). Used as alternative constructors.@staticmethod): Takes neither self nor cls. It behaves like a regular function placed inside a class because it logically belongs there, but it does not interact with the class or instance state.These methods are essential for daily data manipulation across different types.
These are always available without needing to import a module.
len(obj): Returns the number of items in an object (length of a list, number of keys in a dict, etc.).type(obj): Returns the type of the object.id(obj): Returns the identity of an object (its memory address).range(start, stop, step): Returns an iterable sequence of numbers.sum(iterable): Sums the items of an iterable.max(iterable), min(iterable): Returns the largest/smallest item in an iterable.sorted(iterable): Returns a new sorted list from the items in the iterable (does not sort in place).zip(*iterables): Combines multiple iterables element-wise, returning an iterator of tuples.map(func, iterable): Applies a function to all the items in an input list and returns an iterator.filter(func, iterable): Filters an iterable by a function that returns True/False for each item, returning an iterator.iter(obj), next(iterator): Used for manual iteration over iterables (see section 9).
data = [3, 1, 4, 2]
print(f"Sorted (New List): {sorted(data)}") # [1, 2, 3, 4]
print(f"Original Data: {data}") # [3, 1, 4, 2] (No change)
text = " Python is awesome "
# Strings are immutable; methods return a new string
print(f".upper(): {text.upper()}") # Output: ' PYTHON IS AWESOME '
print(f".strip(): {text.strip()}") # Output: 'Python is awesome' (removes leading/trailing whitespace)
print(f".split(): {text.strip().split()}") # Output: ['Python', 'is', 'awesome']
print(f".join(): {'-'.join(['A', 'B', 'C'])}") # Output: 'A-B-C' (joins elements of an iterable)
print(f".replace(): {text.replace('Python', 'Java')}") # Output: ' Java is awesome '
print(f".startswith(): {text.strip().startswith('P')}") # Output: True
data = [10, 50, 20]
data.append(40) # Adds 40 to the end
data.insert(1, 15) # Inserts 15 at index 1 -> [10, 15, 50, 20, 40]
other = [30, 60]
data.extend(other) # Merges 'other' into 'data'
print(f"Extended List: {data}") # [10, 15, 50, 20, 40, 30, 60]
data.pop(2) # Removes and returns element at index 2 (50 is removed)
data.remove(20) # Removes the first occurrence of the VALUE 20
data.sort() # Sorts the list in place (mutates the list)
print(f"Final Sorted List: {data}") # [10, 15, 30, 40, 60]
print(f"Length (len()): {len(data)}") # Output: 5 (len() is a built-in function, not a method)
user = {'id': 101, 'name': 'Alice', 'role': 'Admin'}
# .get() provides safe access, returning default if key is missing
print(f"Value for 'role': {user.get('role', 'Guest')}") # Output: Admin
print(f"Value for 'email' (safe): {user.get('email', 'N/A')}") # Output: N/A
user.update({'role': 'Manager', 'city': 'NY'}) # Updates existing and adds new keys
print(f"Keys View: {user.keys()}") # Returns a dynamic view object
print(f"Items View: {user.items()}")
removed_role = user.pop('role') # Removes the key and returns its value
print(f"Removed Item: {removed_role}") # Output: Manager
A = {1, 2, 3, 4}
B = {3, 4, 5, 6}
A.add(5) # Adds 5 to set A
A.remove(1) # Removes 1 from set A. Raises KeyError if 1 is not present.
print(f"Union (A | B): {A.union(B)}") # {2, 3, 4, 5, 6} (Elements in A or B)
print(f"Intersection (A & B): {A.intersection(B)}") # {3, 4, 5} (Elements common to A and B)
print(f"Difference (A - B): {A.difference(B)}") # {2} (Elements in A but not B)
Mastering these concepts allows for more efficient, Pythonic, and robust code.
__next__() method to yield the next element.
my_list = [10, 20, 30] # Iterable
my_iterator = iter(my_list) # Get Iterator using the iter() built-in function
print(f"1st item: {next(my_iterator)}") # Output: 1st item: 10
print(f"2nd item: {next(my_iterator)}") # Output: 2nd item: 20
# next(my_iterator) is called implicitly by a for-loop until StopIteration is raised.
yield keyword. Generators are iterators that generate values on the fly (lazy evaluation), suspending execution after yield and resuming on the next call to next(). They save memory for large sequences.
def square_numbers(n):
print("Generator initialized...")
for i in range(n):
yield i * i # Pauses here, returns i*i
print(f"Resumed for i={i+1}")
gen = square_numbers(3)
print(f"First square: {next(gen)}") # Output: Generator initialized... First square: 0
print(f"Second square: {next(gen)}") # Output: Resumed for i=1 / Second square: 1
# List Comprehension [expression for item in iterable if condition]
squares = [x**2 for x in range(10) if x % 2 == 0]
print(f"Squares of Evens: {squares}") # Output: [0, 4, 16, 36, 64]
# Dictionary Comprehension {key_expr: value_expr for item in iterable}
status_list = [("A", 200), ("B", 404), ("C", 500)]
status_dict = {name: code for name, code in status_list if code < 500}
print(f"Status Dict: {status_dict}") # Output: {'A': 200, 'B': 404}
with statement. The __enter__ method prepares the resource, and the __exit__ method cleans it up, even if an exception occurs.
# Context Manager Example (File Handling)
with open("temp.txt", "w") as file:
# file is open here (via __enter__)
file.write("Data written safely.")
# 'file' is automatically closed here (via __exit__), guaranteeing resource release.
print("File operation finished and resource closed.")
def multiplier(n):
# n is remembered by the inner function (closure)
def calculate(x):
return x * n
return calculate # Returns the inner function
times_five = multiplier(5) # n=5 is closed over
print(f"10 * 5 = {times_five(10)}") # Output: 50
These topics are crucial for writing enterprise-level, maintainable, and highly flexible code.
A Decorator is a function that takes another function and extends its behavior without modifying the original function's source code. This is used for logging, timing, authorization, and caching. The @ syntax is syntactic sugar for function = decorator(function).
def log_execution(func):
"""A simple decorator to log function calls."""
def wrapper(*args, **kwargs):
print(f"--- Calling {func.__name__} with arguments: {args} ---")
result = func(*args, **kwargs)
print(f"--- {func.__name__} returned: {result} ---")
return result
return wrapper
@log_execution
def add(a, b):
return a + b
add(10, 5)
# Output:
# --- Calling add with arguments: (10, 5) ---
# --- add returned: 15 ---
Keywords async (defines a coroutine) and await (pauses a coroutine non-blockingly) are central to the asyncio framework. This enables efficient handling of numerous I/O-bound tasks (network requests, database queries) concurrently on a single thread. This is different from parallelism (multithreading/multiprocessing).
import asyncio
import time
async def fetch_data(delay):
"""A coroutine simulating an I/O delay."""
print(f"Starting fetch data after {delay}s...")
await asyncio.sleep(delay) # Non-blocking wait: Python can switch to another task
return f"Data fetched after {delay} seconds."
async def run_concurrent():
start = time.time()
# Execute multiple tasks concurrently, waiting for all to complete
results = await asyncio.gather(
fetch_data(3), # Longest task
fetch_data(1) # Shortest task
)
# The total time is ~3 seconds, as they run at the same time.
print(f"Results: {results}")
print(f"Total time: {time.time() - start:.2f}s")
# This must be run outside this block using: asyncio.run(run_concurrent())
The "class of a class." A Metaclass inherits from type and controls the class creation process (the mechanism that reads the class definition and creates the class object). They are highly advanced tools used in frameworks to enforce coding standards, auto-register classes, or inject methods dynamically.
class SecureAttributeMeta(type):
"""Metaclass that automatically adds an 'audit_log' list to every class created."""
def __new__(mcs, name, bases, attrs):
# Enforce rule: All new classes must have a logging mechanism
if 'audit_log' not in attrs:
attrs['audit_log'] = []
# Call the standard type creation process
return super().__new__(mcs, name, bases, attrs)
class DatabaseConnector(metaclass=SecureAttributeMeta):
# This class automatically inherits the 'audit_log' attribute
pass
print(f"Connector has log: {DatabaseConnector.audit_log}") # Output: []
DatabaseConnector.audit_log.append("Connection successful")
print(f"Log updated: {DatabaseConnector.audit_log}") # Output: ['Connection successful']
Writing production-ready code requires disciplined error handling and adherence to standards.
Use the full structure for robust error management and guaranteed resource cleanup.
file = None
try:
# 1. Try: Code that might raise an exception
# file = open("non_existent_file.txt", "r") # Example: Raises FileNotFoundError
result = 10 / 0 # Raises ZeroDivisionError
except FileNotFoundError:
# 2. Except: Handle specific known error
print("Handled: The file was not found.")
except ZeroDivisionError:
# 3. Except: Handle specific known error
print("Handled: Cannot divide by zero.")
except Exception as e:
# 4. Except (Catch-all): Catch all other unexpected errors
print(f"Caught unexpected error: {e}")
else:
# 5. Else: Executes ONLY if the try block completes WITHOUT any exception
print("Success: No errors occurred.")
finally:
# 6. Finally: ALWAYS executes, regardless of success or failure (Essential for cleanup)
if file:
file.close()
print("Resource cleanup finished.")
For professional code, define and raise specific exception classes to make error handling clearer for the caller.
class SecurityError(Exception):
"""Custom exception for security-related issues."""
pass
def check_permission(user_role):
if user_role != "Admin":
raise SecurityError(f"User {user_role} is unauthorized.")
print("Permission granted.")
try:
check_permission("Guest")
except SecurityError as e:
print(f"Access Denied: {e}")
# Output: Access Denied: User Guest is unauthorized.
Use triple quotes ("""...""") to document modules, functions, classes, and methods. PEP 257 defines the conventions for writing effective docstrings, typically covering parameters (Args), return value (Returns), and possible exceptions (Raises).
def analyze_network_traffic(data: bytes, threshold: int) -> bool:
"""
Analyzes network data for malicious activity exceeding a threshold.
Args:
data: The raw network packet data (bytes).
threshold: The maximum acceptable threat level score (int).
Returns:
True if the threat level is acceptable, False otherwise (bool).
Raises:
ValueError: If data is empty.
"""
if not data:
raise ValueError("Input data cannot be empty.")
# Implementation logic...
return True
# help(analyze_network_traffic) will display this documentation.
Annotations (param: type -> return_type) that specify the expected data types. This is purely for static analysis (tools like Mypy) and IDE assistance; Python does not enforce them at runtime.
from typing import List, Dict, Optional, Union
# Optional: may be T or None / Union: may be one of the specified types
def get_user_config(user_id: int) -> Optional[Dict[str, Union[str, int]]]:
"""Returns user config dictionary or None if not found."""
if user_id == 1:
return {"name": "Mirali", "level": 5}
return None
config = get_user_config(1)
print(f"Config is: {config}") # Static analysis ensures config is handled correctly
unittest)Ensuring that the smallest testable parts of an application (units) are individually correct. The unittest module provides the foundational framework.
import unittest
def is_secure_password(password):
"""Checks if a password is secure (length > 8)."""
return len(password) > 8
class TestSecurity(unittest.TestCase):
# Test method for a successful case
def test_long_password(self):
self.assertTrue(is_secure_password("a_strong_password_123"))
# Test method for a failure case
def test_short_password(self):
# Checks if the result is False
self.assertFalse(is_secure_password("short"))
# To run tests from the command line: python -m unittest filename.py
A software development process where development cycles are driven by writing failing tests before writing the necessary code to make those tests pass. This ensures high test coverage and robust design from the start.
Mastering these tools is essential for working in team environments and large-scale projects.
venv/virtualenv): Creates isolated Python environments to manage project-specific dependencies without conflicting with global packages.
# Creation: python -m venv my_project_env
# Activation (Linux/macOS): source my_project_env/bin/activate
# Activation (Windows): .\my_project_env\Scripts\activate
# Saving Dependencies: pip freeze > requirements.txt
# Installation: pip install -r requirements.txt
pyproject.toml): Defining metadata and dependencies for a Python package to be installed via PyPI. This is the modern standard, replacing setup.py.These modules are included with Python and must be known for professional scripting.
os & pathlib: For interacting with the operating system (e.g., file paths, environment variables). pathlib provides an object-oriented way to handle paths.
import os
from pathlib import Path
# os.getcwd() -> Current directory
# Path("data/file.txt").exists() -> Check existence
json: Encoding Python objects into JSON strings and decoding JSON strings back into Python objects (essential for web services).datetime: Working with dates, times, and time deltas with high precision.logging: The professional standard for recording events and errors, replacing simple print() statements in production code.
import logging
logging.basicConfig(level=logging.INFO)
logging.info("A process started.")
Rules for bringing external code into your current file:
import module_name: Imports the module, requiring all access to be prefixed (e.g., math.sqrt()).import module_name as alias: Imports the module with a shorter name (e.g., import numpy as np).from module import name: Imports only the specified name(s) from the module, allowing direct access without prefix (e.g., from math import sqrt; sqrt(4)).from module import *): This practice pollutes the current namespace and makes it difficult to track where functions originate. It is generally discouraged in production code.Efficient and secure file input/output (I/O) is crucial for data persistence and logging. Using the with open(...) statement is the professional standard as it guarantees the file is closed, even if errors occur.
The open(filename, mode) function uses specific modes to define how the file is accessed:
FileNotFoundError if the file doesn't exist.FileExistsError if the file already exists.bytes (e.g., 'rb', 'wb').Using the 'w' mode completely clears the file before writing the new content.
Example: Creating and Overwriting a File
filename = "security_report.txt"
content_to_write = [
"--- System Audit Log ---",
"Status: Clean",
"Time: 2025-11-25 12:00:00"
]
# Use 'w' mode to overwrite existing content
with open(filename, 'w') as f:
f.write("Initial write: This will be replaced.\n")
f.writelines(line + "\n" for line in content_to_write)
print(f"File '{filename}' created and overwritten.")
# The file now contains the content_to_write list.
The 'r' mode is used to retrieve content from a file.
Example: Reading Methods
# Re-using the file created above
read_filename = "security_report.txt"
# Method 1: Reading the entire file content as a single string
with open(read_filename, 'r') as f:
full_content = f.read()
print("--- Full Content (read()) ---")
print(full_content)
# Method 2: Reading content line by line (most memory-efficient for large files)
with open(read_filename, 'r') as f:
print("--- Reading Line by Line (readlines()) ---")
lines = f.readlines()
for i, line in enumerate(lines):
print(f"Line {i}: {line.strip()}")
# Method 3: Iterating directly over the file object (the best way)
print("--- Iterating Directly ---")
with open(read_filename, 'r') as f:
for line in f:
if "Status" in line:
print(f"Found status line: {line.strip()}")
The 'a' (append) mode adds new data to the end of the file without deleting the existing content. The 'r+' mode allows reading and writing, keeping the original content.
Example: Appending Data ('a')
append_filename = "security_report.txt"
# Use 'a' mode to append to the end
with open(append_filename, 'a') as f:
f.write("\nNew entry: Integrity Check Passed.")
print(f"Appended a new line to '{append_filename}'.")
# Reading the appended content
with open(append_filename, 'r') as f:
print("--- Appended Content Check ---")
print(f.read())
When working with files in read/write modes, the file pointer (cursor) dictates the next position for I/O. seek() moves the pointer, and tell() reports its current position.
tell(): Returns the current position of the file pointer (number of bytes from the beginning).seek(offset, whence): Changes the file pointer's position.
whence = 0 (default): Offset from the start of the file.whence = 1 (current): Offset from the current position.whence = 2 (end): Offset from the end of the file.Example: Rewriting a specific line ('r+')
pointer_filename = "data_file.txt"
# Create dummy file with 'w'
with open(pointer_filename, 'w') as f:
f.write("Line 1: Old Data\nLine 2: Target Data\nLine 3: Final Data")
# Open in 'r+' mode (Read/Write, pointer starts at 0)
with open(pointer_filename, 'r+') as f:
# Read the first line to move the pointer
first_line = f.readline()
print(f"Current Position (after reading Line 1): {f.tell()}") # e.g., 18
# Use seek to go back 5 characters (to overwrite 'Old Data' with 'New Data')
# Since we are in text mode, seek is complicated, but for simplicity:
f.seek(10) # Manually moving pointer back to start of 'Old'
# Overwrite the data from the current position
f.write("Updated Data") # Overwrites 'Target Data' and moves pointer
# Move pointer to the beginning to read the new content
f.seek(0)
print("--- Content After Seek and Write ---")
print(f.read())
# Output: Line 1: Updated Data\nLine 3: Final Data (Note: The length of the new string matters in r+!)
When dealing with non-text files (images, compressed files), the 'b' mode is mandatory. I/O operations must use bytes objects instead of str.
Example: Writing Binary Data ('wb')
binary_filename = "binary_log.bin"
binary_data = b'\x48\x65\x6C\x6C\x6F' # ASCII for "Hello" in bytes
with open(binary_filename, 'wb') as f:
f.write(binary_data)
print(f"Wrote {len(binary_data)} bytes to '{binary_filename}'.")
# Reading binary data ('rb')
with open(binary_filename, 'rb') as f:
data = f.read()
print(f"Read binary data: {data}") # Output: b'Hello'
Happy Coding! Keep practicing and building projects. For professional consulting, contact Mir Ali Shahidi via miralishahidi.ir.