# ============================================================
# Fichier: python_cheats/cheatsheets/syntax.txt
# Cheatsheet Syntaxe Python - Guide Complet
# ============================================================

[OK] VARIABLES & AFFECTATION

# Affectation simple
x = 10                      # Entier
y = 3.14                    # Float
name = "Alice"              # String
is_valid = True             # Boolean
nothing = None              # None

# Affectation multiple
a, b, c = 1, 2, 3
x = y = z = 0

# Swap de variables
a, b = b, a

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

# Affectation avec opérateurs
x += 5    # x = x + 5
x -= 3    # x = x - 3
x *= 2    # x = x * 2
x /= 4    # x = x / 4
x //= 2   # Division entière
x %= 3    # Modulo
x **= 2   # Puissance

# Affectation conditionnelle (ternaire)
x = 10 if condition else 20

# Walrus operator (Python 3.8+)
if (n := len(liste)) > 10:
    print(f"Liste longue: {n} éléments")

[OK] TYPES DE DONNÉES PRIMITIFS

# Nombres
entier = 42
float_num = 3.14
complexe = 3 + 4j
binaire = 0b1010        # 10 en décimal
octal = 0o12            # 10 en décimal
hexa = 0xA              # 10 en décimal

# Conversion de types
int("10")               # String vers int
float("3.14")           # String vers float
str(42)                 # Nombre vers string
bool(1)                 # Vers boolean (0 = False, reste = True)

# Opérations numériques
x + y                   # Addition
x - y                   # Soustraction
x * y                   # Multiplication
x / y                   # Division (toujours float)
x // y                  # Division entière
x % y                   # Modulo
x ** y                  # Puissance
abs(x)                  # Valeur absolue
round(3.7)              # Arrondi (4)
pow(2, 3)               # 2^3 = 8

[OK] STRINGS (CHAÎNES)

# Création
s = "Hello"
s = 'World'
s = """Multi
ligne"""
s = r"Raw string\n"     # N'interprète pas \n

# Opérations
s1 + s2                 # Concaténation
s * 3                   # Répétition
s[0]                    # Premier caractère
s[-1]                   # Dernier caractère
s[1:4]                  # Slicing
s[::-1]                 # Inverser
len(s)                  # Longueur
'llo' in s              # Contient

# Méthodes courantes
s.upper()               # MAJUSCULES
s.lower()               # minuscules
s.capitalize()          # Première lettre majuscule
s.title()               # Chaque Mot Capitalisé
s.strip()               # Enlever espaces début/fin
s.lstrip()              # Enlever espaces à gauche
s.rstrip()              # Enlever espaces à droite
s.replace('old', 'new') # Remplacer
s.split(',')            # Découper en liste
','.join(liste)         # Joindre liste en string
s.startswith('He')      # Commence par
s.endswith('lo')        # Finit par
s.find('ll')            # Position (-1 si absent)
s.count('l')            # Nombre d'occurrences
s.isdigit()             # Que des chiffres?
s.isalpha()             # Que des lettres?
s.isalnum()             # Lettres et chiffres?
s.islower()             # Tout en minuscules?
s.isupper()             # Tout en majuscules?

# Formatage
name, age = "Alice", 30

# f-strings (Python 3.6+, recommandé)
f"Je m'appelle {name} et j'ai {age} ans"
f"Calcul: {10 + 5}"
f"Float: {3.14159:.2f}"              # 2 décimales
f"Padding: {name:>10}"                # Alignement droite
f"Binary: {42:b}"                     # Binaire

# format()
"Je m'appelle {} et j'ai {} ans".format(name, age)
"{0} {1} {0}".format("A", "B")       # A B A
"{name} a {age} ans".format(name="Bob", age=25)

# % (ancien style)
"Je m'appelle %s et j'ai %d ans" % (name, age)

[OK] LISTES

# Création
lst = [1, 2, 3, 4, 5]
lst = list(range(5))            # [0, 1, 2, 3, 4]
lst = [0] * 5                   # [0, 0, 0, 0, 0]

# Accès
lst[0]                          # Premier élément
lst[-1]                         # Dernier élément
lst[1:3]                        # Slicing [élément 1, 2]
lst[::2]                        # Éléments pairs
lst[::-1]                       # Inverser

# Modification
lst[0] = 10                     # Modifier élément
lst[1:3] = [20, 30]            # Modifier slice

# Ajout
lst.append(6)                   # Ajouter à la fin
lst.insert(0, -1)              # Insérer à position
lst.extend([7, 8])             # Ajouter plusieurs
lst += [9, 10]                  # Concaténer

# Suppression
lst.pop()                       # Retirer et retourner dernier
lst.pop(0)                      # Retirer à index
lst.remove(5)                   # Retirer première occurrence
del lst[0]                      # Supprimer à index
del lst[1:3]                    # Supprimer slice
lst.clear()                     # Vider la liste

# Recherche
lst.index(3)                    # Index de première occurrence
lst.count(2)                    # Nombre d'occurrences
3 in lst                        # Contient?

