DevTools

Cheatsheet Python

Linguagem de programação versátil e fácil de aprender

Back to languages
Python
109 cards found
Categories:
Versions:

Basic Syntax


12 cards
Variables and Assignment
name = "Anna"
age = 30
price = 9.99
active = True

# Multiple assignment
x, y, z = 1, 2, 3
a = b = c = 0

# Elegant swap
x, y = y, x

# Type hints (optional)
name: str = "Anna"
age: int = 30

Python has dynamic typing: you don't need to declare the type. Multiple assignment with commas. x, y = y, x swaps without a temporary variable. Type hints are optional and not enforced at runtime.

Operators
# Arithmetic
10 + 3   # 13
10 - 3   # 7
10 * 3   # 30
10 / 3   # 3.333 (float)
10 // 3  # 3 (integer division)
10 % 3   # 1 (remainder)
2 ** 10  # 1024 (power)

# Comparison
5 == 5   # True
5 != 3   # True
5 > 3    # True

# Logical
True and False  # False
True or False   # True
not True        # False

# Identity and membership
x is None       # True/False
5 in [1, 2, 5]  # True

/ always returns a float; // does integer division. ** is power. is compares identity (same object), == compares value. in checks membership.

Comments and Documentation
# Line comment

"""
Multi-line docstring
for modules, classes and functions.
"""

def sum(a: int, b: int) -> int:
    """Sums two numbers.

    Args:
        a: First number
        b: Second number

    Returns:
        The sum of a and b
    """
    return a + b

# Access the docstring
help(sum)
print(sum.__doc__)

# for comments. """...""" (docstring) documents functions/classes. Convention: use the Google style or NumPy style format. help() shows the docstring. Tools like Sphinx generate automatic docs.

Data Types
# Primitive types
integer = 10        # int
decimal = 3.14      # float
complex = 2 + 3j   # complex
text = "Hello"       # str
active = True        # bool
empty = None        # NoneType

# Collections
list = [1, 2, 3]   # list
tuplo = (1, 2, 3)   # tuple
dicio = {"a": 1}    # dict
conj  = {1, 2, 3}   # set

# Check type
type(42)            # <class 'int'>
isinstance(42, int) # True

Built-in types: int, float, str, bool, NoneType. Collections: list, tuple, dict, set. Use type() or isinstance() to check.

Conversions (Casting)
int("42")       # 42
float("3.14")   # 3.14
str(100)        # "100"
bool(0)         # False
bool("text")   # True

list("abc")     # ['a', 'b', 'c']
tuple([1, 2])   # (1, 2)
set([1, 1, 2])  # {1, 2}
dict([("a", 1)])  # {"a": 1}

int(3.9)        # 3 (truncates!)
round(3.9)      # 4
round(3.14159, 2)  # 3.14

