[OK] CHEATSHEET COMPLET - FONCTIONS PYTHON


[OK] 1. DÉFINITION BASIQUE D'UNE FONCTION
[OK] Une fonction est un bloc de code réutilisable qui effectue une tâche
def greet():
    """Fonction simple sans paramètres ni valeur de retour"""
    print("Hello, World!")

# Appel de la fonction
greet()  # Affiche: Hello, World!


[OK] 2. FONCTION AVEC PARAMÈTRES
[OK] Les paramètres permettent de passer des données à la fonction
def greet_person(name):
    """Fonction avec un paramètre positionnel"""
    print(f"Hello, {name}!")

greet_person("Alice")  # Affiche: Hello, Alice!

# Plusieurs paramètres
def add_numbers(a, b):
    """Additionne deux nombres"""
    result = a + b
    print(f"{a} + {b} = {result}")

add_numbers(5, 3)  # Affiche: 5 + 3 = 8


[OK] 3. RETURN - RETOURNER UNE VALEUR
[OK] return permet de renvoyer un résultat au code appelant
def multiply(a, b):
    """Retourne le produit de deux nombres"""
    return a * b

result = multiply(4, 5)
print(result)  # 20

# Return multiple - retourne un tuple
def get_min_max(numbers):
    """Retourne le min et le max d'une liste"""
    return min(numbers), max(numbers)

minimum, maximum = get_min_max([1, 5, 3, 9, 2])
print(f"Min: {minimum}, Max: {maximum}")  # Min: 1, Max: 9

# Return anticipé pour sortir de la fonction
def is_adult(age):
    """Vérifie si une personne est majeure"""
    if age >= 18:
        return True
    return False  # Exécuté seulement si age < 18


[OK] 4. PARAMÈTRES PAR DÉFAUT
[OK] Permettent de rendre certains paramètres optionnels
def greet_with_title(name, title="Mr."):
    """Salue avec un titre (par défaut: Mr.)"""
    return f"Hello, {title} {name}!"

print(greet_with_title("Smith"))           # Hello, Mr. Smith!
print(greet_with_title("Smith", "Dr."))    # Hello, Dr. Smith!

# [ATTENTION] ATTENTION: Ne jamais utiliser de valeurs mutables par défaut
# [X] MAUVAIS
def add_item_bad(item, items=[]):  # Liste partagée entre tous les appels!
    items.append(item)
    return items

# [OK] BON
def add_item_good(item, items=None):
    """Ajoute un élément à une liste"""
    if items is None:
        items = []  # Nouvelle liste à chaque appel
    items.append(item)
    return items


[OK] 5. ARGUMENTS POSITIONNELS ET NOMMÉS

def create_user(username, email, age, country="USA"):
    """Crée un profil utilisateur"""
    return {
        'username': username,
        'email': email,
        'age': age,
        'country': country
    }

# Arguments positionnels (ordre important)
user1 = create_user("john_doe", "john@email.com", 25)

# Arguments nommés (ordre n'a pas d'importance)
user2 = create_user(age=30, username="jane_doe", email="jane@email.com")

# Mélange des deux (positionnels d'abord, puis nommés)
user3 = create_user("bob", "bob@email.com", age=28, country="Canada")


[OK] 6. *args - NOMBRE VARIABLE D'ARGUMENTS POSITIONNELS
[OK] *args capture tous les arguments positionnels dans un tuple
def sum_all(*args):
    """Additionne tous les nombres passés en argument"""
    print(f"args type: {type(args)}")  # <class 'tuple'>
    print(f"args value: {args}")
    return sum(args)

print(sum_all(1, 2, 3))           # 6
print(sum_all(10, 20, 30, 40))    # 100

def print_names(*names):
    """Affiche tous les noms"""
    for name in names:
        print(f"- {name}")

print_names("Alice", "Bob", "Charlie")


[OK] 7. **kwargs - NOMBRE VARIABLE D'ARGUMENTS NOMMÉS
[OK] **kwargs capture tous les arguments nommés dans un dictionnaire
def print_info(**kwargs):
    """Affiche toutes les informations passées"""
    print(f"kwargs type: {type(kwargs)}")  # <class 'dict'>
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=30, city="Paris")

def create_profile(username, **additional_info):
    """Crée un profil avec infos optionnelles"""
    profile = {'username': username}
    profile.update(additional_info)
    return profile

profile = create_profile(
    "john_doe",
    email="john@email.com",
    age=25,
    country="USA",
    hobby="coding"
)