# Tri
lst.sort()                      # Trier en place
lst.sort(reverse=True)          # Trier décroissant
sorted(lst)                     # Retourner copie triée
lst.reverse()                   # Inverser en place
reversed(lst)                   # Retourner itérateur inversé

# Opérations
len(lst)                        # Longueur
min(lst)                        # Minimum
max(lst)                        # Maximum
sum(lst)                        # Somme
any(lst)                        # Au moins un True?
all(lst)                        # Tous True?

# Copie
lst_copy = lst.copy()           # Copie superficielle
lst_copy = lst[:]               # Copie superficielle
import copy
lst_deep = copy.deepcopy(lst)   # Copie profonde

[OK] TUPLES

# Création
t = (1, 2, 3)
t = 1, 2, 3                     # Parenthèses optionnelles
t = (1,)                        # Tuple à 1 élément
t = tuple([1, 2, 3])            # Depuis liste

# Accès (comme listes mais immutable)
t[0]                            # Premier élément
t[-1]                           # Dernier élément
t[1:3]                          # Slicing

# Unpacking
a, b, c = t
a, *rest = t                    # a=1, rest=[2,3]

# Méthodes
t.count(2)                      # Nombre d'occurrences
t.index(3)                      # Index de première occurrence

# Named tuples
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(10, 20)
p.x, p.y                        # 10, 20

[OK] DICTIONNAIRES

# Création
d = {'nom': 'Alice', 'age': 30}
d = dict(nom='Alice', age=30)
d = dict([('nom', 'Alice'), ('age', 30)])
d = {k: v for k, v in zip(keys, values)}

# Accès
d['nom']                        # Accès (KeyError si absent)
d.get('nom')                    # Retourne None si absent
d.get('ville', 'Paris')         # Avec valeur par défaut

# Modification
d['nom'] = 'Bob'                # Modifier
d['ville'] = 'Paris'            # Ajouter
d.update({'pays': 'France'})    # Ajouter/modifier plusieurs
d.setdefault('age', 25)         # Définir si absent

# Suppression
del d['nom']                    # Supprimer clé
d.pop('age')                    # Retirer et retourner
d.pop('absent', None)           # Sans erreur si absent
d.popitem()                     # Retirer dernière paire (Python 3.7+)
d.clear()                       # Vider

# Navigation
d.keys()                        # Vue des clés
d.values()                      # Vue des valeurs
d.items()                       # Vue des paires (clé, valeur)
'nom' in d                      # Clé existe?
'Alice' in d.values()           # Valeur existe?

# Fusion (Python 3.9+)
d1 | d2                         # Fusionner (d2 prioritaire)
d1 |= d2                        # Fusionner en place

# Fusion (avant Python 3.9)
{**d1, **d2}                    # Fusionner
d1.update(d2)                   # Fusionner en place

# Copie
d_copy = d.copy()               # Copie superficielle
import copy
d_deep = copy.deepcopy(d)       # Copie profonde

# Defaultdict
from collections import defaultdict
d = defaultdict(list)           # Valeur par défaut = liste vide
d['key'].append(1)              # Pas d'erreur

# Counter
from collections import Counter
c = Counter([1, 2, 2, 3, 3, 3])
c.most_common(2)                # [(3, 3), (2, 2)]

[OK] SETS (ENSEMBLES)

# Création
s = {1, 2, 3}
s = set([1, 2, 3])
s = set()                       # Set vide (pas {})

# Ajout
s.add(4)                        # Ajouter élément
s.update([5, 6])                # Ajouter plusieurs

# Suppression
s.remove(3)                     # Retirer (erreur si absent)
s.discard(3)                    # Retirer (pas d'erreur)
s.pop()                         # Retirer élément arbitraire
s.clear()                       # Vider

# Opérations d'ensemble
s1 | s2                         # Union
s1 & s2                         # Intersection
s1 - s2                         # Différence
s1 ^ s2                         # Différence symétrique
s1.union(s2)                    # Union (méthode)
s1.intersection(s2)             # Intersection
s1.difference(s2)               # Différence
s1.symmetric_difference(s2)     # Différence symétrique

# Tests
s1.issubset(s2)                 # s1 ⊆ s2
s1.issuperset(s2)               # s1 ⊇ s2
s1.isdisjoint(s2)               # Pas d'élément commun

[OK] CONDITIONS (IF/ELIF/ELSE)

# Structure de base
if x > 10:
    print("Grand")
elif x > 5:
    print("Moyen")
else:
    print("Petit")

# Opérateurs de comparaison
x == y          # Égal
x != y          # Différent
x > y           # Supérieur
x < y           # Inférieur
x >= y          # Supérieur ou égal
x <= y          # Inférieur ou égal
x is y          # Même objet
x is not y      # Objet différent
x in lst        # Appartient à
x not in lst    # N'appartient pas à

# Opérateurs logiques
x and y         # ET logique
x or y          # OU logique
not x           # NON logique

# Conditions combinées
if x > 0 and x < 10:
    pass