Conversions: int(), float(), str(), bool(). int() truncates decimals (doesn't round). round() rounds. Falsy values: 0, "", [], None, False.

Identity and Membership Operators
# is / is not (identity)
a = [1, 2, 3]
b = [1, 2, 3]
a == b      # True (same value)
a is b      # False (different objects)

c = a
c is a      # True (same object)

# in / not in (membership)
5 in [1, 2, 5]        # True
"x" not in "text"    # False
"a" in {"a": 1}       # True (key)
3 in (1, 2, 3)        # True

# None: always with is
if value is None:
    pass
if value is not None:
    pass

is checks whether they are the same object (identity). == compares values. Use is None for None. in/not in checks membership in lists, dicts, strings, tuples and sets.

Strings — Methods
s = "  Hello World  "

s.strip()            # "Hello World"
s.upper()            # "  HELLO WORLD  "
s.lower()            # "  hello world  "
s.title()            # "  Hello World  "
s.split()            # ["Hello", "World"]
s.replace("World", "Python")
s.startswith("Hello")  # False (has spaces)
s.find("World")      # 6 (index)
s.count("o")         # 2
len(s)               # 13

# Concatenation and repetition
"Hello" + " " + "World"
"ha" * 3             # "hahaha"

Strings are immutable: methods return a new string. strip() removes spaces. split() splits into a list. find() returns the index (-1 if not found). len() gives the length.

Input and Output
# Output
print("Hello", "World")         # Hello World
print("a", "b", sep="-")      # a-b
print("without break", end=" ")  # no \n
print(f"Name: {name}")

# Input
name = input("Name: ")
age = int(input("Age: "))

# print with a list
print([1, 2, 3])       # [1, 2, 3]
print(*[1, 2, 3])      # 1 2 3 (unpack)
print(*[1, 2, 3], sep=", ")  # 1, 2, 3

print() with sep and end customizes the output. input() always returns a str — use int() to convert. *list unpacks the separate arguments.

Multi-line and Expressions
# Multi-line string
text = """
First line
Second line
"""

# Multi-line expression
result = (
    value1
    + value2
    + value3
)

# Multi-line list
fruits = [
    "apple",
    "banana",
    "cherry",
]

# Backslash (avoid)
total = price * \
        quantity

"""...""" creates multi-line strings. Parentheses () allow multi-line expressions (preferred). A trailing comma in lists is valid (trailing comma). Avoid \ for continuation.

f-strings and Formatting
name = "Anna"
age = 30
price = 9.5

# f-string (recommended)
print(f"A {name} tem {age} years")
print(f"Expression: {age + 5}")
print(f"Method: {name.upper()}")

# Numeric formatting
print(f"Price: {price:.2f}€")   # 9.50€
print(f"{0.85:.0%}")            # 85%
print(f"{1234567:,}")           # 1,234,567
print(f"{42:08d}")              # 00000042
print(f"{'center':^20}")        # centered

# Alternatives
"Hello {}".format(name)
"Hello %s" % name

f-strings (prefix f) insert expressions inside {}. Formatting: :.2f decimals, :, thousands, :08d padding. .format() and % are older alternatives.

None and Truthiness
# None is Python's "null"
result = None
if result is None:
    print("Sem result")

# Truthy vs Falsy
# Falsy: False, 0, 0.0, "", [], {}, (), set(), None
# Truthy: everything else

if []:       # False (empty list)
    pass
if [1, 2]:   # True (list with items)
    pass

# Pattern: value or default
name = input() or "Anonymous"
value = data.get("x") or 0

None is the null value — compare with is None, never == None. Falsy values: 0, "", [], {}, None. or returns the first truthy value.

Constants and Conventions
# Python has no real constants
# Convention: UPPER_SNAKE_CASE
MAX_RETRIES = 3
API_URL = "https://api.example.com"
PI = 3.14159

# typing.Final (3.8+) — hint for IDEs
from typing import Final
VERSION: Final = "2.0.0"
TIMEOUT: Final[int] = 30

# Enum for related constants
from enum import Enum, auto

class Status(Enum):
    ACTIVE = auto()
    INACTIVE = auto()
    SUSPENDED = auto()

Status.ACTIVE.value  # 1
Status.ACTIVE.name   # "ACTIVE"

Python has no real constants — use UPPER_SNAKE_CASE by convention. typing.Final tells the IDE it shouldn't be changed. Enum creates sets of related constants. auto() assigns automatic values.

Flow Control


8 cards
if / elif / else
age = 20

if age >= 18:
    print("Adult")
elif age >= 13:
    print("Teenager")
else:
    print("Child")

# No braces — use indentation!
# Operators: and, or, not
if age >= 18 and age < 65:
    print("Age active")

# Inline condition
status = "adult" if age >= 18 else "minor"

Python uses indentation (4 spaces) instead of braces. elif = else if. Logical operators: and, or, not. Ternary expression: x if cond else y.

break, continue and else
# break: exits the loop
for i in range(10):
    if i == 5:
        break
    print(i)  # 0, 1, 2, 3, 4

# continue: skips the iteration
for i in range(10):
    if i % 2 == 0:
        continue
    print(i)  # 1, 3, 5, 7, 9

# else in a loop (runs without break)
for n in range(2, 20):
    for x in range(2, n):
        if n % x == 0:
            break
    else:
        print(f"{n} is prime")

break ends the loop. continue skips to the next iteration. else in loops only runs if the loop ends without break — useful for searches.

Ternary Operator
# Syntax: value_if_true if condition else value_if_false
age = 20
status = "adult" if age >= 18 else "minor"

# With expressions
result = x * 2 if x > 0 else 0

# Chained (avoid!)
level = "high" if x > 100 else "average" if x > 50 else "low"

# With or (default)
name = input() or "Anonymous"
value = data.get("key") or "default"

# In list comprehension
even = [x for x in range(10) if x % 2 == 0]

Ternary expression: value if condition else default. More readable than if/else for simple cases. or gives a default for falsy values. In comprehensions, the if comes at the end the a filter.

match / case (3.10+)
# Pattern matching (Python 3.10+)
def process(command):
    match command.split():
        case ["exit"]:
            return "Goodbye"
        case ["hello", name]:
            return f"Hello, {name}!"
        case ["sum", *nums]:
            return sum(int(n) for n in nums)
        case _:
            return "Command unknown"

# With types
match value:
    case int():
        print("Is integer")
    case str():
        print("Is string")
    case [x, y]:
        print(f"Even: {x}, {y}")
    case _:
        print("Other")

match/case (Python 3.10+) is structural pattern matching. case _ is the default. It can unpack lists, capture variables and check types. More powerful than switch in other languages.

for and Iteration
# Iterate a list
for fruit in ["apple", "banana", "cherry"]:
    print(fruit)

# range
for i in range(5):       # 0, 1, 2, 3, 4
    print(i)

for i in range(2, 10, 3):  # 2, 5, 8
    print(i)

# Iterate a string
for char in "Python":
    print(char)

# Iterate a dict
for key, value in {"a": 1, "b": 2}.items():
    print(f"{key}: {value}")

# for with else (runs if there is no break)
for n in range(2, 10):
    for x in range(2, n):
        if n % x == 0:
            break
    else:
        print(f"{n} is prime")

for iterates any iterable. range(start, end, step) generates a sequence. .items() iterates a dict's key+value. for...else: the else runs if the loop ends without break.

Chained Comparisons
# Python allows chaining comparisons
x = 15
0 < x < 100        # True (equivalent to x > 0 and x < 100)
1 <= x <= 20       # True

# Multiple
a, b, c = 1, 5, 10
a < b < c          # True
a < b > c          # False

# With variables
limit_inf = 0
limit_sup = 100
if limit_inf <= x <= limit_sup:
    print("In range")

# any / all with conditions
nums = [2, 4, 6, 8]
all(n % 2 == 0 for n in nums)  # True
any(n > 5 for n in nums)       # True

Python allows chained comparisons: 0 < x < 100 is more readable than x > 0 and x < 100. all() returns True if all comply. any() returns True if any complies.

while
# Loop with a condition
counter = 0
while counter < 5:
    print(counter)
    counter += 1

# Infinite loop with break
while True:
    cmd = input("> ")
    if cmd == "exit":
        break
    process(cmd)

# while with else
n = 10
while n > 0:
    if n == 5:
        break
    n -= 1
else:
    print("Terminou without break")

while repeats while the condition is True. while True + break for input-driven loops. while...else: the else runs if there is no break. Watch out for infinite loops!

Nested Loops
# Multiplication table
for i in range(1, 11):
    for j in range(1, 11):
        print(f"{i*j:4}", end="")
    print()

# Matrix
matrix = [[1, 2], [3, 4], [5, 6]]
for line in matrix:
    for elem in line:
        print(elem, end=" ")
    print()

# Flattening with comprehension
flat = [x for sub in matrix for x in sub]
# [1, 2, 3, 4, 5, 6]

# Nested enumerate
for i, line in enumerate(matrix):
    for j, val in enumerate(line):
        print(f"[{i}][{j}] = {val}")

Nested loops for matrices and tables. In comprehensions: the outer for comes first. enumerate() gives index + value. To flatten: [x for sub in list for x in sub].

Functions


10 cards
Defining Functions
def greeting(name: str) -> str:
    """Returns a greeting."""
    return f"Hello, {name}!"

# Default values
def power(base, exponent=2):
    return base ** exponent

power(3)       # 9
power(2, 10)   # 1024

# Multiple returns (tuple)
def divide(a, b):
    return a // b, a % b

quot, remainder = divide(17, 5)  # 3, 2

Functions with def. Optional type hints (: str, -> str). Default values on parameters. Multiple return via tuple with unpack. Without return, it returns None.

Closures and Scope
# Closure: function that "remembers" the scope
def counter():
    count = 0
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

c = counter()
c()  # 1
c()  # 2
c()  # 3

# LEGB: Local, Enclosing, Global, Built-in
x = "global"
def outer():
    x = "enclosing"
    def inner():
        x = "local"
        print(x)
    inner()

# global
def set_global():
    global x
    x = "modified"

Closure: inner function that captures variables from the outer scope. nonlocal allows modifying the enclosing scope variable. LEGB rule: Local → Enclosing → Global → Built-in. global accesses the global variable.

Functions the Objects
# Functions are first-class
def shout(text):
    return text.upper()

def whisper(text):
    return text.lower()

# Pass the argument
def execute(fn, msg):
    return fn(msg)

execute(shout, "hello")     # "HELLO"
execute(whisper, "HELLO")  # "hello"

# Store in structures
ops = {
    "sum": lambda a, b: a + b,
    "mult": lambda a, b: a * b,
}
ops["sum"](3, 4)  # 7

# Return a function
def multiplier(n):
    return lambda x: x * n

double = multiplier(2)
double(5)  # 10

Functions are first-class objects: they can be passed, stored and returned. Higher-order functions receive/return functions. The basis of decorators, callbacks and patterns like Strategy.

*args and **kwargs
# *args: variable positional arguments
def sum(*nums):
    return sum(nums)

sum(1, 2, 3)      # 6
sum(1, 2, 3, 4)   # 10

# **kwargs: variable named arguments
def profile(**data):
    for key, value in data.items():
        print(f"{key}: {value}")

profile(name="Anna", age=30)

# Combine
def func(*args, **kwargs):
    print(args)    # tuple
    print(kwargs)  # dict

# Unpacking
list = [1, 2, 3]
sum(*list)  # 6

*args receives extra arguments the a tuple. **kwargs receives named ones the a dict. Order: normal parameters, *args, keyword-only, **kwargs. *list unpacks.

Type Hints
from typing import Optional, Union, List, Dict

def greet(name: str) -> str:
    return f"Hello, {name}"

def fetch(id: int) -> Optional[str]:
    ...  # returns str or None

def process(x: int | str) -> None:
    ...  # Python 3.10+: union with |

def total(nums: list[int]) -> float:
    return sum(nums)

def config() -> dict[str, int]:
    return {"timeout": 30}

# Callable
from typing import Callable
def apply(fn: Callable[[int], str], x: int) -> str:
    return fn(x)

Type hints are not enforced at runtime — they are for IDEs and mypy. Optional[X] = X | None. Python 3.10+: int | str instead of Union. list[int] instead of List[int] (3.9+).

functools — Tools
from functools import lru_cache, partial, reduce, wraps

# lru_cache: memoization
@lru_cache(maxsize=128)
def fib(n):
    return n if n < 2 else fib(n-1) + fib(n-2)

# cache (3.9+): lru_cache without limit
from functools import cache
@cache
def factorial(n):
    return n * factorial(n-1) if n else 1

# partial: fixes arguments
def power(base, exp):
    return base ** exp
square = partial(power, exp=2)
square(5)  # 25

# singledispatch: overload by type
from functools import singledispatch
@singledispatch
def process(x):
    return str(x)
@process.register(list)
def _(x):
    return ", ".join(map(str, x))

@lru_cache memoizes results. @cache (3.9+) is unlimited. partial() fixes a function's arguments. @singledispatch creates generic functions with overload by type. @wraps preserves metadata in decorators.

Lambda and Anonymous Functions
# Lambda: single-expression function
double = lambda x: x * 2
double(5)  # 10

sum = lambda a, b: a + b
sum(3, 4)  # 7

# Common use: sorting
people = [("Anna", 30), ("Ray", 25), ("Eve", 35)]
people.sort(key=lambda p: p[1])
# [("Ray", 25), ("Anna", 30), ("Eve", 35)]

# With map/filter
nums = [1, 2, 3, 4, 5]
list(map(lambda x: x**2, nums))     # [1, 4, 9, 16, 25]
list(filter(lambda x: x > 3, nums)) # [4, 5]

# sorted with key
sorted(people, key=lambda p: p[0])

lambda creates anonymous single-expression functions. Ideal for key in sort()/sorted() and short callbacks. For complex logic, use a normal def.

Useful Built-in Functions
# Transformation
list(map(str, [1, 2, 3]))       # ['1', '2', '3']
list(filter(None, [0, 1, "", "a"]))  # [1, "a"]

# Aggregation
sum([1, 2, 3])       # 6
min([3, 1, 2])       # 1
max([3, 1, 2])       # 3
len([1, 2, 3])       # 3

# Inspection
type(42)             # <class 'int'>
isinstance(42, int)  # True
dir(str)             # str methods
help(print)          # documentation

# Others
sorted([3, 1, 2])          # [1, 2, 3]
reversed([1, 2, 3])        # iterator
abs(-5)                    # 5
divmod(17, 5)              # (3, 2)

Built-in functions: map(), filter(), sum(), min(), max(), len(), sorted(), abs(), divmod(). isinstance() checks the type. dir() lists attributes.

Decorators
import functools
import time

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()
        print(f"{func.__name__}: {end-start:.4f}s")
        return result
    return wrapper

@timer
def slow_process():
    time.sleep(1)

slow_process()  # "slow_process: 1.0012s"

# Decorator with arguments
def repeat(n):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(n):
                func(*args, **kwargs)
        return wrapper
    return decorator

Decorators wrap functions with @name. The wrapper adds behavior before/after. @functools.wraps preserves metadata. Decorators with arguments need 3 levels. Used in logging, cache, auth.

Recursion
# Factorial
def factorial(n: int) -> int:
    if n <= 1:
        return 1
    return n * factorial(n - 1)

factorial(5)  # 120

# Fibonacci
def fib(n: int) -> int:
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

# With memoization
from functools import lru_cache

@lru_cache(maxsize=None)
def fib_cache(n: int) -> int:
    if n < 2:
        return n
    return fib_cache(n - 1) + fib_cache(n - 2)

fib_cache(100)  # instant

Recursion: a function that calls itself. It needs a base case to stop. @lru_cache memoizes results (avoids recomputation). Python has a recursion limit (~1000) — for large cases, prefer iteration.

Classes e OOP


10 cards
Basic Class
class Person:
    # Class attribute
    species = "Homo sapiens"

    def __init__(self, name: str, age: int):
        # Instance attributes
        self.name = name
        self.age = age

    def introduce(self) -> str:
        return f"I am {self.name}, {self.age} years"

    def __repr__(self):
        return f"Person({self.name!r}, {self.age})"

ana = Person("Anna", 30)
ana.introduce()  # "I am Anna, 30 years"
ana.name          # "Anna"

__init__ is the constructor. self is the instance (mandatory). Class attributes are shared; instance ones are unique. __repr__ defines the representation. Creation: Person("Anna", 30).

Dunder Methods
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})"

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __len__(self):
        return 2

    def __getitem__(self, i):
        return (self.x, self.y)[i]

