DEV Community

Cover image for Python Notes #1 - General Introduction
Elvin Seyidov
Elvin Seyidov

Posted on • Edited on • Originally published at elvinseyidov.Medium

Python Notes #1 - General Introduction

Python’s key features

  • Python is dynamically typed, interpreted, and supports multiple programming paradigms (OOP, functional, procedural). It has a large standard library and automatic memory management.

Python memory management

  • Python uses a private heap for memory management and has built-in garbage collection using reference counting and cyclic garbage collection.

Deep copy and shallow copy

  • A shallow copy creates a new object but references the original elements, while a deep copy creates a completely independent copy of the original object and its elements.

Global Interpreter Lock (GIL)

  • GIL is a mutex in CPython that allows only one thread to execute at a time, limiting true parallel execution in multi-threaded programs.

Python dynamic typing handling

  • Python determines variable types at runtime, making development faster but potentially leading to runtime errors if types are misused.

CPython and Python’s different implementations

  • CPython is the default interpreter, PyPy offers JIT compilation for speed, Jython runs on the JVM, and IronPython integrates with .NET.
  • CPython is the default and most widely used implementation of Python, written in C. It compiles Python code into bytecode and executes it using a stack-based virtual machine. CPython includes a built-in garbage collector and follows the Global Interpreter Lock (GIL), which restricts true parallel execution in multi-threaded programs. It is the reference implementation of Python, meaning all other implementations (like PyPy, Jython, and IronPython) aim for compatibility with CPython.

Python’s exception handling

  • Python uses try-except-finally blocks to handle runtime errors and prevent crashes. Exceptions help manage unexpected conditions like invalid input, file errors, or network failures.

✅ Basic Exception Handling

try:
    x = 1 / 0  # Division by zero error
except ZeroDivisionError:
    print("Cannot divide by zero")
Enter fullscreen mode Exit fullscreen mode

✅ Catching Multiple Exceptions

try:
    num = int(input("Enter a number: "))  # Can raise ValueError
    result = 10 / num  # Can raise ZeroDivisionError
except (ValueError, ZeroDivisionError) as e:
    print(f"Error occurred: {e}")
Enter fullscreen mode Exit fullscreen mode

✅ Using else with try (Only Runs if No Exception Occurs)

try:
    file = open("data.txt", "r")
except FileNotFoundError:
    print("File not found")
else:
    print(file.read())  # Runs only if no exception occurs
Enter fullscreen mode Exit fullscreen mode

finally Block (Always Executes)

try:
    file = open("data.txt", "r")
finally:
    file.close()  # Ensures the file is closed no matter what
Enter fullscreen mode Exit fullscreen mode

✅ Raising Custom Exceptions

def check_age(age):
    if age < 18:
        raise ValueError("Age must be 18 or above")
    return "Access granted"

print(check_age(15))  # Raises ValueError
Enter fullscreen mode Exit fullscreen mode

✅ Defining Custom Exception Classes

class CustomError(Exception):
    """Custom exception example."""
    pass

try:
    raise CustomError("Something went wrong!")
except CustomError as e:
    print(e)
Enter fullscreen mode Exit fullscreen mode

✅ Best Practices

  • Catch specific exceptions (avoid generic except Exception:).
  • Use finally for resource cleanup (closing files, database connections).
  • Keep exception handling lightweight (avoid swallowing errors).
  • Log errors properly for debugging instead of printing (logging module).

Python’s logging Module

Python’s logging module provides structured logging with different levels (DEBUG, INFO, WARNING, ERROR, CRITICAL) for debugging and monitoring.
✅ Basic Logging

import logging

logging.basicConfig(level=logging.DEBUG)  # Set log level
logging.debug("Debugging info")  
logging.info("General info")  
logging.warning("Warning!")  
logging.error("Error occurred")  
logging.critical("Critical issue")  
Enter fullscreen mode Exit fullscreen mode

✅ Logging to a File

logging.basicConfig(filename="app.log", level=logging.INFO,
                    format="%(asctime)s - %(levelname)s - %(message)s")
logging.info("Application started")
Enter fullscreen mode Exit fullscreen mode

✅ Logging Exceptions

try:
    1 / 0
except ZeroDivisionError:
    logging.error("Exception occurred", exc_info=True)
Enter fullscreen mode Exit fullscreen mode

✅ Best Practices

  • Use log levels (DEBUG, INFO, etc.).
  • Avoid print() in production.
  • Write logs to files for debugging.
  • Use structured logs with timestamps & file names.
  • The logging module improves debugging & production monitoring!