if 0 < x < 10:  # Pythonic
    pass

# Expression conditionnelle (ternaire)
valeur = "positif" if x > 0 else "négatif"

# Truthiness
# False: False, None, 0, 0.0, '', [], {}, set()
# True: Tout le reste

if lst:         # Vérifie si liste non vide
    pass

# Match/Case (Python 3.10+)
match x:
    case 1:
        print("Un")
    case 2 | 3:
        print("Deux ou trois")
    case _:
        print("Autre")

[OK] BOUCLES FOR

# Itérer sur range
for i in range(5):              # 0 à 4
    print(i)

for i in range(2, 10):          # 2 à 9
    print(i)

for i in range(0, 10, 2):       # 0, 2, 4, 6, 8
    print(i)

# Itérer sur liste
for item in liste:
    print(item)

# Avec index (enumerate)
for i, val in enumerate(liste):
    print(f"{i}: {val}")

for i, val in enumerate(liste, start=1):  # Commence à 1
    print(f"{i}: {val}")

# Itérer sur dictionnaire
for key in d:                   # Clés
    print(key)

for key in d.keys():            # Clés (explicite)
    print(key)

for value in d.values():        # Valeurs
    print(value)

for key, value in d.items():    # Paires
    print(f"{key}: {value}")

# Itérer sur plusieurs listes (zip)
for a, b in zip(list1, list2):
    print(a, b)

# Boucle infinie
for i in range(1000000000):     # Éviter si possible
    break

# Contrôle de flux
for i in range(10):
    if i == 3:
        continue                # Passer à l'itération suivante
    if i == 7:
        break                   # Sortir de la boucle
    print(i)
else:                           # Exécuté si pas de break
    print("Boucle terminée normalement")

[OK] BOUCLES WHILE

# Structure de base
while condition:
    # code
    pass

# Exemple
i = 0
while i < 5:
    print(i)
    i += 1

# Avec break et continue
while True:
    x = input("Entrez 'q' pour quitter: ")
    if x == 'q':
        break
    if not x:
        continue
    print(f"Vous avez entré: {x}")

# Clause else
i = 0
while i < 5:
    i += 1
else:
    print("Boucle terminée")

[OK] FONCTIONS

# Fonction simple
def ma_fonction():
    print("Hello")

# Avec paramètres
def add(a, b):
    return a + b

# Paramètres par défaut
def greet(name, message="Hello"):
    return f"{message}, {name}!"

# Paramètres nommés
result = greet(name="Alice", message="Hi")

# *args (arguments positionnels variables)
def somme(*args):
    return sum(args)

somme(1, 2, 3, 4)               # 10

# **kwargs (arguments nommés variables)
def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

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

# Combiner tout
def ma_fonction(pos1, pos2, *args, kwarg1="default", **kwargs):
    pass

# Annotations de type (Python 3.5+)
def add(a: int, b: int) -> int:
    return a + b

# Docstring
def ma_fonction(x):
    """
    Description de la fonction.
    
    Args:
        x: Description du paramètre
    
    Returns:
        Description du retour
    """
    return x * 2

# Retours multiples
def get_coords():
    return 10, 20

x, y = get_coords()

# Fonction comme objet
func = ma_fonction
func()

[OK] LAMBDA (FONCTIONS ANONYMES)

# Syntaxe de base
lambda x: x * 2

# Affectation
double = lambda x: x * 2
result = double(5)              # 10

# Plusieurs paramètres
add = lambda x, y: x + y
result = add(3, 4)              # 7

# Avec map
numbers = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x * 2, numbers))

# Avec filter
evens = list(filter(lambda x: x % 2 == 0, numbers))

# Avec sorted (clé de tri)
students = [('Alice', 25), ('Bob', 20), ('Charlie', 30)]
sorted(students, key=lambda x: x[1])  # Trier par âge

# Lambda dans compréhension (éviter, utiliser fonction normale)
# [(lambda x: x * 2)(x) for x in range(5)]

[OK] COMPRÉHENSIONS

# Liste
[x**2 for x in range(10)]
[x for x in range(20) if x % 2 == 0]
[x if x > 0 else 0 for x in numbers]

# Nested
[(x, y) for x in range(3) for y in range(3)]
[[col * row for col in range(3)] for row in range(3)]

# Dictionnaire
{k: v**2 for k, v in d.items()}
{x: x**2 for x in range(5)}
{k: v for k, v in d.items() if v > 10}

# Set
{x**2 for x in range(10)}
{x for x in numbers if x > 0}

# Generator (avec parenthèses)
(x**2 for x in range(10))       # Paresseux, économe en mémoire

[OK] GÉNÉRATEURS

# Fonction génératrice (avec yield)
def count_up_to(n):
    i = 0
    while i < n:
        yield i
        i += 1

for num in count_up_to(5):
    print(num)

# Expression génératrice
gen = (x**2 for x in range(10))
next(gen)                       # 0
next(gen)                       # 1

# Consommer générateur
list(gen)                       # Convertir en liste
sum(gen)                        # Sommer tous les éléments