v = Vector(1, 2) + Vector(3, 4)
print(v)      # Vector(4, 6)
v[0]          # 4

Dunder methods (__x__) define operator behavior. __add__ for +, __eq__ for ==, __repr__ for print, __len__ for len(), __getitem__ for indexing.

__slots__ and Performance
class Point:
    __slots__ = ('x', 'y')

    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(1, 2)
p.x = 10      # OK
p.z = 3       # AttributeError!

# Without __slots__: each instance has __dict__
# With __slots__: no __dict__, less memory

# Comparison
import sys
class Normal:
    def __init__(self): self.x = 1

class Slim:
    __slots__ = ('x',)
    def __init__(self): self.x = 1

sys.getsizeof(Normal().__dict__)  # ~104 bytes
# Slim: no __dict__

__slots__ prevents creating dynamic attributes and eliminates the per-instance __dict__. It reduces memory by ~40% for many instances. You can't add undeclared attributes. Ideal for classes with millions of instances.

Inheritance and Polymorphism
class Animal:
    def __init__(self, name: str):
        self.name = name

    def speak(self) -> str:
        raise NotImplementedError

class Dog(Animal):
    def speak(self) -> str:
        return f"{self.name}: Au au!"

class Cat(Animal):
    def speak(self) -> str:
        return f"{self.name}: Miau!"

# Polymorphism
animals = [Dog("Rex"), Cat("Felix")]
for a in animals:
    print(a.speak())

# Multiple inheritance
class DomesticAnimal(Animal, Pet):
    pass

Inheritance with class Child(Pai). super() calls the parent's methods. Polymorphism: same method, different behavior. Python supports multiple inheritance with MRO (Method Resolution Order).

Dataclass (3.7+)
from dataclasses import dataclass, field

@dataclass
class Product:
    name: str
    price: float
    quantity: int = 0
    tags: list[str] = field(default_factory=list)

    @property
    def total(self) -> float:
        return self.price * self.quantity

p1 = Product("Book", 19.99, 2)
p2 = Product("Book", 19.99, 2)

p1 == p2       # True (compares fields!)
print(p1)      # Product(name='Book', price=19.99, ...)
p1.total       # 39.98

# Immutable
@dataclass(frozen=True)
class Point:
    x: float
    y: float

@dataclass generates __init__, __repr__, __eq__ automatically. field(default_factory=list) for mutables. frozen=True makes it immutable. Ideal for DTOs and value objects.

Enums (3.4+)
from enum import Enum, IntEnum, auto, Flag

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

class Status(Enum):
    ACTIVE = auto()    # 1
    INACTIVE = auto()  # 2

# Backed enum (with values)
class HttpMethod(Enum):
    GET = "GET"
    POST = "POST"
    PUT = "PUT"

# IntEnum: comparable with int
class Priority(IntEnum):
    LOW = 1
    MEDIUM = 2
    HIGH = 3

Priority.HIGH > Priority.LOW  # True

# Flag: bitwise
class Perm(Flag):
    READ = auto()
    WRITE = auto()
    EXEC = auto()

Enum creates sets of named constants. auto() assigns automatic values. IntEnum is comparable with integers. Flag supports bitwise operations (|, &). Access with .value and .name.

super() and Inheritance
class Vehicle:
    def __init__(self, brand: str, year: int):
        self.brand = brand
        self.year = year

class Car(Vehicle):
    def __init__(self, brand: str, year: int, doors: int):
        super().__init__(brand, year)
        self.doors = doors

    def info(self) -> str:
        base = f"{self.brand} ({self.year})"
        return f"{base} - {self.doors} doors"

# MRO
print(Car.__mro__)
# (Car, Vehicle, object)

super().__init__() calls the parent class constructor. Essential in inheritance to initialize the parent's attributes. __mro__ shows the method resolution order. In multiple inheritance, follow the MRO order.

Class and Static Methods
class Data:
    def __init__(self, day, month, year):
        self.day = day
        self.month = month
        self.year = year

    @classmethod
    def from_string(cls, s: str) -> "Data":
        day, month, year = map(int, s.split("/"))
        return cls(day, month, year)

    @staticmethod
    def e_valida(s: str) -> bool:
        try:
            d, m, a = map(int, s.split("/"))
            return 1 <= m <= 12 and 1 <= d <= 31
        except ValueError:
            return False

# Factory method
d = Data.from_string("25/12/2024")
Date.is_valid("31/02/2024")  # False

@classmethod receives cls (the class) — ideal for factory methods. @staticmethod receives neither self nor cls — it's a normal function inside the class. Use @classmethod for alternative constructors.

Properties
class Circle:
    def __init__(self, radius: float):
        self._radius = radius

    @property
    def radius(self) -> float:
        return self._radius

    @radius.setter
    def radius(self, value: float):
        if value < 0:
            raise ValueError("Radius cannot be negative")
        self._radius = value

    @property
    def area(self) -> float:
        return 3.14159 * self._radius ** 2

c = Circle(5)
c.radius        # 5 (getter)
c.radius = 10   # setter with validation
c.area        # 314.159 (read-only)

@property creates getters/setters with attribute syntax. @x.setter for validation on assignment. No setter = read-only. Convention: internal attribute with _ prefix. Access: obj.prop (no parentheses).

ABC and Protocol
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float: ...

    @abstractmethod
    def perimeter(self) -> float: ...

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14159 * self.radius ** 2

    def perimeter(self):
        return 2 * 3.14159 * self.radius

# Protocol (static duck typing)
from typing import Protocol

class Renderable(Protocol):
    def render(self) -> str: ...

ABC + @abstractmethod forces implementation in subclasses. You can't instantiate an ABC directly. Protocol (3.8+) defines interfaces via static duck typing — without explicit inheritance. Checked by mypy.

Collections


9 cards
Lists
fruits = ["apple", "banana", "cherry"]

# Access and modification
fruits[0]       # "apple"
fruits[-1]      # "cherry" (last)
fruits[1] = "kiwi"

# Methods
fruits.append("grape")       # adds at the end
fruits.insert(1, "mango")  # inserts at index
fruits.remove("banana")    # removes by value
fruits.pop()               # removes and returns last
fruits.extend(["a", "b"])  # extends

# Checking
"kiwi" in fruits    # True
len(fruits)         # length
fruits.index("kiwi")  # index

# Copying
copy = fruits.copy()
copy = fruits[:]

Lists are mutable and ordered. Negative indexes count from the end. append() adds, pop() removes. in checks existence. Copy with .copy() or [:] (shallow copy).

Slicing
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

nums[2:5]     # [2, 3, 4]
nums[:3]      # [0, 1, 2]
nums[7:]      # [7, 8, 9]
nums[-3:]     # [7, 8, 9]
nums[::2]     # [0, 2, 4, 6, 8] (step 2)
nums[::-1]    # [9, 8, ..., 0] (reverse)
nums[1:8:2]   # [1, 3, 5, 7]

# Strings
"Python"[1:4]   # "yth"
"Python"[::-1]  # "nohtyP"

# Assignment with slice
nums[2:5] = [20, 30, 40]

# Shallow copy
copy = nums[:]

Slicing: [start:end:step]. end is exclusive. [::-1] reverses. [::2] skips every 2. Works on lists, strings and tuples. Assignment with slice modifies the original list.

Arrays and Memory
import array
import sys

# array: fixed type, less memory
nums = array.array('i', [1, 2, 3, 4, 5])
nums.append(6)
nums[0]  # 1

# Types: 'i' int, 'f' float, 'd' double, 'b' byte
print(sys.getsizeof([1,2,3]))       # ~88 bytes (list)
print(sys.getsizeof(array.array('i', [1,2,3])))  # ~76 bytes

# bytes and bytearray
data = b"\x48\x65\x6c\x6c\x6f"  # b"Hello"
mutable = bytearray(b"abc")
mutable[0] = 65  # b"Abc"

# memoryview (zero-copy)
view = memoryview(bytearray(1024))
view[0:4] = b"test"

# For numbers: numpy
# import numpy as np
# arr = np.array([1, 2, 3])

array.array is a list with a fixed type — less memory. bytes is immutable, bytearray is mutable. memoryview allows zero-copy access to buffers. For numerical computation, use numpy.

Dictionaries
person = {"name": "Anna", "age": 30}

# Access
person["name"]           # "Anna"
person.get("email")      # None (no error)
person.get("email", "N/A")  # "N/A"

# Modification
person["email"] = "ana@mail.com"
del person["age"]
person.update({"city": "Lisbon"})

# Iteration
for key in person:
    print(key, person[key])
for key, value in person.items():
    print(f"{key}: {value}")

# Checking
"name" in person    # True
len(person)         # 2

# Dict comprehension
squares = {x: x**2 for x in range(5)}

Dicts map key → value. .get() avoids KeyError. .items() iterates pairs. in checks keys. Since Python 3.7, dicts keep insertion order. Keys must be hashable.

Sorting
nums = [3, 1, 4, 1, 5, 9]

