Catégorie : développement

Tout ce qui concerne le développement en général, que ce soit des choses générales, ou des choses bien précises. Cela va de la gestion de projet à la recherche du fonctionnement de pointeurs en C.

JavaScript hack update

Mise à jour en pur JavaScript hacks

Voici une petite mise à jour en vanilla JS qui fait la même chose que ce que j’ai mis ici.

function emptyBody() {
    document.body.innerHTML = '';
}
function getRandomInt(max) {
    return Math.floor(Math.random() * Math.floor(max));
}
function addNewAvatar() {
    let curr = getRandomInt(5000);
    return function() {
        const img = document.createElement('img');
        img.src = `https://avatars.githubusercontent.com/u/${curr}`;
        img.style.maxWidth = '50px';
        img.style.display = 'inline-block';
        img.style.float = 'left';
        img.style.margin = '0';
        img.style.padding = '0';
        document.body.appendChild(img);
        curr += (1 + getRandomInt(3));
        setTimeout(addNewAvatar(), 100);
    };
}
emptyBody();
setTimeout(addNewAvatar(), 100);

Python : f-string vs str()

Quelle est la différence de performance entre f"{x}" et str(x) ?

Voici mes tests qui m’ont surpris, car je m’attendais à l’inverse :

from typing import Dict

def benchmark() -> None:
  """Main
  function for benchmark.
  """
  t1 = timeit.timeit("f_str()",
                     globals=globals(),
                     number=50000000)
  t2 = timeit.timeit("u_str()",
                     globals=globals(),
                     number=50000000)
  t3 = timeit.timeit("n_str()",
                     globals=globals(),
                     number=50000000)
  d: Dict[str, float] = {
    "f-string": t1,
    "str": t2,
    "no str": t3
  }
  s: Dict[str, float] = {k: v
                         for k, v
                         in sorted(d.items(),
                                   key=lambda i:
                                   i[1])}
  f: float = min(s.values())
  print("Method\tTime\tPerc.")
  print("------\t----\t-----")
  for k, v in s.items():
    p: float = (v / f) * 100
    print(f"{k}\t{v:.2f}\t{p:.2f}%")

if __name__ == "__main__":
  import timeit
  class T:
    def __init__(
            self, l: str) -> None:
      self.l: str = l
  o: T = T("test")
  def f_str() -> str:
    return f"{o.l}"
  def u_str() -> str:
    return str(o.l)
  def n_str() -> str:
    return o.l
  benchmark()

Explications

  • f"{self.label}" utilise le mécanisme d’interpolation de chaînes de caractères de Python qui peut être légèrement plus rapide parce qu’il est optimisé pour concaténer des littéraux de chaîne et des variables ;
  • str(self.label) appelle explicitement le constructeur de la classe str, ce est un peu plus lent en raison de l’appel de fonction.

Panda vs Numpy

Ce qu’il faut retenir

Numpy et Pandas n’ont pas exactement les mêmes objectifs.

Dans la plupart des cas, NumPy peut être légèrement plus rapide que pandas, car NumPy est plus bas niveau et a moins de surcharge. Cependant, pandas offre des structures de données et des fonctionnalités plus avancées, ce qui peut faciliter le travail avec des ensembles de données complexes. Les performances relatives de NumPy et pandas dépendent également des opérations spécifiques effectuées sur les données, de sorte que les différences de performances peuvent varier en fonction des tâches spécifiques. Certaines fonctions n’existent qu’avec pandas, et qui n’ont pas d’équivalents NumPy sont : read_csv, read_excel, groupby, pivot_table, merge, concat, melt, crosstab, cut, qcut, get_dummies et applymap.

Résultats

Résultat : image générée : notez bien que j’ai appelé des fonctions « bas niveau » pour qu’on voie ce que NumPy a dans le ventre et des fonctions qui n’existent que dans pandas, que ré-implémentées en Python pur + NumPy.

Résultats pandas vs NumPy

Code source

Voici le code source que j’ai fait, qui appelle quelques fonctions connues de NumPy et de pandas.

import numpy as np
import pandas as pd
import time
import matplotlib.pyplot as plt

# Générer un grand ensemble de données
data_np = np.random.rand(30_000_000)
data_pd = pd.DataFrame({"values": data_np})

operations = (
    "sum",
    "mean",
    "filter",
    "cum_sum",
    "sort",
    "complex",
    "pivot",
    "group_by",
    "rolling",
)
time_np = []
time_pd = []


# Définir une fonction pour chronométrer et stocker les temps d'exécution
def measure_time(start_time, end_time, time_list):
    time_list.append(end_time - start_time)