[OK] 8. COMBINAISON: args, kwargs ET PARAMÈTRES NORMAUX
[OK] Ordre strict: normaux, *args, défauts, **kwargs
def complex_function(required, *args, optional="default", **kwargs):
    """Démontre tous les types de paramètres"""
    print(f"Required: {required}")
    print(f"Args: {args}")
    print(f"Optional: {optional}")
    print(f"Kwargs: {kwargs}")

complex_function(
    "value1",              # required
    "arg1", "arg2",        # *args
    optional="custom",     # paramètre nommé
    key1="val1",           # **kwargs
    key2="val2"
)


[OK] 9. PARAMÈTRES KEYWORD-ONLY (Python 3+)
[OK] Paramètres après * doivent être passés par nom
def create_account(username, *, email, password):
    """email et password doivent être nommés explicitement"""
    return {'username': username, 'email': email}

# [OK] Correct
user = create_account("john", email="john@email.com", password="secret")

# [X] Erreur: email et password doivent être nommés
# user = create_account("john", "john@email.com", "secret")

def configure_server(host, port=8080, *, ssl=False, debug=False):
    """Forcer ssl et debug à être nommés pour la clarté"""
    print(f"Server: {host}:{port}, SSL={ssl}, Debug={debug}")

configure_server("localhost", 3000, ssl=True, debug=True)


[OK] 10. PARAMÈTRES POSITIONAL-ONLY (Python 3.8+)
[OK] Paramètres avant / doivent être positionnels uniquement
def divide(numerator, denominator, /):
    """Paramètres doivent être positionnels (pas de noms)"""
    return numerator / denominator

# [OK] Correct
result = divide(10, 2)

# [X] Erreur: ne peut pas utiliser de noms
# result = divide(numerator=10, denominator=2)

# Combinaison complète
def full_syntax(pos_only, /, standard, *, kwd_only):
    """Démontre les trois types de paramètres"""
    pass

# pos_only: positional-only
# standard: peut être positionnel ou nommé
# kwd_only: keyword-only


[OK] 11. DÉBALLAGE D'ARGUMENTS (* et **)
[OK] * déballe une liste/tuple, ** déballe un dictionnaire

def sum_three(a, b, c):
    return a + b + c

# Déballage avec *
numbers = [1, 2, 3]
result = sum_three(*numbers)  # Équivaut à sum_three(1, 2, 3)
print(result)  # 6

# Déballage avec **
def greet_person(name, age):
    return f"{name} is {age} years old"

person_data = {'name': 'Alice', 'age': 30}
message = greet_person(**person_data)  # Équivaut à greet_person(name='Alice', age=30)

# Combinaison
def complex_call(a, b, c, d, e):
    return a + b + c + d + e

args = [1, 2, 3]
kwargs = {'d': 4, 'e': 5}
result = complex_call(*args, **kwargs)


[OK] 12. ANNOTATIONS DE TYPE (Type Hints)
[OK] Indiquent les types attendus (pas appliqués à l'exécution)
def add(a: int, b: int) -> int:
    """Additionne deux entiers et retourne un entier"""
    return a + b

def greet(name: str, excited: bool = False) -> str:
    """Salue une personne"""
    greeting = f"Hello, {name}!"
    return greeting + "!" if excited else greeting

# Types complexes
from typing import List, Dict, Optional, Union, Tuple, Any

def process_data(
    items: List[str],
    config: Dict[str, Any],
    user_id: Optional[int] = None
) -> Tuple[bool, str]:
    """
    Traite des données avec configuration
    
    Args:
        items: Liste d'éléments à traiter
        config: Configuration sous forme de dictionnaire
        user_id: ID utilisateur optionnel
    
    Returns:
        Tuple de (succès, message)
    """
    return True, "Success"

# Union type (plusieurs types possibles)
def format_value(value: Union[int, float, str]) -> str:
    """Formate une valeur en string"""
    return str(value)


[OK] 13. DOCSTRINGS - DOCUMENTATION

def calculate_discount(price: float, discount: float) -> float:
    """
    Calcule le prix après application d'une remise.
    
    Args:
        price: Prix original en euros
        discount: Pourcentage de remise (0-100)
    
    Returns:
        Prix après remise
    
    Raises:
        ValueError: Si le prix est négatif ou la remise invalide
    
    Examples:
        >>> calculate_discount(100, 20)
        80.0
        >>> calculate_discount(50, 10)
        45.0
    """
    if price < 0:
        raise ValueError("Price cannot be negative")
    if not 0 <= discount <= 100:
        raise ValueError("Discount must be between 0 and 100")
    
    return price * (1 - discount / 100)

# Accéder à la docstring
print(calculate_discount.__doc__)
help(calculate_discount)


[OK] 14. FONCTIONS LAMBDA (ANONYMES)
[OK] Fonctions courtes d'une ligne sans nom
square = lambda x: x ** 2
print(square(5))  # 25

# Lambda avec plusieurs paramètres
add = lambda a, b: a + b
print(add(3, 4))  # 7

# Utilisation courante: avec map, filter, sorted
numbers = [1, 2, 3, 4, 5]

# map: applique une fonction à chaque élément
squared = list(map(lambda x: x ** 2, numbers))
print(squared)  # [1, 4, 9, 16, 25]

# filter: filtre selon une condition
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens)  # [2, 4]