# sorted: returns a new list
sorted(nums)              # [1, 1, 3, 4, 5, 9]
sorted(nums, reverse=True)  # [9, 5, 4, 3, 1, 1]

# .sort(): modifies in-place
nums.sort()

# With key
words = ["banana", "fig", "pineapple"]
sorted(words, key=len)  # by length
sorted(words, key=str.lower)

# List of tuples
people = [("Anna", 30), ("Ray", 25)]
sorted(people, key=lambda p: p[1])

# operator.itemgetter
from operator import itemgetter
sorted(people, key=itemgetter(1))

sorted() returns a new list; .sort() modifies in-place. key defines the criterion. reverse=True for descending. operator.itemgetter is faster than lambda for tuples.

Tuples and NamedTuple
# Tuple: immutable
point = (3, 4)
x, y = point  # unpacking

# Without parentheses also works
coords = 10, 20, 30

# 1-element tuple
single = (42,)  # comma required!

# NamedTuple (tuple with names)
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
p.x  # 3
p.y  # 4

# typing.NamedTuple (with type hints)
from typing import NamedTuple
class Ponto2(NamedTuple):
    x: float
    y: float

p2 = Ponto2(1.5, 2.5)

Tuples are immutable — faster than lists. Unpacking: x, y = point. A 1-element tuple needs a comma: (42,). namedtuple adds names to fields. Ideal for returning multiple values.

collections
from collections import Counter, defaultdict, deque

# Counter: counts occurrences
text = "abracadabra"
c = Counter(text)
c.most_common(2)  # [('a', 5), ('b', 2)]

# defaultdict: default value
groups = defaultdict(list)
for name, group in [("Anna", "A"), ("Ray", "B"), ("Eve", "A")]:
    groups[group].append(name)
# {'A': ['Anna', 'Eve'], 'B': ['Ray']}

# deque: queue with O(1) operations at both ends
queue = deque([1, 2, 3])
queue.appendleft(0)  # [0, 1, 2, 3]
queue.popleft()      # 0 (O(1)!)
queue.rotate(1)      # rotates elements

Counter counts elements and provides most_common(). defaultdict automatically creates a value for new keys. deque is a queue with appendleft()/popleft() in O(1) — faster than list for queues.

Sets
# Set: collection without duplicates
nums = {1, 2, 3, 3, 2}  # {1, 2, 3}

# Operations
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

a | b   # union: {1, 2, 3, 4, 5, 6}
a & b   # intersection: {3, 4}
a - b   # difference: {1, 2}
a ^ b   # symmetric: {1, 2, 5, 6}

# Methods
a.add(10)
a.discard(1)    # removes without error
a.issubset(b)   # False
a.issuperset(b) # False

# Remove duplicates from list
list = [1, 2, 2, 3, 3, 3]
unica = list(set(list))  # [1, 2, 3]

# Empty set
s = set()  # {} creates an empty dict!

Sets are unordered collections without duplicates. Operations: | union, & intersection, - difference, ^ symmetric. {} creates an empty dict — use set(). Ideal for membership testing (O(1)).

Packing and Unpacking
# Basic unpacking
a, b, c = [1, 2, 3]

# With * (rest)
first, *middle, last = [1, 2, 3, 4, 5]
# first=1, middle=[2,3,4], last=5

# Ignore values
_, second, _ = (10, 20, 30)

# Merge dicts (3.9+)
d1 = {"a": 1}
d2 = {"b": 2}
merged = d1 | d2  # {"a": 1, "b": 2}

# Unpack in function
def sum(a, b, c):
    return a + b + c
sum(*[1, 2, 3])  # 6

# Unpack dict
def profile(name, age):
    pass
profile(**{"name": "Anna", "age": 30})

Unpacking: a, b, c = list. *rest captures remaining elements. _ ignores values. d1 | d2 merges dicts (3.9+). *args and **kwargs unpack in function calls.

Understandings and Iterators


7 cards
List Comprehension
# Basic
squares = [x**2 for x in range(10)]

# With filter
even = [x for x in range(20) if x % 2 == 0]

# With transformation
names = ["ana", "rui", "eva"]
uppercased = [n.upper() for n in names]

# Nested (flatten matrix)
matrix = [[1, 2], [3, 4], [5, 6]]
flat = [x for sub in matrix for x in sub]
# [1, 2, 3, 4, 5, 6]

# With inline condition
result = [x if x > 0 else 0 for x in nums]

# vs map/filter
[x**2 for x in nums if x > 3]
# equivalent to: list(map(lambda x: x**2, filter(lambda x: x > 3, nums)))

Syntax: [expression for item in iterable if condition]. More readable than map()/filter(). For nested: outer for first. Avoid overly complex comprehensions — use a normal loop.

itertools
from itertools import (
    chain, product, combinations,
    permutations, groupby, islice, cycle
)

# chain: concatenates iterables
list(chain([1, 2], [3, 4]))  # [1, 2, 3, 4]

# product: cartesian product
list(product("AB", "12"))
# [('A','1'), ('A','2'), ('B','1'), ('B','2')]

# combinations and permutations
list(combinations([1,2,3], 2))  # [(1,2),(1,3),(2,3)]
list(permutations([1,2,3], 2))  # 6 pairs

# groupby (requires sorting)
data = sorted(people, key=lambda p: p[1])
for group, items in groupby(data, key=lambda p: p[1]):
    print(group, list(items))

# islice: slice of an iterator
list(islice(range(100), 5, 10))

itertools has tools for iterables. chain() concatenates. product() does the cartesian product. combinations()/permutations() for combinatorics. groupby() groups (requires sorted data). Everything is lazy.

Dict and Set Comprehension
# Dict comprehension
squares = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# Invert dict
original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}

# Filter dict
grades = {"Anna": 15, "Ray": 8, "Eve": 12}
passed = {k: v for k, v in grades.items() if v >= 10}

# Set comprehension
uniques = {x % 5 for x in range(20)}
# {0, 1, 2, 3, 4}

# From list of tuples
even = [(1, "one"), (2, "two")]
d = {k: v for k, v in even}

Dict comprehension: {key: value for ...}. Set comprehension: {expression for ...}. Ideal for transforming/inverting dicts and creating sets with logic. Same syntax the list comp but with {}.

reduce and functools
from functools import reduce, lru_cache, partial

# reduce: accumulates
nums = [1, 2, 3, 4, 5]
reduce(lambda acc, x: acc + x, nums)  # 15
reduce(lambda acc, x: acc * x, nums)  # 120

# With initial value
reduce(lambda acc, x: acc + x, nums, 100)  # 115

# lru_cache: memoization
@lru_cache(maxsize=128)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

# partial: fixes arguments
from functools import partial
double = partial(int, base=2)
double("1010")  # 10

# accumulate
from itertools import accumulate
list(accumulate([1, 2, 3, 4]))  # [1, 3, 6, 10]

reduce() accumulates values (sum, product, etc.). @lru_cache memoizes results of pure functions. partial() fixes arguments of a function. accumulate() is like reduce but returns all steps.

Generators
# Generator function
def countdown(n):
    i = 0
    while i < n:
        yield i
        i += 1

for x in countdown(5):
    print(x)  # 0, 1, 2, 3, 4

# Generator expression (lazy)
sum = sum(x**2 for x in range(1000000))
# Does not create a list in memory!

# vs List comprehension
import sys
list = [x for x in range(10000)]
gen = (x for x in range(10000))
sys.getsizeof(list)  # ~87616 bytes
sys.getsizeof(gen)    # ~112 bytes

# yield from (delegate)
def chain(*iterables):
    for it in iterables:
        yield from it

yield creates generators: lazy evaluation (one value at a time). (x for x in ...) is a generator expression. Uses far less memory than lists. yield from delegates to another iterable. Ideal for infinite sequences.

Custom Iterators
# Iteration protocol: __iter__ + __next__
class ContagemRegressiva:
    def __init__(self, start):
        self.n = start

    def __iter__(self):
        return self

    def __next__(self):
        if self.n <= 0:
            raise StopIteration
        self.n -= 1
        return self.n + 1

for x in ContagemRegressiva(5):
    print(x)  # 5, 4, 3, 2, 1

# Simpler: generator
def countdown(n):
    while n > 0:
        yield n
        n -= 1

# iter() with callable
import random
scroll = iter(lambda: random.randint(1, 6), 6)
# Generates numbers until a 6 comes out

Iterators implement __iter__() and __next__(). StopIteration ends the iteration. Generators with yield are simpler. iter(callable, sentinel) calls until it returns the sentinel.

zip, enumerate and map
# zip: combines iterables
names = ["Anna", "Ray"]
ages = [30, 25]
for name, age in zip(names, ages):
    print(f"{name}: {age}")

# zip to create dict
d = dict(zip(names, ages))

# enumerate: index + value
for i, fruit in enumerate(["apple", "banana"]):
    print(f"{i}: {fruit}")

# map: applies function to all
list(map(str.upper, ["hello", "world"]))
list(map(lambda x: x*2, [1, 2, 3]))

# filter: keeps the True ones
list(filter(lambda x: x > 2, [1, 2, 3, 4]))

# zip_longest (different lengths)
from itertools import zip_longest
list(zip_longest([1, 2], [3], fillvalue=0))

zip() combines iterables into pairs. enumerate() gives index + value. map() applies a function to all. filter() keeps truthy elements. zip_longest() for different lengths with fillvalue.

Exceptions and Files


9 cards
try / except / finally
try:
    result = 10 / 0
except ZeroDivisionError the e:
    print(f"Error: {e}")
except (TypeError, ValueError) as e:
    print(f"Type or value: {e}")