# Effectuer les différentes opérations et mesurer les temps d'exécution
for operation in operations:
    # print(f"operation: {operation}")
    print(f"{operation}")
    if operation == "sum":
        start_time_np = time.time()
        result_np = np.sum(data_np)
        end_time_np = time.time()
        measure_time(start_time_np, end_time_np, time_np)

        start_time_pd = time.time()
        result_pd = data_pd["values"].sum()
        end_time_pd = time.time()
        measure_time(start_time_pd, end_time_pd, time_pd)

    elif operation == "mean":
        start_time_np = time.time()
        mean_np = np.mean(data_np)
        end_time_np = time.time()
        measure_time(start_time_np, end_time_np, time_np)

        start_time_pd = time.time()
        mean_pd = data_pd["values"].mean()
        end_time_pd = time.time()
        measure_time(start_time_pd, end_time_pd, time_pd)

    elif operation == "filter":
        start_time_np = time.time()
        filtered_np = data_np[data_np > 0.5]
        end_time_np = time.time()
        measure_time(start_time_np, end_time_np, time_np)

        start_time_pd = time.time()
        filtered_pd = data_pd[data_pd["values"] > 0.5]
        end_time_pd = time.time()
        measure_time(start_time_pd, end_time_pd, time_pd)

    elif operation == "cum_sum":
        start_time_np = time.time()
        cum_sum_np = np.cumsum(data_np)
        end_time_np = time.time()
        measure_time(start_time_np, end_time_np, time_np)

        start_time_pd = time.time()
        cum_sum_pd = data_pd["values"].cumsum()
        end_time_pd = time.time()
        measure_time(start_time_pd, end_time_pd, time_pd)

    elif operation == "sort":
        start_time_np = time.time()
        sorted_np = np.sort(data_np)
        end_time_np = time.time()
        measure_time(start_time_np, end_time_np, time_np)

        start_time_pd = time.time()
        sorted_pd = data_pd["values"].sort_values()
        end_time_pd = time.time()
        measure_time(start_time_pd, end_time_pd, time_pd)
    elif operation == "complex":
        # Générer des données structurées
        data_1 = np.random.randint(0, 1_000_000, (2_000, 2))
        data_2 = np.random.randint(0, 1_000_000, (2_000, 2))

        # Créer des DataFrames pandas
        df_1 = pd.DataFrame(data_1, columns=["id", "value_1"])
        df_2 = pd.DataFrame(data_2, columns=["id", "value_2"])

        # Créer des arrays structurés NumPy
        d_type = np.dtype([("id", int), ("value", int)])
        numpy_data_1 = np.array(
            list(map(tuple, data_1)), dtype=d_type
        )
        numpy_data_2 = np.array(
            list(map(tuple, data_2)), dtype=d_type
        )

        # Jointure avec NumPy
        def numpy_join(data1, data2):
            result = []
            for row1 in data1:
                for row2 in data2:
                    if row1["id"] == row2["id"]:
                        result.append(
                            (row1["id"], row1["value"], row2["value"])
                        )
            return np.array(
                result,
                dtype=[
                    ("id", int),
                    ("value_1", int),
                    ("value_2", int),
                ],
            )

        start_time_np = time.time()
        numpy_result = numpy_join(numpy_data_1, numpy_data_2)
        end_time_np = time.time()
        measure_time(
            start_time_np, end_time_np, time_np
        )  # Ajoutez cette ligne

        # Jointure avec pandas
        start_time_pd = time.time()
        pandas_result = df_1.merge(df_2, on="id")
        end_time_pd = time.time()

        measure_time(start_time_pd, end_time_pd, time_pd)
    elif operation == "pivot":
        # Générer des données structurées
        unique_ids = np.arange(0, 60_000)
        unique_groups = np.arange(0, 3)
        id_col = np.repeat(unique_ids, len(unique_groups))
        group_col = np.tile(unique_groups, len(unique_ids))
        value_col = np.random.randint(0, 100, len(id_col))
        data = np.column_stack((id_col, group_col, value_col))

        # Créer des DataFrames pandas
        df = pd.DataFrame(data, columns=["id", "group", "value"])

        # Créer des arrays structurés NumPy
        d_type = np.dtype(
            [("id", int), ("group", int), ("value", int)]
        )
        numpy_data = np.array(list(map(tuple, data)), dtype=d_type)

        # Pivot avec NumPy
        def numpy_pivot(_data, _id_col, _group_col, _value_col):
            _unique_ids = np.unique(_data[_id_col])
            _unique_groups = np.unique(_data[_group_col])

            pivot_table = np.zeros(
                (len(_unique_ids), len(_unique_groups))
            )


            for row in _data:
                id_index = np.where(_unique_ids == row[_id_col])[0][0]
                group_index = np.where(
                    _unique_groups == row[_group_col]
                )[0][0]
                pivot_table[id_index, group_index] = row[_value_col]

            return pivot_table

        start_time_np = time.time()
        numpy_pivot_table = numpy_pivot(
            numpy_data, "id", "group", "value"
        )
        end_time_np = time.time()
        measure_time(start_time_np, end_time_np, time_np)

        # Pivot avec pandas
        start_time_pd = time.time()
        pandas_pivot_table = df.pivot(
            index="id", columns="group", values="value"
        )
        end_time_pd = time.time()
        measure_time(start_time_pd, end_time_pd, time_pd)

    elif operation == "group_by":
        # Générer des données structurées
        data = np.random.randint(0, 10_000_000, (100_000, 2))

        # Créer des DataFrames pandas
        df = pd.DataFrame(data, columns=["id", "value"])

        # Créer des arrays structurés NumPy
        d_type = np.dtype([("id", int), ("value", int)])
        numpy_data = np.array(list(map(tuple, data)), dtype=d_type)

        # Group_by avec NumPy
        def numpy_group_by_mean(_data):
            _unique_ids, counts = np.unique(
                _data["id"], return_counts=True
            )
            sums = np.zeros_like(_unique_ids, dtype=float)
            for row in _data:
                sums[np.where(_unique_ids == row["id"])[0][0]] += row[
                    "value"
                ]
            return _unique_ids, sums / counts

        start_time_np = time.time()
        numpy_result = numpy_group_by_mean(numpy_data)
        end_time_np = time.time()
        measure_time(start_time_np, end_time_np, time_np)

        # Group_by avec pandas
        start_time_pd = time.time()
        pandas_result = df.groupby("id")["value"].mean()
        end_time_pd = time.time()
        measure_time(start_time_pd, end_time_pd, time_pd)

    elif operation == "rolling":
        # Générer un grand ensemble de données
        data_np = np.random.rand(100_000_000)
        data_pd = pd.DataFrame({"values": data_np})

        window = 100

        def numpy_rolling_mean(arr, _window):
            _cum_sum = np.cumsum(np.insert(arr, 0, 0))
            return (
                _cum_sum[_window:] - _cum_sum[:-_window]
            ) / _window

        start_time_np = time.time()
        numpy_result = numpy_rolling_mean(data_np, window)
        end_time_np = time.time()
        measure_time(start_time_np, end_time_np, time_np)

        # Rolling avec pandas
        start_time_pd = time.time()
        pandas_result = (
            data_pd["values"].rolling(window=window).mean()
        )
        end_time_pd = time.time()
        measure_time(start_time_pd, end_time_pd, time_pd)