# sorted: tri avec clé personnalisée
users = [{'name': 'Alice', 'age': 30}, {'name': 'Bob', 'age': 25}]
sorted_users = sorted(users, key=lambda u: u['age'])

# [ATTENTION] Note: Pour du code complexe, préférer def à lambda


[OK] 15. FONCTIONS IMBRIQUÉES (NESTED FUNCTIONS)
[OK] Une fonction définie à l'intérieur d'une autre
def outer_function(x):
    """Fonction externe"""
    
    def inner_function(y):
        """Fonction interne (accessible seulement dans outer_function)"""
        return x + y
    
    result = inner_function(10)
    return result

print(outer_function(5))  # 15

# Utilité: encapsulation et organisation du code
def process_text(text):
    """Traite un texte avec plusieurs étapes"""
    
    def remove_spaces(s):
        return s.strip()
    
    def to_lowercase(s):
        return s.lower()
    
    def remove_punctuation(s):
        return ''.join(c for c in s if c.isalnum() or c.isspace())
    
    # Pipeline de traitement
    text = remove_spaces(text)
    text = to_lowercase(text)
    text = remove_punctuation(text)
    return text


[OK] 16. CLOSURES - FERMETURES
[OK] Une fonction interne qui "capture" les variables de la fonction externe
def make_multiplier(factor):
    """Crée une fonction qui multiplie par un facteur donné"""
    
    def multiplier(number):
        # 'factor' est capturé de la fonction externe
        return number * factor
    
    return multiplier  # Retourne la fonction interne

# Créer différents multiplicateurs
times_2 = make_multiplier(2)
times_5 = make_multiplier(5)

print(times_2(10))  # 20
print(times_5(10))  # 50

# Exemple pratique: compteur
def create_counter():
    """Crée un compteur avec état persistant"""
    count = 0  # Variable capturée
    
    def increment():
        nonlocal count  # Permet de modifier la variable externe
        count += 1
        return count
    
    return increment

counter1 = create_counter()
counter2 = create_counter()

print(counter1())  # 1
print(counter1())  # 2
print(counter2())  # 1 (compteur indépendant)


[OK] 17. DÉCORATEURS - DECORATORS
[OK] Modifient ou étendent le comportement d'une fonction

# Décorateur simple
def uppercase_decorator(func):
    """Convertit le résultat en majuscules"""
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.upper()
    return wrapper

@uppercase_decorator
def greet(name):
    return f"hello, {name}"

print(greet("alice"))  # HELLO, ALICE

# Décorateur avec timer
import time

def timer_decorator(func):
    """Mesure le temps d'exécution d'une fonction"""
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start:.4f} seconds")
        return result
    return wrapper

@timer_decorator
def slow_function():
    time.sleep(1)
    return "Done"

# Décorateur avec paramètres
def repeat(times):
    """Répète l'exécution d'une fonction"""
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def say_hello():
    print("Hello!")

say_hello()  # Affiche Hello! trois fois

# Préserver les métadonnées avec functools.wraps
from functools import wraps

def my_decorator(func):
    @wraps(func)  # Préserve __name__, __doc__, etc.
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper


[OK] 18. GÉNÉRATEURS - GENERATORS
[OK] Fonctions qui utilisent yield au lieu de return
# Génèrent des valeurs une par une (lazy evaluation)

def count_up_to(n):
    """Génère les nombres de 1 à n"""
    count = 1
    while count <= n:
        yield count  # Pause et retourne la valeur
        count += 1

# Utilisation
for number in count_up_to(5):
    print(number)  # 1, 2, 3, 4, 5