except Exception the e:
    print(f"Generic: {e}")
else:
    print("Sem errors!")
finally:
    print("Always executa")

# Multiple exceptions
try:
    value = int(input())
except ValueError:
    print("Not is number")
except KeyboardInterrupt:
    print("Cancelado")

try/except catches errors. the e gives access to the exception. else runs if there is no error. finally always runs (cleanup). Catch specific exceptions before generic ones.

JSON and CSV
import json
import csv

# JSON
data = {"name": "Anna", "age": 30}
json_str = json.dumps(data, ensure_ascii=False, indent=2)
object = json.loads(json_str)

# JSON in file
with open("data.json", "w") as f:
    json.dump(data, f, ensure_ascii=False)
with open("data.json") as f:
    data = json.load(f)

# CSV
with open("data.csv", "w", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["Name", "Age"])
    writer.writerow(["Anna", 30])

with open("data.csv") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row["Name"])

json.dumps()/loads() for strings. json.dump()/load() for files. ensure_ascii=False for accents. csv.DictReader reads with column names. csv.writer writes rows.

ExceptionGroup (3.11+)
# ExceptionGroup: multiple exceptions (3.11+)
try:
    raise ExceptionGroup("errors", [
        ValueError("value invalid"),
        TypeError("type wrong"),
    ])
except* ValueError the eg:
    for e in eg.exceptions:
        print(f"ValueError: {e}")
except* TypeError the eg:
    for e in eg.exceptions:
        print(f"TypeError: {e}")

# TaskGroup (asyncio, 3.11+)
import asyncio

async def main():
    async with asyncio.TaskGroup() as tg:
        t1 = tg.create_task(fetch("url1"))
        t2 = tg.create_task(fetch("url2"))
    # If one fails, all are cancelled
    print(t1.result(), t2.result())

ExceptionGroup (3.11+) groups multiple exceptions. except* catches by type within the group. asyncio.TaskGroup (3.11+) runs tasks concurrently — if one fails, it cancels all. Replaces gather() with better error handling.

Raising Exceptions
# raise
def divide(a, b):
    if b == 0:
        raise ValueError("Divisor cannot be zero")
    return a / b

# Re-raise
try:
    process()
except Exception the e:
    log(e)
    raise  # re-raises the same exception

# Custom exception
class InsufficientBalance(Exception):
    def __init__(self, balance, value):
        self.balance = balance
        self.value = value
        super().__init__(
            f"Balance {balance} < {value}"
        )

raise InsufficientBalance(50, 100)

raise raises exceptions. Create classes with extends Exception for specific errors. raise without arguments re-raises the current exception. Include clear messages and relevant data.

pathlib
from pathlib import Path

# Create paths
p = Path("/var/www/data")
p = Path.home() / "Documents" / "file.txt"

# Information
p.name        # "file.txt"
p.stem        # "file"
p.suffix      # ".txt"
p.parent      # Path("Documents")
p.exists()    # True/False
p.is_file()   # True/False
p.is_dir()    # True/False

# Operations
p.mkdir(parents=True, exist_ok=True)
p.touch()
p.rename("new.txt")
p.unlink()    # delete file

# Listing
for f in Path(".").glob("*.py"):
    print(f)
for f in Path(".").rglob("*.txt"):
    print(f)  # recursive

pathlib.Path is the modern way to work with paths. / concatenates paths. .glob() finds files. .rglob() is recursive. .read_text()/.write_text() for quick I/O. Replaces os.path.

Context Managers (with)
# with: guarantees cleanup
with open("file.txt") as f:
    content = f.read()
# File closed automatically!

# Multiple resources
with open("a.txt") the a, open("b.txt") as b:
    data = a.read() + b.read()

# Custom context manager
class Timer:
    def __enter__(self):
        import time
        self.start = time.perf_counter()
        return self

    def __exit__(self, *args):
        import time
        self.end = time.perf_counter()
        print(f"Time: {self.end - self.start:.4f}s")

with Timer():
    slow_process()

# contextlib
from contextlib import contextmanager

with guarantees cleanup even with exceptions. __enter__/__exit__ define the protocol. Ideal for files, DB connections, locks. @contextmanager creates context managers with yield.

Logging
import logging

# Basic configuration
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler("app.log"),
        logging.StreamHandler(),
    ]
)

logger = logging.getLogger(__name__)

logger.debug("Detail technical")
logger.info("Operation normal")
logger.warning("Attention!")
logger.error("Algo failed")
logger.critical("Error grave!")

# With exception
try:
    1 / 0
except Exception:
    logger.exception("Error na division")

logging replaces print() for debugging. Levels: DEBUG, INFO, WARNING, ERROR, CRITICAL. getLogger(__name__) per module. logger.exception() includes the traceback automatically.

File Operations
# Read
with open("data.txt", "r", encoding="utf-8") as f:
    content = f.read()       # everything
    lines = f.readlines()    # list of lines

# Read line by line (memory efficient)
with open("big.log") as f:
    for line in f:
        process(line)

# Write
with open("output.txt", "w") as f:
    f.write("Hello\n")
    f.writelines(["a\n", "b\n"])

# Append
with open("log.txt", "a") as f:
    f.write("new line\n")

# pathlib (modern)
from pathlib import Path
p = Path("data.txt")
p.read_text(encoding="utf-8")
p.write_text("content")
p.exists()

open() with with closes automatically. Modes: r read, w write, a append. encoding="utf-8" for accents. pathlib.Path is the modern, OOP way.

assert and Debugging
# assert: checks a condition in development
def divide(a, b):
    assert b != 0, "Divisor cannot be zero"
    return a / b

# assert with message
assert isinstance(name, str), f"Esperado str, got {type(name)}"

# breakpoint() (Python 3.7+)
def process(x):
    result = x * 2
    breakpoint()  # opens the debugger (pdb)
    return result + 1

# In pdb:
# n = next, s = step, c = continue
# p variable = print
# l = list code

# python -m pdb script.py
# PYTHONBREAKPOINT=0 disables it

assert checks conditions in development (removed with -O). breakpoint() opens the pdb debugger. Commands: n next, s step, c continue, p print. Don't use assert for input validation.

Modules and Packages


6 cards
Importing Modules
# Ways to import
import os
import os.path
from the import path
from os.path import join, exists
import numpy as np
from typing import List, Optional

# Conditional import
try:
    import ujson as json
except ImportError:
    import json

# Relative import (inside a package)
from . import utils
from ..models import User

# __all__ controls from x import *
# __init__.py:
__all__ = ["ClasseA", "func_b"]

import module imports everything. from x import y imports something specific. the creates an alias. Relative import with . and ... __all__ controls exports. Avoid from x import *.

Creating a Package
# Structure:
# my_package/
#   __init__.py
#   core.py
#   utils/
#     __init__.py
#     helpers.py

# __init__.py (exports the public API)
from .core import MainClass
from .utils.helpers import helper_func

__all__ = ["MainClass", "helper_func"]
__version__ = "1.0.0"

# Usage:
from my_package import MainClass
from my_package.utils import helpers

# __name__ == "__main__"
if __name__ == "__main__":
    # Only runs the a script
    print("Executed directly")

Packages need __init__.py. __all__ defines public exports. __name__ == "__main__" checks if it's the main script. Structure: folder with __init__.py + modules. __version__ by convention.

pip and venv
# Virtual environment
python -m venv .venv
source .venv/bin/activate   # Linux/Mac
.venv\Scripts\activate      # Windows
deactivate

# pip
pip install requests
pip install "flask>=2.0,<3.0"
pip install -r requirements.txt
pip freeze > requirements.txt
pip uninstall requests
pip list
pip show requests

# pyproject.toml (modern)
# [project]
# dependencies = [
#     "requests>=2.28",
#     "flask>=2.0",
# ]

# pip-tools / poetry / uv
pip install pip-tools
pip-compile pyproject.toml

venv isolates dependencies per project. pip install installs packages. requirements.txt lists dependencies. pyproject.toml is the modern format. Tools: poetry, uv, pip-tools.

__name__ and Scripts
# script.py
def main():
    print("Main program")

if __name__ == "__main__":
    main()

# When run: __name__ == "__main__"
# When imported: __name__ == "script"

# CLI with argparse
import argparse

parser = argparse.ArgumentParser(description="Tool")
parser.add_argument("name", help="Name")
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument("-n", "--count", type=int, default=1)
args = parser.parse_args()

if args.verbose:
    print(f"Hello {args.name}!" * args.count)

if __name__ == "__main__" separates execution vs import code. argparse creates a CLI with flags and automatic help. action="store_true" for boolean flags. type=int converts arguments.

Useful Standard Modules
import os, sys, json, math, random
import datetime, collections, itertools
import re, pathlib, subprocess, hashlib

# the / pathlib
os.getcwd()
os.environ.get("HOME")
Path.home()

# sys
sys.argv        # CLI arguments
sys.version     # Python version
sys.exit(1)     # exit with code

# math
math.pi         # 3.14159...
math.sqrt(16)   # 4.0
math.ceil(3.2)  # 4

# random
random.randint(1, 100)
random.choice([1, 2, 3])
random.shuffle(list)

# hashlib
hashlib.sha256(b"text").hexdigest()

Python's stdlib is huge. the/pathlib for files. sys for system. math for math. random for randomness. json, re, datetime, collections, itertools.

datetime
from datetime import datetime, date, timedelta

# Now
now = datetime.now()
today = date.today()