# Créer un graphique de comparaison
x = np.arange(len(operations))
width = 0.35

fig, ax = plt.subplots()

rects1 = ax.bar(
    x - width / 2,
    time_np,
    width,
    label="NumPy",
    color="#c9daf8",
    edgecolor="black",
    hatch="//",
    linewidth=1,
)
rects2 = ax.bar(
    x + width / 2,
    time_pd,
    width,
    label="pandas",
    color="#c2e8b8",
    edgecolor="black",
    hatch=".",
    linewidth=1,
    alpha=0.5,
)


# Modification de la taille des marqueurs dans rects2
for rect in rects2:
    rect.set_linewidth(2)

ax.set_yscale("log")
ax.set_ylabel("Temps d'exécution (s) - Échelle logarithmique")
ax.set_title(
    "Comparaison des temps d'exécution entre NumPy et pandas"
)
ax.set_xticks(x)
ax.set_xticklabels(operations)
ax.legend()


def autolabel(rects):
    for _rect in rects:
        height = _rect.get_height()
        ax.annotate(
            "{:.2f}".format(height),
            xy=(_rect.get_x() + _rect.get_width() / 2, height),
            xytext=(0, 3),  # 3 points vertical offset
            textcoords="offset points",
            ha="center",
            va="bottom",
        )


autolabel(rects1)
autolabel(rects2)

fig.tight_layout()
plt.savefig("pandas_vs_numpy.png")

IUT alternants : projet Django / Python à rendre le 12 février minuit au plus tard

A rendre le dimanche 12 février 2023 minuit au plus tard


Projet individuel


Comment le rendre

Faites un fichier README.txt et déposez-le ici
Dans le fichier README.txt, précisez :

  • le sujet choisi
  • l’adresse de votre site
  • un nom d’utilisateur
  • un mot de passe
  • (et plusieurs nom/mot de passe, s’il y a plusieurs niveaux de droits (administrateur/visiteur etc.))
  • si vous avez utilisé des librairies spécifiques que je vous ai autorisées, merci de le re-préciser

Sujet

Ce que vous voulez tant que c’est dans le cadre de ce que l’on a vu. Vous avez tout le Web comme inspiration !
N’oubliez pas de me donner le nom et le mot de passe pour se connecter !
Si vous gérez des profils différents (admin / user ou autre), donnez moi les noms et mots de passe de différents profils !


Fonctionnalités obligatoires

  • Nouveaux modèles
  • Nouvelles relations à mettre en oeuvre : ForeignKey, ManyToMany, OneToOne
  • Au moins un formulaire
  • Connexion + déconnexion (vu en cours)
  • Visualisation de tout dans l’interface d’administration

Sujets possibles

  1. Site de partage de photos
  2. Site de cocktails (cf ci-dessus)
  3. e-rated : site d’appréciations (selon des sujets, à définir)
  4. Ask-a-question : site où l’on pose des questions sur des sujets divers, et des gens répondent
  5. Write-a-book-together : site où l’on se connecte et où on peut écrire un livre à plusieurs
  6. Wedding-couple-site : site où l’on uploade + partage des photos de mariage + livre de commandes
  7. Playing-cards-collection : site où on scanne + échange des cartes (Magic the gathering)
  8. Polls-and-surveys : site de création de sondages (= QCM, exemple très beau ici : quipoquiz)
  9. Poems-generator : faire un cadavre exquis qui génère des poèmes + possibilité pour les utilisateurs de les noter / d’ajouter des mots
  10. The-future-of-post-it : faire un carnet de choses à faire pour les utilisateurs, qui envoie des mails de rappels de ces choses à des dates données
  11. Gift-ideas : un site où l’on va faire des idées de cadeaux / suggérer des idées de cadeaux + les noter (les meilleurs ressortent en premier)
  12. Le-bon-recoin : refaire le bon coin en plus simple
  13. Suggest-crawlers : site de suggestions : on clique sur un mot, il en suggère plein d’autres avec + définitions / liens de sites pour chacuns
  14. Tv-fans : site de présentations + notes d’émissions télé
  15. Faire le jeu SokoBan vu en cours, avec la possibilité de login, enregistrement. Pour les appels JSON supplémentaires, lorsque l’utilisateur choisit un tableau, s’en souvenir (= AJAX) et lorsqu’il se reconnecte, le remettre directement. Puis enregistrer son score lorsqu’il a terminé un niveau + montrer les meilleurs scores.