# yield from (Python 3.3+)
def flatten(lst):
    for item in lst:
        if isinstance(item, list):
            yield from flatten(item)
        else:
            yield item

[OK] GESTION D'ERREURS (TRY/EXCEPT)

# Structure de base
try:
    result = 10 / 0
except ZeroDivisionError:
    print("Division par zéro!")

# Capturer l'exception
try:
    result = 10 / 0
except ZeroDivisionError as e:
    print(f"Erreur: {e}")

# Plusieurs exceptions
try:
    # code
    pass
except (TypeError, ValueError) as e:
    print(f"Erreur de type ou valeur: {e}")

# Exception générique
try:
    # code
    pass
except Exception as e:
    print(f"Erreur: {e}")

# Clause else (si pas d'exception)
try:
    result = 10 / 2
except ZeroDivisionError:
    print("Erreur")
else:
    print("Succès!")

# Clause finally (toujours exécuté)
try:
    f = open('file.txt')
except FileNotFoundError:
    print("Fichier introuvable")
finally:
    print("Nettoyage")

# Lever une exception
raise ValueError("Message d'erreur")
raise ValueError("Message") from original_error

# Exception personnalisée
class MonErreur(Exception):
    pass

raise MonErreur("Message")

# Assert (pour debugging)
assert x > 0, "x doit être positif"

[OK] CLASSES & POO

# Classe simple
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def greet(self):
        return f"Hello, I'm {self.name}"

# Instanciation
p = Person("Alice", 30)
p.greet()

# Attributs de classe vs instance
class MyClass:
    class_attr = "shared"       # Attribut de classe
    
    def __init__(self):
        self.instance_attr = "unique"  # Attribut d'instance

# Méthodes spéciales (dunder methods)
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __str__(self):          # str(point)
        return f"Point({self.x}, {self.y})"
    
    def __repr__(self):         # repr(point)
        return f"Point({self.x}, {self.y})"
    
    def __add__(self, other):   # point1 + point2
        return Point(self.x + other.x, self.y + other.y)
    
    def __eq__(self, other):    # point1 == point2
        return self.x == other.x and self.y == other.y
    
    def __len__(self):          # len(point)
        return 2
    
    def __getitem__(self, key): # point[0]
        if key == 0:
            return self.x
        elif key == 1:
            return self.y
        raise IndexError

# Propriétés (property)
class Circle:
    def __init__(self, radius):
        self._radius = radius
    
    @property
    def radius(self):
        return self._radius
    
    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError("Radius must be positive")
        self._radius = value
    
    @property
    def area(self):
        return 3.14159 * self._radius ** 2

# Méthodes de classe et statiques
class MyClass:
    count = 0
    
    def __init__(self):
        MyClass.count += 1
    
    @classmethod
    def get_count(cls):
        return cls.count
    
    @staticmethod
    def utility_function():
        return "Utility"

# Héritage
class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

# super()
class Child(Parent):
    def __init__(self, name, age):
        super().__init__(name)
        self.age = age

# Héritage multiple
class A:
    pass

class B:
    pass

class C(A, B):
    pass

# Méthodes et attributs privés (convention)
class MyClass:
    def __init__(self):
        self._protected = "protected"  # Convention: protégé
        self.__private = "private"     # Name mangling

# Dataclasses (Python 3.7+)
from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int
    
    def greet(self):
        return f"Hello, I'm {self.name}"

[OK] FICHIERS

# Lire fichier
with open('file.txt', 'r') as f:
    content = f.read()          # Tout le contenu

with open('file.txt', 'r') as f:
    lines = f.readlines()       # Liste de lignes

with open('file.txt', 'r') as f:
    for line in f:              # Ligne par ligne (économe)
        print(line)

# Écrire fichier
with open('file.txt', 'w') as f:
    f.write("Hello\n")

with open('file.txt', 'w') as f:
    f.writelines(['line1\n', 'line2\n'])

# Append (ajouter)
with open('file.txt', 'a') as f:
    f.write("More content\n")

# Modes d'ouverture
'r'     # Lecture (défaut)
'w'     # Écriture (écrase)
'a'     # Append (ajoute)
'x'     # Création exclusive (erreur si existe)
'b'     # Binaire
't'     # Texte (défaut)
'+'     # Lecture et écriture

# Exemples
with open('file.txt', 'rb') as f:   # Lecture binaire
    data = f.read()

with open('file.txt', 'r+') as f:   # Lecture et écriture
    content = f.read()
    f.write("more")

# Encodage
with open('file.txt', 'r', encoding='utf-8') as f:
    content = f.read()

# Pathlib (moderne, recommandé)
from pathlib import Path

path = Path('file.txt')
content = path.read_text()          # Lire texte
path.write_text("Hello")            # Écrire texte
data = path.read_bytes()            # Lire binaire
path.write_bytes(b'data')           # Écrire binaire

# Vérifications
path.exists()                       # Existe?
path.is_file()                      # Est un fichier?
path.is_dir()                       # Est un dossier?

[OK] IMPORTS