# Format
now.strftime("%d/%m/%Y %H:%M")
# "25/12/2024 14:30"

# Parse
dt = datetime.strptime("25/12/2024", "%d/%m/%Y")

# Arithmetic
tomorrow = today + timedelta(days=1)
in_1h = now + timedelta(hours=1)
difference = dt2 - dt1
difference.days  # days

# ISO
datetime.now().isoformat()
datetime.fromisoformat("2024-12-25T14:30:00")

# Comparison
if now > dt:
    print("Past")

datetime.now() gives the current date/time. strftime() formats, strptime() parses. timedelta for arithmetic. isoformat()/fromisoformat() for ISO format. Subtraction gives a timedelta.

Tips and Good Practices


8 cards
Walrus Operator (:=)
# Assignment in an expression (Python 3.8+)
if (n := len(data)) > 10:
    print(f"List big: {n}")

# In while loops
while (line := input("> ")) != "exit":
    process(line)

# In comprehensions
results = [
    y for x in data
    if (y := process(x)) is not None
]

# With match (3.10+)
match data:
    case {"name": name} if (n := len(name)) > 3:
        print(f"Name long: {name} ({n})")

# Without walrus (before):
# n = len(data)
# if n > 10: ...

:= (walrus) assigns and returns a value in an expression. Avoids extra assignment lines. Useful in while, if and comprehensions. Don't abuse it — only use it when it improves readability.

Anti-patterns to Avoid
# 1. Mutable the default (BUG!)
def mau(list=[]):      # NEVER!
    list.append(1)
    return list

def bom(list=None):
    if list is None:
        list = []
    list.append(1)
    return list

# 2. Comparing with None using ==
if x == None:    # bad
if x is None:    # good

# 3. Loop with index
for i in range(len(list)):  # bad
    print(list[i])
for item in list:           # good
    print(item)

# 4. Import *
from module import *  # bad (pollutes namespace)

# 5. Generic except
except:  # bad (catches everything, even KeyboardInterrupt)
except Exception:  # better

Anti-patterns: mutable default in functions (use None), == None (use is None), loop with index (use for item), import *, generic except:. Follow PEP 8 and use ruff.

PEP 8 and Style
# Names
my_variable = 1      # snake_case
MY_CONSTANT = 100   # UPPER_SNAKE
class MyClass:      # PascalCase
    pass
def my_func():     # snake_case
    pass

# Formatting
x = 1 + 2              # spaces around operators
list = [1, 2, 3]      # space after comma
func(a, b, c=3)        # no space around = in kwargs

# Imports (top of the file)
import os               # stdlib
import requests         # third-party
from . import utils     # local

# Max lines: 79 (code) / 72 (docs)
# Tools: black, ruff, flake8, isort

PEP 8 is the style guide. Names: snake_case for variables/functions, PascalCase for classes, UPPER_SNAKE for constants. Tools: black formats, ruff lints, isort sorts imports.

CLI with argparse and click
# argparse (stdlib)
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("file")
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument("-o", "--output", default="out.txt")
parser.add_argument("-n", type=int, default=10)
args = parser.parse_args()

# click (third-party, more elegant)
import click

@click.command()
@click.argument("file")
@click.option("-v", "--verbose", is_flag=True)
@click.option("-n", "--count", default=10, type=int)
def process(file, verbose, count):
    """Processa one file."""
    click.echo(f"Processando {file} x{count}")

if __name__ == "__main__":
    process()

argparse (stdlib) for simple CLIs. click (third-party) is more elegant with decorators. action="store_true" for flags. type=int converts. Docstring becomes --help. For complex CLIs: typer (type hints).

Idiomatic Tricks
# Chained comparisons
if 0 < x < 100:
    pass

# Ternary
status = "even" if n % 2 == 0 else "odd"

# Unpacking
first, *rest = [1, 2, 3, 4, 5]

# dict.get with default
value = d.get("key", "default")

# any / all
if all(x > 0 for x in nums):
    print("All positivos")

# f-string debug (3.8+)
x = 42
print(f"{x = }")  # "x = 42"

# Reverse string
"reverse"[::-1]  # "retrevni"

# Merge dicts (3.9+)
merged = d1 | d2

# enumerate with start
for i, item in enumerate(list, start=1):
    print(f"{i}. {item}")

Idiomatic tricks: chained comparisons, ternary, unpacking, dict.get(), any()/all(). f"{x = }" shows name and value (debug). [::-1] reverses. | merges dicts.

Advanced Dataclasses
from dataclasses import dataclass, field, asdict, astuple

@dataclass(order=True)
class Product:
    name: str = field(compare=False)
    price: float
    stock: int = 0
    tags: list[str] = field(default_factory=list)

    @property
    def available(self) -> bool:
        return self.stock > 0

p = Product("Book", 19.99, 5)
asdict(p)    # {"name": "Book", "price": 19.99, ...}
astuple(p)   # ("Book", 19.99, 5, [])

# Sorting by price (order=True)
products = [Product("A", 30), Product("B", 10)]
sorted(products)  # sorts by price

# slots=True (3.10+)
@dataclass(slots=True)
class Point:
    x: float
    y: float

@dataclass(order=True) generates comparison methods. field(compare=False) excludes a field from sorting. asdict()/astuple() convert. slots=True (3.10+) reduces memory. default_factory for mutables.

Performance and Optimization
# timeit: measure time
import timeit
timeit.timeit('"-".join(map(str, range(100)))', number=10000)

# cProfile: profiling
import cProfile
cProfile.run("my_func()")

# Performance tips:
# 1. list comprehension > loop with append
# 2. dict/set lookup O(1) > list O(n)
# 3. join() > concatenation in a loop
# 4. generators > lists for large data
# 5. lru_cache for pure functions

# __slots__ for many instances
# local variables > global variables

# collections.deque > list for queues
# numpy for numeric arrays

timeit measures execution time. cProfile shows detailed profiling. Tips: use comprehensions, dict/set for lookups, join() for strings, generators for large data, @lru_cache for memoization.

Advanced Type Hints
from typing import TypeVar, Generic, Protocol, runtime_checkable

# TypeVar and Generic
T = TypeVar("T")

class Stack(Generic[T]):
    def __init__(self):
        self._items: list[T] = []
    def push(self, item: T) -> None:
        self._items.append(item)
    def pop(self) -> T:
        return self._items.pop()

s: Stack[int] = Stack()
s.push(42)

# Protocol (structural subtyping)
@runtime_checkable
class Renderable(Protocol):
    def render(self) -> str: ...

def show(obj: Renderable) -> None:
    print(obj.render())

# TypeAlias (3.12+)
# type Vector = list[float]

TypeVar + Generic create generic classes. Protocol defines interfaces by structure (static duck typing). @runtime_checkable allows isinstance(). Python 3.12: type Alias = ... for type aliases. Verify with mypy.

Strings e Regex


6 cards
Advanced String Methods
s = "hello world python"

s.capitalize()    # "Hello world python"
s.title()         # "Hello World Python"
s.swapcase()      # "HELLO WORLD PYTHON"
s.center(30, "-") # "---hello world python---"
s.zfill(20)       # "0000hello world python"

# Checking
s.isalpha()    # False (has spaces)
s.isdigit()    # False
s.isalnum()    # False
s.isspace()    # False

# Join and split
"-".join(["a", "b", "c"])  # "a-b-c"
"a,b,c".split(",")         # ["a", "b", "c"]
"a  b  c".split()          # ["a", "b", "c"]

# partition
"name@email.com".partition("@")
# ('name', '@', 'email.com')

join() is more efficient than concatenation in a loop. split() with no args splits on whitespace. partition() splits into 3 parts. zfill() pads with zeros. is*() checks character type.

Template and Textwrap
from string import Template
import textwrap

# Template (safe for user input)
t = Template("Hello $name, you are $age years")
t.substitute(name="Anna", age=30)
# safe_substitute does not raise an error if missing
t.safe_substitute(name="Anna")

# textwrap
text = "Text very long that needs to be broken em several lines to stay readable."
textwrap.fill(text, width=40)
textwrap.dedent("""
    Remove indentation
    common de all the lines
""")
textwrap.shorten(text, width=30)  # "Text very long that..."

# repr vs str
repr("hello\nmundo")  # "'hello\\nmundo'" (debug)
str("hello\nmundo")   # formatted output

Template is safe for user input (no injection). textwrap.fill() wraps text into lines. textwrap.dedent() removes common indentation. repr() shows the debug representation, str() shows the formatted one.

Basic Regex
import re

text = "Contact: ana@mail.com or 912-345-678"

# search: first occurrence
m = re.search(r"\d{3}-\d{3}-\d{3}", text)
if m:
    print(m.group())  # "912-345-678"

# findall: all of them
emails = re.findall(r"[\w.]+@[\w.]+", text)
# ["ana@mail.com"]

# sub: replace
re.sub(r"\d", "X", "abc123")  # "abcXXX"

# match: start of the string
re.match(r"\d+", "123abc")  # Match
re.match(r"\d+", "abc123")  # None

# compile (reuse)
pattern = re.compile(r"\b\w+@\w+\.\w+\b")
pattern.findall(text)

re.search() finds the first occurrence. re.findall() returns all of them. re.sub() replaces. re.match() only at the start. re.compile() to reuse patterns. Use raw strings (r"...").

difflib and Comparison
import difflib

# Difference between texts
texto1 = "Hello World\nPython 3".splitlines()
texto2 = "Hello World\nPython 3.12".splitlines()

diff = difflib.unified_diff(texto1, texto2, lineterm="")
print("\n".join(diff))