Pour les sujets qui suivent, ils sont possibles mais plutôt complexes et demandent plus d’investissement. Si vous êtes motivés, demandez-moi plus d’informations, je vous expliquerai les difficultés que vous allez rencontrer.

  1. Turn-by-turn : faire un jeu multijoueurs en tour par tour (jeu de cartes, de poker, ou de plateau etc)
  2. Chat-with-someone : site de chat/discussion
  3. A-maze-ing : site où l’on peut se ballader dans un labyrinthe et essayer d’en trouver la sortie

Sujet imposé si vous n’avez pas d’idée

Cocktails : on se connecte, on a une liste d’éléments (récupérés en JSON) disponibles, on coche ceux qui nous intéressent, on valide, c’est envoyé, et le retour en JSON affiche les cocktails qu’il est possible de faire avec ce que l’on a coché.


Ce que vous devez rendre

Idéalement

Si vous n’avez pas le choix

Les fichiers source de votre projet


Pour favoriser votre organisation

Utilisez ce que l’on a vu en cours (Google boilerplate)


Librairies autorisées


React autorisé

Note pour ceux qui connaissent / font / du React : la librairie est autorisée, mais il me faut le code d’origine, et non pas le code minifié / de production.


Interdiction d’utiliser une librairie JavaScript qui ne vienne pas des sites autorisés précédemment


Retard

Après la date et heure limite

Passé ce délai ce sera 1 pt par 2 heures de retard (mon robot qui analyse les mails prend en compte la date de réception du mail, tout est fait automatiquement).
Pour ceux qui essaient vraiment d’aller jusqu’à la dernière minute, toute heure entamée est comptée comme une heure complète.
Exemple : un point en moins si je le reçois un jour après à la minute près, soit date limite plus 00:01 minute.

N’oubliez pas de me donner le nom et le mot de passe pour se connecter !


Copier-coller

  • Copie sur une autre personne (« je se savais pas comment implémenter telle ou telle fonctionnalité dont j’avais besoin pour aller plus loin, je l’ai copiée sur un autre ») :
    • si la personne est clairement nommée : note pour la fonctionnalité divisée par 2 (uniquement la moitié du travail a été faite) ;
    • 0 aux deux personnes sinon ;
  • Si je m’aperçois que vous avez bêtement copié collé des sources Internet, je vous convoquerai pour vous demander de m’expliquer la fonctionnalité, et :
    • si vous ne savez pas m’expliquer le code alors 0 ;
    • si vous savez m’expliquer tout le code alors votre note totale sera divisée par vous + le nombre de contributeurs à ce projet, ce qui se rapprochera certainement de 0 aussi.

Voici un exemple de ce que vous pouvez faire, si vous choisissez le projet cocktails.


PDFs

Python Django

Expressions régulières

Regexp hints / aide

Récupéré ici, traduction à finir

Caractères

Caractères Explication Exemple Chaînes qui « matchent »
[abc]
[a-c]
Fait correspondre les caractères / la plage de caractères donné(e)s abc[abc] abca
abcb
abcc
[^abc]
[^ac]
Négation des caractères / la plage de caractères donnés abc[^abc] abcd
abce
abc1
...
. Tout caractère sauf saut de ligne bc. bca
bcd
bc1
b.
...
\d Tout caractère numérique (équivalent à [0-9]) c\d c1
c2
c3
...
\D Tout caractère non numérique (équivalent à [^0-9]) c\D cA
cB
c*
...
\w Tout caractère alphanumérique (équivalent à [A-Za-z0-9_]) a\w aa
a1
a_
...
\W Tout caractère non alphanumérique (équivalent à [^A-Za-z0-9_]) a\W a)
a$
a?
...
\s Généralement utilisé pour les espaces blancs, mais peut être utilisé pour les nouvelles lignes, tabulations, etc. a\s [espace]
\S Tout sauf un espace blanc ou nouvelle ligne ou tabulation. a\S aa
\t Tabulation horizontale t\txy t[tab]xy
\r Correspond à un retour chariot AB\r\nCD AB[\return]CD
\n Correspond à un saut de ligne AB\r\nCD AB[\return]CD
| Correspond à « x » ou « y » aa|bb aa
bb
Chars Définition Ex match
Chars Définition Ex match
Chars Définition Ex match
Commentaires fermés sur Expressions régulières Publié dans

Godot

Objets