# Import module complet
import math
math.sqrt(16)

# Import avec alias
import numpy as np
np.array([1, 2, 3])

# Import fonctions spécifiques
from math import sqrt, pi
sqrt(16)

# Import tout (déconseillé)
from math import *

# Import relatif (dans un package)
from . import module              # Même dossier
from .. import module             # Dossier parent
from .subpackage import module    # Sous-package

# Import conditionnel
try:
    import optional_module
except ImportError:
    optional_module = None

# Vérifier si module existe
try:
    import module_name
except ImportError:
    print("Module non installé")

[OK] F-STRINGS & FORMATAGE (Python 3.6+)

name = "Alice"
age = 30
pi = 3.14159

# Basique
f"Hello, {name}!"
f"Je m'appelle {name} et j'ai {age} ans"

# Expressions
f"Calcul: {10 + 5}"
f"Double: {age * 2}"
f"Comparaison: {age > 18}"

# Appels de fonctions
f"Longueur: {len(name)}"
f"Majuscules: {name.upper()}"

# Formatage de nombres
f"Float: {pi:.2f}"                # 2 décimales: 3.14
f"Pourcentage: {0.85:.1%}"        # 85.0%
f"Scientific: {1000:.2e}"         # 1.00e+03
f"Binaire: {42:b}"                # 101010
f"Hexadécimal: {42:x}"            # 2a
f"Octal: {42:o}"                  # 52

# Padding et alignement
f"{name:<10}"                     # Gauche (Alice     )
f"{name:>10}"                     # Droite (     Alice)
f"{name:^10}"                     # Centré (  Alice   )
f"{age:05d}"                      # Zéros (00030)

# Séparateur de milliers
f"{1000000:,}"                    # 1,000,000
f"{1000000:_}"                    # 1_000_000

# Signe
f"{42:+d}"                        # +42
f"{-42: d}"                       # -42

# Debug (Python 3.8+)
x = 42
f"{x=}"                           # x=42
f"{x=:.2f}"                       # x=42.00

# Dates
from datetime import datetime
now = datetime.now()
f"{now:%Y-%m-%d}"                 # 2024-10-26
f"{now:%H:%M:%S}"                 # 14:30:45

# Échapper accolades
f"{{not a variable}}"             # {not a variable}

[OK] OPÉRATEURS SPÉCIAUX

# Opérateurs d'identité
x is y              # Même objet en mémoire
x is not y          # Objet différent

# Opérateurs d'appartenance
x in lst            # x est dans lst
x not in lst        # x n'est pas dans lst

# Opérateur morse (walrus) := (Python 3.8+)
if (n := len(lst)) > 10:
    print(f"Liste de {n} éléments")

# Opérateur d'unpacking *
first, *middle, last = [1, 2, 3, 4, 5]
# first=1, middle=[2,3,4], last=5

# Unpacking dans appel de fonction
def func(a, b, c):
    return a + b + c

args = [1, 2, 3]
func(*args)         # Équivalent à func(1, 2, 3)

kwargs = {'a': 1, 'b': 2, 'c': 3}
func(**kwargs)      # Équivalent à func(a=1, b=2, c=3)

# Opérateur @ (multiplication matricielle, Python 3.5+)
# Utilisé avec numpy
# import numpy as np
# A @ B   # Multiplication matricielle

[OK] BUILT-IN FUNCTIONS (FONCTIONS NATIVES)

# Fonctions mathématiques
abs(-5)                 # Valeur absolue: 5
round(3.7)              # Arrondi: 4
round(3.14159, 2)       # Arrondi à 2 décimales: 3.14
pow(2, 3)               # Puissance: 8
pow(2, 3, 5)            # (2^3) % 5 = 3
divmod(17, 5)           # (quotient, reste): (3, 2)
min(1, 2, 3)            # Minimum: 1
max(1, 2, 3)            # Maximum: 3
sum([1, 2, 3])          # Somme: 6

# Conversion de types
int('42')               # String vers int
float('3.14')           # String vers float
str(42)                 # Vers string
bool(0)                 # Vers boolean
list('abc')             # ['a', 'b', 'c']
tuple([1, 2, 3])        # (1, 2, 3)
set([1, 1, 2])          # {1, 2}
dict([('a', 1)])        # {'a': 1}

# Fonctions de séquence
len([1, 2, 3])          # Longueur: 3
sorted([3, 1, 2])       # Trier: [1, 2, 3]
reversed([1, 2, 3])     # Inverser (itérateur)
enumerate(['a', 'b'])   # [(0, 'a'), (1, 'b')]
zip([1, 2], ['a', 'b']) # [(1, 'a'), (2, 'b')]
range(5)                # 0, 1, 2, 3, 4
range(2, 5)             # 2, 3, 4
range(0, 10, 2)         # 0, 2, 4, 6, 8

# Fonctions de test
any([False, True])      # Au moins un True: True
all([True, True])       # Tous True: True
isinstance(x, int)      # x est un int?
issubclass(Dog, Animal) # Dog hérite d'Animal?
callable(func)          # func est appelable?

