Cheatsheet Python
Linguagem de programação versátil e fácil de aprender
Python
Basic Syntax
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) # TrueBuilt-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.14Conversions: 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:
passis 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, 3print() 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" % namef-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 0None 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
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) # TruePython 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
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, 2Functions 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) # 10Functions 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 decoratorDecorators 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) # instantRecursion: 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
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] # 4Dunder 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):
passInheritance 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
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 elementsCounter 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
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 ityield 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 outIterators 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
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) # recursivepathlib.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 contextmanagerwith 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 itassert 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
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
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: # betterAnti-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, isortPEP 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 arraystimeit 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
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 outputTemplate 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
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 iasync 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-boundThread 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 orderThreadPoolExecutor 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
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 connectionrequests 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_playwrightBeautifulSoup 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
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 devpyproject.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.