Avoir le noeud « racine » owner
Nombre de points (« vertices ») utilisés  
print(
    Performance.get_monitor(
        Performance.RENDER_VERTICES_IN_FRAME
    )
)
Attendre une frame  
yield(get_tree(), "idle_frame")

ou (peut prendre plus d’une frame) :

yield(get_tree(), "physics_frame")

Déplacer
un objet
dans un autre
func _on_barillet_body_shape_entered(
    body_rid, body,
    body_shape_index, local_shape_index
):
    # distant object:
    # body.shape_owner_get_owner(body_shape_index)
    # local object:
    # shape_owner_get_owner(local_shape_index)
    var me = shape_owner_get_owner(
        local_shape_index
    )
    body.get_parent().remove_child(body)
    get_parent().add_child(body)
queue_free()
vs
free()
queue_free() est un raccourci pour détruire un nœud et le supprimer de l’arborescence en toute sécurité à la fin de la frame : le nœud est mis dans une « file d’attente » qui est une liste de « choses à supprimer lorsque le jeu a fini de traiter à cette frame« .. Cela n’arrive pas immédiatement pour éviter certaines situations délicates qui feraient autrement planter le jeu. Pour garder les choses simples, il est recommandé d’utiliser queue_free() pour les nœuds de l’arborescence.

Supprimer les plus vieux fichiers d’un dossier tant qu’on dépasse une certaine taille


Exemples de lancement du script

Notez qu’il faut lancer en utilisant « source« 

  • Supprimer les plus vieux fichiers du dossier courant (./) tant qu’il prend plus de 96Mo :
    source ./clean_custom.sh --path ./ -l 9600000
  • Supprimer les plus vieux fichiers du dossier temporaire (/tmp/) tant qu’il prend plus de 2Go :
    source ./clean_custom.sh --path /tmp/ -l 2000000000

Code du script

#!/usr/bin/env bash                                                              
PATH_TO_CLEAN=                                                                   
NUMBER_FILES_TO_DELETE_EACH_LOOP=1                                               
SIZE_LIMIT=2000000000                                                            
                                                                                 
# ----------------------------------------------------------------------------   
# usage:                                                                         
usage()                                                                          
{                                                                                
    echo "Clean directory: while size of a dir > limit, oldest files first."
    echo "Usage: ${filename} [-p|--path path] [-s|--max-size size] | [-h]"
    echo "    -p|--path: path to clean"            
    echo "    -l|--limit: max size for the folder (must be > 0)"
    echo "    -h|--help this help"                 
}                                                                                
                                                                                 
# ----------------------------------------------------------------------------   
# handling arguments:                                                            
args=("$@")                                                            
filename=$(basename -- "$0" | sed 's/\(.*\)\..*/\1/')        
while [ "$1" != "" ]; do                                     
    case $1 in                                               
        -p | --path ) shift              
                      # stop if path doesn't exist:
                      if [ ! -d "$1" ]; then
                          echo "Path not found: '$1'"
                          usage
                          return 1
                      fi
                      PATH_TO_CLEAN=$1
                      ;;
        -l | --limit ) shift             
                       SIZE_LIMIT=$(echo $1 | bc)
                       if [ $SIZE_LIMIT -le 0 ]
                       then
                           usage
                           return 1
                       fi
                       ;;
        -h | --help ) usage              
                      return
                      ;;
        * ) usage                        
            return 1 
    esac                                                     
    shift                                                    
done                                                                             
[ -z "$PATH_TO_CLEAN" ] && echo "Path empty" && usage && return 1
echo "Cleanin dir: '$PATH_TO_CLEAN', size limit=$SIZE_LIMIT" 
# ----------------------------------------------------------------------------   
# handling arguments:                                                            
while [ 1 ]                                                                      
do                                                                               
    s=$(du -sb $PATH_TO_CLEAN | cut -f1 | bc)                
    if [ $s -gt $SIZE_LIMIT ]                                
    then                                                     
        find $PATH_TO_CLEAN -type f -printf '%T+ %p\n' | \
            sort -nr | \
            tail -$NUMBER_FILES_TO_DELETE_EACH_LOOP | \
            cut -d' ' -f 2- | \
            xargs -I {} rm -f {}
    else                                                     
        break                            
    fi                                                                                                                                                                                                                                                      
done                                                                             
return 0

Django scripting : « AppRegistryNotReady: Apps aren’t loaded yet » solution

Si vous voulez faire un script simple qui veut importer votre application construite sur le framework Django, vous ferez sûrement ce code :

import django
from app.models import MyModel

Vous aurez sûrement cette erreur : django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.

Pas de panique !
La solution est de lancer setup() de votre application avant les imports, comme suit :

import django

if __name__ == '__main__':
    django.setup()
    # import AFTER setup
    from app.models import MyModel
    # je peux maintenant utiliser MyModel!!

Python : compiler et faire tourner plusieurs versions sans collisions

Il faut aller chercher le code source qui vous intéresse.

Exemple, faire tourner un « vieux » Python 3.6, aller dans les versions ici et prendre celle qui nous intéresse.

Puis récupérer le code source et le compiler :

mkdir ~/source ; cd ~/source
wget https://www.python.org/ftp/python/3.6.13/Python-3.6.13.tar.xz
tar xvf Python-3.6.13.tar.xz
cd ~/source/Python-3.6.13
./configure && make
sudo make altinstall

