├── database └── .gitkeep ├── hardware └── .gitkeep ├── projets └── .gitkeep ├── CNAME ├── architecture ├── .gitkeep ├── index.md ├── faire-des-commits-au-quotidien.md └── les-messages-de-commit-dans-git.md ├── bots-discord └── .gitkeep ├── langages ├── cpp │ └── .gitkeep ├── haskell │ └── .gitkeep ├── javascript-typescript │ └── .gitkeep ├── index.md ├── c │ ├── index.md │ └── debunk-c │ │ ├── input-wrong.c │ │ ├── input.c │ │ └── ub-overflow.c ├── python │ ├── index.md │ ├── programmer-en-python-quel-ide-choisir.md │ ├── apprendre-python-quoi-lire-quoi-regarder.md │ ├── relatif-vs-absolu-demystifions-les-imports-python.md │ ├── critique-du-cours-video-sur-python-de-graven.md │ └── comment-structurer-un-projet-python.md ├── scala │ ├── index.md │ ├── programmer-en-scala-3-prerequis.md │ ├── programmer-en-scala-3-mutabilite-et-boucles.md │ ├── programmer-en-scala-3-types-et-variables.md │ ├── programmer-en-scala-3-booleens-et-conditions.md │ └── scala-comme-premier-langage.md └── php │ └── les-arrays-php-sont-ils-des-arrays.md ├── systeme-et-reseau └── .gitkeep ├── paradigmes ├── fonctionnel │ └── .gitkeep └── oriente-objet │ └── .gitkeep ├── .gitignore ├── index.md ├── robots.txt ├── game-dev ├── index.md └── ressources.md ├── assets ├── images │ ├── wtf.png │ ├── github-squash.png │ ├── memory-scheme.png │ ├── py-ide-langchart.png │ ├── variable-scheme.png │ ├── icons │ │ ├── logo_moche_128.png │ │ └── logo_moche_32.png │ ├── scala-repl-screenshot.png │ ├── variable-type-scala-scheme.png │ └── strip-Les-specs-cest-du-code.jpg └── css │ ├── extra.css │ └── normalize_custom.css ├── Gemfile ├── _config.yml ├── .github └── ISSUE_TEMPLATE │ ├── autre-chose-quun-article.yaml │ ├── proposer-un-article.yaml │ ├── commenter-un-article.yaml │ ├── corriger-un-article.yaml │ └── suggestion-de-ressource-de-gamedev.yaml ├── README.md ├── post-skeleton.md ├── _includes ├── opengraph.html └── date.html ├── sitemap.xml ├── _layouts ├── post.html ├── index.html └── default.html ├── CONTRIBUTING.md └── LICENSE /database/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hardware/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | hub.notaname.fr -------------------------------------------------------------------------------- /architecture/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bots-discord/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /langages/cpp/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /langages/haskell/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /systeme-et-reseau/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /paradigmes/fonctionnel/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /paradigmes/oriente-objet/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /langages/javascript-typescript/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _site/ 2 | .sass-cache 3 | Gemfile.lock 4 | -------------------------------------------------------------------------------- /index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: index 3 | title: Index 4 | --- 5 | -------------------------------------------------------------------------------- /robots.txt: -------------------------------------------------------------------------------- 1 | Sitemap: https://hub.notaname.fr/sitemap.xml 2 | -------------------------------------------------------------------------------- /game-dev/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: index 3 | title: Index 4 | --- 5 | -------------------------------------------------------------------------------- /langages/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: index 3 | title: Index 4 | --- 5 | -------------------------------------------------------------------------------- /architecture/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: index 3 | title: Index 4 | --- 5 | -------------------------------------------------------------------------------- /langages/c/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: index 3 | title: Index 4 | --- 5 | -------------------------------------------------------------------------------- /langages/python/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: index 3 | title: Index 4 | --- 5 | -------------------------------------------------------------------------------- /langages/scala/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: index 3 | title: Index 4 | --- 5 | -------------------------------------------------------------------------------- /assets/images/wtf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotANameServer/Not-a-Hub/HEAD/assets/images/wtf.png -------------------------------------------------------------------------------- /assets/images/github-squash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotANameServer/Not-a-Hub/HEAD/assets/images/github-squash.png -------------------------------------------------------------------------------- /assets/images/memory-scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotANameServer/Not-a-Hub/HEAD/assets/images/memory-scheme.png -------------------------------------------------------------------------------- /assets/images/py-ide-langchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotANameServer/Not-a-Hub/HEAD/assets/images/py-ide-langchart.png -------------------------------------------------------------------------------- /assets/images/variable-scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotANameServer/Not-a-Hub/HEAD/assets/images/variable-scheme.png -------------------------------------------------------------------------------- /assets/images/icons/logo_moche_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotANameServer/Not-a-Hub/HEAD/assets/images/icons/logo_moche_128.png -------------------------------------------------------------------------------- /assets/images/icons/logo_moche_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotANameServer/Not-a-Hub/HEAD/assets/images/icons/logo_moche_32.png -------------------------------------------------------------------------------- /assets/images/scala-repl-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotANameServer/Not-a-Hub/HEAD/assets/images/scala-repl-screenshot.png -------------------------------------------------------------------------------- /assets/images/variable-type-scala-scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotANameServer/Not-a-Hub/HEAD/assets/images/variable-type-scala-scheme.png -------------------------------------------------------------------------------- /assets/images/strip-Les-specs-cest-du-code.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NotANameServer/Not-a-Hub/HEAD/assets/images/strip-Les-specs-cest-du-code.jpg -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gem "github-pages", group: :jekyll_plugins 3 | gem "kramdown-parser-gfm", "~> 1.1" 4 | gem "webrick", "~> 1.8" 5 | -------------------------------------------------------------------------------- /langages/c/debunk-c/input-wrong.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(void){ 4 | int x ; 5 | scanf("%d", &x); 6 | printf("Input was: %d\n", x); 7 | } 8 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman 2 | 3 | title: Not a Hub 4 | lang: fr 5 | 6 | exclude: [ post-skeleton.md ] 7 | 8 | remote_theme: pages-themes/cayman@v0.2.0 9 | plugins: 10 | - jekyll/theme 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/autre-chose-quun-article.yaml: -------------------------------------------------------------------------------- 1 | name: Autre chose qu'un article 2 | description: Ouvrir une discussion sur l'organisation, le blog en général ou les outils qui gravitent autours 3 | labels: ['Meta'] 4 | -------------------------------------------------------------------------------- /langages/c/debunk-c/input.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(void){ 4 | int x ; 5 | int err ; 6 | do { 7 | err = scanf("%d", &x); 8 | if(err != 1) scanf ("%*[^\n]"); 9 | } while(err != 1); 10 | 11 | printf("Input was: %d\n", x); 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Not a Hub 2 | 3 | Blog technique de la communauté NaN, disponible à cette adresse: 4 | 5 | 6 | 7 | ## Comment contribuer ? 8 | 9 | Vous pouvez vous réferrer au [Guide de contribution](https://github.com/NotANameServer/Not-a-Hub/blob/master/CONTRIBUTING.md). -------------------------------------------------------------------------------- /langages/c/debunk-c/ub-overflow.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void function(int x){ 5 | int old = x ; 6 | x++ ; 7 | printf("X is: %d\n", x); 8 | if(x > old){ 9 | printf("%d > %d\n", x, old); 10 | } 11 | } 12 | 13 | int main(void){ 14 | int x = INT_MAX ; 15 | function(x); 16 | } 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/proposer-un-article.yaml: -------------------------------------------------------------------------------- 1 | name: Proposer un article 2 | description: Proposer un nouvel article pour le blog 3 | labels: ['Nouvel article'] 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: Sujet de l'article 8 | description: Décrivez votre sujet ici afin que l'on puisse en discuter 9 | validations: 10 | required: true -------------------------------------------------------------------------------- /post-skeleton.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | author: Anne Onyme 4 | date: YYYY-MM-DD 5 | title: "Gardez le titre entre quotes pour éviter de mauvaise surprises" 6 | --- 7 | 8 | À partir d'ici vous pouvez ajouter votre contenu. Notez qu'il n'y a pas besoin 9 | de titre de premier niveau (`#` en markdown) puisque celui-ci sera généré grâce 10 | aux méta-données renseignées plus haut. Aussi, dans cette partie, le plus haut 11 | niveau de titre sera de niveau 2 (`##` en markdown). 12 | -------------------------------------------------------------------------------- /_includes/opengraph.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/commenter-un-article.yaml: -------------------------------------------------------------------------------- 1 | name: Commenter un article 2 | description: Poser une question à propos d'un article 3 | labels: ['Question'] 4 | body: 5 | - type: input 6 | id: url-article 7 | attributes: 8 | label: URL de l'article concerné 9 | validations: 10 | required: true 11 | - type: textarea 12 | attributes: 13 | label: À propos de cet article 14 | description: 'Écrivez votre question ci-dessous' 15 | validations: 16 | required: true 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/corriger-un-article.yaml: -------------------------------------------------------------------------------- 1 | name: Corriger un article 2 | description: Apporter une correction ou une précision à propos d'un article 3 | labels: ['Amélioration'] 4 | body: 5 | - type: input 6 | id: url-article 7 | attributes: 8 | label: URL de l'article concerné 9 | validations: 10 | required: true 11 | - type: textarea 12 | attributes: 13 | label: À propos de cet article 14 | description: 'Écrivez ce que vous souhaiteriez modifier ci-dessous' 15 | validations: 16 | required: true 17 | -------------------------------------------------------------------------------- /assets/css/extra.css: -------------------------------------------------------------------------------- 1 | body 2 | { 3 | color: #505B5E; 4 | } 5 | 6 | .main-content h1, .main-content h2, .main-content h3, .main-content h4, .main-content h5, .main-content h6 7 | { 8 | color: #17874F; 9 | } 10 | 11 | .project-tagline 12 | { 13 | opacity: 1; 14 | } 15 | 16 | .btn 17 | { 18 | color: white; 19 | border-color: rgba(255,255,255,0.6); 20 | } 21 | 22 | .btn:hover 23 | { 24 | color: white; 25 | background-color: rgba(255,255,255,0.16); 26 | } 27 | 28 | .main-content code 29 | { 30 | color: #5E7382; 31 | } 32 | 33 | .main-content blockquote 34 | { 35 | color: #67787E; 36 | border-left-color: #7198BC; 37 | } 38 | 39 | .site-footer-credits 40 | { 41 | color: #67777E; 42 | } 43 | 44 | address 45 | { 46 | font-style: normal; 47 | } 48 | -------------------------------------------------------------------------------- /sitemap.xml: -------------------------------------------------------------------------------- 1 | --- 2 | layout: null 3 | --- 4 | 5 | 6 | 7 | 8 | {% for page in site.html_pages %} 9 | {% if page.name != "index.md" %} 10 | 11 | {{ site.url }}{{ page.url | remove: "index.html" }} 12 | {% if page.last_update %} 13 | {{ page.last_update | date_to_xmlschema }} 14 | {% else %} 15 | {% if page.date %} 16 | {{ page.date | date_to_xmlschema }} 17 | {% else %} 18 | {{ site.time | date_to_xmlschema }} 19 | {% endif %} 20 | {% endif %} 21 | monthly 22 | 0.3 23 | 24 | {% endif %} 25 | {% endfor %} 26 | 27 | 28 | -------------------------------------------------------------------------------- /_includes/date.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | We use this file to print out dates because GitHub pages does not support the 3 | Jekyll plugin dedicated to locales ... 4 | 5 | !!BEWARE!! 6 | _DO NOT_ introduce an empty line at EOF, it would introduce spurious spaces 7 | when displaying dates. 8 | {% endcomment %} 9 | 10 | {% assign m = include.the_date | date: "%-m" %} 11 | {{ include.the_date | date: "%-d" }} 12 | {% case m %} 13 | {% when '1' %}Janvier 14 | {% when '2' %}Février 15 | {% when '3' %}Mars 16 | {% when '4' %}Avril 17 | {% when '5' %}Mai 18 | {% when '6' %}Juin 19 | {% when '7' %}Juillet 20 | {% when '8' %}Août 21 | {% when '9' %}Septembre 22 | {% when '10' %}Octobre 23 | {% when '11' %}Novembre 24 | {% when '12' %}Decembre 25 | {% endcase %} 26 | {{ include.the_date | date: "%Y" }} -------------------------------------------------------------------------------- /_layouts/post.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |
6 |
7 |

{{ page.title }}

8 |
9 |
Par {{ page.author }}
10 | Le 11 | {% if page.last_update %} 12 | (Dernière mise à jour le ) 13 | {% endif %} 14 |
15 | Tags : 16 | {% assign tags = page.dir | split: "/" %} 17 | {% assign idx_path = "/" %} 18 | {% for tag in tags %} 19 | {% if tag != "" %} 20 | {% assign idx_path = idx_path | append: tag | append: "/" %} 21 | {{ tag }} 22 | {% endif %} 23 | {% endfor %} 24 |
25 |
26 |
27 | {{ content }} 28 |
29 | -------------------------------------------------------------------------------- /_layouts/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | {% assign tag = page.dir | split: "/" | last %} 6 | 7 |

Index{% if tag %} : {{ tag }} {% endif %}

8 | 9 | {% assign posts_by_year = site.html_pages 10 | | where_exp: "post", "post.name != 'index.md'" 11 | | where_exp: "post", "post.dir contains page.dir" 12 | | group_by_exp: "post", "post.date | date: '%Y'" 13 | | sort: "name" 14 | | reverse 15 | %} 16 | 17 | {% for year in posts_by_year %} 18 |

{{ year.name }}

19 |
    20 | {% assign posts = year.items | sort: "date" | reverse %} 21 | {% for post in posts %} 22 |
  • 23 | {{ post.title }} 24 | {% assign tags = post.dir | split: "/" %} 25 | {% assign idx_path = "/" %} 26 | {% for tag in tags %} 27 | {% if tag != "" %} 28 | {% assign idx_path = idx_path | append: tag | append: "/" %} 29 | {{ tag }} 30 | {% endif %} 31 | {% endfor %} 32 |
  • 33 | {% endfor %} 34 |