Python Execution Model

  • Python code is first compiled into bytecode (.pyc files), which is then executed by the Python Virtual Machine (PVM). The PVM interprets bytecode and interacts with the system to execute the program. (source code → bytecode → interpreter)

Python Standard Library & Built-in Modules

Python comes with a rich standard library that provides built-in modules for common tasks:

  • sys – System-specific parameters and functions (sys.argv, sys.exit())
  • os – Interacting with the operating system (os.path, os.environ)
  • math – Mathematical operations (math.sqrt(), math.pi)
  • random – Random number generation (random.randint(), random.choice())
  • datetime – Handling dates and times (datetime.datetime.now(), timedelta)

Using pip to Install Packages

Python uses pip (Python Package Installer) to manage third-party libraries. Install packages with:

pip install package_name
Enter fullscreen mode Exit fullscreen mode

Upgrade packages:

pip install --upgrade package_name
Enter fullscreen mode Exit fullscreen mode

List installed packages:

pip list
Enter fullscreen mode Exit fullscreen mode

Use virtual environments (venv) to keep dependencies isolated.


Importance of PEP 8 (Python Style Guide)

PEP 8 is Python’s official style guide that ensures readability and consistency. Key rules:

  • Use 4 spaces per indentation
  • Keep line length ≤ 79 characters
  • Use meaningful variable names
  • Follow snake_case for functions and variables
  • Add docstrings and comments
  • Use flake8 or black for automatic PEP 8 checks.