Et voilà :

~/source/Python-3.6.13$ python3.6
Python 3.6.13 (default, May 21 2021, 17:12:12) 
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

La loi des abstractions qui fuient

Nb : j’ai toujours été très admiratif de Joël Spolsky (créateur de Trello qu’il a revendu $425M) et de ses articles excellents. Il l’a retiré de son wiki mais il est toujours disponible ici, sur Webarchive.

Je me permets de le copier coller, avec de très légères modifications, car il est excellent – cela n’est que mon opinion bien sûr.


Il y a une part de magie dans l’ingénierie de l’Internet sur laquelle vous vous appuyez tous les jours. Elle œuvre au niveau du protocole TCP, un des blocs constitutifs fondamentaux de l’Internet.

TCP est un moyen de transmettre des données qui est fiable. Par cela, je veux dire : si vous envoyez un message sur un réseau en utilisant TCP, il arrivera à destination et ne sera ni tronqué ni corrompu.

Nous utilisons TCP pour beaucoup de choses, comme accéder à des pages web ou envoyer du courrier électronique. La fiabilité de TCP est ce qui permet à tous les mails excitants d’escrocs est-africains d’arriver à destination en parfait état. Ô joie.

Par comparaison, il existe une autre méthode de transmission de données appelée IP, qui est peu fiable. Personne ne vous garantit que vos données arriveront à destination et qu’elles ne seront pas endommagées avant d’arriver. Si vous envoyez une série de messages avec IP, ne soyez pas surpris que seulement la moitié d’entre eux arrive, et que certains d’entre eux arrivent dans un ordre différent de l’ordre d’envoi, et que d’autres soient remplacés par des messages alternatifs, contenant peut-être des photos d’adorables bébés orangs-outangs, ou plus probablement beaucoup de fatras illisible ressemblant au champ objet d’un spam taiwanais.

Et voici la part de magie : TCP fonctionne au-dessus d’IP. En d’autres termes, TCP est obligé de se débrouiller pour envoyer des données de manière fiable en utilisant uniquement un outil peu fiable.

Pour illustrer en quoi ceci est magique, considérez le scénario suivant issu du monde réel, moralement équivalent quoique quelque peu déraisonnable.

Imaginez que nous ayons un moyen d’envoyer des acteurs de Broadway à Hollywood, qui consiste à les mettre dans des voitures et à les conduire à travers le pays. Certaines de ces voitures s’écrasent, tuant les pauvres acteurs. Parfois les acteurs se saoulent en chemin et se rasent la tête ou se font des tatouages sur le nez, devenant de ce fait trop laids pour travailler à Hollywood, et fréquemment, les acteurs arrivent dans ordre différent de l’ordre initial, car ils ont tous pris des routes différentes. Maintenant imaginez un nouveau service intitulé Hollywood Express, qui livre des acteurs à Hollywood en garantissant (a) qu’ils arrivent à destination (b) dans l’ordre et (c) en parfait état. La part de magie est que Hollywood Express ne dispose d’aucune autre méthode pour livrer les acteurs que la méthode peu fiable qui consiste à les mettre dans des voitures et les conduire à travers le pays. Hollywood Express fonctionne en vérifiant que chaque acteur arrive en parfait état et, dans le cas contraire, appelle la maison mère pour demander qu’un jumeau identique de l’acteur soit envoyé pour le remplacer. Si les acteurs arrivent dans le désordre, Hollywood Express les remet dans l’ordre. Si un grand OVNI faisant route vers la zone 51 s’écrase sur l’autoroute du Nevada, la rendant impraticable, tous les acteurs qui devaient passer par-là sont re-routés via l’Arizona et Hollywood Express n’avertit même pas les réalisateurs californiens de ce qui est arrivé. Pour eux, il semble simplement que les acteurs mettent un peu plus de temps que d’habitude pour arriver, et ils n’entendent même pas parler du crash de l’OVNI.

Voilà, approximativement, la magie de TCP. C’est ce que les informaticiens aiment appeler une abstraction : une simplification de quelque chose de beaucoup plus compliqué qui se trame dans l’ombre. A ce qu’il paraît, une part importante de la programmation des ordinateurs consiste à élaborer des abstractions. Qu’est-ce qu’une bibliothèque de chaînes de caractères ? C’est une manière de simuler que les ordinateurs peuvent manipuler des chaînes de caractères aussi facilement qu’ils manipulent des nombres. Qu’est-ce qu’un système de fichiers ? C’est une manière de simuler qu’un disque dur n’est pas vraiment un ensemble de plateaux magnétiques en rotation capables de stocker des bits à certains endroits, mais plutôt un système hiérarchique de dossiers-dans-des-dossiers, qui contiennent eux-mêmes des fichiers individuels, qui à leur tour contiennent une ou plusieurs chaînes d’octets.