35 | {% endfor %} 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/suggestion-de-ressource-de-gamedev.yaml: -------------------------------------------------------------------------------- 1 | name: Suggestion de ressource de gamedev 2 | description: Ajout d'une ressource a la page "Ressources pour game-dev" 3 | labels: ['Gamedev resources'] 4 | body: 5 | - type: input 6 | id: nom 7 | attributes: 8 | label: Nom 9 | validations: 10 | required: true 11 | - type: input 12 | id: lien 13 | attributes: 14 | label: Lien 15 | validations: 16 | required: true 17 | - type: input 18 | id: licence 19 | attributes: 20 | label: Licence 21 | validations: 22 | required: true 23 | - type: input 24 | id: source-licence 25 | attributes: 26 | label: URL où se trouve la licence 27 | - type: input 28 | id: prix 29 | attributes: 30 | label: Prix 31 | description: Indiquer gratuit, ou le prix si payant 32 | validations: 33 | required: true 34 | - type: input 35 | id: meta 36 | attributes: 37 | label: Meta 38 | description: >- 39 | Metadonnéée(s)/tag(s) à choisir dans: Animation, Audio, Background, 40 | Decals, Font, HDRI, Landscape, Modèle, PBR, Sprite, Texture, Tileset, UI -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ page.title }} | {{ site.title }} 6 | 7 | {% include opengraph.html %} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 26 |
27 | {{ content }} 28 | 36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /langages/python/programmer-en-python-quel-ide-choisir.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | author: Julien Castiaux 4 | date: 2021-08-30 5 | last_update: 2023-05-28 6 | title: "Programmer en Python, quel IDE choisir" 7 | --- 8 | 9 | Python est un langage suffisamment simple pour qu'un IDE complet ne soit pas *nécessaire* à avoir une bonne vélocité de 10 | développement. Contrairement aux langages à la syntaxe plus lourde, il n'est pas forcément nécessaire d'avoir toute une 11 | série de patterns automatiquement générés par l'éditeur. 12 | 13 | Python a un système de module léger : contrairement à Java où une classe = un fichier, il n'est pas rare d'avoir toute 14 | une série de classes liées définies au sein d'un même module. Ceci limite drastiquement la quantité d'`import` 15 | nécessaires en début de fichier et extensiblement la nécessité d'avoir ces lignes d'`import` faites automatiquement. 16 | 17 | Python propose nativement un environnement REPL (Read-Eval-Print-Loop) via le terminal ou IDLE. Il est donc aisé de 18 | tester un bout de code, d'accéder à la documentation au moyen de la fonction `help` ou de lister l'ensemble des 19 | méthodes/fonctions d'une classe ou d'un module via la fonction `dir`. Nous trouvons qu'il est tout aussi rapide 20 | d'ouvrir l'interpréteur et de taper `dir(list)` suivi de `help(list.extend)` que d'utiliser la documentation fournie 21 | par un IDE ou de laisser l'autocomplétion faire des suggestions. 22 | 23 | Python est dynamiquement typé. Bien qu'il existe des outils comme [mypy](http://mypy-lang.org) qui peuvent aider à la 24 | vérification des types, peu de modules tirent avantage de la bibliothèque `typing` et un IDE se retrouve fort peu utile 25 | pour lever des warning en cas d'incohérence de types. 26 | 27 | Tout ceci explique pourquoi un IDE n'est pas *nécessaire* au développement en Python. Un éditeur de texte plus léger 28 | reste un choix tout aussi viable. 29 | 30 | ## Mais donc quel IDE ? 31 | 32 | Au final il n'y pas d'éditeur qui se démarque particulièrement des autres. Il s'agit surtout d'en choisir un et se 33 | l'approprier. 34 | 35 | La liste suivante présente une liste non exhaustive : 36 | 37 | * [IDLE](https://docs.python.org/3/library/idle.html) (pro: installé de base, con: vraiment limité) 38 | * [Neovim](https://neovim.io/) (pro: très puissant, con: difficile à prendre en main) 39 | * [Onivim](https://v2.onivim.io/) (pro: interface graphique moderne, con: projet très jeune) 40 | * [Emacs](https://www.gnu.org/software/emacs/) (pro: très puissant, con: difficile à prendre en main) 41 | * [Sublime Text](https://www.sublimetext.com) (pro: léger et rapide, con: pas fort extensible) 42 | * [VS Code](https://code.visualstudio.com/) (pro: complet et extensible, con: lourd) 43 | * [Pulsar](https://pulsar-edit.dev/) (pro: moderne et open-source, con: lent au démarrage) 44 | * [Pycharm](https://www.jetbrains.com/pycharm/) (pro: IDE complet, con: très lourd) 45 | 46 | ## Qu'est-ce qu'utilise la populace ? 47 | 48 | ![IDEs chart]({{ site.baseurl }}/assets/images/py-ide-langchart.png) 49 | -------------------------------------------------------------------------------- /langages/scala/programmer-en-scala-3-prerequis.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | author: Raphaël Fromentin 4 | date: 2022-07-05 5 | last_update: 2023-05-21 6 | title: "Programmer en Scala 3 : Les prérequis" 7 | --- 8 | 9 | ## Qu'est-ce que Scala ? 10 | 11 | Scala, dont le nom veut dire "Scalable language" (Langage qui s'adapte à la taille du projet), est un langage orienté objet et fonctionnel créé par Martin Odersky. 12 | 13 | Scala est un langage dit "general purpose", qui n'est pas centré sur un domaine précis. Toutefois, le langage brille particulièrement dans deux domaines: 14 | - Systèmes concurrents et distribués, c'est à dire des systèmes se coordonant et se répartissant les tâches. (LinkedIn, Twitter, Netflix...) 15 | - Traitement des données, Big Data. (Netflix, Disney+, AdColony...) 16 | 17 | Les entreprises citées ci-dessus utilisent Scala pour plusieurs raisons dont: 18 | - La polyvalence: le langage est très versatile et peut-être adapté au domaine que l'on souhaite 19 | - La maintenabilité: le langage dispose de nombreuses fonctionnalités permettant de rendre son code durable et d'éviter au maximum les bugs 20 | - Les performances: l'implémentation principale du langage, la JVM (que nous aborderons plus tard), a été conçue pour être très performante et adaptée aux programmes gérant de larges volumes de données/d'utilisateurs. 21 | 22 | ## Installation 23 | 24 | Maintenant que nous avons brièvement présenté le langage, nous allons commencer la pratique. 25 | 26 | *Note: La dernière version de Scala à la date où cet article est écrit est la 3.1.3. La plupart des exemples seront cependant utilisables tels quels en Scala 3.0 ou supérieur.* 27 | 28 | Scala peut être installé sur n'importe quel système UNIX (Mac OS, Linux) et également sur Windows. Pour ce tutoriel et les futurs cours, nous utiliserons [Scala CLI](https://scala-cli.virtuslab.org), un outil pour facilement créer de petits programmes en Scala. En voici la [page d'installation](https://scala-cli.virtuslab.org/install). 29 | 30 | ## Lancement d'un premier programme en Scala 31 | 32 | Une fois Scala CLI installé nous pouvons écrire notre premier programme. Commençons d'abord par créer un premier fichier que nous appellerons `hello.sc`. Le nom n'a pas d'importance mais si vous en choisissez un autre, pensez à remplacer également le nom du fichier utilisé dans les commandes énoncées plus tard. 33 | 34 | Ouvrons le fichier et commençons avec un classique de la programmation: un "Hello World". Il s'agit d'un programme qui consiste juste à afficher la phrase "Hello World" ("Bonjour Monde") une fois lancé. Voici à quoi ressemble un tel programme en Scala 3: 35 | ```scala 36 | println("Hello World") 37 | ``` 38 | 39 | *Note: `println` sert à afficher du texte dans la console puis retourner à la ligne.* 40 | 41 | Ouvrez maintenant votre terminal à l'endroit où vous avez créé le fichier et entrez la commande suivante: 42 | 43 | ```sh 44 | scala-cli hello.sc 45 | ``` 46 | 47 | Vous obtiendrez la sortie suivante: 48 | ``` 49 | Hello World 50 | ``` 51 | 52 | Félicitations, vous venez de lancer votre premier programme écrit en Scala ! 53 | 54 | 55 | ## Conclusion 56 | 57 | Nous avons vu dans ce chapitre les différents prérequis pour programmer en Scala après en avoir fait une présentation. Nous aborderons la prochaine fois les bases du langage. 58 | 59 | Voici quelques liens utiles: 60 | - [Site officiel du langage](https://scala-lang.org/) 61 | - [Site de Scala CLI](https://scala-cli.virtuslab.org/) 62 | 63 | *Suite : [Types et variables](./programmer-en-scala-3-types-et-variables)* 64 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Comment contribuer 2 | 3 | Tous les contributeurs sont priés de respecter les règles de NaN qui sont disponibles sur notre serveur Discord et sur le repository [https://github.com/NotANameServer/discord/](https://github.com/NotANameServer/discord/). Les articles doivent être rédigés en français, les messages de commit ainsi que l'activité sur GitHub peuvent être fait en français ou en anglais. Il est préférable de commencer par ouvrir une issue pour ouvrir une discussion avant d'ouvrir une pull-request pour proposer les changements. 4 | 5 | Nous distingons la rédaction des articles (`add`/`edit`/`fix`) des contributions au site-même (`meta`). Il vous est possible de tester vos modifications soit en local en [installant jekyll](https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll/testing-your-github-pages-site-locally-with-jekyll) et quelques [dépendances](https://github.com/NotANameServer/Not-a-Hub/blob/master/Gemfile) supplémentaires sur votre propre machine. Il vous est aussi possible de fork ce repository et de changer un paramettre (repo > settings > pages > branch) pour laisser github s'occuper du rendu de votre branche. 6 | 7 | ## Contribuer aux articles 8 | 9 | Nous avons créé toute une série de templates dans les issues pour vous guider. Vous pouvez [proposer un nouvel article](https://github.com/NotANameServer/Not-a-Hub/issues/new?assignees=&labels=article&template=proposer-un-article.yaml&title=%5BNOUVEL+ARTICLE%5D+) afin d'ouvrir une discussion sur ce que devrait contenir cet article avant de proposer un premier brouillon via une pull-request. Vous pouvez également [commenter un article existant](https://github.com/NotANameServer/Not-a-Hub/issues/new?assignees=&labels=article&template=commenter-un-article.yaml&title=%5BCommentaire%5D+) pour rapporter une erreur ou simplement ouvrir une discussion ou poser une question sur cet article. 10 | 11 | ## Contribuer au site 12 | 13 | Nous vous recommandons de commencer par ouvrir une nouvelle issue "vierge" (sans passer par les templates) pour ouvrir la discussion quant à ce que vous voudriez changer. Vous pouvez ensuite proposer vos changements via une pull-request. 14 | 15 | ## Convention pour les messages de commit 16 | 17 | Chaque message doit commencer par un titre séparé du reste du commit par une ligne vide. Le titre doit de préférence ne pas dépasser 50 caractères et doit commencer par l'un des tags suivant: 18 | 19 | - `add: ` pour l'ajout d'un nouvel article 20 | - `edit: ` pour la modification du contenu sémantique d'un article 21 | - `fix: ` pour une correction orthographique ou grammaticale 22 | - `meta: ` pour les contributions au site même 23 | 24 | Faites l'effort de rédiger un message de commit, notez à minimal un paragraphe qui explique *pourquoi* ces changements sont bienvenues et référencez aussi la/les issues auxquelles votre commit est lié. 25 | 26 | ## Indexage des articles 27 | 28 | Lorsque vous ajoutez ou renommez un article, pensez à ajouter un entête de cette forme au début du fichier markdown: 29 | 30 | ``` 31 | --- 32 | layout: post 33 | author: 34 | date: 35 | title: "Le titre de votre article" 36 | --- 37 | 38 | ``` 39 | 40 | Cela permettra à l'action GitHub Pages d'indexer automatiquement votre article. Lorsque vous mettez à jour un article, vous pouvez aussi ajouter le champ `last_update` dans l'entête pour indiquer la date de dernière mise à jour tout en conservant la date de publication initiale. 41 | 42 | Pensez également à placer le fichier markdown dans le bon dossier correspondant au sujet de votre article pour que les tags appropriés soient automatiquement ajoutés. 43 | Par exemple un article placé dans le dossier `/langages/python/` aura les tags `langages` et `python`. 44 | -------------------------------------------------------------------------------- /langages/python/apprendre-python-quoi-lire-quoi-regarder.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | author: Julien Castiaux 4 | date: 2021-08-30 5 | last_update: 2021-09-11 6 | title: "Apprendre Python, quoi lire, quoi regarder" 7 | --- 8 | 9 | La documentation officielle, votre principale source de connaissance (aussi disponible en français) : 10 | 11 | Parmi la documentation, voici quelques pages que nous jugeons essentielles à lire : 12 | 13 | * 14 | * 15 | * 16 | * 17 | * 18 | 19 | ## Cours vidéos 20 | 21 | * En profondeur (conseillé) : 22 | * Expéditif (nous avons quelques [critiques](https://docs.drlazor.be/python_graven.md)) : 23 | 24 | ## Cours écrits 25 | 26 | * Complet (conseillé) : 27 | * Assez complet : 28 | * Pour ceux venant d'autres langages : 29 | 30 | ## Notions avancées 31 | 32 | * La philosophie du langage (must read !) : 33 | * Le style de rédaction à privilégier : 34 | * Orienté objet : 35 | * Classes, dundler, descripteurs et itérateurs : 36 | * Tutoriels sur la bibliothèque standard : 37 | * Un livre reprenant des centaines de recettes de code pour explorer les possibilités de Python et de sa bibliothèque standard : 38 | * Un livre qui rentre en profondeur dans l'écosystème Python et qui s'adresse à un public plus confirmé : 39 | 40 | ## Thèmes 41 | 42 | * Desktop (Tcl/Tk) : , et 43 | * Desktop (Qt) : 44 | * Mobile : 45 | * Web : 46 | * Jeux-vidéos : , 47 | * Programmation réseau : 48 | * Programmation scientifique : et 49 | 50 | ## Conférences 51 | 52 | Deux conférenciers à suivre : Raymond Hettinger et David Beazley 53 | 54 | * 55 | * 56 | 57 | En vrac quelques conférences que nous jugeons particulièrement intéressantes : 58 | 59 | * [Transforming Code into Beautiful, Idiomatic Python](https://youtu.be/OSGv2VnC0go) (attention, c'est du Python 2) 60 | * [Asynchronous Python for the Complete Beginner](https://www.youtube.com/watch?v=iG6fr81xHKA) 61 | * [What Does It Take To Be An Expert At Python?](https://youtu.be/7lmCu8wz8ro) 62 | * [Unicode what is the big deal](https://youtu.be/7m5JA3XaZ4k) 63 | * [Keynote on Concurrency](https://youtu.be/9zinZmE3Ogk) 64 | * [Beyond PEP 8 -- Best practices for beautiful intelligible code](https://youtu.be/wf-BqAjZb8M) 65 | 66 | ## Outils et distribution 67 | 68 | * Choisir son éditeur de texte : 69 | * `pip`, pour installer des bibliothèques externes : 70 | * `poetry`, pour gérer vos projets : 71 | * Structurer et partager vos projets : 72 | 73 | ## J'en veux plus 74 | 75 | Des centaines de ressources à lire en plus : 76 | -------------------------------------------------------------------------------- /langages/scala/programmer-en-scala-3-mutabilite-et-boucles.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | author: Raphaël Fromentin 4 | date: 2023-05-14 5 | last_update: 2023-05-21 6 | title: "Programmer en Scala 3 : Mutabilité et boucles" 7 | --- 8 | 9 | *Précédemment : [Booléens et conditions](./programmer-en-scala-3-booleens-et-conditions)* 10 | 11 | ## La mutabilité 12 | 13 | Nous avons vu précédemment un moyen de stocker des valeurs sous un nom grâce aux variables. Cependant, nous n'avons jusqu'à présent qu'utilisé des variables dites "immuables", que l'on ne peut modifier. Vous avez peut-être tenté de faire la chose suivante : 14 | 15 | ```scala 16 | val x = 1 17 | x = 3 18 | ``` 19 | 20 | en obtenant une erreur : 21 | 22 | ```scala 23 | -- [E052] Type Error: ---------------------------------------------------------- 24 | 1 |x = 3 25 | |^^^^^ 26 | |Reassignment to val x 27 | | 28 | | longer explanation available when compiling with `-explain` 29 | ``` 30 | 31 | Pourtant, pouvoir changer la valeur d'une variable est parfois bien utile ! Par exemple le nombre de vies de Mario change quand il meurt ou ramasse 100 pièces. Son état (grand, petit, Mario de feu...) change également. 32 | 33 | Nous pouvons le faire en Scala en utilisant un autre mot clé : `var`. À l'instar de `val`, `var` permet de déclarer une variable à la différence près que la valeur de celle-ci peut-être changée. 34 | 35 | ```scala 36 | var x = 1 37 | x = 3 38 | 39 | println(x) //3 40 | ``` 41 | 42 | Bien sûr, il est possible de combiner cette fonctionnalité avec les autres vues précédemment : 43 | 44 | ```scala 45 | var vies = 1 46 | vies = vies + 1 //+1 Up ! 47 | 48 | println(vies) //2 49 | ``` 50 | 51 | Le langage propose aussi quelques contractions comme : 52 | 53 | | Contracté | Non-contracté | 54 | | --------- | ------------- | 55 | | `x += y` | `x = x + y` | 56 | | `x -= y` | `x = x - y` | 57 | | `x *= y` | `x = x * y` | 58 | | `x /= y` | `x = x / y` | 59 | 60 | Ainsi, le code ci-dessus peut s'abréger en : 61 | 62 | ```scala 63 | var vies = 1 64 | vies += 1 65 | 66 | println(vies) //Toujours 2 67 | ``` 68 | 69 | Note : en Scala et dans d'autres langages, on préfère utiliser `val` par défaut pour ne pas se soucier d'un potentiel changement de valeur. On utilise alors `var` uniquement en cas de besoin. 70 | 71 | ## Les boucles 72 | 73 | Reprenons l'exemple de notre jeu Mario. Nous avons parfois besoin d'exécuter une même action plusieurs fois : 74 | - Tant qu'il reste du temps (et que Mario n'est pas mort), la partie continue. 75 | - Quand Mario lance une boule de feu, celle-ci rebondit 3 fois. 76 | 77 | En Scala, ces répétitions peuvent être décrites en utilisant les boucles. Il en existe deux types. 78 | 79 | ### La boucle while 80 | 81 | En anglais, "while" peut se traduire en "alors que"/"tant que". La boucle `while` répète une action tant qu'une condition (représentée par un booléen) est satisfaite. 82 | 83 | Elle se présente sous cette forme : 84 | 85 | ```scala 86 | while condition do 87 | action 88 | ``` 89 | 90 | À titre d'exemple, faisons un petit décompte avec cette boucle : 91 | 92 | ```scala 93 | var restant = 5 94 | while restant > 0 do 95 | println(restant) 96 | restant -= 1 97 | 98 | println("Fin du temps") 99 | ``` 100 | 101 | Sortie : 102 | 103 | > 5\ 104 | > 4\ 105 | > 3\ 106 | > 2\ 107 | > 1\ 108 | > Fin du temps 109 | 110 | `println(restant)` 111 | 112 | ### La boucle for 113 | 114 | En anglais, "for" veut dire "pour"... Ça ne nous aide pas ! La boucle `for` peut-être utilisée pour répéter une action n fois. Le code suivant aura la même sortie que notre précédent exemple : 115 | 116 | ```scala 117 | for restant <- 5 until 0 do 118 | println(restant) 119 | 120 | println("Fin du temps") 121 | ``` 122 | 123 | Ici, `restant` va prendre à la première itération ("tour de boucle") la valeur 5, puis 4 pour la seconde itération, puis 3, ... jusqu'à 1. `5 until 0` veut dire "de 5 jusqu'à 0 exclus" 124 | 125 | Dans le cas des boules de feu de Mario, nous pouvons écrire le code suivant : 126 | 127 | ```scala 128 | //Le nombre de rebonds que peut faire une boule de feu 129 | val rebonds = 3 130 | 131 | for i <- 0 until rebonds do 132 | val rebondActuel = i + 1 133 | println(rebondActuel + " rebonds effectués") 134 | 135 | println("La boule de feu a disparu") 136 | ``` 137 | 138 | Sortie : 139 | 140 | > 1 rebonds effectués\ 141 | > 2 rebonds effectués\ 142 | > 3 rebonds effectués\ 143 | > La boule de feu a disparu 144 | 145 | Note : en réalité, la boucle `for` en Scala est plus puissante que ça mais nous en reparlerons plus tard. 146 | 147 | ## Conclusion 148 | 149 | Nous avons aujourd'hui vu comment répéter une action un certain nombre de fois ou selon une condition. Les boucles et les conditions sont à la base de la plupart des langages de programmation. 150 | 151 | Nous verrons d'autres utilisations de la boucle `for` dans un prochain article ainsi qu'un projet pour mettre en œuvre les notions que nous avons précédemment vues. 152 | -------------------------------------------------------------------------------- /langages/scala/programmer-en-scala-3-types-et-variables.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | author: Raphaël Fromentin et TheElectronWill 4 | date: 2022-08-15 5 | last_update: 2023-05-21 6 | title: "Programmer en Scala 3 : Types et Variables" 7 | --- 8 | 9 | *Précédemment : [Prérequis](./programmer-en-scala-3-prerequis)* 10 | 11 | ## Les variables 12 | 13 | ### Introduction 14 | 15 | Un programme informatique manipule souvent un grand nombre d'informations. Prenons l'exemple d'un jeu vidéo comme Mario Bros : le jeu doit stocker la santé de Mario (petit, grand...), le nombre de pièces ou encore le nombre de vies restantes. 16 | 17 | Ces données sont stockées dans la mémoire de l'ordinateur (ou de la console de jeux) à une certaine adresse. Chaque case mémoire possède une adresse qui permet de lire et d'écrire la valeur qui y est stockée, un peu comme un grand tableau. 18 | 19 | ![adresses mémoires]({{ site.baseurl }}/assets/images/memory-scheme.png) 20 | 21 | *Remarque : notez que la première adresse est l'adresse 0, et non l'adresse 1 ! C'est courant en informatique de compter à partir de zéro. Puisque c'est le premier chiffre, ne pas l'utiliser pour numéroter reviendrait à "perdre" un numéro.* 22 | 23 | Dans des temps reculés, il fallait retenir à quelle adresse était stockée chaque valeur ce qui, avec le nombre de valeurs à stocker, devenait rapidement compliqué ! Où as-tu stocké le nombre de pièces déjà ? Adresse 756 ou 759 ? 24 | 25 | C'est pour résoudre ce problème que les variables ont été créées. L'idée est d'utiliser des noms compréhensibles par les humains, et de laisser la machine s'occuper des adresses automatiquement. C'est un peu comme si on mettait une étiquette sur les adresses des cases mémoires. On appelle le nom qu'on utilise (l'étiquette choisie) et la valeur qui lui est associée (stockée dans la case mémoire) une **variable**. 26 | 27 | ![variables]({{ site.baseurl }}/assets/images/variable-scheme.png) 28 | 29 | Et voilà ! Pas besoin de retenir les adresses mémoires, il suffit de retenir le nom de la variable ! 30 | 31 | ### Utilisation 32 | 33 | En Scala, une variable se crée en utilisant le mot-clé `val` suivi d'un égal et d'une valeur: 34 | 35 | ```scala 36 | val x = 1 37 | println(x) // 1 38 | ``` 39 | 40 | Dans ce petit exemple, nous avons créé une variable avec pour nom `x` et pour valeur `1`. On peut alors dire que "x vaut 1". Nous avons ensuite affiché la valeur de `x` avec `println`. Le suffixe `ln` est une abbréviation de "line", il signifie que l'on affiche une valeur puis que l'on saute une ligne. 41 | 42 | Ainsi, le code suivant : 43 | 44 | ```scala 45 | println(1) 46 | println(2) 47 | ``` 48 | 49 | affiche chaque valeur sur une ligne différente : 50 | 51 | > 1\ 52 | > 2 53 | 54 | 55 | Bien sûr, Scala n'est pas limité aux nombres. Il est aussi possible d'afficher du texte, en l'écrivant entre guillemetes doubles (`"`). Par exemple : 56 | 57 | ```scala 58 | println("Bonjour !") 59 | ``` 60 | 61 | affiche : 62 | 63 | > Bonjour ! 64 | 65 | ## Les types 66 | 67 | En cuisine, il est important de savoir ce que l'on manipule. (Comment ça je ne peux pas couper des oignons avec une cuillère ?!) 68 | 69 | En programmation aussi ! On aimerait bien savoir : 70 | - le genre de valeur que l'on est en train d'utiliser / on a le droit d'utiliser : un nombre entier ? du texte ? une liste de courses ? 71 | - ce que l'on peut faire avec : multiplier deux nombres semble logique et utile, mais multiplier deux bouts de texte... moins. 72 | - comment l'ordinateur doit stocker les informations dans les cases mémoires 73 | 74 | Toutes ces informations sont données par le **type** de la valeur. 75 | 76 | En Scala, toute valeur et toute variable possède un type. Le type d'une variable est connu avant le lancement du programme, et restreint les valeurs qu'elle accepte. Par exemple, une variable de type "nombre entier" ne peut contenir que des nombres entiers, et pas du texte. Le langage définit certains types de base, et il est possible d'en définir de nouveaux (nous verrons cela plus tard). 77 | 78 | Voici quelques types de base : 79 | 80 | | Type | Ce qu'on peut mettre dedans | Exemple de valeur | 81 | |--------|-----------------------------|-------------------| 82 | | Int | un nombre entier | `1` | 83 | | Double | un nombre décimal | `1.5` | 84 | | String | du texte | `"Bonjour !"` | 85 | 86 | Ajoutons les types sur notre schéma. On voit que chaque variable a finalement un nom, un type, une adresse mémoire, et une valeur. Par exemple : 87 | - `x` a pour type `Int`, pour adresse `0` et pour valeur `1`. 88 | - `nombrePieces` a pour type `Int`, pour adresse `756` et pour valeur `102`. 89 | 90 | ![variables typées]({{ site.baseurl }}/assets/images/variable-type-scala-scheme.png) 91 | 92 | ### Opérations 93 | 94 | Comme nous l'avons dit plus haut, le type permet de connaître les opérations que l'on peut faire avec les valeurs. Certaines opérations ne sont possibles qu'avec certains types. C'est le cas de la plupart des opérations arithmétiques : 95 | 96 | ```scala 97 | 4 - 1 // 3 98 | "4" - "1" // Erreur ! Comment soustraire deux textes ? 99 | ``` 100 | 101 | Nous pouvons écrire `4 - 1` car le type `Int` définit un "opérateur" `-` (prononcé "moins"), qui effectue une soustraction entre deux nombres entiers. En revanche, le type `String` ne définit pas cet opérateur, et Scala nous informe donc le code `"4" - "1"` n'est pas valide. 102 | 103 | Certains opérateurs existent pour plusieurs types mais se comportent différemment. C'est le cas de l'opérateur `+` qui additionne les nombres et concatène les textes (c'est-à-dire qui les colle bout à bout). Ne vous a-t-on jamais fait la blague "1 et 1 font 11" ? Nous pouvons rencontrer le même genre de facétie en informatique : 104 | 105 | ```scala 106 | 1 + 1 // 2: Int (entier) 107 | 1.0 + 1.0 // 2.0: Double (décimal) 108 | "1" + "1" // 11: String (texte) 109 | ``` 110 | 111 | `1 + 1` font `2` car il s'agit de deux entiers que l'on additionne tandis que `"1" + "1"` font `"11"` car il s'agit de deux textes que l'on concatène. `1` et `"1"` n'ont pas le même type et se comportent donc différemment malgré leur apparente similitude. 112 | 113 | En plus des soustractions et des additions, on peut également effectuer des multiplications avec l'opérateur `*` et des divisions avec l'opérateur `/`. Pas de surprise avec les multiplications, mais attention avec la division. Si on divise deux nombres entiers, le résultat est un nombre entier *tronqué*. Si on divise deux nombres décimaux, le résultat est un nombre décimal. 114 | 115 | ```scala 116 | 6 / 2 // 3: Int 117 | 5 / 2 // 2: Int - Attention: la partie décimale est tronquée (supprimée) 118 | 5.0 / 2.0 // 2.5: Double 119 | 5 / 2.0 // 2.5: Egalement un Double ! 120 | ``` 121 | 122 | ### Type explicite 123 | 124 | En Scala, les variables possèdent toutes un type. Mais, comme vous l'avez peut-être remarqué, nous n'avons jamais écrit ce type dans notre code pour l'instant ! En effet, Scala est capable de trouver automatiquement le type des variables, en fonction de la valeur que l'on donne lors de leur création. On appelle ce mécanisme _l'inférence de type_. 125 | 126 | Reprenons le premier exemple de ce chapitre : 127 | ```scala 128 | val x = 1 129 | ``` 130 | 131 | `x` est ici une variable qui stocke des nombres entiers, son type est donc `Int`. Scala a trouvé le type tout seul, il n'est pas écrit. 132 | 133 | Il est possible d'expliciter le type d'une variable en écrivant après son nom `: NomDuType` : 134 | 135 | ```scala 136 | val x: Int = 1 // toujours de type Int, mais explicitement écrit 137 | ``` 138 | 139 | ### Conversions implicites 140 | 141 | Les valeurs de certains types peuvent être converties vers un autre. C'est le cas des nombres entiers vers les nombres décimaux : 142 | ```scala 143 | val x: Int = 1 // 1: Entier 144 | val y: Double = x // 1.0: Nombre décimal 145 | ``` 146 | 147 | Notons cependant que ce n'est pas réciproque : 148 | ```scala 149 | val x: Double = 1.0 150 | val y: Int = x // Erreur 151 | ``` 152 | 153 | Dans le cas présent, cela est dû au fait qu'un nombre décimal n'est pas forcément un entier : `1.0` a comme équivalent `1` mais quel est l'équivalent entier de `1.5` ? 154 | 155 | ## Conclusion 156 | 157 | Nous avons vu comment stocker des données en utilisant les variables. Nous avons également parlé de la notion de type lié aux valeurs manipulées, importante en Scala. 158 | 159 | Nous aborderons dans le cours suivant les conditions. 160 | 161 | *Suite : [Booléens et conditions](./programmer-en-scala-3-booleens-et-conditions)* 162 | -------------------------------------------------------------------------------- /assets/css/normalize_custom.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.highlight table td{padding:5px}.highlight table pre{margin:0}.highlight .cm{color:#999988;font-style:italic}.highlight .cp{color:#999999;font-weight:bold}.highlight .c1{color:#999988;font-style:italic}.highlight .cs{color:#999999;font-weight:bold;font-style:italic}.highlight .c,.highlight .cd{color:#999988;font-style:italic}.highlight .err{color:#a61717;background-color:#e3d2d2}.highlight .gd{color:#000000;background-color:#ffdddd}.highlight .ge{color:#000000;font-style:italic}.highlight .gr{color:#aa0000}.highlight .gh{color:#999999}.highlight .gi{color:#000000;background-color:#ddffdd}.highlight .go{color:#888888}.highlight .gp{color:#555555}.highlight .gs{font-weight:bold}.highlight .gu{color:#aaaaaa}.highlight .gt{color:#aa0000}.highlight .kc{color:#000000;font-weight:bold}.highlight .kd{color:#000000;font-weight:bold}.highlight .kn{color:#000000;font-weight:bold}.highlight .kp{color:#000000;font-weight:bold}.highlight .kr{color:#000000;font-weight:bold}.highlight .kt{color:#445588;font-weight:bold}.highlight .k,.highlight .kv{color:#000000;font-weight:bold}.highlight .mf{color:#009999}.highlight .mh{color:#009999}.highlight .il{color:#009999}.highlight .mi{color:#009999}.highlight .mo{color:#009999}.highlight .m,.highlight .mb,.highlight .mx{color:#009999}.highlight .sb{color:#d14}.highlight .sc{color:#d14}.highlight .sd{color:#d14}.highlight .s2{color:#d14}.highlight .se{color:#d14}.highlight .sh{color:#d14}.highlight .si{color:#d14}.highlight .sx{color:#d14}.highlight .sr{color:#009926}.highlight .s1{color:#d14}.highlight .ss{color:#990073}.highlight .s{color:#d14}.highlight .na{color:#008080}.highlight .bp{color:#999999}.highlight .nb{color:#0086B3}.highlight .nc{color:#445588;font-weight:bold}.highlight .no{color:#008080}.highlight .nd{color:#3c5d5d;font-weight:bold}.highlight .ni{color:#800080}.highlight .ne{color:#990000;font-weight:bold}.highlight .nf{color:#990000;font-weight:bold}.highlight .nl{color:#990000;font-weight:bold}.highlight .nn{color:#555555}.highlight .nt{color:#000080}.highlight .vc{color:#008080}.highlight .vg{color:#008080}.highlight .vi{color:#008080}.highlight .nv{color:#008080}.highlight .ow{color:#000000;font-weight:bold}.highlight .o{color:#000000;font-weight:bold}.highlight .w{color:#bbbbbb}.highlight{background-color:#f8f8f8}*{box-sizing:border-box}body{padding:0;margin:0;font-family:"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;font-size:16px;line-height:1.5;color:#606c71}#skip-to-content{height:1px;width:1px;position:absolute;overflow:hidden;top:-10px}#skip-to-content:focus{position:fixed;top:10px;left:10px;height:auto;width:auto;background:#e19447;outline:thick solid #e19447}a{color:#1e6bb8;text-decoration:none}a:hover{text-decoration:underline}.btn{display:inline-block;margin-bottom:1rem;color:rgba(255,255,255,0.7);background-color:rgba(255,255,255,0.08);border-color:rgba(255,255,255,0.2);border-style:solid;border-width:1px;border-radius:0.3rem;transition:color 0.2s, background-color 0.2s, border-color 0.2s}.btn:hover{color:rgba(255,255,255,0.8);text-decoration:none;background-color:rgba(255,255,255,0.2);border-color:rgba(255,255,255,0.3)}.btn+.btn{margin-left:1rem}@media screen and (min-width: 64em){.btn{padding:0.75rem 1rem}}@media screen and (min-width: 42em) and (max-width: 64em){.btn{padding:0.6rem 0.9rem;font-size:0.9rem}}@media screen and (max-width: 42em){.btn{display:block;width:100%;padding:0.75rem;font-size:0.9rem}.btn+.btn{margin-top:1rem;margin-left:0}}.page-header{color:#fff;text-align:center;background-color:#159957;background-image:linear-gradient(120deg, #155799, #159957)}@media screen and (min-width: 64em){.page-header{padding:5rem 6rem}}@media screen and (min-width: 42em) and (max-width: 64em){.page-header{padding:3rem 4rem}}@media screen and (max-width: 42em){.page-header{padding:2rem 1rem}}.project-name{margin-top:0;margin-bottom:0.1rem}@media screen and (min-width: 64em){.project-name{font-size:3.25rem}}@media screen and (min-width: 42em) and (max-width: 64em){.project-name{font-size:2.25rem}}@media screen and (max-width: 42em){.project-name{font-size:1.75rem}}.project-tagline{margin-bottom:2rem;font-weight:normal;opacity:0.7}@media screen and (min-width: 64em){.project-tagline{font-size:1.25rem}}@media screen and (min-width: 42em) and (max-width: 64em){.project-tagline{font-size:1.15rem}}@media screen and (max-width: 42em){.project-tagline{font-size:1rem}}.main-content{word-wrap:break-word}.main-content :first-child{margin-top:0}@media screen and (min-width: 64em){.main-content{max-width:64rem;padding:2rem 6rem;margin:0 auto;font-size:1.1rem}}@media screen and (min-width: 42em) and (max-width: 64em){.main-content{padding:2rem 4rem;font-size:1.1rem}}@media screen and (max-width: 42em){.main-content{padding:2rem 1rem;font-size:1rem}}.main-content kbd{background-color:#fafbfc;border:1px solid #c6cbd1;border-bottom-color:#959da5;border-radius:3px;box-shadow:inset 0 -1px 0 #959da5;color:#444d56;display:inline-block;font-size:11px;line-height:10px;padding:3px 5px;vertical-align:middle}.main-content img{max-width:100%}.main-content h1,.main-content h2,.main-content h3,.main-content h4,.main-content h5,.main-content h6{margin-top:2rem;margin-bottom:1rem;font-weight:normal;color:#159957}.main-content p{margin-bottom:1em}.main-content code{padding:2px 4px;font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;font-size:0.9rem;color:#567482;background-color:#f3f6fa;border-radius:0.3rem}.main-content pre{padding:0.8rem;margin-top:0;margin-bottom:1rem;font:1rem Consolas, "Liberation Mono", Menlo, Courier, monospace;color:#567482;word-wrap:normal;background-color:#f3f6fa;border:solid 1px #dce6f0;border-radius:0.3rem}.main-content pre>code{padding:0;margin:0;font-size:0.9rem;color:#567482;word-break:normal;white-space:pre;background:transparent;border:0}.main-content .highlight{margin-bottom:1rem}.main-content .highlight pre{margin-bottom:0;word-break:normal}.main-content .highlight pre,.main-content pre{padding:0.8rem;overflow:auto;font-size:0.9rem;line-height:1.45;border-radius:0.3rem;-webkit-overflow-scrolling:touch}.main-content pre code,.main-content pre tt{display:inline;max-width:initial;padding:0;margin:0;overflow:initial;line-height:inherit;word-wrap:normal;background-color:transparent;border:0}.main-content pre code:before,.main-content pre code:after,.main-content pre tt:before,.main-content pre tt:after{content:normal}.main-content ul,.main-content ol{margin-top:0}.main-content blockquote{padding:0 1rem;margin-left:0;color:#819198;border-left:0.3rem solid #dce6f0}.main-content blockquote>:first-child{margin-top:0}.main-content blockquote>:last-child{margin-bottom:0}.main-content table{display:block;width:100%;overflow:auto;word-break:normal;word-break:keep-all;-webkit-overflow-scrolling:touch}.main-content table th{font-weight:bold}.main-content table th,.main-content table td{padding:0.5rem 1rem;border:1px solid #e9ebec}.main-content dl{padding:0}.main-content dl dt{padding:0;margin-top:1rem;font-size:1rem;font-weight:bold}.main-content dl dd{padding:0;margin-bottom:1rem}.main-content hr{height:2px;padding:0;margin:1rem 0;background-color:#eff0f1;border:0}.site-footer{padding-top:2rem;margin-top:2rem;border-top:solid 1px #eff0f1}@media screen and (min-width: 64em){.site-footer{font-size:1rem}}@media screen and (min-width: 42em) and (max-width: 64em){.site-footer{font-size:1rem}}@media screen and (max-width: 42em){.site-footer{font-size:0.9rem}}.site-footer-owner{display:block;font-weight:bold}.site-footer-credits{color:#819198} -------------------------------------------------------------------------------- /langages/scala/programmer-en-scala-3-booleens-et-conditions.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | author: Raphaël Fromentin et TheElectronWill 4 | date: 2023-03-10 5 | last_update: 2023-05-21 6 | title: "Programmer en Scala 3 : Booléens et conditions" 7 | --- 8 | 9 | *Précédemment : [Types et variables](./programmer-en-scala-3-types-et-variables)* 10 | 11 | ## Les booléens 12 | 13 | Nous avons vu précédemment comment stocker des valeurs et les manipuler en utilisant des opérations. 14 | 15 | Dans le monde réel, un programme informatique a souvent besoin de vérifier si certaines propositions sont vraies ou 16 | fausses. En reprenant l'exemple du jeu Mario Bros, le jeu doit vérifier si : 17 | 18 | - Mario est vivant 19 | - Mario possède encore des vies 20 | - Mario a 100 pièces 21 | - etc... 22 | 23 | Une proposition est représentée par un type de données très courant en informatique : un booléen. Ce type ne peut 24 | prendre que deux valeurs : "vrai" ou "faux". 25 | 26 | En Scala, le type "booléen" s'écrit `Boolean`. Les valeurs "vrai" et "faux" s'écrivent respectivement `true` et `false`. 27 | 28 | ```scala 29 | val x = true // "vrai" 30 | val y = false // "faux" 31 | ``` 32 | 33 | ### Opérations booléennes 34 | 35 | À l'instar des types de données vus dans le cours précédent, les booléens possèdent des opérations qui leur sont propres dont trois principales, "non", "ou", "et". 36 | On les appelle également des "opérations logiques". 37 | 38 | *Note : ces opérations sont issues de l'[Algèbre de Boole](https://fr.wikipedia.org/wiki/Algèbre_de_Boole_(logique)) 39 | , souvent étudié en classe préparatoire ou en licence.* 40 | 41 | #### Non 42 | 43 | L'opération "non" est une opération qui retourne "vrai" si le booléen donné est "faux" et "faux" si le booléen est " 44 | vrai". En pratique, on peut dire que cette opération "inverse" le booléen donné : 45 | 46 | | Entrée | Résultat | 47 | |--------|----------| 48 | | Vrai | Faux | 49 | | Faux | Vrai | 50 | 51 | Le "non" en Scala s'écrit `!` avant la valeur à "inverser" : 52 | 53 | ```scala 54 | !true // false 55 | ``` 56 | 57 | #### Ou 58 | 59 | Le "ou" est une opération qui prend deux booléens pour renvoyer "vrai" si __au moins__ un des deux booléens l'est également. On parle aussi de "ou inclusif" : 60 | 61 | | A | B | Résultat | 62 | |------|------|----------| 63 | | Vrai | Vrai | Vrai | 64 | | Vrai | Faux | Vrai | 65 | | Faux | Vrai | Vrai | 66 | | Faux | Faux | Faux | 67 | 68 | Cette opération s'écrit `||` en Scala : 69 | 70 | ```scala 71 | true || false // true 72 | ``` 73 | 74 | #### Et 75 | 76 | L'opération "et", à l'instar du "ou", prend deux booléens. Elle renvoie "vrai" si et seulement si les deux booléens sont 77 | vrais : 78 | 79 | | A | B | Résultat | 80 | |------|------|----------| 81 | | Vrai | Vrai | Vrai | 82 | | Vrai | Faux | Faux | 83 | | Faux | Vrai | Faux | 84 | | Faux | Faux | Faux | 85 | 86 | Cette opération s'écrit `&&`: 87 | 88 | ```scala 89 | true && true // true 90 | ``` 91 | 92 | ## Les comparaisons 93 | 94 | En plus des opérations entre booléens citées ci-dessus, Scala met à disposition d'autres opérations qui retournent des booléens comme les comparaisons. 95 | 96 | ### Égalité 97 | 98 | L'opérateur classique en Scala pour vérifier l'égalité entre deux valeurs est le double égal `==` : 99 | 100 | ```scala 101 | 1 == 2 // false 102 | 4 == 4 // vrai 103 | "a" == "a" // vrai 104 | ``` 105 | 106 | Attention : comme vu dans le cours précédent, certaines valeurs, bien que similaires, n'ont pas le même type ! Elles ne 107 | sont donc pas égales : 108 | 109 | ```scala 110 | "1" == 1 // false 111 | ``` 112 | 113 | Comme la négation de l'égalité ("est différent de") est une opération très courante, Scala comme beaucoup d'autres 114 | langages propose un opérateur dédié pour éviter de faire `!(a == b)`. Cet opérateur est noté `!=` : 115 | 116 | ```scala 117 | !(1 == 2) //true 118 | 1 != 2 //true 119 | ``` 120 | 121 | ### Inégalités 122 | 123 | Pour les types numériques, Scala propose en opérateurs les inégalités de base : 124 | 125 | ```scala 126 | 1 < 3 //infériorité stricte 127 | 1 <= 3 //infériorité large (inférieur ou égal) 128 | 1 > 3 //supériorité stricte 129 | 1 >= 3 //supériorité large (supérieur ou égal) 130 | ``` 131 | 132 | *Attention : pour les inégalités larges, le "égal" doit être placé après le symbole `<` ou `>`. En Scala, l'opérateur `=>` 133 | existe également et n'a aucun lien avec `>=`.* 134 | 135 | ## Les conditions 136 | 137 | Comme indiqué dans l'introduction, les conditions permettent de faire certaines actions si une proposition, c'est-à-dire un 138 | booléen, est vrai ou faux. 139 | 140 | Par exemple, __si__ Mario est mort, __alors__ il faut lui soustraire une vie. __Si__ Mario a encore une vie __alors__ on 141 | le fait recommencer le niveau __sinon__, c'est un Game Over. 142 | 143 | ### Si ... alors 144 | 145 | À l'instar de la plupart des langages de programmation, Scala propose un moyen simple de définir une condition, en 146 | utilisant les mots-clés `if` (si) et `then` (alors): 147 | 148 | ```scala 149 | if true then 150 | println("hello") 151 | 152 | if false then 153 | println("world") 154 | ``` 155 | 156 | Sortie: 157 | 158 | > hello 159 | 160 | Seul le `println("hello")` a été exécuté car la proposition testée est vraie (`true`). 161 | 162 | La valeur passée entre le `if` et le `then` est tout simplement un booléen comme vu ci-dessus. Toute opération/variable 163 | produisant un booléen est donc utilisable ! 164 | 165 | #### Indentation 166 | 167 | Notez dans cet exemple ainsi que le précédent les deux espaces avant le `println` : 168 | 169 | ```scala 170 | val pieces = 100 171 | 172 | //Dans le jeu Mario Bros, Mario gagne une vie toutes les 100 pièces. 173 | if pieces == 100 then 174 | println("+1Up") 175 | println("Félicitations") 176 | ``` 177 | 178 | Ces deux espaces, appelés "indentation" permettent d'indiquer que ces deux lignes sont dans la condition et pas en dehors : 179 | 180 | ```scala 181 | val pieces = 100 182 | 183 | //Dans le jeu Mario Bros, Mario gagne une vie toutes les 100 pièces. 184 | if pieces == 100 then 185 | println("+1Up") 186 | println("Félicitations") 187 | ``` 188 | 189 | Dans ce nouvel exemple, "Félicitations" sera affiché peu importe si la condition a été remplie. 190 | 191 | Avec 100 pièces : 192 | 193 | > +1Up\ 194 | > Félicitations 195 | 196 | Avec un nombre pièces différent de 100 : 197 | 198 | > Félicitations 199 | 200 | L'indentation est donc cruciale : si elle n'est plus la même alors le code peut ne pas avoir la même signification ! 201 | 202 | Une condition qui n'exécute qu'une seule ligne/instruction peut être également écrite sur une ligne sans indentation : 203 | 204 | ```scala 205 | if pieces == 100 then println("+1Up") 206 | ``` 207 | 208 | Cette forme plus compacte est couramment utilisée en Scala pour de petites conditions. 209 | 210 | L'indentation n'est pas propre aux conditions en Scala. Elle s'applique à presque toutes les structures du langage : conditions, boucles, fonctions (que nous aborderons plus tard) et même variables : 211 | 212 | ```scala 213 | val x = 214 | 2 215 | ``` 216 | 217 | ### Sinon 218 | 219 | Dans de nombreux cas, nous voudrions exécuter une action si la proposition testée est vraie et une autre si elle est 220 | fausse. Par exemple : 221 | 222 | ```scala 223 | val sante = 2 224 | 225 | if sante > 0 then 226 | println("Mario est vivant") 227 | 228 | if sante <= 0 then 229 | println("Mario est mort") 230 | ``` 231 | 232 | Ce programme en l'état va afficher "Mario est vivant". Si je change la première ligne par `val sante = 0` (ou un nombre 233 | négatif) alors il affichera "Mario est mort". 234 | 235 | Ce cas de figure est très courant en informatique, c'est pourquoi la plupart des langages possèdent un mot clé "sinon" 236 | pour raccourcir ces deux conditions en une seule. En Scala, il s'agit du mot-clé `else`: 237 | 238 | ```scala 239 | val sante = 2 240 | 241 | if sante > 0 then 242 | println("Mario est vivant") 243 | else 244 | println("Mario est mort") 245 | ``` 246 | 247 | Cette condition peut se traduire par 248 | 249 | > Si `sante` est supérieur à 0, alors afficher "Mario est vivant", sinon afficher "Mario est mort". 250 | 251 | Le `else` nous a permis d'éviter une répétition et de rendre notre code plus lisible. 252 | 253 | ### Condition en tant que valeur 254 | 255 | En Scala, la plupart des structures du langage sont des expressions, c'est-à-dire qu'elles renvoient une valeur. C'est le cas 256 | de la condition `if ... else`: 257 | 258 | ```scala 259 | val sante = 2 260 | 261 | val statut = 262 | if sante > 0 then 263 | "Vivant" 264 | else 265 | "Mort" 266 | 267 | println("Mario est " + statut) 268 | ``` 269 | 270 | > Mario est vivant 271 | 272 | Et si nous changeons la valeur de `sante` à 0 : 273 | 274 | > Mario est mort 275 | 276 | La condition étant une valeur comme une autre, on peut l'utiliser partout à la place d'une variable ou d'une valeur 277 | directement écrite. On appelle cette fonctionnalité "if as expression" ou en français "if en tant qu'expression". 278 | 279 | ## Conclusion 280 | 281 | Nous avons aujourd'hui vu comment exécuter des actions suivant une condition en Scala. Cela nous permet maintenant de 282 | faire plus que de simples opérations mathématiques. 283 | 284 | Nous aborderons dans le prochain chapitre les boucles qui nous permettront de répéter des actions. 285 | 286 | *Suite : [Mutabilité et boucles](./programmer-en-scala-3-mutabilite-et-boucles)* 287 | -------------------------------------------------------------------------------- /langages/php/les-arrays-php-sont-ils-des-arrays.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | author: Adrien Baudet 4 | date: 2023-08-21 5 | title: "Les arrays php sont-ils des arrays ?" 6 | --- 7 | Si vous êtes utilisateur de PHP, cette question vous déroutera peut-être, ou vous donnera l'impression d'être face à une blague. Au cours de cet article, j'espère néanmoins vous avoir convaincu de sa pertinence. 8 | 9 | 10 | ## Que sont les arrays de php ? 11 | 12 | Une chose assez commune en programmation c'est que **les noms ne sont pas toujours consistants entre deux langages** : une boucle for en Python s'écrira foreach en PHP; un hashmap en Java sera un map en Javascript, ou un dictionary en C#. En fait cela vient tout simplement de la diversités des langages et de leurs façons de faire : Il n'y a pas une façon parfaite qui domine les autres mais plusieurs approches, souvent complémentaires, pour résoudre des problèmes et le vocabulaire d'un langage va s'adapter à son approche. 13 | 14 | Qu'en est-il alors des arrays de PHP ? Voyons ce que la [documentation](https://www.php.net/manual/en/language.types.array.php) a à nous apprendre : 15 | 16 | 17 | > An array in PHP is actually an ordered map. A map is a type that associates values to keys. This type is optimized for several different uses; it can be treated as an array, list (vector), hash table (an implementation of a map), dictionary, collection, stack, queue, and probably more. As array values can be other arrays, trees and multidimensional arrays are also possible. 18 | 19 | 20 | Ici on parle de map, comme je le citais plus haut, mais on mentionne aussi de nombreux autres usages. Cela veux donc dire que l'array php serait tout cela à la fois ? Pour mieux comprendre, je vous propose une définition des collections. 21 | 22 | 23 | ## Quels types de collections existe-t-il ? 24 | 25 | Pour cette partie je vais définir des collections d'un point de vue purement algorithmique, c'est à dire qu'on ne s'attachera ni à un langage ni à une implémentation précise : on visera un contexte plus général et abstrait. On pourra citer quatre types principaux de collections : 26 | 27 | 28 | ### Les arrays 29 | 30 | Un array est une collection linéraire où les éléments sont placés les uns après les autres. Chaque élément est accessible dans l'array à partir de sa position, en commençant généralement par la position 0. 31 | 32 | Par exemple : 33 | ``` 34 | array = [1, 14, 42] 35 | ``` 36 | 37 | Ici on pourra accéder à la valeur `1` via la position `0` (e.g : `array[0]`) et la valeur `42` à la position `2`. Si on venait à supprimer la valeur `14`, alors la valeur `42` serait accessible à la position `1`, puisqu'il vient de passer une place en avant. 38 | 39 | Selon l'implémentation, il arrive qu'un array soit de taille fixe. Quand l'array n'est pas de taille fixe, il est alors courant d'ajouter des valeurs au début où à la fin du array, offrant alors la possibilité de modifier leurs positions. 40 | 41 | 42 | ### Les listes chainées 43 | 44 | Une liste chainée est une collection dynamique constituée d'une paire contenant un élément et un lien vers une paire suivante contenant l'élément suivant et un nouveau lien, et ce autant de fois qu'il y a d'éléments dans la liste. 45 | 46 | 47 | ### Les dictionnaires 48 | 49 | Un dictionnaire est une collection de paires de clés-valeurs. On n'accède plus à une valeur par le biais de sa position mais par le biais de sa clé correspondante. De plus il n'est pas nécessaire d'utiliser des clés numériques : on peut utiliser potentiellement n'importe quel type scalaire, comme des chaines de caractères. On peut alors associer cette collection à un dictionnaire physique, où chaque définition est accédée via le terme d'elle définit. 50 | 51 | Par exemple : 52 | ``` 53 | dictionnaire = [ 54 | "eau" => "du liquide", 55 | "la reponse" => "42", 56 | ] 57 | ``` 58 | 59 | Ici on pourra accéder à la réponse en faisait `dictionnaire["la reponse"]` 60 | 61 | 62 | ### Les sets 63 | 64 | Un set est une collection de valeurs en exemplaires uniques, c'est à dire qu'il n'est pas possible pour cette collection de posséder des duplicatas. 65 | 66 | Par exemple : 67 | ``` 68 | set = {1, 3, 46, 72} 69 | ``` 70 | 71 | Dans cet exemple tenter de rajouter les valeurs `3`, ou `72` n'aura aucun effet car elles sont déjà présentes dans le set. 72 | 73 | 74 | ### Ordonnés ou pas ? 75 | 76 | Petite subtilité qui nous servira pour après : on a dit plus haut que dans le cas d'un array les valeurs étaient récupérables d'après leur position. Comme ce n'est pas le cas pour le dictionnaire et le set, il est courant de les retrouver sous une forme non ordonnée : c'est à dire qu'en les parcourant on ne retrouvera pas les valeurs dans l'ordre où elle ont été insérées. 77 | 78 | 79 | ## Qu'en est-il donc du array de php ? 80 | 81 | Si on regarde à nouveau la [documentation](https://www.php.net/manual/en/language.types.array.php) de php on peut lire : 82 | 83 | ```php 84 | // An array can be created using the array() language construct. It takes any number of comma-separated key => value pairs as arguments. 85 | // A short array syntax exists which replaces array() with []. 86 | 87 | array( 88 | key => value, 89 | key2 => value2, 90 | key3 => value3, 91 | ... 92 | ) 93 | ``` 94 | 95 | On parle bien ici de paires de clé-valeur : l'array php est donc bien un dictionnaire ? 96 | 97 | 98 | ### Mais qu'en est-il de la syntaxe sans clés ? 99 | 100 | En effet il existe une seconde syntaxe possible où aucune clé n'est renseignée. Si on regarde la doc : 101 | 102 | ```php 103 | // The key is optional. If it is not specified, PHP will use the increment of the largest previously used int key. 104 | 108 | 109 | // The above example will output: 110 | array(4) { 111 | [0]=> 112 | string(3) "foo" 113 | [1]=> 114 | string(3) "bar" 115 | [2]=> 116 | string(5) "hello" 117 | [3]=> 118 | string(5) "world" 119 | } 120 | ``` 121 | 122 | De fait, certes on n'a mentionné aucune clé mais elles sont toujours là, **les clés sont seulement définie implicitement par l'interpréteur'**. 123 | 124 | À partir de ce constat, les arrays de PHP ont tout pour être des dictionnaires. Néanmoins, la possibilité d'accéder aux valeurs comme dans un array implique une subtilité supplémentaire en terme d'ordonancement. Comme je disais plus haut, les dictionnaires sont généralemrent non ordonnés ce qui implique que l'ordre d'insertion n'est pas conservé, contrairement aux arrays. Il ne s'agit donc pas de simples dictionnaires, mais de `dictionnaires ordonnés`. 125 | 126 | 127 | ## Qu'en est-il à l'usage ? 128 | 129 | Malgré cette conclusion, il me semblait intéressant d'ajouter un dernier point sur l'usage que l'on peut avoir des arrays PHP, en dehors se sa pure définition. 130 | 131 | En premier lieu et pour simplifier mon propos je vais introduire le terme de liste. Une liste en PHP est un dictionnaire dont les clés sont constituées de nombres consécutifs de 0 à count($array)-1. Cela correspond à la définition fournie par la fonction standard de PHP [array_is_list](https://www.php.net/manual/en/function.array-is-list.php). 132 | 133 | Par extension et si on reprend les exemples de syntaxe au dessus, une liste est ce que founit PHP quand on ne mentionne aucune clé dans son array, soit le cas le plus éloigné syntaxiquement du dictionnaire. 134 | 135 | 136 | ### Tests du array PHP 137 | 138 | Pour tester comment PHP traite les listes, j'ai testé le comportement de plusieurs fonctions qui insèrent ou enlèvent des valeurs pour voir comment les clés sont traitées. Les fonctions testées sont: 139 | 140 | - array_shift 141 | - array_filter 142 | - array_slice 143 | - array_splice 144 | - array_splice pour un ajout 145 | - array_unshift 146 | - shuffle (car il modifie l'ordre) 147 | 148 | Pour être sûr de ne rien louper je fais les tests avec 149 | 150 | - un array (associatif) avec clés explicites 151 | - une liste avec clés explicites 152 | - une liste avec clés explicites dans l'ordre inverse 153 | - une liste avec clés implicites. 154 | 155 | Pour `array_shift` et `array_filter`, mon script ressemble à ceci : 156 | 157 | ```php 158 | 0, "un" => 1, "deux" => 2]; 160 | // $numbers = [0 => 0, 1 => 1, 2 => 2]; 161 | // $numbers = [2 => 0, 1 => 1, 0 => 2]; 162 | $numbers = [0, 1, 2]; 163 | 164 | 165 | $test = $numbers; 166 | array_shift($test); 167 | var_dump("Array shift"); 168 | var_dump($test); 169 | 170 | $test = array_filter($numbers, fn($value) => ($value % 2) !== 0); 171 | var_dump("Array filter"); 172 | var_dump($test); 173 | ``` 174 | 175 | Avec ce code on obtiendra le résultat suivant : 176 | 177 | ```php 178 | string(11) "Array shift" 179 | array(2) { 180 | [0]=> 181 | int(1) 182 | [1]=> 183 | int(2) 184 | } 185 | 186 | string(12) "Array filter" 187 | array(1) { 188 | [1]=> 189 | int(1) 190 | } 191 | ``` 192 | 193 | On remarque que pour array_shift les clés ont été changées, mais que pour array_filter la clé reste la même pour la valeur restante. 194 | 195 | 196 | ### Résultats 197 | 198 | Ce que j'ai pu constater d'abord c'est que dans les deux cas de liste, les résultats sont identiques, et ce n'est pas tellement surprenant. On remarque aussi que pour toutes les fonctions hormis `array_filter`, les clés sont modifiées pour qu'un array reste une liste. 199 | 200 | Dans le cas d'un array non liste, au contraire, les clés ne sont pas modifiées, hormis la fonction `shuffle`, qui semble transformer l'array en liste. 201 | 202 | On notera aussi que splice va traiter l'offset de modification d'après ordre dans lequel les valeurs sont insérées et non pas d'après les clés ainsi `array_splice($test, 1, 1)` ou `$test = [1 => 0, 2 => 1, 0 => 2]` supprimera la paire `2 => 1`. 203 | 204 | 205 | ## Conclusion 206 | 207 | Pour conclure on a vu que par définition les arrays de PHP ne sont pas vraiment des arrays mais des hashs. On remarquera cependant que les fonctions et la syntaxe sont conçus pour les utiliser comme de vrai arrays en camouflant potentiellement les clés tout le long de la manipulation. Il faudra tout de même faire attention car certaines fonctions comme `array_filter` ne font pas le nécessaire et il faudra garder cela en tête afin de ne pas être surpris. 208 | 209 | Je ne les ai pas testé mais il y a aussi les fonctions de tri qui peuvent changer l'ordre des valeurs, dans ce cas cependant c'est le choix de la fonction et non pas la nature de l'array qui déterminera ou non la réassignation des clés. 210 | 211 | Donc les arrays php sont-ils des arrays ? Techniquement non, mais on peut faire comme si. 212 | -------------------------------------------------------------------------------- /architecture/faire-des-commits-au-quotidien.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | author: Julien Castiaux 4 | date: 2023-05-29 5 | title: "Faire des commits au quotidien" 6 | --- 7 | 8 | Vous savez donc comment faire des beaux [messages de commit dans git] mais au moment de passer à la pratique vous êtes perdus. Vous aviez l'habitude de créer des nouveaux commits à intervalle régulier pour envoyer votre travail sur GitHub et ainsi avoir une copie de secours. Vos commits étaient courts, écrits à coup de `-m`, et décrivaient ce que vous aviez changé ces deux/trois dernières heures. 9 | 10 | Vous avez aussi commencé à travailler avec d'autres personnes et donc à faire ou à recevoir des *reviews* sur GitHub. Vous êtes ainsi tombés sur une branche avec des dizaines de commits, résultat de la pratique de commit le travail effectué toutes les deux/trois heures, branche que vous vous retrouvez soudainement à review... Dur travail... 11 | 12 | Vous voudriez continuer de créer des commits toutes les deux/trois heures pour garder une sauvegarde sur GitHub... Vous voudriez aussi qu'au moment de la review, tous les commits soient bien remis dans l'ordre et que chacun soit accompagné d'un chouette message. 13 | 14 | Cet article vous explique comment réconcilier les deux, comment continuer à faire des petits commits incrémentaux pendant le développement, comment nettoyer sa branche juste avant d'ouvrir la pull/merge-request et comment continuer à commit dessus. En des termes plus techniques, cet article vous explique comment réécrire l'historique de vos branches. 15 | 16 | [messages de commit dans git]: https://hub.notaname.fr/architecture/les-messages-de-commit-dans-git 17 | 18 | ## Solution du pauvre : Squash and Merge 19 | 20 | Une première solution (très facile) est de faire fi de l'historique des commits dans la branche, vous vous dites qu'au final ce qui compte c'est ce qui arrivera sur la branche `main` pour la postérité, que vous pouvez vous débrouiller pour votre review avec ce qui est écrit dans le message de la pull/merge-request. 21 | 22 | GitHub a une solution pour ça, c'est ce bouton: 23 | 24 | ![IDEs chart]({{ site.baseurl }}/assets/images/github-squash.png) 25 | 26 | En utilisant cette approche, au moment de merger votre branche, GitHub va fusionner tous les commits de la branche en un seul. Il vous demandera aussi de choisir un titre et un message pour ce nouveau commit, il vous proposera par défaut le titre et le message de la pull/merge-request. 27 | 28 | Super facile donc ! Au moment d'ouvrir votre pull/merge-request, vous pouvez prendre le temps d'écrire un bon titre et un bon message. Vos collègues reviewers seront contents parce qu'il y aura une bonne description du travail, description qui se retrouvera aussi dans l'historique de la branche `main` une fois la PR/MR mergée ! 29 | 30 | Attention, cette approche ne fonctionne pas si la branche contient plusieurs travaux indépendants, par exemple un petit correctif de bug en marge du travail principal. Imaginez la situation si dans quelques semaines vous étiez amenés à *revert* le commit en question, vous ré-introduiriez le bug qui avait été corrigé. Idéalement il aurait dû y avoir deux commits séparés, pas possible d'utiliser *squash and merge* dans ce cas là. 31 | 32 | ## Réécrire tout l'historique de sa branche 33 | 34 | Une seconde solution est de décider de ne pas se préoccuper de ses commits le temps du développement, de faire des petits commits type "wip" régulièrement et de les envoyer sur GitHub histoire d'avoir une copie quelque part. Ensuite au moment venu de proposer sa branche dans une PR/MR, d'annuler uniquement les commits tout en gardant les fichiers modifiés sur le disque, ouvrant ainsi la possibilité de *refaire* des commits bien propre. 35 | 36 | ### Reset 37 | 38 | Pour cette approche, il est possible d'utiliser la commande `reset`. Très précisément, `reset` permet de faire pointer la branche courante sur un autre commit, la commande pourrait être `move-this-branch-to-another-commit` mais `reset` est plus court à écrire. La commande prend un numéro de commit comme argument, le numéro du commit sur lequel faire pointer la branche. Dans ce cas-ci comme nous voulons annuler tous les commits propres à la branche, vous devez *reset* sur le commit sur lequel votre branche est basée, c'est-à-dire le commit qui précède les commits propres à votre branche. 39 | 40 | Attention, il est impératif que vous utilisiez `reset` **sans** les options `--hard` ou `--soft` : 41 | 42 | * `--hard` aurait pour effet d'annuler toutes vos modifications sur disque, dans le cas présent vous voulez justement conserver toutes ces modifications ; 43 | * `--soft` aurait pour effet de recréer un seul commit sur base de tous les fichiers modifiés, dans le cas présent vous voulez potentiellement refaire plusieurs commits ou bien ne pas conserver certains fichiers. 44 | 45 | Une fois la commande exécutée, vous pouvez recréer vos commits un à un cette fois en prenant le temps de rédiger de beaux messages et en séparant dans différents commits ce qui doit l'être. 46 | 47 | Une fois l'historique correctement réécrit, il sera nécessaire d'envoyer les changements sur GitHub avec `push`, GitHub vous empêchera d'envoyer vos modifications parce que l'historique des deux branches (locale sur votre machine vs distante sur GitHub) n'est plus cohérent. C'est normal puisque l'historique vient d'être réécrit, dans cette situation il est donc normal d'utiliser `push` avec `--force-with-lease`. 48 | 49 | Attention, utiliser `--force` écrase systématiquement la branche sur GitHub par votre branche sur votre PC. Si vous avez un collègue qui a poussé des commits sur votre branche entre temps, utiliser `--force` écrasera son travail. Utiliser `--force-with-lease` permet d'éviter cette situation, il force uniquement si il n'y a pas de nouveaux commits. 50 | 51 | ### Exemple 52 | 53 | Prenons la branche dans laquelle ce présent article est rédigé à titre d'exemple: 54 | 55 | ```bash 56 | $ git log --oneline 57 | c66a677 wip 58 | 6554ca8 wip 59 | bc92aa7 add: how to cleanup git branches before a review 60 | d64706e edit: ajout de liens entre articles 61 | 84481d7 fix: wrong index 62 | ``` 63 | 64 | De tous ces commits, uniquement les trois premiers (`c66a677`, `6554ca8` et `bc92aa7`) font partie de notre branche, les deux derniers sont d'anciens commits de la branche master. Puisque nous voulons annuler tous les commits de notre branche, nous voulons déplacer (*reset*) notre branche sur le 1er commit ne faisant par partie de notre travail, nous voulons donc la déplacer sur `d64706e edit: ajout de liens entre articles`. 65 | 66 | ```bash 67 | $ git reset d64706e 68 | $ git status 69 | On branch lazor/new-git-cleanup 70 | Your branch is behind 'origin/lazor/new-git-cleanup' by 3 commits, and can be fast-forwarded. 71 | (use "git pull" to update your local branch) 72 | 73 | Changes not staged for commit: 74 | (use "git add ..." to update what will be committed) 75 | (use "git restore ..." to discard changes in working directory) 76 | modified: architecture/index.md 77 | modified: index.md 78 | modified: sitemap.txt 79 | 80 | Untracked files: 81 | (use "git add ..." to include in what will be committed) 82 | architecture/nettoyer-sa-branche-git-avant-d'ouvrir-une-pr.md 83 | assets/images/github-squash.png 84 | 85 | no changes added to commit (use "git add" and/or "git commit -a") 86 | ``` 87 | 88 | Si nous décidons de créer un seul commit avec l'ensemble des modifications, nous pouvons faire: 89 | 90 | ```bash 91 | $ git add -A 92 | $ git commit 93 | [lazor/new-git-cleanup 7b99b92] new: how to cleanup git branches before a review 94 | 5 files changed, 91 insertions(+) 95 | create mode 100644 architecture/nettoyer-sa-branche-git-avant-d'ouvrir-une-pr.md 96 | create mode 100644 assets/images/github-squash.png 97 | ``` 98 | 99 | Nous constatons comme attendu que l'historique de notre branche locale n'est plus cohérent avec celui de notre branche sur GitHub et il nous faut donc forcer (avec `--force-with-lease` qui est plus sur que `--force`) GitHub à utiliser notre branche locale. Notez que le conseil de git qui voudrait qu'on `pull` est complètement idiot dans le cas présent. 100 | 101 | ```bash 102 | $ git status 103 | On branch lazor/new-git-cleanup 104 | Your branch and 'origin/lazor/new-git-cleanup' have diverged, 105 | and have 1 and 3 different commits each, respectively. 106 | (use "git pull" to merge the remote branch into yours) 107 | 108 | nothing to commit, working tree clean 109 | 110 | $ git push --force-with-lease 111 | Enumerating objects: 17, done. 112 | Counting objects: 100% (17/17), done. 113 | Delta compression using up to 16 threads 114 | Compressing objects: 100% (10/10), done. 115 | Writing objects: 100% (10/10), 16.46 KiB | 8.23 MiB/s, done. 116 | Total 10 (delta 4), reused 0 (delta 0), pack-reused 0 117 | remote: Resolving deltas: 100% (4/4), completed with 4 local objects. 118 | To github.com:NotANameServer/Not-a-Hub.git 119 | + c66a677...7b99b92 lazor/new-git-cleanup -> lazor/new-git-cleanup (forced update) 120 | ``` 121 | 122 | ## Juste modifier un précédent commit 123 | 124 | Réécrire l'ensemble de l'historique à chaque changement peut être très fastidieux, imaginez la situation où vous avez réorganisé votre travail en 5 beaux commits et que lors de la review on vous demande d'apporter des modifications à plusieurs fichiers, certains dans votre 2e commit, certains dans votre 3e commit. Vous ne voulez pas perdre du temps à reset l'ensemble de vos commits, les recréer un à un en corrigeant ce qui doit l'être et repush. Vous voulez plutôt venir modifier un précédent commit sans changer les autres, c'est possible avec `--amend` et `--fixup`. 125 | 126 | ### Amend 127 | 128 | Utiliser `commit` avec l'option `--amend` permet de modifier le tout dernier commit. Vous n'avez donc qu'à faire vos modifications, les `git add` et ensuite `git commit --amend` pour intégrer ces modifications dans le dernier commit. Vous aurez aussi l'occasion de modifier le message de commit, très pratique pour corriger les fautes d'orthographe. 129 | 130 | ### Fixup & Rebase 131 | 132 | Utiliser `commit` avec l'option `--fixup` permet de créer un nouveau commit qui pourra être intégré avec n'importe quel commit existant. Comme pour *amend*, vous commencez par faire vos modifications, les `git add` et ensuite `git commit --fixup numéro-du-commit`, où "numéro-du-commit" correspond au commit à modifier. Contrairement à *amend* qui modifie directement le commit, *fixup* crée un *nouveau* commit par dessus tous les autres et il reste encore une étape à faire pour l'intégrer au bon endroit. 133 | 134 | Git propose aussi un outil pour réordonner des commits et les fusionner, cet outil s'appelle le *rebase interactif* et permet de traiter automatiquement les commits *fixup*. Typiquement vous allez utiliser la commande suivante: `git rebase -i --autosquash numéro-de-commit` où le numéro de commit correspond à un commit suffisamment loin dans le passé, vous pouvez écrire HEAD~10 (le 10ème précédent commit) pour brasser large, vous pouvez réutiliser le même commit que vous aviez mis dans l'étape de *reset* plus haut, c'est à dire le commit sur lequel votre branche est basée. 135 | 136 | En reprenant la même branche que dans le précédent exemple: 137 | 138 | ```bash 139 | git add ... # vous faites tous vos ajouts, peut-être avec -p 140 | 141 | $ git log --oneline 142 | 7b99b92 new: how to cleanup git branches before a review 143 | d64706e edit: ajout de liens entre articles 144 | 84481d7 fix: wrong index 145 | 146 | $ git commit --fixup 7b99b92 147 | [lazor/new-git-cleanup f77d973] fixup! new: how to cleanup git branches before a review 148 | 1 file changed, 48 insertions(+), 6 deletions(-) 149 | 150 | $ git rebase -i --autosquash 84481d7 151 | Successfully rebased and updated refs/heads/lazor/new-git-cleanup. 152 | ``` 153 | 154 | Pendant l'étape du *rebase*, une fenêtre s'est ouverte pour vous demander comment réordonner l'historique, avec l'option `--autosquash` les commits fixup ont déjà été déplacés au bon endroit et la bonne action a déjà été sélectionnée, vous pouvez donc simplement sauvegarder et quitter. 155 | 156 | À nouveau, puisque vous avez réécrit l'historique, vous devrez pousser vos commits avec l'option `--force-with-lease`. 157 | 158 | ## Conclusion 159 | 160 | Dans cet article nous avons identifié différentes situations communes du cycle de vie d'une branche. Nous avons vu qu'on peut très bien créer des petits commits "wip" incrémentaux le temps du développement car il est parfaitement possible de tout nettoyer juste avant de proposer sa branche en pull/merge-request. Nous avons aussi vu qu'une fois qu'un historique est nettoyé, il est toujours possible de continuer à créer des petit commits "fixup" incrémentaux qui pourront être injectés au bon endroit dans l'historique le moment venu. 161 | -------------------------------------------------------------------------------- /langages/python/relatif-vs-absolu-demystifions-les-imports-python.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | author: Julien Castiaux 4 | date: 2022-06-27 5 | title: "Relatif vs Absolu, démystifions les imports python" 6 | --- 7 | 8 | Dans cet article, nous étudierons les modules en python avec un regard poussé sur la notion de package. Nous étudierons aussi la manière de définir des modules et la manière de les importer de sorte à créer des arborescences cohérentes et à prévenir certains problèmes, notamment les problèmes liés aux imports relatifs. 9 | 10 | Cet article part du principe que vous avez déjà utilisé `import` et que vous avez déjà écrit vos propres modules. Si vous pensez avoir besoin d'une remise à niveau, nous vous conseillons de relire le chapitre 7 "Pas à pas vers la modularité (2/2)" du livre "[Apprenez à programmer en Python][goff11]" de Vincent le Goff. 11 | 12 | Package 13 | ------- 14 | 15 | Dans le jargon python, un *package* est un dossier qui contient un fichier `__init__.py` où sont regroupés des modules python. Dans la bibliothèque standard on retrouve par exemple `asyncio`, `email`, `html`, `http`, `importlib`, `tkinter`, `unittest`, `urllib` et `xml`. Côté PyPI on peut citer `flask`, `numpy`, `matplotlib`, `requests`, `fastapi` et `sqlalchemy`. Tous sont des packages où sont regroupés parfois jusqu'à plusieurs centaines de modules python. 16 | 17 | Il est rare de développer un projet qui ne va tenir que dans un seul module, la plupart du temps un projet est implémenté au travers de plusieurs modules qui vont s'importer mutuellement et qui sont regroupés dans différents dossiers. Tous ces modules regroupés en différents dossiers doivent pouvoir utiliser les fonctions et les classes qui sont définies dans les autres modules, il est donc important de pouvoir importer et de pouvoir être importé facilement. Python propose deux mécanismes pour importer des choses venant d'autres modules : les imports absolus (au `sys.path`) et les imports relatifs (au package courant). 18 | 19 | Import absolu et `sys.path` 20 | --------------------------- 21 | 22 | Lorsque vous écrivez `import x` ou `from x import y`, vous faites un import absolu. La bibliothèque (le module ou le package) doit se trouver soit dans le *working-directory*, soit être installée dans l'environnement virtuel courant, soit être installée au niveau de l'utilisateur, soit être installée au niveau du système. Python scanne ces différentes sources à la recherche de la bibliothèque demandée pour l'importer. Si après avoir scanné l'ensemble des sources possibles python ne trouve pas la bibliothèque demandée alors il lève une erreur du type `ModuleNotFoundError: No module named 'x'`. 23 | 24 | L'ensemble des dossiers que python va scanner à la recherche de la bibliothèque demandée varie d'un système à l'autre. Heureusement, il est possible de récupérer cette information depuis l'interpréteur python via la variable `sys.path`. Ci-dessous un exemple de ce que contient cette variable sur le PC d'un des auteurs : 25 | 26 | >>> import sys 27 | >>> sys.path 28 | ['', 29 | '/usr/lib/python38.zip', 30 | '/usr/lib/python3.8', 31 | '/usr/lib/python3.8/lib-dynload', 32 | '/home/julien/.local/lib/python3.8/site-packages', 33 | '/usr/local/lib/python3.8/dist-packages', 34 | '/usr/lib/python3/dist-packages', 35 | '/usr/lib/python3.8/dist-packages'] 36 | 37 | Les différents dossiers à scanner sont listés dans l'ordre. La string vide en tout premier est remplacée au moment de l'import par le *working-directory* (`os.getcwd()`), c'est à dire le dossier d'où a été lancé l'interpréteur python. Dans notre cas python a été lancé depuis le dossier `/home/julien`, on obtient donc : 38 | 39 | >>> import os 40 | >>> os.getcwd() 41 | '/home/julien' 42 | 43 | Au moment de faire un import absolu python va donc chercher la bibliothèque dans l'ordre des dossiers : `/home/julien`, `/usr/lib/python38.zip`, `/usr/lib/python3.8`,…, `/usr/lib/python3.8/dist-packages`. Dans les deux exemples ci-dessus, pour `sys` et `os`, il n'aura scanné que `/home/julien` et `/usr/lib/python38.zip` avant de trouver les deux fichiers `os.py` et `sys.py` dans le dossier `/usr/lib/python3.8`. 44 | 45 | Import relatif et `__package__` 46 | ------------------------------- 47 | 48 | Lorsque vous écrivez `from . import x` ou `from .x import y` vous faites un import relatif. Python n'utilise plus le `sys.path` mais la variable magique `__package__` du module courant pour déterminer où trouver le module à importer. La variable `__package__` est similaire à la variable `__name__` : il s'agit d'une string qui représente le nom du package actuel qui s'ajoute aux noms des différents packages parents. La plupart du temps, `__package__` est juste `__name__` auquel on a retiré le nom du module courant pour n'y laisser que la partie package, nous verrons par la suite qu'il y a quelques exceptions. 49 | 50 | Voici quelques exemples de ce que contiennent les variables `__name__` et `__package__` pour quelques modules connus : 51 | 52 | >>> # random est un fichier random.py accessible directement depuis le sys.path 53 | >>> import random 54 | >>> random.__name__, random.__package__ 55 | ('random', '') 56 | 57 | >>> # urllib est un dossier avec un fichier __init__.py 58 | >>> import urllib 59 | >>> urllib.__name__, urllib.__package__ 60 | ('urllib', 'urllib') 61 | 62 | >>> # urllib.parse est un fichier parse.py dans le dossier urllib 63 | >>> import urllib.parse 64 | >>> urllib.parse.__name__, urllib.parse.__package__ 65 | ('urllib.parse', 'urllib') 66 | 67 | Au moment de jouer l'instruction `from . import x`, c'est comme si python remplaçait le `.` par le contenu de la variable `__package__`. Disons que cette variable contient la valeur `a.b`, l'instruction devient alors `from a.b import x` : un import absolu. 68 | 69 | Si au moment de jouer l'instruction `from . import x`, le module courant n'était pas lié à un quelconque package (sa variable `__package__` était vide), l'erreur suivante se serait produite : `ImportError: attempted relative import with no known parent package`. L'erreur dénote l'impossibilité de faire un import relatif du fait que le module courant n'est pas lié à un package. 70 | 71 | La fausse intuition, le piège 72 | ----------------------------- 73 | 74 | Nous venons de voir les notions de package, d'import absolu et d'import relatif. Nous avons vu qu'un package est un dossier contenant un fichier `__init__.py`, que les imports absolus reposent sur le `sys.path` et que les imports relatifs reposent sur la variable `__package__`. 75 | 76 | Cependant cette variable `__package__` nous réserve quelques surprises. 77 | 78 | Notre intuition de développeur fait que nous pensons qu'un package est un dossier contenant des modules python, avec un artefact supplémentaire : le fichier `__init__.py`. En suivant notre intuition nous pensons donc qu'à partir du moment où un fichier se trouve dans un tel dossier, il aura forcément une variable `__package__` contenant le nom du dossier. 79 | 80 | Notre intuition est fausse : le package n'est pas défini par le dossier, *le package est défini au moment où le fichier `__init__.py` est exécuté*. Si un module python se trouve dans un dossier ayant un fichier `__init__.py` mais que ce fichier `__init__.py` n'a pas été évalué avant d'exécuter le module alors le module aura une variable `__package__` vide ! 81 | 82 | En d'autres termes, pour qu'un module soit lié à un package, pour qu'un module ait une variable `__package__` définie et non-vide, il **faut** que le fichier `__init__.py` ait été exécuté avant. 83 | 84 | 85 | Le piège en pratique 86 | -------------------- 87 | 88 | Étudions le programme suivant : 89 | 90 | ~ $ ls monpkg/ 91 | __init__.py __main__.py tartempion.py 92 | ~ $ cat monpkg/__init__.py 93 | print(__file__, f'{__name__=}', f'{__package__=}') 94 | ~ $ cat monpkg/__main__.py 95 | print(__file__, f'{__name__=}', f'{__package__=}') 96 | ~ $ cat monpkg/tartempion.py 97 | print(__file__, f'{__name__=}', f'{__package__=}') 98 | 99 | Il s'agit d'un package minimaliste, seulement trois fichiers : `__init__.py`, `__main__.py` et `tartempion.py`. Les trois fichiers exécutent la même instruction : afficher leurs propres variables `__file__`, `__name__` et `__package__`. 100 | 101 | Rappelons que lorsqu'un *package* est importé (lorsque vous faites `import monpkg`), le fichier `__init__.py` est automatiquement joué. Rappelons aussi que lorsqu'un *dossier* est exécuté (lorsque vous faites `python3 monpkg`), le fichier `__main__.py` est automatiquement joué. 102 | 103 | Nous allons maintenant étudier 5 manières différentes d'exécuter ce programme. 104 | 105 | ### Directement exécuter le fichier tartempion.py 106 | 107 | ~ $ python3 monpkg/tartempion.py 108 | /home/julien/monpkg/tartempion.py __name__='__main__' __package__=None 109 | 110 | Dans ce premier exemple, le fichier `tartempion.py` est directement exécuté via la ligne de commande. 111 | 112 | Nous constatons que ni le fichier `__init__.py` ni le fichier `__main__.py` n'ont été exécutés automatiquement. Le fichier `__init__.py` n'a pas été exécuté parce que le package n'a pas été importé. Le fichier `__main__.py` n'a pas été exécuté parce le dossier n'a pas été exécuté. 113 | 114 | Nous constatons également que le nom du module est `'__main__'` au lieu d'être `'tartempion'`. Il s'agit ici d'une règle en python, le module qui est exécuté en tout premier, le point d'entré, a toujours une variable `__name__` défini à `'__main__'`. 115 | 116 | Nous constatons pour finir que le module n'est pas rattaché à un quelconque package puisque le fichier `__init__.py` n'a pas été chargé. Les imports relatifs ne fonctionneront pas dans le cas présent pour le module tartempion. 117 | 118 | 119 | ### Directement exécuter le fichier \_\_main\_\_.py 120 | 121 | Dans ce second exemple, le fichier `__main__.py` est directement exécuté via la ligne de commande. 122 | 123 | ~ $ python3 monpkg/__main__.py 124 | /home/julien/monpkg/__main__.py __name__='__main__' __package__=None 125 | 126 | Nous constatons à nouveau que le fichier `__init__.py` n'a pas été automatiquement chargé. Qu'encore une fois la variable `__package__` est donc vide et qu'encore une fois les imports relatifs ne fonctionneront pas. À minima, nous constatons qu'au moins cette fois-ci, le nom du module est cohérent avec le nom du fichier. 127 | 128 | ### Directement exécuter le dossier monpkg 129 | 130 | Dans ce troisième exemple, le dossier `monpkg` est directement exécuté via la ligne de commande. 131 | 132 | ~ $ python3 monpkg 133 | /home/julien/monpkg/__main__.py __name__='__main__' __package__='' 134 | 135 | Force nous est de constater qu'encore une fois le fichier `__init__.py` n'a pas été automatiquement chargé. De ce fait la variable `__package__` est vide et les imports relatifs ne fonctionneront toujours pas. 136 | 137 | ### Importer \_\_main\_\_.py depuis l'extérieur 138 | 139 | Dans ce quatrième exemple, nous ajoutons un fichier `run.py` avec `import monpkg.__main__` pour seule instruction. Nous plaçons ce fichier à côté du dossier monpkg et nous l'exécutons. L'idée ici est de charger à la fois `__init__.py` et `__main__.py` pour avoir à la fois un package (via init) et l'exécuter (via main). 140 | 141 | ~ $ ls 142 | monpkg run.py… 143 | ~ $ cat run.py 144 | import monpkg.__main__ 145 | ~ $ python3 run.py 146 | /home/julien/monpkg/__init__.py __name__='monpkg' __package__='monpkg' 147 | /home/julien/monpkg/__main__.py __name__='monpkg.__main__' __package__='monpkg' 148 | 149 | Nous constatons cette fois-ci que les deux fichiers `__init__.py` et `__main__.py` ont bien chacun été chargés et exécutés. Nous constatons également que chacun des deux modules partagent une variable `__package__` défini au nom du dossier. Nous sommes enfin dans une situation où les imports relatifs vont fonctionner. 150 | 151 | Notez que la ligne `import monpkg.__main__` fait un import absolu. Il fonctionne dans le cas présent car le package `monpkg` se trouve dans le working directory. Pour que le fichier `run.py` puisse être exécuté de partout, même depuis un dossier différent ce celui où ce fichier se trouve, il faut modifier le `sys.path` manuellement *avant* d'importer le package : 152 | 153 | import sys 154 | import os.path 155 | sys.path.insert(1, os.path.parent(__file__)) 156 | import monpkg.__main__ 157 | 158 | Le lecteur attentif aura relevé que le nom du module `__main__.py` n'est pas `'__main__'` mais bien `'monpkg.__main__'`. En effet, du fait que ce module a été importé et non pas directement exécuté, il n'est pas le point d'entré du programme et ne s'appelle donc pas `__main__`. Celui qui s'appelle `__main__` est en fait le fichier `run.py`, c'est lui le point d'entré. 159 | 160 | ### Exécuter le package avec -m 161 | 162 | Dans ce cinquième et dernier exemple, nous utilisons l'option `-m` de la ligne de commande python pour lancer notre package. 163 | 164 | ~ $ python3 -m monpkg 165 | /home/julien/monpkg/__init__.py __name__='monpkg' __package__='monpkg' 166 | /home/julien/monpkg/__main__.py __name__='__main__' __package__='monpkg' 167 | 168 | Dans ce tout dernier exemple nous constatons à nouveau que les deux fichiers ont été chargés et exécutés. Nous constatons à nouveau que la variable `__package__` est correctement définie et donc que les imports relatifs vont fonctionner. En bonus par rapport à la solution précédente, nous n'avons pas dû recourir à artifice supplémentaire dont le seul rôle était d'importer notre programme. 169 | 170 | Conclusion 171 | ---------- 172 | 173 | Les imports relatifs et absolus sont des mécanismes équivalents qui permettent à Python d'être un langage modulaire. Nous avons vu sur quoi reposaient ces deux mécanismes et nous avons étudié en profondeur la notion de package, quelle est leur nature et quel est leur rôle. Nous avons vu que la nature des packages n'était pas aussi simple et intuitive qu'il pourrait sembler. Nous avons cerné en quoi la nature réelle des packages avait son lot d'implication quant à la manière de les exécuter. 174 | 175 | Nombreux sont les développeurs, pas si débutants que ça, qui peinent à comprendre et à résoudre les erreurs du type `ImportError: attempted relative import with no known parent package` ou encore `ModuleNotFoundError: No module named ...`. Leur solution est souvent de fournir un fichier supplémentaire, un artifice, un module dont le seul rôle est de modifier le `sys.path` et d'importer leur projet. La réelle solution trop peu connue repose en fait sur l'option `-m` de la ligne de commande python. 176 | 177 | Nous pouvons regretter que les nombreux cours négligent d'apporter des explications sur comment structurer des projets python au vu de la nature des packages. Nous pouvons également regretter que le sujet est fort peu documenté même dans la documentation officielle. En outre, l'article du tutoriel officiel de python comporte son lot d'erreurs qui n'ont jamais été adressés en plus de 15 ans. 178 | 179 | [goff11]: https://user.oc-static.com/ftp/livre/python/apprenez_a_programmer_en_python.pdf 180 | -------------------------------------------------------------------------------- /langages/scala/scala-comme-premier-langage.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | author: Raphaël Fromentin et TheElectronWill 4 | date: 2023-03-04 5 | last_update: 2023-05-24 6 | title: "Scala comme premier langage de programmation" 7 | --- 8 | 9 | ## Introduction 10 | 11 | Quel langage de programmation enseigner aux débutants ? Faut-il leur apprendre un langage bas niveau comme le [C](https://fr.wikipedia.org/wiki/C_(langage)) ou haut niveau comme [Python](https://python.org/) ? Faut-il prendre un langage populaire ou est-ce qu'un langage moins connu peut faire l'affaire ? La question du langage à enseigner aux néophytes est largement débattue, et certains ont un avis très tranché. Nous ne proposons pas de donner une réponse définitive ici, mais d'exposer ce qui, selon nous, fait du langage Scala un bon candidat. 12 | 13 | Cet article s'adresse principalement aux professeurs enseignant la programmation. 14 | 15 | ## Accessibilité 16 | 17 | Il est important que le langage soit utilisable par le plus grand nombre, peu importe le niveau, c'est-à-dire qu'il soit accessible. On peut en effet supposer qu'un débutant ne possède pas de compétences techniques avancées. Plus il sera facile de commencer à utiliser le langage, moins le débutant se découragera, et plus il pourra se concentrer sur son apprentissage. 18 | 19 | Un critère d'accessibilité important est le caractère multiplateforme : le novice doit pouvoir facilement utiliser le langage sur le système d'exploitation avec lequel il est le plus à l'aise. [Scala peut être exécuté sur Windows, Mac OS et Linux](https://www.scala-lang.org/download/). 20 | 21 | Que le langage fonctionne sur sa machine c'est bien, mais c'est insuffisant ! Il faut que les outils autour du langage suivent. Le développeur, néophyte inclus, a besoin de facilement éditer son code, de l'exécuter, de le déboguer. Scala propose de nombreux outils, dont certains très faciles d'utilisation. Citons notamment le REPL, installé de base avec le langage, qui permet d'exécuter quelques lignes de Scala à la volée. En voici un aperçu : 22 | 23 | ![REPL Scala en action]({{ site.baseurl }}/assets/images/scala-repl-screenshot.png) 24 | 25 | 26 | Notons que cette interface interactive embarque de la coloration syntaxique et de l'autocomplétion ! Elle permet de facilement tester de petits fragments de code. À l'opposé, un éditeur comme Visual Studio Code couplé avec un outil de build convient pour des scripts et des projets de plusieurs fichiers. Il existe plusieurs outils, mais le plus accessible est certainement [Scala CLI](https://scala-cli.virtuslab.org/), qui se veut plus simple que Maven, Gradle, etc. 27 | 28 | Il existe aussi [Scastie, un éditeur en ligne](https://scastie.scala-lang.org/) qui permet d'expérimenter avec Scala et de partager facilement son code, sans rien installer. 29 | 30 | Enfin, Scala est un [langage plutôt populaire](https://redmonk.com/rstephens/2022/10/20/top20-jun2022/) qui dispose d'une communauté active (notamment sur les plateformes [Scala Users Forum](https://users.scala-lang.org/) et [Discord](https://discord.gg/scala)) ainsi que de nombreuses [ressources en ligne](https://docs.scala-lang.org/learn.html) (cours vidéo, livres, articles, etc.). Non seulement le débutant peut apprendre en autonomie, mais il peut aussi obtenir de l'aide quand il en a besoin. 31 | 32 | ## "Scalable language" 33 | 34 | Le nom "Scala" est la contraction de "Scalable language", que l'on peut traduire littéralement par "langage capable de passer à l'échelle" ou en meilleur français, "langage évolutif". En d'autres termes, Scala se veut aussi bien adapté pour de petits scripts de quelques lignes que pour de gros projets en entreprise. De ce fait, il permet d'enseigner les bases de la programmation mais aussi des concepts avancés, tels que la programmation parallèle ! L'enseignant pourra donc commencer son cours par des exemples simples dans son navigateur ou dans le REPL, puis entraîner les étudiants sur de petits scripts, et pourquoi pas les faire travailler sur un projet plus important à la fin du semestre. 35 | 36 | 37 | > The scalable language also scales for teaching! 38 | > − *Julien Richard-Foy, CTO au Scala Center.* 39 | 40 | Scala combine la programmation orientée objet et la programmation fonctionnelle. Cela peut paraître étrange, mais force est de constater que de nombreux langages orientés objet incorporent aujourd'hui des éléments de programmation fonctionnelle : Java possède des "lambdas" depuis plusieurs années, Python propose depuis peu des "dataclasses", etc... Cette combinaison n'est pas forcée : le langage met simplement les outils à disposition du programmeur. Cela permet au débutant de travailler chaque paradigme séparément et de manière progressive, sans avoir besoin d'apprendre une nouvelle syntaxe et de nouveaux outils. C'est aussi un gain de temps pour l'enseignant, qui peut se concentrer sur les concepts et non sur les détails d'un langage supplémentaire. 41 | 42 | Avec Scala, le professeur peut enseigner la programmation [impérative](https://fr.wikipedia.org/wiki/Programmation_imp%C3%A9rative) et [procédurale](https://fr.wikipedia.org/wiki/Programmation_proc%C3%A9durale), puis aborder la programmation orientée objet et la programmation fonctionnelle dans l'ordre de son choix. L'étudiant pourra alors découvrir les avantages de chaque paradigme. 43 | 44 | ## Pédagogie 45 | 46 | Le choix du premier langage est important : il doit inculquer aux élèves les paradigmes, modes de pensée et bonnes habitudes générales, réutilisables dans les autres langages. 47 | 48 | C'est pourquoi son aspect pédagogique joue un rôle capital. 49 | 50 | ### Une syntaxe claire 51 | 52 | À l'instar de [Python](https://fr.wikipedia.org/wiki/Python_(langage)), Scala 3 possède une syntaxe concise et lisible. En voici une illustration : 53 | 54 | ```scala 55 | case class User(name: String) 56 | 57 | val friends = List(User("Bob"), User("Alice")) 58 | val sortedFriends = friends.sortBy(_.name) 59 | 60 | for friend <- sortedFriends do 61 | println(friend) 62 | ``` 63 | 64 | Bien que Scala soit statiquement typé, il est possible d'omettre les annotations de type dans de nombreux cas. Dans l'exemple ci-dessus, nous aurions pu écrire `val friends: List[User]`, mais Scala l'a inféré pour nous. 65 | 66 | La [syntaxe basée sur l'indentation](https://wikipedia.org/fr/Indentation_comme_syntaxe) permet de réduire le "bruit" qui nuit à la lecture du code en laissant optionnel l'usage de délimiteurs explicites là où ils sont utiles. 67 | 68 | Un autre avantage de la syntaxe présentée ci-dessus est sa lisibilité, qui résulte en partie de l'équilibre entre concision et verbosité. La verbosité d'un langage est souvent vue d'un mauvais oeil car considérée comme distrayant le programmeur ; mais il ne faut pas raccourcir la syntaxe au point qu'elle devienne obscure (un langage comme [05AB1E](https://github.com/Adriandmen/05AB1E) n'a d'intérêt que pour les compétitions de *code golf*). 69 | 70 | ### Régularité 71 | 72 | En plus d'avoir une syntaxe lisible, le langage est également très cohérent et régulier. 73 | 74 | Un bon porte-étendard de cette régularité est l'aspect "orienté objet pur" de Scala : toute valeur est objet. Il n'y a pas de primitif ou autre valeur "spéciale" : tout se traite de la même façon. Par exemple, voici une procédure en Scala : 75 | 76 | ```scala 77 | def sayHello(): Unit = 78 | println("Hello World") 79 | ``` 80 | 81 | On peut noter deux choses : 82 | - Le type de retour d'une fonction se note comme le type d'une variable, avec deux-points suivi du type. Quel est le type de `sayHello()` ? C'est `Unit`. 83 | - Dans la plupart des langages mainstreams, le type de retour d'une [procédure](https://fr.wikipedia.org/wiki/Routine_(informatique)) est un type particulier souvent appelé "vide"/"void". En Scala, `Unit` est un type comme les autres. 84 | 85 | Concrètement, cela veut dire que `Unit` se traite comme les autres types et fonctionne très bien avec les génériques, ce qui n'est pas le cas en [Go](https://fr.wikipedia.org/wiki/Go_(langage)) par exemple. 86 | 87 | En Scala : 88 | ```scala 89 | def workOnData[R](f: String => R): Unit = ??? 90 | 91 | workOnData(s => s.length) // ok 92 | workOnData(s => println(s)) // ok 93 | ``` 94 | 95 | En Go : 96 | ```go 97 | func workOnData[R any](f func(string) R) {} 98 | func getLength(s string) int { // renvoie un int 99 | return len(s) 100 | } 101 | func display(s string) // ne renvoie rien 102 | 103 | workOnData(getLength) // ok 104 | workOnData(len) // erreur! "len (built-in) must be called" 105 | workOnData(display) // erreur! "type func(s string) of display does not match func(string) R (cannot infer R)" 106 | ``` 107 | 108 | D'une manière générale, il y a peu de cas particuliers en Scala. Il n'y a par exemple pas de fonction "built-in" dont le status serait différent des autres. La bibliothèque standard fournit des fonctions comme les autres. Encore une fois, ce n'est pas le cas en Go qui traite `len` différemment. En choisissant Scala, on évite de passer du temps à expliquer ce genre de subtilités. 109 | 110 | D'autres exemples de régularité : 111 | - Les fonctions sont des objets comme les autres, on peut par exemple les stocker dans des variables. 112 | - Le type d'une fonction qui prend un paramètre de type `A` et renvoie une valeur de type `B` s'écrit `A => B`, tout comme les lambdas : `a => b` 113 | - Le corps d'une variable et d'une fonction sont exactement pareils 114 | - Les opérateurs sont des fonctions comme les autres, par exemple `def +`. On ne définit pas de méthode "magique" (comme `__add__` en Python mais qui s'utilise finalement `+`). 115 | 116 | ### Typage statique et erreurs claires 117 | 118 | Nous l'avons dit dans la section précédente : contrairement à Python, Scala est statiquement typé. Cela veut dire que le type de chaque fonction, variable et opération est connu à la compilation (avant exécution du programme). La cohérence entre ces types peut donc être vérifiée pour détecter les erreurs. 119 | 120 | Prenons ce code en Python : 121 | ```python 122 | def double(x): 123 | return x*2 124 | ``` 125 | 126 | si l'on affiche le résultat de `double(1)`, nous obtiendrons `2`. Cependant, si l'élève se trompe de valeur en faisant `double("1")`, il obtiendra en console `"11"` et peut ainsi penser à tort que sa fonction ne marche pas. 127 | 128 | Voici l'équivalent en Scala : 129 | ```scala 130 | def double(x: Double) = 131 | x*2 132 | ``` 133 | 134 | ```scala 135 | double(1) //Retourne 2 136 | ``` 137 | ```scala 138 | double("1") //Erreur à la compilation : "1" de type String n'est pas un Double 139 | ``` 140 | 141 | Note : `Double` représente en Scala un nombre à virgule flottante (à double précision). 142 | 143 | Voici l'erreur exacte : 144 | ```scala 145 | -- [E007] Type Mismatch Error: ------------------------------------------------- 146 | 1 |double("1") 147 | | ^^^ 148 | | Found: ("1" : String) 149 | | Required: Double 150 | | 151 | | longer explanation available when compiling with `-explain` 152 | 1 error found 153 | ``` 154 | 155 | L'erreur ci-dessus est très claire : elle indique de manière concise ce qui ne va pas, où et pourquoi. 156 | 157 | Certains messages d'erreur proposent même des solutions : 158 | 159 | ```scala 160 | import scala.mat //il manque un h 161 | ``` 162 | 163 | ```scala 164 | -- [E008] Not Found Error: ----------------------------------------------------- 165 | 1 |import scala.mat 166 | | ^^^ 167 | | value mat is not a member of scala - did you mean scala.math? 168 | 1 error found 169 | ``` 170 | 171 | Le fait de retourner des erreurs explicatives permet aux étudiants de pouvoir eux-mêmes comprendre et résoudre leur problème. Ils gagnent ainsi en autonomie. 172 | 173 | > *In my experience with Scala, the dialog with the compiler about static errors strongly supports conceptual learning and strengthens self-efficacy: "I can do this and I’m getting a grip of it!".* 174 | > -- Björn Regnell, Lund University, Sweden. 175 | 176 | ### Discipline 177 | 178 | Scala incite les élèves à utiliser de bonnes pratiques reconnues et à prendre de bonnes habitudes de programmation, réutilisables dans d'autres langages. 179 | 180 | Par exemple, la bibliothèque standard du langage utilise l'objet `Option` pour gérer l'absence potentielle de valeur : 181 | 182 | ```scala 183 | val a: List[Int] = List(1, 2, 3) 184 | val b: List[Int] = List.empty 185 | 186 | a.headOption //Some(1) 187 | b.headOption //None 188 | ``` 189 | 190 | Cela permet de forcer l'utilisateur à prendre en compte la possible absence de valeur et donc à prévenir les bugs/oublis : 191 | 192 | ```scala 193 | val list: List[Int] = ??? 194 | 195 | list.headOption * 2 196 | ``` 197 | ```scala 198 | -- [E008] Not Found Error: ----------------------------------------------------- 199 | 3 |list.headOption * 2 200 | |^^^^^^^^^^^^^^^^^ 201 | |value * is not a member of Option[Int], but could be made available as an extension method. 202 | 1 error found 203 | ``` 204 | 205 | > `Option[Int]` ne possède pas d'opérateur `*` 206 | 207 | Il y a plusieurs moyens d'utiliser `Option` mais une méthode courante est d'utiliser le [pattern matching](https://docs.scala-lang.org/tour/pattern-matching.html) : 208 | 209 | ```scala 210 | list.headOption match 211 | case Some(head) => head * 2 212 | case None => 0 213 | ``` 214 | 215 | Si la liste est vide alors ce programme retournera `0`. Si cette liste a comme tête `2` alors ce programme retournera 4. 216 | 217 | Autre exemple : par défaut, les variables en Scala sont immuables. 218 | 219 | ```scala 220 | val x = 5 221 | x = 4 //Erreur 222 | ``` 223 | 224 | Les structures de données par défaut le sont également. Par exemple, aucune méthode de `List` ne change son contenu mais renvoie à la place une nouvelle liste. 225 | 226 | ```scala 227 | val list = List("Bob", "Clara", "Alice") 228 | 229 | list.sorted //List("Alice", "Bob", "Clara") 230 | list.reverse //List("Alice", "Clara", "Bob") 231 | list.sorted.reverse //List("Clara", "Bob", "Alice") 232 | ``` 233 | 234 | Même si cela peut parraître contre-intuitif pour quelqu'un qui programme en C ou Python, la non-muabilité par défaut permet de libérer son esprit de tout souci de mutation non-contrôlée (les fameuses "mutation races"). 235 | 236 | Prenons l'exemple de la liste en Python qui est muable : 237 | 238 | ```python 239 | l = [] 240 | 241 | do_something(l) 242 | do_another_thing(l) 243 | ``` 244 | 245 | *Est-ce qu'à la fin de ce programme `l` a changé ? Si oui où ça ? Est-ce que l'ordre a de l'importance ?* Ces problèmes sont prévenus par l'immuabilité : 246 | 247 | ```scala 248 | val list = List(1, 2, 3) 249 | 250 | doSomething(l) 251 | doAnotherThing(l) 252 | ``` 253 | 254 | Ici, nous savons que `list` n'a pas été modifiée donc nous n'avons pas à nous poser les questions ci-dessus. Nous pouvons ainsi nous focaliser localement, sans nous préoccuper de nos `doSomething` et `doAnotherThing`. 255 | 256 | ## Conclusion 257 | 258 | Nous avons vu que Scala possède des atouts qui font de lui un langage adapté à l'enseignement de la programmation. Certaines fonctionnalités de Scala le démarquent de C ou Python comme l'immuabilité par défaut ou encore le typage statique (comparé à Python). 259 | -------------------------------------------------------------------------------- /architecture/les-messages-de-commit-dans-git.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | author: Julien Castiaux 4 | date: 2023-03-12 5 | title: "Les messages de commit dans git" 6 | --- 7 | 8 | Au moment de créer un nouveau commit, git demande à l'utilisateur de décrire les changements effectués via le *message de commit*. La rédaction de ces messages peut sembler fastidieuse voire inutile, aussi bien aux novices qu'aux personnes plus expérimentés, mais est d'une importance capitale pour mener à bien un projet de développement informatique sur le long terme. 9 | 10 | Ce document se découpe en plusieurs parties. La première est consacrée à expliquer l'origine des messages de commit et leurs liens avec les emails échangés entre les contributeurs au kernel Linux. La seconde découpe la structure d'un message de commit et explique quoi mettre à quel endroit. La troisième explique où et quand sont lus les messages de commit et explicite pourquoi il est important de prendre du temps à rédiger des bons messages de commit. Pour finir une quatrième est plus succincte et donne quelques pistes pour évaluer la qualité des commits et de l'historique git d'un projet et comment l'améliorer. 11 | 12 | ## Origine 13 | 14 | Git a été développé afin de rendre possible la collaboration à grande échelle sur le noyau Linux. Avant d'utiliser git, les développeurs du noyau collaboraient à l'aide de mailing lists, ils discutaient des changements à venir ou en cours via mail et s'échangeaient du code en pièce jointe. 15 | 16 | Le développement d'une nouvelle fonctionnalité commençait par l'envoi d'un e-mail à tous les inscrits de la mailing list pour décrire les nouveaux besoins. Si vous trouviez un bug dans le noyau, vous pouviez envoyer un mail à cette mailing list pour décrire le bug et demander à ce qu'il soit corrigé. Quiconque intéressé pouvait alors "répondre à tous" sur cet e-mail et ouvrir une discussion autours de la fonctionnalité à développer ou du bug à corriger. Après quelques jours, quelqu'un répondait à la suite d'e-mails avec un *patch*, c'est à dire une archive compressée contenant l'ensemble des *diff* à appliquer aux fichiers sources du noyau. Les autres développeurs répondaient avec cet e-mail pour donner leur avis sur les changements dans le but d'arriver à la meilleure solution. Une fois le patch approuvé par tout le monde (enfin surtout par Linus), il pouvait être appliqué sur le noyau et inclus dans les nouvelles versions. 17 | 18 | Dans le lot, le développeur qui avait pris la peine de proposer un patch ne devait pas se contenter de répondre un e-mail vide avec le code en pièce jointe. Il avait aussi la responsabilité de résumer les discussions préalables et d'apporter des possibles éclaircissements sur son code. L'idée générale étant de faire gagner du temps à tout le monde (et surtout à Linus) en rappelant le contexte, les besoins et en apportant des explications quant à l'implémentation si ce n'était pas trivial. 19 | 20 | Comme git a été développé avec comme objectif d'être utilisé par les contributeurs du noyau Linux, cette idée d'accompagner chaque contribution par un message explicatif a été reprise et a donné les messages de commit. 21 | 22 | ## Structure 23 | 24 | À la manière des e-mails dont les messages sont inspirés, un message de commit se compose en 2 parties: le titre et le corps. Le titre s'inspire des sujets pour les e-mails tandis que le corps s'inspire du corps des e-mails. Le corps du message peut contenir plusieurs paragraphes de texte qui apportent toutes les infos nécessaires à la compréhension du commit. Le corps du message peut se terminer par un pied de page où sont repris différentes méta-informations, par exemple une liste de co-contributeurs, des notes de bas de page ou encore des références vers des numéros de tickets ou des issues. 25 | 26 | type(scope): titre sous 50 caractères............ 27 | 28 | Corps avec plusieurs paragraphes, chaque ligne sous 72 caractères et... 29 | qui rappelle le contexte, explique pourquoi les changements sont....... 30 | nécessaires et apporte des éclairsissements si le code est compliqué... 31 | 32 | Après le corps on peut retrouver une série de références, notez que.... 33 | certains [mots-clés] sont automatiquement reconnus par github.......... 34 | 35 | [mots-clés]: https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests 36 | 37 | ### Titre 38 | 39 | Le titre doit être un résumé concis et de préférence sous 50 caractères du contenu du commit. Normalement, il s'agit d'une phrase à l'infinitif en français (à l'impératif en anglais) qui peut compléter la phrase "Thanks to this commit we can ...", par exemple "*Thanks to this commit we can* fix bad smtp To headers for unicode addresses". 40 | 41 | Selon les organisations, les titres des commits peuvent suivre un format précis où le *type* du changement et *l'endroit* du changement doivent être systématiquement écris. Par exemple en suivant la convention de [conventional commits], le précédent titre deviendrait "fix(smtp): bad To header for unicode addresses". En suivant la convention en vigueur à [Odoo], il deviendrait "[FIX] smtp: bad To headers for unicode addresses". 42 | 43 | [conventional commits]: https://www.conventionalcommits.org/en/v1.0.0/ 44 | [Odoo]: https://www.odoo.com/documentation/master/contributing/development/git_guidelines.html 45 | 46 | ### Corps 47 | 48 | Le corps du message est la partie la plus importante, c'est dans cette partie que sont formellement décrits tous les changements que le commit apporte. C'est aussi, malheureusement, la partie la moins comprise et la plus souvent ignorée par les novices de git. 49 | 50 | Là où la diff qui accompagne le message de commit explique formellement *comment* et *où* dans le code les changements ont été effectués, le corps du message de commit doit formellement expliquer *qu'est-ce qui* et *pourquoi* ça a été changé. 51 | 52 | Il est inutile de répéter dans le message de commit ce qui peut être appris en lisant soit le titre soit la diff. Ceci implique qu'un effort a été réalisé pour rédiger du code propre et correctement documenté avec des docstrings et des commentaires. Il est parfois judicieux de profiter du message de commit pour rappeler comment une fonctionnalité a évolué au fil du temps, pour justifier l'état actuel du code source avec des explications historiques qui n'ont pas vraiment leur place ni en docstring ni en commentaire mais il ne faut pas expliquer le code. Lorsqu'on ressent le besoin d'expliquer la diff, c'est peut-être une indication que le code n'est pas très propre. 53 | 54 | Dans le corps du message, il faut donc se concentrer à apporter les informations qui ne peuvent pas être comprises en lisant seulement la diff. Il faut notamment donner un bref rappel des fonctionnalités en cours de modification pour donner le contexte générale, une vue d'ensemble. Il faut aussi impérativement expliquer *pourquoi* les changements ont été réalisé, paraphraser les spécifications dans le cas d'une nouvelle fonctionnalité ou bien donner les étapes pour reproduire un bug qui a été corrigé. 55 | 56 | [![Des spécifications très complètes et très précises](/assets/images/strip-Les-specs-cest-du-code.jpg)](https://www.commitstrip.com/fr/2016/08/25/a-very-comprehensive-and-precise-spec/) 57 | 58 | ### Pied de page 59 | 60 | En pied de page (footer, trailer) peuvent se trouver toutes une série de méta-information libres au format "Clé: valeur". On y retrouve: 61 | 62 | * Les URLs pour tous les liens présents dans le corps du message. 63 | * Les références vers les issues et les PR de Github. 64 | * Les références vers les tâches ou les tickets dans les autres logiciels de gestion. 65 | * Les références vers les autres commits liés au commit présent. 66 | * Le nom des co-auteurs et de ceux qui ont effectué une relecture (*review*). 67 | 68 | Dans le cas où notre commit est lié au ticket numéro 123 dans Jira et qu'il vient également clôturer l'issue 172 et la pull-request 173 dans Github, on écrira: 69 | 70 | Closes: #172 71 | Closes: #173 72 | JRA-123 73 | 74 | Notez que pour éviter que le numéro d'un ticket dans un logiciel externe soit reconnu (à tord) comme le numéro d'une issue ou d'une PR dans Github, la convention est d'accoler le numéro avec un tiret plutôt que deux-points. 75 | 76 | Dans le cas où notre commit est en relation avec un commit provenant d'une autre branche ou d'un autre projet, on écrira: 77 | 78 | Reference-to: orga/repo@1a2bc3 (fix bad smtp To header for unicode address) 79 | 80 | Dans le cas où notre collègue John Doe nous a beaucoup aidé dans la conception ou la réalisation du travail et qu'on souhaiter le créditer, on écrira: 81 | 82 | Co-authored-by: John Doe 83 | 84 | Comme ces champs sont libres, il n'existe pas de registre officiel où l'ensemble des clés possibles sont regroupées. Ces quelques précédents exemples ne sont que des exemples assez répandus. 85 | 86 | Nous pouvons aussi citer `Helper-By`, `Acked-By` et `Signed-off-by` pour respectivement créditer quelqu'un qui a aidé pendant le développement, ceux qui ont review la pull request et celui qui prend la responsabilité légale des changements. 87 | 88 | ## Utilisation 89 | 90 | Les commits et leurs messages sont lus à trois occasions différentes. Lors de la review par les reviewers, par intérêt général par les curieux qui lisent régulièrement l'historique pour se tenir au courant des modifications et au final par les collègues qui se creusent la tête au moment de debugger un bout de code qu'ils ne comprennent pas. 91 | 92 | ### Review 93 | 94 | Lors de la review, les reviewers doivent comprendre l'ensemble des changements et les valider quant aux fonctionnalités qui étaient spécifiées. Ils étudient les changements en lisant scrupuleusement chaque ligne de code et en testant les nouvelles fonctionnalités. Le but de la review est de s'assurer que le travail est bien fait, de vérifier qu'il n'y a pas de bug et d'ouvrir une discussion pour proposer des améliorations avant que le code ne soit validé et fusionné. 95 | 96 | Pour ouvrir une discussion constructive, il est donc important que les reviewers comprennent l'ensemble des changements apportés au code. Le message de commit doit leur permettre d'avoir une vue d'ensemble de ce qui est modifié de sorte à ce qu'il n'y ait pas de surprises, pas de moments "WTF?!", lorsqu'ils liront le code la première fois. 97 | 98 | [![code review wtf/minutes](/assets/images/wtf.png)](https://commadot.com/wp-content/uploads/2009/02/wtf.png) 99 | 100 | Ceci implique que le code doit être soigné et correctement structuré pour inviter des commentaires plus de fonds que de formes. Ceci implique aussi que les reviewers doivent avoir une vue d'ensemble de ce qui est modifié et comprendre les décisions qui ont été prises au moment du développement. C'est ici qu'intervient le message de commit. En rappelant le contexte générale et en justifiant les choix techniques. 101 | 102 | Lorsque le message de commit est manquant, incomplet ou qu'il n'est pas correctement rédigé, les reviewers se retrouvent alors dans une situation où ils doivent beaucoup plus travailler pour comprendre ce qu'il se passe. Ils doivent relire l'ensemble des discussions autours de la spécification originale (si une telle spécification existe) et passer beaucoup plus de temps sur le code. Il y a alors un risque non négligeable que le reviewer ne prenne pas ce temps nécessaire et se contente d'une review minimale... avec son lot de répercutions sur la dette technique. Cette situation est bien reprise dans le short suivant, même s'il devrait plutôt être labellisé "how we write/review code in *shitty* companies": . 103 | 104 | ### Blame 105 | 106 | Le second moment où les messages de commit s'avèrent utiles est lorsqu'il s'agit d'étudier un bout de code, de comprendre le fonctionnement d'une fonction au travers du `git blame`. 107 | 108 | `git blame` est une commande qui permet d'associer chaque ligne de code d'un fichier avec le commit qui en dernier a modifié la ligne. Il est alors possible d'étudier les changements qui ont été opérés sur une fonction pour mieux en comprendre son fonctionnement. 109 | 110 | Le plus souvent cet outil est utilisé lorsqu'un bout de code est "WTF?!", que quelques lignes de code sortent de l'ordinaire, contrastent avec le reste d'une fonction sans qu'aucun commentaire/docstring ne l'explique, ou bien qu'il y a bel et bien un commentaire mais que celui-ci reste trop succin. 111 | 112 | Le moment où une personne étudiera l'historique au travers du `git blame` sera souvent complètement déconnecté du moment où les changements auront été commités. On peut parler ici d'une différence allant même jusqu'à plusieurs années. Il est donc important de s'assurer au moment de créer les commits que chaque ligne dans la diff puisse être comprise via le message de commit. Il faut donc résister à l'envie de créer un seul gros commit pour plusieurs changements orthogonaux (des changements qui n'ont pas grand chose à voir les uns avec les autres) et au besoin isoler chaque groupe de changements dans des commits séparés. Il est d'ailleurs tout à fait acceptable d'avoir plusieurs commits dans une même pull request pour profiter d'une seule review pour l'ensemble des changements mais de tout de même correctement segmenter le travail en prévision d'un futur `git blame`. 113 | 114 | ### Log 115 | 116 | Le dernier moment où quelqu'un est susceptible de lire les messages de commit est lorsque cette personne s'intéresse à l'historique en générale pour se tenir au courant des changements récents. C'est principalement le cas dans les petites équipes, lorsqu'il y est encore humainement faisable de se tenir au courant de l'ensemble des modifications apportées au projet. La plupart des bibliothèques open-source évoluent d'ailleurs suffisamment lentement pour qu'il soit possible de se tenir à jour des changements d'API. 117 | 118 | Au moment d'écrire un message de commit, si le travail contient une nouvelle fonctionnalité ou un changement d'API, il est intéressant de rédiger un résumé explicatif des nouvelles fonctionnalités et de clairement expliquer tout changement d'API avec des exemples claires. 119 | 120 | ## Qualité 121 | 122 | Déterminer la qualité d'un message de commit ou plus généralement d'un historique git est assez subjectif. Il n'y a pas de recette miracle pour améliorer sa rédaction en dehors de pratiquer régulièrement et de prendre la peine de s'auto-évaluer. Un exercice utile peut être de se porter régulièrement volontaire pour faire des reviews de pull requests et de porter un regard attentif au message de commit. À force de faire des reviews, vous apprendrez à reconnaitre ce qui est utile, nécessaire, voir même précieux dans l'ensemble de la paperasse (spec, message de commit, documentation, docstrings, commentaires, ...) et à reprendre les bons exemples et éviter les mauvais. 123 | 124 | Vous pouvez vous poser la question quant à la fréquence à laquelle vous lisez les messages de commit. Que ce soit pendant les reviews, avec `git blame` ou avec `git log`. Si vous n'arrivez jamais à trouver les informations que vous cherchez ni avec blame, ni avec log, c'est certainement que l'historique et les commits ne sont pas bons. Si au contraire vous vous retrouvez régulièrement à naviguer dans l'historique, c'est certainement que le travail est bien fait. 125 | 126 | ## Conclusion 127 | 128 | La rédaction des messages de commit fait parti intégrante du métier de développeur et vient, à l'instar de l'écriture des tests unitaires, différencier le développeur amateur du développeur professionnel. 129 | 130 | Les messages de commit sont le cœur de la documentation technique à destination des mainteneurs. Ils sont écris pour les collègues d'aujourd'hui qui devront aussi bien valider notre travail via les pull requests, ils sont aussi écrits pour les collègues de demain qui devront étudier notre code pour le faire évoluer. 131 | 132 | Grâce aux messages de commit, il est possible de retracer l'évolution d'un logiciel et de développer sa propre compréhension des différentes étapes qui l'ont mené à être ce qu'il est aujourd'hui. Il s'agit d'un atout précieux qui renforce la confiance des développeurs quant aux changements qu'ils apportent au logiciel. 133 | -------------------------------------------------------------------------------- /langages/python/critique-du-cours-video-sur-python-de-graven.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | author: Julien Castiaux 4 | date: 2021-08-30 5 | last_update: 2021-09-12 6 | title: "Critique du cours vidéo sur Python de Graven" 7 | --- 8 | 9 | Le cours intitulé "Apprendre le Python" est un cours proposé sur la chaîne YouTube Gravenilvectuto. Le cours se présente 10 | comme un cours pour apprendre les fondamentaux du langage de programmation Python. Il est constitué de 9 vidéos sur le 11 | langage d'une moyenne de 13 minutes et d'une vidéo sur la bibliothèque Tkinter de 40 minutes. 12 | 13 | Avant tout chose, je (Julien -Dr Lazor- Castiaux) n'ai aucun grief contre Lorenzo, le vidéaste auteur de ce cours. La 14 | qualité visuelle des vidéos est supérieure à ce que l'on trouve d'ordinaire, le rythme est correctement soutenu et les 15 | exemples sont bien choisis. Les critiques de ce billet ne portent que sur le coté technique, le code source et les 16 | explications. 17 | 18 | ## Les défauts du cours 19 | 20 | ### Épisode 1 - Introduction 21 | 22 | Contrairement à ce qui est présenté dans la vidéo, un IDE (environnement de développement intégré) n'est 23 | pas *nécessaire* pour développer. Il est d'ailleurs plus commun de croiser des développeurs Python qui utilisent le 24 | combo éditeur de texte et terminal du fait de la légèreté du langage. [J'ai écrit un billet à ce sujet](https://docs.drlazor.be/python_ide.md). 25 | 26 | L'ensemble des vidéos sera capturé dans cet environnement. On peut regretter que le vidéaste ne désactive pas les 27 | différents linters et correcteurs orthographiques. Les démonstrations sont sans-cesse interrompues par myriade de 28 | pop-up de conseil et les différents mots français sont régulièrement soulignés inutilement par le correcteur 29 | orthographique. 30 | 31 | Lors de l'installation de Python, l'attention du téléspectateur n'est pas attirée sur l'option "Add Python to PATH", il 32 | s'agit d'une case à cocher qui rend accessible les programmes `python`, `py` et `pip` via la ligne de commande. Les 33 | débutants ne savent généralement pas comment changer les variables d'environnement sur Windows et se plaignent de ne 34 | pas réussir à lancer Python car `'python' n’est pas reconnu en tant que commande interne ou externe, un programme 35 | exécutable ou un fichier de commandes.`. 36 | 37 | Le tout premier bout de code montré à l'écran est le suivant : 38 | 39 | ```python 40 | if __name__ == "__main__": 41 | print("Hello world") 42 | ``` 43 | 44 | Si il est commun de voir un `if __name__ == "__main__"` dans les projets Python, il n'est pas nécessaire et ne présente 45 | un intérêt qu'au moment où les modules sont introduits. Il aurait été plus judicieux de ne pas l'introduire pour ne pas 46 | rajouter de la complexité inutile. 47 | 48 | Le dernier point sera heureusement rectifié dans deux vidéos. 49 | 50 | ### Épisode 2 - Variables 51 | 52 | L'épisode est bon dans l'ensemble. 53 | 54 | Je regrette néanmoins que la fonction `type()` n'ait pas été introduite, elle aurait permis de construire la notion de 55 | type autrement que par la coloration syntaxique dans pycharm. 56 | 57 | Je regrette également que la fonction `print` n'ait pas été utilisé correctement. La fonction peut prendre plusieurs 58 | arguments qui seront tous castés en string et concaténés avec un espace. La solution proposée est de passer via le type 59 | `str` pour convertir le nombre en chaîne de caractères. 60 | 61 | Il aurait été judicieux de présenter ces types plus tôt, à savoir les types `int()`, `str()`, `float()` et `bool()`, et 62 | de montrer qu'il peuvent être utilisés pour convertir les types entre eux. Les explications de la conversion en booléen 63 | auraient par exemple pu servir de fondation pour l'épisode sur les conditions. 64 | 65 | La fonction `format` sera présentée dans la vidéo suivante pour faciliter l'insertion de données dans les chaînes de 66 | caractères. 67 | 68 | ### Épisode 3 - Conditions 69 | 70 | Il existe une [syntaxe dédiée](https://www.python.org/dev/peps/pep-0308/) pour les ternaires depuis Python 2.5(sortie en 71 | 2006). La syntaxe est la suivante : 72 | 73 | ```python 74 | x = true_value if condition else false_value 75 | ``` 76 | 77 | L'exemple d'expression ternaire présenté dans la vidéo, en plus d'être difficile à lire et obsolète, est faux. Le code 78 | montré réalise un accès dans un tuple littéral. Cet accès est possible car le tuple possède deux éléments accessibles 79 | aux indices 0 et 1 et que les deux valeurs booléenes ont un équivalent entier : 0 pour False, 1 pour True. Il est dit 80 | dans la vidéo que le premier élément sera renvoyé si l'expression est vraie, or c'est le second qui sera renvoyé. 81 | 82 | ### Épisode 4 - Listes 83 | 84 | Si l'ensemble des opérations présentées sur les listes sont correctes, l'attention quant à la complexité de ces 85 | opérations n'est pas attiré. Bon nombre d'opérations (`del`, `insert`, `remove`) sont en complexité linéaire et non pas 86 | constante. Utiliser ces opérations doit donc se faire avec parcimonie. 87 | 88 | Pour récupérer le dernier élément d'une liste, la syntaxe `len(online_players) - 1` est présentée. Cette syntaxe, bien 89 | que correcte, est inutilement verbeuse. Python est en fait capable de directement indexer en partant de la fin en 90 | fournissant un indice négatif. 91 | 92 | La vidéo présente la bibliothèque `statistics` pour calculer la moyenne d'une liste. Cette opération est en fait 93 | facilement réalisable au moyen des fonctions `sum` et `len`. 94 | 95 | Les listes seront d'ailleurs les seules structures de données présentées dans la vidéo, pas un mot sur les 96 | dictionnaires, les sets ou la bibliothèque collections. 97 | 98 | ### Épisode 5 - Boucles 99 | 100 | Le `for` traditionnellement utilisé dans les langages comme C, Java (`for (int x = 0; x < 5; x++)`) n'existe pas en 101 | Python. Python n'est capable d'itérer que sur des objets itérables (ceux qui définissent les méthodes magiques 102 | `__iter__` ou/et `__next__`). `range` est un de ces objets, bien qu'à l'utilisation il permet de simuler le `for` 103 | classique, il n'en reste pas moins un objet qui implémente ces deux méthodes. 104 | 105 | ### Épisode 6 - Fonctions 106 | 107 | Tout de suite après avoir montré comment définir et appeler des fonctions, l'auteur introduit les variables globales. 108 | Les variables globales font partie des techniques appelées de *mémoire partagée*, un ensemble de la mémoire qui est 109 | accessible et qui peut être modifié par plusieurs entités à la fois, ici les fonctions. Ces techniques sont connues 110 | pour mener à des programmes qui sont difficiles à maintenir, qui présentent plus de bug et qui empêchent l'exécution 111 | parallèle. Il est dangereux d'introduire ce mécanisme et on lui préférera systématiquement les fonctions 112 | paramétriques. 113 | 114 | En programmation orientée objet, lorsqu'une fonction nécessite un contexte pour fonctionner, on préférera utiliser un 115 | objet pour sauvegarder ce contexte et écrire une *méthode*. 116 | 117 | L'auteur se trompe lorsqu'il dit qu'il est nécessaire de marquer les variables globales comme globales pour pouvoir y 118 | accéder. Il n'y a aucun problème à accéder en *lecture* (affichage, utilisation) aux variables définies dans le 119 | contexte global. Ce mot clé n'est nécessaire que lorsqu'on *modifie* (affectation) les variables globales. 120 | 121 | ### Épisode 7 - Objets 122 | 123 | La vidéo est d'une très grande qualité visuelle, les animations sont réussies, les exemples sont bien choisis et le tout 124 | est correctement dynamique. En bref l'auteur réussi à expliquer le mécanisme de l'orienté objet dans une vidéo de 125 | seulement 20 minutes, chapeau l'artiste ! 126 | 127 | Cependant, les bonnes pratiques de développement Python contrastent avec les bonnes pratiques d'autres langages de 128 | programmation orientée-objet comme Java, C# ou PHP. En Python, la notion d'accessibilité des variables n'existe pas, 129 | pas de mot clé `public` ou `private` pour définir la visibilité des attributs ou des méthodes. En réalité il s'agit 130 | d'un choix d'implémentation, en Python on accédera directement à l'attribut tant en lecture qu'en écriture sans passer 131 | par des méthodes `get_xyz()` ou `set_xyz()`. Si exécuter une fonction est nécessaire pour effectuer en traitement avant 132 | de renvoyer une valeur, on préférera utiliser une propriété. 133 | 134 | ### Épisodes suivants 135 | 136 | Les épisodes 8, 9 et 10 ne présentent pas de problème significatif. 137 | 138 | ## Discussion 139 | 140 | ### Structure de données 141 | 142 | Les seules structures de données présentées dans le cours sont les listes et les objets. Python est un langage très 143 | riche qui définit une abondance de structures de données qu'il est nécessaire de connaître et de savoir exploiter. 144 | 145 | Un ensemble mathématique est une structure de données où les éléments sont ou ne sont pas présents. D'un point de vue 146 | informatique, ils peuvent être considérés comme des listes non-ordonnées sans doublons. Ils sont implémentés en Python 147 | via l'objet `set`. 148 | 149 | Un dictionnaire est un livre qui associe chaque mot d'une langue à une définition. Il existe aussi des dictionnaires de 150 | traduction où chaque mot d'une langue est associé au même mot dans une autre langue. En informatique, un dictionnaire 151 | (aussi appelé tableau associatif ou *map*) est une structure de données qui associe un élément quelconque à un autre 152 | élément quelconque. Ils sont implémentés en Python via l'objet `dict`. Les dictionnaires de base ne sont pas forcément 153 | triés (avant Python 3.7), leur équivalent trié est implémenté via `collections.OrderedDict`. 154 | 155 | Un multi-ensemble mathématique est un ensemble où les éléments peuvent exister plusieurs fois. En informatique ils sont 156 | généralement implémentés comme un dictionnaire qui associe chaque élément au nombre de fois où il existe. En Python ils 157 | sont implémentés via `collections.Counter`. 158 | 159 | Une file ou une queue comme une file à la caisse est une autre structure de données où on ajoute des éléments d'un côté 160 | et où on retire les éléments depuis l'autre côté. Si ces opérations sont possible sur des listes classiques, ces 161 | opérations sont néanmoins non-optimisées. La structure optimisée pour gérer des files existe via l'objet 162 | `collections.deque`. 163 | 164 | Les bibliothèques `collections`, `queue`, `bisect` et `heapq` regorgent d'autres structures de données qui ne sont pas 165 | reprises ici. 166 | 167 | ### Scope Python et variables globales 168 | 169 | Ce qu'on appelle le *scope* est le niveau d'accessibilité des variables, c'est-à-dire quand et comment les variables du 170 | programme sont accessibles. 171 | 172 | Lorsque Python tente d'accéder à une variable *en lecture*, il va consulter le scope de la fonction : il commence par 173 | consulter les variables locales à la fonction. S'il ne la trouve pas, il va consulter le scope de la fonction 174 | supérieure et ainsi de suite jusqu'à arriver au scope dit *global* qui correspond à la racine du module. Si la variable 175 | n'a été trouvée dans aucun scope, une erreur `NameError` sera remontée à l'utilisateur. 176 | 177 | Lorsque Python tente d'accéder à une variable *en écriture*, il ne consulte *que* les variables locales, si la variable 178 | n'existe pas elle sera créée dans les variables locales *même si* elle existait dans un scope supérieur. Pour pouvoir 179 | modifier une variable d'un scope supérieur il est nécessaire d'utiliser les mots clés `global` ou `nonlocal`. 180 | 181 | Si ce comportement est celui par défaut c'est qu'il est déconseillé de modifier les variables définies en dehors de sa 182 | propre fonction. En effet, pour un programme conséquent, il devient difficile de déterminer quels sont les fonctions 183 | qui modifient une variable, le développeur n'a pas d'autre solution que de scanner l'ensemble du programme pour 184 | déterminer quelle fonction lit / écrit cette variable ce qui pose des problèmes quant à la compréhension du programme 185 | en général. 186 | 187 | Un autre problème survient lorsqu'on souhaite paralléliser un traitement sur plusieurs processeurs. Si un processeur lit 188 | le contenu d'une variable globale en même temps qu'un autre processeur en change son contenu, on se retrouve dans une 189 | situation où *l'on ne sait pas* ce qui a été lu. La lecture a-t-elle eu lieu avant la modification ? Après ? Ou pire... 190 | Pendant ? 191 | 192 | Ces problèmes ne se produisent pas lorsqu'on utilise une stratégie d'implémentation claire ou l'utilisation des 193 | variables est clairement définie comme pour les fonctions paramétriques ou les objets. 194 | 195 | ### Visibilité des attributs et des méthodes 196 | 197 | La visibilité en orienté objet est une notion qui permet de contrôler *qui* a accès (en lecture, en écriture ou en 198 | exécution) aux attributs et aux méthodes d'un objet. Cette notion est implémentée dans nombre de langages orientés 199 | objet statiques comme Java, C# et C++. Lorsqu'un attribut est déclaré avec `private` elle n'est accessible que par 200 | l'objet courant, il en va de même pour les méthodes. Au contraire lorsqu'un attribut/une méthode est déclaré public, 201 | tout le monde peut y accéder. 202 | 203 | Cette notion de visibilité est absente de Python qui préfère *faire confiance aux développeurs* qu'ils n'aillent pas 204 | toucher ce qu'ils ne devraient pas. Ainsi la seule notion qui sépare les attributs, propriétés et méthodes publiques de 205 | celles considérées comme "privées" est une convention d'écriture. On préfixera ce qui est privé par un underscore. Le 206 | développeur est libre de n'en faire qu'à sa tête de tout de même les utiliser même si c'est déconseillé. 207 | 208 | Dans l'industrie il n'y a au final pas de problème à ce que le langage ne définisse pas lui-même de niveau de 209 | visibilité. Une équipe constituée de développeurs compétents, que ce soit en Python ou dans un autre langage, est 210 | capable de faire la part des choses. Il s'avère même parfois de meilleur goût de ne pas respecter cette convention de 211 | visibilité et de tout de même utiliser une variable / méthode "privée". 212 | 213 | ### Accesseurs et mutateurs 214 | 215 | Dans la même lignée qu'il n'y a pas de notion de visibilité en Python, les pratiques de développement Python conseillent 216 | de directement modifier les attributs des objets sans passer par des méthodes de contrôle. Dit autrement, l'utilisation 217 | systématique d'accesseurs (*getters*) et de mutateurs (*setters*) est déconseillé. 218 | 219 | Dans l'industrie ceci ne pose en réalité aucun souci, les développeurs qui définissent les interfaces objets sont 220 | généralement ceux qui les utiliseront ou qui seront chargés de relire le code des autres développeurs. 221 | 222 | Cependant, il est parfois nécessaire d'effectuer des traitements à l'accès ou à la modification des attributs. Pour ce 223 | faire, Python a opté pour les propriétés. Une propriété s'utilise comme une variable normale, on récupère la valeur en 224 | donnant uniquement le nom de la propriété et on modifie sa valeur comme une simple affectation. La différence est qu'au 225 | lieu d'être directement accédée ou modifiée, c'est une fonction de traitement qui sera chargée de renvoyer / modifier 226 | la variable. 227 | 228 | Via ce mécanisme, il est possible de laisser un attribut "public" (sans underscore en préfixe) et s'il s'avère à 229 | l'utilisation qu'une fonction de traitement est nécessaire, il suffit de préfixer l'attribut pour qu'il soit "privé" et 230 | de définir au niveau de la classe une propriété du même nom que l'ancien attribut public. Le code qui exploitait 231 | l'attribut exploite maintenant la propriété indifféremment. 232 | 233 | ### Cours de Java... en Python... 234 | 235 | Si la qualité visuelle des vidéos et la dynamique du cours sont supérieures à bon nombre de cours en ligne (gratuit et 236 | payant), le côté technique est quant à lui approximatif. L'approche pédagogique de l'auteur souffre de nombreux biais 237 | qui sont communs aux développeurs Java qui s'essaient au langage Python. 238 | 239 | Plusieurs arguments penchent en cette faveur : 240 | 241 | * Il est commun d'utiliser un IDE complet pour développer en Java, pas en Python ; 242 | * Il y a conversion automatique des entiers vers les chaînes de caractère en Java, en Python elles doivent être 243 | explicites ; 244 | * L'expression `condition ? true_value : false_value` est une expression ternaire en Java, l'expression Python ` 245 | (false_value, true_value)[condition]` y ressemble fâcheusement (expressions finales d'un côté, condition de l'autre) 246 | mais n'est pas une expression ternaire en Python ; 247 | * Le *for* classique n'existe pas en Python, il est simulé par l'objet `range` mais il n'existe bien en Python que ce 248 | que les développeurs Java appellent le *for each* ; 249 | * Les bonnes pratiques Java dictent qu'il est important de définir des méthodes dédiées (*getters* et *setters*) pour 250 | manipuler les attributs d'un objet, les bonnes pratiques Python dictent de modifier directement l'attribut ou de 251 | passer par des propriétés. 252 | 253 | ## Conclusion 254 | 255 | Le cours est gangrené par les habitudes de développeur Java de l'auteur. Certains concepts essentiels dans le 256 | développement Python sont complètements omis comme les dictionnaires, les sets, les générateurs ou la bibliothèque 257 | standard. Certains sont approximatifs comme les conditions ou les boucles. Les derniers introduisent des concepts qui 258 | vont à l'encontre des bonnes pratiques de développement en Python comme les fonctions ou les objets. 259 | 260 | Graven est certainement un très bon développeur Java ! 261 | -------------------------------------------------------------------------------- /game-dev/ressources.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | author: Le serveur Not a Name 4 | date: 2021-09-04 5 | last_update: 2023-03-12 6 | title: "Ressources pour game-dev" 7 | --- 8 | 9 | Ce document liste differentes ressources utiles au game-dev. 10 | 11 | **Tags:** [Animation], [Audio], [Background], [Decals], [Font], [HDRI], [Landscape], [Modèle], [PBR], [Sprite], [Texture], [Tileset], [Traduction], [UI] 12 | 13 | ## Asset 14 | 15 | | | Licence | Prix | Meta 16 | | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ----------------- | --------------------- 17 | | [3D Warehouse ](https://3dwarehouse.sketchup.com) | Variées | Gratuit | [Modèle] 18 | | [99 Sounds ](https://99sounds.org/free-sound-effects/) | [Custom](https://99sounds.org/license/) | Gratuit ou Payant | [Audio] 19 | | [AmbientCG ](https://ambientcg.com) | [CC0](https://help.ambientcg.com/01-General/Licensing.html) | Gratuit | [HDRI], [Modèle], [PBR], [Texture] 20 | | [Archive3D ](https://archive3d.net) | Variées | Gratuit | [Modèle] 21 | | [ArtStation - Gamedev ](www.artstation.com/marketplace/game-dev) | Variées | Gratuit ou Payant | [Background] / [Modèle] / [Texture] / [UI] 22 | | [Assimp Model Database ](https://github.com/assimp/assimp-mdb) | Free | Gratuit | [Modèle] 23 | | [AudioJungle ](https://audiojungle.net) | [Custom](https://audiojungle.net/licenses/music?license=music_standard) | Payant | [Audio] 24 | | [BlendSwap ](https://www.blendswap.com/blends) | Variées | Gratuit | [Modèle] blender 25 | | [Blogoscoped.com ](http://blogoscoped.com/archive/2006-08-08-n51.html) | [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/) | Gratuit | Avatars de fantasy - [Sprite] 26 | | [Blender 3D Models ](https://www.blender-models.com) | Variées | Gratuit | [Modèle] blender divers 27 | | [CG Book case ](https://www.cgbookcase.com) | CC0 | Gratuit | [PBR] / [Texture] 28 | | [CMU GLMCD ](http://mocap.cs.cmu.edu) | Free | Gratuit | Base de donnée remplies d'[Animation] 29 | | [Common game localizations ](https://docs.google.com/spreadsheets/d/135HgMYcRDt6vnJN0d-xFMEZUeWjVbc61ETf8uVwHERE/edit?usp=sharing) | ? | Gratuit | [Traduction] 30 | | [Crateboy ](https://crateboy.itch.io/crateboy-2007-2014) | Creative Commons | Gratuit | [Sprite] et [Tileset] divers 31 | | [Dafont ](https://www.dafont.com/) | Variées | Gratuit ou Payant | [Font] 32 | | [Dexsoft ](https://www.dexsoft-games.com) | Commercial | Payant | [Animation] / [Modèle] / [Texture] 33 | | [Dumbanex.com ](http://www.dumbmanex.com/bynd_freestuff.html) | Free avec attribution | Gratuit | [Sprite] / [Tileset] 34 | | [FamFamFam ](http://www.famfamfam.com/lab/icons/) | Free | Gratuit | [UI] 35 | | [FlatIcons ](https://flaticons.net) | Royalty free | Gratuit | [UI] 36 | | [FontLibrary ](https://fontlibrary.org) | [Variées](https://fontlibrary.org/en/guidebook/supported_licenses) | Gratuit | [Font] 37 | | [FreeGameArts ](http://freegamearts.tuxfamily.org) | Variées | Gratuit | [Audio] / [Modèle] / [Texture] 38 | | [Freesound ](https://freesound.org) | Variées | Gratuit | Enorme bibliothèque [Audio] 39 | | [Free Music Archive ](https://freemusicarchive.org) | Creative Commons | Gratuit | [Audio] 40 | | [Free To Use Sound ](https://www.freetousesounds.com/) | Variées | Gratuit | [Audio] 41 | | [Game-Icons ](https://game-icons.net) | [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/) | Gratuit | [UI] 42 | | [GameArt2D ](https://www.gameart2d.com/) | [CC0 ou Custom](https://www.gameart2d.com/license.html) | Gratuit ou Payant | [Sprite] / [Tileset] / [UI] 43 | | [GameAssets.org ](https://gameasset.org) | Public domain | Gratuit | [Audio] / [Modèle] / [Sprite] / [Texture] 44 | | [Game common words translation sheet](https://docs.google.com/spreadsheets/d/17f0dQawb-s_Fd7DHgmVvJoEGDMH_yoSd8EYigrb0zmM/edit?usp=sharing) | ? | Gratuit | [Traduction] 45 | | [Glitch ](https://www.glitchthegame.com/) | Public domain | Gratuit | [Background] / [Sprite] / [Tileset] 46 | | [Glyphicons ](https://glyphicons.com) | Custom payante | Payant | Packs d'icones diverses pour [UI] 47 | | [Hero Patterns ](http://www.heropatterns.com) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Gratuit | [Background] / [UI] 48 | | [Icônes.js ](https://icones.js.org) | Variées | Gratuit | [UI] 49 | | [HeroIcons ](http://www.heroicons.com) | MIT | Gratuit | [UI] 50 | | [IconFinder ](https://www.iconfinder.com) | Variées | Gratuit ou Payant | [UI] 51 | | [Image * After ](http://www.imageafter.com) | [Custom](http://www.imageafter.com/terms.php) | Gratuit | [Texture] 52 | | [Incompetech ](https://incompetech.com/music/royalty-free/) | [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/) | Gratuit | [Audio] 53 | | [Itch.io ](https://itch.io/game-assets) | Variées | Gratuit ou Payant | De tout: [Animation], [Audio], [Background], [Decals], [Font], [HDRI], [Landscape], [Modèle], [PBR], [Sprite], [Texture], [Tileset], [UI] 54 | | [Julio Sillet 3D Art ](https://juliosillet.gumroad.com) | Variées | Gratuit ou Payant | [Modèle] / [Texture] 55 | | [Kenney ](https://kenney.nl) | [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/) | Gratuit | [Audio] / [Modèle] / [Sprite] / [Tileset] / [UI] 56 | | [Kitbash 3D ](https://kitbash3d.com/collections/kits) | [Custom](https://kitbash3d.com/pages/licenses) | Payant | [Modèle] 3D haute qualité 57 | | [LodBook ](https://lodbook.com/models/) | ? | Gratuit | [Modèle] 58 | | [Mocapdata ](http://mocapdata.com) | Variées | Gratuit ou Payant | [Animation] 59 | | [Museum Textures ](https://www.museumtextures.com) | [CC BY-NC 2.0](https://creativecommons.org/licenses/by-nc/2.0/fr/) | Gratuit | [Texture] 60 | | [OpenGameArt ](https://opengameart.org) | [Variées](https://opengameart.org/content/faq#q-proprietary) | Gratuit | [Audio] / [Font] / [Modèle] / [Sprite] / [Texture] / [Tileset] / [UI] 61 | | [Open Music Archive ](http://www.openmusicarchive.org) | [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) | Gratuit | [Audio] 62 | | [PDSounds ](http://pdsounds.org) | Public domain | Gratuit | [Audio] 63 | | [Pixabay ](https://pixabay.com) | Variées | Gratuit | [Texture] - Grande banque d'images 64 | | [PM SFX ](https://www.pmsfx.com) | Variées | Gratuit | [Audio] 65 | | [PolyglotGamedev ](https://docs.google.com/spreadsheets/d/17f0dQawb-s_Fd7DHgmVvJoEGDMH_yoSd8EYigrb0zmM/edit?usp=sharing) | CC0 | Gratuit | [Traduction] 66 | | [Polyhaven ](https://polyhaven.com/) | [CC0](https://polyhaven.com/license) | Gratuit | [HDRI] / [Modèle] / [Texture] 67 | | [Quaternius ](https://quaternius.com/index.html) | Variées | Gratuit ou Payant | [Modèle] 3D / [Animation] 68 | | [Reinerstilesets.de ](https://www.reinerstilesets.de) | [Free avec attribution](https://www.reinerstilesets.de/graphics/lizenz/) | Gratuit | [Audio] / [Modèle] / [Sprite] / [Tileset] 69 | | [Renderpeople ](https://renderpeople.com/free-3d-people/) | Variées | Gratuit ou Payant | [Modèle] 3D de personnées scannées 70 | | [ScanTheWorld ](https://www.myminifactory.com/scantheworld/) | Variées | Gratuit | Scans de [Modèle] 3D 71 | | [Sketchfab ](https://sketchfab.com) | Variées | Gratuit ou Payant | [Modèle] 3D 72 | | [Smithsonian Open Access ](https://www.si.edu/openaccess) | [CCO](https://www.si.edu/openaccess/faq) | Gratuit | [Modèle] 3D / [Texture] 73 | | [Sonniss Game Audio GDC ](https://sonniss.com/gameaudiogdc) | Variées | Gratuit | Historique de l'[Audio] de la GDC 74 | | [Subtle Patterns ](https://www.toptal.com/designers/subtlepatterns/) | Free avec attribution | Gratuit | [Background] / [UI] 75 | | [ShareCG ](https://www.sharecg.com) | Variées | Gratuit | [Audio] / [Modèle] / [Texture] 76 | | [SpriteLib ](https://www.widgetworx.com/spritelib/) | [CPL-1.0](https://opensource.org/licenses/cpl1.0.php) | Gratuit | [Sprite] 77 | | [Textures.com ](https://www.textures.com/) | [Licence](https://www.textures.com/support/faq-license) | Gratuit ou Payant | [Decals] / [HDRI] / [Modèle] / [PBR] / [Texture] 78 | | [The base mesh ](https://thebasemesh.com/model-library) | [CC0](https://thebasemesh.com/faqs) | Gratuit | [Modèle] 79 | | [The league of movable type](https://www.theleagueofmoveabletype.com) | [Free](https://www.theleagueofmoveabletype.com/manifesto) | Gratuit | [Font] 80 | | [TrueBones ](https://truebones.com) | Commercial | Payant | [Animation] 81 | | [Turbosquid ](https://www.turbosquid.com) | Variées | Gratuit ou Payant | [Modèle] 82 | | [Unity assets store ](https://assetstore.unity.com) | Variées | Gratuit ou Payant | De tout: [Animation], [Audio], [Background], [Decals], [Font], [HDRI], [Landscape], [Modèle], [PBR], [Sprite], [Texture], [Tileset], [UI] 83 | | [Unreal free assets ](https://unrealengine.com/marketplace/en-US/free) | Variées | Gratuit | De tout: [Animation], [Audio], [Background], [Decals], [Font], [HDRI], [Landscape], [Modèle], [PBR], [Sprite], [Texture], [Tileset], [UI] 84 | | [Unsplash ](https://unsplash.com) | Variées | Gratuit | [Texture] - Grande banque d'images 85 | | [Untamed.wild-refuge.net ](http://untamed.wild-refuge.net/rpgxp.php) | Free | Gratuit | [Sprite] et [Tileset] divers sur le thème du RPG 86 | 87 | ## Création d'assets 88 | 89 | | | Licence | Prix | Meta 90 | |-----------------------------------------------------------------------------|------------------|-------------------------|--------------------- 91 | | [Asset Forge ](https://assetforge.io) | - | $19.95 ou $39.95 | Outil de création de [Modèle] très complet 92 | | [Coolors ](https://coolors.co) | - | Gratuit | Generateurs de palettes de couleurs 93 | | [Colormind ](http://colormind.io) | - | Gratuit | Generateurs de palettes de couleurs 94 | | [DrPetter’s sfxr ](http://www.drpetter.se/project_sfxr.html) | - | Gratuit | Generateur d'effets sonores - [Audio] 95 | | [InterfaceInGame ](https://interfaceingame.com) | - | Gratuit | Analyse/Inspiration d'[UI] de jeux connus 96 | | [KenShape ](https://tools.kenney.nl/kenshape/) | - | $3.99 | Createur d'assets 3D lowpoly supportés par la plupart des moteurs graphiques - [Modèle] 97 | | [MocapX ](https://www.mocapx.com) | - | $0-299 | Outil de motion capture utilisable avec Maya et un telephone - [Animation] 98 | | [Nukeygara ](https://www.nukeygara.com) | - | Essai gratuit ou Payant | Logiciel d'animation 3D fait spécifiquement pour le jeux vidéo 99 | | [Paletton ](https://paletton.com/) | - | Gratuit | Generateurs de palettes de couleurs 100 | | [PicoCAD ](https://johanpeitz.itch.io/picocad) | - | Gratuit | Outil de création de [Modèle] lowpoly 101 | | [Quixel mixer ](https://quixel.com/mixer) | - | Gratuit | Permet de fusionner des [Texture] 102 | | [Spacescape ](http://alexcpeterson.com/spacescape/) | - | Gratuit | Cubemap spatial 103 | | [Unreal MetaHuman ](https://www.unrealengine.com/en-US/metahuman-creator) | - | Gratuit | Outil de création de visages réalistes - [Animation] / [Modèle] 104 | | [VideoGameInterfaces](https://videogameinterfaces.com) | - | Gratuit | Analyse/Inspiration d'[UI] de jeux connus 105 | | [World creator ](https://www.world-creator.com/) | - | Payant (3 tiers) | [Landscape] 106 | 107 | 108 | [Animation]: #ressources-pour-game-dev 109 | [Audio]: #ressources-pour-game-dev 110 | [Background]: #ressources-pour-game-dev 111 | [Decals]: #ressources-pour-game-dev 112 | [Font]: #ressources-pour-game-dev 113 | [HDRI]: #ressources-pour-game-dev 114 | [Landscape]: #ressources-pour-game-dev 115 | [Modèle]: #ressources-pour-game-dev 116 | [PBR]: #ressources-pour-game-dev 117 | [Sprite]: #ressources-pour-game-dev 118 | [Texture]: #ressources-pour-game-dev 119 | [Tileset]: #ressources-pour-game-dev 120 | [Traduction]: #ressources-pour-game-dev 121 | [UI]: #ressources-pour-game-dev 122 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Attribution-ShareAlike 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution-ShareAlike 4.0 International Public 58 | License 59 | 60 | By exercising the Licensed Rights (defined below), You accept and agree 61 | to be bound by the terms and conditions of this Creative Commons 62 | Attribution-ShareAlike 4.0 International Public License ("Public 63 | License"). To the extent this Public License may be interpreted as a 64 | contract, You are granted the Licensed Rights in consideration of Your 65 | acceptance of these terms and conditions, and the Licensor grants You 66 | such rights in consideration of benefits the Licensor receives from 67 | making the Licensed Material available under these terms and 68 | conditions. 69 | 70 | 71 | Section 1 -- Definitions. 72 | 73 | a. Adapted Material means material subject to Copyright and Similar 74 | Rights that is derived from or based upon the Licensed Material 75 | and in which the Licensed Material is translated, altered, 76 | arranged, transformed, or otherwise modified in a manner requiring 77 | permission under the Copyright and Similar Rights held by the 78 | Licensor. For purposes of this Public License, where the Licensed 79 | Material is a musical work, performance, or sound recording, 80 | Adapted Material is always produced where the Licensed Material is 81 | synched in timed relation with a moving image. 82 | 83 | b. Adapter's License means the license You apply to Your Copyright 84 | and Similar Rights in Your contributions to Adapted Material in 85 | accordance with the terms and conditions of this Public License. 86 | 87 | c. BY-SA Compatible License means a license listed at 88 | creativecommons.org/compatiblelicenses, approved by Creative 89 | Commons as essentially the equivalent of this Public License. 90 | 91 | d. Copyright and Similar Rights means copyright and/or similar rights 92 | closely related to copyright including, without limitation, 93 | performance, broadcast, sound recording, and Sui Generis Database 94 | Rights, without regard to how the rights are labeled or 95 | categorized. For purposes of this Public License, the rights 96 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 97 | Rights. 98 | 99 | e. Effective Technological Measures means those measures that, in the 100 | absence of proper authority, may not be circumvented under laws 101 | fulfilling obligations under Article 11 of the WIPO Copyright 102 | Treaty adopted on December 20, 1996, and/or similar international 103 | agreements. 104 | 105 | f. Exceptions and Limitations means fair use, fair dealing, and/or 106 | any other exception or limitation to Copyright and Similar Rights 107 | that applies to Your use of the Licensed Material. 108 | 109 | g. License Elements means the license attributes listed in the name 110 | of a Creative Commons Public License. The License Elements of this 111 | Public License are Attribution and ShareAlike. 112 | 113 | h. Licensed Material means the artistic or literary work, database, 114 | or other material to which the Licensor applied this Public 115 | License. 116 | 117 | i. Licensed Rights means the rights granted to You subject to the 118 | terms and conditions of this Public License, which are limited to 119 | all Copyright and Similar Rights that apply to Your use of the 120 | Licensed Material and that the Licensor has authority to license. 121 | 122 | j. Licensor means the individual(s) or entity(ies) granting rights 123 | under this Public License. 124 | 125 | k. Share means to provide material to the public by any means or 126 | process that requires permission under the Licensed Rights, such 127 | as reproduction, public display, public performance, distribution, 128 | dissemination, communication, or importation, and to make material 129 | available to the public including in ways that members of the 130 | public may access the material from a place and at a time 131 | individually chosen by them. 132 | 133 | l. Sui Generis Database Rights means rights other than copyright 134 | resulting from Directive 96/9/EC of the European Parliament and of 135 | the Council of 11 March 1996 on the legal protection of databases, 136 | as amended and/or succeeded, as well as other essentially 137 | equivalent rights anywhere in the world. 138 | 139 | m. You means the individual or entity exercising the Licensed Rights 140 | under this Public License. Your has a corresponding meaning. 141 | 142 | 143 | Section 2 -- Scope. 144 | 145 | a. License grant. 146 | 147 | 1. Subject to the terms and conditions of this Public License, 148 | the Licensor hereby grants You a worldwide, royalty-free, 149 | non-sublicensable, non-exclusive, irrevocable license to 150 | exercise the Licensed Rights in the Licensed Material to: 151 | 152 | a. reproduce and Share the Licensed Material, in whole or 153 | in part; and 154 | 155 | b. produce, reproduce, and Share Adapted Material. 156 | 157 | 2. Exceptions and Limitations. For the avoidance of doubt, where 158 | Exceptions and Limitations apply to Your use, this Public 159 | License does not apply, and You do not need to comply with 160 | its terms and conditions. 161 | 162 | 3. Term. The term of this Public License is specified in Section 163 | 6(a). 164 | 165 | 4. Media and formats; technical modifications allowed. The 166 | Licensor authorizes You to exercise the Licensed Rights in 167 | all media and formats whether now known or hereafter created, 168 | and to make technical modifications necessary to do so. The 169 | Licensor waives and/or agrees not to assert any right or 170 | authority to forbid You from making technical modifications 171 | necessary to exercise the Licensed Rights, including 172 | technical modifications necessary to circumvent Effective 173 | Technological Measures. For purposes of this Public License, 174 | simply making modifications authorized by this Section 2(a) 175 | (4) never produces Adapted Material. 176 | 177 | 5. Downstream recipients. 178 | 179 | a. Offer from the Licensor -- Licensed Material. Every 180 | recipient of the Licensed Material automatically 181 | receives an offer from the Licensor to exercise the 182 | Licensed Rights under the terms and conditions of this 183 | Public License. 184 | 185 | b. Additional offer from the Licensor -- Adapted Material. 186 | Every recipient of Adapted Material from You 187 | automatically receives an offer from the Licensor to 188 | exercise the Licensed Rights in the Adapted Material 189 | under the conditions of the Adapter's License You apply. 190 | 191 | c. No downstream restrictions. You may not offer or impose 192 | any additional or different terms or conditions on, or 193 | apply any Effective Technological Measures to, the 194 | Licensed Material if doing so restricts exercise of the 195 | Licensed Rights by any recipient of the Licensed 196 | Material. 197 | 198 | 6. No endorsement. Nothing in this Public License constitutes or 199 | may be construed as permission to assert or imply that You 200 | are, or that Your use of the Licensed Material is, connected 201 | with, or sponsored, endorsed, or granted official status by, 202 | the Licensor or others designated to receive attribution as 203 | provided in Section 3(a)(1)(A)(i). 204 | 205 | b. Other rights. 206 | 207 | 1. Moral rights, such as the right of integrity, are not 208 | licensed under this Public License, nor are publicity, 209 | privacy, and/or other similar personality rights; however, to 210 | the extent possible, the Licensor waives and/or agrees not to 211 | assert any such rights held by the Licensor to the limited 212 | extent necessary to allow You to exercise the Licensed 213 | Rights, but not otherwise. 214 | 215 | 2. Patent and trademark rights are not licensed under this 216 | Public License. 217 | 218 | 3. To the extent possible, the Licensor waives any right to 219 | collect royalties from You for the exercise of the Licensed 220 | Rights, whether directly or through a collecting society 221 | under any voluntary or waivable statutory or compulsory 222 | licensing scheme. In all other cases the Licensor expressly 223 | reserves any right to collect such royalties. 224 | 225 | 226 | Section 3 -- License Conditions. 227 | 228 | Your exercise of the Licensed Rights is expressly made subject to the 229 | following conditions. 230 | 231 | a. Attribution. 232 | 233 | 1. If You Share the Licensed Material (including in modified 234 | form), You must: 235 | 236 | a. retain the following if it is supplied by the Licensor 237 | with the Licensed Material: 238 | 239 | i. identification of the creator(s) of the Licensed 240 | Material and any others designated to receive 241 | attribution, in any reasonable manner requested by 242 | the Licensor (including by pseudonym if 243 | designated); 244 | 245 | ii. a copyright notice; 246 | 247 | iii. a notice that refers to this Public License; 248 | 249 | iv. a notice that refers to the disclaimer of 250 | warranties; 251 | 252 | v. a URI or hyperlink to the Licensed Material to the 253 | extent reasonably practicable; 254 | 255 | b. indicate if You modified the Licensed Material and 256 | retain an indication of any previous modifications; and 257 | 258 | c. indicate the Licensed Material is licensed under this 259 | Public License, and include the text of, or the URI or 260 | hyperlink to, this Public License. 261 | 262 | 2. You may satisfy the conditions in Section 3(a)(1) in any 263 | reasonable manner based on the medium, means, and context in 264 | which You Share the Licensed Material. For example, it may be 265 | reasonable to satisfy the conditions by providing a URI or 266 | hyperlink to a resource that includes the required 267 | information. 268 | 269 | 3. If requested by the Licensor, You must remove any of the 270 | information required by Section 3(a)(1)(A) to the extent 271 | reasonably practicable. 272 | 273 | b. ShareAlike. 274 | 275 | In addition to the conditions in Section 3(a), if You Share 276 | Adapted Material You produce, the following conditions also apply. 277 | 278 | 1. The Adapter's License You apply must be a Creative Commons 279 | license with the same License Elements, this version or 280 | later, or a BY-SA Compatible License. 281 | 282 | 2. You must include the text of, or the URI or hyperlink to, the 283 | Adapter's License You apply. You may satisfy this condition 284 | in any reasonable manner based on the medium, means, and 285 | context in which You Share Adapted Material. 286 | 287 | 3. You may not offer or impose any additional or different terms 288 | or conditions on, or apply any Effective Technological 289 | Measures to, Adapted Material that restrict exercise of the 290 | rights granted under the Adapter's License You apply. 291 | 292 | 293 | Section 4 -- Sui Generis Database Rights. 294 | 295 | Where the Licensed Rights include Sui Generis Database Rights that 296 | apply to Your use of the Licensed Material: 297 | 298 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 299 | to extract, reuse, reproduce, and Share all or a substantial 300 | portion of the contents of the database; 301 | 302 | b. if You include all or a substantial portion of the database 303 | contents in a database in which You have Sui Generis Database 304 | Rights, then the database in which You have Sui Generis Database 305 | Rights (but not its individual contents) is Adapted Material, 306 | 307 | including for purposes of Section 3(b); and 308 | c. You must comply with the conditions in Section 3(a) if You Share 309 | all or a substantial portion of the contents of the database. 310 | 311 | For the avoidance of doubt, this Section 4 supplements and does not 312 | replace Your obligations under this Public License where the Licensed 313 | Rights include other Copyright and Similar Rights. 314 | 315 | 316 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 317 | 318 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 319 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 320 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 321 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 322 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 323 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 324 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 325 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 326 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 327 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 328 | 329 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 330 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 331 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 332 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 333 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 334 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 335 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 336 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 337 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 338 | 339 | c. The disclaimer of warranties and limitation of liability provided 340 | above shall be interpreted in a manner that, to the extent 341 | possible, most closely approximates an absolute disclaimer and 342 | waiver of all liability. 343 | 344 | 345 | Section 6 -- Term and Termination. 346 | 347 | a. This Public License applies for the term of the Copyright and 348 | Similar Rights licensed here. However, if You fail to comply with 349 | this Public License, then Your rights under this Public License 350 | terminate automatically. 351 | 352 | b. Where Your right to use the Licensed Material has terminated under 353 | Section 6(a), it reinstates: 354 | 355 | 1. automatically as of the date the violation is cured, provided 356 | it is cured within 30 days of Your discovery of the 357 | violation; or 358 | 359 | 2. upon express reinstatement by the Licensor. 360 | 361 | For the avoidance of doubt, this Section 6(b) does not affect any 362 | right the Licensor may have to seek remedies for Your violations 363 | of this Public License. 364 | 365 | c. For the avoidance of doubt, the Licensor may also offer the 366 | Licensed Material under separate terms or conditions or stop 367 | distributing the Licensed Material at any time; however, doing so 368 | will not terminate this Public License. 369 | 370 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 371 | License. 372 | 373 | 374 | Section 7 -- Other Terms and Conditions. 375 | 376 | a. The Licensor shall not be bound by any additional or different 377 | terms or conditions communicated by You unless expressly agreed. 378 | 379 | b. Any arrangements, understandings, or agreements regarding the 380 | Licensed Material not stated herein are separate from and 381 | independent of the terms and conditions of this Public License. 382 | 383 | 384 | Section 8 -- Interpretation. 385 | 386 | a. For the avoidance of doubt, this Public License does not, and 387 | shall not be interpreted to, reduce, limit, restrict, or impose 388 | conditions on any use of the Licensed Material that could lawfully 389 | be made without permission under this Public License. 390 | 391 | b. To the extent possible, if any provision of this Public License is 392 | deemed unenforceable, it shall be automatically reformed to the 393 | minimum extent necessary to make it enforceable. If the provision 394 | cannot be reformed, it shall be severed from this Public License 395 | without affecting the enforceability of the remaining terms and 396 | conditions. 397 | 398 | c. No term or condition of this Public License will be waived and no 399 | failure to comply consented to unless expressly agreed to by the 400 | Licensor. 401 | 402 | d. Nothing in this Public License constitutes or may be interpreted 403 | as a limitation upon, or waiver of, any privileges and immunities 404 | that apply to the Licensor or You, including from the legal 405 | processes of any jurisdiction or authority. 406 | 407 | 408 | ======================================================================= 409 | 410 | Creative Commons is not a party to its public licenses. 411 | Notwithstanding, Creative Commons may elect to apply one of its public 412 | licenses to material it publishes and in those instances will be 413 | considered the “Licensor.” The text of the Creative Commons public 414 | licenses is dedicated to the public domain under the CC0 Public Domain 415 | Dedication. Except for the limited purpose of indicating that material 416 | is shared under a Creative Commons public license or as otherwise 417 | permitted by the Creative Commons policies published at 418 | creativecommons.org/policies, Creative Commons does not authorize the 419 | use of the trademark "Creative Commons" or any other trademark or logo 420 | of Creative Commons without its prior written consent including, 421 | without limitation, in connection with any unauthorized modifications 422 | to any of its public licenses or any other arrangements, 423 | understandings, or agreements concerning use of licensed material. For 424 | the avoidance of doubt, this paragraph does not form part of the public 425 | licenses. 426 | 427 | Creative Commons may be contacted at creativecommons.org. -------------------------------------------------------------------------------- /langages/python/comment-structurer-un-projet-python.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | author: Julien Castiaux 4 | date: 2023-04-07 5 | last_update: 2023-05-28 6 | title: "Comment structurer un projet Python" 7 | --- 8 | 9 | 10 | * [Introduction](#introduction) 11 | * [Mise en place du squelette du projet](#mise-en-place-du-squelette-du-projet) 12 | * [Adaptation d'un programme existant](#adaptation-dun-programme-existant) 13 | * [Isolation dans un environnement virtuel](#isolation-dans-un-environnement-virtuel) 14 | * [Ajout de tests automatiques](#ajout-de-tests-automatiques) 15 | * [Création d'une archive installable](#création-dune-archive-installable) 16 | * [Configuration de VSCode](#configuration-de-vscode) 17 | * [Configuration de PyCharm](#configuration-de-pycharm) 18 | * [Choix des technologies](#choix-des-technologies) 19 | * [Références](#références) 20 | 21 | ## Introduction 22 | 23 | La plupart des cours que nous recommandons[^1] [^2] [^3] pour apprendre Python se concentrent sur l'enseignement de la programmation *au travers* de Python et se réservent bien d'enseigner comment *structurer et déployer* une application ou une bibliothèque écrite en Python. 24 | 25 | Il existe en ligne une pléthore de tutoriels[^4] [^5] [^6] sur comment structurer son projet et comment le déployer. Cependant l'éco-système de Python a tellement évolué depuis 2018 sur tout ce qui est relatif au *packaging* qu'il a rendu bon nombre de ces tutoriels caduques[^7] [^8]. 26 | 27 | Cet article a donc pour objectif de guider les lecteurs vers une structure de projet adaptée aux usages de 2023. Parmi les usages communs, nous avons retenu pour cet article : comment [tester son application] et comment [partager son programme avec autrui]. 28 | 29 | Cet article s'appuie sur les recommandations de la [PyPA] (*Python Packaging Autority*) qui est en charge de *spécifier*, *maintenir* et *documenter* toute une série de pratiques et d'outils relatifs au partage des applications et des bibliothèques développées en Python. À défaut de recommandations de la PyPA, nous utiliserons les pratiques et les outils les plus répandus. Tous les partis pris par ce document sont discutés dans le [dernier chapitre]. 30 | 31 | [tester son application]: #ajout-de-tests-automatiques 32 | [partager son programme avec autrui]: #création-dune-archive-installable 33 | [PyPA]: https://www.pypa.io/en/latest/ 34 | [dernier chapitre]: #choix-des-technologies 35 | 36 | ## Mise en place du squelette du projet 37 | 38 | *Vous avez toujours besoin de cette étape.* 39 | 40 | Commencez par créer un dossier nommé selon le nom de votre projet, dans cet exemple nous allons nommer ce dossier `tartempion`. Déplacez vous ensuite à l'intérieur de ce dossier et créez les fichiers suivants: 41 | 42 | * `README.md` 43 | * `LICENSE.txt` 44 | 45 | Le fichier `README.md` est le fichier qui décrit votre logiciel, comment l'installer, comment l'utiliser et pointe des ressources pour aller plus loin. À minima, ce fichier reprend le nom du projet ainsi qu'une courte description: 46 | 47 | tartempion 48 | ========== 49 | 50 | Un projet vide juste pour montrer comment structurer un projet en python. 51 | 52 | Le fichier `LICENSE.txt` est un fichier à vocation légale, il explique qui a le droit de modifier et/ou d'utiliser votre projet. Par défaut, en l'absence d'un tel fichier, vous êtes le seul détenteur du logiciel, seul vous avez le droit de le modifier, de l'utiliser et de le commercialiser. Pour qu'il n'y ait aucune ambiguïté, vous pouvez rappeler votre droit d'auteur en écrivant ceci dans le fichier: 53 | 54 | Copyright (c) votre nom et prénom 55 | 56 | Si vous souhaitez rendre votre projet open-source, avec plus ou moins de conditions, vous pouvez copier/coller le texte d'une licence existante. Vous trouverez une liste de licences communes sur le site . 57 | 58 | Votre projet devrait ressembler à ceci : 59 | 60 | tartempion/ 61 | LICENSE.txt 62 | README.md 63 | 64 | En étant dans le dossier de votre projet, donc en étant à l'intérieur du dossier `tartempion`, créez un second dossier du même nom, c'est à dire créez à nouveau un dossier `tartempion`. Créez ensuite dans ce nouveau dossier, les deux fichiers suivants : 65 | 66 | * `__init__.py` 67 | * `__main__.py` 68 | 69 | Ce second dossier contiendra votre programme, c'est-à-dire tous vos fichiers python (à l'exception des tests automatiques). Pour différencier les deux dossiers, nous appellerons le premier dossier (celui qui contient le fichier `README.md`) la *racine du projet* (*project root*). 70 | 71 | Le fichier `__init__.py` fait en sorte que le dossier soit reconnu comme un *package* par python, c'est-à-dire comme un dossier contenant des fichiers python (appellés *modules*). Il sera automatiquement exécuté à chaque fois que le projet sera importé et/ou exécuté. 72 | 73 | Le fichier `__main__.py` est le point d'entrée de votre application, il contient le code qui sera exécuté (après `__init__.py`) lorsque vous lancerez votre programme. Ce fichier n'est pas nécessaire si vous comptez développer une bibliothèque de fonctions et non pas un programme exécutable. 74 | 75 | Au terme de cette étape, votre projet devrait ressembler à ceci: 76 | 77 | tartempion/ 78 | tartempion/ 79 | __init__.py 80 | __main__.py 81 | LICENSE.txt 82 | README.md 83 | 84 | Si vous utilisez un logiciel de contrôle de version (comme *git*), vous pouvez d'ores et déjà initialiser votre logiciel avec les fichiers existants. Les utilisateurs de *git* peuvent donc taper la commande `git init` en étant à l'intérieur de la racine du projet, c'est-à-dire à l'intérieur du premier dossier `tartempion`. 85 | 86 | ### Lancement du projet 87 | 88 | Pour vérifier que cette structure minimale fonctionne correctement, vous pouvez écrire ceci dans le fichier `__init__.py` : 89 | 90 | ```py 91 | print("Dans __init__.py") 92 | print(" file:", __file__) 93 | print(" name:", __name__) 94 | print(" package:", __package__) 95 | ``` 96 | 97 | et ceci dans le fichier `__main__.py` : 98 | 99 | ```py 100 | print("Dans __main__.py") 101 | print(" file:", __file__) 102 | print(" name:", __name__) 103 | print(" package:", __package__) 104 | ``` 105 | 106 | Ouvrez alors un terminal à la *racine du projet* (le premier dossier `tartempion`) et exécutez la commande suivante pour constater qu'il est possible d'*importer* votre projet comme une *bibliothèque de fonctions* (sur windows, remplacez `python3` par `py`) : 107 | 108 | python3 -c "import tartempion" 109 | 110 | La commande va ouvrir un interpréteur python, exécuter l'instruction `import tartempion` et quitter. Le résultat de cette commande devrait être: 111 | 112 | Dans __init__.py 113 | file: __init__.py 114 | name: tartempion 115 | package: tartempion 116 | 117 | Exécutez ensuite la commande suivante pour constater qu'il est possible d'*exécuter* votre projet comme un *programme* (sur windows, remplacez `python3` par `py`) : 118 | 119 | python3 -m tartempion 120 | 121 | La commande va charger le module dans un interpréteur python et l'exécuter. Le résultat de cette commande devrait être : 122 | 123 | Dans __init__.py 124 | file: __init__.py 125 | name: tartempion 126 | package: tartempion 127 | Dans __main__.py 128 | file: __main__.py 129 | name: __main__ 130 | package: tartempion 131 | 132 | Une fois que vous avez vérifié que votre programme fonctionne bien, vous pouvez vider les deux fichiers, c'est-à-dire retirer tous les `print`. Ne supprimez pas les fichiers. 133 | 134 | ## Adaptation d'un programme existant 135 | 136 | *Vous avez besoin de cette étape si vous avez déjà commencé à faire un programme mais qu'il n'était pas correctement structuré.* 137 | 138 | Vous avez deux dossiers, un dossier mal structuré qui contient votre ancien programme et un dossier bien structuré (mais presque vide) que vous avez créé en suivant les instructions précédentes. Nous appellerons ici ces dossiers `old_tartempion` et `new_tartempion`. 139 | 140 | ### Structure de l'ancien projet 141 | 142 | Votre dossier `old_tartempion` doit certainement avoir une structure similaire à la structure suivante : 143 | 144 | old_tartempion/ 145 | tartempion.py 146 | tartes/ 147 | tarte.py 148 | tarte_cerise.py 149 | tarte_pomme.py 150 | 151 | Du point de vue des imports, le contenu de vos fichiers doit certainement ressembler à quelque chose dans le genre de : 152 | 153 | ```py 154 | # tartempion.py 155 | from tartes.tarte_cerise import TarteCerise 156 | 157 | tarte = TarteCerise() 158 | print(tarte) 159 | ``` 160 | 161 | ```py 162 | # tarte.py 163 | class Tarte: 164 | ... 165 | ``` 166 | 167 | ```py 168 | # tarte_cerise.py 169 | from . import tarte 170 | 171 | class TarteCerise(tarte.Tarte): 172 | ... 173 | ``` 174 | 175 | ```py 176 | # tarte_pomme.py 177 | from .tarte import Tarte 178 | 179 | class TartePomme(Tarte): 180 | ... 181 | ``` 182 | 183 | Vous devez certainement lancer votre programme soit en faisant `python3 tartempion.py` depuis votre terminal soit en cliquant sur le bouton "run" tout en étant sur le fichier `tartempion.py` dans votre IDE. 184 | 185 | Si la structure de votre projet actuel ne ressemble pas à celle décrite ici ou bien si vous n'êtes pas sûr alors venez nous demander de l'aide sur Discord. Faites bien attention à nous expliquer comment vos fichiers sont structurés, comment vous faites vos imports et surtout comment vous lancez votre programme. 186 | 187 | ### Structure du nouveau projet 188 | 189 | Après adaptation, la structure de votre projet devrait plutôt ressembler à ceci: 190 | 191 | new_tartempion/ 192 | tartempion/ 193 | __init__.py 194 | __main__.py 195 | tartes/ 196 | __init__.py 197 | tarte.py 198 | tarte_cerise.py 199 | tarte_pomme.py 200 | LICENSE.txt 201 | README.md 202 | 203 | Le fichier `tartempions.py` est devenu le fichier `__main__.py` qui se trouve à l'intérieur du *package* `tartempion`. Le *package* `tartes` a été déplacé à l'intérieur du *package* `tartempion`. Deux nouveaux fichiers (vides) `__init__.py` ont été rajoutés, l'un dans le dossier `tartempion` et l'autre dans le dossier `tartes`. 204 | 205 | Niveau imports dans vos fichiers, vous devez corriger toutes vos lignes où vous avez importé des modules de votre projet à l'aide d'un import absolu. Un import est absolu lorsqu'il ne commence pas par un point. 206 | 207 | Exemple d'imports absolus (ne commencent pas par un point) : 208 | 209 | ```py 210 | import tartes 211 | from tartes import tarte_cerise 212 | import tartes.tarte_cerise 213 | from tartes.tarte_cerise import TarteCerise 214 | ``` 215 | 216 | Exemple d'imports relatifs (commencent par un point) : 217 | 218 | ```py 219 | from . import tarte 220 | from .tarte import TarteCerise 221 | ``` 222 | 223 | Vous pouvez en apprendre plus sur les différences entre imports absolus et imports relatifs dans [cet autre article](relatif-vs-absolu-demystifions-les-imports-python) 224 | 225 | En reprenant la structure de `old_tartempion` en exemple ici, le seul import que vous devez corriger est celui qui provient du fichier `tartempion.py` (renommé `__main__.py` dans la nouvelle structure). Dans votre propre projet, vous devrez certainement en corriger plus. 226 | 227 | ```py 228 | # tartempion.py 229 | from .tartes.tarte_cerise import TarteCerise 230 | # ^ ya un point en plus ici 231 | 232 | tarte = TarteCerise() 233 | print(tarte) 234 | ``` 235 | 236 | Pour lancer votre projet depuis votre terminal, vous devez maintenant faire `python3 -m tartempion` (sur Windows, remplacez `python3` par `py`) en étant dans le dossier `new_tartempion`. 237 | 238 | Si vous aviez l'habitude d'exécuter votre code depuis votre IDE, les étapes pour le configurer pour qu'il utilise aussi `-m` sont expliquées plus bas. 239 | 240 | ## Isolation dans un environnement virtuel 241 | 242 | *Vous avez besoin de cette étape si vous voulez installer avec pip des applications, des outils ou bibliothèques supplémentaires comme `pylint`, `requests` ou `pygame`.* 243 | 244 | ### Introduction 245 | 246 | Ouvrez un terminal et entrez la commande `python3 -m pip list` (sur windows, remplacez `python3` par `py`). Le résultat de cette commande devrait lister toutes les bibliothèques supplémentaires qui sont installées pour Python sur votre machine. 247 | 248 | Si vous êtes sur Linux ou MacOS, cette liste devrait déjà être bien remplie comme les deux systèmes utilisent Python en interne pour toute une série de routines systèmes. Si vous êtes sur Windows, cette liste devrait être vide ou presque. 249 | 250 | Vous pouvez répéter la commande avec l'option `--user` pour lister les bibliothèques qui sont installées pour l'utilisateur actuel, la liste devrait être (beaucoup) plus courte. 251 | 252 | Les paquets qui sont installés au niveau du système sont les paquets que le système gère lui-même, par exemple avec `apt` sur Ubuntu, vous ne devriez jamais y toucher vous-même, ni installer de nouvelles bibliothèques, ni en supprimer. Les paquets installées pour l'utilisateur actuel sont des outils dont vous avez besoin dans votre vie de tous les jours, par exemple pour faire du scripting. 253 | 254 | Lorsque vous développez un nouveau projet, il est important d'isoler les bibliothèques utilisées pour ce projet du reste des bibliothèques de votre système. Vous ne voudriez pas écraser une bibliothèque utilisée par votre système par une version trop récente/ancienne dans votre projet et ainsi compromettre le bon fonctionnement de votre système. 255 | 256 | En Python, pour isoler les bibliothèques utilisées d'un endroit à autre, nous utilisons des environnements virtuels. 257 | 258 | ### Créer un environnement virtuel 259 | 260 | Ouvrez un terminal à la racine de votre projet et entrez la commande `python3 -m venv .venv` (remplacez `python3` par `py` sur Windows). La commande va créer un nouvel environnement virtuel dans le dossier `.venv` à la racine de votre projet. 261 | 262 | Une fois l'environnement créé, vous pouvez vérifier qu'il fonctionne bien en ouvrant l'interpréteur Python lié à ce nouvel environnement. Sur MacOS/Linux, vous pouvez exécuter la commande `./.venv/bin/python` dans votre terminal, sur Windows vous pouvez faire `.\.venv\Scripts\python`, dans les deux cas l'exécution de cette commande devrait ouvrir l'interpréteur que vous pouvez fermer en appelant la fonction `exit()`. 263 | 264 | Vous pouvez aussi lister les modules supplémentaires qui sont installés dans cet environnement virtuel en faisant `./.venv/bin/pip list` (`.\.venv\Scripts\` sur Windows), vous devriez constater que seul `pip` (et peut-être aussi `setuptools`) est installé, malgré la possible présence de nombreux autres paquets installés sur votre système. 265 | 266 | ### Utiliser un environnement virtuel 267 | 268 | Pour utiliser votre environnement virtuel, vous n'avez donc qu'à ouvrir un terminal à la racine de votre projet et utiliser le python et le pip qui se trouvent dans le dossier `.venv/bin/` (`.\.venv\Scripts\pip list` sur Windows). 269 | 270 | Pour installer une bibliothèque, vous pouvez alors juste faire `./.venv/bin/pip install` suivi du nom des bibliothèques que vous voulez installer. Dans cet exemple, nous allons installer la bibliothèque `requests` qui permet de faire des requêtes sur internet en HTTP et vérifier qu'elle fonctionne. Vous faites `./.venv/bin/pip install requests` et vous ouvrez ensuite un interpréteur python avec `./.venv/bin/python` dans lequel vous pouvez entrer les instructions suivantes: 271 | 272 | Python 3.10.6 (main, Mar 10 2023, 10:55:28) [GCC 11.3.0] on linux 273 | Type "help", "copyright", "credits" or "license" for more information. 274 | >>> import requests 275 | >>> print(requests.__version__) 276 | 2.28.2 277 | >>> exit() 278 | 279 | ### Configurer son terminal pour ne pas devoir tout le temps écrire `./.venv/bin` 280 | 281 | Vous constatez donc qu'il est nécessaire de toujours donner le chemin vers le python et le pip qui se trouvent dans votre environnement virtuel. Lorsque vous comptez travailler toute la journée sur un même projet, devoir répéter ces instructions peut être fastidieux. Les développeurs de Python ont prévu le coup, il est possible de reconfigurer son terminal pour remplacer `python` et `pip` de sorte à utiliser ceux de l'environnement virtuel plutôt que ceux du système. Précision importante, ce changement n'aura d'impact qu'à l'intérieur du terminal actuellement ouvert, le reste du système continuera bien d'utiliser le `python` et le `pip` installés au niveau du système (ceci inclut donc aussi les IDE). 282 | 283 | Pour reconfigurer son terminal, ou comme nous disons dans le jargon *activer son environnement virtuel*, vous pouvez *sourcer* le script `activate` qui se trouve à côté de `python` et `pip` dans le dossier `bin` de votre environnement virtuel: `source ./.venv/bin/activate` (sur Windows, pas besoin de `source`, appelez `.\.venv\Scripts\activate` directement à la place). 284 | 285 | Pour vérifier que la reconfiguration (l'activation) s'est bien déroulée, vous pouvez exécuter la commande `pip --version` (sans donner le chemin complet) et vérifier que le `pip` utilisé est celui de votre environnement virtuel. 286 | 287 | $ pip --version 288 | pip 22.0.2 from /tmp/.venv/lib/python3.10/site-packages/pip (python 3.10) 289 | 290 | Pour faire la reconfiguration inverse, réutiliser les `python` et `pip` du système, ou comme nous disons dans le jargon *désactiver son environnement virtuel*, vous pouvez simplement exécuter la commande `deactivate`. 291 | 292 | [venv]: https://docs.python.org/3/library/venv.html 293 | 294 | ## Ajout de tests automatiques 295 | 296 | *Vous avez besoin de cette étape lorsque vous comptez vérifier le fonctionnement de votre application à l'aide de tests automatiques.* 297 | 298 | Ouvrez un terminal à la racine de votre projet et créez un nouveau dossier `tests`, dans ce dossier créez deux fichiers: `__init__.py` et `test_tartempion.py`. La structure de votre projet devrait donc ressembler à ceci : 299 | 300 | tartempion/ 301 | tartempion/ 302 | __init__.py 303 | __main__.py 304 | tests/ 305 | __init__.py 306 | test_tartempion.py 307 | LICENSE.txt 308 | README.md 309 | 310 | Vous pouvez laisser le fichier `__init__.py` vide, il sert uniquement à indiquer que le dossier est un *package* pour python. À l'intérieur du fichier `test_tartempion.py` vous pouvez écrire ceci : 311 | 312 | ```py 313 | import unittest 314 | import tartempion 315 | 316 | class TartempionTest(unittest.TestCase): 317 | def test_tartempion_loaded(self): 318 | self.assertTrue(tartempion) 319 | ``` 320 | 321 | Une fois le test écrit, assurez vous de vous positionner à la racine de votre projet dans votre terminal. Vous pouvez ensuite lancer les tests avec la commande `python3 -m unittest` (utilisez `py -m unittest` sur Windows, pensez à passer par votre environnement virtuel si vous en avez un). Vous devriez avoir un résultat comme suit: 322 | 323 | . 324 | ---------------------------------------------------------------------- 325 | Ran 1 test in 0.000s 326 | 327 | OK 328 | 329 | Chaque `.` sur la première ligne représente un test qui est passé, les tests échoués apparaissent avec un `!`. Vous pouvez aussi utiliser l'option `-v` si vous voulez plutôt énumérer le nom de tests un à un : 330 | 331 | test_tartempion (tests.test_tartempion.TestTartempion) ... ok 332 | 333 | ---------------------------------------------------------------------- 334 | Ran 1 test in 0.000s 335 | 336 | OK 337 | 338 | Le fonctionnement général d'[unittest] est qu'il chargera de lui-même tous les fichiers à l'intérieur du dossier `tests` qui commencent par `test_`. À l'intérieur de ces modules python, il exécutera de lui-même toutes les méthodes qui commencent par `test_` dans les classes qui héritent de `TestCase`. 339 | 340 | Vous pouvez ajouter autant de modules de tests et de classes que vous voulez. Il peut être judicieux de définir les tests dans des modules différents en fonction de la partie du code qu'ils testent. 341 | 342 | Il n'y a pas beaucoup de documentation disponible dans la sphère francophone sur le testing en Python. Si vous avez des questions à ce propos, n'hésitez pas à poser vos questions sur Discord. 343 | 344 | [unittest]: https://docs.python.org/3/library/unittest.html 345 | 346 | ## Création d'une archive installable 347 | 348 | *Vous avez besoin de cette étape si vous comptez partager votre programme à une tierce personne, si vous comptez héberger votre programme sur un serveur, ou bien si vous comptez rendre votre bibliothèque installable avec pip.* 349 | 350 | Assurez-vous d'avoir toutes les dépendances suivantes installées sur votre système: [build], [setuptools], [wheel]. Sur les systèmes Linux dérivés de Debian/Ubuntu, vous pouvez utiliser votre package-manager: `apt install python3-build python3-setuptools python3-wheel`. Pour Windows, vous pouvez directement utiliser `pip` et installer ces bibliothèques au niveau de votre utilisateur (en dehors d'un environnement virtuel donc): `py -m pip install --user --upgrade build pip setuptools wheel` 351 | 352 | Assurez-vous ensuite que ces libs sont également installées au niveau de votre environnement virtuel (si vous en utilisez un): `.venv/bin/pip install --upgrade build pip setuptools wheel` (`.venv\Scripts\pip` sur Windows, ou bien activez votre environnement virtuel). 353 | 354 | ### Déclaration du projet 355 | 356 | Une fois les dépendances installées, vous pouvez définir les méta-données relatives à votre projet dans un nouveau fichier `pyproject.toml` que vous créez à la racine de votre projet. Nous vous proposons le fichier suivant, à titre d'exemple : 357 | 358 | ```toml 359 | [project] 360 | name = "tartempion_NaN" 361 | version = "0.0.1" 362 | description = "Une courte description sur une ligne" 363 | readme = "README.md" 364 | license = { file = "LICENSE.txt" } 365 | authors = [ 366 | { name = "Bob", email = "bob@example.com" }, 367 | ] 368 | 369 | classifiers = [ 370 | "Private :: Do Not Upload", 371 | ] 372 | 373 | # Exemple de dépendances 374 | dependencies = [ 375 | "pygame", 376 | "requests", 377 | ] 378 | 379 | [build-system] 380 | requires = ["setuptools>=61.0", "wheel"] 381 | build-backend = "setuptools.build_meta" 382 | ``` 383 | 384 | Le fichier utilise un format relativement nouveau, le [TOML], il s'agit d'un format de fichier adapté aux fichiers de configuration. 385 | 386 | La première partie du fichier, `[project]`, défini l'ensemble des méta-données relatives à votre projet. Il existe en tout plus d'une vingtaine de méta-données différentes, nous avons décidé de ne garder que les plus essentielles d'entre elles ici. La liste complète est définie dans la PEP [621]. Pensez à changer les valeurs pour le nom, la description et les auteurs. Pensez aussi à déclarer vos dépendances, si vous en avez, pour qu'elles soient automatiquement installées avec votre projet. 387 | 388 | **Important**, le champ `name` dans ce fichier n'est pas obligatoirement le même que le nom de votre dossier. Il est d'ailleurs conseillé d'ajouter votre pseudo ou un autre élément unique à la fin pour vous assurer qu'il sera unique sur internet. Dans cet exemple nous avons ajouté `_NaN` pour "Not a Name", ce blog. 389 | 390 | Contrairement à une croyance populaire, reprise par beaucoup de tutoriels erronés en ligne, les dépendances sont bien à inscrire dans le champ `dependencies` du `pyproject.toml` et non pas dans un fichier `requirements.txt`. Vous pouvez lire la discussion [install_requires vs requirements files] dans la documentation officielle pour en apprendre plus. 391 | 392 | La liste associée au champ `classfiers` permet de catégoriser votre logiciel en fonction de l'audience visée, le sujet générale de votre logiciel et d'autres informations. Ce champ n'est pas essentiel. Si vous comptez héberger votre logiciel sur , vous devrez retirer la ligne `"Private :: Do Not Upload",`. 393 | 394 | La seconde partie du fichier, `[build-system]`, décrit quels outils seront utilisés pour créer les archives pour votre projet. Cette partie est obligatoire mais vous pouvez vous contenter de garder celle de l'exemple. 395 | 396 | ### Construction de l'archive 397 | 398 | Une fois le fichier `pyproject.toml` complété, vous pouvez utiliser [build] pour créer une archive de votre fichier. Placez-vous à la racine de votre projet et lancer la commande `.venv/bin/pyproject-build` (`.venv\Scripts\pyproject-build` sur Windows). La commande est très verbeuse, parmi toutes les lignes affichées vous devriez voir celles-ci: 399 | 400 | * Creating venv isolated environment... 401 | * Installing packages in isolated environment... (setuptools) 402 | * Getting build dependencies for sdist... 403 | * Building sdist... 404 | * Building wheel from sdist 405 | * Creating venv isolated environment... 406 | * Installing packages in isolated environment... (setuptools) 407 | * Getting build dependencies for wheel... 408 | * Installing packages in isolated environment... (wheel) 409 | * Building wheel... 410 | Successfully built tartempion-0.0.1.tar.gz and tartempion-0.0.1-py3-none-any.whl 411 | 412 | Le programme aura créé plusieurs fichiers et dossiers à la racine de votre projet, parmi ces dossiers un dossier `dist/` qui contient les deux archives créées : `tartempion-0.0.1.tar.gz` (`.zip` sur Windows) et `tartempion-0.0.1-py3-none-any.whl`. 413 | 414 | La première archive au format `.tar.gz` ou `.zip` est une *distribution source*, elle est principalement à destination des développeurs. La seconde archive au format `.whl` est une *distribution wheel* (sur roulette), elle est principalement à destination des utilisateurs. 415 | 416 | Chacune de ces deux archives est installable via [pip]. Vous pouvez donc les partagez autours de vous (préférez partager la wheel) et vos utilisateurs pourront l'installer sur leur propre machine. 417 | 418 | [TOML]: https://toml.io/fr/ 419 | [621]: https://packaging.python.org/en/latest/specifications/declaring-project-metadata/#declaring-project-metadata 420 | [install_requires vs requirements files]: https://packaging.python.org/en/latest/discussions/install-requires-vs-requirements/ 421 | [build]: https://pypa-build.readthedocs.io/en/stable/index.html 422 | [setuptools]: https://setuptools.pypa.io/en/latest/ 423 | [wheel]: https://wheel.readthedocs.io/en/latest/ 424 | [pip]: https://pip.pypa.io/en/stable/ 425 | 426 | ## Configuration de VSCode 427 | 428 | *Vous avez besoin de cette étape si vous voulez que VSCode arrête de lever des erreurs pour des problèmes d'import et/ou pouvoir lancer votre projet directement depuis la console de VSCode.* 429 | 430 | Installez l'extension Python de Microsoft pour VSCode, cette extension ajoute le nécessaire pour exécuter et debugger du code python directement depuis VSCode. 431 | 432 | Ouvrez une nouvelle fenêtre dans VSCode (File > New Window), ouvrez le dossier qui contient votre projet. Une fois votre projet ouvert, assurez-vous que votre environnement virtuel a été chargé en regardant le coin inférieur droit, vérifiez de bien lire `('.venv': venv)` précédé de la version de Python avec laquelle vous aviez créé votre projet. 433 | 434 | Pour lancer votre module directement depuis VSCode et profiter du debugger intégrer, ajoutez une nouvelle configuration pour le lancement de votre programme (Run > Add Configuration). VSCode vous proposera une configuration parmi d'autres, sélectionnez "**Module** Debug a Python module by invoking it '-m'", il devrait être le second dans la liste. VSCode vous demande ensuite de donner le nom de votre module, nous entrons `tartempion` dans notre cas, vous inscrivez le nom de votre module. Une fois la nouvelle configuration ajoutée, il vous reste à lancer le debugger avec F5 (ou bien Run > Start Debugging). 435 | 436 | ## Configuration de PyCharm 437 | 438 | *Vous avez besoin de cette étape si vous voulez que PyCharm arrête de lever des erreurs pour des problèmes d'import et/ou pouvoir lancer votre projet directement depuis la console de PyCharm.* 439 | 440 | 441 | 442 | ## Choix des technologies 443 | 444 | Tout au long de ce document, il n'a jamais été présenté qu'une seule approche, un seul choix. Il s'agit d'un choix assumé par la nécessité de dissimuler aux débutants toute la complexité réelle qui se cache derrière le *packaging* en Python. Dans cette section, nous présentons les alternatives connues pour chacune de nos décisions et nous justifions nos choix. 445 | 446 | ### Squelette du projet 447 | 448 | Il existe deux manières de structurer un projet python[^9], ce document a présenté la structure dite "à plat" (*flat-layout*) mais certains projets préfèrent la structure dite "imbriquée" (*src-layout* ou *nested-layout*). 449 | 450 | Structure à plat: 451 | 452 | tartempion/ 453 | tartempion/ 454 | __init__.py 455 | __main__.py 456 | tests/ 457 | test_tartempion.py 458 | README.md 459 | 460 | Structure imbriquée: 461 | 462 | tartempion/ 463 | src/ 464 | tartempion/ 465 | __init__.py 466 | __main__.py 467 | tests/ 468 | test_tartempion.py 469 | README.md 470 | 471 | Nous avons préféré présenter la structure à plat car elle est plus simple. La structure imbriquée n'est utile que lorsqu'un même projet se décline en plusieurs modules dépendants les uns des autres qui ne doivent pas partager un même *namespace*. Plutôt rare comme cas de figure. 472 | 473 | Un autre avantage de la structure à plat est de pouvoir lancer le programme et les tests automatiques depuis la racine du projet sans installation. Avec la structure imbriquée, il est obligatoire de recourir à un paquet installé en mode éditable dans l'environnement virtuel courant pour lancer et tester l'application. Le revers est qu'en cas d'erreur dans la construction d'une archive, dans le cas où les modules python n'ont pas été correctement inclus, ces modules *pourraient* apparaître comme accessibles (parce que se trouvant dans le working-directory) sans réellement l'être (parce que non inclus dans le package)[^10]. 474 | 475 | ### Format du README 476 | 477 | Il existe plusieurs formats de fichiers assez répandus pour la rédaction des fichiers README. Les plus communs sont le format texte, le format Markdown et le format reStructuredText. Sur , le format utilisé par défaut est reStructuredText, cependant lorsqu'un projet est créé depuis [Github], le fichier README sera au format Markdown. 478 | 479 | Il y a une grosse tendance à favoriser le Markdown pour l'écriture des fichiers README et cette tendance n'est pas spécifique à Python. Puisque le Markdown est également supporté sur (via la directive `long_description_content_type=text/markdown`) et que le Markdown est généralement considéré comme plus simple à utiliser que le reStructuredText, nous avons choisi le format Markdown pour le README. 480 | 481 | [Github]: https://github.com 482 | 483 | ### Environnement Virtuel 484 | 485 | Il existe plusieurs logiciels capables de créer et de gérer des environnements virtuels parmi lesquels [venv], [virtualenv] et [pyenv-virtualenv]. Il existe aussi plusieurs logiciels de gestion de projet python "tout-en-un" qui intègrent un environnement virtuel par défaut comme [hatch], [pdm] ou [poetry]. 486 | 487 | Parmi tous ces outil, [venv] est disponible de base et était recommandé par la PyPA sur sa page des outils recommandés[^11] au moment de la rédaction de ce document. Le nom du dossier: `.venv` vient d'ailleurs de sa page de documentation: 488 | 489 | > un nom habituel pour ce dossier cible est `.venv` 490 | 491 | [venv]: https://docs.python.org/3/library/venv.html 492 | [virtualenv]: https://virtualenv.pypa.io/en/stable/index.html 493 | [pyenv-virtualenv]: https://github.com/pyenv/pyenv-virtualenv 494 | [hatch]: https://hatch.pypa.io/latest/ 495 | [pdm]: https://pdm.fming.dev/ 496 | [poetry]: https://python-poetry.org/ 497 | [setuptools]: https://setuptools.readthedocs.io/en/latest/ 498 | 499 | ### Tests automatiques 500 | 501 | Il existe plusieurs bibliothèques pour écrire et lancer des tests unitaires sur Python. On peut citer [unittest], [pytest], [nose], [ward] et [hammett]. Parmi toutes ces bibliothèques, les deux plus répandues sont [unittest] et [pytest], ensemble elles dominent l'écosystème Python. Nous avons décidé de présenter [unittest] dans ce tutoriel car elle reste (à une très courte majorité) la bibliothèque la plus utilisée et parce qu'elle est disponible directement dans la bibliothèque standard. 502 | 503 | [unittest]: https://docs.python.org/3/library/unittest.html 504 | [pytest]: https://docs.pytest.org 505 | [nose]: https://nose.readthedocs.io/en/latest/ 506 | [ward]: https://ward.readthedocs.io/en/latest/ 507 | [hammett]: https://github.com/boxed/hammett 508 | 509 | ### Distribution 510 | 511 | Depuis 2018, de nombreux systèmes ont vu le jour avec comme objectif de simplifier la gestion des projets en Python. On peut citer [poetry] avec son système de gestion de dépendances novateur, [pdm] et sa volonté de fournir un outil semblable à npm, [flit] et sa volonté de fournir un logiciel *simple* et pour finir [hatch] qui se veut être un outil tout-en-un et qui est de plus en plus répandu. 512 | 513 | Il a existé une époque entre 2018 et 2021 pendant laquelle les mises à jour de [setuptools] stagnaient derrière ces nouveaux outils en terme d'adoption des standards (PEP-[517]/[518]/[621]) et pendant laquelle il nous aurait été difficile de suivre la PyPA dans ses recommandations. Heureusement, la [version 61] de [setuptools] (avril 2021) a ajouté le support pour la PEP-[621] (*Storing project metadata in pyproject.toml*) et il est depuis lors raisonnable d'utiliser setuptools à nouveau. 514 | 515 | Nous avons utilisé setuptools dans cet article car il est compatible avec les PEP-[517]/[621], car il semble toujours être le seul outil capable de gérer des extensions écrites en C et car il est toujours le seul outil recommandé[^11] par la PyPA. 516 | 517 | [flit]: https://flit.readthedocs.io/en/latest/ 518 | [hatch]: https://hatch.pypa.io/latest/ 519 | [pdm]: https://pdm.fming.dev/ 520 | [poetry]: https://python-poetry.org/ 521 | [setuptools]: https://setuptools.readthedocs.io/en/latest/ 522 | [version 61]: https://setuptools.pypa.io/en/latest/history.html#v61-0-0 523 | 524 | [517]: https://peps.python.org/pep-0517/ 525 | [518]:https://peps.python.org/pep-0518/ 526 | [621]:https://packaging.python.org/en/latest/specifications/declaring-project-metadata/#declaring-project-metadata 527 | 528 | ### IDE 529 | 530 | Parmi tous les éditeurs de texte et IDE disponibles pour Python, nous avons décidé de n'en documenter que deux, [VSCode] et [PyCharm]. Cette décision repose sur l'expérience des auteurs quant à la fréquence des questions relatives à l'utilisation combinées des environnements virtuels et des éditeurs de texte/IDE, expérience qui a été confirmée par plusieurs sondages. 531 | 532 | Les débutants qui utilisent [VSCode] et [PyCharm] utilisent souvent le bouton "run" de leur IDE pour exécuter leur programme tandis que les débutants qui utilisent d'autres éditeurs (comme [Sublime Text]) semblent préférer passer par un terminal. Ceci s'ajoute au fait que les utilisateurs débutants de VSCode et PyCharm utilisent souvent un linter intégré à leur IDE contrairement aux utilisateurs débutants qui utilisent d'autres éditeurs. 533 | 534 | Ces deux aspects, le bouton "run" et le linter, font qu'il est crucial de correctement configurer ces IDE pour qu'ils utilisent le bon environnement virtuel. Ne pas configurer d'environnement virtuel est une source commune d'erreur chez les débutants qui se plaignent soit qu'ils n'arrivent pas à installer de bibliothèques soit que leur IDE ne reconnaît pas la bibliothèque qu'ils viennent d'installer. Les deux affirmations sont en fait la conséquence du même problème, la bibliothèque n'a pas été installée dans le même environnement virtuel que celui utilisé dans l'IDE. 535 | 536 | Il est rare de voir des questions de ce genre pour d'autres IDE, nous n'avons donc pas cherché à les documenter dans cet article. Nous ne les recommandons d'ailleurs pas plus que d'autres éditeur de texte / IDE. 537 | 538 | [VSCode]: https://code.visualstudio.com/ 539 | [PyCharm]: https://www.jetbrains.com/pycharm/ 540 | [Sublime Text]: https://www.sublimetext.com/ 541 | 542 | ## Références 543 | 544 | [^1]: entwanne “Un Zeste de Python.” dans : Zeste de Savoir [En ligne]. [s.l.] : Zeste de Savoir, 2022. Disponible sur : (consulté le 19 Mars 2023) 545 | [^2]: Swinnen G. “Apprendre à programmer avec Python 3: Avec 60 pages d'exercices corrigés!” 3e ed. Paris, France : Eyrolles, 2012. 546 | [^3]: Champagne J. “Python - cours ✔.” dans : YouTube [En ligne]. [s.l.] : YouTube, 2017. Disponible sur : (consulté le 19 Mars 2023) 547 | [^4]: Reitz K., Schlusser T. “The hitchhiker's guide to python: Best practices for development.” Sebastopol, CA : O'Reilly Media, Inc., 2016. 548 | [^5]: Stratis K. “Python application layouts: A reference.” dans : Real Python [En ligne]. [s.l.] : Real Python, 2023. Disponible sur : (consulté le 19 Mars 2023) 549 | [^6]: Le J. P. “Guide to python project structure and packaging.” dans : Medium [En ligne]. [s.l.] : MLearning.ai, 2023. Disponible sur : (consulté le 19 Mars 2023) 550 | [^7]: Setuptools “Setup.py discouraged” dans : Quickstart - setuptools 67.6.0 documentation [En ligne]. [s.l.] : [s.n.], [s.d.]. Disponible sur : (consulté le 19 Mars 2023) 551 | [^8]: Ganssle P. “Why you shouldn't invoke setup.py directly.” dans : Paul Ganssle Blog [En ligne]. [s.l.] : [s.n.], 2021. Disponible sur : (consulté le 19 Mars 2023) 552 | [^9]: PyPA. “src layout vs flat layout.” dans : Python Packaging User Guide [En ligne]. [s.l.] : Python Packaging Autority, [s.d.]. Disponible sur : (consulté le 19 Mars 2023) 553 | [^10]: Cristian Mărieș I. “Packaging a python library - thoughts on packaging python libraries.” dans : ionel's codelog [En ligne]. [s.l.] : [s.n.], 2019. Disponible sur : (consulté le 19 Mars 2023) 554 | [^11]: PyPA. “Tool recommendations” dans : Python Packaging User Guide [En ligne]. [s.l.] : Python Packaging Autority, [s.d.]. Disponible sur : (consulté le 19 Mars 2023) 555 | --------------------------------------------------------------------------------