# Générateur infini
def fibonacci():
    """Génère la suite de Fibonacci indéfiniment"""
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# Utiliser avec next()
fib = fibonacci()
print(next(fib))  # 0
print(next(fib))  # 1
print(next(fib))  # 1
print(next(fib))  # 2

# Générateur avec paramètres
def read_large_file(file_path):
    """Lit un fichier ligne par ligne (économe en mémoire)"""
    with open(file_path, 'r') as file:
        for line in file:
            yield line.strip()

# Generator expression (comme list comprehension)
squares_gen = (x**2 for x in range(10))  # () au lieu de []
print(list(squares_gen))


[OK] 19. FONCTIONS RÉCURSIVES
[OK] Une fonction qui s'appelle elle-même

def factorial(n):
    """Calcule la factorielle de n"""
    # Cas de base: condition d'arrêt
    if n <= 1:
        return 1
    # Cas récursif
    return n * factorial(n - 1)

print(factorial(5))  # 120 (5 * 4 * 3 * 2 * 1)

# Fibonacci récursif
def fib(n):
    """Retourne le n-ième nombre de Fibonacci"""
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

# Récursion avec cache (memoization)
from functools import lru_cache

@lru_cache(maxsize=None)
def fib_cached(n):
    """Fibonacci optimisé avec cache"""
    if n <= 1:
        return n
    return fib_cached(n - 1) + fib_cached(n - 2)

# [ATTENTION] Attention à la limite de récursion
import sys
print(sys.getrecursionlimit())  # Par défaut: 1000
# sys.setrecursionlimit(2000)  # Augmenter si nécessaire


[OK] 20. FONCTIONS DE PREMIÈRE CLASSE
[OK] En Python, les fonctions sont des objets comme les autres

def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

# Assigner une fonction à une variable
operation = add
print(operation(5, 3))  # 8

# Passer une fonction en argument
def apply_operation(func, a, b):
    """Applique une fonction à deux nombres"""
    return func(a, b)

result = apply_operation(add, 10, 5)  # 15
result = apply_operation(subtract, 10, 5)  # 5

# Retourner une fonction
def get_operation(op_type):
    """Retourne une fonction selon le type"""
    if op_type == 'add':
        return add
    elif op_type == 'subtract':
        return subtract

op = get_operation('add')
print(op(3, 4))  # 7

# Liste de fonctions
operations = [add, subtract, lambda x, y: x * y]
for op in operations:
    print(op(5, 3))


[OK] 21. FONCTIONS BUILT-IN UTILES


# map(): Applique une fonction à chaque élément
numbers = [1, 2, 3, 4]
squared = list(map(lambda x: x**2, numbers))

# filter(): Filtre selon une condition
evens = list(filter(lambda x: x % 2 == 0, numbers))

# reduce(): Réduit une séquence à une seule valeur
from functools import reduce
total = reduce(lambda x, y: x + y, numbers)  # 10

# zip(): Combine plusieurs itérables
names = ['Alice', 'Bob']
ages = [25, 30]
combined = list(zip(names, ages))  # [('Alice', 25), ('Bob', 30)]

# enumerate(): Itère avec index
for index, value in enumerate(['a', 'b', 'c']):
    print(f"{index}: {value}")

# any() et all()
print(any([False, True, False]))   # True (au moins un True)
print(all([True, True, True]))     # True (tous True)

# sorted() avec key
users = [{'name': 'Alice', 'age': 30}, {'name': 'Bob', 'age': 25}]
sorted_by_age = sorted(users, key=lambda u: u['age'])


[OK] 22. PARTIAL FUNCTIONS
[OK] Créer une nouvelle fonction avec certains arguments pré-remplis
from functools import partial

def power(base, exponent):
    return base ** exponent

# Créer une fonction "carré" et "cube"
square = partial(power, exponent=2)
cube = partial(power, exponent=3)

print(square(5))  # 25
print(cube(5))    # 125

# Exemple pratique
def log_message(message, level='INFO'):
    print(f"[{level}] {message}")

# Créer des loggers spécialisés
log_error = partial(log_message, level='ERROR')
log_warning = partial(log_message, level='WARNING')

log_error("Something went wrong")      # [ERROR] Something went wrong
log_warning("Be careful")               # [WARNING] Be careful


[OK] 23. CALLBACK FUNCTIONS
[OK] Fonctions passées en argument pour être appelées plus tard

def process_data(data, on_success, on_error):
    """Traite des données avec callbacks"""
    try:
        result = data * 2
        on_success(result)
    except Exception as e:
        on_error(e)