Revenons à TCP. Tout à l’heure, dans un souci de simplicité, j’ai raconté une petite fable et certains d’entre vous ont maintenant de la fumée qui sort par les oreilles parce que cette fable est en train de les rendre fous. J’ai dit que TCP garantissait que votre message arrive à destination. En fait, il ne le garantit pas vraiment. Si votre serpent apprivoisé a mâchouillé le câble réseau relié à votre ordinateur et qu’aucun paquet IP ne peut passer, alors TCP ne peut rien faire contre ça et votre message n’arrivera jamais. Si vous avez rudoyé les administrateurs système de votre entreprise et qu’ils se sont vengés en vous reliant à un concentrateur surchargé, seulement certains de vos paquets IP vont passer et TCP va fonctionner, mais tout sera vraiment lent.

C’est ce que j’appelle une abstraction qui fuit. TCP essaie de fournir une abstraction complète d’un réseau sous-jacent peu fiable, mais parfois le réseau fuit à travers l’abstraction et vous sentez des choses contre lesquelles l’abstraction ne peut pas vraiment vous protéger. Ce n’est qu’un exemple de ce que j’ai intitulé la Loi des Abstractions qui Fuient :

Toute abstraction non triviale, à un certain degré, fuit.

Les abstractions échouent. Parfois un peu, parfois beaucoup. Il y a des fuites. Les choses tournent mal. Cela arrive tout le temps quand vous avez des abstractions. Voici quelques exemples :

  • Quelque chose d’aussi simple que de parcourir un tableau à deux dimensions de taille importante peut avoir des performances radicalement différentes si vous le faites horizontalement plutôt que verticalement, en fonction du « sens de la fibre » — une direction peut résulter en beaucoup plus de défaillances de page que l’autre direction, et les défaillances de page sont lentes. Même les programmeurs en assembleur sont censés pouvoir faire semblant de disposer d’un vaste espace d’adressage, mais la mémoire virtuelle leur rappelle que ce n’est qu’une abstraction, qui fuit quand il y a une défaillance de page et que certains accès mémoire prennent beaucoup plus de nano-secondes que d’autres.
  • Le langage SQL est censé faire abstraction des étapes des procédures nécessaires pour interroger une base de données, en les remplaçant par la possibilité de définir simplement ce que l’on veut et de laisser la base de données trouver les étapes des procédure pour l’obtenir. Mais dans certains cas, les requêtes SQL sont des milliers de fois plus lentes que d’autres qui sont logiquement équivalentes. Un célèbre exemple de cela est que certains serveurs SQL sont nettement plus rapides si vous spécifiez « where a=b and b=c and a=c » plutôt que si vous spécifiez seulement « where a=b and b=c », même si le résultat est identique. Vous n’êtes pas censé devoir vous occuper de la procédure, mais seulement de la spécification. Mais parfois l’abstraction fuit et mène à des performances horribles et vous devez sortir l’analyseur de plan de requête et étudier ce qui ne va pas et trouver comment rendre votre requête plus rapide.
  • Même si certaines bibliothèques réseau comme NFS ou SMB vous permettent de traiter des fichiers sur des machines distantes « comme s’ils » étaient locaux, parfois la connexion devient très lente ou tombe et le fichier cesse de se comporter comme s’il était local, et en tant que programmeur vous devez écrire du code pour prendre cela en compte. L’abstraction « un fichier distant c’est la même chose qu’un fichier local » fuit. Voici un exemple concret pour les administrateurs système UNIX. Si vous placez les répertoires home des utilisateurs sur des disques montés en NFS (une abstraction), et que vos utilisateurs créent des fichiers .forward pour transmettre leur courrier électronique ailleurs (une autre abstraction), et que le serveur NFS tombe pendant que des nouveaux courriers arrivent, les messages ne seront pas transmis car le fichier .forward sera introuvable. La fuite dans l’abstraction aura en fait laissé s’échapper quelques messages.
  • En C++, les classes de chaînes de caractères sont censées vous permettre de faire semblant que les chaînes de caractères sont un type de données à part entière. Elles tentent de faire abstraction du fait que les chaînes de caractères sont difficiles à manipuler et vous permettent d’agir comme si c’était aussi facile qu’avec des entiers. Presque toutes les classes C++ de chaînes de caractères surchargent l’opérateur  » +  » et vous pouvez écrire s + "toto" pour les concaténer. Mais vous savez quoi ? Peu importe à quel point elles essaient, il n’y a pas sur Terre de classe C++ de chaînes de caractères qui vous permette d’écrire "toto" + "titi", car les chaînes de caractères littérales en C++ sont toujours des char*, jamais des chaînes. L’abstraction a créé une fuite que le langage ne vous permet pas de reboucher. (De manière amusante, l’histoire de l’évolution de C++ au fil du temps peut être décrite comme l’histoire des tentatives pour reboucher les fuites dans l’abstraction des chaînes de caractères. Pourquoi ne pouvaient-ils pas simplement ajouter une classe de chaîne de caractères native au langage lui-même? Cela m’échappe pour le moment.)
  • Et vous ne pouvez pas conduire aussi vite quand il pleut, même si votre voiture a des essuie-glaces et des phares et un toit et un chauffage, qui tous vous permettent de ne pas vous inquiéter du fait qu’il pleut (ils font abstraction de la météo), mais zut, vous devez faire attention à l’aquaplaning et parfois la pluie est si forte que vous ne pouvez pas voir très loin devant vous et donc vous roulez plus lentement quand il pleut, parce qu’on ne peut jamais faire complètement abstraction de la météo, à cause de la loi des abstractions qui fuient.