Using Docstrings and Comments

  • Comments (#) explain code but are ignored at runtime.
  • Docstrings (""" """) provide documentation inside functions, classes, and modules.
# This is a comment
def add(x, y):
    """Returns the sum of x and y."""
    return x + y
# Use help(add) to view the function docstring.
Enter fullscreen mode Exit fullscreen mode

Unicode Handling (str vs bytes)

  • str: Represents Unicode text (default in Python 3).
  • bytes: Represents binary data (e.g., encoded strings, files, network data).
text = "hello"  # Unicode string
binary = b"hello"  # Byte string

encoded = text.encode("utf-8")  # Convert str → bytes
decoded = encoded.decode("utf-8")  # Convert bytes → str
Enter fullscreen mode Exit fullscreen mode

Integer Division Behavior (/ vs //)

  • / (True division): Always returns a float (even for integers).
  • // (Floor division): Discards the decimal, returns an integer.
print(5 / 2)  # 2.5
print(4 / 2)  # 2.0
print(5 // 2)  # 2
print(4 // 2)  # 2
Enter fullscreen mode Exit fullscreen mode

Brief Mention of asyncio

Python’s asyncio module enables asynchronous programming, allowing tasks to run concurrently without blocking execution. It is useful for I/O-bound operations like network requests, file handling, and database queries.
Example:

import asyncio

async def say_hello():
    await asyncio.sleep(1)
    print("Hello, Async!")

asyncio.run(say_hello())
Enter fullscreen mode Exit fullscreen mode
  • Key Concepts: async def, await, event loop, asyncio.run().
  • Use Cases: Web scraping, API calls, chat servers, background tasks.
  • Not a replacement for threading/multiprocessing but great for non-blocking I/O.

How Python Supports Concurrency Despite GIL

Python’s Global Interpreter Lock (GIL) prevents multiple threads from executing Python bytecode simultaneously in CPython, limiting true parallelism in multi-threaded programs.
However, Python still supports concurrency through:

  • Multi-threading (threading module) – Suitable for I/O-bound tasks (e.g., API calls, file operations) since GIL releases the lock during I/O.
  • Multi-processing (multiprocessing module) – Bypasses GIL by using separate processes, allowing true parallel execution for CPU-bound tasks.
  • Asynchronous programming (asyncio) – Uses an event loop to run non-blocking tasks concurrently without creating multiple threads or processes. Example: Multiprocessing for True Parallelism
from multiprocessing import Pool

def square(n):
    return n * n

with Pool(processes=4) as pool:
    print(pool.map(square, [1, 2, 3, 4]))  # Runs in parallel
Enter fullscreen mode Exit fullscreen mode

✅ Use threads for I/O-bound tasks and multiprocessing for CPU-heavy computations.


with Statement & Context Managers in Python

The with statement is used to manage resources efficiently by ensuring proper setup and cleanup, even in case of errors. It automatically calls __enter__() when entering the block and __exit__() when exiting.

✅ Using with for File Handling (Auto-Close Files)

with open("data.txt", "r") as file:
    content = file.read()  # No need for `file.close()`
Enter fullscreen mode Exit fullscreen mode

✔ Ensures the file closes automatically after exiting the block.

✅ Using with for Database Connections

import sqlite3

with sqlite3.connect("mydb.sqlite") as conn:
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM users")
Enter fullscreen mode Exit fullscreen mode

✔ No need to manually close the connection.

✅ Using with for Locking Threads

from threading import Lock

lock = Lock()

with lock:  
    # Critical section (automatically releases lock)
    print("Thread-safe operation")
Enter fullscreen mode Exit fullscreen mode

✔ Ensures the lock is released properly after execution.

✅ Creating a Custom Context Manager (__enter__() & __exit__())

class ManagedResource:
    def __enter__(self):
        print("Resource acquired")
        return self  # Object returned inside `with`

    def __exit__(self, exc_type, exc_value, traceback):
        print("Resource released")

with ManagedResource() as res:
    print("Using resource")
Enter fullscreen mode Exit fullscreen mode

✔ Manages resource allocation and cleanup automatically.

✅ Best Practices

  • Use with whenever working with files, database connections, locks, network sockets.
  • Prevents resource leaks and improves readability.
  • Prefer built-in context managers (open(), threading.Lock(), sqlite3.connect()) when available.
  • Using with ensures reliable resource management in Python!

Best Practices & Common Beginner Pitfalls in Python

✅ Best Practices

  • Follow PEP 8 for Clean Code Use meaningful variable names, proper indentation, and consistent formatting.
# ✅ Good
def calculate_area(radius):
    return 3.14 * radius ** 2

# ❌ Bad
def ca(r): return 3.14*r*r
Enter fullscreen mode Exit fullscreen mode
  • Use Virtual Environments (venv) Always create isolated environments to manage dependencies and avoid conflicts.
python -m venv myenv
source myenv/bin/activate  # (Linux/macOS)
myenv\Scripts\activate     # (Windows)
Enter fullscreen mode Exit fullscreen mode
  • Use get() to Avoid KeyErrors in Dictionaries
data = {"name": "Alice"}
print(data.get("age", "Unknown"))  # Instead of data["age"]
Enter fullscreen mode Exit fullscreen mode
  • Use List Comprehensions for Cleaner Code
squared = [x**2 for x in range(10)]
Enter fullscreen mode Exit fullscreen mode
  • Use with for File Handling (Auto Close Files)
with open("file.txt", "r") as f:
    content = f.read()
Enter fullscreen mode Exit fullscreen mode
  • Use enumerate() Instead of Manual Indexing
for i, item in enumerate(["a", "b", "c"]):
    print(i, item)
Enter fullscreen mode Exit fullscreen mode
  • Use zip() to Iterate Multiple Sequences Simultaneously
names = ["Alice", "Bob"]
scores = [85, 90]
for name, score in zip(names, scores):
    print(name, score)
Enter fullscreen mode Exit fullscreen mode

❌ Common Beginner Pitfalls
❌ Modifying a List While Iterating

# Wrong
nums = [1, 2, 3, 4]
for num in nums:
    if num % 2 == 0:
        nums.remove(num)  # ❌ Unexpected behavior

# Correct
nums = [num for num in nums if num % 2 != 0]
Enter fullscreen mode Exit fullscreen mode

❌ Using Mutable Default Arguments in Functions

# Wrong (Mutable default argument)
def add_item(item, lst=[]):
    lst.append(item)
    return lst

print(add_item(1))  # [1]
print(add_item(2))  # [1, 2] ❌ Unexpected!

# Correct
def add_item(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst
Enter fullscreen mode Exit fullscreen mode

❌ Not Handling Exceptions Properly

# Wrong
value = int(input("Enter a number: "))  # Crashes on invalid input

# Correct
try:
    value = int(input("Enter a number: "))
except ValueError:
    print("Invalid input!")
Enter fullscreen mode Exit fullscreen mode

❌ Misusing is Instead of == for Comparisons

x = 1000
print(x == 1000)  # ✅ True (Correct)
print(x is 1000)  # ❌ May be False (Don't use `is` for value comparisons)
Enter fullscreen mode Exit fullscreen mode

✅ Final Tips:

  • Use type hints for better readability (def greet(name: str) -> str:).
  • Avoid global variables inside functions.
  • Learn iterators, generators, and functional programming early.
  • Profile and optimize performance only when necessary.

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

Top comments (0)

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

👋 Kindness is contagious

If you found this post useful, please drop a ❤️ or leave a kind comment!

Okay