# Fonctions d'itération
map(func, lst)          # Applique func à chaque élément
filter(func, lst)       # Filtre éléments où func(x) est True
next(iterator)          # Prochain élément
iter([1, 2, 3])         # Crée un itérateur

# Autres
type(x)                 # Type de x
id(x)                   # Identifiant mémoire
hash(x)                 # Hash de x
dir(obj)                # Liste attributs et méthodes
vars(obj)               # Dict des attributs
help(obj)               # Documentation
input("Prompt: ")       # Lire input utilisateur
print(*objects)         # Afficher
open('file.txt')        # Ouvrir fichier

# Fonctions avancées
eval('2 + 2')           # Évaluer expression: 4 ([ATTENTION] dangereux)
exec('x = 5')           # Exécuter code ([ATTENTION] dangereux)
compile(source, '', 'exec')  # Compiler code
globals()               # Dict variables globales
locals()                # Dict variables locales
getattr(obj, 'attr')    # obj.attr
setattr(obj, 'attr', val)  # obj.attr = val
hasattr(obj, 'attr')    # obj a l'attribut?
delattr(obj, 'attr')    # del obj.attr

[OK] CONTEXT MANAGERS (WITH)

# Fichiers (automatique fermeture)
with open('file.txt') as f:
    content = f.read()

# Multiples context managers
with open('in.txt') as fin, open('out.txt', 'w') as fout:
    fout.write(fin.read())

# Créer son context manager (classe)
class MyContext:
    def __enter__(self):
        print("Entering")
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Exiting")
        return False  # Propage l'exception

with MyContext() as ctx:
    print("Inside")

# Avec décorateur (plus simple)
from contextlib import contextmanager

@contextmanager
def my_context():
    print("Entering")
    yield
    print("Exiting")

with my_context():
    print("Inside")

# Supprimer fichier temporaire
from contextlib import contextmanager
import os

@contextmanager
def temp_file(filename):
    open(filename, 'w').close()
    try:
        yield filename
    finally:
        os.remove(filename)

[OK] DÉCORATEURS

# Décorateur simple
def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Avant")
        result = func(*args, **kwargs)
        print("Après")
        return result
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

# Équivalent à:
# say_hello = my_decorator(say_hello)

# Décorateur avec arguments
def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def greet():
    print("Hello!")

# Préserver métadonnées
from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

# Décorateurs built-in
@property           # Transforme méthode en propriété
@staticmethod       # Méthode statique
@classmethod        # Méthode de classe
@abstractmethod     # Méthode abstraite

# Décorateurs multiples
@decorator1
@decorator2
def func():
    pass

[OK] ITÉRATEURS & ITÉRABLES

# Créer un itérateur
class Counter:
    def __init__(self, max):
        self.max = max
        self.current = 0
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.current < self.max:
            self.current += 1
            return self.current
        raise StopIteration

counter = Counter(5)
for num in counter:
    print(num)  # 1, 2, 3, 4, 5

# Itérateur vs Itérable
lst = [1, 2, 3]         # Itérable
it = iter(lst)          # Itérateur
next(it)                # 1
next(it)                # 2

[OK] TYPE HINTS (ANNOTATIONS DE TYPES)

# Types basiques
def add(a: int, b: int) -> int:
    return a + b

# Types composés
from typing import List, Dict, Tuple, Set, Optional, Union, Any

def process(items: List[int]) -> Dict[str, int]:
    return {'count': len(items)}

# Optional (peut être None)
def find(name: str) -> Optional[str]:
    return name if name else None

# Union (plusieurs types possibles)
def process(value: Union[int, str]) -> str:
    return str(value)

# Any (n'importe quel type)
def process(value: Any) -> Any:
    return value

# Callable
from typing import Callable

def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
    return func(a, b)

# Type alias
Vector = List[float]

def scale(vector: Vector, scalar: float) -> Vector:
    return [x * scalar for x in vector]

# Generics
from typing import TypeVar, Generic

T = TypeVar('T')

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

# Protocol (Python 3.8+)
from typing import Protocol

class Drawable(Protocol):
    def draw(self) -> None: ...

# Literal (Python 3.8+)
from typing import Literal

def move(direction: Literal['up', 'down', 'left', 'right']) -> None:
    pass

[OK] MODULES STANDARD IMPORTANTS

# os - Système d'exploitation
import os
os.getcwd()                 # Dossier courant
os.listdir('.')             # Lister fichiers
os.mkdir('dir')             # Créer dossier
os.remove('file.txt')       # Supprimer fichier
os.path.exists('path')      # Chemin existe?
os.path.join('dir', 'file') # Joindre chemins

# sys - Système Python
import sys
sys.argv                    # Arguments ligne de commande
sys.exit(0)                 # Quitter programme
sys.path                    # Chemins de recherche modules
sys.version                 # Version Python

# datetime - Dates et heures
from datetime import datetime, date, time, timedelta

now = datetime.now()
today = date.today()
delta = timedelta(days=7)
tomorrow = today + delta