Une des raisons pour lesquelles la loi des abstractions qui fuient est problématique est qu’elle implique que les abstractions ne nous simplifient pas vraiment la vie autant qu’elles le devraient. Quand je forme quelqu’un à être programmeur C++, ce serait bien si je n’avais jamais à lui parler des char* et de l’arithmétique des pointeurs. Ce serait bien si je pouvais aller tout droit aux chaînes de caractères STL. Mais un jour il va devoir écrire le code "toto" + "titi", et des choses vraiment bizarres vont se passer, et je vais devoir m’arrêter et tout lui apprendre sur les char* de toutes façons. Ou un jour il va essayer d’appeler une fonction de l’API Windows qui est documentée comme ayant un argument OUT LPTSTR et il ne sera pas capable de comprendre comment l’appeler jusqu’à ce qu’il ait tout appris sur les char*, et les pointeurs, et l’Unicode, et les wchar_t, et les fichiers d’en-tête TCHAR, et tout ce fatras qui fuit.

Lors de l’enseignement de la programmation COM, ce serait bien si je pouvais juste leur apprendre comment utiliser les assistants de Visual Studio et toutes les fonctionnalités de génération de code, mais si quoi que ce soit tourne mal, ils n’auront pas la moindre idée de ce qui s’est passé et de comment le corriger et réparer. Je vais devoir tout leur apprendre sur IUnknown et les CLSID et les ProgID et … ah, l’humanité !

Lors de l’enseignement de la programmation ASP.NET, ce serait bien si je pouvais juste leur apprendre qu’ils peuvent double-cliquer sur des choses et ensuite écrire le code qui s’exécute sur le serveur quand l’utilisateur clique sur ces choses. En fait ASP.NET fait abstraction de la différence entre écrire le code HTML pour traiter le clic sur un hyper lien (<a>) et le code pour traiter le clic sur un bouton. Problème : les concepteurs d’ASP.NET devaient cacher le fait qu’en HTML, il n’y a pas moyen de soumettre un formulaire avec un hyper lien. Ils font cela en générant quelques lignes de JavaScript et en attachant un contrôleur d’événement « onclick » au lien. L’abstraction fuit, pourtant. Si l’utilisateur final a désactivé JavaScript, l’application ASP.NET ne fonctionne pas bien, et si le programmeur ne comprend pas ce qu’ASP.NET fait comme abstraction, il n’aura simplement aucun indice sur ce qui ne va pas.

La loi des abstractions qui fuient implique que chaque fois que quelqu’un arrive avec un nouvel outil magique de génération de code qui est censé nous rendre tous super-efficaces, vous entendez plein de gens dire « apprends d’abord comment le faire manuellement, et après utilise l’outil magique pour gagner du temps ». Les outils de génération de code qui prétendent faire abstraction de quelque chose, comme toutes les abstractions, fuient, et la seule façon compétente de s’accommoder des fuites est d’apprendre comment les abstractions fonctionnent et ce dont elles font abstraction. Ainsi les abstractions nous font gagner du temps lors du travail, mais elles ne nous font pas gagner du temps lors de l’apprentissage.

Tout ceci implique que paradoxalement, alors même que nous avons des outils de programmation de plus en plus haut niveau avec des abstractions toujours meilleures, devenir un programmeur compétent devient de plus en plus difficile.

Lors de mon premier stage chez Microsoft, j’ai écrit des bibliothèques de chaînes de caractères pour Macintosh. Une tâche typique : écrire une version de strcat qui retourne un pointeur sur la fin de la nouvelle chaîne. Quelques lignes de code C. Tout ce que j’ai fait était directement issu du K&R — un bouquin peu épais sur le langage de programmation C.

Aujourd’hui, pour travailler, j’ai besoin de connaître Visual Basic, COM, ATL, C++, InnoSetup, les rouages d’Internet Explorer, les expressions régulières, COM, HTML, CSS, et XML. Rien que des outils de haut niveau comparés aux vieux trucs de K&R, mais j’ai toujours besoin de connaître les trucs de K&R ou alors je suis fichu.

Il y a dix ans, nous pouvions imaginer que de nouveaux paradigmes de programmation rendraient la programmation plus facile aujourd’hui. En fait, les abstractions que nous avons créées au fil des années nous permettent d’aborder de nouveaux ordres de complexité dans le développement logiciel, que nous n’avions pas à aborder il y a dix ou quinze ans, comme la programmation d’interfaces graphiques ou la programmation réseau. Et alors que ces fabuleux outils, comme les langages modernes de type objet, nous permettent d’effectuer beaucoup de travail incroyablement vite, soudain un jour nous devons régler un problème là où l’abstraction a fuit, et cela prend 2 semaines. Et quand vous devez embaucher un programmeur pour faire majoritairement du VB, ce n’est pas suffisant d’embaucher un programmeur VB, parce qu’il restera collé au goudron chaque fois que l’abstraction VB fuira.

La loi des abstractions qui fuient nous met des bâtons dans les roues.

© Joël Spolsky

Commentaires fermés sur La loi des abstractions qui fuient Publié dans Mots-clé