# Similarity
difflib.SequenceMatcher(None, "python", "pyton").ratio()
# 0.909...

# Suggestions (close matches)
difflib.get_close_matches("pytho", ["python", "java", "perl"])
# ["python"]

# ndiff: character-by-character difference
list(difflib.ndiff("abc", "axc"))
# ['  a', '- b', '+ x', '  c']

difflib compares sequences. unified_diff() shows differences in unified format. SequenceMatcher.ratio() gives similarity (0-1). get_close_matches() suggests corrections. ndiff() shows character-by-character difference.

Advanced Regex
import re

# Named groups
m = re.search(
    r"(?P<name>\w+)@(?P<dominio>[\w.]+)",
    "ana@mail.com"
)
m.group("name")     # "ana"
m.group("dominio")  # "mail.com"

# Lookahead / Lookbehind
re.findall(r"\w+(?=@)", "ana@mail rui@test")
# ["ana", "rui"] (before @)

# Quantifiers
# \d{2,4}   2 to 4 digits
# \w+       1 or more
# \s*       0 or more spaces
# .+?       non-greedy

# Flags
re.findall(r"hello", text, re.IGNORECASE)
re.split(r"\s+", text, flags=re.MULTILINE)

# sub with function
re.sub(r"\d+", lambda m: str(int(m.group())*2), "a1b2")
# "a2b4"

Named groups: (?P...). Lookahead: (?=...). re.IGNORECASE ignores case. sub() accepts a function the replacement. non-greedy with ? after the quantifier.

Encoding and Unicode
# Strings are Unicode by default
text = "Hello 🌍"
len(text)  # 7 (characters)

# Encode/Decode
bytes_data = text.encode("utf-8")
texto2 = bytes_data.decode("utf-8")

# Read file with encoding
with open("file.txt", encoding="utf-8") as f:
    content = f.read()

# Detect encoding (chardet)
# import chardet
# result = chardet.detect(bytes_data)

# Unicode escapes
"\u0041"      # "A"
"\U0001F600"  # "😀"
chr(65)       # "A"
ord("A")      # 65

# Normalization
import unicodedata
unicodedata.normalize("NFC", text)

Python 3: strings are Unicode. encode() converts to bytes. decode() converts bytes to string. Always specify encoding="utf-8" for files. chr()/ord() convert character/code.

Async and Concurrency


6 cards
async / await
import asyncio

async def fetch_data(url: str) -> str:
    print(f"Start: {url}")
    await asyncio.sleep(1)  # simulates I/O
    print(f"Done: {url}")
    return f"Data de {url}"

async def main():
    # Sequential (3s)
    r1 = await fetch_data("api/1")
    r2 = await fetch_data("api/2")

    # Concurrent (1s!)
    results = await asyncio.gather(
        fetch_data("api/1"),
        fetch_data("api/2"),
        fetch_data("api/3"),
    )
    print(results)

asyncio.run(main())

async def defines a coroutine. await pauses until complete. asyncio.gather() runs concurrently. asyncio.run() starts the event loop. Ideal for I/O (HTTP, DB, files). Don't use time.sleep() — use asyncio.sleep().

multiprocessing
from multiprocessing import Pool, cpu_count
import multiprocessing as mp

# Process pool
def square(n):
    return n ** 2

if __name__ == "__main__":
    with Pool(processes=cpu_count()) as pool:
        results = pool.map(square, range(10))
        # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

    # apply_async (non-blocking)
    with Pool() as pool:
        r = pool.apply_async(square, (10,))
        print(r.get(timeout=5))  # 100

    # starmap (multiple args)
    def power(base, exp):
        return base ** exp
    with Pool() as pool:
        pool.starmap(power, [(2,3), (3,2)])

print(f"CPUs: {cpu_count()}")

multiprocessing bypasses the GIL with separate processes. Pool.map() distributes work. cpu_count() gives the number of colors. if __name__ == "__main__" is required on Windows. For I/O, prefer threading or asyncio.

asyncio — Patterns
import asyncio

# Task: create and cancel
async def task():
    await asyncio.sleep(10)

async def main():
    task = asyncio.create_task(task())
    task.cancel()

# Semaphore: limit concurrency
without = asyncio.Semaphore(5)

async def fetch_limitado(url):
    async with without:
        await asyncio.sleep(1)
        return url

# Queue: producer/consumer
async def produtor(queue):
    for i in range(10):
        await queue.put(i)
    await queue.put(None)

async def consumer(queue):
    while (item := await queue.get()) is not None:
        process(item)

create_task() schedules a coroutine. Semaphore limits concurrency. asyncio.Queue for producer/consumer. task.cancel() cancels. Use async with for async resources.

async for and async with
import asyncio

# async for: async iterator
async def read_lines(file):
    async with aiofiles.open(file) as f:
        async for line in f:
            yield line.strip()

async def main():
    async for line in read_lines("data.txt"):
        process(line)

# async with: async context manager
import aiohttp

async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return await resp.json()

# async generator
async def ticker(interval, n):
    for i in range(n):
        await asyncio.sleep(interval)
        yield i

async for iterates over async iterators. async with for async context managers (connections, files). async generators with yield in async functions. Use libraries like aiohttp and aiofiles for async I/O.

Threading
from threading import Thread, Lock
import threading

# Basic thread
def worker(name):
    print(f"Thread {name} a run")

t = Thread(target=worker, args=("A",))
t.start()
t.join()  # waits until it finishes

# Multiple threads
threads = []
for i in range(5):
    t = Thread(target=worker, args=(str(i),))
    threads.append(t)
    t.start()
for t in threads:
    t.join()

# Lock (thread-safe)
lock = Lock()
counter = 0

def increment():
    global counter
    with lock:
        counter += 1

# GIL: Python threads do not parallelize CPU
# Use for I/O, not for CPU-bound

Thread for concurrent I/O operations. Lock protects shared data. t.join() waits until it finishes. The GIL prevents real parallelism on CPU — for CPU-bound use multiprocessing.

concurrent.futures
from concurrent.futures import (
    ThreadPoolExecutor,
    ProcessPoolExecutor,
    as_completed
)

# ThreadPool (I/O-bound)
def fetch(url):
    import requests
    return requests.get(url).status_code

with ThreadPoolExecutor(max_workers=5) as pool:
    urls = ["http://a.com", "http://b.com"]
    results = list(pool.map(fetch, urls))

# ProcessPool (CPU-bound)
def cpu_work(n):
    return sum(i*i for i in range(n))

with ProcessPoolExecutor() as pool:
    futures = [pool.submit(cpu_work, n) for n in nums]
    for f in as_completed(futures):
        print(f.result())

# as_completed: results in completion order

ThreadPoolExecutor for I/O (HTTP, files). ProcessPoolExecutor for CPU (calculations). pool.map() applies a function to a list. as_completed() returns in completion order. High-level API over threads/processes.

Web e APIs


9 cards
requests
import requests

# GET
r = requests.get("https://api.example.com/users")
r.status_code   # 200
r.json()        # parse JSON
r.headers       # headers
r.text          # raw body

# With parameters
r = requests.get(url, params={"page": 1, "limit": 10})

# POST
r = requests.post(url, json={"name": "Anna"})
r = requests.post(url, data={"name": "Anna"})

# Headers and auth
r = requests.get(url, headers={"Authorization": f"Bearer {token}"})

# Timeout and session
r = requests.get(url, timeout=5)
with requests.Session() as s:
    s.get(url)  # reuses connection

requests is the standard HTTP library. .json() parses automatically. params for query string. json= sends JSON with the correct header. timeout avoids hangs. Session() reuses connections.

Web Scraping
import requests
from bs4 import BeautifulSoup

# Fetch + parse
r = requests.get("https://example.com")
soup = BeautifulSoup(r.text, "html.parser")

# CSS selectors
title = soup.select_one("h1").text
links = soup.select("a.link")
for a in links:
    print(a["href"], a.text)

# By tag and attribute
soup.find("div", class_="content")
soup.find_all("p", limit=5)

# lxml (faster)
soup = BeautifulSoup(r.text, "lxml")

# For dynamic sites: selenium / playwright
# from playwright.sync_api import sync_playwright

BeautifulSoup parses HTML. select() uses CSS selectors. find()/find_all() by tag/attribute. lxml is a faster parser. For dynamic JavaScript: playwright or selenium.

httpx and Async HTTP
import httpx

# Sync (like requests)
r = httpx.get("https://api.example.com/users")
r.json()

# Async
async def fetch_all(urls):
    async with httpx.AsyncClient() as client:
        tasks = [client.get(url) for url in urls]
        responses = await asyncio.gather(*tasks)
        return [r.json() for r in responses]

# With timeout and headers
async with httpx.AsyncClient(
    timeout=10.0,
    headers={"Authorization": f"Bearer {token}"},
) as client:
    r = await client.post(
        url,
        json={"name": "Anna"},
    )

# HTTP/2
async with httpx.AsyncClient(http2=True) as c:
    r = await c.get(url)

httpx is a modern alternative to requests with async support. AsyncClient for concurrent requests. Supports HTTP/2. API compatible with requests. Ideal for FastAPI and async applications. asyncio.gather() for multiple requests.

Flask — Basic API
from flask import Flask, jsonify, request

app = Flask(__name__)

@app.route("/api/users", methods=["GET"])
def list_users():
    users = [{"id": 1, "name": "Anna"}]
    return jsonify(users)

