One place for hosting & domains

      módulo

      Cómo usar el módulo collections en Python 3


      El autor seleccionó el COVID-19 Relief Fund para que reciba una donación como parte del programa Write for DOnations.

      Introducción

      Python 3 tiene varias estructuras de datos integradas, incluyendo tuplas, diccionarios y listas. Las estructuras de datos nos proporcionan una forma de organizar y almacenar datos. El módulo collections nos ayuda a completar y manipular las estructuras de datos de forma eficiente.

      En este tutorial, veremos tres clases del módulo collections para ayudarle a trabajar con tuples, diccionarios y listas. Usaremos namedtuples para crear tuplas con campos con nombre, defaultdict para agrupar de forma concisa la información en diccionarios, y deque para añadir elementos de forma eficiente a cualquier lado de un objeto lista.

      Para este tutorial, trabajaremos principalmente con un inventario de peces que necesitamos modificar a medida que se añaden o retiran peces a un acuario ficticio.

      Requisitos previos

      Para sacar el máximo partido a este tutorial, se recomienda que esté algo familiarizado con los tipos de datos tupla, diccionario y lista, con su sintaxis y con cómo recuperar datos desde ellos. Puede revisar estos tutoriales para encontrar la información básica necesaria:

      Añadir campos con nombre a las tuplas

      Las tuplas de Python son secuencias de elementos ordenadas de forma inmutable o inalterable. Las tuplas se utilizan frecuentemente para representar datos en columnas; por ejemplo, líneas de un archivo CSV o filas de una base de datos SQL. Un acuario puede mantener un seguimiento de su inventario de peces como una serie de tuplas.

      Una tupla de peces individual:

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

      Esta tupla está compuesta por tres elementos de cadena.

      Aunque es útil en cierta forma, esta tupla no indica claramente qué representa cada uno de sus campos. En realidad, el elemento 0 es un nombre, el elemento 1 es una especie y el elemento 2 es el depósito.

      Explicación de los campos de la tupla de peces:

      nombre especie tanque
      Sammy tiburón tanque-a

      Esta tabla deja claro que cada uno de los tres elementos de la tupla tiene un significado claro.

      namedtuple del módulo collections le permite añadir nombres explícitos a cada elemento de una tupla para hacer que estos significados sean claros en su programa Python.

      Vamos a usar namedtuple para generar una clase que claramente denomine a cada elemento de la tupla de peces:

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

      from collections import namedtuple proporciona a su programa Python acceso a la función de fábrica namedtuple. La invocación de la función namedtuple() devuelve una clase que está vinculada al nombre Fish. La función namedtuple() tiene dos argumentos: el nombre deseado de nuestra nueva clase "Fish" y una lista de elementos denominados ["name", "species", "tank"].

      Podemos usar la clase Fish para representar la tupla de peces anterior:

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

      Si ejecuta este código, verá el siguiente resultado:

      Output

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

      sammy se instancia usando la clase Fish. sammy es una tupla con tres elementos claramente nombrados.

      Se puede acceder a los campos de sammy por su nombre o con un índice de tupla tradicional:

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

      Si ejecutamos estas dos invocaciones print, veremos el siguiente resultado:

      Output

      shark shark

      Acceder a .species devuelve el mismo valor que acceder al segundo elemento de sammy usando [1]​​​.

      Usar namedtuple desde el módulo collections hace que su programa sea más legible al tiempo que mantiene las propiedades importantes de una tupla (que son inmutables y están ordenadas).

      Además, la función de fábrica namedtuple añade varios métodos adicionales para las instancias de Fish.

      Utilice ._asdict() para convertir una instancia en un diccionario:

      print(sammy._asdict())
      

      Si ejecutamos print, verá un resultado como el siguiente:

      Output

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

      Invocar .asdict() en sammy devuelve una asignación de diccionario de cada uno de los tres nombres de campo con sus correspondientes valores.

      Las versiones de Python anteriores a 3.8 pueden mostrar esta línea de forma ligeramente diferente. Es posible, por ejemplo, que vea OrderedDict en vez del diccionario simple mostrado aquí.

      Nota: En Python, los métodos con guiones bajos precedentes se consideran, normalmente, “privados”. Los métodos adicionales proporcionados por namedtuple (por ejemplo, _asdict(), ._make(), ._replace(), etc.), sin embargo, son públicos.

      Recoger datos en un diccionario

      A menudo es útil recopilar datos en los diccionarios Python. defaultdict del módulo collections puede ayudarnos a ensamblar información en diccionarios de forma rápida y concisa.

      defaultdict nunca plantea un KeyError. Si no está presente una clave, defaultdict solo inserta y devuelve un valor de marcador de posición:

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

      Si ejecutamos este código, veremos un resultado como el siguiente:

      Output

      []

      defaultdict inserta y devuelve un valor de marcador de posición en vez de lanzar un KeyError. En este caso, especificamos el valor de marcador de posición como una lista.

      Los diccionarios regulares, por el contrario, lanzarán un KeyError sobre las claves que faltan:

      my_regular_dict = {}
      
      my_regular_dict["missing"]
      

      Si ejecutamos este código, veremos un resultado como el siguiente:

      Output

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

      El diccionario regular my_regular_dict plantea un KeyError cuando intentamos acceder a una clave que no está presente.

      defaultdict se comporta de forma diferente a un diccionario regular. En vez de plantear un KeyError sobre una clave que falta, defaultdict invoca el valor de marcador de posición sin argumentos para crear un nuevo objeto. En este caso list() para crear una lista vacía.

      Continuando con nuestro ejemplo de acuario ficticio, digamos que tenemos una lista de tuplas de peces que representan el inventario de un acuario:

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

      Existen tres peces en el acuario; sus nombres, especies y tanque se anotan en estas tres tuplas.

      Nuestro objetivo es organizar nuestro inventario por tanque; queremos conocer la lista de peces presentes en cada tanque. En otras palabras, queremos un diccionario que asigne "tank-a" a ["Jamie", "Mary"] y "tank-b" a ["Jamie"].

      Podemos usar defaultdict para agrupar los peces por tanque:

      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)
      

      Cuando ejecutemos este código, veremos el siguiente resultado:

      Output

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

      fish_names_by_tank se declara como un defaultdict que va por defecto a insertar list() en vez de lanzar un KeyError. Ya que esto garantiza que cada clave en fish_names_by_tank apuntará a una lista, podemos invocar libremente .append() para añadir nombres a la lista de cada tanque.

      defaultdict le ayuda aquí porque reduce la probabilidad de KeyErrors inesperados. Reducir los KeyErrors inesperados significa que su programa puede escribirse de forma más clara y con menos líneas. Más concretamente, el idioma defaultdict le permite evitar instanciar manualmente una lista vacía para cada tanque.

      Sin defaultdict, el cuerpo de bucle for podría haber tenido un aspecto más similar a este:

      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)
      

      Usar simplemente un diccionario regular (en vez de un defaultdict) significa que el cuerpo de bucle for siempre tiene que comprobar la existencia de tank dado en fish_names_by_tank. Solo tras haber verificado que tank ya está presente en fish_names_by_tank, o que ha sido inicializado con un [], podremos anexar el nombre del pez.

      defaultdict puede ayudar a reducir el código de plantilla cuando se completan diccionarios, porque nunca plantea un KeyError.

      Usar deque para añadir elementos de forma eficiente a cada lado de una colección

      Las listas de Python son secuencias de elementos ordenadas mutables, o alterables. Python puede anexar a listas en tiempo constante (la longitud de la lista no tiene efecto sobre el tiempo en que se tarda en anexar), pero insertar al principio de una lista puede ser más lento (el tiempo que tarda aumenta a medida que la lista aumenta).

      En términos de la notación Big O, anexar a una lista es una operación O(1) de tiempo constante. Insertar al principio de una lista, por el contrario, es más lento con el rendimiento de O(n).

      Nota: Los ingenieros de software a menudo miden el rendimiento de los procedimientos usando algo llamado la notación “Big O”. Cuando el tamaño de una entrada no tiene efecto sobre el tiempo que se tarda en realizar un procedimiento, se dice que se ejecuta en tiempo constante u O(1) (“Big O de 1”) Como ha aprendido antes, Python puede anexar a listas con un rendimiento de tiempo constante, conocido como O(1).

      A veces, el tamaño de una entrada afecta directamente a la cantidad de tiempo que se tarda en ejecutar un procedimiento. Insertar al principio de una lista Python, por ejemplo, es más lento cuantos más elementos haya en la lista. La notación Big O utiliza la letra n para representar el tamaño de la entrada. Esto significa que añadir elementos al principio de una lista Python se ejecuta en “tiempo lineal” u O(n) (“Big O de n”).

      En general, los procedimientos O(1) son más rápidos que los procedimientos O(n).

      Podemos insertar al principio de una lista Python:

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

      Si ejecutamos lo siguiente, veremos un resultado como el siguiente:

      Output

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

      El método .insert(index, object) en la lista nos permite insertar "Alice" al principio de favorite_fish_list. Notablemente, sin embargo, insertar al principio de una lista tiene un rendimiento O(n). A medida que la longitud de favorite_fish_list aumenta, el tiempo para insertar un pez al principio de la lista aumentará proporcionalmente y tardará cada vez más.

      deque (pronunciado “deck”) del módulo collections es un objeto tipo listado que nos permite insertar elementos al principio o al final de una secuencia con un rendimiento de tiempo constante O(1).

      Insertar un elemento al principio de 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)
      

      Al ejecutar este código, veremos el siguiente resultado:

      Output

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

      Podemos instanciar un deque usando una colección de elementos preexistente, en este caso una lista de nombres de tres peces favoritos. Invocar el método appendleft de favorite_fish_deque nos permite insertar un elemento al principio de nuestra colección con un rendimiento O(1). El rendimiento O(1) significa que el tiempo que se requiere para añadir un elemento al principio de favorite_fish_deque no aumentará incluso si favorite_fish_deque tiene miles o millones de elementos.

      Nota: Aunque deque añade entradas al principio de una secuencia de forma más eficiente que una lista, deque no realiza todas sus operaciones de forma más eficiente que una lista. Por ejemplo, acceder a un elemento aleatorio en un deque tiene un rendimiento O(n), pero acceder a un elemento aleatorio en una lista tiene un rendimiento O(1). Utilice deque cuando sea importante insertar o eliminar elementos de cualquier lado de su colección rápidamente. Podrá ver una comparativa completa del rendimiento de tiempo en la wiki de Python.

      Conclusión

      El módulo collections es una parte potente de la biblioteca estándar de Python que le permite trabajar con datos de forma concisa y eficiente. Este tutorial ha cubierto tres de las clases proporcionadas por el módulo collections incluyendo namedtuple, defaultdict y deque.

      Desde aquí, puede usar la documentación del módulo collectionpara aprender más sobre otras clases y utilidades disponibles. Para obtener más información sobre Python en general, puede leer nuestra serie de tutoriales Cómo codificar en Python 3.



      Source link

      Como usar o módulo coleções em Python 3


      O autor selecionou a COVID-19 Relief Fund​​​​​ para receber uma doação como parte do programa Write for DOnations.

      Introdução

      O Python 3 possui uma série de estruturas de dados integrados, incluindo tuplas, dicionários e listas. As estruturas de dados nos fornecem uma maneira de organizar e armazenar dados. O módulo collections (coleções) nos ajuda a preencher e manipular eficientemente as estruturas de dados.

      Neste tutorial, vamos abordar três classes no módulo collections para ajudá-lo a trabalhar com tuplas, dicionários e listas. Usaremos o namedtuples para criar tuplas com campos nomeados, defaultdict para agrupar informações de forma concisa em dicionários e deque para adicionar com eficiência elementos a qualquer um dos lados de um objeto do tipo lista.

      Para este tutorial, trabalharemos principalmente com um inventário de peixes que precisaremos modificar à medida que peixes são adicionados a ou removidos de um aquário fictício.

      Pré-requisitos

      Para aproveitar ao máximo este tutorial, é recomendável ter alguma familiaridade com os tipos de dados tupla, dicionário e lista. Tanto com suas sintaxes, quanto com como recuperar dados deles. Você pode revisar estes tutoriais para as informações básicas necessárias:

      Como adicionar campos nomeados a tuplas

      As tuplas em Python são uma sequência ordenada imutável, ou inalterável, de elementos. As tuplas são frequentemente usadas para representar dados colunares. Por exemplo, linhas de um arquivo CSV ou linhas de um banco de dados SQL. Um aquário pode acompanhar seu inventário de peixes como uma série de tuplas.

      Uma tupla de peixe individual:

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

      Esta tupla é composta por três elementos string.

      Embora seja útil de algumas maneiras, esta tupla não indica com clareza o que cada um de seus campos representa. Na verdade, o elemento 0 é um nome, o elemento 1 é uma espécie e o elemento 2 é o tanque onde está localizado.

      Explicação dos campos da tupla de peixe:

      nome espécie tanque
      Sammy tubarão tanque-a

      Essa tabela deixa claro que cada um dos três elementos da tupla possui um significado claro.

      O namedtuple do módulo collections permite que você adicione nomes explícitos a cada elemento de uma tupla para tornar seus significados claros em seu programa Python.

      Vamos usar o namedtuple para gerar uma classe que nomeia com clareza cada elemento da tupla de peixe:

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

      from collections import namedtuple dá ao seu programa Python acesso à função de fábrica namedtuple. A chamada de função namedtuple() retorna uma classe que está ligada ao nome Fish (peixe). A função namedtuple() possui dois argumentos: o nome desejado da nossa nova classe "Fish" e uma lista de elementos nomeados ["name", "species", "tank"] (“nome”, “espécie”, “tanque”).

      Podemos usar a classe Fish para representar a tupla de peixe de antes:

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

      Se executarmos esse código, veremos o seguinte resultado:

      Output

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

      A sammy é instanciada usando a classe Fish. sammy é uma tupla com três elementos claramente nomeados.

      Os campos de sammy podem ser acessados pelo seu nome ou com um índice tradicional de tupla:

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

      Se executarmos essas duas chamadas de print, veremos o seguinte resultado:

      Output

      shark shark

      Acessar .species retorna o mesmo valor que quando acessa-se o segundo elemento de sammy usando [1].

      Usar o namedtuple do módulo collections torna seu programa mais legível, ao mesmo tempo em que mantém as propriedades importantes de uma tupla (que são imutáveis e ordenadas).

      Além disso, a função de fábrica namedtuple adiciona diversos métodos extras para as instâncias de Fish.

      Use ._asdict() para converter uma instância em um dicionário:

      print(sammy._asdict())
      

      Se executarmos print, você verá um resultado como o seguinte:

      Output

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

      Chamar .asdict() em sammy retorna um dicionário que mapeia cada um dos três nomes de campo aos seus valores correspondentes.

      As versões do Python mais antigas que 3.8 podem gerar esta linha ligeiramente diferente como resultado. Você pode, por exemplo, ver um OrderedDict em vez do dicionário evidente mostrado aqui.

      Nota: em Python, os métodos com underline à esquerda são geralmente considerados “privados”. Métodos adicionais disponibilizados pelo namedtuple (como _asdict(), ._make(), ._replace(), etc), no entanto, são públicos.

      Como coletar dados em um dicionário

      Muitas vezes, pode ser útil coletar dados em dicionários Python. O defaultdict do módulo collections pode nos ajudar a reunir as informações em dicionários de maneira rápida e concisa.

      O defaultdict nunca provoca um KeyError. Se uma chave não estiver presente, o defaultdict simplesmente insere e retorna um valor de espaço reservado:

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

      Se executarmos esse código, veremos um resultado como o seguinte:

      Output

      []

      O defaultdict insere e retorna um valor de espaço reservado ao invés de lançar um KeyError. Neste caso, especificamos o valor de espaço reservado como uma lista.

      Os dicionários regulares, por outro lado, lançarão um KeyError para chaves que estejam faltando:

      my_regular_dict = {}
      
      my_regular_dict["missing"]
      

      Se executarmos esse código, veremos um resultado como o seguinte:

      Output

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

      O dicionário regular my_regular_dict provoca um KeyError quando tentamos acessar uma chave que não está presente.

      O defaultdict comporta-se de maneira diferente de um dicionário regular. Em vez de criar um KeyError para uma chave que esteja faltando, o defaultdict chama o valor de espaço reservado com nenhum argumento para criar um novo objeto. Neste caso, o list() para criar uma lista vazia.

      Voltando ao nosso exemplo de aquário fictício, vamos supor que temos uma lista de tuplas de peixe representando o inventário de um aquário.

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

      Existem três peixes no aquário — seus nomes, espécies e tanques são anotados nestas três tuplas.

      Nosso objetivo é organizar nosso inventário por tanque — queremos saber a lista de peixes presentes em cada tanque. Em outras palavras, queremos um dicionário que mapeie "tank-a" para ["Jamie, "Mary"], e "tank-b" para ["Jamie"].

      Podemos usar o defaultdict para agrupar peixes por tanque:

      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)
      

      Ao executarmos esse código, veremos o seguinte resultado:

      Output

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

      O fish_names_by_tank é declarado como um defaultdict, que utiliza-se do padrão para inserir o list() ao invés de lançar um KeyError. Como isso garante que todas as chaves em fish_names_by_tank apontarão para uma list (lista), temos a liberdade para chamar o .append() para adicionar nomes à lista de cada tanque.

      O defaultdict é útil aqui porque isso reduz as chances de KeyErrors inesperados. A redução dos KeyErrors inesperados significa que seu programa pode ser escrito com maior clareza e com menos linhas. Mais concretamente, o idioma defaultdict permite que você evite instanciar manualmente uma lista vazia para cada tanque.

      Sem o defaultdict, o loop for poderia ter ficado parecido com isto:

      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)
      

      O uso de apenas um dicionário regular (em vez de um defaultdict) significa que o loop for precisa sempre verificar se o tank indicado em fish_names_by_tank existe. Só depois de termos verificado que o tank já está presente em fish_names_by_tank, ou que acabou de ser inicializado com um [], é que podemos acrescentar o nome do peixe.

      O defaultdict pode ajudar a diminuir o código boilerplate ao preencher os dicionários, pois ele nunca causa um KeyError.

      Como usar o deque para adicionar elementos de maneira eficiente a qualquer lado de uma coleção

      As listas Python são uma sequência ordenada mutável, ou alterável, de elementos. O Python pode acrescentar elementos às listas constantemente (o comprimento da lista não tem efeito no tempo necessário para a inserção). Entretanto, inserir elementos no início de uma lista pode demorar mais — o tempo necessário aumenta à medida que a lista aumenta de tamanho.

      Em termos de notação O-grande, acrescentar um elemento a uma lista é uma operação de tempo constante O(1). A inserção de um elemento no início de uma lista, por outro lado, é mais lenta com um desempenho O(n).

      Nota: os engenheiros de softwares geralmente medem o desempenho de procedimentos usando algo conhecido como notação “O-grande”. Quando o tamanho de uma entrada não tem efeito no tempo necessário para se executar um procedimento, diz-se que a execução ocorre em tempo constante, ou O(1) (“O-grande de 1”). Assim como você aprendeu acima, o Python pode acrescentar elementos a listas com um desempenho temporal constante, também conhecido como O(1).

      Às vezes, o tamanho de uma entrada afeta diretamente o tempo necessário para se executar um procedimento. A inserção de um elemento no início de uma lista Python, por exemplo, é executada mais lentamente à medida que existam mais elementos na lista. A notação O-grande utiliza a letra n para representar o tamanho da entrada. Isso significa que a adição de itens no início de uma lista Python é executada em “tempo linear” ou O(n) (“O-grande de n”).

      De um modo geral, os procedimentos O(1) são mais rápidos que os procedimentos O(n).

      Somos capazes inserir elementos no início de uma lista Python:

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

      Se executarmos isso, veremos um resultado como o seguinte:

      Output

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

      O método .insert(index, object) em lista permite que adicionemos "Alice" no início de favorite_fish_list. No entanto, deve-se notar que a inserção de um elemento no início de uma lista possui um desempenho O(n). À medida que o comprimento de favorite_fish_list cresce, o tempo necessário para inserir um peixe no início da lista crescerá proporcionalmente e demorará mais tempo.

      O deque (pronunciado como “deck”) do módulo collections é um objeto do tipo lista que nos permite inserir elementos no início ou final de uma sequência com performance temporal constante (O(1)).

      Insira um item no início de um deque:

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

      Ao executarmos esse código, veremos o seguinte resultado:

      Output

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

      Podemos instanciar um deque usando uma coleção preexistente de elementos, neste caso, uma lista de três nomes de peixes favoritos. Chamar o método appendleft de favorite_fish_deque nos permite inserir um item no início de nossa coleção com o desempenho O(1). Ter um desempenho O(1) significa que o tempo necessário para adicionar um item no início de favorite_fish_deque não aumentará, mesmo se favorite_fish_deque possuir milhares ou milhões de elementos.

      Nota: embora o deque adicione entradas no início de uma sequência mais eficientemente que uma lista, o deque não realiza todas as suas operações com maior eficiência que uma lista. Por exemplo, acessar um item aleatório em um deque possui um desempenho O(n), mas acessar um item aleatório em uma lista possui um desempenho O(1). Use o deque quando for importante inserir ou remover elementos de um dos lados de sua coleção rapidamente. Uma comparação completa do desempenho temporal está disponível na wiki do Python.

      Conclusão

      O módulo collections é uma parte poderosa da biblioteca padrão Python que permite que você trabalhe com dados de maneira concisa e eficiente. Este tutorial abordou três das classes disponibilizadas pelo módulo collections, incluindo namedtuple, defaultdict e deque.

      A partir daqui, utilize a documentação do módulo collection para aprender mais sobre outras classes e utilitários disponíveis. Para aprender mais sobre o Python em geral, consulte nossa série de tutoriais sobre Como programar em Python 3.



      Source link

      Como criar um servidor Web em Node.js com o módulo HTTP


      O autor selecionou a COVID-19 Relief Fund​​​​​ para receber uma doação como parte do programa Write for DOnations.

      Introdução

      Ao visualizar uma página em seu navegador, você está fazendo uma solicitação a outro computador na Internet, que em resposta, fornece a você a página Web. O computador ao qual você está se comunicando pela Internet é um servidor Web. Um servidor Web recebe solicitações HTTP de um cliente, como seu navegador, e fornece uma resposta HTTP, como uma página HTML ou um JSON de uma API.

      Para que um servidor retorne uma página da Web, vários softwares são envolvidos no processo. Normalmente, estes softwares se enquadram em duas categorias: o front-end e o back-end. O código de front-end lida com a forma como o conteúdo é apresentado, como a cor de uma barra de navegação e o estilo do texto. O código de back-end lida com a forma como os dados são trocados, processados e armazenados. O código que cuida das solicitações de rede do seu navegador, ou que se comunica com o banco de dados é gerenciado, principalmente, pelo código de back-end.

      O Node.js permite que os desenvolvedores utilizem o JavaScript para escrever o código de back-end, embora ele seja tradicionalmente usado no navegador para escrever o código de front-end. Ter o front-end e o back-end juntos reduz o esforço necessário para criar um servidor Web. Esse é o principal motivo pelo qual o Node.js é a escolha mais popular para escrever códigos de back-end.

      Neste tutorial, você aprenderá como desenvolver servidores Web usando o módulo http que está incluído no Node.js. Você desenvolverá servidores Web que podem retornar dados em JSON, arquivos CSV e páginas Web em HTML.

      Pré-requisitos

      • Verifique se o Node.js está instalado em sua máquina de desenvolvimento. Este tutorial utiliza a versão 10.19.0 do Node.js. Para instalar essa versão em macOS ou Ubuntu 18.04, siga os passos descritos no artigo sobre Como instalar o Node.js e criar um ambiente de desenvolvimento local em macOS ou a seção intitulada Instalando usando um PPA, do artigo sobre Como instalar o Node.js no Ubuntu 18.04.
      • A plataforma Node.js pode ser usada para criar servidores Web prontos para uso. Antes de começar, você precisa estar familiarizado com os princípios básicos do Node.js. Para isso, revise nosso guia sobre Como escrever e executar seu primeiro programa em Node.js.
      • Também usamos a programação assíncrona em uma de nossas seções. Se você não estiver familiarizado com a programação assíncrona em Node.js ou com o uso do módulo fs para interagir com arquivos, você pode aprender mais sobre estes assuntos no nosso artigo Como escrever um código assíncrono em Node.js.

      Passo 1 — Criando um servidor HTTP básico

      Começaremos criando um servidor que retorna um texto sem formatação ao usuário. O processo abordará os conceitos fundamentais necessários para configurar um servidor, que fornecerão a base necessária para retornar os formatos de dados mais complexos, como o JSON.

      Primeiro, precisamos configurar um ambiente de programação acessível para fazer nossos exercícios, bem como os outros execícios no artigo. No terminal, crie uma pasta chamada first-servers:

      Então, acesse aquela pasta:

      Agora, crie o arquivo para guardar o código:

      Abra o arquivo em um editor de texto. Utilizaremos o nano, pois ele está disponível no terminal:

      Começaremos carregando o módulo http, que é padrão em todas as instalações do Node.js. Adicione a linha seguinte ao hello.js:

      first-servers/hello.js

      const http = require("http");
      

      O módulo http contém a função de criar o servidor, que veremos depois. Se quiser aprender mais a respeito de módulos em Node.js, consulte nosso artigo Como criar um módulo Node.js.

      Nosso próximo passo será definir duas constantes, o host e a porta em que nosso servidor se associará:

      first-servers/hello.js

      ...
      const host = 'localhost';
      const port = 8000;
      

      Como mencionado anteriormente, os servidores Web aceitam solicitações de navegadores e de outros clientes. Podemos interagir com um servidor Web ao digitar um nome de domínio, que é traduzido para um endereço IP por um servidor DNS. Um endereço IP é uma sequência única de números que identificam uma máquina em uma rede, como a Internet. Para obter mais informações sobre conceitos de nome de domínio, consulte nosso artigo de Introdução à terminologia, componentes e conceitos do DNS.

      O valor localhost é um endereço privado especial que os computadores utilizam para se referir a eles mesmos. Normalmente, ele é equivalente ao endereço IP interno 127.0.0.1 e está disponível apenas para o computador local, ou seja, não está disponível para nenhuma rede local da qual participamos ou para a Internet.

      A porta é um número que os servidores usam como um ponto de extremidade ou uma “passagem” para nosso endereço IP. Em nosso exemplo, utilizaremos a porta 8000 para nosso servidor Web. As portas 8080 e 8000 são normalmente usadas como as portas padrão em processos de desenvolvimento e, na maioria dos casos, os desenvolvedores utilizarão essas portas em vez de outras disponíveis para servidores HTTP.

      Ao vincularmos nosso servidor a este host e porta, conseguiremos acessar nosso servidor ao visitarmos http://localhost:8000 em um navegador local.

      Vamos adicionar uma função especial que em Node.js, chamamos de request listener. Esta função foi criada para processar uma solicitação HTTP de entrada e retornar uma resposta HTTP. A função deve ter dois argumentos: um objeto de solicitação e um objeto de resposta. O objeto de solicitação capta todos os dados da solicitação HTTP que chegam. O objeto de resposta é usado para devolver respostas HTTP para o servidor.

      Queremos que nosso primeiro servidor retorne a seguinte mensagem sempre que alguém o acessar: "My first server!".

      Vamos adicionar a função a seguir:

      first-servers/hello.js

      ...
      
      const requestListener = function (req, res) {
          res.writeHead(200);
          res.end("My first server!");
      };
      

      Normalmente, o nome de uma função baseia-se no que ela faz. Por exemplo, se criássemos uma função request listener para retornar uma lista de livros, daríamos a ela o nome listBooks(). Como este é um caso de exemplo, usaremos o nome genérico requestListener.

      Todas as funções request listener em Node,js aceitam dois argumentos: req e res (podemos dar nomes diferentes a eles se quisermos). A solicitação HTTP que o usuário envia é capturada em um objeto Request, que corresponde ao primeiro argumento, req. A resposta HTTP que retornamos para o usuário é formada pela interação com o objeto Response no segundo argumento, res.

      A primeira linha res.writeHead(200); define o código de status HTTP da resposta. Os códigos de status do HTTP indicam o quão bem uma solicitação HTTP foi processada pelo servidor. Neste caso, o código de status 200 corresponde a "OK". Se estiver interessado em aprender sobre os vários códigos HTTP que seus servidores Web podem retornar e o que eles significam, o nosso guia Como resolver problemas comuns de erros de códigos HTTP será um bom ponto de partida.

      A próxima linha da função res.end("My first server!") ; escreve a resposta HTTP e a retorna para o cliente que a solicitou. Esta função retorna todos os dados que o servidor precisa retornar. Neste caso, ele retorna dados de texto.

      Por fim, podemos criar nosso servidor e usar nosso request listener:

      first-servers/hello.js

      ...
      
      const server = http.createServer(requestListener);
      server.listen(port, host, () => {
          console.log(`Server is running on http://${host}:${port}`);
      });
      

      Salve e saia do nano pressionando CTRL+X.

      Na primeira linha, criamos um novo objeto server através da função createServer() do módulo http. Este servidor aceita solicitações HTTP e as passa para nossa função requestListener().

      Após criarmos nosso servidor, precisaremos associá-lo a um endereço de rede. Faremos isso com o método server.listen(). Ele aceita três argumentos: port, host e uma função de retorno de chamada que é acionada quando o servidor começa a escutar.

      Todos esses argumentos são opcionais, mas é aconselhável especificar qual porta e qual host queremos que o servidor Web utilize. Ao implantar servidores Web para diferentes ambientes, é importante saber a porta e o host nos quais ele está funcionando para configurar o balanceamento de carga ou um alias DNS.

      A função de retorno de chamada registra uma mensagem no nosso console para que possamos saber quando o servidor começou a escutar conexões.

      Nota: embora o requestListener() não utilize o objeto req, ele deve ser o primeiro argumento da função.

      Usando menos de quinze linhas de código, criamos um servidor Web. Vamos vê-lo em ação e testá-lo por completo, executando o programa:

      No console, veremos esta saída:

      Output

      Server is running on http://localhost:8000

      Note que o prompt desaparece. Isso acontece porque o servidor Node.js é um processo de longa duração. Ele só fecha caso encontre um erro que cause falha e encerramento, ou se interrompermos o processo do Node.js que está sendo executado o servidor.

      Em uma janela do terminal separada, nos comunicaremos com o servidor usando o cURL, uma ferramenta CLI que transfere dados para e a partir de uma rede. Digite o seguinte comando para fazer uma solicitação GET HTTP ao nosso servidor em execução:

      • curl http://localhost:8000

      Ao pressionar ENTER, nosso terminal mostrará o seguinte resultado:

      Output

      My first server!

      Acabamos de configurar um servidor e receber nossa primeira resposta do servidor.

      Vamos detalhar o que aconteceu quando testamos nosso servidor. Ao utilizar o cURL, enviamos uma solicitação GET para o servidor em http://localhost:8000. Nosso servidor Node.js escutou as conexões deste endereço. O servidor transmitiu a solicitação para a função requestListener(). A função retornou dados de texto com o código de status 200. Em seguida, o servidor enviou esta resposta de volta ao cURL, que exibiu a mensagem em nosso terminal.

      Antes de continuarmos, vamos sair de nosso servidor em execução pressionando CTRL+C. Isso interrompe a execução de nosso servidor, e nos traz de volta ao prompt de linha de comando.

      Na maioria dos sites que visitamos ou APIs que utilizamos, as respostas do servidor raramente são textos sem formatação. Os formatos de resposta mais comuns que recebemos são páginas HTML e dados JSON. No próximo passo, aprenderemos como retornar respostas HTTP em formatos de dados comuns que encontramos na Web.

      Passo 2 — Retornando tipos diferentes de conteúdo

      A resposta que retornamos a partir de um servidor Web pode ter vários formatos. O JSON e o HTML foram mencionados anteriormente e, além deles, podemos retornar outros formatos de texto, como o XML e o CSV. Por fim, os servidores Web podem retornar dados não textuais, como PDFs, arquivos zip, áudio e vídeo.

      Neste artigo, além do texto sem formatação que acabamos de retornar, você aprenderá como retornar os tipos de dados a seguir:

      Os três tipos de dados são baseados em texto e são formatos populares para o envio de conteúdos na Web. Muitas linguagens e ferramentas de desenvolvimento do servidor possuem recursos para retornar esses tipos diferentes de dados. No contexto do Node.js, precisaremos fazer duas coisas:

      1. Definir o cabeçalho do Content-Type em nossas respostas HTTP com o valor adequado.
      2. Confirmar que o res.end() recebe os dados no formato correto.

      Veremos isso em ação com alguns exemplos. O código que escreveremos nesta seção e em seções posteriores possui muitas semelhanças com o código que escrevemos anteriormente. A maioria das alterações existem dentro da função requestListener(). Criaremos arquivos com este “código modelo” para facilitar o acompanhamento das próximas seções.

      Crie um arquivo novo chamado html.js. Este arquivo será usado mais tarde para retornar texto HTML em uma resposta HTTP. Colocaremos o código modelo aqui e o copiaremos para os outros servidores que retornam vários tipos.

      No terminal, digite o seguinte:

      Abra este arquivo em um editor de texto:

      Copie o “código modelo” Cole o código no nano:

      first-servers/html.js

      const http = require("http");
      
      const host = 'localhost';
      const port = 8000;
      
      const requestListener = function (req, res) {};
      
      const server = http.createServer(requestListener);
      server.listen(port, host, () => {
          console.log(`Server is running on http://${host}:${port}`);
      });
      

      Salve e saia do html.js com CTRL+X e, em seguida, retorne ao terminal.

      Vamos copiar este arquivo em dois arquivos novos. O primeiro arquivo terá o objetivo de retornar dados CSV na resposta HTTP:

      O segundo arquivo retornará uma resposta JSON no servidor:

      Os arquivos restantes serão úteis para exercícios posteriores:

      • cp html.js htmlFile.js
      • cp html.js routes.js

      Agora estamos prontos para continuar nossos exercícios. Começaremos retornando o JSON.

      Apresentando o JSON

      Mais conhecido como JSON, o JavaScript Object Notation é um formato de troca de dados baseado em texto. Como o nome sugere, ele é derivado de objetos do JavaScript, mas é independente de linguagem e pode ser usado por qualquer linguagem de programação que possa analisar sua sintaxe.

      O JSON é geralmente usado pelas APIs para aceitar e retornar dados. Sua popularidade se deve ao fato de ser um formato de transferência de dados mais leve que os padrões anteriores a ele, como o XML, bem como às ferramentas que permitem que esse dados sejam analisados pelos programas sem muito esforço. Se quiser aprender mais sobre o JSON, leia nosso guia sobre Como trabalhar com JSON em JavaScript.

      Abra o arquivo json.js com o nano:

      Queremos retornar uma resposta JSON. Vamos modificar a função requestListener() para retornar o cabeçalho apropriado a respostas JSON, modificando as linhas destacadas da seguinte maneira:

      first-servers/json.js

      ...
      const requestListener = function (req, res) {
          res.setHeader("Content-Type", "application/json");
      };
      ...
      

      O método res.setHeader() adiciona um cabeçalho HTTP à resposta. Os cabeçalhos HTTP são informações adicionais que podem ser anexadas a uma solicitação ou uma resposta. O método res.setHeader() recebe dois argumentos: o nome do cabeçalho e o valor dele.

      O cabeçalho Content-Type é usado para indicar o formato dos dados, também conhecido como tipo de mídia, que está sendo enviado com a solicitação ou resposta. Neste caso, nosso Content-Type é o application/json.

      Vamos retornar o conteúdo JSON ao usuário. Modifique o json.js para que ele se pareça com isto:

      first-servers/json.js

      ...
      const requestListener = function (req, res) {
          res.setHeader("Content-Type", "application/json");
          res.writeHead(200);
          res.end(`{"message": "This is a JSON response"}`);
      };
      ...
      

      Assim como antes, diremos aos usuários que as solicitações deles foram bem-sucedidas, retornando um código de status 200. Desta vez, na chamada response.end(), nosso argumento string contém um JSON válido.

      Salve e saia do json.js pressionando CTRL+X. Agora, vamos executar o servidor com o comando node:

      Em outro terminal, vamos acessar o servidor usando o cURL:

      • curl http://localhost:8000

      Ao pressionar ENTER, veremos o seguinte resultado:

      Output

      {"message": "This is a JSON response"}

      Agora, retornamos com sucesso uma resposta JSON, à semelhança de várias APIs populares que usamos para criar aplicativos. Você precisa sair do servidor em execução, usando CTRL+C, para que possamos retornar ao prompt do terminal padrão. Em seguida, vamos examinar outro formato popular de retorno de dados: o CSV.

      Apresentando o CSV

      O formato de arquivos CSV (valores separados por vírgula) é um padrão de texto muito usado para fornecer dados tabulares. Na maioria dos casos, cada linha é separada por um caractere de nova linha, e cada item da linha é separado por uma vírgula.

      Em nosso espaço de trabalho, abra o arquivo csv.js com um editor de texto:

      Vamos adicionar as linhas seguintes para nossa função requestListener():

      first-servers/csv.js

      ...
      const requestListener = function (req, res) {
          res.setHeader("Content-Type", "text/csv");
          res.setHeader("Content-Disposition", "attachment;filename=oceanpals.csv");
      };
      ...
      

      Desta vez, nosso Content-Type indica que um arquivo CSV está sendo retornado, pois o valor é text/csv. O segundo cabeçalho que adicionamos é o Content-Disposition. Este cabeçalho diz ao navegador como exibir os dados, em particular no navegador, ou como um arquivo separado.

      Ao retornarmos respostas CSV, a maioria dos navegadores modernos baixam automaticamente o arquivo, mesmo se o cabeçalho Content-Disposition não estiver definido. No entanto, ao retornar um arquivo CSV, devemos adicionar este cabeçalho mesmo assim, pois ele nos permite definir o nome do arquivo CSV. Neste caso, sinalizamos ao navegador que este arquivo CSV é um anexo e ele deve ser baixado. Em seguida, dizemos ao navegador que o nome do arquivo é oceanpals.csv.

      Vamos escrever os dados CSV na resposta HTTP:

      first-servers/csv.js

      ...
      const requestListener = function (req, res) {
          res.setHeader("Content-Type", "text/csv");
          res.setHeader("Content-Disposition", "attachment;filename=oceanpals.csv");
          res.writeHead(200);
          res.end(`id,name,emailn1,Sammy Shark,shark@ocean.com`);
      };
      ...
      

      Assim como antes, retornarmos um status 200/OK com nossa resposta. Desta vez, nossa chamada para o res.end() tem uma string que é uma CSV válida. A vírgula separa o valor de cada coluna e o caractere de nova linha (n) separa as linhas. Temos duas linhas, uma para o cabeçalho da tabela e outra para os dados.

      Testaremos este servidor no navegador. Salve o csv.js e saia do editor com o CTRL+X.

      Execute o servidor com o comando Node.js:

      Em outro terminal, acesse o servidor pelo cURL:

      • curl http://localhost:8000

      O console mostrará isso:

      Output

      id,name,email 1,Sammy Shark,shark@ocean.com

      Se formos para http://localhost:8000 em nosso navegador, um arquivo CSV será baixado. O nome do arquivo será oceanpals.csv.

      Saia do servidor em execução com CTRL+C, para retornar ao prompt do terminal padrão.

      Ao retornar o JSON e o CSV, abordamos dois casos populares das APIs. Em seguida, vamos abordar como retornar dados para sites que as pessoas visualizam em um navegador.

      Apresentando o HTML

      O HTML, ou HyperText Markup Language, é o formato mais comum utilizado quando queremos que usuários interajam com nosso servidor através de um navegador Web. Ele foi criado para estruturar o conteúdo Web. Os navegadores Web são desenvolvidos para exibir conteúdo HTML, bem como qualquer estilo que adicionarmos com o CSS (outra tecnologia Web de front-end que nos permite alterar a estética de nossos sites).

      Vamos abrir novamente o html.js com nosso editor de texto:

      Modifique a função requestListener() para retornar o cabeçalho de Content-Type apropriado para uma resposta HTML:

      first-servers/html.js

      ...
      const requestListener = function (req, res) {
          res.setHeader("Content-Type", "text/html");
      };
      ...
      

      Vamos retornar o conteúdo HTML ao usuário. Adicione as linhas destacadas ao html.js para que se pareça com isso:

      first-servers/html.js

      ...
      const requestListener = function (req, res) {
          res.setHeader("Content-Type", "text/html");
          res.writeHead(200);
          res.end(`<html><body><h1>This is HTML</h1></body></html>`);
      };
      ...
      

      Adicionamos primeiro o código de status HTTP. Em seguida, chamamos o response.end() com um argumento string que contém um HTML válido. Quando acessarmos nosso servidor no navegador, veremos uma página HTML com uma tag de cabeçalho que contém This is HTML.

      Vamos salvar e sair pressionando o CTRL+X. Agora, vamos executar o servidor com o comando node:

      Veremos a mensagem Server is running on http://localhost:8000 quando nosso programa for iniciado.

      Vá para o navegador e visite http://localhost:8000. Nossa página se parecerá com esta:

      Imagem da resposta HTML retornada do servidor Node.js

      Vamos encerrar a execução do servidor com CTRL+C e retornar ao prompt do terminal padrão.

      É comum que o HTML seja escrito em um arquivo separado do código do servidor, como nossos programas do Node.js. Em seguida, veremos como retornar respostas HTML a partir dos arquivos.

      Passo 3 — Apresentando uma página HTML a partir de um arquivo

      Podemos apresentar aos usuários o HTML como strings no Node.js, mas é melhor carregar os arquivos HTML e exibir seu conteúdo a eles. Desta maneira, não precisamos manter strings longas em nosso código Node.js à medida que o arquivo HTML cresce. Isso mantém o código mais conciso e permite que trabalhemos em cada aspecto do nosso site de maneira independente. Esta “separação de problemas” é comum em várias configurações de desenvolvimento Web. Assim, é bom saber como carregar arquivos HTML para permitir esta separação no Node.js.

      Para exibir arquivos HTML aplicaremos o módulo fs para carregar os arquivos e usaremos os dados dele ao escrever nossa resposta HTTP.

      Primeiro, criaremos um arquivo HTML, que será retornado pelo servidor Web quando solicitado. Crie um novo arquivo HTML:

      Abra o index.html em um editor de texto:

      Nossa página Web será bem simples. Ela terá um plano de fundo e exibirá um texto de saudação no centro. Adicione este código ao arquivo:

      first-servers/index.html

      <!DOCTYPE html>
      
      <head>
          <title>My Website</title>
          <style>
              *,
              html {
                  margin: 0;
                  padding: 0;
                  border: 0;
              }
      
              html {
                  width: 100%;
                  height: 100%;
              }
      
              body {
                  width: 100%;
                  height: 100%;
                  position: relative;
                  background-color: rgb(236, 152, 42);
              }
      
              .center {
                  width: 100%;
                  height: 50%;
                  margin: 0;
                  position: absolute;
                  top: 50%;
                  left: 50%;
                  transform: translate(-50%, -50%);
                  color: white;
                  font-family: "Trebuchet MS", Helvetica, sans-serif;
                  text-align: center;
              }
      
              h1 {
                  font-size: 144px;
              }
      
              p {
                  font-size: 64px;
              }
          </style>
      </head>
      
      <body>
          <div class="center">
              <h1>Hello Again!</h1>
              <p>This is served from a file</p>
          </div>
      </body>
      
      </html>
      

      Esta página Web mostra duas linhas de texto: Hello Again! e This is served from a file. As linhas aparecem no centro da página, uma acima da outra. A primeira linha de texto é exibida como um título, ou seja, em letras maiores. A segunda linha de texto aparecerá em letras ligeiramente menores. Todo o texto aparecerá na cor branca enquanto a página Web tem plano de fundo laranja.

      Embora não seja o âmbito deste artigo ou série, se estiver interessado em aprender mais sobre HTML, CSS e outras tecnologias Web de front-end, dê uma olhada no guia Introdução à Web do Mozilla.

      Isso é tudo o que precisamos para o HTML. Assim, salve e saia do arquivo com o CTRL+X. Agora, podemos avançar para o código do servidor.

      Para este exercício, trabalharemos com o htmlFile.js. Abra-o com o editor de texto:

      Como temos que ler um arquivo, vamos começar importando o módulo fs:

      first-servers/htmlFile.js

      const http = require("http");
      const fs = require('fs').promises;
      ...
      

      Este módulo contém uma função readFile() que utilizaremos para carregar o arquivo HTML corretamente. Importamos o objeto promessa do tipo variant, de acordo com as práticas modernas recomendadas para o JavaScript. Usamos as promessas por serem sintaticamente mais sucintas em comparação ao retorno de chamada, que teríamos que usar se atribuíssemos o fs para fazer somente o require('fs'). Para aprender mais sobre as práticas recomendadas da programação assíncrona, leia nosso guia Como escrever um código assíncrono no Node.js.

      Queremos que nosso arquivo HTML seja lido quando um usuário solicitar nosso sistema. Começaremos modificando o requestListener() para ler o arquivo:

      first-servers/htmlFile.js

      ...
      const requestListener = function (req, res) {
          fs.readFile(__dirname + "/index.html")
      };
      ...
      

      Usamos o método fs.readFile() para carregar o arquivo. Seu argumento possui o __dirname + "/index.html". A variável especial __dirname tem o caminho absoluto de onde o código Node.js está sendo executado. Em seguida, acrescentamos o /index.html para poder carregar o arquivo HTML que criamos mais cedo.

      Agora, vamos retornar a página HTML assim que estiver carregada:

      first-servers/htmlFile.js

      ...
      const requestListener = function (req, res) {
          fs.readFile(__dirname + "/index.html")
              .then(contents => {
                  res.setHeader("Content-Type", "text/html");
                  res.writeHead(200);
                  res.end(contents);
              })
      };
      ...
      

      Se a promessa fs.readFile() for resolvida com sucesso, ela retornará os dados dela. Usamos o método then() para processar este caso. O parâmetro contents contém os dados do arquivo HTML.

      Primeiramente, definimos o cabeçalho Content-Type para text/html para dizer ao cliente que estamos retornando dados HTML. Em seguida, escrevemos o código do status para indicar que a solicitação foi bem-sucedida. Por fim, enviamos ao cliente a página HTML que carregamos, com os dados na variável contents.

      O método fs.readFile() pode falhar ocasionalmente, por este motivo, precisamos saber como lidar com esta questão quando recebermos um erro. Adicione isso à função requestListener():

      first-servers/htmlFile.js

      ...
      const requestListener = function (req, res) {
          fs.readFile(__dirname + "/index.html")
              .then(contents => {
                  res.setHeader("Content-Type", "text/html");
                  res.writeHead(200);
                  res.end(contents);
              })
              .catch(err => {
                  res.writeHead(500);
                  res.end(err);
                  return;
              });
      };
      ...
      

      Salve o arquivo e saia do nano com o CTRL+X.

      Quando uma promessa encontra um erro, ela é rejeitada. Trataremos esta questão usando o método catch(). Ele aceita o erro que o fs.readFile() retorna, define o código de status para 500 sinalizando que um erro interno foi encontrado, e retorna o erro para o usuário.

      Execute nosso servidor com o comando node:

      No navegador Web, visite http://localhost:8000​​​. Você verá esta página:

      Imagem da página HTML carregada a partir de um arquivo no Node.js

      Você retornou uma página HTML a partir de um servidor para o usuário. Você pode encerrar a execução do servidor com o CTRL+C. Você verá o prompt do terminal quando encerrar o servidor.

      Ao escrever um código como este em um ambiente de produção, não é recomendável carregar uma página HTML toda vez que você receber uma solicitação HTTP. Embora essa página HTML tenha cerca de 800 bytes em tamanho, sites mais complexos podem ter megabytes em tamanho. Arquivos muito grandes podem levar um bom tempo para carregar. Se for esperado que seu site tenha um tráfego intenso, é recomendável carregar os arquivos HTML no momento da inicialização, além de salvar o conteúdo deles. Após eles carregarem, defina o servidor e faça-o escutar solicitações em um endereço.

      Para demonstrar este método, veremos como podemos retrabalhar nosso servidor para torná-lo mais eficiente e escalonável.

      Apresentando o HTML adequadamente

      Neste passo, em vez de carregar o HTML para cada solicitação, o carregaremos apenas uma vez, no início. A solicitação retornará os dados que carregamos na inicialização.

      No terminal, abra novamente o script do Node.js com um editor de texto:

      Começaremos adicionando uma nova variável antes de criarmos a função requestListener():

      first-servers/htmlFile.js

      ...
      let indexFile;
      
      const requestListener = function (req, res) {
      ...
      

      Quando executarmos este programa, esta variável reterá o conteúdo do arquivo HTML.

      Agora, vamos reajustar a função requestListener(). Em vez de carregar o arquivo, ele retornará o conteúdo do indexFile:

      first-servers/htmlFile.js

      ...
      const requestListener = function (req, res) {
          res.setHeader("Content-Type", "text/html");
          res.writeHead(200);
          res.end(indexFile);
      };
      ...
      

      Em seguida, trocaremos a lógica de leitura do arquivo, da função requestListener() para a inicialização do nosso servidor. Faça as seguintes alterações à medida que criamos o servidor:

      first-servers/htmlFile.js

      ...
      
      const server = http.createServer(requestListener);
      
      fs.readFile(__dirname + "/index.html")
          .then(contents => {
              indexFile = contents;
              server.listen(port, host, () => {
                  console.log(`Server is running on http://${host}:${port}`);
              });
          })
          .catch(err => {
              console.error(`Could not read index.html file: ${err}`);
              process.exit(1);
          });
      

      Salve o arquivo e saia do nano com o CTRL+X.

      O código que lê o arquivo é parecido com o que escrevemos em nossa primeira tentativa. No entanto, quando a leitura do arquivo for bem-sucedida, salvaremos seu conteúdo em nossa variável global indexFile. Em seguida, iniciamos o servidor com o método listen(). O ponto principal é que o arquivo precisa ser carregado antes do servidor ser executado. Desta maneira, a função requestListener() certamente retornará uma página HTML, pois a variável indexFile não estará vazia.

      Nosso manipulador de erros também foi alterado. Se o arquivo não puder ser carregado, o erro será capturado e exibido em nosso console. Em seguida, saímos do programa Node.js com a função exit() sem iniciar o servidor. Desta maneira, podemos ver a razão pela qual a leitura do arquivo falhou, resolver o problema e, em seguida, iniciar novamente o servidor.

      Nós criamos servidores Web diferentes que retornam vários tipos de dados para um usuário. Até agora, não utilizamos nenhuma solicitação de dados para determinar o que deveria ser retornado. Precisaremos usar dados de solicitação ao configurar rotas ou caminhos diferentes em um servidor Node.js. Por este motivo, veremos a seguir como eles funcionam juntos.

      Passo 4 — Gerenciando rotas usando um objeto de solicitação HTTP

      A maioria dos sites que visitamos ou APIs que utilizamos geralmente possui mais de um ponto de extremidade, para que possamos acessar vários recursos. Um bom exemplo disso seria um sistema de gerenciamento de livros que poderia ser usado em uma biblioteca. Esse sistema precisaria gerenciar não apenas os dados de livros, mas também os dados de autores, para facilitar os processos de catalogação e consulta.

      Embora os dados para livros e autores estejam relacionados, eles são dois objetos diferentes. Nestes casos, os desenvolvedores de software normalmente programam pontos de extremidades diferentes, como uma maneira de indicar aos usuários da API com quais tipos de dados eles estão interagindo.

      Vamos criar um novo servidor para uma biblioteca pequena, com o propósito de retornar dois tipos diferentes de dados. Se um usuário acessar o endereço de nosso servidor em /books, ele receberá uma lista de livros em JSON. Se eles forem para /authors, receberão uma lista de informações do autor em JSON.

      O que fizemos até agora foi retornar a mesma resposta para cada solicitação que recebemos. Vamos ilustrar isso rapidamente.

      Execute novamente nosso exemplo de resposta JSON:

      Em outro terminal, faremos a mesma solicitação cURL de antes:

      • curl http://localhost:8000

      Você verá:

      Output

      {"message": "This is a JSON response"}

      Agora, vamos testar outro comando curl:

      • curl http://localhost:8000/todos

      Após pressionar Enter, verá o mesmo resultado:

      Output

      {"message": "This is a JSON response"}

      Nós não desenvolvemos nenhuma lógica especial em nossa função requestListener() que possa processar um pedido cuja URL contenha /todos. Por esse motivo, o Node.js retorna a mesma mensagem JSON por padrão.

      Como queremos desenvolver um servidor de gerenciamento para uma biblioteca pequena, separaremos os tipos de dados retornados de acordo com o ponto de extremidade que o usuário acessar.

      Primeiro, saia do servidor em execução com o CTRL+C.

      Abra o routes.js em seu editor de texto:

      Começaremos armazenando nossos dados JSON em variáveis antes da função requestListener():

      first-servers/routes.js

      ...
      const books = JSON.stringify([
          { title: "The Alchemist", author: "Paulo Coelho", year: 1988 },
          { title: "The Prophet", author: "Kahlil Gibran", year: 1923 }
      ]);
      
      const authors = JSON.stringify([
          { name: "Paulo Coelho", countryOfBirth: "Brazil", yearOfBirth: 1947 },
          { name: "Kahlil Gibran", countryOfBirth: "Lebanon", yearOfBirth: 1883 }
      ]);
      ...
      

      A variável books é uma string que contém dados JSON para uma matriz de objetos do tipo livro. Cada livro tem um título ou nome, um autor e o ano de publicação.

      A variável authors é uma string que contém o JSON para uma matriz de objetos do tipo autor. Cada autor tem um nome, país de origem e seu ano de nascimento.

      Agora que temos os dados que nossas respostas retornarão, vamos começar a modificar a função requestListener() para retornar as rotas corretas.

      Primeiro, vamos garantir que cada resposta de nosso servidor tenha o cabeçalho Content-Type correto:

      first-servers/routes.js

      ...
      const requestListener = function (req, res) {
          res.setHeader("Content-Type", "application/json");
      }
      ...
      

      Agora, queremos retornar o JSON correto de acordo com o caminho da URL que o usuário acessar. Vamos criar uma instrução de switch na URL da solicitação:

      first-servers/routes.js

      ...
      const requestListener = function (req, res) {
          res.setHeader("Content-Type", "application/json");
          switch (req.url) {}
      }
      ...
      

      Para obter o caminho da URL de um objeto de solicitação, precisamos acessar sua propriedade url. Podemos adicionar casos à instrução switch para retornar o JSON apropriado.

      A instrução switch do JavaScript fornece uma maneira de controlar qual código é executado, dependendo do valor de um objeto ou expressão JavaScript (por exemplo, o resultado de operações matemáticas). Se precisar aprender ou recordar como utilizá-las, consulte nosso guia sobre Como usar a instrução switch em JavaScript.

      Vamos seguir em frente adicionando um case para quando o usuário quiser receber nossa lista de livros:

      first-servers/routes.js

      ...
      const requestListener = function (req, res) {
          res.setHeader("Content-Type", "application/json");
          switch (req.url) {
              case "/books":
                  res.writeHead(200);
                  res.end(books);
                  break
          }
      }
      ...
      

      Definimos nosso código de status para 200, que indica que está tudo bem com a solicitação, e retorna o JSON que contém a lista de nossos livros. Agora, vamos adicionar outro case para nossos autores:

      first-servers/routes.js

      ...
      const requestListener = function (req, res) {
          res.setHeader("Content-Type", "application/json");
          switch (req.url) {
              case "/books":
                  res.writeHead(200);
                  res.end(books);
                  break
              case "/authors":
                  res.writeHead(200);
                  res.end(authors);
                  break
          }
      }
      ...
      

      Assim como antes, o código de status será 200, pois não há nada errado com a solicitação. Desta vez retornamos o JSON que contém a lista de autores.

      Temos que retornar um erro caso o usuário tente acessar qualquer outro caminho. Para fazermos isso, adicionaremos o caso padrão:

      routes.js

      ...
      const requestListener = function (req, res) {
          res.setHeader("Content-Type", "application/json");
          switch (req.url) {
              case "/books":
                  res.writeHead(200);
                  res.end(books);
                  break
              case "/authors":
                  res.writeHead(200);
                  res.end(authors);
                  break
              default:
                  res.writeHead(404);
                  res.end(JSON.stringify({error:"Resource not found"}));
          }
      }
      ...
      

      Usamos a palavra-chave default em uma instrução switch para capturar todos os outros cenários que não foram capturados pelos nossos casos anteriores. Definimos o código de status para 404, que indica que a URL que eles estavam procurando não foi encontrada. Em seguida, definimos um objeto JSON que contém uma mensagem de erro.

      Vamos testar nosso servidor para verificar se ele se comporta como o esperado. Em outro terminal, executamos primeiro um comando para ver se recebemos nossa lista de livros:

      • curl http://localhost:8000/books

      Pressione Enter para ver o seguinte resultado:

      Output

      [{"title":"The Alchemist","author":"Paulo Coelho","year":1988},{"title":"The Prophet","author":"Kahlil Gibran","year":1923}]

      Até agora, tudo certo. Vamos fazer o mesmo processo para o /authors. Digite o comando a seguir no terminal:

      • curl http://localhost:8000/authors

      Você verá o seguinte resultado quando o comando for concluído:

      Output

      [{"name":"Paulo Coelho","countryOfBirth":"Brazil","yearOfBirth":1947},{"name":"Kahlil Gibran","countryOfBirth":"Lebanon","yearOfBirth":1883}]

      Vamos testar uma URL errada para confirmar que o requestListener() retorna a resposta de erro:

      • curl http://localhost:8000/notreal

      Ao digitar este comando, a seguinte mensagem será exibida:

      Output

      {"error":"Resource not found"}

      Você pode sair da execução do servidor com o CTRL+C.

      Acabamos de criar caminhos diferentes para que os usuários obtenham dados diferentes. Nós também adicionamos uma resposta padrão que retornará um erro HTTP caso o usuário digite uma URL não compatível.

      Conclusão

      Neste tutorial, você criou uma série de servidores HTTP Node.js. Primeiro, você retornou uma resposta de texto básica. Em seguida, avançou para retornar vários tipos de dados a partir de nosso servidor: JSON, CSV e HTML. A partir de então, você conseguiu combinar o carregamento de arquivos com as respostas HTTP para retornar uma página HTML do servidor para o usuário, além de criar uma API que usou informações da solicitação do usuário para determinar quais dados deveriam ser enviados em resposta a esta solicitação.

      Você está preparado para criar servidores Web que podem processar uma variedade de solicitações e respostas. Com esse conhecimento, é possível criar um servidor que retorna várias páginas HTML para usuários em pontos de extremidades diferentes. É possível também criar sua própria API.

      Para aprender mais sobre servidores Web HTTP em Node.js, leia a documentação do Node.js no módulo http. Caso queira continuar aprendendo sobre o Node.js, volte para a página da série Como programar em Node.js.



      Source link