One place for hosting & domains

      função

      Como usar a função Map do Python


      Introdução

      Podemos usar a função integrada do Python, map(), para aplicar uma função a cada item em um iterável (como uma lista ou um dicionário) e retornar um novo iterador para recuperar os resultados. map() retorna um objeto map (um iterador), que podemos usar em outras partes do nosso programa. Também podemos passar o objeto map à função list(), ou outro tipo de sequência, para criar um iterável.

      A sintaxe para a função map() é a seguinte:

      map(function, iterable, [iterable 2, iterable 3, ...])
      

      Em vez de usar um loop for , a função map() fornece uma maneira de aplicar uma função a todos os itens em um iterável. Portanto, muitas vezes ela pode ser mais eficiente, uma vez que ela está aplicando a função apenas a um item de cada vez, em vez de fazer cópias dos itens em outro iterável. Isso é particularmente útil ao trabalhar em programas que processam grandes conjuntos de dados. map() também pode tomar vários iteráveis como argumentos para a função, enviando um item de cada iterável de cada vez para a função.

      Neste tutorial, vamos revisar três maneiras diferentes de trabalhar com map(): com uma função lambda, com uma função definida pelo usuário, e finalmente com uma função embutida usando vários argumentos iteráveis.

      Usando uma função Lambda

      O primeiro argumento para map() é uma função, que usamos para aplicar a cada item. O Python chama a função uma vez para cada item no iterável que passamos para map() e retorna o item manipulado dentro de um objeto map. Como o primeiro argumento da função, podemos passar uma função definida pelo usuário ou fazer uso de funções lambda, particularmente quando a expressão for menos complexa.

      A sintaxe de map() com uma função lambda é a seguinte:

      map(lambda item: item[] expression, iterable)
      

      Com uma lista como a seguinte, podemos implementar uma função lambda com uma expressão que queremos aplicar a cada item em nossa lista:

      numbers = [10, 15, 21, 33, 42, 55]
      

      Para aplicar uma expressão contra cada um dos nossos números, podemos usar map() e lambda:

      mapped_numbers = list(map(lambda x: x * 2 + 3, numbers))
      

      Aqui declaramos um item em nossa lista como x. Em seguida, adicionamos nossa expressão. Passamos em nossa lista de números como o iterável para map().

      Para receber os resultados disso imediatamente, imprimimos uma lista do objeto map :

      print(mapped_numbers)
      

      Output

      [23, 33, 45, 69, 87, 113]

      Usamos list() para que o objeto map seja retornado a nós como uma lista, ao invés de um objeto menos legível por humanos, como <map object at 0x7fc250003a58>. O objeto map é um iterador sobre nossos resultados, assim poderíamos fazer loop sobre ele com for ou podemos usar list() para transformá-lo em uma lista. Estamos fazendo isso aqui porque é uma boa maneira de revisar os resultados.

      Por fim, map() é mais útil ao trabalhar com grandes conjuntos de dados, então provavelmente trabalharíamos mais com o objeto map e, de maneira geral, não estaríamos usando um construtor como list() neles.

      Para conjuntos de dados menores, as list comprehensions podem ser mais adequadas, mas para os propósitos deste tutorial, estamos usando um pequeno conjunto de dados para demonstrar a função map().

      Implementando uma função definida pelo usuário

      Da mesma forma que o lambda, podemos usar uma função que definimos para aplicar a um iterável. Embora as funções lambda sejam mais úteis para implementar quando você está trabalhando com uma expressão de uma linha, as funções definidas pelo usuário são mais adequadas quando a expressão cresce em complexidade. Além disso, quando precisamos passar outro dado para a função que você está aplicando ao seu iterável, as funções definidas pelo usuário podem ser uma escolha melhor para a legibilidade.

      Por exemplo, no seguinte iterável, cada item é um dicionário que contém detalhes diferentes sobre cada uma das nossas criaturas do aquário:

      aquarium_creatures = [
          {"name": "sammy", "species": "shark", "tank number": 11, "type": "fish"},
          {"name": "ashley", "species": "crab", "tank number": 25, "type": "shellfish"},
          {"name": "jo", "species": "guppy", "tank number": 18, "type": "fish"},
          {"name": "jackie", "species": "lobster", "tank number": 21, "type": "shellfish"},
          {"name": "charlie", "species": "clownfish", "tank number": 12, "type": "fish"},
          {"name": "olly", "species": "green turtle", "tank number": 34, "type": "turtle"}
      ]
      

      Decidimos que todas as criaturas do aquário estão na verdade se movendo para o mesmo tanque. Precisamos atualizar nossos registros para refletir que todas as nossas criaturas estão se movendo para o tanque 42. Para que map() acesse cada dicionário e cada par chave:valor neles, construimos uma função aninhada:

      def assign_to_tank(aquarium_creatures, new_tank_number):
          def apply(x):
              x["tank number"] = new_tank_number
              return x
          return map(apply, aquarium_creatures)
      

      Definimos uma função assign_to_tank() que recebe aquarium_creatures e new_tank_number como parâmetros. Em assign_to_tank() passamos apply() como a função para map() na linha final. A função assign_to_tank retornará o iterador resultante de map().

      apply() toma x como um argumento, que representa um item em nossa lista — um único dicionário.

      Em seguida, definimos que x é a chave "tank number" de aquarium_creatures e que ele deve armazenar o new_tank_number que foi passado. Retornamos cada item depois de aplicar o novo número do tanque.

      Chamamos assign_to_tank() com nossa lista de dicionários e o novo número do tanque que queremos substituir para cada criatura:

      assigned_tanks = assign_to_tank(aquarium_creatures, 42)
      

      Assim que a função tiver sido concluída, temos nosso objeto map armazenado na variável assigned_tanks, que transformamos em uma lista e imprimimos:

      print(list(assigned_tanks))
      

      Receberemos a seguinte saída deste programa:

      Output

      [{'name': 'sammy', 'species': 'shark', 'tank number': 42, 'type': 'fish'}, {'name': 'ashley', 'species': 'crab', 'tank number': 42, 'type': 'shellfish'}, {'name': 'jo', 'species': 'guppy', 'tank number': 42, 'type': 'fish'}, {'name': 'jackie', 'species': 'lobster', 'tank number': 42, 'type': 'shellfish'}, {'name': 'charlie', 'species': 'clownfish', 'tank number': 42, 'type': 'fish'}, {'name': 'olly', 'species': 'green turtle', 'tank number': 42, 'type': 'turtle'}]

      Mapeamos o novo número do tanque para nossa lista de dicionários. Usando uma função que definimos, podemos incorporar map() para aplicar a função de forma eficiente em cada item da lista.

      Da mesma maneira que as funções lambda ou nossas próprias funções definidas, podemos usar as funções embutidas do Python com map(). Para aplicar uma função com múltiplos iteráveis, passamos outro nome iterável após o primeiro. Por exemplo, usando a função pow() que pega dois números para encontrar a potência do número base ao expoente fornecido.

      Aqui temos nossas listas de inteiros que gostaríamos de usar com pow():

      base_numbers = [2, 4, 6, 8, 10]
      powers = [1, 2, 3, 4, 5]
      

      Em seguida, passamos pow() como nossa função em map() e fornecemos as duas listas como nossos iteráveis:

      numbers_powers = list(map(pow, base_numbers, powers))
      
      print(numbers_powers)
      

      map() irá aplicar a função pow() ao mesmo item em cada lista para fornecer a potência. Portanto, nossos resultados mostrarão 2**1, 4**2, 6**3, e assim por diante:

      Output

      [2, 16, 216, 4096, 100000]

      Se pudéssemos fornecer a map() um iterável que fosse mais longo do que o outro, map() pararia de calcular assim que ele atingisse o final daquele mais curto. No programa a seguir, estamos estendendo base_numbers com três números adicionais:

      base_numbers = [2, 4, 6, 8, 10, 12, 14, 16]
      powers = [1, 2, 3, 4, 5]
      
      numbers_powers = list(map(pow, base_numbers, powers))
      
      print(numbers_powers)
      

      Como resultado, nada será a alterado dentro do cálculo deste programa e assim ele ainda irá produzir o mesmo resultado:

      Output

      [2, 16, 216, 4096, 100000]

      Usamos a função map() com uma função integrada do Python e vimos que ela pode lidar com vários iteráveis. Também revisamos que map() continuará a processar vários iteráveis até atingir o final daquele com o menor número de itens.

      Conclusão

      Neste tutorial, aprendemos as diferentes formas de usar a função map() em Python. Agora, você pode usar map() com sua própria função, uma função lambda, e com quaisquer outras funções integradas. Você também pode implementar map() com funções que exigem vários iteráveis.

      Neste tutorial, imprimimos os resultados de map() imediatamente para um formato de lista para fins de demonstração. Em nossos programas, normalmente usaríamos o objeto map retornado para manipular ainda mais os dados.

      Se você quiser aprender mais sobre Python, confira nossa série Como programar em Python 3 e nossa página do tópico Python. Para aprender mais sobre como trabalhar com conjuntos de dados em programação funcional, confira nosso artigo sobre a função filter().



      Source link

      Como usar a função de filtro do Python


      Introdução

      A função integrada filter() do Python pode ser usada para criar um novo iterador a partir de um iterável existente (como uma list a ou um dicionário) que irá filtrar de forma eficiente os elementos usando uma função que fornecemos. Um iterável é um objeto do Python que pode “sofrer iteração”, ou seja, ele retornará itens em uma sequência para que possamos usá-la em um loop for.

      A sintaxe básica para a função filter() é:

      filter(function, iterable)
      

      Isso irá retornar um objeto de filtro, que é um iterável. Podemos usar uma função como a list() para fazer uma lista de todos os itens retornados em um objeto de filtro.

      A função filter() fornece uma forma de filtrar valores que muitas vezes pode ser mais eficiente do que uma compreensão de lista, especialmente quando começamos a trabalhar com conjuntos de dados maiores. Por exemplo, uma compreensão de lista fará uma nova lista, que irá aumentar o tempo de execução para esse processamento. Isso significa que depois que nossa compreensão de lista tiver completado a expressão, teremos duas listas na memória. Por outro lado, a função filter() fará um objeto simples que contém uma referência à lista original, a função fornecida e um índice de onde ir na lista original, que irá ocupar menos memória.

      Neste tutorial, vamos revisar quatro maneiras diferentes de usar a filter(): com duas estruturas iteráveis diferentes, com uma função lambda e sem uma função definida.

      O primeiro argumento para filter() é uma função, que usamos para decidir se é preciso incluir ou filtrar cada item. A função é chamada uma vez para cada item presente no iterável passado como segundo argumento e, cada vez que ela retorna False, o valor é abandonado. Como este argumento é uma função, podemos passar uma função normal ou fazer uso das funções lambda, especialmente quando a expressão for menos complexa.

      A seguir, está a sintaxe de uma lambda com filter():

      filter(lambda item: item[] expression, iterable)
      

      Em uma lista, como a seguinte, podemos incorporar uma função lambda com uma expressão sobre a qual queremos avaliar cada item da lista:

      creature_names = ['Sammy', 'Ashley', 'Jo', 'Olly', 'Jackie', 'Charlie']
      

      Para filtrar esta lista de forma a encontrar os nomes das nossas criaturas de aquário que começam com uma vogal, podemos executar a seguinte função lambda:

      print(list(filter(lambda x: x[0].lower() in 'aeiou', creature_names)))
      

      Aqui declaramos um item em nossa lista como x. Então, definimos nossa expressão para acessar o primeiro caractere de cada string (ou o caractere “zero”), que é x[0]. Deixar a primeira letra minúscula em cada um dos nomes garante que eles terão letras correspondentes com a string em nossa expressão, 'aeiou'.

      Por fim, passamos o iterável creature_names. Assim como na seção anterior, aplicamos list() ao resultado para criar uma lista a partir dos retornos do filter() iterador.

      O resultado será o seguinte:

      Output

      ['Ashley', 'Olly']

      Este mesmo resultado pode ser alcançado usando uma função que definimos:

      creature_names = ['Sammy', 'Ashley', 'Jo', 'Olly', 'Jackie', 'Charlie']
      
      def names_vowels(x):
        return x[0].lower() in 'aeiou'
      
      filtered_names = filter(names_vowels, creature_names)
      
      print(list(filtered_names))
      

      Nossa função names_vowels define a expressão que implementaremos para filtrar creature_names.

      Mais uma vez, o resultado seria o seguinte:

      Output

      ['Ashley', 'Olly']

      De modo geral, as funções lambda alcançam o mesmo resultado com filter() se comparadas a quando usamos uma função regular. A necessidade de definir uma função regular cresce à medida que a complexidade das expressões que filtram nossos dados aumenta e é provável que isso promova uma maior legibilidade em nosso código.

      Podemos passar None como o primeiro argumento para filter() para que o iterador retornado filtre qualquer valor que o Python considere “aparentemente falso”. No geral, o Python considera qualquer coisa com um comprimento 0 (como uma lista vazia ou uma string vazia) ou numericamente equivalente a 0 como falso, por isso o uso do termo “aparentemente falso”.

      No caso a seguir, queremos filtrar nossa lista para mostrar apenas os números de tanques em nosso aquário:

      aquarium_tanks = [11, False, 18, 21, "", 12, 34, 0, [], {}]
      

      Neste código, temos uma lista que contém inteiros, sequências vazias e um valor booleano.

      filtered_tanks = filter(None, aquarium_tanks)
      

      Usamos a função filter() com None e inserimos a lista aquarium_tanks como nosso iterável. Como passamos o None como o primeiro argumento, vamos verificar se os itens em nossa lista são considerados falsos.

      print(list(filtered_tanks))
      

      Então, nós colocamos filtered_tanks em uma função list() para que ela retorne uma lista para filtered_tanks quando imprimirmos.

      Aqui vemos que o resultado mostra apenas os inteiros. Todos os itens que foram avaliados como False, que equivalem a 0 de comprimento, foram removidos por filter():

      Output

      [11, 25, 18, 21, 12, 34]

      Nota: se não usarmos list() e imprimirmos filtered_tanks receberíamos um objeto de filtro parecido com este: <filter object at 0x7fafd5903240>. O objeto de filtro é um iterável, então poderíamos aplicar um loop sobre ele com for ou podemos usar o list() para transformá-lo em uma lista, que é o que estamos fazendo aqui porque é uma boa maneira de revisar os resultados.

      Com o None, usamos filter() para remover itens de nossa lista que foram considerados falsos.

      Quando temos uma estrutura de dados mais complexa, ainda podemos usar filter() para avaliar cada um dos itens. Por exemplo, se tivermos uma lista de dicionários, não só queremos iterar sobre cada item na lista — um dos dicionários — mas também podemos querer iterar sobre cada par de chave:valor em um dicionário para avaliar todos os dados.

      Como um exemplo, vamos considerar que temos uma lista de cada criatura em nosso aquário, juntamente com detalhes específicos sobre cada uma delas:

      aquarium_creatures = [
        {"name": "sammy", "species": "shark", "tank number": "11", "type": "fish"},
        {"name": "ashley", "species": "crab", "tank number": "25", "type": "shellfish"},
        {"name": "jo", "species": "guppy", "tank number": "18", "type": "fish"},
        {"name": "jackie", "species": "lobster", "tank number": "21", "type": "shellfish"},
        {"name": "charlie", "species": "clownfish", "tank number": "12", "type": "fish"},
        {"name": "olly", "species": "green turtle", "tank number": "34", "type": "turtle"}
      ]
      

      Queremos filtrar esses dados por uma string de pesquisa que damos à função. Para que filter() acesse cada dicionário e cada item nos dicionários, construímos uma função aninhada, como a seguinte:

      def filter_set(aquarium_creatures, search_string):
          def iterator_func(x):
              for v in x.values():
                  if search_string in v:
                      return True
              return False
          return filter(iterator_func, aquarium_creatures)
      

      Definimos uma função filter_set() que recebe aquarium_creatures e search_string como parâmetros. Em filter_set(), passamos nossa iterator_func() como a função para filter(). A função filter_set() irá retornar o iterador resultante de filter().

      A iterator_func() recebe x como um argumento, que representa um item em nossa lista (ou seja, um único dicionário).

      Em seguida, o loop for acessa os valores em cada par chave:valor em nossos dicionários e então usa uma declaração condicional para verificar se o search_string está em v, representando um valor.

      Assim como em nossos exemplos anteriores, se a expressão for avaliada como True, a função adiciona o item ao objeto de filtro. Isso será retornado depois que a função filter_set() tiver sido concluída. Posicionamos return False fora do nosso loop para que ele verifique todos os itens em cada dicionário, em vez de retornar depois de verificar somente o primeiro dicionário.

      Chamamos filter_set() com nossa lista de dicionários e a string de pesquisa para a qual queremos encontrar correspondências:

      filtered_records = filter_set(aquarium_creatures, "2")    
      

      Assim que a função tiver sido concluída, temos nosso objeto de filtro armazenado na variável filtered_records, que transformamos em uma lista e imprimimos:

      print(list(filtered_records))      
      

      Veremos o seguinte resultado a partir deste programa:

      Output

      [{'name': 'ashley', 'species': 'crab', 'tank number': '25', 'type': 'shellfish'}, {'name': 'jackie', 'species': 'lobster', 'tank number': '21', 'type': 'shellfish'}, {'name': 'charlie', 'species': 'clownfish', 'tank number': '12', 'type': 'fish'}]

      Filtramos a lista de dicionários com a string de pesquisa 2. Podemos ver que os três dicionários que incluíam um número de tanque com 2 foram retornados. Usar nossa própria função aninhada nos permitiu acessar todos os itens e comparar eficientemente cada um com a string de busca.

      Conclusão

      Neste tutorial, aprendemos as diferentes formas de usar a função filter() em Python. Agora, você é capaz de usar filter() com sua própria função, uma função lambda, ou com None para a filtragem, sendo capaz de lidar com itens de complexidades e em estruturas de dados diferentes.

      Embora neste tutorial tenhamos imprimido os resultados de filter() imediatamente em formato de lista, é provável que em nossos programas usemos o objeto filter() retornado e manipulemos ainda mais os dados.

      Se você quiser aprender mais sobre Python, confira nossa série Como programar em Python 3 e nossa página do tópico Python.



      Source link

      Lidando com a função Panics em Go


      Introdução

      Os erros que um programa encontra se enquadram em duas grandes categorias: a dos erros que o programador previu e a dos que ele não previu. A interface de error – que abordamos nos dois artigos anteriores sobre Como lidar com erros – trata, em grande parte, dos erros esperados, à medida que escrevemos programas em Go. A interface de error nos permite reconhecer a rara possibilidade de um erro ocorrer a partir das chamadas de função, de modo que possamos responder adequadamente nessas situações.

      Panics se enquadra na segunda categoria de erros, os quais não são previstos pelo programador. Esses erros imprevistos levam um programa a fechar espontaneamente e a sair do programa Go em execução. Com frequência, os erros comuns são os responsáveis pela criação de panics. Neste tutorial, examinaremos algumas maneiras como operações comuns podem produzir panics em Go, bem como veremos maneiras de evitar esses panics. Também usaremos as instruções defer junto com a função recover para captar panics, antes que tenham a chance de encerrar inesperadamente nossos programas do Go em execução.

      Entendendo a função Panics

      Existem certas operações em Go que retornam panics automaticamente e interrompem o programa. As operações comuns incluem indexar uma matriz para além de sua capacidade, executar as afirmações de tipo, chamar métodos em ponteiros nil, utilizar incorretamente exclusões mútuas e tentar trabalhar com canais fechados. A maioria destas situações resultam de erros cometidos durante a programação, em que o compilador não tem a capacidade de detectar enquanto compila o seu programa.

      Uma vez que os panics incluem detalhes úteis para resolver um problema, normalmente, os desenvolvedores os utilizam como uma indicação de que cometeram um erro durante o desenvolvimento de um programa.

      Panics fora dos limites

      Ao tentar acessar um índice que vai além do tamanho de uma fatia ou da capacidade de uma matriz, o tempo de execução do Go irá gerar um panic.

      O exemplo a seguir comete o erro comum de tentar acessar o último elemento de uma fatia usando o tamanho da fatia retornada pela função integrada len. Tente executar este código para ver por que isso pode produzir um panic:

      package main
      
      import (
          "fmt"
      )
      
      func main() {
          names := []string{
              "lobster",
              "sea urchin",
              "sea cucumber",
          }
          fmt.Println("My favorite sea creature is:", names[len(names)])
      }
      

      Isso terá o seguinte resultado:

      Output

      panic: runtime error: index out of range [3] with length 3 goroutine 1 [running]: main.main() /tmp/sandbox879828148/prog.go:13 +0x20

      O nome do resultado do panic fornece uma dica: panic: runtime error: index out of range (pânico: erro de tempo de execução: índice fora do intervalo). Criamos uma fatia com três criaturas marinhas. Em seguida, tentamos obter o último elemento da fatia, indexando aquela fatia com o tamanho própria fatia – com a função integrada len. Lembre-se que fatias e matrizes são baseadas em zero; portanto, o primeiro elemento é zero e o último elemento nessa fatia está no índice 2. Considerando que tentamos acessar a fatia no terceiro índice, 3, não há nehum elemento na fatia para retornar porque ele está fora dos limites da fatia. Assim, ao tempo de execução não resta outra opção senão a de encerrar e sair, uma vez que pedimos a ele que fizesse algo impossível. Além disso, durante a compilação, o Go não tem como provar que esse código tentará fazer isso e, consequentemente, o compilador não consegue detectar a ocorrência.

      Note também que o código subsequente não foi executado. Isso acontece porque um panic é um evento que impede completamente a execução do seu programa Go. A mensagem produzida contém vários fragmentos de informações úteis para diagnosticar a causa do panic.

      Anatomia de um panic

      Os panics são compostos por uma mensagem que indica a causa do panic e um rastreamento de pilha que ajuda a localizar onde, em seu código, o panic foi produzido.

      A primeira parte de qualquer panic é a mensagem. Ela começará sempre com a string panic:, seguida de uma string que varia de acordo com a causa do panic. O panic do exercício anterior tem a mensagem:

      panic: runtime error: index out of range [3] with length 3
      

      A string runtime error: depois do prefixo panic: nos diz que o panic foi gerado pelo tempo de execução da linguagem. Esse panic nos diz que tentamos usar um índice [3] que estava fora do alcance do comprimento da fatia 3.

      Após essa mensagem está o rastreamento de pilha. Os rastreamentos de pilha formam um mapa que podemos seguir para localizar exatamente qual linha de código estava executando quando o panic foi gerado e como aquele código foi invocado pelo código anterior.

      goroutine 1 [running]:
      main.main()
          /tmp/sandbox879828148/prog.go:13 +0x20
      

      O rastreamento de pilha do exemplo anterior mostra que nosso programa gerou o panic a partir do arquivo /tmp/sandbox879828148/prog.go,​​​ na linha de número 13. Além disso, ele nos diz que esse panic foi gerado na função main() do pacote main.

      O rastreamento de pilha é dividido em blocos separados — sendo um para cada goroutine de seu programa. Toda execução do programa Go é realizada por uma ou mais goroutines que podem, cada qual – de modo simultâneo e independente, executar partes de seu código Go. Cada block inicia com o cabeçalho goroutine X [state]: O cabeçalho dá o número da ID da goroutine junto com o estado em que ela estava quando o panic ocorreu. Após o cabeçalho, o rastreamento de pilha mostra a função que o programa estava executando quando o panic aconteceu, junto com o nome do arquivo e número da linha onde a função foi executada.

      O panic no exemplo anterior foi gerado por um acesso fora dos limites de uma fatia. Os panics também podem ser gerados quando os métodos são chamados nos ponteiros que não foram definidos.

      Receptores nulos

      A linguagem de programação Go tem ponteiros para se referir à instância específica de algum tipo existente na memória do computador em tempo de execução. Os ponteiros podem assumir o valor nil, o que indica que não estão apontando para nada. Quando tentarmos chamar métodos em um ponteiro que é nil, o tempo de execução do Go irá gerar um panic. De igual modo, as variáveis que são tipos de interface também produzirão panics quando os métodos forem chamados neles. Para ver os panics gerados nesses casos, teste o seguinte exemplo:

      package main
      
      import (
          "fmt"
      )
      
      type Shark struct {
          Name string
      }
      
      func (s *Shark) SayHello() {
          fmt.Println("Hi! My name is", s.Name)
      }
      
      func main() {
          s := &Shark{"Sammy"}
          s = nil
          s.SayHello()
      }
      

      Os panics produzidos se parecerão com isto:

      Output

      panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xdfeba] goroutine 1 [running]: main.(*Shark).SayHello(...) /tmp/sandbox160713813/prog.go:12 main.main() /tmp/sandbox160713813/prog.go:18 +0x1a

      Neste exemplo, definimos um struct chamado Shark. O Shark tem um método definido em seu ponteiro receptor chamado SayHello, que irá imprimir uma saudação para a saída padrão quando chamado. Dentro do corpo da nossa função main, criamos uma nova instância desse struct Shark e pedimos um ponteiro a ela, usando o operador &. Esse ponteiro está atribuído à variável s. Em seguida, vamos reatribuir a variável s para o valor nil com a instrução s = nil. Por fim, tentamos chamar o método SayHello na variável s. Em vez de receber uma mensagem amigável do Sammy, recebemos um panic de que tentamos acessar um endereço de memória inválido. Como a variável s é nil, quando a função SayHello é chamada, ela tenta acessar o campo Name no tipo *Shark. Como se trata de um ponteiro receptor e, neste caso, de um receptor que é nil, ele entra em pânico, uma vez que não consegue desreferenciar um ponteiro nil.

      A despeito de termos definido s para nil de maneira explícita neste exemplo, na prática isso ocorre não ocorre de modo tão claro. Ao ver panics envolvendo nil pointer dereference, certifique-se de ter atribuído devidamente quaisquer variáveis de ponteiros que possa ter criado.

      Panics gerados a partir de ponteiros nulos e de acessos fora dos limites são dois panics de ocorrência comum, sendo gerados pelo tempo de execução. Também é possível gerar um panic manualmente, usando uma função integrada.

      Usando o função integrada panic

      Também podemos gerar nossos próprios panics usando a função integrada panic. Essa função utiliza uma string única como argumento, que é a mensagem que o panic produzirá. Normalmente, essa mensagem resulta menos prolixa do que reescrever o código para retornar um erro. Além disso, podemos usar isso dentro dos nossos próprios pacotes para indicar aos desenvolvedores que eles podem ter cometido um erro ao usar o código do nosso pacote. Sempre que possível, o melhor a se fazer é tentar retornar valores de error para os consumidores de nossos pacotes.

      Execute este código para ver um panic gerado a partir de uma função chamada de outra função:

      package main
      
      func main() {
          foo()
      }
      
      func foo() {
          panic("oh no!")
      }
      

      O resultado do panic produzido fica parecido com este:

      Output

      panic: oh no! goroutine 1 [running]: main.foo(...) /tmp/sandbox494710869/prog.go:8 main.main() /tmp/sandbox494710869/prog.go:4 +0x40

      Aqui, definimos uma função foo que chama o panic integrado com a string "oh no!". Essa função é chamada por nossa função main. Observe que o resultado tem a mensagem panic: oh no! e o rastreamento de pilha mostra uma goroutine única com duas linhas no rastreamento de pilha: uma para a função main() e outra para nossa função foo().

      Vimos que os panics parecem encerrar nosso programa no local em que foram gerados. Isso pode criar problemas quando houver recursos abertos que precisam ser fechados de maneira adequada. O Go fornece um mecanismo para sempre executar alguns códigos, mesmo na presença de um panic.

      Funções adiadas

      Seu programa pode ter recursos que ele deve limpar de maneira adequada, mesmo enquanto um panic estiver sendo processado em tempo de execução. O Go permite que você adie a execução de uma chamada de função até que sua chamada tenha concluído a execução. As funções adiadas são executadas mesmo na presença de um panic. Elas são usadas como um mecanismo de segurança para proteger o programa da natureza caótica dos panics. As funções são adiadas quando as chamamos como de costume e depois fazemos a prefixação da instrução toda, usando a palavra-chave defer, como em defer sayHello(). Execute este exemplo para ver como uma mensagem pode ser impressa, ainda que um panic tenha sido produzido:

      package main
      
      import "fmt"
      
      func main() {
          defer func() {
              fmt.Println("hello from the deferred function!")
          }()
      
          panic("oh no!")
      }
      

      O resultado produzido a partir desse exemplo se parecerá com:

      Output

      hello from the deferred function! panic: oh no! goroutine 1 [running]: main.main() /Users/gopherguides/learn/src/github.com/gopherguides/learn//handle-panics/src/main.go:10 +0x55

      Dentro da função main desse exemplo, fazemos primeiro o defer (adiamos) de uma chamada para uma função anônima que imprime a mensagem "hello from the deferred function!". Na sequência, a função main produz imediatamente um panic usando a função panic. No resultado desse programa, vemos primeiro que a função adiada é executada e imprime sua mensagem. Depois disso, vemos o panic que geramos na função main.

      As funções adiadas proporcionam proteção contra a natureza surpreendente dos panics. Dentro das funções adiadas, o linguagem de programação Go também nos dá a oportunidade de impedir um panic de encerrar nosso programa Go, usando outra função integrada.

      Os panics possuem um mecanismo de recuperação único — a função integrada recover. Essa função permite que você intercepte um panic em seu caminho até a pilha de chamadas e impeça-o de encerrar inesperadamente o seu programa. Ela possui regras estritas de uso, mas pode ser algo inestimável em um aplicativo de produção.

      Como ela faz parte do pacote builtin, a função recover pode ser chamada sem importar nenhum pacote adicional:

      package main
      
      import (
          "fmt"
          "log"
      )
      
      func main() {
          divideByZero()
          fmt.Println("we survived dividing by zero!")
      
      }
      
      func divideByZero() {
          defer func() {
              if err := recover(); err != nil {
                  log.Println("panic occurred:", err)
              }
          }()
          fmt.Println(divide(1, 0))
      }
      
      func divide(a, b int) int {
          return a / b
      }
      

      Este exemplo produzirá o seguinte:

      Output

      2009/11/10 23:00:00 panic occurred: runtime error: integer divide by zero we survived dividing by zero!

      Nesse exemplo, nossa função main chama uma função que definimos, a divideByZero. Dentro dessa função, usamos a função defer para adiar uma chamada para uma função anônima – responsável por lidar com quaisquer panics que possam surgir durante o execução da função divideByZero. Dentro dessa função anônima adiada, chamamos a função integrada recover (recuperar) e atribuímos o erro que ela retorna para uma variável. Se o divideByZero entrar em pânico, esse valor de error será definido, caso contrário, ele será nil (nulo ou inválido). Ao comparar a variável err com a nil, podemos detectar se um panic ocorreu, e neste caso, registramos o evento de panic usando a função log.Println – como se fosse qualquer outro error.

      Depois dessa função anônima adiada, chamados outra função que definimos – a divide – e tentamos imprimir seus resultados usando fmt.Println. Os argumentos fornecidos farão a divide executar uma divisão por zero, o que produzirá um panic.

      No resultado para esse exemplo, vemos primeiro a mensagem de registro da função anônima que recupera o panic, seguida da mensagem we survived dividing by zero! (sobrevivemos à divisão por zero!). Nós fizemos isso, graças à função integrada recover que impediu que um panic catastrófico fechasse nosso programa em Go.

      O valor de err retornado do recover() é exatamente o valor que foi fornecido à chamada para o panic(). Assim, é crucial garantir que o valor de err somente seja nil na ausência de um panic.

      A função recover depende do valor do erro para fazer determinações quanto a se um panic ocorreu ou não. Como o argumento para a função panic é uma interface vazia, ele pode ser de qualquer tipo. O valor zero para qualquer tipo de interface, incluindo a interface vazia, é nil. Tome cuidado para evitar o nil como um argumento para panic, como mostrado por este exemplo:

      package main
      
      import (
          "fmt"
          "log"
      )
      
      func main() {
          divideByZero()
          fmt.Println("we survived dividing by zero!")
      
      }
      
      func divideByZero() {
          defer func() {
              if err := recover(); err != nil {
                  log.Println("panic occurred:", err)
              }
          }()
          fmt.Println(divide(1, 0))
      }
      
      func divide(a, b int) int {
          if b == 0 {
              panic(nil)
          }
          return a / b
      }
      
      

      Isso resultará em:

      Output

      we survived dividing by zero!

      Esse exemplo é o mesmo que o exemplo anterior, que envolve o recover com algumas pequenas alterações. A função divide foi alterada para verificar se seu divisor b é igual a 0. Caso seja, ele irá gerar um panic usando a função panic integrada com um argumento de nil. O resultado, dessa vez, não inclui a mensagem de registro mostrando que um panic ocorreu mesmo que um tenha sido criado pelo divide. Esse comportamento silencioso acontece porque é muito importante garantir que o argumento para a função integrada panic não seja nil.

      Conclusão

      Vimos várias maneiras para a criação de panics em Go e como eles podem ser recuperados usando a função recover integrada. Ainda que você não faça, necessariamente, uso da função de panic, propriamente dita, a recuperação adequada dos eventos de panics é um passo importante para deixar os aplicativos em Go prontos para a produção.

      Você também pode explorar nossa série de artigos sobre Como codificar em Go.



      Source link