From 015b896f652bea959773a1b1f9103a277558ee9e Mon Sep 17 00:00:00 2001 From: Adrien Date: Sat, 4 Jun 2022 12:57:39 +0200 Subject: [PATCH] Weekly backup. --- 20211230101331-software.org | 31 +- 20211230101535-python.org | 180 ++++++- 20211230160756-emacs.org | 14 +- 20211230210035-flask.org | 20 +- 20220101172208-la_methode___missing__.org | 2 +- 20220104155310-postgres.org | 24 +- 20220109134723-linux.org | 8 +- 20220130153624-textual.org | 62 ++- ...20528-arrondir_les_flottants_en_python.org | 40 +- 20220217175029-mypy.org | 4 +- 20220226100700-matrix.org | 105 ++++ 20220306172805-defusedxml.org | 3 +- 20220306173625-lxml.org | 9 + 20220306181623-xml_billion_laughs.org | 35 ++ ...06184527-xml_external_entity_expension.org | 28 + 20220319091825-anki.org | 20 +- 20220327100232-elisp.org | 37 ++ 20220327102701-lvm.org | 31 ++ 20220327105648-bash.org | 13 + ..._laquelle_la_valeur_est_la_plus_petite.org | 79 +++ 20220416112952-timethese.org | 96 ++++ 20220429091956-ci_cd.org | 32 ++ 20220429093533-test_driven_development.org | 54 ++ 20220430122532-list_vs_tuple.org | 128 +++++ ...501130626-python_peephole_optimization.org | 78 +++ 20220501153651-interning_optimization.org | 64 +++ 20220501191904-git.org | 91 ++++ ...nt_d_un_iterable_satisfaisant_avec_any.org | 52 ++ ...style_du_texte_affiche_sur_une_console.org | 45 ++ ...736-difference_entre_les_operateurs_et.org | 38 ++ 20220516145022-type_object.org | 47 ++ ...16151958-any_et_all_d_un_iterable_vide.org | 26 + ...5011-ordre_de_resolution_des_attributs.org | 31 ++ ...708-attribut_imag_des_types_numeriques.org | 28 + 20220516160421-evaluation_tardive.org | 33 ++ 20220519082710-la_methode_new.org | 62 +++ 20220519090118-la_methode_prepare.org | 59 +++ 20220520230639-la_methode_hash.org | 89 ++++ 20220522101323-method_resolution_order.org | 66 +++ 20220522143325-dataclasses.org | 58 +++ 20220522151020-namedtuple.org | 43 ++ 20220524120058-guix.org | 23 + 20220524120307-moteurs_de_recherche.org | 10 + 20220525214257-drbd.org | 71 +++ 20220526073445-nyxt.org | 19 + 20220526082057-self_hosted_server.org | 31 ++ 20220526082204-gitea.org | 20 + 20220526083306-exim_dovecot.org | 104 ++++ 20220526105643-roundcube.org | 92 ++++ 20220527131001-magit.org | 15 + 20220527165926-l_attribut_de_classe_slots.org | 99 ++++ 20220527190024-fonction_zip.org | 55 ++ 20220528104202-set_et_frozenset.org | 106 ++++ 20220528110814-string.org | 52 ++ 20220528161514-la_methode_bool.org | 56 ++ 20220528183301-fonction_vars.org | 30 ++ 20220529102607-blockchain.org | 92 ++++ 20220529123824-generator.org | 98 ++++ 20220529131614-latex.org | 17 + 20220530080115-youtube.org | 52 ++ 20220604110059-json.org | 69 +++ .../320px-Drbd-arch_2022-05-25_22-05-47.png | Bin 0 -> 32644 bytes .../640px-Drbd-arch_2022-05-25_22-06-08.png | Bin 0 -> 75388 bytes ...iagramme_Matrix_fr_2022-02-26_10-29-56.svg | 481 ++++++++++++++++++ Introduction/LVM1_2022-03-27_10-39-39.jpg | Bin 0 -> 40478 bytes .../Cycle-global-tdd_2022-04-29_10-17-43.png | Bin 0 -> 240403 bytes ...gramme_Matrix_fr.svg_2022-02-26_10-14-23.png | Bin 0 -> 196587 bytes matrix/Diagramme_Matrix_fr.png | Bin 0 -> 62564 bytes 68 files changed, 3530 insertions(+), 27 deletions(-) create mode 100644 20220226100700-matrix.org create mode 100644 20220306173625-lxml.org create mode 100644 20220306181623-xml_billion_laughs.org create mode 100644 20220306184527-xml_external_entity_expension.org create mode 100644 20220327100232-elisp.org create mode 100644 20220327102701-lvm.org create mode 100644 20220327105648-bash.org create mode 100644 20220416105003-obtenir_la_cle_d_un_dictionnaire_pour_laquelle_la_valeur_est_la_plus_petite.org create mode 100644 20220416112952-timethese.org create mode 100644 20220429091956-ci_cd.org create mode 100644 20220429093533-test_driven_development.org create mode 100644 20220430122532-list_vs_tuple.org create mode 100644 20220501130626-python_peephole_optimization.org create mode 100644 20220501153651-interning_optimization.org create mode 100644 20220501191904-git.org create mode 100644 20220514130004-trouver_le_premier_element_d_un_iterable_satisfaisant_avec_any.org create mode 100644 20220514133841-modifier_le_style_du_texte_affiche_sur_une_console.org create mode 100644 20220516125736-difference_entre_les_operateurs_et.org create mode 100644 20220516145022-type_object.org create mode 100644 20220516151958-any_et_all_d_un_iterable_vide.org create mode 100644 20220516155011-ordre_de_resolution_des_attributs.org create mode 100644 20220516155708-attribut_imag_des_types_numeriques.org create mode 100644 20220516160421-evaluation_tardive.org create mode 100644 20220519082710-la_methode_new.org create mode 100644 20220519090118-la_methode_prepare.org create mode 100644 20220520230639-la_methode_hash.org create mode 100644 20220522101323-method_resolution_order.org create mode 100644 20220522143325-dataclasses.org create mode 100644 20220522151020-namedtuple.org create mode 100644 20220524120058-guix.org create mode 100644 20220524120307-moteurs_de_recherche.org create mode 100644 20220525214257-drbd.org create mode 100644 20220526073445-nyxt.org create mode 100644 20220526082057-self_hosted_server.org create mode 100644 20220526082204-gitea.org create mode 100644 20220526083306-exim_dovecot.org create mode 100644 20220526105643-roundcube.org create mode 100644 20220527131001-magit.org create mode 100644 20220527165926-l_attribut_de_classe_slots.org create mode 100644 20220527190024-fonction_zip.org create mode 100644 20220528104202-set_et_frozenset.org create mode 100644 20220528110814-string.org create mode 100644 20220528161514-la_methode_bool.org create mode 100644 20220528183301-fonction_vars.org create mode 100644 20220529102607-blockchain.org create mode 100644 20220529123824-generator.org create mode 100644 20220529131614-latex.org create mode 100644 20220530080115-youtube.org create mode 100644 20220604110059-json.org create mode 100644 Introduction/320px-Drbd-arch_2022-05-25_22-05-47.png create mode 100644 Introduction/640px-Drbd-arch_2022-05-25_22-06-08.png create mode 100644 Introduction/Diagramme_Matrix_fr_2022-02-26_10-29-56.svg create mode 100644 Introduction/LVM1_2022-03-27_10-39-39.jpg create mode 100644 Processus cyclique de développement/Cycle-global-tdd_2022-04-29_10-17-43.png create mode 100644 Références/800px-Diagramme_Matrix_fr.svg_2022-02-26_10-14-23.png create mode 100644 matrix/Diagramme_Matrix_fr.png diff --git a/20211230101331-software.org b/20211230101331-software.org index 756bcf2..8b70672 100644 --- a/20211230101331-software.org +++ b/20211230101331-software.org @@ -1,14 +1,41 @@ :PROPERTIES: :ID: ca50d517-3e8a-4d03-ba38-7ff411e87408 -:mtime: 20220130102557 +:mtime: 20220602223012 :ctime: 20211230101331 :END: #+title: Software +* Conceptes +** /LBYL/ et /EAFP/ +*** *L*ook *B*efore *Y*ou *L*eap +Technique consistant à anticiper les éventuelles erreurs (en vérifiant un pointeur /null/ par exemple). +*** *E*asier to *A*sk *F*orgiveness than *P*ermission +Technique privilégiant l'interception d'erreurs à leur anticipation. Cela permet : + * De réduire la charge CPU en vérifications, surtout lorsque la probabilité d'exception est faible, d'autant plus à + partir de Python3.11 ([[https://github.com/python/cpython/issues/84403][zero-cost exceptions]]). +Il est nécessaire que le language mis en oeuvre permette l'interception d'erreurs (pas le cas du C ou du Go). + +* Design pattern +TODO + * Langages ** Compilés *** [[id:ed8be72a-8a4d-4ef7-92e4-78d07095deaf][C++]] *** [[id:673d43c9-0b31-4f28-b550-4eb615c41dac][Rust]] ** Interprétés +*** [[id:a877b82e-4925-41de-8903-8109dd98e773][Bash]] +*** [[id:33ef1e68-70ad-43c8-850d-4b8ed2c5ea16][Elisp]] *** [[id:4fabfe6a-b104-464f-8a87-dfd7d761dbcc][Python]] -* Design pattern + +* Composition de docs +** [[id:3250943b-32f0-4bdc-b0d2-5fbcf1724f36][Latex]] + +* Méthodes +** [[id:6da0b985-e6f4-4454-bb6a-e941b722365b][Test driven development]] + +* Gestionnaire de versions +** [[id:e93719b3-088d-4fe7-9ef8-fc9a4fd84827][Git]] + +* Outils +** Performances +*** [[https://www.intel.com/content/www/us/en/developer/tools/oneapi/vtune-profiler.html#gs.26xa9e][Intel® VTune™ Profiler]] diff --git a/20211230101535-python.org b/20211230101535-python.org index d62a83c..5045490 100644 --- a/20211230101535-python.org +++ b/20211230101535-python.org @@ -1,6 +1,6 @@ :PROPERTIES: :ID: 4fabfe6a-b104-464f-8a87-dfd7d761dbcc -:mtime: 20220306173558 +:mtime: 20220601215830 :ctime: 20211230101535 :END: #+title: Python @@ -9,13 +9,40 @@ * Language ** Classe *** Méthodes dunder (Double UNDERscore method) - * [[id:dc2a8693-7158-4155-8eff-fc35a21a077d][La méthode dunder __missing__]] +**** [[id:5803422c-c089-4369-93cc-80caca71a26b][La méthode __bool__]] +**** [[id:aaf77222-82d2-492c-bf5f-2dd47659b1be][La méthode __new__]] +**** [[id:dc2a8693-7158-4155-8eff-fc35a21a077d][La méthode __missing__]] +**** [[id:0d9a7351-babd-4394-9d95-1d40cb46f615][La méthode __prepare__]] +**** [[id:d1877b3b-3fe5-43d5-9be2-afb5ffe6b918][La méthode __hash__]] +**** Les méthodes __repr__ et __str__ +La méthode ~__repr__~ est utilisée pour le debug quand ~__str__~ pour l'utilisateur (/good looking strings/). +*** [[id:1512a18b-221a-4f15-90a0-9ccac023dfd6][Method Resolution Order]] +** Types +*** [[id:f892dbdd-0cb5-4204-bca0-09aafb41f7ca][Dataclasses]] +*** [[id:67410dad-d959-4029-b281-9bf1c9e69ede][Generator]] +*** [[id:234f4590-b484-4286-9dbd-49612f4657be][NamedTuple]] +*** [[id:cc0e3fd0-2832-4bf6-909f-354507f7ed11][Set et frozenset]] +*** [[id:8b686fdd-2abd-459d-8d8d-0c57915de8fb][String]] +*** [[id:ed7551e9-706c-4bc3-90fb-e5a63a4a7903][Table de hashage]] +** Fonctions Built-in +*** [[id:487f57f5-e9fc-4df7-ae14-b41f9c1fa186][Fonction vars]] +*** [[id:acda43fa-70be-4939-9128-47114e48e4cb][Fonction zip]] * Frameworks ** Web *** [[id:26b04294-75e8-4043-a9a6-a20acd952963][Flask]] * UI +** GUI +*** [[https://kivy.org/#home][Kiwi]] +*** [[https://www.libavg.de/site/projects/libavg/wiki/FeatureList][libAvg]] +*** [[https://pysimplegui.readthedocs.io/en/latest/][PySimpleGui]] +*** [[https://pyforms.readthedocs.io/en/latest/][Pyforms]] +Framework permettant d'exécuter une IHM dans 3 environnements différents : Desktop GUI, terminal et web. +*** [[https://www.qt.io/qt-for-python][Pyside]] +Bindings python pour Qt. +** CLI +*** [[https://typer.tiangolo.com/https://typer.tiangolo.com/][Typer]] ** Text UI *** [[id:6cc56ee4-6d42-4d50-beb3-bb22a98298dd][textual]] @@ -23,23 +50,162 @@ ** Analyseur statique de code *** [[id:1d258869-5421-496a-b296-2d157ebdf3b6][mypy]] *** [[id:113a938e-3fb2-45cb-ae6e-41801418139b][bandit]] +** Automatisation de tests +*** [[https://nox.thea.codes/en/stable/][Nox]] +Outil en ligne de commande permettant d'automatiser les tests dans multiples environnements Python. Comme Tox, à la +différence, que la configuration est effectuée depuis un fichier Python (et non pas de configuration). +*** [[https://tox.wiki/en/latest/][Tox]] +Outil en ligne de commande permettant d'automatiser les tests dans multiples environnements Python. +** Couverture de code +*** [[https://github.com/plasma-umass/slipcover][Slipcover]] +Outil de mesure de couverture de code plus rapide que [[https://github.com/nedbat/coveragepy][coverage.py]]. ** Formatter *** [[https://github.com/hhatto/autopep8][autopep8]] Utilitaire formattant le code afin de respecter la [[https://www.python.org/dev/peps/pep-0008/][PEP8]] guideline. Il est basé sur l'analyse de code effectuée par [[https://github.com/PyCQA/pycodestyle][pycodestyle]]. Autopep8 ne modifie que les espaces contenus dans le code (l'option ~--aggressive~ permet d'accroitre le pouvoir d'autopep8 et le laisser modifier le code). +*** [[https://github.com/PyCQA/isort][isort]] +Outil classant les imports par ordre alphabétique, les séparant par section et par type. +*** [[https://black.readthedocs.io/en/stable/][Black]] +Formatter n'appliquant que le style [[https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html][/black/]]. Il accepte la même synthaxe de contrôle que YAPF. *** [[https://github.com/google/yapf][YAPF]] En plus de vérifier la conformité avec la [[https://www.python.org/dev/peps/pep-0008/][PEP8]], YAPF formatte le code afin de respecter un style (approche basée sur ~clang-format~ et similaire à ~gofmt~). -*** [[https://black.readthedocs.io/en/stable/][Black]] -Formatter n'appliquant que le style [[https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html][/black/]]. Il accepte la même synthaxe de contrôle que YAPF. +** Générateur de données +*** [[https://github.com/lk-geimfari/mimesis][Memesis]] +Génération de données pouvant être utilisées pour: + * Remplir une base de données de test, + * Créer une faux /API endpoint/, + * Créer des fichiers JSON ou XML d'après une structure arbitraire. +*** [[https://github.com/joke2k/faker][Faker]] +Génération de données. +** Mockup +*** [[https://github.com/spulec/freezegun][FreezeGun]] +Mockup du module [[https://docs.python.org/fr/3/library/datetime.html][datetime]] permettant de simuler des changements de temps. +*** [[https://github.com/getsentry/responses][Responses]] +Mockup du module [[https://docs.python-requests.org/en/latest/][Requests]] permettant de simuler les réponses d'un serveur HTTP. +** Profiling +*** [[https://docs.python.org/3/library/profile.html#][cProfile]] +Programme fournissant des /deteministic profiling/ de programmes (indique le nombre d'appel de chaque +méthode et le temps passé). +*** [[https://github.com/bloomberg/memray][Menray]] +/Memory profiler/ traçant les allocations mémoire dans le code Python, les modules extensions et l'interpréteur. +*** [[https://jiffyclub.github.io/snakeviz/][SnakeViz]] +Visualisation des rapports de /cProfile/ (web browser). +*** [[https://github.com/nvdv/vprof][vprof]] +Profiling de programmes python permettant de surveiller le temps d'exécution et l'usage mémoire. +~vprof -c hpm ""~ +** Test de charge +*** [[https://github.com/locustio/locust][Locust]] +Outil permettant de tester la tenue en charge d'applications (initialement web servers). + +** Traceur d'exécution de code +*** [[https://github.com/ionelmc/python-hunter][Python-hunter]] +Traçage des appels de fonctions, du code exécuté et des valeurs retournées (vs. smiley, pytrace, PySnooper). +** Debugger +*** [[https://github.com/ionelmc/python-manhole][Manhole]] +Service (interface via une socket Unix permettant l'accès à un REPL) permettant l'inspection d'un programme en cours d'exécution. + + +* Implémentations +** CPython +Implémentation de référence du language Python. +*** Développement de CModules +**** Références + * [[https://pythondev.readthedocs.io/][Python development notes - Victor Stinner]] +*** Debug +Activation du mode développeur ~python -X dev~ ou ~PYTHONDEVMODE=1 python~ (cf. [[https://docs.python.org/dev/library/devmode.html][Python Development Mode]]). + +** Cinder +Implémentation CPython optimisée par Instagram : + * [[https://engineering.fb.com/2022/05/02/open-source/cinder-jits-instagram/][Jit]] * Modules intéressants +** Bases de données +*** [[https://spark.apache.org/docs/latest/api/python/][PySpark]] +Binding Python a /Apache Spark/ permettant le traitement de bases de données massives, lorsque /Pandas/ devient trop +lent. +** Benchmarking +*** [[id:4e351659-8a96-44af-bacb-f548ea35913e][timethese]] +** CSV +*** [[https://csvkit.readthedocs.io/en/latest/index.html][csvkit]] +Ensemble d'outils en ligne de commande pour convertir en csv et pour les manipuler. +** Graphique +*** [[https://github.com/tfardet/mpl_chord_diagram][mpl_chord_diagram]] +Génération de [[https://en.wikipedia.org/wiki/Chord_diagram_(information_visualization)][Chord diagrams]] avec /matplotlib/. +** Logs +*** [[https://github.com/itamarst/eliot][eliot]] +Génération de logs sous forme d'arbre. +*** [[https://www.structlog.org/en/stable/index.html][structlog]] +Formattage de logs. +** Manipulation de classes +*** [[https://github.com/MagicStack/immutables][Immutables]] +Création de tables de hashage /immutables/. +*** [[https://www.attrs.org/en/stable/][attrs]] +Création de classes en évitant l'écriture du /boilerplate code/ (possibilité de créer des objets /immutables/). +** Médias +*** [[https://github.com/ytdl-org/youtube-dl][youtube-dl]] +Téléchargement de vidéo depuis youtube, utilisé par mpv. +** Sérialization/déserialization +*** [[https://github.com/ICRAR/ijson][ijson]] +Parser JSON ne nécessitant pas que l'ensemble de la donnée soit chargée pour la parser (optimisation de la mémoire +consommée, cf. [[https://pythonspeed.com/articles/json-memory-streaming/][JSON memory streaming - Pythonspeed]]). +** Surveillance de fichers +*** [[https://watchfiles.helpmanual.io/][watchfiles]] +Surveillance de fichiers basé sur la librarie Rust /Notify/. ** Vulnérabilités/Cyber *** [[id:ba4c7c25-ee27-4b5e-8ef7-ba2ecc34f127][defusedxml]] +** Web-scaping +*** [[https://scrapy.org/][scrapy]] +Framework d'extraction de données depuis des site web. + +* Performances +** [[id:b9f392bd-bd45-4e9e-94ec-b19caedff86f][List vs tuple]] +** [[id:26e1fdfb-1f8e-4c62-a08f-468a56ab03c8][Peephole optimization]] +** [[id:c0d562c7-9d2f-4f35-b7b1-f6b6ca5273f9][Interning optimization]] +** [[id:05864d6a-7a05-4c11-af14-4faebfdd1926][L'attribut de classe __slots__]] + +* Génération et déploiement +** Génération d'un artifact distribuable depuis le code source + * Utilisation de [[https://setuptools.pypa.io/en/latest/][setuptools]]. + * Template de fichier [[https://github.com/pypa/sampleproject/blob/main/setup.py][setup.py]]. +** Stockage et distribution de l'artifact + * Utilisation de [[https://pip.pypa.io/en/stable/][pip]]. +*** Les fichiers ~requirements.txt~ + * Il peut être intéressant de produire différents fichiers ~requirements.txt~ selon l'usage souhaité: + * ~/requirements/app.txt~ pour les dépendances nécessaires à l'exécution du code, + * ~/requirements/test.txt~ pour les dépendances nécessaires aux tests du code. + * Dans un environnement de dev virtualisé, la commande ~pip freeze~ permet d'obtenir l'ensemble des dépendances et + leurs version, récursivement. + * [[https://github.com/jazzband/pip-tools][Pip-tools]] propose des outils permettant de simplifier la gestion des dépendances: + * ~pip-compile --generate-hashes -o ./requirements.txt~ permet la génération de fichiers ~requirements.txt~ depuis les ~setup.py~ ou ~requirements.in~ d'un package, + * La génération de hashes (option ~generate-hashes~) permet la vérification, lors de l'installation des dépendances, + d'en vérifier la non altération (par rapport au paquet utilisé lors de la génération de notre package), + * ~pip-compile --generate-hashes --extra dev -o ./requirements_dev.txt~ permet la génération d'un fichier + ~requirements_dev.txt~ comprenant les dépendances identifiées par les fichiers ~install_requires~ et + ~extras_require[dev]~. + * [[https://devpi.net/docs/devpi/devpi/stable/+d/index.html#][devpi]] fournit un serveur PyPI-compatible (mirroir PyPI) et un utilitaire en ligne de commande pour les activités de mise en paquet, + test (intégration avec Jenkins) et livraison. +** Gestion des dépendances et de leur version et leur cloisement par projet + * Utilisation de [[https://python-guide-pt-br.readthedocs.io/fr/latest/dev/virtualenvs.html][virtualenv]], + * utiliser ~python -m pip~ et ~python -m venv~ (exécution du module en tant que script, au lieu de ~pip~ et ~virtualenv~) permet d'éviter les incohérences + entre les différents répertoires des packages Python (cas lorsqu'un répertoire est présent dans le ~PYTHONPATH~ et qu'un autre l'est dans le ~PATH~). + * Tips -** [[id:4ef76164-0e67-410a-8d26-b03071a0cc41][Compter la fréquence des éléments d'une liste]] -** [[id:cb3c63b9-6452-4016-9b2f-a25784941d5d][List vs deque]] -** [[id:9bdede16-5137-4393-a027-a5afbffd1618][Génération chaine de caractères aléatoires]] +** [[id:24408701-21d8-4f4e-aed9-c58746df2244][Différence entre les opérateurs + et +=]] +** [[id:a32ab138-f9a8-4d61-9c09-97953c5a0a92][type == object]] +** [[id:16d16a4b-6a4c-4597-a45f-1a99f2cb9f29][Any et all d'un iterable vide]] +** [[id:e9a0cb94-ee68-4dfd-9013-c83ce2a18481][Ordre de résolution des attributs]] +** [[id:279b5e79-6918-432c-85fa-2fbefc06619a][Attribut imag des types numériques]] +** [[id:831fbfd2-c668-4099-b2d7-ecae734b9ec4][Evaluation tardive]] ** [[id:f7c05933-90e6-4a9c-acd5-1f0baf62f07f][Arrondir les flottants en python]] +** [[id:4ef76164-0e67-410a-8d26-b03071a0cc41][Compter la fréquence des éléments d'une liste]] +** [[id:9bdede16-5137-4393-a027-a5afbffd1618][Génération chaine de caractères aléatoires]] +** [[id:cb3c63b9-6452-4016-9b2f-a25784941d5d][List vs deque]] +** [[id:e9adeaaa-701a-4473-a30b-94f1e744c6d1][Obtenir la clé d'un dictionnaire pour laquelle la valeur est la plus petite]] +** [[id:fa7e728b-dd2a-4d30-8f4b-e87813dc5f33][Modifier le style du texte affiché sur une console]] +** [[id:20753ab5-70c3-4c8f-b261-56832cd5392c][Trouver le premier élément d'un iterable satisfaisant avec any]] + +* Howtos + * [[https://towardsdatascience.com/how-to-make-gifs-in-python-664d15ed4256][How to make GIFs in Python - Medium]] diff --git a/20211230160756-emacs.org b/20211230160756-emacs.org index c8079c4..26aa003 100644 --- a/20211230160756-emacs.org +++ b/20211230160756-emacs.org @@ -1,6 +1,6 @@ :PROPERTIES: :ID: 7bae5645-d19d-41c3-aafe-7c80c2a90789 -:mtime: 20211230161355 +:mtime: 20220604121806 :ctime: 20211230160756 :END: #+title: Emacs @@ -11,3 +11,15 @@ * Modes ** [[id:59e3160b-39bc-4e2e-ab0a-79d58ac11804][Org]] +** [[id:22c81d3d-29cc-4b49-8e94-bf8a7f8cb9b9][Magit]] + +* Libraries +** [[https://github.com/Malabarba/elisp-bug-hunter][elisp-bug-hunter]] +Debug des scripts /elisp/ de configuration, permet d'identifier l'origine de l'erreur. + +* Dotemacs +** [[https://github.com/Gavinok/emacs.d][Gavinok's dotemacs]] +** [[https://github.com/rougier/dotemacs][Rougier's dotemacs]] +** [[https://github.com/yiufung/dot-emacs][Yiufung's dotemacs]] +Intéressant pour la configuration du mode /anki/. +** [[https://github.com/Gavinok/rational-emacs][rational-emacs - Gavinok]] diff --git a/20211230210035-flask.org b/20211230210035-flask.org index 6f561d6..8991c90 100644 --- a/20211230210035-flask.org +++ b/20211230210035-flask.org @@ -1,15 +1,29 @@ :PROPERTIES: :ID: 26b04294-75e8-4043-a9a6-a20acd952963 -:mtime: 20211230210701 +:mtime: 20220526081400 :ctime: 20211230210035 :END: #+title: Flask #+filetags: :TODO:Python:Flask: -TODO +* Introduction +Flask est un micro-framework /open-source/ de développement web écrit en Python dont les caractéristiques sont les +suivantes : + * Noyau simple et extensible (multiples extensions), + * Basé sur les modules : + * [[https://werkzeug.palletsprojects.com/en/latest/][Werkzeug]] : librairie complète permettant de réaliser des applications web [[https://en.wikipedia.org/wiki/Web_Server_Gateway_Interface][WSGI]], + * [[https://palletsprojects.com/p/jinja/][Jinja2]] : Moteur de template pour le language Python. + * Licence BSD, -* Modules +* Extensions * [[id:0eaaef80-51f9-4670-96c8-6a911efe152e][Flask-admin]] * [[id:5f41e674-eb59-4d69-981b-46a18cf28452][Flask-security]] +* Performance +Pour détecter les /bottlenecks/ : + * [[https://werkzeug.palletsprojects.com/en/2.1.x/middleware/profiler/][Werkzeug profiler]] : Inspecteur basé sur /cProfile/ permettant de mesurer le temps passé dans chaque méthode. + * Référence + * [[https://smirnov-am.github.io/flask-perf/][How to increase Flask performance - Alexey Smirnov]] +** Tuturiels + * [[https://github.com/tecladocode/rest-apis-flask-python][REST APIs with Flask and Python - Github]] diff --git a/20220101172208-la_methode___missing__.org b/20220101172208-la_methode___missing__.org index 02ef996..51b0d17 100644 --- a/20220101172208-la_methode___missing__.org +++ b/20220101172208-la_methode___missing__.org @@ -1,6 +1,6 @@ :PROPERTIES: :ID: dc2a8693-7158-4155-8eff-fc35a21a077d -:mtime: 20220101184431 +:mtime: 20220519084452 :ctime: 20220101172208 :END: #+title: La méthode __missing__ diff --git a/20220104155310-postgres.org b/20220104155310-postgres.org index 9b9ba1a..74e10a4 100644 --- a/20220104155310-postgres.org +++ b/20220104155310-postgres.org @@ -1,6 +1,6 @@ :PROPERTIES: :ID: 171ce2f7-4028-47b0-b4e0-5a4a6ccb74ac -:mtime: 20220104161040 +:mtime: 20220527114623 :ctime: 20220104155310 :END: #+title: Postgres @@ -13,3 +13,25 @@ * Interfaces utilisateur ** [[id:0455921f-3ac0-437e-ba76-1afb3f6f85ea][Psql]] + +* Howto +** Pour lister les utilisateurs +#+BEGIN_SRC sql +\du +#+END_SRC + +** Pour supprimer un utilisateur +#+BEGIN_SRC shell +sudo -u postgres dropuser -e +#+END_SRC +** Pour supprimer une base de données +#+BEGIN_SRC shell +sudo -u postgres dropdb +#+END_SRC +** Pour modifier le password d'un utilisateur (dans psql) +#+BEGIN_SRC sql +\password +#+END_SRC + +* Références + * [[https://stackoverflow.com/questions/45395538/postgres-md5-password-plain-password][Postgres: MD5 Password / Plain password - Github]] diff --git a/20220109134723-linux.org b/20220109134723-linux.org index 22f9f26..2ce8db1 100644 --- a/20220109134723-linux.org +++ b/20220109134723-linux.org @@ -1,15 +1,20 @@ :PROPERTIES: :ID: e7581fe3-f83f-4243-91ed-6ef7ade6a844 -:mtime: 20220130201008 +:mtime: 20220530204913 :ctime: 20220109134723 :END: #+title: Linux * [[id:5943c76c-8b25-4cbd-b0b9-c819e5a490ba][cyber]] +* Distributions +** [[id:393342ff-bf38-4472-8713-3de5ebe43eca][Guix]] + * Systèmes d'initialisation ** [[id:af912c20-4752-44ba-bdc0-99451ac0cd10][systemd]] +* Gestionnaire de volumes logiques : [[id:f202b810-0fba-4c90-bc4c-f8cbc001fe88][LVM]] + * Fonctionnalités ** [[id:fad57303-ce0c-4ae4-9529-294f70ecfaa5][Inotify]] @@ -19,3 +24,4 @@ * Utilitaires ** Parsing de contenu json (lib+bin) : [[id:83908b49-3945-4dce-8b26-2a5e4636df13][jq]] ** Transfert de données à un serveur (lib+bin) : [[id:5ea61eaa-7f37-464c-aa69-8251de8f81af][curl]] +** Template utilisant les variables d'environnement : [[https://manpage.me/?q=envsubst][envsubst]] diff --git a/20220130153624-textual.org b/20220130153624-textual.org index 0f715ee..93c79e2 100644 --- a/20220130153624-textual.org +++ b/20220130153624-textual.org @@ -1,6 +1,6 @@ :PROPERTIES: :ID: 6cc56ee4-6d42-4d50-beb3-bb22a98298dd -:mtime: 20220130154530 +:mtime: 20220528185642 :ctime: 20220130153624 :END: #+title: textual @@ -13,6 +13,66 @@ Rich est une librairie Python permettant de : * Afficher des tables et du contenu markdown, * Appliquer une coloration synthaxique. +* Howto +** Pour inspecter un objet (/rich.inspect method) +#+BEGIN_SRC python :results output +from rich import inspect + +class Dummy: + + def __init__(self): + self.a = 'A' + self.b = 123 + +inspect(vars) + +inspect(Dummy()) +#+END_SRC +#+RESULTS: +: ╭────────────────── ──────────────────╮ +: │ def vars(...) │ +: │ │ +: │ vars([object]) -> dictionary │ +: │ │ +: │ 29 attribute(s) not shown. Run inspect(inspect) for options. │ +: ╰──────────────────────────────────────────────────────────────╯ +: ╭────────── ───────────╮ +: │ ╭───────────────────────────────────────────╮ │ +: │ │ <__main__.Dummy object at 0x7f55d6d2d7c0> │ │ +: │ ╰───────────────────────────────────────────╯ │ +: │ │ +: │ a = 'A' │ +: │ b = 123 │ +: ╰───────────────────────────────────────────────╯ + +** Formatter les logs +#+BEGIN_SRC python :results output +import logging +from rich.logging import RichHandler + +FORMAT = "%(message)s" +logging.basicConfig( + level="NOTSET", + format=FORMAT, + datefmt="[%X]", + handlers=[RichHandler(rich_tracebacks=True)]) + +log = logging.getLogger("rich") + +log.info("Logging set up.") + +def division(a, b): + log.debug(f"Dividing {a} by {b}.") + try: + return a / b + except ZeroDivisionError: + log.exception("Oh noes!") + +division(3, 2) +division(5, 0) +#+END_SRC +#+RESULTS: + * Références * [[https://github.com/Textualize/textual][Textual - Github]] * [[https://github.com/Textualize/rich][Rich - Github]] diff --git a/20220206220528-arrondir_les_flottants_en_python.org b/20220206220528-arrondir_les_flottants_en_python.org index 24e63f1..1fd7687 100644 --- a/20220206220528-arrondir_les_flottants_en_python.org +++ b/20220206220528-arrondir_les_flottants_en_python.org @@ -1,26 +1,62 @@ :PROPERTIES: :ID: f7c05933-90e6-4a9c-acd5-1f0baf62f07f -:mtime: 20220207072847 +:mtime: 20220528103954 :ctime: 20220206220528 :END: #+title: Arrondir les flottants en python #+filetags: :Python: +* Arrondir à l'entier le plus proche +** Avec la fonction /built-in/ /round/ +#+BEGIN_SRC python :results output +print(f"{round(7 / 2) = }") +print(f"{round(3 / 2) = }") +print(f"{round(5 / 2) = }") +#+END_SRC + +#+RESULTS: +: round(7 / 2) = 4 +: round(3 / 2) = 2 +: round(5 / 2) = 2 + +~round(5 / 2)~ retourne 2 et non 3 car la fonction /built-in/ implémente l'[[https://fr.wikipedia.org/wiki/Arrondi_(math%C3%A9matiques)#Arrondi%20au%20pair%20le%20plus%20proche][arrondi au pair le plus proche]]. + * Arrondir un nombre à l'entier inférieur #+BEGIN_SRC python :results output print(22 // 5) #+END_SRC - #+RESULTS: : 4 * Arrondir un noombre à l'entier supérieur +** Arithmétique simple +#+BEGIN_SRC python :results output +n = 22 +div = 5 +print(f'{int(n/div) + (n % div>0) = }') +#+END_SRC +#+RESULTS: +: int(n/div) + (n % div>0) = 5 + +** Opérateur à étage // (ne fonctionne qu'avec les entiers) +L'opérateur à étage // se comporte comme l'opérateur de division /, à la différence que le résultat est arrondi à +l'entier inférieur : #+BEGIN_SRC python :results output print(22 // -5 * -1) #+END_SRC +#+RESULTS: +: 5 +** Méthode /numpy.ceil/ +#+BEGIN_SRC python :results output +from numpy import ceil + +print(int(ceil(22 / 5))) +#+END_SRC #+RESULTS: : 5 * Références + * [[https://medium.com/@saint_sdmn/10-hardest-python-questions-98986c8cd309][10 Hardest Python Questions - Medium]] + * [[id:f7c05933-90e6-4a9c-acd5-1f0baf62f07f][Arrondir les flottants en python]] * [[https://www.delftstack.com/fr/howto/python/python-round-up/][Arrondir un nombre en Python]] diff --git a/20220217175029-mypy.org b/20220217175029-mypy.org index df062be..2e79245 100644 --- a/20220217175029-mypy.org +++ b/20220217175029-mypy.org @@ -1,9 +1,9 @@ :PROPERTIES: :ID: 1d258869-5421-496a-b296-2d157ebdf3b6 -:mtime: 20220217175437 +:mtime: 20220327100104 :ctime: 20220217175029 :END: -#+title: mypy +#+title: Mypy * Description /Mypy/ est un outil permettant de vérifier statiquement les types annotés. diff --git a/20220226100700-matrix.org b/20220226100700-matrix.org new file mode 100644 index 0000000..b3dc59d --- /dev/null +++ b/20220226100700-matrix.org @@ -0,0 +1,105 @@ +:PROPERTIES: +:ID: 2e51f7e7-cf37-45d3-b7c4-c136d4e3cc64 +:mtime: 20220313180139 +:ctime: 20220226100700 +:END: +#+title: matrix + +* Introduction + * Protocole de communication ouvert (specs ouvertes) pour la communication en temps réel (messagerie). + * Passerelle avec différents fournisseurs de services (xmpp, Skype, IRC, ...). + * Sécurisé (chiffrement de bout en bout activé par défaut). + * Décentralisé (possibilité d'héberger son propre serveur). + * Distribué/fédéré: chaque participant conserve l'historique de ses conversations (réplication entre les différents serveurs). + * Appels voix & vidéo basés sur WebRTC. + * Contrairement à XMPP (envoi de messages), Matrix fonctionne comme git : synchronisation de serveurs (conversation complète). + * Matrix is /transport agnostic/: JSON/HTTP pourrait être remplacé. + +#+DOWNLOADED: https://upload.wikimedia.org/wikipedia/commons/b/bd/Diagramme_Matrix_fr.svg @ 2022-02-26 10:29:56 +#+ATTR_ORG: :width 400 +[[file:Introduction/Diagramme_Matrix_fr_2022-02-26_10-29-56.svg]] + + * Long-term secret = private key. + +* Les serveurs Home +** API + * Synchro des messages et de l'état des rooms entre serveurs, en temps réel. + * Synchro de l'historique des messages sur d'autres serveurs (à la git pull) : en cas d'offline, le serveur peut + récupérer les historiques auprès d'autres serveurs afin de combler le trou. + * Récupérer le profil et la présence d'un utilisateur. +** Implémentations + * Synapse (implémentation de référence) en Python/twisted. + * Dendrite (implémentation de 2nd génération - plus scalable/performant) en Go. + * Conduit (simple/rapide/reliable) en Rust. + * Construct (orienté performances avec un min de deps) en C++. + +* Les serveurs d'application +** API + * Possède un accès privilégié à un serveur Home. + * Peut souscrire au trafic d'un server. + * Peut /mascarade/ comme un utilisateur virtuel. + +** Serveurs d'intégration + * Permet d'intégrer de nouveaux services. +*** Exemples + * Etherpad: + * RSS bot: + * Grafana: publication de dashboards grafana pour un affichage directement sur les clients. + * Jitsi: + * Matrix-content-scanner: + +** Les bridges + * Unifier différents /réseaux/ ensemble (ie: signal, IRC, ...). +*** Types de bridges + * /Bridgebot/: + * /Puppeted/: L'usage du bridge est transparent pour les utilisateurs. + +* Les clients +** API client-server + * Conçue pour être user-friendly. +** Implémentations UI + * Element (implémentation de référence avec le plus de features) pour WebApp/desktop(electron)/IOS/Android. + * Ditto (réalisé avec React) pour IOS/Android. + * Nio (réalisé avec Switch) pour IOS. + * Pattle (réalisé avec flutter) pour IOS/Android. + * FluttyChat. + * Seaglass: client matrix pour IOS. + * Spectral: client matrix desktop écrit en c++ avec QtQuick control. + * Quaternion: client IM desktop écrit en c++ avec Qt5. + * Nheko reborn: client matrix desktop écrit en c++17 avec Qt. + * Mirage: client matrix desktop configurable et opérable avec le clavier (à la Emacs). + * Fractal: client matrix desktop écrit en rust. +** Implémentations console + * weechat-matrix: + * gomuks: client matrix pour terminal écrit en go. + * matrixcli: client matrix en ligne de commande. + * matrix-commander: client matrix cli simple supportant le chiffrement E2E et la vérif. emoji. + * tchap: client développé par le gouvernement français (inclus l'intégration du matrix-content-server). + +* Les espaces (TODO) + * Public /space/: l'annuaire hiérarchisé et public de Matrix.org. + * Restricted /space/: pour les organisations, permet de regrouper des /rooms/ dans un même domaine. + * private /space/: pour un usage personnel + +* P2P (TODO) + +* OSS +Matrix utilise les OSS suivants: + * olm: Librairie implémentant un /double-ratchet/ (basé sur les specs de Signal). + * [[https://gitlab.matrix.org/matrix-org/olm/blob/master/docs/megolm.md][megolm]]: Librairie réalisant un /ratchet/ crypto basé sur AES pour les communications de groupe. + +* Inconvénients + * Backward secrecy (/future secrecy/ ou /post-compromise security/, le vol de la clé privée d'un correspondant ne + compromet pas la confidentialité des communications futures) faible (tant que la session n'est pas renouvellée). + * [[https://fr.wikipedia.org/wiki/Confidentialit%C3%A9_persistante][Confidentialité persistante]] (/forward secrecy/ ou /perfect forward secrecy/, le vol de la clé privée d'un + correspondant ne compromet pas la confidentialité des communications passées) faible (dépend de la fréquence de + renouvellement des clés). + +* Optimisations réalisées + * Lazy loading members: Ne charger les métadonnées que des personnes communiquant dans la /room/ (réduction de la RAM consommée). + +* Références + * [[https://fr.wikipedia.org/wiki/Matrix_(protocole)][Wikipedia]] + * [[https://www.youtube.com/watch?v=cD8xbci4wAY][Matrix - Open, Secure, Decentralised, Real-Time Communication Across Networks - Oleg Fiksel]] + * [[https://www.youtube.com/watch?v=9cjUzftDuoQ][Matrix in the French State What happens when a government adopts open source & open standards for all its internal communication? by Matthew Hodgson]] + * [[https://2021.commcon.xyz/talks/matrix-101][Matrix 101 - Thibault Martin]] diff --git a/20220306172805-defusedxml.org b/20220306172805-defusedxml.org index c4ace03..cccf4b4 100644 --- a/20220306172805-defusedxml.org +++ b/20220306172805-defusedxml.org @@ -1,6 +1,6 @@ :PROPERTIES: :ID: ba4c7c25-ee27-4b5e-8ef7-ba2ecc34f127 -:mtime: 20220306184630 +:mtime: 20220522180309 :ctime: 20220306172805 :END: #+title: defusedxml @@ -46,6 +46,5 @@ et = parse(xmlfile) réaliser l'inclusion. 7. Il s'agit de fonctionnalités mais peuvent introduire des failles supplémentaires. - * Références * [[https://github.com/tiran/defusedxml][Github]] diff --git a/20220306173625-lxml.org b/20220306173625-lxml.org new file mode 100644 index 0000000..1309947 --- /dev/null +++ b/20220306173625-lxml.org @@ -0,0 +1,9 @@ +:PROPERTIES: +:ID: b18fe210-fac3-4f63-8041-4b686c64ee6a +:mtime: 20220306173658 +:ctime: 20220306173625 +:END: +#+title: lxml + +* Introduction +Module Python permettant la manipulation de données au format XML. diff --git a/20220306181623-xml_billion_laughs.org b/20220306181623-xml_billion_laughs.org new file mode 100644 index 0000000..d7b2ba9 --- /dev/null +++ b/20220306181623-xml_billion_laughs.org @@ -0,0 +1,35 @@ +:PROPERTIES: +:ID: 7b090d0a-b96b-4d40-afec-2155a6909935 +:mtime: 20220306182044 +:ctime: 20220306181623 +:END: +#+title: XML billion laughs + +* Introduction + * Attaque de type déni de service visant les parseurs XML. + * Peut aussi servir de vecteur pour faciliter les attaques par /dépassement de tampon/. + +* Principe +Le standard XML permet la déclaration récursive d'entités XML sans aucune limite de profondeur ou de longueur des +éléments. L'attaquant peut donc donner une très grande longueur au résultat censé être produit lors de l'analyse +lexicale du document XML. Ainsi, le déni de service est provoqué par le parseur XML tentant de lire le document avec +toutes ses entités décrites de manière récursive, ce qui peut potentiellement provoquer un dépassement mémoire. + +* Exemple +#+BEGIN_SRC xml + + + + + +… + +]> +&lol128; +#+END_SRC + +* Références + * [[https://en.wikipedia.org/wiki/Billion_laughs_attack][Wikipedia]] + + diff --git a/20220306184527-xml_external_entity_expension.org b/20220306184527-xml_external_entity_expension.org new file mode 100644 index 0000000..646f1e6 --- /dev/null +++ b/20220306184527-xml_external_entity_expension.org @@ -0,0 +1,28 @@ +:PROPERTIES: +:ID: 869e88ed-fe68-47b5-a911-e27cf3927f66 +:mtime: 20220307073210 +:ctime: 20220306184527 +:END: +#+title: XML external entity expension + +* Introduction + * Attaque visant les parseurs XML, + * Permettant: + * La divulgation de données, + * Le déni de service, + * /Server-side request forgery (SSRF)/. + +* Principe +Le standard XML 1.0, définissant la structure d'un document XML, introduit le concept d'/entity/ qui consiste en une +unité de stockage d'un certain type. Quelques types d'/entity/ + +There are a few different types of entities, external general/parameter parsed entity often shortened to external entity, that can access local or remote content via a declared system identifier. The system identifier is assumed to be a URI that can be dereferenced (accessed) by the XML processor when processing the entity. The XML processor then replaces occurrences of the named external entity with the contents dereferenced by the system identifier. If the system identifier contains tainted data and the XML processor dereferences this tainted data, the XML processor may disclose confidential information normally not accessible by the application. Similar attack vectors apply the usage of external DTDs, external style sheets, external schemas, etc. which, when included, allow similar external resource inclusion style attacks. + +Attacks can include disclosing local files, which may contain sensitive data such as passwords or private user data, using file: schemes or relative paths in the system identifier. Since the attack occurs relative to the application processing the XML document, an attacker may use this trusted application to pivot to other internal systems, possibly disclosing other internal content via http(s) requests or launching a CSRF attack to any unprotected internal services. In some situations, an XML processor library that is vulnerable to client-side memory corruption issues may be exploited by dereferencing a malicious URI, possibly allowing arbitrary code execution under the application account. Other attacks can access local resources that may not stop returning data, possibly impacting application availability if too many threads or processes are not released. + +Note that the application does not need to explicitly return the response to the attacker for it to be vulnerable to information disclosures. An attacker can leverage DNS information to exfiltrate data through subdomain names to a DNS server under their control. + + + +* Références + * [[https://en.wikipedia.org/wiki/XML_external_entity_attack][Wikipedia]] diff --git a/20220319091825-anki.org b/20220319091825-anki.org index 7f9d989..c02840e 100644 --- a/20220319091825-anki.org +++ b/20220319091825-anki.org @@ -1,12 +1,24 @@ + :PROPERTIES: :ID: 97d8c2d9-a539-4f0b-ad2e-953bf7845e8c -:mtime: 20220319160846 +:mtime: 20220327110913 :ctime: 20220319091825 :END: -#+title: anki +#+title: Anki * Introduction -TODO + * logiciel libre permettant d'apprendre/réviser des cartes-mémoires grâce à la répétition espacée, + * Anki (暗記) signifie « mémorisation » en japonais. + * S'appuie sur l'algorithme SM2 à la fin des années 1980. L'algorithme a été modifié pour redéfinir l'ordre + d'apparition des cartes par priorité. + * Permet de créer des cartes-mémoires contenant plusieurs champs sur une même face. + * Sauvegarde de son apprentissage en ligne. + * Histogrammes et statistiques d'apprentissage. + * Les cartes et données d'apprentissages stockées au format SQLite. + * L'algorithme de la répétition espacée permet de réviser plus souvent les cartes les moins connues. Fondé sur la + courbe de l'oubli d'Hermann Ebbinghaus. + * Chaque utilisateur peut créer ses paquets de cartes facilement. + * De nombreux add-ons (greffons), modifiant l'aspect d'Anki ou lui ajoutant des fonctions. * Installer son serveur Anki ** Howto @@ -92,5 +104,5 @@ Update configuration (in menu parameters/advanced/Custom sync server): Cf. https://github.com/ankicommunity/anki-sync-server#anki-21 ** Références + * [[https://fr.wikipedia.org/wiki/Anki][Wikipedia]] * [[https://github.com/ankicommunity/anki-sync-server][Github]] - diff --git a/20220327100232-elisp.org b/20220327100232-elisp.org new file mode 100644 index 0000000..cfc8c48 --- /dev/null +++ b/20220327100232-elisp.org @@ -0,0 +1,37 @@ +:PROPERTIES: +:ID: 33ef1e68-70ad-43c8-850d-4b8ed2c5ea16 +:mtime: 20220327102013 +:ctime: 20220327100232 +:END: +#+title: Elisp + +* Introduction +Emacs Lisp est un dialecte du language /Lisp/ utilisé, entre autres, par les éditeurs /Emacs/ et /XEmacs/. + +* Listes +** Backquote ` +L'usage de /backquote/ avec une liste permet : + * La convertir en chaine de caractères, +#+BEGIN_SRC emacs-lisp :results verbatim +`(a list of ,(+ 2 3) elements) +#+END_SRC +#+RESULTS: +: (a list of 5 elements) + + * L'évaluation sélective d'éléments, +#+BEGIN_SRC emacs-lisp :results verbatim +(setq some-list '(2 3)) +#+END_SRC +#+RESULTS: +: (2 3) + + * La fusion de listes +#+BEGIN_SRC emacs-lisp :results verbatim +`(1 ,@some-list 4 ,@some-list) +#+END_SRC +#+RESULTS: +: (1 2 3 4 2 3) + +* Références + * [[https://emacstil.com/til/2022/01/05/elisp-what-does-backquote-do/][Elisp: What does backquote ` do ?]] + * [[https://www.gnu.org/software/emacs/manual/html_node/eintr/][An Introduction to Programming in Emacs Lisp]] diff --git a/20220327102701-lvm.org b/20220327102701-lvm.org new file mode 100644 index 0000000..0133e03 --- /dev/null +++ b/20220327102701-lvm.org @@ -0,0 +1,31 @@ +:PROPERTIES: +:ID: f202b810-0fba-4c90-bc4c-f8cbc001fe88 +:mtime: 20220327105541 +:ctime: 20220327102701 +:END: +#+title: LVM + +* Introduction +La gestion par volumes logiques (/Logical Volume Management/) est : + * Une méthode, + * Un logiciel de gestion de l'utilisation des espaces de stockage d'un ordinateur. + +Il permet de gérer de manière souple les espaces de stockage en ligne dans les systèmes d'exploitation de type UNIX. + +#+DOWNLOADED: https://upload.wikimedia.org/wikipedia/commons/4/42/LVM1.jpg @ 2022-03-27 10:39:39 +[[file:Introduction/LVM1_2022-03-27_10-39-39.jpg]] + +** Le volume physique (PV) +Il s'agit d'un disque ou d'une partition, un espace de stockage réel (block device) géré par LVM. +** Groupe de volumes (VG) +Il s'agit d'un ensemble de volumes physiques (au moins 1 PV par VG).LVM requiert au moins 1 VG. Mettre plusieurs disques +dans un même VG peut permettre « d'étaler » un système de fichiers sur plusieurs disques(//home/ sur 2 disques par +exemple, dangereux en cas de perte d'un disque et moins performant que le RAID-0). +** Volume logique (LV) +Un LV est un espace « quelque part dans un groupe de volume » où l'on peut mettre un système de fichiers (remplaçant des +partitions). + +* Références + * [[https://fr.wikipedia.org/wiki/Gestion_par_volumes_logiques][Wikipedia]] + * [[https://doc.ubuntu-fr.org/lvm][Ubuntu]] + * [[https://gist.github.com/kzap/7e5994dda6c01b19760a][kzap/create-lvm.sh]] diff --git a/20220327105648-bash.org b/20220327105648-bash.org new file mode 100644 index 0000000..6819bfa --- /dev/null +++ b/20220327105648-bash.org @@ -0,0 +1,13 @@ +:PROPERTIES: +:ID: a877b82e-4925-41de-8903-8109dd98e773 +:mtime: 20220327105718 +:ctime: 20220327105648 +:END: +#+title: Bash + +* Introduction + +** Lock: [[id:7052d327-0b67-40f6-a581-46df621cddb9][verrou-bash]] + +* Références + diff --git a/20220416105003-obtenir_la_cle_d_un_dictionnaire_pour_laquelle_la_valeur_est_la_plus_petite.org b/20220416105003-obtenir_la_cle_d_un_dictionnaire_pour_laquelle_la_valeur_est_la_plus_petite.org new file mode 100644 index 0000000..9e3b2c3 --- /dev/null +++ b/20220416105003-obtenir_la_cle_d_un_dictionnaire_pour_laquelle_la_valeur_est_la_plus_petite.org @@ -0,0 +1,79 @@ +:PROPERTIES: +:ID: e9adeaaa-701a-4473-a30b-94f1e744c6d1 +:mtime: 20220416112424 +:ctime: 20220416105003 +:END: +#+title: Obtenir la clé d'un dictionnaire pour laquelle la valeur est la plus petite + +* Howto +#+BEGIN_SRC python :results output +from random import randint +from timethese import cmpthese, pprint_cmp + +data = {randint(0, 1000): randint(0, 1000) for i in range(100)} + +def convert_to_lists(): + v=list(data.values()) + k=list(data.keys()) + return k[v.index(min(v))] + +def dict_comprehension(): + d3={v:k for k,v in data.items()} + return d3[min(d3)] + +def filter_and_lambda(): + return list(filter(lambda t: t[1]==min(data.values()), data.items()))[0][0] + +def filter_and_lambda2(): + m=min(data.values()) + return list(filter(lambda t: t[1]==m, data.items()))[0][0] + +def list_comprehension(): + return [k for k,v in data.items() if v==min(data.values())][0] + +def list_comprehension2(): + m=min(data.values()) + return [k for k,v in data.items() if v==m][0] + +def using_min(): + return min(data, key=data.get) + +def convert_to_lists_shortened(): + v=list(data.values()) + return list(data.keys())[v.index(min(v))] + +def using_min_and_lambda(): + return min(data, key=lambda k: data[k]) + +tl = [convert_to_lists, + dict_comprehension, + filter_and_lambda, + filter_and_lambda2, + list_comprehension, + list_comprehension2, + using_min, + convert_to_lists_shortened, + using_min_and_lambda] + +print(pprint_cmp(cmpthese(1000, tl))) + +#+END_SRC + +#+RESULTS: +#+begin_example + Rate 0.convert_to_lists 7.convert_to_lists_shortened 6.using_min 5.list_comprehension2 1.dict_comprehension 8.using_min_and_lambda 3.filter_and_lambda2 4.list_comprehension 2.filter_and_lambda + 0.convert_to_lists 424342/s . 1% 52% 105% 178% 216% 312% 5414% 5745% +7.convert_to_lists_shortened 419268/s -1% . 50% 103% 175% 213% 307% 5348% 5675% + 6.using_min 280002/s -34% -33% . 36% 84% 109% 172% 3538% 3757% + 5.list_comprehension2 206598/s -51% -51% -26% . 36% 54% 101% 2585% 2746% + 1.dict_comprehension 152444/s -64% -64% -46% -26% . 14% 48% 1881% 2000% + 8.using_min_and_lambda 134115/s -68% -68% -52% -35% -12% . 30% 1643% 1747% + 3.filter_and_lambda2 103036/s -76% -75% -63% -50% -32% -23% . 1239% 1319% + 4.list_comprehension 7696/s -98% -98% -97% -96% -95% -94% -93% . 6% + 2.filter_and_lambda 7260/s -98% -98% -97% -96% -95% -95% -93% -6% . +#+end_example + +* Références + * [[https://docs.python.org/3/library/functions.html#min][Python]] + * [[https://blog.finxter.com/how-to-get-the-key-with-minimum-value-in-a-python-dictionary/][Finxter]] + * [[https://stackoverflow.com/questions/268272/getting-key-with-maximum-value-in-dictionary][Stackoverflow]] diff --git a/20220416112952-timethese.org b/20220416112952-timethese.org new file mode 100644 index 0000000..cddf8eb --- /dev/null +++ b/20220416112952-timethese.org @@ -0,0 +1,96 @@ +:PROPERTIES: +:ID: 4e351659-8a96-44af-bacb-f548ea35913e +:mtime: 20220416113532 +:ctime: 20220416112952 +:END: +#+title: timethese + +* Introduction + * Package permettant de mesurer les temps nécessaires à l'exécution de plusieurs méthodes et de les comparer. + * Approche en 3 étapes : + * Définition des méthodes à benchmarker, + * Préparation et exécution des tests (usage de la classe /cmpthese/), + * Formattage des résultas (pretty print). + +* Installation +#+BEGIN_SRC shell +pip install timethese +#+END_SRC + +* Exemple +#+BEGIN_SRC python :results output +from timethese import cmpthese, pprint_cmp, timethese + +xs = range(10) + + +# 1. DEFINE FUNCTIONS + +def map_hex(): + list(map(hex, xs)) + + +def list_compr_hex(): + list([hex(x) for x in xs]) + + +def map_lambda(): + list(map(lambda x: x + 2, xs)) + + +def map_lambda_fn(): + fn = lambda x: x + 2 + list(map(fn, xs)) + + +def list_compr_nofn(): + list([x + 2 for x in xs]) + + +# 2. FEED THE FUNCTIONS TO CMPTHESE + +# AS DICT: + +cmp_res_dict = cmpthese( + 10000, + { + "map_hex": map_hex, + "list_compr_hex": list_compr_hex, + "map_lambda": map_lambda, + "map_lambda_fn": map_lambda_fn, + "list_compr_nofn": list_compr_nofn, + }, + repeat=3, +) + + +# OR AS LIST: + +cmp_res_list = cmpthese( + 10000, [map_hex, list_compr_hex, map_lambda, map_lambda_fn, list_compr_nofn,], repeat=3, +) + +# 3. PRETTY PRINT THE RESULTS + +print(pprint_cmp(cmp_res_dict)) +print(pprint_cmp(cmp_res_list)) +#+END_SRC + +#+RESULTS: +#+begin_example + Rate list_compr_nofn map_hex map_lambda map_lambda_fn list_compr_hex +list_compr_nofn 1889389/s . 21% 42% 45% 68% + map_hex 1565635/s -17% . 18% 21% 40% + map_lambda 1328914/s -30% -15% . 2% 18% + map_lambda_fn 1298638/s -31% -17% -2% . 16% + list_compr_hex 1121963/s -41% -28% -16% -14% . + Rate 4.list_compr_nofn 0.map_hex 3.map_lambda_fn 2.map_lambda 1.list_compr_hex +4.list_compr_nofn 1836243/s . 17% 37% 38% 73% + 0.map_hex 1568271/s -15% . 17% 18% 47% + 3.map_lambda_fn 1336453/s -27% -15% . 0% 26% + 2.map_lambda 1334377/s -27% -15% -0% . 25% + 1.list_compr_hex 1063984/s -42% -32% -20% -20% . +#+end_example + +* Références + * [[https://github.com/jwbargsten/python-timethese][Github]] diff --git a/20220429091956-ci_cd.org b/20220429091956-ci_cd.org new file mode 100644 index 0000000..022d7e9 --- /dev/null +++ b/20220429091956-ci_cd.org @@ -0,0 +1,32 @@ +:PROPERTIES: +:ID: 607219f8-675b-4244-a3ce-b399bf210366 +:mtime: 20220429094502 +:ctime: 20220429091956 +:END: +#+title: CI/CD + +* Introduction + * Comble le fossé entre les activités et les équipes de développement et d'exploitation en imposant : + * L'automatisation de la création, des tests (CI), + * Du déploiement des applications (CD). + * Les pratiques DevOps modernes impliquent le CI/CD et la surveillance continue des applications logicielles tout au long de leur cycle de vie. + +* CI (Continuous Integration) + * Première étape du pipeline, + * Intégration des modifications apportées au code par différents développeurs dans un dépôt (ie: Git), + * Exécution de tests unitaires et d'intégration, + * Couplé au process de développement [[id:6da0b985-e6f4-4454-bb6a-e941b722365b][Test driven development]]. + +* CD (Continuous Delivery/Continuous Deployment) +** Continuous Delivery + * Livraison manuelle du /build/ (dépose dans un app store par exemple), + * Quand il est nécessaire de réaliser des tests manuels (/acceptance tests/) après le CI. +** Continuous Deployment + * Livraison automatique du code sortant du CI (résultats de test satisfaisants). + * Permet une plus grande réactivité: livraison dès que le code est prêt (plus de livraison formelle), + * Ajoute une pression sur l'équipe de développement, + * Nécessite que les tests effectués en CI soient suffisants. + +* Références + * [[https://fr.wikipedia.org/wiki/CI/CD][Wikipedia]] + * [[https://blog.devgenius.io/a-simple-definition-of-the-ci-cd-pipeline-8a48169be938?source=rss----4e2c1156667e---4][Definition of the CI/CD pipeline - Medium]] diff --git a/20220429093533-test_driven_development.org b/20220429093533-test_driven_development.org new file mode 100644 index 0000000..861826e --- /dev/null +++ b/20220429093533-test_driven_development.org @@ -0,0 +1,54 @@ +:PROPERTIES: +:ID: 6da0b985-e6f4-4454-bb6a-e941b722365b +:mtime: 20220429104331 +:ctime: 20220429093533 +:END: +#+title: Test driven development + +* Introduction + * Initialement, cela consistait à écriture les tests avant de coder (/test-first design/), + * Méthode de dev. logiciel consistant à concevoir un logiciel step by step en : + * Ecrivant les tests avant la feature, + * Remaniant le code continuellement. + +* Les 3 lois du Test Driven Development + +| N° | Lois | | +|----+---------------------------------------------------------------------------------------------------------------------+---| +| 1 | Il faut écrire un test qui échoue avant d’écrire le code de production correspondant. | | +| 2 | Il faut écrire une seule assertion à la fois, qui fait échouer le test ou qui échoue à la compilation. | | +| 3 | Il faut écrire le minimum de code de production pour que l'assertion du test actuellement en échec soit satisfaite. | | + +* Processus cyclique de développement + +#+DOWNLOADED: https://upload.wikimedia.org/wikipedia/commons/0/0e/Cycle-global-tdd.png @ 2022-04-29 10:17:43 +#+ATTR_ORG: :width 800 +[[file:Processus cyclique de développement/Cycle-global-tdd_2022-04-29_10-17-43.png]] + +** Intérêts + * Permet préciser le besoin, puis de spécifier le comportement souhaité, avant chaque étape de codage. Le logiciel + produit répond avec justesse au besoin et est conçu pour le faire avec une complexité minimale => meilleures + conception et testabilité, et logiciel plus fiable et de meilleure qualité. + * Chaque test correspond à des modifications du code minimales : un test unique permet de faire un lien évident entre + une régression et sa cause en cas d'échec. + * Le rejeu des tests suite à la modification du code permet d'envisager avec sérénité n'importe quelle modification du + code (transformation - modification qui affecte le comportement - ou d'un remaniement - modification qui n'altère pas le comportement). + * Le remaniement régulier permet le réalignement de la conception du code avec les besoins connus, les tests permettant + de garantir l'absence de régressions. + * Les tests permettent aussi de documenter le comportement du logiciel. + +Le TDD fait gagner en productivité de plusieurs façons, il permet de : + * Eviter des modifications de code sans lien avec le but recherché (focalisation sur la satisfaction d'un besoin précis + en conservant le cap du problème d'ensemble), + * Eviter les accidents de parcours, où des tests échouent sans qu'on puisse identifier le changement à l'origine, + * Maîtriser le coût des évolutions logicielles au fil du temps, grâce à une conception du code perméable au changement, + * S'approprier plus facilement n'importe quelle partie du code en vue de le faire évoluer (chaque test ajouté explique et documente le comportement du logiciel en traduisant l'intention des auteurs), + * Livrer une version d'un logiciel avec un haut niveau de confiance dans la qualité des livrables (couverture et pertinence des tests à sa construction). + +** Programmation binomiale +Différents usages: + * Une personne écrit les tests, puis le code et une seconde supervise. Les rôles sont inversés régulièrement, + * Une personne rédige les tests lorsque la seconde le code. + +* Références + * [[https://fr.wikipedia.org/wiki/Test_driven_development][Wikipedia]] diff --git a/20220430122532-list_vs_tuple.org b/20220430122532-list_vs_tuple.org new file mode 100644 index 0000000..b66e2ef --- /dev/null +++ b/20220430122532-list_vs_tuple.org @@ -0,0 +1,128 @@ +:PROPERTIES: +:ID: b9f392bd-bd45-4e9e-94ec-b19caedff86f +:mtime: 20220501143116 +:ctime: 20220430122532 +:END: +#+title: List vs tuple +#+filetags: :Optimisation:Python: + +* Points communs +/list/ et /tuple/ sont des données de type /sequence/ : + * L'ordre d'insertion des données est conservé (/ordered sets/), + * Support des fonctions de concaténation, répétition, indexation et /slicing/, + * Stockage de données hétérogèes (généralement homogènes)/ + +* Différences + * Syntaxe ([] pour /list/, () pour /tuple/), + * La /list/ est /mutable/, le /tuple/ /immutable/ (ne peut être modifié une fois créé), + +** Empreinte RAM + * Le /tuple/ requiert moins de RAM : + +#+BEGIN_SRC python :results output +from sys import getsizeof +from typing import List, Tuple + + +def print_list_tuple_comparison(list_: List, tuple_: Tuple) -> None: + list_size = getsizeof(list_) + tuple_size = getsizeof(tuple_) + diff = list_size - tuple_size + print(f"Number of elements: {len(list_)}, list size: {list_size:<3}, tuple size: {tuple_size:<3}, diff: {diff} bytes ({diff/list_size*100:.2f}%)") + + +def print_list_tuple_ram_footprint(elements_nb: int) -> None: + list_ = [] + tuple_ = () + + print_list_tuple_comparison(list_, tuple_) + + for i in range(1, elements_nb + 1): + list_.append(i) + tuple_ = tuple(range(1, i + 1)) + + print_list_tuple_comparison(list_, tuple_) + + +print_list_tuple_ram_footprint(10) +#+END_SRC +#+RESULTS: +: Number of elements: 0, list size: 56 , tuple size: 40 , diff: 16 bytes (28.57%) +: Number of elements: 1, list size: 88 , tuple size: 48 , diff: 40 bytes (45.45%) +: Number of elements: 2, list size: 88 , tuple size: 56 , diff: 32 bytes (36.36%) +: Number of elements: 3, list size: 88 , tuple size: 64 , diff: 24 bytes (27.27%) +: Number of elements: 4, list size: 88 , tuple size: 72 , diff: 16 bytes (18.18%) +: Number of elements: 5, list size: 120, tuple size: 80 , diff: 40 bytes (33.33%) +: Number of elements: 6, list size: 120, tuple size: 88 , diff: 32 bytes (26.67%) +: Number of elements: 7, list size: 120, tuple size: 96 , diff: 24 bytes (20.00%) +: Number of elements: 8, list size: 120, tuple size: 104, diff: 16 bytes (13.33%) +: Number of elements: 9, list size: 184, tuple size: 112, diff: 72 bytes (39.13%) +: Number of elements: 10, list size: 184, tuple size: 120, diff: 64 bytes (34.78%) + +** Temps de création +Le /tuple/ est plus rapide à créé que la /list/: +#+BEGIN_SRC python :results output +from timeit import timeit + +tuple_elapsed_time = timeit("(1,2,3,4,5,6,7,8,9,10)", number=10_000_000) +list_elapsed_time = timeit("[1,2,3,4,5,6,7,8,9,10]", number=10_000_000) + +print(f'Time needed to create a list of 10 ints 10_000_000 times: {list_elapsed_time}') +print(f'Time needed to create a tuple of 10 ints 10_000_000 times: {tuple_elapsed_time} ({(list_elapsed_time - tuple_elapsed_time) / list_elapsed_time * 100:.2f}% faster)') +#+END_SRC + +#+RESULTS: +: Time needed to create a list of 10 ints 10_000_000 times: 0.6825288010004442 +: Time needed to create a tuple of 10 ints 10_000_000 times: 0.0716032920172438 (89.51% faster) + +*** Création d'une liste et accès à un de ses membres +#+BEGIN_SRC python :results output +from dis import dis + +def my_list(): + x = [10, 20, 30, 'abc'] + y = x[0] + +dis(my_list) +#+END_SRC + +#+RESULTS: +: 4 0 LOAD_CONST 1 (10) +: 2 LOAD_CONST 2 (20) +: 4 LOAD_CONST 3 (30) +: 6 LOAD_CONST 4 ('abc') +: 8 BUILD_LIST 4 +: 10 STORE_FAST 0 (x) +: +: 5 12 LOAD_FAST 0 (x) +: 14 LOAD_CONST 5 (0) +: 16 BINARY_SUBSCR +: 18 STORE_FAST 1 (y) +: 20 LOAD_CONST 0 (None) +: 22 RETURN_VALUE + +*** Création d'un tuple et accès à un de ses membres +#+BEGIN_SRC python :results output +from dis import dis + +def my_tuple(): + x = (10, 20, 30, 'abc') + y = x[0] + +dis(my_tuple) +#+END_SRC +#+RESULTS: +: 4 0 LOAD_CONST 1 ((10, 20, 30, 'abc')) +: 2 STORE_FAST 0 (x) +: +: 5 4 LOAD_FAST 0 (x) +: 6 LOAD_CONST 2 (0) +: 8 BINARY_SUBSCR +: 10 STORE_FAST 1 (y) +: 12 LOAD_CONST 0 (None) +: 14 RETURN_VALUE + +La création d'un /tuple/ est plus rapide du fait de la [[id:26e1fdfb-1f8e-4c62-a08f-468a56ab03c8][Python peephole optimization]]. + +* Références + * [[https://pub.towardsai.net/python-list-vs-tuple-an-in-depth-comparison-42c59348d8a8][Python List Vs. Tuple: An In-Depth Comparison - Medium]] diff --git a/20220501130626-python_peephole_optimization.org b/20220501130626-python_peephole_optimization.org new file mode 100644 index 0000000..964784c --- /dev/null +++ b/20220501130626-python_peephole_optimization.org @@ -0,0 +1,78 @@ +:PROPERTIES: +:ID: 26e1fdfb-1f8e-4c62-a08f-468a56ab03c8 +:mtime: 20220501153548 +:ctime: 20220501130626 +:END: +#+title: Peephole optimization + +* Introduction +Lorsque du code python est exécuté, celui-ci est compilé en /bytecode/ et enregistré dans des fichiers .pyc stockés dans +les répertoires ~__pycache__~. Ces fichiers .pyc contiennent une version *optimisée* et plus *rapide* du code. + +** L'objet /code/ +L'objet compilé /code/: + * Est accessible depuis l'attribut ~__code__~ des fonctions, + * Contient le *bytecode* et d'autres informations nécessaires à *CPython* pour son exécution, notamment : + * ~co_consts~ : un tuple regroupant les constantes présentes dans la fonction, + * ~co_varnames~ : un tuple regroupant le nom des variables locales utilisées par la fonction, + * ~co_names~ : un tuple regroupant les noms non-locaux référencés dans le corps de la fonction. + +* L'optimisation /Peephole/ +Durant la phase de *transcription* en bytecode, certaines données telles que les expressions numériques, /strings/ et /tuples/ sont optimisées et stockées dans des instruction *bytecode*. + +** Les expressions constantes +*** Les calculs numériques +Les *expressions constantes* telles que ~a = 30 * 8 * 70~ sont optimisées. +*** Les chaines de caractères et tuples +Les *strings* dont la *longueur <= 4096* et les *tuples* dont la *longueur <= 256* sont optimisées, les autres, non. +*** Les /membership tests/ +Les *membership tests* (opérateur ~in~ et ~not in~) permettent de tester la présence d'une valeur dans des +/sequences/. Durant la phase de transcription, Python convertit les objets *mutables* (/list/ et /set/) en leur version *non mutables*: + * Les *lists* en *tuples*, + * Les *sets* en *frozen sets* + + #+BEGIN_SRC python :results output + def toto(): + a = 30 * 8 * 7 + b = "TDS" * 3 + c = "T" * 4097 + d = (1, 2) * 5 + e = (10, ) * 257 + f = [101, 102] * 2 + print("Hello TDS !!!") + + print(f'toto function constants: {toto.__code__.co_consts}') + print(f'toto fonction local variable names: {toto.__code__.co_varnames}') + print(f'toto fonction non-local names: {toto.__code__.co_names}') + #+END_SRC + + #+RESULTS: + : toto function constants: (None, 1680, 'TDSTDSTDS', 'T', 4097, (1, 2, 1, 2, 1, 2, 1, 2, 1, 2), (10,), 257, 101, 102, 2, 'Hello TDS !!!') + : toto fonction local variable names: ('a', 'b', 'c', 'd', 'e', 'f') + : toto fonction non-local names: ('print',) + +Commentaires : + * ~30 * 8 * 70~ est une *expression constante* et a été évaluée à ~16800~ par le compilateur, + * ~“TDS” * 3~ est aussi une *expression constante* et sa *longueur est <= 4096*. Elle a été évaluée à + ~TDSTDSTDS~ par le compilateur, + * ~“T” * 4097~, de *longueur > 4096*, n'est pas optimisée, + * ~(1, 2) * 5~ est une séquence dont la *longueur est <= 256* (10). Elle a été évaluée et stockée comme le tuple ~(1,2,1,2,1,2,1,2,1,2)~. + * ~(10,) * 257~ de *longueur > 256*, n'est pas optimisée, + * ~[101, 102] * 2~ est une /list/ (objet *mutable*) et n'a pas été optimisée. + +#+BEGIN_SRC python :results output +def toto(): + for a in [10, 20, 30]: + pass + +def titi(): + for a in {40, 50, 60}: + pass + +print(f'toto function constants: {toto.__code__.co_consts}') +print(f'titi function constants: {titi.__code__.co_consts}') +#+END_SRC + +#+RESULTS: +: toto function constants: (None, (10, 20, 30)) +: titi function constants: (None, frozenset({40, 50, 60})) diff --git a/20220501153651-interning_optimization.org b/20220501153651-interning_optimization.org new file mode 100644 index 0000000..15a7ecd --- /dev/null +++ b/20220501153651-interning_optimization.org @@ -0,0 +1,64 @@ +:PROPERTIES: +:ID: c0d562c7-9d2f-4f35-b7b1-f6b6ca5273f9 +:mtime: 20220501162112 +:ctime: 20220501153651 +:END: +#+title: Interning optimization + +* Introduction + * Concerne /CPython/, + * Consiste en la *réutilisation* d'objet plutôt que d'en recrééer. + +* Integer interning + * Au démarrage, /CPython/ pré-charge en mémoire les entiers de *-5 à 256*, + * A chaque fois qu'un entier compris dans cet intervalle est utilisé, celui-ci fait référence à un entier pré-chargé. +#+BEGIN_SRC python :results output +a = 100 +b = 100 + +print(f'Memory address of a: {id(a)}') +print(f'Memory address of b: {id(b)}') +print(f'a is b: {a is b}') +#+END_SRC + +#+RESULTS: +: Memory address of a: 9792128 +: Memory address of b: 9792128 +: a is b: True + +* String interning + * Au démarrage, /CPython/ pré-charge en mémoire les chaines de caractères présentes dans le code et de longueur *<= + 4096*, + * Il est possible de forcer le pré-chargement en mémoire d'une chaine à l'aide de ~sys.intern~ + +#+BEGIN_SRC python :results output +from random import choice +from string import printable +from sys import intern + +a = "Data" +b = "Data" + +print(f'Memory address of a: {id(a)}') +print(f'Memory address of b: {id(b)}') +print(f'a is b: {a is b}') + +c = intern(''.join(choice(printable) for _ in range(4097))) +d = c + +print(f'Memory address of c: {id(c)}') +print(f'Memory address of d: {id(d)}') +print(f'c is d: {c is d}') +#+END_SRC + +#+RESULTS: +: Memory address of a: 139629084500336 +: Memory address of b: 139629084500336 +: a is b: True +: Memory address of c: 28459264 +: Memory address of d: 28459264 +: c is d: True + +* Références + * [[https://pythonsimplified.com/optimization-in-python-interning/][Optimization in Python — Interning - pythonsimplified]] + * [[https://stackoverflow.com/questions/55347581/why-does-the-is-operator-behave-differently-in-a-script-vs-the-repl][Why does the `is` operator behave differently in a script vs the REPL? - Stack-Overflow]] diff --git a/20220501191904-git.org b/20220501191904-git.org new file mode 100644 index 0000000..786fb46 --- /dev/null +++ b/20220501191904-git.org @@ -0,0 +1,91 @@ +:PROPERTIES: +:ID: e93719b3-088d-4fe7-9ef8-fc9a4fd84827 +:mtime: 20220524205131 +:ctime: 20220501191904 +:END: +#+title: Git + +* Introduction + * Outil de gestion de versions, + * Créé par L. TORVALDS, + * Fonctionnement décentralisé. + +* Commandes +| Commande | Action | +|----------------+--------------------------------------------------------------------------------------------------------------------------------------------------| +| ~git init~ | Crée un nouveau dépôt | +| ~git clone​~ | Clone un dépôt distant | +| ~git add​~ | Ajoute de nouveaux /blobs/ dans la base des objets pour chaque fichier modifié depuis le dernier commit. Les objets précédents restent inchangés | +| ~git commit~ | Intègre la somme de contrôle SHA-1 d'un /tree/ et les sommes de contrôle des /commits/ parents pour créer un nouvel /commit/ | +| ~git branch~ | ​Liste les branches | +| ~git merge~ | ​Fusionne une branche dans une autre | +| ~git rebase~ | ​Déplace les /commits/ de la branche courante devant les nouveaux /commits/ d’une autre branche | +| ~git log​~ | Affiche la liste des commits effectués sur une branche | +| ~git push~ | Publie les nouvelles révisions sur le remote | +| ~git pull~ | ​Récupère les dernières modifications distantes du projet (depuis le Remote) et les fusionne dans la branche courante | +| ~git stash~ | ​Stocke de côté un état non commité afin d’effectuer d’autres tâches | +| ~git checkout~ | ​Annule les modifications effectuées, déplacement sur une référence (branche, hash) | +| ~git switch~ | ​Changement de branche | +| ~git remote~ | ​Gestion des remotes | + +Pour positionner ~HEAD~ : + * Au commit père : ~git checkout HEAD^~ ou ~git checkout HEAD~1~, + * En cas de plusieurs pères : ~git checkout HEAD^2~ pour positionner au second père, + * Au commit grand-père : ~git checkout HEAD^^~ ou ~git checkout HEAD~2~ + +~HEAD~ est /detached/ lorsqu'un /checkout/ est effectué sur un /commit/ et non pas une /branch/. + +Pour forcer le déplacement d'une branche : ~git branch -f ~. + +Pour retourner en arrière : + * Branche locale : ~git reset ~ (position où nous souhaitons être). + * Différentes options : + * ~--soft~ : le commit sera supprimé mais les changements seront conservés et /stashed/, + * ~--mixed~ (par défaut) : le commit sera supprimé mais les changements seront conservés et /unstashed/, + * ~--hard ~ pour les changements à ne pas conserver. + * Branche distante (/remote/) : ~git revert ~ : + * Création d'un nouveau /commit/ qui inversera les modifications apportées par le ~~, + * Conservation de l'historique, + * Pour supprimer plusieurs /commit/ consécutifs : ~git revert ..~ (~~~ exclu, + ~~ inclus). + +Pour copier une série de commits après l'emplacement actuel (/HEAD/) : ~git cherry-pick <...>~ + +*Rebase* interractif (~git rebase -i~) permet de : + * Réarranger les /commits/, + * Omettre certains /commits/ (/pick/), + * Ecraser des /commits/. + +Pour modifier le dernier commit : ~git commit --amend~. + +Pour décrire (/describe/) les changements entre /HEAD/ et le tag le plus récent : ~git describe~ (~git describe ~ sinon). + +Pour mettre à jour une branche distante : ~git fetch~ : + * Télécharge les /commits/ que le dépôt distant possède mais qui ne sont pas dans le notre, puis, + * Met à jour nos branches distantes (par exemple, origin/main), + * Ne met par à jour nos branches locales (par exemple, main), + +Pour rapatrier (/fetch/) les branches distantes et les fusionner (/merge/) : ~git pull~. + +Pour rapatrier (/fetch/) les branches distantes et /rebase/ : ~git pull --rebase~ + +Pour créer une branche afin que celle-ci suive une distante : + * Création d'une nouvelle branche : ~git checkout -b ;git +pull~, + * La branche existe déjà : ~git branch -u ~ + +* Outils tiers +** Visualisation +*** [[https://github.com/dandavison/delta][Delta]] +Affichage de ~git diff~. +*** [[https://github.com/darrenburns/dunk][Dunk]] +Affichage de ~git diff~. + +* Apprentissage +** [[https://github.com/benthayer/git-gud][Git-gud]] +Jeu en ligne de commande permettant d'apprendre l'usage de Git. +** [[https://learngitbranching.js.org/][learngitbranching.js.org]] +Site permettant l'apprentissage de l'usage de Git. + +* Références + * diff --git a/20220514130004-trouver_le_premier_element_d_un_iterable_satisfaisant_avec_any.org b/20220514130004-trouver_le_premier_element_d_un_iterable_satisfaisant_avec_any.org new file mode 100644 index 0000000..4d8ba00 --- /dev/null +++ b/20220514130004-trouver_le_premier_element_d_un_iterable_satisfaisant_avec_any.org @@ -0,0 +1,52 @@ +:PROPERTIES: +:ID: 20753ab5-70c3-4c8f-b261-56832cd5392c +:mtime: 20220514132808 +:ctime: 20220514130004 +:END: +#+title: Trouver le premier élément d'un iterable satisfaisant avec any + +* Introduction + +Le but est de remplacer le code suivant par une version plus courte à l'aide de la fonction /any/: + +#+BEGIN_SRC python :results output +from timeit import timeit +from typing import List, Optional + + +def get_first_odd(items: List[int]) -> Optional[int]: + for item in items: + if item % 2: + return item + return None + +count = 1_000_000 +elapsed = timeit(lambda: get_first_odd([14, 16, 18, 20, 35, 41, 100]), number=count) +print(f'Elapsed time to find the first odd {count} times: {elapsed}.') +#+END_SRC + +#+RESULTS: +: Elapsed time to find the first odd 1000000 times: 0.31392509397119284. + +#+BEGIN_SRC python :results output +from timeit import timeit +from typing import List, Optional + +def get_first_odd(items: List[int]) -> Optional[int]: + is_odd = lambda x: x % 2 + if any(is_odd(found := item) for item in items): + return found + return None + +count = 1_000_000 +elapsed = timeit(lambda: get_first_odd([14, 16, 18, 20, 35, 41, 100]), number=count) +print(f'Elapsed time to find the first odd {count} times: {elapsed}.') +#+END_SRC + +#+RESULTS: +: Elapsed time to find the first odd 1000000 times: 0.9898421700345352. + + +* Références + * [[https://docs.python.org/fr/3/library/functions.html#any][Python Built-in Functions]] + * [[https://mathspp.com/blog/pydonts/boolean-short-circuiting][Boolean short-circuiting - Pydon't]] diff --git a/20220514133841-modifier_le_style_du_texte_affiche_sur_une_console.org b/20220514133841-modifier_le_style_du_texte_affiche_sur_une_console.org new file mode 100644 index 0000000..5ab434a --- /dev/null +++ b/20220514133841-modifier_le_style_du_texte_affiche_sur_une_console.org @@ -0,0 +1,45 @@ +:PROPERTIES: +:ID: fa7e728b-dd2a-4d30-8f4b-e87813dc5f33 +:mtime: 20220514155841 +:ctime: 20220514133841 +:END: +#+title: Modifier le style du texte affiché sur une console + +* Introduction +Le but est de modifier l'apparence de chaines de caractéres à afficher sur la console (/stdout/ ou /stderr/). + +* Howto +** Utilisation des [[https://stringfixer.com/fr/ANSI_escape_sequence][séquences d'échappement ANSI]] +#+BEGIN_SRC python :results output +BOLD_ESC = '\x1B[1m' +UNDERLINED_ESC = '\x1B[4m' +BOLD_AND_UNDERLINED_ESC = '\x1B[1;4m' +CLOSING_ESC = '\x1B[0m' + +def bold_and_uderline(text: str) -> str: + return f'{BOLD_AND_UNDERLINED_ESC}{text}{CLOSING_ESC}' + +text = "dummy text" +print(f'{text} -> {bold_and_uderline(text)}') +#+END_SRC + +#+RESULTS: +: dummy text -> dummy text + +** Utilisation du package [[https://github.com/hugovk/termcolor][termcolor]] +#+BEGIN_SRC python :results output +# Install: pip install termcolor +from termcolor import colored + +def bold_and_uderline(text: str) -> str: + return colored(text, attrs=['bold', 'underline']) + +text = "dummy text" +print(f'{text} -> {bold_and_uderline(text)}') +#+END_SRC + +#+RESULTS: +: dummy text -> dummy text + +* Références + * [[https://github.com/hugovk/termcolor][termcolor - Github]] diff --git a/20220516125736-difference_entre_les_operateurs_et.org b/20220516125736-difference_entre_les_operateurs_et.org new file mode 100644 index 0000000..d02cfdc --- /dev/null +++ b/20220516125736-difference_entre_les_operateurs_et.org @@ -0,0 +1,38 @@ +:PROPERTIES: +:ID: 24408701-21d8-4f4e-aed9-c58746df2244 +:mtime: 20220516142825 +:ctime: 20220516125736 +:END: +#+title: Différence entre les opérateurs + et += + +* Introduction +Comment expliquer la différence de comportement entre les opérateurs *+* et *+=* : + +#+BEGIN_SRC python :results output +a1 = a2 = [1, 2] +a1 += [3] # Uses __iadd__, modifies a1 in-place +print(f"When += is used: {a1=}, {a2=}") + +a1 = a2 = [1, 2] +a1.extend([3]) # Uses __add__, creates new list, assigns it to b1 +print(f"When extend is used: {a1=}, {a2=}") + +a1 = a2 = [1, 2] +a1 = a1 + [3] # Uses __add__, creates new list, assigns it to b1 +print(f"When + is used: {a1=}, {a2=}") +#+END_SRC + +#+RESULTS: +: When += is used: a1=[1, 2, 3], a2=[1, 2, 3] +: When extend is used: a1=[1, 2, 3], a2=[1, 2, 3] +: When + is used: a1=[1, 2, 3], a2=[1, 2] + +* Explications +L'opérateur *+=* appelle la méthode dunder *__iadd__*. Si celle-ci n'existe pas (cas des objets /immutables/), il +appelle *__add__*: + * La méthode *__iadd__* signifie *in-place addition* : cela implique la modification de l'objet /mutable/ (et non la création + d'une nouvelle instance - cf. __add__), + * La méthode *__add__* retourne un nouvel object contenant le résultat de l'opération. + +* Références + * [[https://stackoverflow.com/questions/2347265/why-does-behave-unexpectedly-on-lists/2347423#2347423][Stackoverflow]] diff --git a/20220516145022-type_object.org b/20220516145022-type_object.org new file mode 100644 index 0000000..6d884b7 --- /dev/null +++ b/20220516145022-type_object.org @@ -0,0 +1,47 @@ +:PROPERTIES: +:ID: a32ab138-f9a8-4d61-9c09-97953c5a0a92 +:mtime: 20220516152042 +:ctime: 20220516145022 +:END: +#+title: type == object + + +* Introduction +#+BEGIN_SRC python :results output +from termcolor import colored + +def check_instance(obj, type_): + repr_attrs=['bold'] + ret = isinstance(obj, type_) + print(f"Is {colored(obj.__name__, attrs=repr_attrs)} a instance of {colored(type_.__name__, attrs=repr_attrs)}: {ret}") + return ret + +for obj, type_ in [(type, object), (object, type), (object, object), (type, type)]: + check_instance(obj, type_) +#+END_SRC + +#+RESULTS: +: Is type a instance of object: True +: Is object a instance of type: True +: Is object a instance of object: True +: Is type a instance of type: True + +* Explications + * Tout est objet, ainsi ~isinstance(Anything, object)~ retourne toujours ~True~, + * /type/ est une /metaclass/ permettant la construction de tous les types : int, str et object sont des instances du + type /class/, + * /type/ est le seul objet qui est une instance de lui même. + +#+BEGIN_SRC python :results output +print(type(5)) +print(type(int)) +print(type(type)) +#+END_SRC + +#+RESULTS: +: +: +: + +* Références + * [[https://medium.com/@saint_sdmn/10-hardest-python-questions-98986c8cd309][10 Hardest Python Questions - Medium]] diff --git a/20220516151958-any_et_all_d_un_iterable_vide.org b/20220516151958-any_et_all_d_un_iterable_vide.org new file mode 100644 index 0000000..0be38e7 --- /dev/null +++ b/20220516151958-any_et_all_d_un_iterable_vide.org @@ -0,0 +1,26 @@ +:PROPERTIES: +:ID: 16d16a4b-6a4c-4597-a45f-1a99f2cb9f29 +:mtime: 20220516154116 +:ctime: 20220516151958 +:END: +#+title: Any et all d'un iterable vide + +* Introduction +#+BEGIN_SRC python :results output +print(f"{any([])=}") +print(f"{all([])=}") +#+END_SRC + +#+RESULTS: +: any([])=False +: all([])=True + +* Explications + * La fonction /build-in/ /any/ recherche le premier élément ~True~, et si aucun n'esst trouvé, retourne ~False~ (cas + lorsque une /list/ est vide), sinon ~True~. + * La fonction /build-in/ /all/ recherche le premier élément ~False~, et si aucun n'est trouvé, retourne ~True~, ~False~ sinon. + +* Références + * [[https://medium.com/@saint_sdmn/10-hardest-python-questions-98986c8cd309][10 Hardest Python Questions - Medium]] + + diff --git a/20220516155011-ordre_de_resolution_des_attributs.org b/20220516155011-ordre_de_resolution_des_attributs.org new file mode 100644 index 0000000..4055d5b --- /dev/null +++ b/20220516155011-ordre_de_resolution_des_attributs.org @@ -0,0 +1,31 @@ +:PROPERTIES: +:ID: e9a0cb94-ee68-4dfd-9013-c83ce2a18481 +:mtime: 20220516155419 +:ctime: 20220516155011 +:END: +#+title: Ordre de résolution des attributs + +* Introduction +#+BEGIN_SRC python :results output +class A: + answer = 42 + def __init__(self): + self.answer = 21 + self.__add__ = lambda x, y: x.answer + y + + def __add__(self, y): + return self.answer - y + +print(f"{A() + 5 = }") +#+END_SRC + +#+RESULTS: +: A() + 5 = 16 + +* Explication +Afin de résoudre les noms, Python cherche au niveau de l'instance, puis à celui de la classe et enfin à celui des +classes parentes, sauf pour les /dunder methods/ pour lesquelles Python cherche directement au niveau de la classe. + +* Référence + * [[https://medium.com/@saint_sdmn/10-hardest-python-questions-98986c8cd309][10 Hardest Python Questions - Medium]] + diff --git a/20220516155708-attribut_imag_des_types_numeriques.org b/20220516155708-attribut_imag_des_types_numeriques.org new file mode 100644 index 0000000..a2d994a --- /dev/null +++ b/20220516155708-attribut_imag_des_types_numeriques.org @@ -0,0 +1,28 @@ +:PROPERTIES: +:ID: 279b5e79-6918-432c-85fa-2fbefc06619a +:mtime: 20220516160109 +:ctime: 20220516155708 +:END: +#+title: Attribut imag des types numériques + +* Introduction +#+BEGIN_SRC python :results output +ret = sum([ + el.imag + for el in [ + 0, 5, 10e9, float('inf'), float('nan') + ] +]) +print(f"{ret =}") +#+END_SRC + +#+RESULTS: +: ret =0.0 + +* Explication +Le code précédent ne génère pas d'/AttributeError/ car tous les types numériques (/int/, /real/ et /float/) supportent +les attributs /real/ (partie réelle) et /imag/ (partie imaginaire), /NaN/ et /Infinity/ inclus. + +* Références + * [[https://medium.com/@saint_sdmn/10-hardest-python-questions-98986c8cd309][10 Hardest Python Questions - Medium]] + * [[https://docs.python.org/fr/3/library/numbers.html][Numbers - Classes de base abstraites numériques]] diff --git a/20220516160421-evaluation_tardive.org b/20220516160421-evaluation_tardive.org new file mode 100644 index 0000000..173cdf6 --- /dev/null +++ b/20220516160421-evaluation_tardive.org @@ -0,0 +1,33 @@ +:PROPERTIES: +:ID: 831fbfd2-c668-4099-b2d7-ecae734b9ec4 +:mtime: 20220519083124 +:ctime: 20220516160421 +:END: +#+title: Evaluation tardive + +* Introduction +#+BEGIN_SRC python :results output +class A: + def function(self): + return A() + +a = A() +A = int +print(f"{a.function() = }") +#+END_SRC + +#+RESULTS: +: a.function() = 0 + +* Explications + * Le code contenu dans une fonction n'est exécuté uniquement lorsque celle-ci est invoquée. Ainsi, la référence à la +fonction non déclarée ~A~ par la méthode ~A.function~ ne génère pas d'erreur, + * Durant l'exécution de la méthode ~A.function~, + +However, during the execution Python will bind the name A from +the outer scope, which means that function method will return a newly created int instance. + + +* Références + * [[https://medium.com/@saint_sdmn/10-hardest-python-questions-98986c8cd309][10 Hardest Python Questions - Medium]] + diff --git a/20220519082710-la_methode_new.org b/20220519082710-la_methode_new.org new file mode 100644 index 0000000..9decf61 --- /dev/null +++ b/20220519082710-la_methode_new.org @@ -0,0 +1,62 @@ +:PROPERTIES: +:ID: aaf77222-82d2-492c-bf5f-2dd47659b1be +:mtime: 20220519210820 +:ctime: 20220519082710 +:END: +#+title: La méthode __new__ + +* Synthaxe +#+BEGIN_SRC python +object.__new__(cls, *args, **kwargs) +#+END_SRC +La méthode ~object.__new__~ : + * Crée les instances d'une classe (constructeur), + * Est appelée avant ~object.__init__~, + * Retourne une nouvelle instance ou une référence à une précedente (classes /singleton/). + +* Exemples +** Utilisation classique +#+BEGIN_SRC python :results output +class B: + + def __new__(cls, *args, **kwargs): + print("__new__ running", cls, args, kwargs) + return super().__new__(cls) + + def __init__(self, *args, **kwargs): + print("__init__ running", self, args, kwargs) + return super().__init__(*args, **kwargs) + +for i in range(2): + B() +#+END_SRC +#+RESULTS: +: __new__ running () {} +: __init__ running <__main__.B object at 0x7f78f614c040> () {} +: __new__ running () {} +: __init__ running <__main__.B object at 0x7f78f614c040> () {} + +** Singleton /design pattern/ +#+BEGIN_SRC python :results output +class SingletonClass: + + _instance = None + + def __new__(cls, *args, **kwargs): + print("__new__ running", cls, args, kwargs) + if cls._instance is None: # Checking if an instance of this class exists + cls._instance = super().__new__(cls, *args, **kwargs) # Creating a new instace + return cls._instance # Returing the instance + +for i in range(2): + print(f'{id(SingletonClass())=}') + + #+END_SRC +#+RESULTS: +: __new__ running () {} +: id(SingletonClass())=140699167274272 +: __new__ running () {} +: id(SingletonClass())=140699167274272 + +* Références + * [[https://python.plainenglish.io/advanced-python-classes-objects-and-mro-423bb01521fb][Advanced Python classes objects and MRO - Medium]] diff --git a/20220519090118-la_methode_prepare.org b/20220519090118-la_methode_prepare.org new file mode 100644 index 0000000..5d5b721 --- /dev/null +++ b/20220519090118-la_methode_prepare.org @@ -0,0 +1,59 @@ +:PROPERTIES: +:ID: 0d9a7351-babd-4394-9d95-1d40cb46f615 +:mtime: 20220520230717 +:ctime: 20220519090118 +:END: +#+title: La méthode __prepare__ + +* Synthaxe +#+BEGIN_SRC python +class.__prepare__() +#+END_SRC +La méthode ~class.__prepare__~ : + * Cf. [[https://peps.python.org/pep-3115/][PEP-3115 - Metaclasses in Python 3000]], + * Est utilisé comme /namespace/ local pour tout le code du corps de la + classe (prépare le contenu du dictionnaire /__dict__/ de l'instance). + * Est appelée avant ~class.__new__~, + * Doit retourner un objet /dictionary-like/, + +* Exemples + +#+BEGIN_SRC python :results output +class DummyMeta(type): + + @classmethod + def __prepare__(mcs, name, bases, **kwargs): + print('DummyMeta.__prepare__') + return {'special': 1} + +class Dummy(metaclass=DummyMeta): + + def __new__(cls, name): + print(f'Dummy.__new__({name})') + return super().__new__(cls) + + def __init__(self, name): + super().__init__() + self.name = name + print(f'Dummy.__init__({self.name})') + +dummy1, dummy2 = (Dummy(f'dummy{i + 1}') for i in range(2)) +print(f'{dummy1.special=}') +dummy1.special += 1 +print(f'{dummy1.special=}') +print(f'{dummy2.special=}') +#+END_SRC + +#+RESULTS: +: DummyMeta.__prepare__ +: Dummy.__new__(dummy1) +: Dummy.__init__(dummy1) +: Dummy.__new__(dummy2) +: Dummy.__init__(dummy2) +: dummy1.special=1 +: dummy1.special=2 +: dummy2.special=1 + +* Références + * [[https://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/][Understanding Python metaclasses - Ionel codelog]] + * [[https://zestedesavoir.com/tutoriels/954/notions-de-python-avancees/4-classes/2-metaclasses/][Metaclasses - Zeste de savoir]] diff --git a/20220520230639-la_methode_hash.org b/20220520230639-la_methode_hash.org new file mode 100644 index 0000000..3939bdb --- /dev/null +++ b/20220520230639-la_methode_hash.org @@ -0,0 +1,89 @@ +:PROPERTIES: +:ID: d1877b3b-3fe5-43d5-9be2-afb5ffe6b918 +:mtime: 20220522095337 +:ctime: 20220520230639 +:END: +#+title: La méthode __hash__ + +* Synthaxe +#+BEGIN_SRC python +class.__hash__(self) +#+END_SRC +La méthode ~class.__hash__~ : + * Est appelée par la /built-in method/ ~hash()~ et pour les opérations sur les membres des /hashed collection/ telles + que /set/, /frozenset/ et /dict/, + * Doit retourner un /int/, + * Deux objet *égaux* doivent retourner la même valeur, + * Si une classe *ne définit pas* de méthode ~__eq__~, elle ne doit pas définir de méthode ~__hash__~, + * Si une classe *définit* une méthode ~__eq__~, mais pas de méthode ~__hash__~, ses instances : + * Une méthode ~__hash__~ par défaut et retournant ~None~ est créée automatiquement, + * Ne pourront pas être utilisées par les /hashable collections/, + * Si une classe *définit* un objet /mutable/ et *définit* une méthode ~__eq__~, elle *ne doit pas* définir de méthode + ~__hash__~, l'implémentation des /hashables collections/ nécessitant que la valeur des clés soit /immutable/, + * Par défaut, les classes définit par l'utilisateur possèdent les méthodes ~__eq__~ et ~__hash__~ (~id(obj) >> 4~): + tous les objets sont comparés comme non-égaux (sauf chaque objet avec lui-même). + +Si ~a == b~, alors ~hash(a) == hash(b)~. +Si ~hash(a) == hash(b)~, alors ~a~ pourait être égal à ~b~. +Si ~hash(a) != hash(b)~, alors ~a != b~. + +* Exemples + +#+BEGIN_SRC python :results output +from __future__ import annotations + +print(f'{hash(10)=}') +print(f'{hash("abc")=}') + +class Dummy: + + def __init__(self, first: str, second: str) -> None: + self.first = first + self.second = second + +class HashableDummy: + + def __init__(self, first: str, second: str) -> None: + self.first = first + self.second = second + + def __eq__(self, other: HashableDummy) -> bool: + return (other is not None + and isinstance(other, HashableDummy) + and self.first == other.first + and self.second == other.second) + + def __hash__(self) -> int: + return hash((self.first, self.second)) + + +dummy1, dummy2 = (Dummy("a", "b") for _ in range(2)) +print(f'{hash(dummy1)=}') +print(f'{hash(dummy2)=}') +print(f'{dummy1 == dummy2=}') + +hashable_dummy1, hashable_dummy2 = (HashableDummy("a", "b") for _ in range(2)) +print(f'{hash(hashable_dummy1)=}') +print(f'{hash(hashable_dummy2)=}') +print(f'{repr(hashable_dummy1)=}') +print(f'{repr(hashable_dummy2)=}') +print(f'{hashable_dummy1 == hashable_dummy2=}') +#+END_SRC + +#+RESULTS: +: hash(10)=10 +: hash("abc")=-9106817633146148147 +: hash(dummy1)=8779192445928 +: hash(dummy2)=8779192413751 +: dummy1 == dummy2=False +: hash(hashable_dummy1)=2529530665278513875 +: hash(hashable_dummy2)=2529530665278513875 +: repr(hashable_dummy1)='<__main__.HashableDummy object at 0x7fc10a69f4c0>' +: repr(hashable_dummy2)='<__main__.HashableDummy object at 0x7fc10a51a1c0>' +: hashable_dummy1 == hashable_dummy2=True + +* Références + * [[https://docs.python.org/3/reference/datamodel.html#object.__hash__][object.__hash__ - Docs Python]] + * [[https://zestedesavoir.com/tutoriels/954/notions-de-python-avancees/1-starters/3-mutables-hashables/#3-3-hashables][Hashables - Zeste de savoir]] + * [[https://eng.lyft.com/hashing-and-equality-in-python-2ea8c738fb9d][Hashing and equality in Python - Roy Williams]] + diff --git a/20220522101323-method_resolution_order.org b/20220522101323-method_resolution_order.org new file mode 100644 index 0000000..64470fb --- /dev/null +++ b/20220522101323-method_resolution_order.org @@ -0,0 +1,66 @@ +:PROPERTIES: +:ID: 1512a18b-221a-4f15-90a0-9ccac023dfd6 +:mtime: 20220528175444 +:ctime: 20220522101323 +:END: +#+title: Method Resolution Order + +* Introduction + +Le /Method Resolution Order/ (MRO) est le mécanisme de recherche de méthodes et attributs dans les classes mères, +notamment lors d'héritages multiples. Celui-ci est basé sur l'algorithme [[https://en.wikipedia.org/wiki/C3_linearization][C3 superclass linearization]]. + +* Ordres de résulution + +#+BEGIN_SRC python :results output +class A: + def func(self): + print("B.func() called") + +class B(A): + def func(self): + print("B.func() called") + +class C(B, A): + pass + +c = C() +c.func() +print(f'{C.__mro__=}') +#+END_SRC + +#+RESULTS: +: B.func() called +: C.__mro__=(, , , ) + +Ordre : ~C -> B -> A -> object~ (pour un même niveau d'héritage, de gauche à droite). + +#+BEGIN_SRC python :results output +class A: + def func(self): + print("A.func() called") + +class B: + def func(self): + print("B.func() called") + +class C(A, B): + pass + +class D(C, B): + pass + +d = D() +d.func() +print(f'{D.__mro__=}') +#+END_SRC + +#+RESULTS: +: A.func() called +: D.__mro__=(, , , , ) + +Ordre : ~D -> C -> A -> B -> object.~ (classe mères, de gauche à droite) + +* Références + * [[https://python.plainenglish.io/advanced-python-classes-objects-and-mro-423bb01521fb][Advanced Python classes objects and mro - Medium]] + diff --git a/20220522143325-dataclasses.org b/20220522143325-dataclasses.org new file mode 100644 index 0000000..7760b71 --- /dev/null +++ b/20220522143325-dataclasses.org @@ -0,0 +1,58 @@ +:PROPERTIES: +:ID: f892dbdd-0cb5-4204-bca0-09aafb41f7ca +:mtime: 20220522151003 +:ctime: 20220522143325 +:END: +#+title: Dataclasses + +* Introduction +Le module /dataclasses/ : + * Décrit par la [[https://www.python.org/dev/peps/pep-0557][PEP-557 - Data Classes]], + * A été ajouté dans la version 3.7 de CPython, + * Fournit un /decorator/ et des fonctions générant automatiquement des /dunder methods/, telles que ~__init__~ or + ~__repr__~, aux classes (évite l'écriture de /boilerplate code/). + +* Howto +** Dataclass /immutable/ +#+BEGIN_SRC python :results output +from dataclasses import dataclass, field +from datetime import datetime +from typing import List +from uuid import UUID, uuid4 + +@dataclass(frozen=True) +class Tweets: + """ Class to store tweets of users """ + + tweet_body: str = None + tweet_time: datetime = datetime.utcnow() + tweet_id: UUID = uuid4() + tweet_lang: str = 'en-IN' + tweet_place: str = 'IN' + tweet_retweet_count: int = field(repr=False, compare=False, default=0) + tweet_hashtags: List[str] = field(default_factory=list) + tweet_user_id: str = None + tweet_user_name: str = None + +from inspect import getmembers, isfunction + +for member in getmembers(Tweets, predicate=isfunction): + print(member) +#+END_SRC +#+RESULTS: +: ('__delattr__', .__delattr__ at 0x7ff1127a78b0>) +: ('__eq__', .__eq__ at 0x7ff1127a7790>) +: ('__hash__', .__hash__ at 0x7ff1127a7940>) +: ('__init__', .__init__ at 0x7ff1127a7550>) +: ('__repr__', .__repr__ at 0x7ff11287c670>) +: ('__setattr__', .__setattr__ at 0x7ff1127a7820>) + +Il est nécessaire d'utiliser la méthode ~dataclasses.field~ et de préciser le paramètre ~default_factory~ pour l'utilisation d'objets /mutable/. + +Les paramètres suivants de la méthode ~dataclasses.field~ permettent de contrôler le code généré : + * ~repr~ : A mettre à ~False~ pour que l'attribut ne soit pas inclus dans la représentation (~__repr__~) de l'objet, + * ~compare~: A mettre à ~False~ pour que l'attribut ne soit pas utilisé par la méthode ~__eq__~ de l'objet + +* Références + * [[https://medium.com/gitconnected/advanced-python-dataclasses-6a1e53bc4d8d][Advanced Python dataclasses - Medium]] + * [[https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass][Dataclasses - Python]] diff --git a/20220522151020-namedtuple.org b/20220522151020-namedtuple.org new file mode 100644 index 0000000..de1d6cf --- /dev/null +++ b/20220522151020-namedtuple.org @@ -0,0 +1,43 @@ +:PROPERTIES: +:ID: 234f4590-b484-4286-9dbd-49612f4657be +:mtime: 20220522171851 +:ctime: 20220522151020 +:END: +#+title: NamedTuple + +* Introduction +Les /tuples/ sont des objets /immutables/ contenant des données /mutable/ ou non, homogènes ou non. Le contenu d'un +/tuple/ est accessible depuis son index (cf. tableau). Le /namedtuple/ est un /tuple/ pour lequel le contenu est aussi +accessible depuis un nom, rendant le code plus lisible. + +Les /namedtuple/ prennent la même quantité de RAM que les /tuple/. + +* Howto +Le module /collections/ propose une implémentation de /namedtuple/ : +#+BEGIN_SRC python :results output +collections.namedtuple(typename, field_names, *, rename=False, defaults=None, module=None) +#+END_SRC + +#+BEGIN_SRC python :results output +from collections import namedtuple +from sys import getsizeof + +Point = namedtuple('Point', ['x', 'y']) +p1 = Point(10, 20) + +print(f'{p1=}') +print(f'{p1.x == p1[0] = }') +print(f'{p1.y == p1[1] = }') + +print(f'{getsizeof(p1) == getsizeof((10, 20)) = }') +#+END_SRC + +#+RESULTS: +: p1=Point(x=10, y=20) +: p1.x == p1[0] = True +: p1.y == p1[1] = True +: getsizeof(p1) == getsizeof((10, 20)) = True + +* Références + * [[https://pythonsimplified.com/what-are-namedtuples-in-python/][What are the named tuples in Python - Python Simplified]] + * [[https://docs.python.org/3/library/collections.html#collections.namedtuple][Namedtuple - Python collections module]] diff --git a/20220524120058-guix.org b/20220524120058-guix.org new file mode 100644 index 0000000..44bec87 --- /dev/null +++ b/20220524120058-guix.org @@ -0,0 +1,23 @@ +:PROPERTIES: +:ID: 393342ff-bf38-4472-8713-3de5ebe43eca +:mtime: 20220526074246 +:ctime: 20220524120058 +:END: +#+title: Guix + +* Introduction +Distribution du système GNU : + * Constituée *exclusivement de logiciels libres*, + * Gestionnaire de paquets : GNU *Guix*, + * Gestionnaire de services : GNU *Shepherd* + * Utilise le noyau *linux-libre*, prochainement *Hurd*. + +* Configuration +La configuration consiste en un ensemble de fichiers [[https://www.gnu.org/software/guile/][Guile Scheme]]. + +* Références + * [[https://guix.gnu.org/][GNU Guix]] + * [[https://www.gnu.org/software/shepherd/][GNU Shepherd]] +** Configurations + * [[https://github.com/podiki/dot.me/tree/master/guix/.config][Podiki's Guix config - Github]] + * [[https://pad.lamyne.org/s/HJ5J3SWCN#][Guix : un outil pour les remplacer tous - Andréas Livet]] diff --git a/20220524120307-moteurs_de_recherche.org b/20220524120307-moteurs_de_recherche.org new file mode 100644 index 0000000..d6ab27f --- /dev/null +++ b/20220524120307-moteurs_de_recherche.org @@ -0,0 +1,10 @@ +:PROPERTIES: +:ID: 13205a76-7bbb-47c5-9176-f272ff65c1c0 +:mtime: 20220524120334 +:ctime: 20220524120307 +:END: +#+title: Moteurs de recherche + +* Payants + * [[https://kagi.com/][Kagi]] + diff --git a/20220525214257-drbd.org b/20220525214257-drbd.org new file mode 100644 index 0000000..9075db0 --- /dev/null +++ b/20220525214257-drbd.org @@ -0,0 +1,71 @@ +:PROPERTIES: +:ID: 34ceb92e-c507-4c61-9c6a-51501b80f054 +:mtime: 20220525221941 +:ctime: 20220525214257 +:END: +#+title: DRBD + +* Introduction +DRBD : + * *Distributed Replicated Block Device* en anglais, périphérique en mode bloc répliqué et distribué en français, + * Architecture de stockage distribuée pour GNU/Linux, + * Permet la réplication de périphériques de bloc (disques, partitions, volumes logiques etc.) entre des serveurs, + * Open source et support commercial, + * Est composé d'un module noyau et d'outils d'administration en /user space/, + * peut être utilisé aussi bien en dessous qu'au-dessus de la pile de Linux LVM, + * Support de la répartition de charge depuis la version 8, + * Réplication entre 2 serveurs, plus à partir de la version 9. + * Peut être intégré à un cluster ([[id:012e07b1-e12a-46e9-9a26-a93cde8b92ce][Pacemaker]] par exemple), + * Est intégré au projet [[http://www.linux-ha.org/wiki/Main_Page][Linux HA]]. + +#+DOWNLOADED: https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/Drbd-arch.png/640px-Drbd-arch.png @ 2022-05-25 22:06:08 +#+ATTR_ORG: :width 500 +[[file:Introduction/640px-Drbd-arch_2022-05-25_22-06-08.png]] + +* Réplication de données +La réplication des données est réalisée : + * En *temps réel* et *en permanence* : pendant que les applications modifient les données présentes sur le /block device/, + * De façon *transparente* (réplication au niveau /block device/) : les applications n'ont pas conscience que ces + données sont stockées et répliquées, + * De façon synchrone ou asynchrone : + * *Synchrone* : l'écriture sur le /block device/ entraine la mise à jour sur l'ensemble des serveurs avant la + notification de fin d'écriture, + * *Asynchrone* : la notification de fin d'écriture est réalisée avant la réplication de la données sur les autres + serveurs. + +* Principes de fonctionnement + * DRBD ajoute une couche logique de /block device/ (conventionnellement nommée /dev/drbdX, ou X est le numéro de périph. mineur), + * Les écritures sur le noeud primaire sont transférées sur le /block device/ et propagées au noeud secondaire pour que + celui-ci transfère les données à son /block device/, + * Les lectures sont effectées localement. + + * En cas de défaillance du nœud primaire : + * Un orchestrateur promeut le nœud /slave/ dans un état /master/, + * Quand l'ancien nœud /master/, précédemment défaillant, revient, le système peut ou non l'élever au rôle de + /master/, après une synchronisation des données du périphérique. + * L'algorithme de synchronisation de DRBD est efficace : *seuls les blocs qui ont changé durant la panne doivent être + resynchronisés*, plutôt que le périphérique dans son entièreté. + +* Les outils +** drbdadm +Outil d'administration de haut niveau de DRBD. Il utilise le fichier de config ~/etc/drbd.conf~ et sert d'interface avec +les outils suivants. +*** Création d'une ressource (à faire sur chacun des serveurs) +#+BEGIN_SRC shell :results output +drbdadm create-md +modprobe drbd +drbdadm up +#+END_SRC +*** Pour forcer un serveur à passer /master/ (suite à un /split-brain/ par exemple) +L'autre server devenant alors /slave/ et devant synchroniser ses données avec celles du nouveau maitre : +#+BEGIN_SRC shell :results output +drbdadm -- --overwrite-data-of-peer primary +#+END_SRC +** drbdsetup +Outil du configuration du module DRBD après son chargement. +** drbdmeta +Outil permettant la manipulation des métadonnées des structures de DRBD. + +* Références + * [[https://fr.wikipedia.org/wiki/DRBD][Drbd - Wikipedia]] + * [[https://fr-wiki.ikoula.com/fr/Mise_en_place_de_DRBD_en_mode_primaire-secondaire][Mise en place de DRBD en mode primaire/secondaire - wiki-ikoula]] diff --git a/20220526073445-nyxt.org b/20220526073445-nyxt.org new file mode 100644 index 0000000..057a4ef --- /dev/null +++ b/20220526073445-nyxt.org @@ -0,0 +1,19 @@ +:PROPERTIES: +:ID: e7950306-e803-44a4-ae94-cc9cc7cf8191 +:mtime: 20220526075541 +:ctime: 20220526073445 +:END: +#+title: Nyxt + +* Introduction +Navigateur web partageant les choix de conception d'Emacs : + * Extensible (/lisp/), + * Keyboard friendly. + +* Références + * [[https://nyxt.atlas.engineer/documentation][Documentation - Nyxt]] + * [[https://github.com/ag91/emacs-with-nyxt][Some code to make Emacs interact with Nyxt - Github]] + +** Configurations + * [[https://github.com/aartaka/nyxt-config/tree/08c743e2a94ec63d432dc96002f7a9db025c5393][Aartaka Nyxt config - Github]] + * diff --git a/20220526082057-self_hosted_server.org b/20220526082057-self_hosted_server.org new file mode 100644 index 0000000..a066e4a --- /dev/null +++ b/20220526082057-self_hosted_server.org @@ -0,0 +1,31 @@ +:PROPERTIES: +:ID: 47707ab9-7792-4592-b25b-8dd2f2e45b05 +:mtime: 20220604101605 +:ctime: 20220526082057 +:END: +#+title: Self-hosted server + +* Introduction +Consiste en l'hébergement soi-même d'un ensemble de services web. + +* Gestion des certificats +** Signature par un CA +[[https://letsencrypt.org/][letsencrypt]] est utilisé pour signer les certificats générés pour les différentes interfaces. + +Pour forcer le renouvellement du certificat (exemple du serveur de mail) : +#+BEGIN_SRC shell +certbot certonly --nginx --force-renew -d imap.adrien.run +#+END_SRC + +* Serveur git +** [[id:bb755a02-0df1-4ee4-8b8c-3f36d8bdfff1][Gitea]] + +* Mails +** Serveur de mail +*** [[id:c226d1ef-02e8-4cd5-bce6-e91a3843a291][Exim+dovecot]] +** Webmail +*** [[id:a9e9ba39-726e-4a49-876e-a0d58623cc48][Roundcube]] + +* Vidéo +** Plateforme de streaming +*** [[https://github.com/Chocobozzz/PeerTube][PeerTube]] diff --git a/20220526082204-gitea.org b/20220526082204-gitea.org new file mode 100644 index 0000000..d03d619 --- /dev/null +++ b/20220526082204-gitea.org @@ -0,0 +1,20 @@ +:PROPERTIES: +:ID: bb755a02-0df1-4ee4-8b8c-3f36d8bdfff1 +:mtime: 20220526083212 +:ctime: 20220526082204 +:END: +#+title: Gitea + +* Introduction +Solution légère d'hébergement de code (serveur git) : + * Ecrite en Go, + * Licence MIT. + +* Installation +Cf. [[https://linuxize.com/post/how-to-install-gitea-on-ubuntu-20-04/#installing-gitea][Installing Gitea]]. + +* Usage +Cf. [[https://docs.gitea.io/en-us/][Docs - Gitea]]. + +* Références + * [[https://gitea.io/en-us/][Gitea website]] diff --git a/20220526083306-exim_dovecot.org b/20220526083306-exim_dovecot.org new file mode 100644 index 0000000..5ee88a1 --- /dev/null +++ b/20220526083306-exim_dovecot.org @@ -0,0 +1,104 @@ +:PROPERTIES: +:ID: c226d1ef-02e8-4cd5-bce6-e91a3843a291 +:mtime: 20220527114245 +:ctime: 20220526083306 +:END: +#+title: Exim+dovecot + +* Introduction +La solution de serveur de mail est basée sur les OSS suivants : + * [[https://www.exim.org/][Exim4]] : MTA léger (Serveur de mail SMTP), + * [[https://www.dovecot.org/][Dovecot]] : + * Serveur de mail POP/IMAP orienté sécurité, + * gère les formats de boîte de messagerie [[https://fr.wikipedia.org/wiki/Mbox][mbox]] et [[https://fr.wikipedia.org/wiki/Maildir][maildir]]. + +* Les différents composants +** Mail Transfer Agent (MTA) +Composant en charge de : + * La transmission des mail émis par ses utilisateurs vers un autre MTA, + * L'acceptation des mails dont ses propres utilisateurs sont destinataires. +Les MTA communiquent entre eux via le protocole SMTP. + +** Mail Delivery Agent (MDA) +Composant en charge de : + * La réception des mails acceptés par le MTA, + * Trie et dépose des mails reçus dans les boites de réception des utilisateurs. + +** Mail Retrieval Agent (MRA) +Composant collectant, depuis le poste de l'utilisateur, de ses mails hébergés par le serveur. + +** Mail User Agent (MUA) +Composant "IHM" : + * Affichage des mails reçus, + * Formattage des mails écrits. + +** Mail Submission Agent (MSA) +Composant relai entre le MUA et le MTA. + +* Sécurisation +** [[https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail][DKIM]] +DKIM : + * Est un système de signature de mails en sortie de MTA, + * La signature consiste en une clé publique disponible depuis le serveur DNS résolvant les noms associés au serveur, + * Permet de garantir l'intégrité du message et certifier que l'émetteur du message dispose du couple clef privée/clef publique dont cette dernière est référencée sur le serveur DNS du domaine de courrier électronique. + +* Installation +** Exim4 +*** Fichier exim4.conf.template +Modifier la section ~MAIN CONFIGURATION SETTINGS~ du fichier : +#+BEGIN_SRC shell +MAIN_TLS_ENABLE=true +MAIN_TLS_CERTIFICATE=/etc/letsencrypt/live/smtp.adrien.run/fullchain.pem +MAIN_TLS_PRIVATEKEY=/etc/letsencrypt/live/smtp.adrien.run/privkey.pem + +DKIM_DOMAIN=adrien.run +DKIM_SELECTOR=mail +DKIM_PRIVATE_KEY=/etc/exim4/dkim.pem +#+END_SRC +*** Fichier update-exim4.conf.conf +#+BEGIN_SRC shell +cat >/etc/exim4/update-exim4.conf.conf <${OUTPUT} </dev/null + fi +done +EOF +chmod +x ${OUTPUT} +#+END_SRC + +Différents tutoriels utiles : + * [[https://transang.me/setup-a-production-ready-exim-dovecot-server/][Setup a production-ready exim/dovecot server - Tran Sang]] + * [[https://www.geekrant.org/2017/04/16/install-exim4-starttls-using-a-free-letsencrypt-certificate/][Install exim4 STARTTLS using a free LetsEncrypt certificate - Geekrant]] + * [[https://buzut.net/connaitre-commandes-dompter-exim4/][Les commandes essentielles pour dompter Exim4 - Buzut]] + +Le site [[https://mxtoolbox.com/SuperTool.aspx?action=smtp%3amail.adrien.run&run=toolpage][mxtoolbox]] permet de vérifier la configuration de serveurs de mail. + + +* Références + * [[https://medspx.fr/blog/Debian/exim4_beginner/][Solution de serveur de courrier électronique auto-hébergé (partie 2) : Débuter avec Exim4 sous Debian - Médéric Ribreux]] + * [[https://medspx.fr/blog/Debian/exim4_beginner_part2/][Solution de serveur de courrier électronique auto-hébergé (partie 5) : Aller plus loin avec Exim4 sous Debian - Médéric Ribreux]] + * [[https://medspx.fr/blog/Debian/mise_en_place_dkim_spf_dmarc_exim4_debian/][Mise en place de DKIM, SPF, DMARC sur Debian Jessie pour Exim4 - Médéric Ribreux]] + * diff --git a/20220526105643-roundcube.org b/20220526105643-roundcube.org new file mode 100644 index 0000000..e39c200 --- /dev/null +++ b/20220526105643-roundcube.org @@ -0,0 +1,92 @@ +:PROPERTIES: +:ID: a9e9ba39-726e-4a49-876e-a0d58623cc48 +:mtime: 20220527130858 +:ctime: 20220526105643 +:END: +#+title: Roundcube + +* Introduction + +* Installation +** Configuration de nginx +# Configure nginx +#+BEGIN_SRC shell +cat >/etc/nginx/sites-available/mail </etc/roundcube/debian-db.php < None: + self.a = a + self.b = b + +dummy = Dummy(1, 2) +try: + print(dummy.__slots__) +except AttributeError as e: + print(e) + +print(f'{dummy.__dict__=}') + +dummy.c = 3 +print(f'{dummy.__dict__=}') + +print(f'{getsizeof(dummy) + getsizeof(dummy.__dict__)}') +#+END_SRC +#+RESULTS: +: 'Dummy' object has no attribute '__slots__' +: dummy.__dict__={'a': 1, 'b': 2} +: dummy.__dict__={'a': 1, 'b': 2, 'c': 3} +: 152 + +L'utilisation d'un ~dict~ pour stocker les attributs de chaque objet permet cette souplesse mais a pour conséquence un +usage mémoire qui pourrait être amélioré. + +C'est pour cela que l'attributs ~__slots__~ a été ajouté par Python3. + +* Usage +L'attribut de classe ~__slots__~ est un tuple (taille inférieure à celle d'un ~dict~ mais /imutable/) contenant les +attributs des instances. +#+BEGIN_SRC python :results output +from sys import getsizeof + +class Dummy: + + __slots__ = ('a', 'b', 'c') + + def __init__(self, a: int, b: int) -> None: + self.a = a + self.b = b + +dummy = Dummy(1, 2) +try: + print(dummy.__dict__) +except AttributeError as e: + print(e) + +print(f'{dummy.__slots__=}') + +dummy.c = 3 +print(f'{dummy.__slots__=}') + +try: + dummy.d = 4 +except AttributeError as e: + print(e) + +print(f'{getsizeof(dummy) + getsizeof(dummy.__slots__)}') +#+END_SRC + +#+RESULTS: +: 'Dummy' object has no attribute '__dict__' +: dummy.__slots__=('a', 'b', 'c') +: dummy.__slots__=('a', 'b', 'c') +: 'Dummy' object has no attribute 'd' +: 120 +** Avantages +L'usage de ~__slots__~ permet de réduire la taille des objets. Néanmoins, l'écart se réduit plus le nombre d'attributs +augmente. Les ~__slots__~ sont donc utiles pour les classes ayant peu d'attributs et un grand nombre d'instances. + +Le principal avantage de ~__slots__~ réside dans les performances (environ 15% plus rapide), conséquence du temps +d'accès plus rapide au contenu d'un ~tuple~ que d'un ~__dict__~. +** Inconvénients +L'absence de ~__dict__~ empèche l'ajout dynamique d'attributs. + +L'usage de ~__slots__~ ajoute les règles suivantes quant à l'héritage : + * Les slots d’une classe mère s’ajoutent à ceux de la classe fille, + * Il ne peut exister qu’une seule classe mère avec une séquence de slots non vide, + * Si une des classes membres de l'arbre d'héritage ne déclare pas de ~__slots__~, mêmes vides, les instances auront un attribut ~__dict__~ (perte de l'optimisation). + +* Références + * [[https://www.invivoo.com/les-slots-une-optimisation-meconnue/][Les slots, une optimisation méconnue - Invivoo]] diff --git a/20220527190024-fonction_zip.org b/20220527190024-fonction_zip.org new file mode 100644 index 0000000..b153a5f --- /dev/null +++ b/20220527190024-fonction_zip.org @@ -0,0 +1,55 @@ +:PROPERTIES: +:ID: acda43fa-70be-4939-9128-47114e48e4cb +:mtime: 20220528183525 +:ctime: 20220527190024 +:END: +#+title: Fonction zip + +* Introduction +Fonction /built-in/ permettant l'itération de plusieurs /iterable/ à la fois : + * Si les /iterable/ n'ont pas le même nombre d'éléments, /zip/ arrêtera une fois le plus court atteint. + +* Howto +** /Iterable/ de tailles différentes +#+BEGIN_SRC python :results output +firsts = ["Anna", "Bob", "Charles", "Chris"] +middles = ["Z.", "A.", "G."] +lasts = ["Smith", "Doe", "Evans"] + +for it in zip(firsts, middles, lasts): + print(f'{type(it)=}') + first, *others = it + print(f"'{first} {others}'") +#+END_SRC +#+RESULTS: +: type(it)= +: 'Anna ['Z.', 'Smith']' +: type(it)= +: 'Bob ['A.', 'Doe']' +: type(it)= +: 'Charles ['G.', 'Evans']' + +#+BEGIN_SRC python :results output +firsts = ["Anna", "Bob", "Charles", "Chris"] +middles = ["Z.", "A.", "G."] +lasts = ["Smith", "Doe", "Evans"] + +for first, middle, last in zip(firsts, middles, lasts): + print(f"'{first} {middle} {last}'") +#+END_SRC +#+RESULTS: +: 'Anna Z. Smith' +: 'Bob A. Doe' +: 'Charles G. Evans' + +** Création d'un /dict/ avec /zip/ +#+BEGIN_SRC python :results output +firsts = ["Anna", "Bob", "Charles"] +lasts = ["Smith", "Doe", "Evans"] +print(f'{dict(zip(firsts, lasts))=}') +#+END_SRC +#+RESULTS: +: dict(zip(firsts, lasts))={'Anna': 'Smith', 'Bob': 'Doe', 'Charles': 'Evans'} + +* Références + * [[https://mathspp.com/blog/pydonts/zip-up][Zip up - Pydon't]] diff --git a/20220528104202-set_et_frozenset.org b/20220528104202-set_et_frozenset.org new file mode 100644 index 0000000..a1b3c2a --- /dev/null +++ b/20220528104202-set_et_frozenset.org @@ -0,0 +1,106 @@ +:PROPERTIES: +:ID: cc0e3fd0-2832-4bf6-909f-354507f7ed11 +:mtime: 20220528110155 +:ctime: 20220528104202 +:END: +#+title: Set et frozenset + +* /Set/ +Un /set/ est un ensemble /mutable/ et non ordonné d'éléments uniques. +#+BEGIN_SRC python :results output +groceries = {"milk", "cheese", "chocolate"} +print(f'{groceries = }') +#+END_SRC + +#+RESULTS: +: groceries = {'milk', 'chocolate', 'cheese'} + +** Egalité +L'ordre n'a pas d'impact sur l'égalité entre deux /set/ : +#+BEGIN_SRC python :results output +groceries = {"milk", "cheese", "chocolate"} +print(f'{set(["milk", "cheese", "chocolate"])==set(["chocolate", "milk", "cheese"]) = }') +#+END_SRC + +#+RESULTS: +: set(["milk", "cheese", "chocolate"])==set(["chocolate", "milk", "cheese"]) = True + +** /Set comprehension/ +#+BEGIN_SRC python :results output +veggies = ["broccoli", "carrot", "tomato", "pepper", "lettuce"] +veggies_with_c = {veggie for veggie in veggies if "c" in veggie} + +print(f'{veggies = }') +print(f'{veggies_with_c = }') +#+END_SRC + +#+RESULTS: +: veggies = ['broccoli', 'carrot', 'tomato', 'pepper', 'lettuce'] +: veggies_with_c = {'carrot', 'lettuce', 'broccoli'} + +** Opérations sur les /set/ +*** L'intersection entre des /set/ est réalisée avec l'opérateur ~&~ +#+BEGIN_SRC python :results output +groceries = {"milk", "cheese", "chocolate"} +treats = {"chocolate", "popcorn", "cookie"} + +print(f'{groceries & treats = }') +#+END_SRC + +#+RESULTS: +: groceries & treats = {'chocolate'} + +*** L'union entre des /set/ est réalisée avec l'opérateur ~|~ +#+BEGIN_SRC python :results output +groceries = {"milk", "cheese", "chocolate"} +treats = {"chocolate", "popcorn", "cookie"} + +print(f'{groceries | treats = }') +#+END_SRC + +#+results: +: groceries | treats = {'cheese', 'popcorn', 'chocolate', 'milk', 'cookie'} + +*** La différences entre /set/ est réalisée avec l'opérateur ~-~ +#+BEGIN_SRC python :results output +groceries = {"milk", "cheese", "chocolate"} +treats = {"chocolate", "popcorn", "cookie"} + +print(f'{groceries - treats = }') +#+END_SRC + +#+RESULTS: +: groceries - treats = {'milk', 'cheese'} + +* /Frozenset/ +A l'inverse du /set/, le /frozenset/ est /immutable/ et est donc /hashable/ (utilisable comme clé des /dict/). + +#+BEGIN_SRC python :results output +groceries = {"milk", "cheese", "chocolate"} +frozen_groceries = frozenset(groceries) + +try: + frozen_groceries.add("beans") +except AttributeError as e: + print(e) + +d = {} + +try: + d[groceries] = None +except TypeError as e: + print(e) + +d[frozen_groceries] = None +print(f'{d = }') +#+END_SRC + +#+RESULTS: +: 'frozenset' object has no attribute 'add' +: unhashable type: 'set' +: d = {frozenset({'chocolate', 'milk', 'cheese'}): None} + + + +* Références + * [[https://mathspp.com/blog/pydonts/set-and-frozenset][Set and frozenset - Pydon't]] diff --git a/20220528110814-string.org b/20220528110814-string.org new file mode 100644 index 0000000..8e1f433 --- /dev/null +++ b/20220528110814-string.org @@ -0,0 +1,52 @@ +:PROPERTIES: +:ID: 8b686fdd-2abd-459d-8d8d-0c57915de8fb +:mtime: 20220528112853 +:ctime: 20220528110814 +:END: +#+title: String + +* Méthode /str.translate/ +La méthode /str.translate/ permet d'appliquer une table de substitution à la chaine de caractères : +#+BEGIN_SRC python :results output +translation_dict = {ord('a'): ord('A'), ord('b'): "BBB", ord('c'): None} +print(f'{"aaa bbb ccc".translate(translation_dict) = }') +#+END_SRC +#+RESULTS: +: "aaa bbb ccc".translate(translation_dict) = 'AAA BBBBBBBBB ' + +* La méthode de classe /str.maketrans/ +La méthode de classe /str.maketrans/ simplifie la création des tables de translation utilisées par /str.stranslate/ en +supprimant la nécessité d'utiliser la fonction ~ord~ : + +#+BEGIN_SRC python :results output +# Without str.mktrans +translation_dict = {ord("0"): "1", ord("1"): "0"} +print(f'{"001011010101001".translate(translation_dict) = }') + +# With str.maketrans (single argument) +translation_dict = str.maketrans({'0': '1', '1': '0'}) +print(f'{"001011010101001".translate(translation_dict) = }') + +# With str.maketrans (two arguments) +translation_dict = str.maketrans("01", "10") +print(f'{"001011010101001".translate(translation_dict) = }') +print(f'{"#0F45cd".translate(str.maketrans("abcdef", "ABCDEF")) = }') +#+END_SRC + +#+RESULTS: +: "001011010101001".translate(translation_dict) = '110100101010110' +: "001011010101001".translate(translation_dict) = '110100101010110' +: "001011010101001".translate(translation_dict) = '110100101010110' +: "#0F45cd".translate(str.maketrans("abcdef", "ABCDEF")) = '#0F45CD' + +Le dernier argument de /str.maketrans/ permet de lister les caractères à remplacer par ~None~, à supprimer : +#+BEGIN_SRC python :results output +print(f'{"#0F45cd".translate(str.maketrans("abcdef", "ABCDEF", "#")) = }') +#+END_SRC + +#+RESULTS: +: "#0F45cd".translate(str.maketrans("abcdef", "ABCDEF", "#")) = '0F45CD' + +* Références + * [[https://mathspp.com/blog/pydonts/string-translate-and-maketrans-methods][String translate and maketrans methods - Pydon't]] + diff --git a/20220528161514-la_methode_bool.org b/20220528161514-la_methode_bool.org new file mode 100644 index 0000000..6a38cdf --- /dev/null +++ b/20220528161514-la_methode_bool.org @@ -0,0 +1,56 @@ +:PROPERTIES: +:ID: 5803422c-c089-4369-93cc-80caca71a26b +:mtime: 20220528173751 +:ctime: 20220528161514 +:END: +#+title: La méthode __bool__ + +* Synthaxe +#+BEGIN_SRC python +object.__bool__(self) +#+END_SRC +La méthode ~object.__bool__~ : + * “Any object can be tested for truth value, for use in an if or while condition or as operand of the Boolean operations below [or, and and not].”, + * Un objet est /Falsy/ lorsqu'il est /empty/ ou /without any useful value/, + * Est appelée par la fonction /built-in/ /bool/. + +* Exemples +#+BEGIN_SRC python :results output +print(f'{bool(None) = }') + +print(f'{bool([]) = }') +print(f'{bool([None]) = }') + +print(f'{bool({}) = }') + +#+END_SRC +#+RESULTS: +: bool(None) = False +: bool([]) = False +: bool([None]) = True +: bool({}) = False + + * Par défaut, les instances de classes retournent /True/ : +#+BEGIN_SRC python :results output +class Dummy: + pass + +print(f'{bool(Dummy()) = }') +#+END_SRC +#+RESULTS: +: bool(Dummy()) = True + +#+BEGIN_SRC python :results output +class Dummy: + + def __bool__(self) -> bool: + return False + +print(f'{bool(Dummy()) = }') +#+END_SRC +#+RESULTS: +: bool(Dummy()) = False + +* Références + * [[https://mathspp.com/blog/pydonts/truthy-falsy-and-bool][Truthy falsy and bool - Pydon't]] + diff --git a/20220528183301-fonction_vars.org b/20220528183301-fonction_vars.org new file mode 100644 index 0000000..b9330e2 --- /dev/null +++ b/20220528183301-fonction_vars.org @@ -0,0 +1,30 @@ +:PROPERTIES: +:ID: 487f57f5-e9fc-4df7-ae14-b41f9c1fa186 +:mtime: 20220528184039 +:ctime: 20220528183301 +:END: +#+title: Fonction vars + +* Introduction +Fonction /built-in/ retournant l'attribut ~__dict__~ d'un module, d'une classe, d'une instance ou de n'importe quel +objet avec un attribut ~__dict__~. + +* Howto +#+BEGIN_SRC python :results output +class Person: + def __init__(self, name): + self.name = name + +print(f'{vars(Person("me")) = }') + +import math +print(f'{vars(math) = }') +#+END_SRC + +#+RESULTS: +: vars(Person("me")) = {'name': 'me'} +: vars(math) = {'__name__': 'math', '__doc__': 'This module provides access to the mathematical functions\ndefined by the C standard.', '__package__': '', '__loader__': , '__spec__': ModuleSpec(name='math', loader=, origin='built-in'), 'acos': , 'acosh': , 'asin': , 'asinh': , 'atan': , 'atan2': , 'atanh': , 'ceil': , 'copysign': , 'cos': , 'cosh': , 'degrees': , 'dist': , 'erf': , 'erfc': , 'exp': , 'expm1': , 'fabs': , 'factorial': , 'floor': , 'fmod': , 'frexp': , 'fsum': , 'gamma': , 'gcd': , 'hypot': , 'isclose': , 'isfinite': , 'isinf': , 'isnan': , 'isqrt': , 'ldexp': , 'lgamma': , 'log': , 'log1p': , 'log10': , 'log2': , 'modf': , 'pow': , 'radians': , 'remainder': , 'sin': , 'sinh': , 'sqrt': , 'tan': , 'tanh': , 'trunc': , 'prod': , 'perm': , 'comb': , 'pi': 3.141592653589793, 'e': 2.718281828459045, 'tau': 6.283185307179586, 'inf': inf, 'nan': nan} + +* Références + * [[https://mathspp.com/blog/til/009][Vars - mathspp]] + diff --git a/20220529102607-blockchain.org b/20220529102607-blockchain.org new file mode 100644 index 0000000..1f0f259 --- /dev/null +++ b/20220529102607-blockchain.org @@ -0,0 +1,92 @@ +:PROPERTIES: +:ID: badea9d7-57cd-4734-98de-343f619ffe70 +:mtime: 20220529123703 +:ctime: 20220529102607 +:END: +#+title: Blockchain + +* Introduction +Blockchain est une *technique de stockage de données* presque impossible à modifier, hacker ou manipuler les données +stockées : + * Basée sur la /Distributed Ledger Technology/ (DLT), + * Principalement utilisée dans les domaines des cryptomonnaies et NFTs. + +Il s'agit d'un *registre numérique de transactions* généralement partagé entre tous les noeuds du réseau. Celui-ci est +composé d'une série de blocs incluant plusieurs transactions et à chaque fois qu'une nouvelle transaction est réalisée, +un enregistrement de celle-ci est ajouté dans le registre de chaque participant. + +Les données stockées dans la chaine de blocs est sécurisé par des signatures cryptographiques (hash des blocks respectifs). + +* Les blocs +Les blocs sont les éléments de base d'une /blockchain/ et sont constitués de : + 1) Transactions, + 2) Le /hash/ du précédent bloc, + 3) Le /hash/ de son propre contenu. + +Le premier /block/ d'un chaine est appelé /genesis block/. + +#+BEGIN_SRC python :results output +from rich import print +from hashlib import sha256 +from json import dumps + +def hash_string_256(string): + """Create a SHA256 hash for a given input string. + Arguments: + :string: The string which should be hashed. + """ + return sha256(string).hexdigest() + +def hash_block(block): + """Hashes a block and returns a string representation of it. + Arguments: + :block: The block that should be hashed. + """ + return hash_string_256(dumps(block, sort_keys=True).encode()) + +genesis_block = { + "previous_hash": "", + "transactions": [] +} +genesis_block_hash = hash_block(genesis_block) +print(f'{genesis_block_hash = }') + +block = { + "previous_hash": genesis_block_hash, + "index": 1, + "transactions": [{"sender": "A", "receiver": "B", "amount": 0.5}], + "proof": 9, +} + +block_hash = hash_block(block) +print(f'{block_hash = }') + +block['new_hash'] = block_hash +print(f'{block = }') +#+END_SRC +#+RESULTS: +: genesis_block_hash = +: '26cfa5c318d1195b72ecc139f11a740c5a71300dc1f3d4656188c684ac8ad01e' +: block_hash = '25cdb202d88ccc0d1c3eae6d27af4c231e7e9f889885f1feb40c85128aac7c37' +: block = {'previous_hash': +: '26cfa5c318d1195b72ecc139f11a740c5a71300dc1f3d4656188c684ac8ad01e', 'index': 1, +: 'transactions': [{'sender': 'A', 'receiver': 'B', 'amount': 0.5}], 'proof': 9, +: 'new_hash': '25cdb202d88ccc0d1c3eae6d27af4c231e7e9f889885f1feb40c85128aac7c37'} + +** /hash/ +Un /hash/ (sha256) est une /str/ de longueur fixe qui apporte une couche de chiffrement autour du /block/, rendant impossible la +manipulation de la /blockchain/ (modification des transactions ou manipulation de la /blockchain/). + +** /Proof of work/ +/Proof of work/ est un algorithme propre à une /blockchain/ : + * C'est le mécanisme qui définit la difficulté à ajouter un /block/ à la /blockchain/, + * Il s'agit d'un nombre devant satisfaire une condition en utilisant un /hash/, /hash/ différent des /hash/ contenu dans les /block/ existant, + * Généralement, ce /hash/ est calculé ainsi : sha256((/transactions/ + /hash/ du /block/ précédent) + /proof of work/), + * /Proof of work/ est ajouté aux /metadata/ du /block/ si le /hash/ calculé satisfait une ou plusieurs condition(s) arbitraire(s) (2 + premiers digits égaux à 0 par exemple), dans le cas échant /proof of work/ est incrémenté et le /hash/ recalculé + jusqu'à satisfaction des conditions, + +* Références + * [[https://medium.com/@seaflux/getting-started-with-blockchain-and-programming-it-in-python-d7663b7cc3ef][Getting started with blockchain and programming it in python - Medium]] + + diff --git a/20220529123824-generator.org b/20220529123824-generator.org new file mode 100644 index 0000000..5b84740 --- /dev/null +++ b/20220529123824-generator.org @@ -0,0 +1,98 @@ +:PROPERTIES: +:ID: 67410dad-d959-4029-b281-9bf1c9e69ede +:mtime: 20220529131239 +:ctime: 20220529123824 +:END: +#+title: Generator + +* Introduction +Un /generator/ est : + * Une fonction retournant un /iterable/, + * Un /iterable/ est un /object/ implémentant les méthodes ~__iter__~ et ~__next__~ et génère une erreur ~StopIteration~ lorsqu'il n'y a plus de valeur à retourner, + * Utilise le /statement/ ~yield~ au lieu de ~return~. + +Le statement ~return~ termine complètement une fonction alors que ~yield~ la suspend (sauvegarde de son contexte afin +de continuer là où elle en était lors de son prochain appel). + +Différences entre une /function/ et un /generator/ : + * Présence d'au moins un ~yield~ dans un ~generator~, + * Le /generator/ retourne un /iterator/ (les méthodes ~__iter__~ et ~__next__~ sont implémentées automatiquement), + * Sauvegarde du contexte d'un /generator/, + * L'erreur ~StopIteration~ est générée automatiquement lors qu'un /generator/ termine. + +Les /generator/ sont appropriés quand il est nécessaire de produire un flux de données infini. + +#+BEGIN_SRC python :results output +def simple_generator(): + for num in range(1, 4): + print(num) + yield num + +gen = simple_generator() +next(gen) +next(gen) +next(gen) +try: + next(gen) +except StopIteration as __: + print('StopIteration raised') + +for num in simple_generator(): + pass +#+END_SRC +#+RESULTS: +: 1 +: 2 +: 3 +: StopIteration raised +: 1 +: 2 +: 3 + +Un /generator/ peut être créé soit en déclarant une fonction (cf. exemple précédent) ou directement via une /generator +expression/ : + +#+BEGIN_SRC python :results output +list_numbers = [10, 18, 13, 23] +list_square = [x**2 for x in list_numbers] + +generator = (x**2 for x in list_numbers) + +print(list_square) +print(generator) +#+END_SRC +#+RESULTS: +: [100, 324, 169, 529] +: at 0x7fae7b90bb30> + +* Différences avec /list/ +La principale différence entre les /list comprehension/ et les /generator expression/ consiste dans le fait que +l'ensemble de la /list/ est créée par la /list compréhension/ alors que seul un /generator/ est produit par la +/generator expression/. Cela apporte quelques avantages : + * Chaque résultat du /generator/ n'est produit que suite à l'appel de ~__next__~ (/lazy execution/), + * L'espace mémoire nécessaire s'en retrouve réduit, + +Il peut s'avérer plus efficace d'utiliser un /generator/ lorsque l'ensemble des résultats ne sera pas utilisé à +l'inverse pour les /list comprehension/. + +* Enchainement de /generator/ +Il est possible d'enchainer plusieurs /generator/ : +#+BEGIN_SRC python :results output +def fibonacci_numbers(numbers): + x, y = 0, 1 + for _ in range(numbers): + x, y = y, x + y + yield x + +def square(numbers): + for num in numbers: + yield num**2 + +print(f'{sum(square(fibonacci_numbers(30))) = }') +#+END_SRC + +#+RESULTS: +: sum(square(fibonacci_numbers(30))) = 1120149658760 + +* Références + * [[https://blog.devgenius.io/what-is-generator-in-python-and-how-does-it-work-e6e0588785c3][What Is Generator in Python and How Does It Work ? - Medium]] diff --git a/20220529131614-latex.org b/20220529131614-latex.org new file mode 100644 index 0000000..ab8add9 --- /dev/null +++ b/20220529131614-latex.org @@ -0,0 +1,17 @@ +:PROPERTIES: +:ID: 3250943b-32f0-4bdc-b0d2-5fbcf1724f36 +:mtime: 20220529132210 +:ctime: 20220529131614 +:END: +#+title: Latex + +* Introduction +LaTeX est : + * Un language et un système de composition de documents, + * Une collection de macro-commandes destinées à faciliter l'utilisation du « processeur de texte » TeX. + +* Packages intéressants +** [[https://ctan.crest.fr/tex-archive/macros/latex/contrib/fancyhdr/fancyhdr.pdf][Fancyhdr et extramarks]] +Packages permettant de modifier le /layout/ d'un document. + +* Références diff --git a/20220530080115-youtube.org b/20220530080115-youtube.org new file mode 100644 index 0000000..44220ff --- /dev/null +++ b/20220530080115-youtube.org @@ -0,0 +1,52 @@ +:PROPERTIES: +:ID: 8e30289a-0064-4803-ae64-2e1d8285dd3c +:mtime: 20220530091227 +:ctime: 20220530080115 +:END: +#+title: Youtube + +* Introduction +Est-ce nécessaire ? + +* Obtenir l'id d'un /channel/ afin de s'abonner à son flux RSS +#+BEGIN_SRC python :results output verbatim +# pip install scrapy + +from scrapy import Spider, Request +from scrapy.crawler import CrawlerProcess +from scrapy.utils.log import configure_logging + + +class YoutubeChannelIdSpider(Spider): + name = "youtube_channel" + allowed_domains = ['youtube.com'] + + def __init__(self, channel, *args, **kwargs): + super().__init__(*args, **kwargs) + self.channel = channel + self.url = f'https://www.youtube.com/c/{channel}' + + def start_requests(self): + yield Request(url=self.url, callback=self.parse, cookies={'CONSENT': 'YES+1'}) + + def parse(self, response): + if channel_id := response.xpath('//meta[@itemprop="channelId"]/@content').get(): + print(f'{self.channel} channel ID is : "{channel_id}"') + else: + print(f'Unable to find ID for channel {self.channel}') + +channel = input("What's the Youtube channel for which the ID shall be retrieved ?") + +process = CrawlerProcess() +process.crawl(YoutubeChannelIdSpider, channel=channel) +process.start() # the script will block here until the crawling is finished +#+END_SRC + +#+RESULTS: +: AAAAAAAAAAAAAAAAAAAAAAAAA +: url = 'https://www.youtube.com/c/Matrixdotorg/' + +* Références + * [[https://docs.scrapy.org/en/latest/index.html][Docs - Scrapy]] + * [[https://danielmiessler.com/blog/rss-feed-youtube-channel/][Rss feed youtube channel - Daniel Miessler]] + * [[https://commentpicker.com/youtube-channel-id.php#youtube-channel-id][Comment Picker]] diff --git a/20220604110059-json.org b/20220604110059-json.org new file mode 100644 index 0000000..d95234e --- /dev/null +++ b/20220604110059-json.org @@ -0,0 +1,69 @@ +:PROPERTIES: +:ID: 6c59d844-1f1a-440c-be78-4a9df286fab6 +:mtime: 20220604114243 +:ctime: 20220604110059 +:END: +#+title: Json + +* Introduction +*J*avaScript *O*bject *N*otation : + * Est un format de données textuelles dérivé de la notation des objets du langage /JavaScript/, + * Permet de représenter de l’information structurée, + * A été créé par Douglas Crockford entre 2002 et 2005, + * Première norme est ECMA-404, publiée en octobre 20032, + * Actuellement décrit par les deux normes en concurrence : RFC 82593 de l’IETF et ECMA-4044 de l'ECMA, denière version + publiée en décembre 2017. + +* Spécifications +Un document JSON comprend : + * 2 types composés : + * Objet ou dictionnaire (clés/valeurs), + * Liste ordonnées de valeurs, + * 4 types scalaires : + * Booléen : /true/ ou /false/, + * Nombre : décimal signé (pas de distinction entre entier et flottant), + * Chaîne de caractères : séquence de 0 ou plusieurs caractères Unicode entourée de guillemets, + * La valeur /null/ : une valeur vide. + +* Extensions de JSON +** JSON5 +Destiné à contourner les limitation de JSON : + * Les noms des champs ne sont plus entre guillemets, + * Ajout des commentaires (sur une ou plusieurs lignes), + * Support du format hexadécimal pour les nombres, + * Les nombres peuvent prendre les valeurs /Infinity/ ou /NaN/, + * Espaces blancs supplémentaires autorisés, + * Les /string/ peuvent être contenus entre apostrophes. +** HJSON +Destiné à contourner les limitation de JSON : + * Les champs peuvent être séparés par un retour à la ligne au lieu d'une virgule, + * Ajout des commentaires (sur une ou plusieurs lignes), + * Utilisation des guillemets pour encapsuler une /string/ n'est pas obligatoire, + * Les /string/ peuvent être écrites sur plusieurs lignes. + +* Description d'une stucture de données JSON (JSPEC) +JSPEC est un language décrivant une structure de données JSON (équivalent à xsd pour le xml). +Les fichiers de descripion JSPEC ont l'extension /*.jspec/. + +** Python +Le module /[[https://github.com/chrismalcolm/jspec][jspec]]/ : +#+BEGIN_SRC python :results output +# pip install jspect +from jspec import loads, check + +spec = loads('{"name": string, "age": int, ... }') + +chris = {"name": "Chris", "age": 26, "status": "online"} +bob = {"name": "Bob", "age": 34.5} + +print(f'{check(spec, chris) = }') +print(f'{check(spec, bob) = }') +#+END_SRC + +#+RESULTS: +: check(spec, chris) = (True, '') +: check(spec, bob) = (False, 'At location $ - exhausted JSON object, failed to match the following JSPEC pairs: ["age": int, ...]') + +* Références + * [[https://medium.com/@chrismalcolm_35838/the-best-json-validation-library-for-python-in-2022-b6de4f8014d4][The best json validation library for python in 2022 - Medium]] + * [[https://fr.wikipedia.org/wiki/JavaScript_Object_Notation][Javascript Object Notation - Wikipedia]] diff --git a/Introduction/320px-Drbd-arch_2022-05-25_22-05-47.png b/Introduction/320px-Drbd-arch_2022-05-25_22-05-47.png new file mode 100644 index 0000000000000000000000000000000000000000..02e116173c430d62afc690f07c09e97d165e8d17 GIT binary patch literal 32644 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 Sogjk8L5YjV2$u`$`TrkY{0uSx literal 0 HcmV?d00001 diff --git a/Introduction/640px-Drbd-arch_2022-05-25_22-06-08.png b/Introduction/640px-Drbd-arch_2022-05-25_22-06-08.png new file mode 100644 index 0000000000000000000000000000000000000000..09e53b7c6d39c3759626267507c2e73f0b4edaee GIT binary patch literal 75388 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 Zmi@0w`atu`TI`N=@PM&SnU-zjzW_qfJa7O2 literal 0 HcmV?d00001 diff --git a/Introduction/Diagramme_Matrix_fr_2022-02-26_10-29-56.svg b/Introduction/Diagramme_Matrix_fr_2022-02-26_10-29-56.svg new file mode 100644 index 0000000..9ed2995 --- /dev/null +++ b/Introduction/Diagramme_Matrix_fr_2022-02-26_10-29-56.svg @@ -0,0 +1,481 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ARCHITECTURE DU RÉSEAU MATRIX + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IRC + + + + + + Jitsi - visioconférence + + + + + + etc. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Home serveurs + + + + + + Applications serveurs + + + + + + Serveurs d’identité + + + + + + Clients + + + + + + Légende : + + + + + + + + + \ No newline at end of file diff --git a/Introduction/LVM1_2022-03-27_10-39-39.jpg b/Introduction/LVM1_2022-03-27_10-39-39.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e02c985a08c768f0736e2eaf2f5b29ac1226b12a GIT binary patch literal 40478 zcmb??WmFtr)8}A8g1g&5us{gzZZr7c8UX?W8C*k1fB?Z|fFZce;E(`=O9BKB0R|r= zxJ!a25McQ~@B8li?C#kQd(PeueQtMk->O^pc2{-%s%~d*mjRSuEsz!f=dL}(c?ba9 z&I8l|1bF!P_;>_&2Lb|udxWG!gm;bN{(WLnDhg_9Dhetp8hTa+8d_#LDk?@UMrJlP z4h{|)25ug1b{M|8u+j0-z$od5m+4hrN8q;Zew#IIx(-XwDfTdP%X7?l95MT3u5N&p8_wetw&EV@IH``l2+G#?&##> z>lgm@r-_9_L)R}!jdw-eJ$#xgrgv;u?*zcT8~fkH_=nG(AVhamMJfO;4jv9Z(VhPA z@bUhU^dBk}0X5g-lzTL^bgIS>FOz45<6>d8UG&^QadkrY3Jw^#rv-re9- zcvJvoz+SGVCw+plSL@HXB3cVaM0exSY*9ch<;UTMVVp9`B4G z>^JjpcCeNIct)Ply6CR^qRaD@E$B!F)g-78HL+gRny>xln~cE}UK+rwjpu&F|7X$u zFDk$rKKj%RX^oWItiWyoLcUcFYbETXz+ElB-ku-jWvOK5(+TZ@@(D^2UKVXJ9le#AT{zmF ztch}fhZX!qq5MU$fTCD_+*tksrZcU=gwoYavFG~9SVn*KDEVi$QdtHYKVK%$+Y zr)05~YqMO$;=}%po!$OW7&-vZb6H*=-tO~>Vn+J?_tie#CxIo5V?vCH|G9&jl~Bx z>wkQjKnw%5s6u_Z7Rs{5k2(arg56q2k!Ww8)#9HAStHqGbwYB9G~!qc&2r|w-(4#@ zUdPP`G+!6Y5uXy4DN`>d35`s|B!wHS=SDa(BFEdj-FawV5k$(^;0w((Qzelgk3ca( zx^snd!bM9hA6w5xGCq1CCY|*pB`>K03qg=y;iA-qp9wkXLQ*n6opr$fl$EN(gk4;w z%ON~=4$Ys1G$MsT=B$EBF@*k)*RpU&6F$B?u1T6N`r119N^)%}hTuc2wN>!@&#wl4 z3$L+fZtIMI_j1`Iy*_vb$yYE$01sJ(2M{Huav`#`fMzbIif2U~}r6pJ1RvY={EI`>{2q z9g{e>guVk*HPa4u|#>-cr z^~@`BTLLM3@5p6+>$?0%e_4#eaTg491_Asjq52@fq?un9-Ur~VkWY41zXGuj^c#6c ze=zO{8`yN|TwO085KiFXDY^>#gay7eSY)mrNg&J6Pu2?0M!(2WuW}e#0<9hxm~5x! zR3Xn8dB|fXGXfjQls!8QbSq*!64x_XMM>VdfFRNM@yobgGZ}*Vc*g3v#mrM)4*LH0 z$MDas)ot?qiV}9Pp&&tsSH#&MUbT8dti;zhgOXAoFc&O7iQgN)yUO3&jihV8WZiAD zT_>5zbH-C>F`=uu5J>EYwaQYhK9i zJf_)t)lglGQ4e8sY@Iv@n&WT z_CdcmGrq17QfdgVV#}kp42U#-AzbZM~ANT4^AIu95HWwHi->}wc5Q)o+@#N zRllqr{^k@!nt~R7)+wUK23W(<n6AC$YbGcBRZ+PjXp6Tph(1PPp?-NbIWaxqC zx(KdK{A)x{cVcN>g>4)xapnd;upbVm?j|I5g~=n}MM0FJUO!Uev(&5P)#{fH@I{?TvcI#aQp>!R)2<@~5=aRiVk+YK96N#^stm3i^^>jh4akk=(za z?ZcmTALOKRRi-)KtKJuwtW%V}x6d2=2?rR&Hsx?$(I{d;CixcA3Ll7TC|S+pX3Gnm zsI|MwE_CoD!P2z{B$Tp_;K;P^21^$Gfz}x$uldv*mY3;AnHQ|W;V35+m;rzk!VY7K2O?ji@50YdQV0WDMd`;DFEhKUuHjnT;mC1ZVIRF{a_VaJAohP zUW|)r)OM={mZ?}bgBa)I$@4jJ9W-f;2xyH}XpJ=i#+tM^cc)Z0-#nhz1w`!R-;>M)@HSLajBycnHLZ=LT%28M##Sm}%Q$^662Cj%A5ztG4^Nvv9m zcc*TQC!!Q_1;Ma8xmjqIx-4LQLJPp~99$v-Q-Uz--wB8F9kSma=LMCt!P2_YI4-a| zLMg1W)Y8TIZywuDdDntF_G%`$p!3`#e|PuPe~JkAgY?+K-m`ldge=d}8}2qeb>9k= zYs@ZQcs`iH&1jM^`1BGc7anf1fZl?`-)`d_s8k5ZvQCAssxFrnE3 z^dVqHy^vjEDCsC_>k6ejEQqAz(7x#LxyIygq^s!sn*}AgGpJGiwZ7x$@(OO9b)Oo1 zqvy91F&PBZy1ITKH5HPb78~tHq!Vv)8aK$}D|5_5<74GSN0vr8sin!e`>4L25gU2@dgxfiGbmCBywjT}x4pVM#-4Aup^M3y0WS@oy}%5Vv`Hti$Nl?{9cMjGU)?j|^C54|u;vTVifXnS+t-;EN(;-i43tXznlF1V%3i8%q&ihl3k*8ucS%VB2hthxiQ;+mI9w{wM z=%ZfN4d}*rfweZN4bFt|ivx}Eyjx708rhTsoA=O^ZO`c!0 z*IFCQkzK`R4Y9#z6->QUx+e)qsfoWYl|p+u<_!$mgHao!A#v+&;;owWy?Tj}=*V`S z=GClK*t(REd>EcW*r*_mafiRg{n?BaY{WsytYgiKOdr zmxww+x%8A^cQQL7gu>ZL(5o*FB23!2NHpS;0(=gPK>+-C{y^#`0 zNKGpmS;?fMgieiL2Eb38_Kzo|W~dFK(ajs*4O2xe>l#-f9Vja+qHdb7pGgnbW(>47 zw_(Uq?VTxysehJ`z!}3_G52~rd42Xt=Dq%ko`XfE27&jsaqG~R<(m@&0)i_A1sjzfV^3Jitt9OC23vU(5n8Cjt# z&l3kc6 z)ck>->T$;cJoN{m+hynM-`P8dYD>Y;^#!2V{GO9}Ntp1%QlEd6lHW3lb|HqjO0gI(U9znd zmRk${bZum zRzwq;+8Z%{m_6;)W;$biV%XFEp4)cWpvfjTqMrVE!fULjdbxDmQ%Ig+*qx-d2?Maq z5V+2q$4=~J%=PF$GLmCzs%xVm_R zEllh3P$;?BiK~E(8^{1spe_2ELe!NF`>ADj?mFA9$Wl_`!OBv)*z%9$DxHWis0&X- zq|bsj&w8>??A#o)pHu|euu*S3O%86JjP<$2t^&Akx>D{$wwsE5UrRBU~i>#=(e4jaw?mKf% zEANyb+Y%!`d7-m+>L&@>WFoaDs`!%vj%Ne=uWWPGzJ@55BC2K4cA$DLu1LxvYOz@% z84%Fjo2buQprQvG;Mlmx;Ws_a!k41i*vVywuPge()Qnm*gN68hANUonvgevsntt~P zHpz`S%08!k=)LT)&-tV@fs+YoU{}Mv3xF8w7yM*$X>u!xDWtK;PU%Ot&hxi#8^3kf z7Y?-s>510!-B+=ujB28-o7YF;7Jo|}7ThGruWZwtkb7dF^4Zq0xjBG(EXtwL)?@*a z91hsd*Jn@p!jd}rp=9{8o3;D6ciWfd?zT^D4_`Pfq_1~@;(L%0GF8;zAZo810I1@{ zS)pW((vU5t9(Hj*Yu{p;+tPFx?B!uIE=*CrvHbKI4iVEWpzg~qbQld2@}F$oLR)-j z+SMwye`r)}lg>^(76WG%GKP-q0Hi=*vvwrRcxwjW8?QZ=PFMbTLARmj0xd9o;RM2Gy23*)SP$=NZS+!#;#ATt81?H~y9W4q zxcbLv@sxt8)EeCm`aZMt$ zB&uY~r^Tk>+wAdS;`@?T;ZK7hPoMH{R5_B{wtbSr0h6@z3;z75y)c~@5oowNVzH`R zP_^4R@%D)O?YQULqAHH5j#8!XQt>Pwxq5Wh&Eu_WCc;bR zM5v^Lp7rNB=D(^2u0DJyhe9M4oMRLIoviH!zUzDFOk^6U|L6Yq>I3AF&7gBU%aODDt_jQpIOq>sM42t;Q99wNKd;j{f^*eH!cYtlwVRXi%|3rM8eS zoev1ulBeBMwhbl8+0v;#guickhfRvDlV6yC-|KWnMoL}gCyoJ}{#xsHPCPl41sQsuuGPyIeZZvhuc z^4kN-W7C8-{sqc?3P(lDw*XDOSl`P%>pZ2__bBWusubT;)3@u_1fQ<74$S6z`wQg=PBH`>ryCex<91thqctj$=l zXG6EO2rz7h(}Vm6cnu)5;=EV;xr?y*2L>k0VymhzH>*=CnyZD#dS-x9G}%z8 z`Afy|-is+jpj}Z<%gzC)(jy>hO{ZaLt*(T0yNsVdev1?^gjG;O!nCPm*-Qy=F#V-G zd9P0$msqEoNY#T zAt!niDqebE*I-L)WAFG<27n?H&~Ref7zK{=s2=_T&Y`37CUOfGEFAs(<*R7>+rOkW zu9VO1nRtI#Y3w*e6#zikJnV&iCmIbr z^HbQ%Jxz>H@tsas{L%J=*V+e~j;f(AJ1A@dqQB^tLnePLP#2k+ha0yG;dymyd~B3w zTUx2EKFMzwXx~9wicpQy%d@((bp^|a2ml#O(o5{0Z=QYUp?GZvjgIpqnvV@E zNCdNlF*2EJgR6t@O~~bqV$ zbK;96-*1D0`j>RM2b^)wH~ZqzQfuAni2`|o$to;#xr4wwfcc>*{`XPr3wXP$E-ZYG z+qCI0xxJHSJNDDpsC&}FB9zo%a41AuBCo*YJg1|#v+s?m)PuSxCt0R9knU{Wul%S$ zl3m=ooJ8B`QNijf{rwxK#+kyBb0`6oGt}nYOO3=N5WB_=a}ZD2Atx?9h)*6QE2-3i zor@_b!2*egU{W*I>{?ZW_BNb|!HutcJTyrwXLE$tmwg$VEKLcEdW)3 zexZ$i&=aJ#7EwC56mK135D74~4ic0(5_+;*ukz%R^PE9pzFSg{`*u`T3U4Q++9FKd zmG!%##3ipR{xUMrvNYl~GfGlFPsk2!dC(aAe4$}p0@l@*BNNSUl5+Qx*#n4oEZ1*o zkR1@<%Lpkh$b8SCX=T}D{laRci2zxP`y%j{5hbpTkD&2F%D{S_LwNzSAL8>w>x)|e zZm;*n;LZgBkLaX)aNXk;RiJk=oGNvKCje81ozMeyEIoX=Xf6IS{{10H0Aj)wf5r= z(6tZvJYJpuk?Rk5Gku*!qbHN?P^p)}xJp;Z9z`2e%K9gDI2~}}eo(Bk;Cp@e=iciX z2g^L&0Q#>CkQ0_0+&B#anDGE1>wuEPQJK6JhF)Z}*lj+cSAP~IHu0?!k%kf$Aw}XR zIP=Y_e78ie_wKWw7$$L!4Ksj7s_g;R)Jez4y7+G@kSaB!TRN@61r@;;jr zH9alO3j&sLhEtSH5n$~QFOUasP81s(Y=smS@s1!bRM85i>ybJ(In=b{3S-Ev4ulIwz+%`KNt{a(|;RP#0@MhcIa@>`!ar=bLa@+#jc{BT~B0cvu| zUzrLdPWqZ${<$Wfn$kM$asL=@>B;q-lJ~1rJIor6Gh_l6)%GgAbB*T{R(CwSM|{Jh zd2`{8=(KvF=vk{3*4%lDq8fD)#;xf}2|L#I^DWmOb4#V}ya^P&=mpzQ1ZPvP&e{NE zsOc#Rhe$_TA%_nn->cg*z#$Xpn94u?fa6b4{>iKQ^>rgFp*VMtKYc$afYX}odcaJY0fKkVR9-->MYColM^7c2={yjcN z;B=UCbCN-G6XJgJmc1NaqgC_7_u?X}68gG^dl-bIfR(}e`gLtO+1|1A%qp2Q;#Ja4S|sULHFvz5)S zHu+nCn6$L{;@=}ZoY zDEpf0kPzc9JD0P`B}ifgOuuDAgRj50rAICjF!J{f{#?lCkZSLAoT9ezDp+&pa`p2<3#ZKoydwWH_|b&01Ku>c<;$fsArQc|;fi&;L&P0K-PcSz48 z7+#dG6DsY!H+`*PLu8 z4WA%dCQ7{+SQ}>sZvlhbP}^Cv`LCw-`%c1teU@iZWW&20u&zL{zO@-C88tR z6%mE^D4PwXC^O!SV-%W**yDM>n&GRcI~1s*u{gl^S5nHiymS~tx{1)+aretOVGjr6 zC6HnuM~ptU`#?+*GefR8SLx;*xL}bBy{|KigNNy_O6)kxZ8+KwKMK1AIQ(viT$=6e zJMhTlde|CWxH!@rT2TE3XC~vJxopc&oY7kT!fewn)UJHWrqLqm?d2g;iK|>NQ6AOL1W7)wb++Kc*T}eGAx~`s=5AnTVibn>qn21qK$!mil0#db@aESg z#d(Xzjr*d01yr8OKk*kgMMsA``Pv(1HAGN@&>kEV?#3feHC(*ae%$B}0tY zOtP3~xwUc&u&-Flkl1YUo~mG#Gc@hj@6nW}`!L$)cab5>Kv&)gjN$MPltNalc zXr;SuV@`Xp;dZm5wa}RoXb=$%_kcU?b$r#3(-aE7*!vT!`4oR8j;h@kzws|dKvZx_ zxK#SvJ=^SJi4%rB;do0wZVzikpPJ31qlbJPlt`sC1XYhs@B&$(2Cu7Oak2^{V{