@app.route("/api/users", methods=["POST"])
def create_user():
    data = request.get_json()
    name = data.get("name")
    return jsonify({"id": 2, "name": name}), 201

@app.route("/api/users/<int:user_id>")
def get_user(user_id):
    return jsonify({"id": user_id})

if __name__ == "__main__":
    app.run(debug=True, port=5000)

Flask is a micro web framework. @app.route() defines endpoints. jsonify() returns JSON. request.get_json() reads the body. methods=["POST"] defines HTTP verbs. debug=True for development.

Environment Variables
import os
from pathlib import Path

# Read variable
db_host = os.environ.get("DB_HOST", "localhost")
api_key = os.environ["API_KEY"]  # KeyError if it does not exist

# python-dotenv
# pip install python-dotenv
from dotenv import load_dotenv
load_dotenv()  # loads .env

# .env:
# DB_HOST=localhost
# DB_PORT=5432
# SECRET_KEY=abc123

# Config pattern
class Config:
    DB_HOST = os.getenv("DB_HOST", "localhost")
    DB_PORT = int(os.getenv("DB_PORT", "5432"))
    DEBUG = os.getenv("DEBUG", "false").lower() == "true"

config = Config()

os.environ.get() reads environment variables. python-dotenv loads the .env file. Never commit .env (add it to .gitignore). Pattern: Config class with defaults. os.getenv() = os.environ.get().

FastAPI
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

class UserCreate(BaseModel):
    name: str
    email: str
    age: int | None = None

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    if user_id > 100:
        raise HTTPException(404, "Not found")
    return {"id": user_id}

@app.post("/users", status_code=201)
async def create_user(user: UserCreate):
    return {"id": 1, **user.model_dump()}

# Run: uvicorn main:app --reload
# Automatic docs: /docs (Swagger)

FastAPI is modern and async. Pydantic validates data automatically. Type hints generate validation and docs. HTTPException for errors. Automatic docs at /docs. Run with uvicorn.

HTTP Methods and Status
# HTTP verbs
# GET    - read resource
# POST   - create resource
# PUT    - replace resource
# PATCH  - partial update
# DELETE - remove resource

# Status codes
# 200 OK
# 201 Created
# 204 No Content
# 400 Bad Request
# 401 Unauthorized
# 403 Forbidden
# 404 Not Found
# 422 Unprocessable Entity
# 500 Internal Server Error

# In Flask
from flask import jsonify
return jsonify({"error": "Not found"}), 404

# In FastAPI
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="Not found")

HTTP verbs: GET read, POST create, PUT replace, PATCH partial, DELETE remove. Codes: 2xx success, 4xx client error, 5xx server error. 401 = not authenticated, 403 = no permission.

JSON API — Best Practices
# Standardized responses
def response_ok(data, msg="Success"):
    return {"status": "ok", "message": msg, "data": data}

def response_error(msg, code=400):
    return {"status": "error", "message": msg}, code

# Validation with Pydantic
from pydantic import BaseModel, EmailStr, field_validator

class UserIn(BaseModel):
    name: str
    email: EmailStr
    age: int

    @field_validator("age")
    @classmethod
    def age_valida(cls, v):
        if v < 0 or v > 150:
            raise ValueError("Age invalid")
        return v

# Pagination
def paginate(items, page=1, per_page=20):
    start = (page - 1) * per_page
    return items[start:start + per_page]

APIs: standardized responses with status, message, data. Pydantic validates input. @field_validator for custom rules. Pagination with page/per_page. Correct HTTP codes: 200, 201, 400, 404, 500.

Testing with pytest
import pytest

# Basic test
def test_sum():
    assert sum(2, 3) == 5

# With fixtures
@pytest.fixture
def client():
    app.config["TESTING"] = True
    with app.test_client() as c:
        yield c

def test_list(client):
    resp = client.get("/api/users")
    assert resp.status_code == 200
    assert len(resp.json) > 0

# Parametrize
@pytest.mark.parametrize("n,esperado", [
    (1, 1), (2, 4), (3, 9),
])
def test_square(n, esperado):
    assert square(n) == esperado

# Exceptions
with pytest.raises(ValueError):
    divide(1, 0)

pytest is the standard testing framework. Simple assert (no self.assertEqual). @pytest.fixture for shared setup. @pytest.mark.parametrize tests multiple cases. pytest.raises() checks exceptions. Run with pytest -v.

Installation and Setup


9 cards
Installing Python
# Windows:
# 1. Download at python.org/downloads
# 2. Check "Add Python to PATH" (important!)
# 3. Install

# macOS (Homebrew):
brew install python

# Linux (Ubuntu/Debian):
sudo apt update
sudo apt install python3 python3-pip

# Verify installation:
python --version     # Windows
python3 --version    # macOS/Linux
pip --version

Download the official installer at python.org. On Windows check Add Python to PATH to use python in the terminal. On macOS use Homebrew. On Linux use apt. python3 is the standard command on Unix systems.

Virtual Environments (venv)
# Create a virtual environment:
python -m venv .venv

# Activate:
# Windows:
.venv\Scripts\activate
# macOS/Linux:
source .venv/bin/activate

# The prompt changes: (.venv) $
# Install isolated packages:
pip install requests

# Deactivate:
deactivate

# Each project should have its
# own virtual environment!

venv creates isolated environments — each project has its own dependencies without conflicts. python -m venv .venv creates it. activate activates it (the prompt shows (.venv)). deactivate deactivates it. Essential for real projects.

pyproject.toml (modern)
# pyproject.toml (modern standard):
[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.9"
dependencies = [
    "requests>=2.31",
    "flask>=2.3",
]

[project.optional-dependencies]
dev = ["pytest", "black", "ruff"]

# Poetry (alternative to pip+venv):
# pip install poetry
poetry new my-project
poetry add requests
poetry install
poetry run python main.py

# Install dev dependencies:
poetry install --with dev

pyproject.toml is the modern configuration standard (replaces setup.py). Poetry manages dependencies and virtual environments in an integrated way. poetry add installs and registers. Alternatives: uv (very fast) and pdm. New projects should use pyproject.toml.

First Script
# hello.py
print("Hello, World!")

# Run in the terminal:
# python hello.py        (Windows)
# python3 hello.py       (macOS/Linux)

# Script with user input:
name = input("What is your name? ")
print(f"Hello, {name}!")

# Comments start with #
# Python uses indentation (4 spaces)
# instead of braces {}

Create a .py file and run it with python file.py. print() shows output. input() reads from the keyboard. f-strings (f"...{var}") interpolate variables. Indentation defines the code blocks.

requirements.txt
# requirements.txt (example):
requests==2.31.0
flask>=2.3.0
numpy~=1.24
django

# Version operators:
# ==  exact version
# >=  greater or equal
# <=  less or equal
# ~=  compatible (same minor)
# !=  different from

# Generate from the environment:
pip freeze > requirements.txt

# Install everything:
pip install -r requirements.txt

requirements.txt lists the project dependencies with versions. == pins the exact version. >= allows newer ones. ~= allows only patches. Generate with pip freeze and install with pip install -r. It guarantees reproducibility.

Interactive REPL
# Start the REPL (Read-Eval-Print Loop):
# python        (Windows)
# python3       (macOS/Linux)

>>> 2 + 3
5
>>> "python".upper()
'PYTHON'
>>> import math
>>> math.sqrt(16)
4.0
>>> exit()      # or Ctrl+D to quit

# IPython (enhanced REPL):
# pip install ipython
# ipython
# Supports autocomplete, history,
# magic commands (%timeit, %run)

The REPL lets you test code line by line — ideal for experimenting. >>> is the prompt. exit() or Ctrl+D to quit. IPython is a more powerful version with autocomplete and magic commands.

IDEs and Editors
# VS Code (recommended):
# 1. Install the "Python" extension (Microsoft)
# 2. Select the interpreter:
#    Ctrl+Shift+P > "Python: Select Interpreter"
# 3. Run: ▶ button or Ctrl+F5

# PyCharm (full IDE):
# Community (free) or Professional
# Debugging, tests and integrated frameworks

# Jupyter Notebook (data/science):
pip install notebook
jupyter notebook
# Opens in the browser — interactive cells

# Run from the terminal:
python script.py

VS Code with the Python extension is the most popular choice. PyCharm is a full IDE with advanced debugging. Jupyter Notebook is ideal for data science with interactive cells. All of them support autocomplete and linting.

pip (Package Manager)
# Install a package:
pip install requests
pip install numpy pandas matplotlib

# Specific version:
pip install django==4.2
pip install "flask>=2.0"

# Upgrade:
pip install --upgrade requests

# List installed:
pip list
pip show requests

# Remove:
pip uninstall requests

# Save/install dependencies:
pip freeze > requirements.txt
pip install -r requirements.txt

pip is Python's standard package manager. pip install installs from PyPI. pip freeze > requirements.txt exports the dependencies. pip install -r reinstalls them. Use pip3 on Unix systems with Python 2 and 3.

Project Structure
my_project/
├── .venv/              # virtual environment
├── my_project/        # main package
│   ├── __init__.py
│   ├── main.py
│   └── utils.py
├── tests/              # tests
│   └── test_main.py
├── requirements.txt    # dependencies
├── .gitignore          # ignore .venv/
└── README.md

# __init__.py marks the folder
# the a Python package

# Essential .gitignore:
# .venv/
# __pycache__/
# *.pyc

Standard structure: package folder with __init__.py, tests/ for tests, requirements.txt for dependencies. .venv/ never goes into git. __pycache__/ are compiled files — ignore them. __init__.py marks the folder the a package.