# time - Temps
import time
time.time()                 # Timestamp Unix
time.sleep(1)               # Pause 1 seconde

# random - Aléatoire
import random
random.random()             # Float entre 0 et 1
random.randint(1, 10)       # Entier entre 1 et 10
random.choice([1, 2, 3])    # Élément aléatoire
random.shuffle(lst)         # Mélanger liste
random.sample(lst, 3)       # 3 éléments aléatoires

# json - JSON
import json
json.dumps({'key': 'value'})    # Python vers JSON
json.loads('{"key": "value"}')  # JSON vers Python

# math - Mathématiques
import math
math.pi                     # π
math.e                      # e
math.sqrt(16)               # Racine carrée
math.ceil(3.2)              # Arrondi supérieur
math.floor(3.8)             # Arrondi inférieur
math.sin(0)                 # Sinus
math.log(10)                # Logarithme naturel

# collections - Collections spécialisées
from collections import (
    Counter,        # Compteur
    defaultdict,    # Dict avec valeur par défaut
    deque,          # Double-ended queue
    namedtuple,     # Tuple nommé
    OrderedDict     # Dict ordonné (< Python 3.7)
)

# itertools - Itérations avancées
from itertools import (
    count,          # Compte à l'infini
    cycle,          # Cycle infiniment
    repeat,         # Répète infiniment
    chain,          # Chaîne itérables
    combinations,   # Combinaisons
    permutations,   # Permutations
    product,        # Produit cartésien
)

# functools - Outils fonctionnels
from functools import (
    reduce,         # Réduction
    partial,        # Application partielle
    wraps,          # Préserver métadonnées
    lru_cache,      # Cache LRU
    cache,          # Cache simple (Python 3.9+)
)

# pathlib - Chemins de fichiers (moderne)
from pathlib import Path
p = Path('.')
p.exists()
p.is_dir()
p.iterdir()

# re - Expressions régulières
import re
re.search(r'\d+', 'abc123')
re.findall(r'\d+', 'a1b2c3')
re.sub(r'\d', 'X', 'a1b2')

# subprocess - Exécuter commandes
import subprocess
subprocess.run(['ls', '-l'])
result = subprocess.run(['echo', 'hello'], capture_output=True)

# argparse - Arguments ligne de commande
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--name')
args = parser.parse_args()

[OK] ASTUCES & IDIOMES PYTHON

# Swap de variables
a, b = b, a

# Chaînage de comparaisons
if 0 < x < 10:
    pass

# Opérateur ternaire
x = "pair" if n % 2 == 0 else "impair"

# Valeur par défaut pour dict
d.get('key', 'default')
d.setdefault('key', 'default')

# Fusionner dicts (Python 3.9+)
d3 = d1 | d2

# Unpacking dans fonction
def func(a, b, c):
    pass
args = [1, 2, 3]
func(*args)

# List comprehension au lieu de map/filter
# [X] map(lambda x: x*2, lst)
# [OK] [x*2 for x in lst]

# Enumerate au lieu d'index manuel
# [X] for i in range(len(lst))
# [OK] for i, val in enumerate(lst)

# Zip pour itérer sur plusieurs listes
for a, b in zip(list1, list2):
    pass

# Any/all au lieu de boucles
# [X] for x in lst: if condition: return True
# [OK] any(condition for x in lst)

# Context managers pour ressources
with open('file.txt') as f:
    pass

# Chaînes avec join au lieu de +
# [X] s = ''; for x in lst: s += x
# [OK] s = ''.join(lst)

# Dict.get au lieu de try/except
# [X] try: x = d['key'] except KeyError: x = None
# [OK] x = d.get('key')

# Pathlib au lieu de os.path
# [X] os.path.join(dir, file)
# [OK] Path(dir) / file

# F-strings au lieu de format/concatenation
# [X] "Hello " + name
# [OK] f"Hello {name}"

[OK] BONNES PRATIQUES

# PEP 8 - Style Guide
# - Indentation: 4 espaces
# - Lignes max: 79 caractères
# - Noms: snake_case pour fonctions/variables
# - Noms: PascalCase pour classes
# - Noms: UPPER_CASE pour constantes
# - 2 lignes vides entre définitions de niveau module
# - 1 ligne vide entre méthodes

# Conventions de nommage
ma_variable = 1         # snake_case
MaClasse = object       # PascalCase
MA_CONSTANTE = 100      # UPPER_CASE
_protege = 1            # Convention: protégé
__prive = 2             # Name mangling

# Docstrings
def ma_fonction(x):
    """
    Description courte.
    
    Description longue si nécessaire.
    
    Args:
        x (int): Description du paramètre
    
    Returns:
        int: Description du retour
    
    Raises:
        ValueError: Quand...
    """
    return x * 2

# Type hints
def add(a: int, b: int) -> int:
    return a + b

# Guard clauses (retour anticipé)
# [X]
def process(x):
    if x is not None:
        if x > 0:
            return x * 2
    return None