def success_handler(result):
    print(f"Success! Result: {result}")

def error_handler(error):
    print(f"Error occurred: {error}")

process_data(10, success_handler, error_handler)


[OK] 24. EXEMPLES PRATIQUES COMPLETS


# Exemple 1: Fonction de validation avec décorateur
def validate_args(*validators):
    """Décorateur pour valider les arguments"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # Valider chaque argument
            for i, (arg, validator) in enumerate(zip(args, validators)):
                if not validator(arg):
                    raise ValueError(f"Argument {i} failed validation")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@validate_args(lambda x: isinstance(x, str), lambda x: isinstance(x, int))
def create_user(name, age):
    return {'name': name, 'age': age}

# Exemple 2: Factory pattern avec fonctions
def create_logger(log_level='INFO'):
    """Crée un logger avec un niveau spécifique"""
    
    def log(message):
        print(f"[{log_level}] {message}")
    
    def set_level(new_level):
        nonlocal log_level
        log_level = new_level
    
    return log, set_level

logger, set_level = create_logger('DEBUG')
logger("Application started")  # [DEBUG] Application started
set_level('ERROR')
logger("Critical error")        # [ERROR] Critical error

# Exemple 3: Pipeline de traitement
def pipeline(*functions):
    """Compose plusieurs fonctions en une pipeline"""
    def inner(value):
        for func in functions:
            value = func(value)
        return value
    return inner

# Fonctions de transformation
def remove_spaces(text):
    return text.strip()

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

def add_prefix(text):
    return f"processed: {text}"

# Créer une pipeline
process = pipeline(remove_spaces, lowercase, add_prefix)
result = process("  HELLO WORLD  ")
print(result)  # processed: hello world


[OK] 25. BONNES PRATIQUES


# [OK] Noms descriptifs
def calculate_total_price(items, tax_rate):  # Clair
    pass

# [X] Noms vagues
def calc(x, y):  # Peu clair
    pass

# [OK] Une fonction = une responsabilité
def validate_email(email):
    """Fait une seule chose: valider un email"""
    return '@' in email and '.' in email

# [OK] Limiter le nombre de paramètres (max 3-5)
def create_user(name, email, age):  # OK
    pass

def create_user_complete(name, email, age, address, phone, city, country, zip_code):  # Trop!
    pass

# [OK] Alternative: utiliser un dictionnaire ou une classe
def create_user_better(user_data: dict):
    pass

# [OK] Valeurs par défaut à la fin
def greet(name, title="Mr."):  # Correct
    pass

# [OK] Documenter avec docstrings
def complex_function(param1, param2):
    """
    Description courte de la fonction.
    
    Description plus détaillée si nécessaire.
    
    Args:
        param1: Description du paramètre 1
        param2: Description du paramètre 2
    
    Returns:
        Description de la valeur retournée
    
    Raises:
        ValueError: Quand cette erreur se produit
    """
    pass

# [OK] Éviter les effets de bord
def add_numbers(a, b):
    return a + b  # Pure function - pas d'effet de bord

# [X] Éviter
total = 0
def add_to_total(n):
    global total  # Modifie l'état global
    total += n

# [OK] Typage pour la clarté
def process(data: List[str]) -> Dict[str, int]:
    pass

# [OK] Gestion d'erreurs explicite
def divide(a: float, b: float) -> float:
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b


[OK] 26. RÉSUMÉ DES CONCEPTS CLÉS

"""
DÉFINITION:
    def function_name(params): ...
    lambda x: x * 2

PARAMÈTRES:
    Positionnels: def func(a, b)
    Par défaut: def func(a, b=10)
    *args: nombre variable d'arguments positionnels
    **kwargs: nombre variable d'arguments nommés
    Keyword-only: def func(a, *, b)
    Positional-only: def func(a, /)

CONCEPTS AVANCÉS:
    Closures: fonction qui capture variables externes
    Décorateurs: @decorator pour modifier comportement
    Générateurs: yield pour générer valeurs lazy
    Récursion: fonction qui s'appelle elle-même
    Partial: pré-remplir certains arguments

BUILT-IN UTILES:
    map(), filter(), reduce(), zip(), enumerate()
    sorted(), any(), all(), sum(), min(), max()

BONNES PRATIQUES:
    - Noms descriptifs et verbes pour actions
    - Une fonction = une responsabilité
    - Documentation avec docstrings
    - Type hints pour la clarté
    - Gestion d'erreurs explicite
    - Éviter les effets de bord
    - Max 3-5 paramètres
"""