One place for hosting & domains

      Comment utiliser le module des collections dans Python 3


      L’auteur a choisi le COVID-19 Relief Fund pour recevoir un don dans le cadre du programme Write for DOnations.

      Introduction

      Python 3 possède un certain nombre de structures de données intégrées, notamment des tuples, des dictionnaires et des listes. Les structures de données nous fournissent un moyen d’organiser et de stocker les données. Le module de collections nous aide à remplir et à manipuler les structures de données de manière efficace.

      Dans ce tutoriel, nous allons passer en revue trois classes des collections pour vous aider à travailler avec des tuples, des dictionnaires et des listes. Nous utiliserons namedtuples pour créer des tuples avec des champs nommés, defaultdict pour regrouper de manière concise les informations dans les dictionnaires, et deque pour ajouter efficacement des éléments de chaque côté d’un objet de type liste. 

      Pour ce tutoriel, nous travaillerons principalement avec un inventaire de poissons que nous devons modifier au fur et à mesure que des poissons sont ajoutés ou retirés d’un aquarium fictif.

      Conditions préalables

      Pour tirer le meilleur parti de ce tutoriel, il est recommandé de se familiariser avec le tuple, le dictionnaire et les types de données de liste que ce soit en ce qui concerne leur syntaxe que de la manière d’en extraire des données. Vous pouvez consulter ces tutoriels pour obtenir les informations de base nécessaires :

      Ajouter des champs nominatifs aux tuples

      Les tuples de Python sont une séquence d’éléments immuables, ou inchangeables, ordonnés. Les tuples sont fréquemment utilisés pour représenter des données en colonnes ; par exemple, les lignes d’un fichier CSV ou les lignes d’une base de données SQL. Un aquarium peut garder une trace de son inventaire de poissons sous la forme d’une série de tuples.

      Un tuple de poissons individuel :

      ("Sammy", "shark", "tank-a")
      

      Ce tuple est composé de trois éléments de chaîne caractères.

      Bien qu’utile à certains égards, ce tuple n’indique pas clairement ce que représente chacun de ses champs. En réalité, l’élément 0 est un nom, l’élément 1 est une espèce, et l’élément 2 est le réservoir de stockage. 

      Explication des champs de tuples de poissons :

      name species tank
      Sammy shark tank-a

      Ce tableau montre clairement que chacun des trois éléments du tuple a une signification claire.

      namedtuple du module collections vous permet d’ajouter des noms explicites à chaque élément d’un tuple pour rendre ces significations claires dans votre programme Python.

      Utilisons namedtuple pour générer une classe qui nomme clairement chaque élément du tuple de poisson :

      from collections import namedtuple
      
      Fish = namedtuple("Fish", ["name", "species", "tank"])
      

      from collections import namedtuple donne à votre programme Python l’accès à la fonction d’usine namedtuple. L’appel de la fonction namedtuple() renvoie une classe qui est liée au nom Fish. Le namedtuple() a deux arguments : le nom souhaité de notre nouvelle classe "Fish" et une liste d’éléments nommés ["name", "species", "tank"]. 

      Nous pouvons utiliser la classe Fish pour représenter le tuple de poissons de tout à l’heure :

      sammy = Fish("Sammy", "shark", "tank-a")
      
      print(sammy)
      

      Si nous exécutons ce code, nous obtiendrons le résultat suivant :

      Output

      Fish(name="Sammy", species="shark", tank='tank-a')

      sammy est instancié à l’aide de la classeFish. sammy est un tuple avec trois éléments clairement nommés. 

      Les champs de sammy sont accessibles par leur nom ou avec un index tuple traditionnel :

      print(sammy.species)
      print(sammy[1])
      

      Si nous lançons ces deux appels d’impression, nous obtiendrons le résultat suivant :

      Output

      shark shark

      L’accès à .species a la même valeur que l’accès au deuxième élément de sammy en utilisant [1]. 

      Utiliser namedtuple du module collections rend votre programme plus lisible tout en conservant les propriétés importantes d’un tuple (à savoir qu’ils sont immuables et ordonnés). 

      De plus, la fonction d’usine namedtuple ajoute plusieurs méthodes supplémentaires aux instances de Fish.

      Utilisez ._asdict() pour convertir une instance en dictionnaire :

      print(sammy._asdict())
      

      Si nous lançons print, vous verrez des résultats comme ceux qui suivent :

      Output

      {'name': 'Sammy', 'species': 'shark', 'tank': 'tank-a'}

      Appeler .asdict() sur sammy renvoie un dictionnaire mettant en correspondance les noms de chacun des trois champs avec leurs valeurs correspondantes. 

      Les versions de Python plus anciennes que 3.8 pourraient produire cette ligne de manière légèrement différente. Vous pourriez, par exemple, voir un OrderedDict au lieu du simple dictionnaire présenté ici. 

      Note : Dans Python, les méthodes avec des traits de soulignement en tête sont généralement considérées comme « privées ». Les méthodes supplémentaires fournies par namedtuple (comme _asdict(), ._make(), ._replace(), etc.), sont toutefois publiques.

      Rassembler des données dans un dictionnaire

      Il est souvent utile de collecter des données dans les dictionnaires Python. defaulttdict du module de collections peut nous aider à rassembler les informations dans les dictionnaires de manière rapide et concise. 

      defaultdict ne soulève jamais une KeyError. Si une clé n’est pas présente, defaulttdict se contente d’insérer et de renvoyer une valeur de remplacement à la place : 

      from collections import defaultdict
      
      my_defaultdict = defaultdict(list)
      
      print(my_defaultdict["missing"])
      

      Si nous exécutons ce code, nous obtiendrons le résultat suivant :

      Output

      []

      defaultdict insère et renvoie une valeur de remplacement au lieu de lancer une KeyError. Dans ce cas, nous avons spécifié la valeur de remplacement sous forme de liste.

      Les dictionnaires ordinaires, en revanche, lancent une KeyError sur les clés manquantes :

      my_regular_dict = {}
      
      my_regular_dict["missing"]
      

      Si nous exécutons ce code, nous obtiendrons le résultat suivant :

      Output

      Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'missing'

      Le dictionnaire habituel my_regular_dict soulève une KeyError lorsque nous essayons d’accéder à une clé qui n’est pas présente. 

      defaulttdict se comporte différemment d’un dictionnaire ordinaire. Au lieu de soulever une KeyError sur une clé manquante, defaultdict appelle la valeur de remplacement sans argument pour créer un nouvel objet. Dans ce cas, list() pour créer une liste vide. 

      Pour continuer avec notre exemple d’aquarium fictif, disons que nous avons une liste de tuples de poissons représentant l’inventaire d’un aquarium :

      fish_inventory = [
          ("Sammy", "shark", "tank-a"),
          ("Jamie", "cuttlefish", "tank-b"),
          ("Mary", "squid", "tank-a"),
      ]
      

      Trois poissons existent dans l’aquarium – leur nom, leur espèce et leur bac de rétention sont notés dans ces trois tuples.

      Notre objectif est d’organiser notre inventaire par réservoir – nous voulons connaître la liste des poissons présents dans chaque réservoir. En d’autres termes, nous voulons un dictionnaire qui cartographie "tank-a". à ["Jamie", "Mary"] et "tank-b" à ["Jamie"].

      Nous pouvons utiliser defaulttdict pour regrouper les poissons par bassin :

      from collections import defaultdict
      
      fish_inventory = [
          ("Sammy", "shark", "tank-a"),
          ("Jamie", "cuttlefish", "tank-b"),
          ("Mary", "squid", "tank-a"),
      ]
      fish_names_by_tank = defaultdict(list)
      for name, species, tank in fish_inventory:
          fish_names_by_tank[tank].append(name)
      
      print(fish_names_by_tank)
      

      En exécutant ce code, nous obtiendrons le résultat suivant :

      Output

      defaultdict(<class 'list'>, {'tank-a': ['Sammy', 'Mary'], 'tank-b': ['Jamie']})

      fish_names_by_tank est déclaré comme un paramètre par défaut qui consiste par défaut à insérer list() au lieu de lancer une KeyError.  Comme cela garantit que chaque clé dansfish_names_by_tank pointera sur une liste, nous pouvons librement appeler .append() pour ajouter des noms à la liste de chaque réservoir.

      defaultdict vous aide ici car il réduit le risque d’erreurs inattendues de KeyErrors. Réduire lesKeyErrors inattendues signifie que votre programme peut être écrit plus clairement et avec moins de lignes. Plus concrètement, l’idiome defaultdict permet d’éviter d’instancier manuellement une liste vide pour chaque réservoir.

      Sans défaultdict, le corps de la boucle for aurait pu ressembler davantage à ceci :

      More Verbose Example Without defaultdict

      ...
      
      fish_names_by_tank = {}
      for name, species, tank in fish_inventory:
          if tank not in fish_names_by_tank:
            fish_names_by_tank[tank] = []
          fish_names_by_tank[tank].append(name)
      

      L’utilisation d’un simple dictionnaire (au lieu d’un defaultdict) signifie que le corps de la boucle for doit toujours vérifier l’existence du tank donné dans fish_names_by_tank. Ce n’est qu’après avoir vérifié que tank est déjà présent dans fish_names_by_tank, ou vient d’être initialisé avec un [], qu’on peut ajouter le nom du poisson.

      defaultdict peut aider à réduire le nombre de codes passe-partout lors du remplissage des dictionnaires car il ne provoque jamais de KeyError.

      Utilisation de deque pour ajouter efficacement des éléments de chaque côté d’une collection

      Les listes Python sont une séquence d’éléments ordonnée, mutable ou modifiable. Python peut ajouter des listes en temps constant (la longueur de la liste n’a aucun effet sur le temps qu’il faut pour l’ajout), mais l’insertion au début d’une liste peut être plus lente (le temps nécessaire augmente à mesure que la liste s’agrandit).

      En termes de notation Big O, l’ajout à une liste est une opération O(1) à temps constant. L’insertion au début d’une liste, en revanche, est plus lente avec O(n) performance. 

      Note : Les informaticiens mesurent souvent la performance des procédures en utilisant ce qu’on appelle la notation « Big O ». Lorsque la taille d’une entrée n’a aucun effet sur le temps nécessaire pour exécuter une procédure, on dit qu’elle se déroule en temps constant ou O(1) (“Big O of 1”). Comme vous l’avez appris plus haut, Python peut s’ajouter aux listes à performance temporelle constante, autrement dit O(1). 

      Parfois, la taille d’une entrée a une incidence directe sur le temps nécessaire à l’exécution d’une procédure. L’insertion au début d’une liste Python, par exemple, est d’autant plus lente qu’il y a plus d’éléments dans la liste. La notation Big O utilise la lettre n pour représenter la taille de l’entrée. Cela signifie que l’ajout d’éléments au début d’une liste Python se fait en « temps linéaire » ou O(n) (“Big O of n”). 

      En général, les procédures O(1) sont plus rapides que les procédures O(n).

      On peut l’insérer au début d’une liste Python :

      favorite_fish_list = ["Sammy", "Jamie", "Mary"]
      
      # O(n) performance
      favorite_fish_list.insert(0, "Alice")
      
      print(favorite_fish_list)
      

      Si nous exécutons ce qui suit, nous obtiendrons le résultat suivant :

      Output

      ['Alice', 'Sammy', 'Jamie', 'Mary']

      La méthode .insert (index, objet) sur list nous permet d’insérer "Alice" au début de favorite_fish_list. Toutefois, il est à noter que l’insertion au début d’une liste a O(n) performance. Comme la durée de favorite_fish_list augmente, le temps nécessaire pour insérer un poisson au début de la liste augmentera proportionnellement et prendra de plus en plus de temps.

      deque (prononcé « deck ») du module collections est un objet de type liste qui nous permet d’insérer des éléments au début ou à la fin d’une séquence avec une performance à temps constant (O(1)).

      Insérez un élément au début d’un deque: 

      from collections import deque
      
      favorite_fish_deque = deque(["Sammy", "Jamie", "Mary"])
      
      # O(1) performance
      favorite_fish_deque.appendleft("Alice")
      
      print(favorite_fish_deque)
      

      En exécutant ce code, nous obtiendrons le résultat suivant :

      Output

      deque(['Alice', 'Sammy', 'Jamie', 'Mary'])

      Nous pouvons instancier un deque en utilisant un ensemble d’éléments préexistants, en l’occurrence une liste de trois noms de poissons favoris. L’appel de la méthode appendleft de favorite_fish_deque nous permet d’insérer un élément au début de notre collection avec la performance O (1). O(1) performance signifie que le temps nécessaire pour ajouter un élément au début de favorite_fish_deque n’augmentera pas, même si favorite_fish_deque a des milliers ou des millions d’éléments. 

      Note : Bien que deque ajoute des entrées au début d’une séquence plus efficacement qu’une liste, deque n’effectue pas toutes ses opérations plus efficacement qu’une liste. Par exemple, l’accès à un article aléatoire dans un deque a une performance O(n), mais l’accès à un élément aléatoire d’une liste a une performance O(1). Utilisez deque lorsqu’il est important d’insérer ou de retirer rapidement des éléments de chaque côté de votre collection. Une comparaison complète des performances temporelles est disponible sur le wiki de Python.

      Conclusion

      Le module des collections est une partie puissante de la bibliothèque standard Python qui vous permet de travailler avec des données de manière concise et efficace.  Ce tutoriel couvrait trois des cours fournis par le module des collections comprenant namedtuple, defaultdict, et deque. 

      D’ici, vous pouvez utiliser la documentation du module de collecte pour en savoir plus sur les autres classes et utilités disponibles. Pour en savoir plus sur Python en général, vous pouvez lire notre Série de tutoriels Comment coder en Python 3. 



      Source link

      Utiliser les buffers dans Node.js


      L’auteur a choisi le COVID-19 Relief Fund pour recevoir un don dans le cadre du programme Write for DOnations.

      Introduction

      Un buffer est un espace en mémoire (généralement de la RAM) qui stocke des données binaires. Dans Node.js, nous pouvons accéder à ces espaces de mémoire grâce à la classe Buffer intégrée. Les buffers stockent une séquence d’entiers, comme un array en JavaScript. Contrairement aux arrays, vous ne pouvez pas modifier la taille d’un buffer une fois qu’il est créé.

      Vous avez peut-être utilisé des buffers sans vous en rendre compte si vous avez déjà écrit du code de Node.js. Par exemple, lorsque vous lisez à partir d’un fichier avec fs.readFile(), les données renvoyées au callback ou à la Promise sont un objet buffer. En outre, lorsque des requêtes HTTP sont effectuées dans Node.js, elles renvoient des flux de données qui sont temporairement stockés dans un buffer interne lorsque le client ne peut pas traiter le flux en une seule fois.

      Les buffers sont utiles lorsque vous interagissez avec des données binaires, généralement à des niveaux de réseau inférieurs. Ils vous donnent également la possibilité d’effectuer des manipulations fines de données dans Node.js.

      Dans ce tutoriel, vous utiliserez Node.js REPL pour parcourir divers exemples de buffers, comme la création de buffers, la lecture de buffers, l’écriture et la copie de buffers, et l’utilisation de buffers pour convertir des données binaires en données codées. À la fin du tutoriel, vous aurez appris comment utiliser la classe Buffer pour travailler avec des données binaires.

      Conditions préalables

      Étape 1 — Création d’un buffer

      Cette première étape vous présentera les deux façons principales de créer un objet buffer dans Node.js.

      Pour décider de la méthode à utiliser, vous devez répondre à cette question : voulez-vous créer un nouveau buffer ou extraire un buffer à partir de données existantes ? Si vous allez stocker en mémoire des données que vous n’avez pas encore reçues, vous voudrez créer un nouveau buffer. Dans Node.js, nous utilisons la fonction alloc() de la classe Buffer pour ce faire.

      Ouvrons le Node.js REPL pour voir par nous-mêmes. Dans votre terminal, entrez la commande node :

      Vous verrez que l’invite commence par >.

      La fonction alloc() prend la taille du buffer comme premier et seul argument requis. La taille est un nombre entier représentant le nombre d’octets de mémoire que l’objet buffer utilisera. Par exemple, si nous voulions créer un buffer de 1 Ko (kilooctet), équivalent à 1024 octets, nous entrerions ceci dans la console :

      • const firstBuf = Buffer.alloc(1024);

      Pour créer un nouveau buffer, nous avons utilisé la classe Buffer globalement disponible, qui contient la méthode alloc(). En fournissant 1024 comme argument pour alloc(), nous avons créé un buffer d’une taille de 1 Ko.

      Par défaut, lorsque vous initialisez un buffer avec alloc(), le buffer est rempli de zéros binaires en guise d’emplacement pour les données ultérieures. Cependant, nous pouvons modifier la valeur par défaut si nous le souhaitons. Si nous voulions créer un nouveau buffer avec des 1 au lieu des 0, nous définirions le deuxième paramètre de la fonction alloc()fill.

      Dans votre terminal, créez un nouveau buffer à l’invite REPL qui est rempli de 1 :

      • const filledBuf = Buffer.alloc(1024, 1);

      Nous venons de créer un nouvel objet buffer qui fait référence à un espace en mémoire qui stocke 1 Ko de 1. Bien que nous ayons saisi un nombre entier, toutes les données stockées dans un buffer sont des données binaires.

      Les données binaires peuvent se présenter sous de nombreux formats différents. Considérons par exemple une séquence binaire représentant un octet de données : 01110110. Si cette séquence binaire représentait une chaîne en anglais utilisant la norme de codage ASCII, il s’agirait de la lettre v. Cependant, si notre ordinateur traitait une image, cette séquence binaire pourrait contenir des informations sur la couleur d’un pixel.

      L’ordinateur sait les traiter différemment parce que les octets sont codés différemment. L’encodage des octets est le format de l’octet. Un buffer dans Node.js utilise le schéma d’encodage UTF-8 par défaut s’il est initialisé avec des données de chaîne. Un octet en UTF-8 représente un nombre, une lettre (en anglais et dans d’autres langues) ou un symbole. L’UTF-8 est un superset de l’ASCII, le code standard américain pour l’échange d’informations. L’ASCII peut coder des octets avec des lettres anglaises majuscules et minuscules, les chiffres 0-9, et quelques autres symboles comme le point d’exclamation (!) ou le signe esperluette (&).

      Si nous écrivions un programme qui ne pourrait fonctionner qu’avec des caractères ASCII, nous pourrions modifier l’encodage utilisé par notre buffer avec le troisième argument de la fonction alloc()encoding.

      Créons un nouveau buffer de cinq octets de long qui ne stocke que des caractères ASCII :

      • const asciiBuf = Buffer.alloc(5, 'a', 'ascii');

      Le buffer est initialisé avec cinq octets du caractère a, en utilisant la représentation ASCII.

      Remarque : par défaut, Node.js prend en charge les codages de caractères suivants :

      • ASCII, représenté sous le nom ascii
      • UTF-8, représenté sous le nom utf-8 ou utf8
      • UTF-16, représenté sous le nom utf-16le ou utf16le
      • UCS-2, représentée sous le nom ucs-2 ou ucs2
      • Base64, représentée sous le nom base64
      • Hexadecimal, représenté sous le nom hex
      • ISO/IEC 8859-1, représenté sous le nom latin1 ou binary

      Toutes ces valeurs peuvent être utilisées dans les fonctions de la classe Buffer qui acceptent un paramètre encoding. Par conséquent, ces valeurs sont toutes valables pour la méthode alloc().

      Jusqu’à présent, nous avons créé de nouveaux buffers avec la fonction alloc(). Mais nous pouvons parfois vouloir créer un buffer à partir de données qui existent déjà, comme une chaîne ou un tableau.

      Pour créer un buffer à partir de données pré-existantes, nous utilisons la méthode from(). Nous pouvons utiliser cette fonction pour créer des buffers à partir de :

      • Un tableau d’entiers : les valeurs des entiers peuvent être comprises entre 0 et 255.
      • Un ArrayBuffer : c’est un objet JavaScript qui stocke une longueur fixe d’octets.
      • Une chaîne.
      • Un autre buffer.
      • D’autres objets JavaScript qui ont une propriété Symbol.toPrimitive. Cette propriété indique à JavaScript comment convertir l’objet en un type de données primitives : boolean, null, undefined, number, string, or symbol. Vous pouvez en savoir plus sur les symboles en consultant la documentation JavaScript de Mozilla.

      Voyons comment nous pouvons créer un buffer à partir d’une chaîne. Dans l’invite Node.js, saisissez ceci :

      • const stringBuf = Buffer.from('My name is Paul');

      Nous avons maintenant un objet buffer créé à partir de la chaîne de caractères My name is Paul. Créons un nouveau buffer à partir d’un autre buffer que nous avons créé précédemment :

      • const asciiCopy = Buffer.from(asciiBuf);

      Nous avons maintenant créé un nouveau buffer asciiCopy qui contient les mêmes données que asciiBuf.

      Maintenant que nous avons fait l’expérience de la création de buffers, nous pouvons nous plonger dans des exemples de lecture de leurs données.

      Étape 2 — Lecture à partir d’un buffer

      Il existe de nombreuses façons d’accéder aux données dans un buffer. Nous pouvons accéder à un octet individuel dans un buffer ou nous pouvons extraire le contenu entier.

      Pour accéder à un octet d’un buffer, nous passons l’index ou l’emplacement de l’octet que nous voulons. Les buffers stockent les données de manière séquentielle comme des tableaux. Ils indexent également leurs données comme des tableaux, à partir de 0. Nous pouvons utiliser la notation de tableau sur l’objet buffer pour obtenir un octet individuel.

      Voyons à quoi cela ressemble en créant un buffer à partir d’une chaîne dans le REPL :

      • const hiBuf = Buffer.from('Hi!');

      Maintenant, lisons le premier octet du buffer :

      Lorsque vous appuyez sur ENTER, le REPL affiche :

      Output

      72

      L’entier 72 correspond à la représentation UTF-8 pour la lettre H.

      Remarque : les valeurs pour les octets peuvent être des nombres entre 0 et 255. Un octet est une séquence de 8 bits. Un bit est binaire, et ne peut donc avoir qu’une seule des deux valeurs : 0 ou 1. Si nous avons une séquence de 8 bits et deux valeurs possibles par bit, alors nous avons un maximum de 2⁸ valeurs possibles pour un octet. Cela correspond à un maximum de 256 valeurs. Comme nous commençons à compter à partir de zéro, cela signifie que notre nombre le plus élevé est 255.

      Faisons de même pour le deuxième octet. Entrez ce qui suit dans le REPL :

      Le REPL renvoie 105, qui représente le i minuscule.

      Enfin, obtenons le troisième caractère :

      Vous verrez 33 affiché dans le REPL, ce qui correspond à !

      Essayons de récupérer un octet d’un index non valide :

      Le REPL renverra :

      Output

      undefined

      C’est comme si nous essayions d’accéder à un élément d’un tableau avec un index incorrect.

      Maintenant que nous avons vu comment lire les octets individuels d’un buffer, voyons nos options pour récupérer en une seule fois toutes les données stockées dans un buffer. L’objet buffer est doté des méthodes toString() et toJSON(), qui renvoient l’intégralité du contenu d’un buffer dans deux formats différents.

      Comme son nom l’indique, la méthode toString() convertit les octets du buffer en une chaîne de caractères et la renvoie à l’utilisateur. Si nous utilisons cette méthode sur hiBuf, nous obtiendrons la chaîne Hi!. Essayons !

      Dans l’invite, saisissez :

      Le REPL renverra :

      Output

      'Hi!'

      Ce buffer a été créé à partir d’une chaîne. Voyons ce qui se passe si nous utilisons la fonction toString() sur un buffer qui n’a pas été créé à partir de données de chaîne.

      Créons un nouveau buffer vide d’une taille de 10 octets :

      • const tenZeroes = Buffer.alloc(10);

      Maintenant, utilisons la méthode toString() :

      Nous verrons le résultat suivant :

      'u0000u0000u0000u0000u0000u0000u0000u0000u0000u0000'
      

      La chaîne u0000 est le caractère Unicode pour NULL. Il correspond au nombre 0. Lorsque les données du buffer ne sont pas encodées sous forme de chaîne, la méthode toString() renvoie l’encodage UTF-8 des octets.

      La toString() a un paramètre optionnel, encoding. Nous pouvons utiliser ce paramètre pour modifier l’encodage des données du buffer qui sont renvoyées.

      Par exemple, si vous voulez l’encodage hexadecimal pour hiBuf, vous devez entrer ce qui suit à l’invite :

      Cette instruction évaluera :

      Output

      '486921'

      486921 est la représentation hexadécimale des octets qui représentent la chaîne Hi! Dans Node.js, lorsque les utilisateurs veulent convertir l’encodage des données d’un formulaire à un autre, ils mettent généralement la chaîne dans un buffer et appellent toString() avec l’encodage souhaité.

      La méthode toJSON() se comporte différemment. Que le buffer ait été fait à partir d’une chaîne ou non, il renvoie toujours les données sous forme de représentation entière de l’octet.

      Réutilisons les buffers hiBuf et tenZeroes pour nous entraîner à utiliser toJSON(). Dans l’invite, saisissez :

      Le REPL renverra :

      Output

      { type: 'Buffer', data: [ 72, 105, 33 ] }

      L’objet JSON a une propriété type qui sera toujours Buffer. C’est ainsi que les programmes peuvent distinguer ces objets JSON des autres objets JSON.

      La propriété data contient un tableau de la représentation entière des octets. Vous avez peut-être remarqué que 72, 105 et 33 correspondent aux valeurs que nous avons reçues lorsque nous avons tiré les octets individuellement.

      Essayons la méthode toJSON() avec tenZeroes :

      Dans le REPL, vous verrez ce qui suit :

      Output

      { type: 'Buffer', data: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] }

      Le type est le même que celui indiqué précédemment. Cependant, les données sont maintenant un tableau avec dix zéros.

      Maintenant que nous avons couvert les principales façons de lire à partir d’un buffer, voyons comment nous modifions le contenu d’un buffer.

      Étape 3 — Modification d’un buffer

      Il existe de nombreuses façons de modifier un objet buffer existant. Comme pour la lecture, nous pouvons modifier individuellement les octets du buffer en utilisant la syntaxe du tableau. Nous pouvons également écrire de nouveaux contenus dans un buffer, en remplacement des données existantes.

      Commençons par examiner comment nous pouvons modifier les octets individuels d’un buffer. Rappelons notre variable tampon hiBuf, qui contient la chaîne de caractères Hi!. Changeons chaque octet pour qu’elle contienne plutôt Hey.

      Dans le REPL, essayons d’abord de remplacer le deuxième élément de hiBuf par e :

      Voyons maintenant ce buffer comme une chaîne de caractères pour confirmer qu’il stocke les bonnes données. Poursuivez en appelant la méthode toString() :

      Il sera évalué comme :

      Output

      'Hu0000!'

      Nous avons reçu cette étrange sortie parce que le buffer ne peut accepter qu’une valeur entière. On ne peut pas l’attribuer à la lettre e ; il faut plutôt lui attribuer le nombre dont l’équivalent binaire représente e :

      Maintenant, lorsque nous appelons la méthode toString() :

      Nous obtenons cette sortie dans le REPL :

      Output

      'He!'

      Pour modifier le dernier caractère du buffer, nous devons fixer le troisième élément au nombre entier qui correspond à l’octet pour y :

      Confirmez en utilisant à nouveau la méthode toString() :

      Votre REPL affichera :

      Output

      'Hey'

      Si nous essayons d’écrire un octet qui est en dehors de la plage du buffer, il sera ignoré et le contenu du buffer ne changera pas. Essayons, par exemple, de régler le quatrième élément inexistant du buffer sur o :

      Nous pouvons confirmer que le buffer est inchangé avec la méthode toString() :

      La sortie est toujours :

      Output

      'Hey'

      Si nous voulons modifier le contenu de l’ensemble du buffer, nous pouvons utiliser la méthode write(). La méthode write() accepte une chaîne de caractères qui remplacera le contenu d’un buffer.

      Utilisons la méthode write() pour modifier le contenu de hiBuf et revenir à Hi!. Dans votre shell Node.js, tapez la commande suivante à l’invite :

      La méthode write() a renvoyé 3 dans le REPL. C’est parce qu’il a écrit trois octets de données. Chaque lettre a la taille d’un octet, puisque ce buffer utilise le codage UTF-8, qui utilise un octet pour chaque caractère. Si le buffer avait utilisé le codage UTF-16, qui comporte un minimum de deux octets par caractère, la fonction write() aurait renvoyé 6.

      Maintenant, vérifiez le contenu du buffer en utilisant toString() :

      Le REPL produira :

      Output

      'Hi!'

      Cette technique est plus rapide que de devoir modifier chaque élément octet par octet.

      Si vous essayez d’écrire plus d’octets que la taille d’un buffer, l’objet buffer n’acceptera que le nombre d’octets qu’il peut recevoir. Pour illustrer cela, créons un buffer qui stocke trois octets :

      • const petBuf = Buffer.alloc(3);

      Essayons maintenant d’y écrire Cats :

      Lorsque l’appel write() est évalué, le REPL renvoie 3, indiquant que seuls trois octets ont été écrits dans le buffer. Confirmez maintenant que le buffer contient les trois premiers octets :

      Le REPL renvoie :

      Output

      'Cat'

      La fonction write() ajoute les octets dans un ordre séquentiel, de sorte que seuls les trois premiers octets ont été placés dans le buffer.

      En revanche, faisons un Buffer qui stocke quatre octets :

      • const petBuf2 = Buffer.alloc(4);

      Ecrivez-lui le même contenu :

      Ensuite, ajoutez un nouveau contenu qui occupe moins d’espace que le contenu original :

      Puisque les buffers s’écrivent séquentiellement en partant de 0, si nous imprimions le contenu du buffer :

      Nous verrions :

      Output

      'Hits'

      Les deux premiers caractères sont écrasés, mais le reste du buffer est intact.

      Parfois, les données que nous voulons dans notre buffer préexistant ne sont pas dans une chaîne mais résident dans un autre objet buffer. Dans ces cas, nous pouvons utiliser la fonction copy() pour modifier ce que notre buffer stocke.

      Créons deux nouveaux buffers :

      • const wordsBuf = Buffer.from('Banana Nananana');
      • const catchphraseBuf = Buffer.from('Not sure Turtle!');

      Les buffers wordsBuf et catchphraseBuf contiennent tous deux des données de chaîne. Nous voulons modifier catchphraseBuf pour qu’il stocke Nananana Turtle! au lieu de Not sure Turtle! . Nous utiliserons copy() pour faire passer Nananana de wordsBuf à catchphraseBuf.

      Pour copier des données d’un buffer à l’autre, nous utiliserons la méthode copy() sur le buffer qui est la source de l’information. Par conséquent, comme wordsBuf possède les données des chaînes de caractères que nous voulons copier, nous devons copier comme ceci :

      • wordsBuf.copy(catchphraseBuf);

      Dans ce cas, le paramètre target est le buffer catchphraseBuf.

      Lorsque nous entrons cela dans le REPL, il renvoie 15 indiquant que 15 octets ont été écrits. La chaîne Nananana n’utilise que 8 octets de données, nous savons donc immédiatement que notre copie ne s’est pas déroulée comme prévu. Utilisez la méthode toString() pour voir le contenu de catchphraseBuf :

      • catchphraseBuf.toString();

      Le REPL renvoie :

      Output

      'Banana Nananana!'

      Par défaut, copy() a pris tout le contenu de wordsBuf et l’a placé dans catchphraseBuf. Nous devons être plus sélectifs dans notre objectif et ne copier que Nananana. Réécrivons le contenu original de catchphraseBuf avant de continuer :

      • catchphraseBuf.write('Not sure Turtle!');

      La fonction copy() dispose de quelques paramètres supplémentaires qui nous permettent de personnaliser les données qui sont copiées dans l’autre buffer. Voici une liste de tous les paramètres de cette fonction :

      • target – C’est le seul paramètre requis de copy(). Comme nous l’avons vu lors de notre précédente utilisation, il s’agit du buffer vers lequel nous voulons copier.
      • targetStart – Il s’agit de l’index des octets du buffer cible vers lequel nous devons commencer la copie. Par défaut, il est de 0, ce qui signifie qu’il copie les données à partir du début du buffer.
      • sourceStart – C’est l’index des octets du buffer source où nous devons copier.
      • sourceEnd – C’est l’index des octets dans le buffer source où nous devons arrêter la copie. Par défaut, il s’agit de la longueur du buffer.

      Donc, pour copier Nananana de wordsBuf à catchphraseBuf, notre target devrait être catchphraseBuf comme avant. targetStart serait 0, car nous voulons que Nananana apparaisse au début de catchphraseBuf. Le sourceStart devrait être 7, car c’est l’indice où commence Nananana dans wordsBuf. Le sourceEnd continuerait à être la longueur des buffers.

      À l’invite du REPL, copiez le contenu de wordsBuf comme ceci :

      • wordsBuf.copy(catchphraseBuf, 0, 7, wordsBuf.length);

      Le REPL confirme que 8 octets ont été écrits. Notez comment wordsBuf.length est utilisé comme valeur pour le paramètre sourceEnd. Comme pour les tableaux, la propriété length nous donne la taille du buffer.

      Voyons maintenant le contenu de catchphraseBuf :

      • catchphraseBuf.toString();

      Le REPL renvoie :

      Output

      'Nananana Turtle!'

      Bravo! Nous avons pu modifier les données de catchphraseBuf en copiant le contenu de wordsBuf.

      Vous pouvez quitter le Node.js REPL si vous le souhaitez. Notez que toutes les variables qui ont été créées ne seront plus disponibles lorsque vous le ferez :

      Conclusion

      Dans ce tutoriel, vous avez appris que les buffers sont des allocations de longueur fixe en mémoire qui stockent des données binaires. Vous avez d’abord créé des buffers en définissant leur taille en mémoire et en les initialisant avec des données pré-existantes. Vous avez ensuite lu les données d’un buffer en examinant leurs octets individuels et en utilisant les méthodes toString() et toJSON(). Enfin, vous avez modifié les données stockées par un buffer en changeant ses octets individuels et en utilisant les méthodes write() et copy().

      Les buffers vous donnent un bon aperçu de la façon dont les données binaires sont manipulées par Node.js. Maintenant que vous pouvez interagir avec les buffers, vous pouvez observer les différentes façons dont l’encodage des caractères affecte la façon dont les données sont stockées. Par exemple, vous pouvez créer des buffers à partir de données de chaînes qui ne sont pas codées en UTF-8 ou ASCII et observer leur différence de taille. Vous pouvez également choisir un buffer avec UTF-8 et utiliser toString() pour le convertir en d’autres schémas d’encodage.

      Pour en savoir plus sur les buffers dans Node.js, vous pouvez lire la documentation de Node.js sur l’objet Buffer. Si vous souhaitez continuer à apprendre Node.js, vous pouvez revenir à la série Comment coder dans Node.js, ou parcourir les projets de programmation et les configurations sur notre page thématique Node.



      Source link

      Comprendre les paramètres par défaut dans JavaScript


      L’auteur a choisi le COVID-19 Relief Fund pour recevoir un don dans le cadre du programme Write for DOnations.

      Introduction

      Dans ECMAScript 2015, les paramètres de fonction par défaut ont été introduits au langage JavaScript. Celles-ci permettent aux développeurs d’initialiser une fonction avec des valeurs par défaut si les arguments ne sont pas fournis à l’appel de fonction. Initialiser les paramètres de fonction de cette manière rendra vos fonctions plus faciles à lire et moins sujettes aux erreurs, et fournira un comportement par défaut pour vos fonctions. Cela vous aidera à éviter les erreurs dues au passage d’arguments undefined et à la déstructuration d’objets qui n’existent pas.

      Dans cet article, vous examinerez la différence entre paramètres et arguments, vous apprendrez comment utiliser les paramètres par défaut dans les fonctions, vous verrez d’autres façons de prendre en charge les paramètres par défaut et vous apprendrez quels types de valeurs et d’expressions peuvent être utilisés comme paramètres par défaut. Vous passerez également en revue des exemples qui montrent comment les paramètres par défaut fonctionnent en JavaScript.

      Arguments et paramètres

      Avant d’expliquer les paramètres de fonction par défaut, il est important de savoir ce que sont les paramètres par défaut. Pour cette raison, nous allons d’abord examiner la différence entre les arguments et les paramètres d’une fonction.  Si vous souhaitez en savoir plus sur cette distinction, consultez notre article précédent de la série JavaScript, Comment définir les fonctions en JavaScript.

      Dans le bloc de code suivant, vous allez créer une fonction qui renvoie le cube d’un numéro donné, défini comme x :

      // Define a function to cube a number
      function cube(x) {
        return x * x * x
      }
      

      La variable x dans cet exemple est un paramètre — une variable nommée passée dans une fonction. Un paramètre doit toujours être contenu dans une variable et ne doit jamais avoir de valeur directe.

      Regardez maintenant ce prochain bloc de code, qui appelle la fonction cube que vous venez de créer :

      // Invoke cube function
      cube(10)
      

      Cela donnera le résultat suivant :

      Output

      1000

      Dans ce cas, 10 est un argument – une valeur transmise à une fonction lorsqu’elle est invoquée. Souvent, la valeur sera également contenue dans une variable comme dans cet exemple :

      // Assign a number to a variable
      const number = 10
      
      // Invoke cube function
      cube(number)
      

      Cela donnera le même résultat :

      Output

      1000

      Si vous ne passez pas un argument à une fonction qui en attend un, la fonction utilisera implicitement undefined en tant que valeur :

      // Invoke the cube function without passing an argument
      cube()
      

      Cela retournera :

      Output

      NaN

      Dans ce cas, cube() essaie de calculer la valeur de undefined * undefined * undefined, ce qui donne NaN, ou “not a number”. Pour en savoir plus, consultez la section sur les nombres de la rubrique “Comprendre les types de données en JavaScript”.

      Ce comportement automatique peut parfois poser problème. Dans certains cas, vous pouvez souhaiter que le paramètre ait une valeur même si aucun argument n’a été transmis à la fonction. C’est là que la fonction de paramètres par défaut est pratique, un sujet que vous aborderez dans la section suivante.

      Syntaxe des paramètres par défaut

      Avec l’ajout de paramètres par défaut dans ES2015, vous pouvez maintenant attribuer une valeur par défaut à n’importe quel paramètre, que la fonction utilisera au lieu d’undefined lorsqu’elle est appelée sans argument. Cette section vous montrera d’abord comment le faire manuellement, puis vous guidera dans la définition des paramètres par défaut.

      Sans paramètres par défaut, vous devriez explicitement rechercher des valeurs undefined afin de définir des valeurs par défaut, comme le montre cet exemple :

      // Check for undefined manually
      function cube(x) {
        if (typeof x === 'undefined') {
          x = 5
        }
      
        return x * x * x
      }
      
      cube()
      

      Cela utilise une instruction conditionnelle pour vérifier si la valeur a été automatiquement fournie comme undefined puis fixe la valeur de x à 5. Cela donnera la sortie suivante :

      Output

      125

      En revanche, l’utilisation de paramètres par défaut atteint le même but avec beaucoup moins de code. Vous pouvez définir une valeur par défaut pour le paramètre en cube en l’assignant avec l’opérateur d’assignation d’égalité (=), comme souligné ici :

      // Define a cube function with a default value
      function cube(x = 5) {
        return x * x * x
      }
      

      Maintenant, lorsque la fonction cube est invoquée sans argument, elle affectera 5 à x et retournera le calcul au lieu de NaN :

      // Invoke cube function without an argument
      cube()
      

      Output

      125

      Elle fonctionnera toujours comme prévu lorsqu’un argument est passé, en ignorant la valeur par défaut :

      // Invoke cube function with an argument
      cube(2)
      

      Output

      8

      Cependant, une mise en garde importante à noter est que la valeur par défaut du paramètre remplacera également une valeur explicite undefined passée comme argument à une fonction, comme démontré ici :

      // Invoke cube function with undefined
      cube(undefined)
      

      Cela donnera le calcul avec x égal à 5 :

      Output

      125

      Dans ce cas, les valeurs des paramètres par défaut ont été calculées, et une valeur undefined explicite ne les a pas remplacées.

      Maintenant que vous avez une idée de la syntaxe de base des paramètres par défaut, la section suivante indiquera comment les paramètres par défaut fonctionnent avec différents types de données.

      Types de données des paramètres par défaut

      Toute valeur ou objet primitif peut être utilisé comme valeur de paramètre par défaut.  Dans cette section, vous verrez comment cette flexibilité augmente les possibilités d’utilisation des paramètres par défaut.

      Tout d’abord, définissez des paramètres en utilisant un numéro, une chaîne, un booléen, un objet, un tableau et une valeur nulle comme valeur par défaut. Cet exemple utilisera la syntaxe des fonctions fléchées :

      // Create functions with a default value for each data type
      const defaultNumber = (number = 42) => console.log(number)
      const defaultString = (string = 'Shark') => console.log(string)
      const defaultBoolean = (boolean = true) => console.log(boolean)
      const defaultObject = (object = { id: 7 }) => console.log(object)
      const defaultArray = (array = [1, 2, 3]) => console.log(array)
      const defaultNull = (nullValue = null) => console.log(nullValue)
      

      Lorsque ces fonctions sont invoquées sans paramètres, elles utiliseront toutes les valeurs par défaut :

      // Invoke each function
      defaultNumber()
      defaultString()
      defaultBoolean()
      defaultObject()
      defaultArray()
      defaultNull()
      

      Output

      42 "Shark" true {id: 7} (3) [1, 2, 3] null

      Notez que tout objet créé dans un paramètre par défaut sera créé chaque fois que la fonction sera appelée. Un des cas d’utilisation courantes des paramètres par défaut est d’utiliser ce comportement pour obtenir des valeurs d’un objet. Si vous essayez de déstructurer ou d’accéder à une valeur à partir d’un objet qui n’existe pas, cela lancera une erreur. Cependant, si le paramètre par défaut est un objet vide, cela donnera simplement des valeurs undefined au lieu de lancer une erreur :

      // Define a settings function with a default object
      function settings(options = {}) {
        const { theme, debug } = options
      
        // Do something with settings
      }
      

      Cela permet d’éviter l’erreur causée par la déstructuration d’objets qui n’existent pas.

      Maintenant que vous avez vu comment les paramètres par défaut fonctionnent avec différents types de données, la section suivante indiquera comment plusieurs paramètres par défaut peuvent fonctionner ensemble.

      Utilisation de plusieurs paramètres par défaut

      Vous pouvez utiliser autant de paramètres par défaut que vous voulez dans une fonction. Cette section vous montrera comment procéder et comment l’utiliser pour manipuler le DOM dans un exemple concret.

      Tout d’abord, déclarez une fonction sum() avec plusieurs paramètres par défaut :

      // Define a function to add two values
      function sum(a = 1, b = 2) {
        return a + b
      }
      
      sum()
      

      Il en résultera le calcul par défaut suivant :

      Output

      3

      De plus, la valeur utilisée dans un paramètre peut être utilisée dans n’importe quel paramètre par défaut ultérieur, de gauche à droite. Par exemple, cette fonction createUser crée un objet userObj comme troisième paramètre, et tout ce que la fonction elle-même fait est de retourner userObj avec les deux premiers paramètres :

      // Define a function to create a user object using parameters
      function createUser(name, rank, userObj = { name, rank }) {
        return userObj
      }
      
      // Create user
      const user = createUser('Jean-Luc Picard', 'Captain')
      

      Si vous appelez user ici, vous obtiendrez ce qui suit :

      Output

      {name: "Jean-Luc Picard", rank: "Captain"}

      Il est généralement recommandé de placer tous les paramètres par défaut à la fin d’une liste de paramètres, afin de pouvoir facilement laisser de côté les valeurs facultatives. Si vous utilisez d’abord un paramètre par défaut, vous devrez explicitement passer undefined pour utiliser la valeur par défaut.

      Voici un exemple avec le paramètre par défaut au début de la liste :

      // Define a function with a default parameter at the start of the list
      function defaultFirst(a = 1, b) {
        return a + b
      }
      

      Lorsque vous appelez cette fonction, vous devriez appeler defaultFirst() avec deux arguments :

      defaultFirst(undefined, 2)
      

      Cela donnerait ce qui suit :

      Output

      3

      Voici un exemple avec le paramètre par défaut à la fin de la liste :

      // Define a function with a default parameter at the end of the list
      function defaultLast(a, b = 1) {
        return a + b
      }
      
      defaultLast(2)
      

      Cela donnerait la même valeur :

      Output

      3

      Les deux fonctions ont le même résultat, mais celle dont la valeur par défaut est la dernière permet un appel de fonction beaucoup plus propre.

      Pour un exemple concret, voici une fonction qui va créer un élément DOM, et ajouter un label texte et des classes, si elles existent.

      // Define function to create an element
      function createNewElement(tag, text, classNames = []) {
        const el = document.createElement(tag)
        el.textContent = text
      
        classNames.forEach(className => {
          el.classList.add(className)
        })
      
        return el
      }
      

      Vous pouvez appeler la fonction avec certaines classes dans un tableau :

      const greeting = createNewElement('p', 'Hello!', ['greeting', 'active'])
      

      L’appel de greeting donnera la valeur suivante :

      Output

      <p class="greeting active">Hello!</p>

      Cependant, si vous quittez le tableau de classNames hors de l’appel de fonction, cela fonctionne toujours.

      const greeting2 = createNewElement('p', 'Hello!')
      

      greatt2 a maintenant la valeur suivante :

      Output

      <p>Hello!</p>

      Dans cet exemple, forEach() peut être utilisé sur un tableau vide sans problème. Si ce tableau vide n’était pas défini dans le paramètre par défaut, vous obtiendriez l’erreur suivante :

      Output

      VM2673:5 Uncaught TypeError: Cannot read property 'forEach' of undefined at createNewElement (<anonymous>:5:14) at <anonymous>:12:18

      Maintenant que vous avez vu comment plusieurs paramètres par défaut peuvent interagir, vous pouvez passer à la section suivante pour voir comment les appels de fonction opèrent en tant que paramètres par défaut.

      Appels de fonction comme paramètres par défaut

      En plus des primitives et des objets, le résultat de l’appel d’une fonction peut être utilisé comme paramètre par défaut.

      Dans ce bloc de code, vous créerez une fonction pour renvoyer un nombre aléatoire, puis vous utiliserez le résultat comme valeur de paramètre par défaut dans une fonction cube :

      // Define a function to return a random number from 1 to 10
      function getRandomNumber() {
        return Math.floor(Math.random() * 10)
      }
      
      // Use the random number function as a default parameter for the cube function
      function cube(x = getRandomNumber()) {
        return x * x * x
      }
      

      Maintenant, l’invocation de la fonction cube sans paramètre aura des résultats potentiellement différents chaque fois que vous l’appelez :

      // Invoke cube function twice for two potentially different results
      cube()
      cube()
      

      Le résultat de ces appels de fonction variera :

      Output

      512 64

      Vous pouvez même utiliser des méthodes intégrées, comme celles de l’objet Math, et utiliser la valeur renvoyée dans un appel de fonction comme paramètre dans une autre fonction.

      Dans l’exemple suivant, un nombre aléatoire est attribué à x, qui est utilisé comme paramètre dans la fonction cube que vous avez créée.  Le paramètre y calculera alors la racine cubique du nombre et vérifiera si x et y sont égaux :

      // Assign a random number to x
      // Assign the cube root of the result of the cube function and x to y
      function doesXEqualY(x = getRandomNumber(), y = Math.cbrt(cube(x))) {
        return x === y
      }
      
      doesXEqualY()
      

      Cela donnera le résultat :

      Output

      true

      Un paramètre par défaut peut même être une définition de fonction, comme on le voit dans cet exemple, qui définit un paramètre comme la fonction inner et renvoie l’appel de fonction du paramètre :

      // Define a function with a default parameter that is an anonymous function
      function outer(
        parameter = function inner() {
          return 100
        }
      ) {
        return parameter()
      }
      
      // Invoke outer function
      outer()
      

      Output

      100

      Cette fonction inner sera créée à partir de zéro chaque fois que la fonction outer est invoquée.

      Conclusion

      Dans cet article, vous avez appris ce que sont les paramètres de fonction par défaut et comment les utiliser. Vous pouvez maintenant utiliser les paramètres par défaut pour vous aider à garder vos fonctions propres et faciles à lire. Vous pouvez également assigner des objets et des tableaux vides aux paramètres par défaut afin de réduire à la fois la complexité et les lignes de code lorsque vous traitez des situations telles que la récupération de valeurs d’un objet ou le bouclage dans un tableau.

      Si vous souhaitez en savoir plus sur JavaScript, consultez la page d’accueil de notre série Comment coder en JavaScript, ou parcourez notre série Comment coder en Node.js pour lire des articles sur le développement back-end.



      Source link