# [OK]
def process(x):
    if x is None:
        return None
    if x <= 0:
        return None
    return x * 2

# DRY - Don't Repeat Yourself
# Extraire code dupliqué en fonctions

# EAFP vs LBYL
# EAFP: Easier to Ask for Forgiveness than Permission
try:
    x = d['key']
except KeyError:
    x = None

# LBYL: Look Before You Leap
if 'key' in d:
    x = d['key']
else:
    x = None

# Python préfère EAFP

[OK] DEBUGGING & TESTING

# Print debugging
print(f"{variable=}")   # Python 3.8+

# Assertions
assert condition, "Message d'erreur"

# Debugger intégré (pdb)
import pdb
pdb.set_trace()         # Point d'arrêt

# Breakpoint (Python 3.7+)
breakpoint()            # Équivalent à pdb.set_trace()

# Logging (mieux que print)
import logging
logging.basicConfig(level=logging.DEBUG)
logging.debug("Message de debug")
logging.info("Information")
logging.warning("Attention")
logging.error("Erreur")
logging.critical("Critique")

# Tests unitaires
import unittest

class TestMyFunction(unittest.TestCase):
    def test_add(self):
        self.assertEqual(add(2, 3), 5)
    
    def test_negative(self):
        self.assertEqual(add(-1, 1), 0)

if __name__ == '__main__':
    unittest.main()

# Pytest (plus moderne)
# pip install pytest
def test_add():
    assert add(2, 3) == 5

[OK] PERFORMANCE

# Mesurer temps d'exécution
import time
start = time.time()
# code
print(f"Temps: {time.time() - start:.2f}s")

# Avec timeit (plus précis)
import timeit
timeit.timeit('sum(range(100))', number=10000)

# Profiling
import cProfile
cProfile.run('ma_fonction()')

# List comprehension vs boucle (plus rapide)
# [OK] [x*2 for x in range(1000)]
# [X] lst = []
#    for x in range(1000):
#        lst.append(x*2)

# Générateurs pour grandes données (économe en mémoire)
# [OK] (x*2 for x in range(1000000))
# [X] [x*2 for x in range(1000000)]

# Set pour tests d'appartenance (O(1) vs O(n))
# [OK] x in my_set
# [X] x in my_list

# Dict.get vs try/except pour clés fréquentes
# [OK] d.get('key', default)
# [X] try: d['key'] except KeyError: default

[OK] ERREURS COURANTES

# [X] Modifier liste pendant itération
for item in lst:
    if condition:
        lst.remove(item)  # [X] Mauvais

# [OK] Créer nouvelle liste ou itérer sur copie
lst = [item for item in lst if not condition]
for item in lst[:]:  # Copie
    if condition:
        lst.remove(item)

# [X] Utiliser mutable comme valeur par défaut
def func(lst=[]):  # [X] Mauvais
    lst.append(1)
    return lst

# [OK]
def func(lst=None):  # [OK] Bon
    if lst is None:
        lst = []
    lst.append(1)
    return lst

# [X] Comparer avec == au lieu de is pour None
if x == None:  # [X] Mauvais
    pass

# [OK]
if x is None:  # [OK] Bon
    pass

# [X] Utiliser eval (dangereux)
x = eval(user_input)  # [X] Dangereux!

# [OK] Utiliser ast.literal_eval ou json.loads
import ast
x = ast.literal_eval(user_input)

# [X] Oublier return dans fonction
def add(a, b):
    a + b  # [X] Retourne None

# [OK]
def add(a, b):
    return a + b

[OK] CHEATSHEET RAPIDE

"""
TYPES DE DONNÉES:
    int, float, str, bool, None
    list, tuple, dict, set
    
OPÉRATEURS:
    + - * / // % **         Arithmétiques
    == != < > <= >=         Comparaison
    and or not              Logiques
    in, not in              Appartenance
    is, is not              Identité

STRUCTURES DE CONTRÔLE:
    if/elif/else            Conditions
    for ... in              Boucle
    while                   Boucle conditionnelle
    break, continue         Contrôle de flux
    
FONCTIONS:
    def func(args): ...     Définition
    lambda x: x*2           Fonction anonyme
    return value            Retour
    
CLASSES:
    class Name:             Définition
    def __init__(self):     Constructeur
    self.attr               Attribut d'instance
    
GESTION D'ERREURS:
    try/except/finally      Capturer erreurs
    raise Exception         Lever erreur
    
FICHIERS:
    with open() as f:       Context manager
    f.read()                Lire
    f.write(text)           Écrire
    
COMPRÉHENSIONS:
    [x for x in lst]        Liste
    {x for x in lst}        Set
    {k:v for k,v in d}      Dict
    (x for x in lst)        Générateur
"""

[OK] RESSOURCES

# Documentation officielle:
# https://docs.python.org/3/

# PEP 8 - Style Guide:
# https://pep8.org/

# Real Python (tutoriels):
# https://realpython.com/

# Python Package Index (PyPI):
# https://pypi.org/

# Awesome Python (ressources):
# https://github.com/vinta/awesome-python