Prévia do material em texto
Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 1 SUMÁRIO Introdução .................................................................................................................................................. 9 Classificação das linguagens de programação ..................................................................................... 9 Razões para estudarmos linguagens de programação .............................................................................. 9 Linguagem de programação e produtividade do programador ............................................................... 9 O papel da abstração nas linguagens de programação ....................................................................... 10 Classificação das linguagens de programação ........................................................................................ 12 Classificação por nível.......................................................................................................................... 12 Classificação por gerações ...................................................................................................................... 14 Linguagens de 1ª geração (linguagem de máquina) ............................................................................ 14 Linguagens de 2ª geração (linguagem de montagem – assembly) ...................................................... 14 Linguagens de 3ª geração (linguagens procedurais) ........................................................................... 15 Linguagens de 4ª geração (linguagens aplicativas) ............................................................................. 16 Linguagens de 5ª geração (voltadas à inteligência artificial) ................................................................ 16 Verificando o aprendizado ........................................................................................................................ 16 Critérios de avaliação de linguagens de programação ................................................................... 17 Domínios da programação ....................................................................................................................... 17 Aplicações científicas (máquinas de calcular com alta precisão) ......................................................... 17 Aplicações comerciais .......................................................................................................................... 18 Aplicações com inteligência artificial .................................................................................................... 18 Programação de sistemas .................................................................................................................... 18 Programação para web ........................................................................................................................ 18 Programação mobile ............................................................................................................................ 19 Avaliação de linguagens de programação ............................................................................................... 19 Legibilidade .............................................................................................................................................. 20 Simplicidade ......................................................................................................................................... 20 Ortogonalidade ..................................................................................................................................... 20 Instruções de controle .......................................................................................................................... 21 Tipos e estruturas de dados ................................................................................................................. 21 Sintaxe ................................................................................................................................................. 21 Facilidade de escrita (redigibilidade) ........................................................................................................ 21 Simplicidade e ortogonalidade ............................................................................................................. 21 Expressividade ..................................................................................................................................... 22 Suporte para a abstração ..................................................................................................................... 22 Confiabilidade........................................................................................................................................... 22 Verificação de tipos .............................................................................................................................. 22 Tratamento de exceção ........................................................................................................................ 22 Aliasing (apelidos) ................................................................................................................................ 22 Legibilidade e facilidade de escrita ....................................................................................................... 23 Custo ........................................................................................................................................................ 23 Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 2 Verificando o aprendizado ........................................................................................................................ 23 Os paradigmas e suas características ............................................................................................. 24 Paradigma imperativo .............................................................................................................................. 24 Paradigma estruturado ......................................................................................................................... 24 Paradigma orientado a objetos ............................................................................................................. 25 Paradigma concorrente ........................................................................................................................ 25 Paradigma declarativo .............................................................................................................................. 26 Paradigma funcional ............................................................................................................................. 26 Paradigma lógico .................................................................................................................................. 27 Verificando o aprendizado ........................................................................................................................ 27 Métodos de implementação das linguagens ................................................................................... 27 Tradução .................................................................................................................................................. 28 Compilador ........................................................................................................................................... 28 Interpretação ............................................................................................................................................ 30 Principais características do interpretador............................................................................................31 Tradução x interpretação...................................................................................................................... 31 Sistemas híbridos ..................................................................................................................................... 32 Sistema de implementação de Python ................................................................................................. 32 Verificando o aprendizado ........................................................................................................................ 32 Considerações finais ................................................................................................................................ 33 Referências .............................................................................................................................................. 33 Explore+ ................................................................................................................................................... 33 Python Básico ...................................................................................................................................... 34 Introdução ................................................................................................................................................ 34 Linguagem Python e suas principais características ...................................................................... 34 Conceitos ................................................................................................................................................. 34 Características da linguagem Python ....................................................................................................... 34 Instalação ................................................................................................................................................. 36 Utilitários e módulos ................................................................................................................................. 36 Blocos ...................................................................................................................................................... 36 Comentários ............................................................................................................................................. 37 Boas práticas de programação................................................................................................................. 37 Verificando o aprendizado ........................................................................................................................ 38 Uso de variáveis em Python ............................................................................................................ 38 Conceitos ................................................................................................................................................. 38 Identificadores de variáveis ...................................................................................................................... 39 Amarrações .............................................................................................................................................. 40 Amarração de tipo ................................................................................................................................ 41 Escopo de visibilidade .............................................................................................................................. 41 Variáveis globais .................................................................................................................................. 41 Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 3 Variáveis locais .................................................................................................................................... 42 Tipos de escopo ................................................................................................................................... 43 Tempo de vida.......................................................................................................................................... 44 Constantes ............................................................................................................................................... 44 Verificando o aprendizado ........................................................................................................................ 44 Tipos de dados e as expressões em Python ................................................................................... 45 Conceitos ................................................................................................................................................. 45 Tipos numéricos ....................................................................................................................................... 45 O tipo int ............................................................................................................................................... 45 O tipo float ............................................................................................................................................ 46 O tipo complex ..................................................................................................................................... 48 O tipo bool ............................................................................................................................................ 49 Operadores numéricos ............................................................................................................................. 50 Operadores matemáticos ..................................................................................................................... 50 Operadores booleanos ......................................................................................................................... 51 Tipos sequenciais ..................................................................................................................................... 51 Strings .................................................................................................................................................. 52 Listas .................................................................................................................................................... 53 Tuplas................................................................................................................................................... 53 Range ................................................................................................................................................... 53 Operadores sequenciais comuns ......................................................................................................... 54 Dicionários................................................................................................................................................ 54 Precedência de operadores ..................................................................................................................... 55 Conversões de tipos ................................................................................................................................. 55 Verificando o aprendizado ........................................................................................................................ 57 Formas de atribuição, de entrada e saída de dados em Python .................................................... 57 Conceitos .................................................................................................................................................57 Sentenças de atribuição ........................................................................................................................... 57 Atribuição simples ................................................................................................................................ 57 Atribuição múltipla ................................................................................................................................ 57 Operadores de atribuição compostos ................................................................................................... 58 Troca de variáveis ................................................................................................................................ 59 O primeiro programa em python............................................................................................................... 60 Saída de dados com a função print() ................................................................................................... 61 Entrada de dados com a função input() ................................................................................................... 62 A função eval() ..................................................................................................................................... 64 Mão na massa .......................................................................................................................................... 65 Saída formatada de dados ....................................................................................................................... 65 Impressão de sequências..................................................................................................................... 67 Verificando o aprendizado ........................................................................................................................ 68 Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 4 Considerações finais ................................................................................................................................ 69 Referências .............................................................................................................................................. 69 Explore+ ................................................................................................................................................... 69 Python Estruturado .............................................................................................................................. 70 Introdução ................................................................................................................................................ 70 Estruturas de decisão e repetição em Python ................................................................................. 70 Tratamento das condições ....................................................................................................................... 70 As Estruturas de decisão If, If-Else E Elif ................................................................................................. 70 Estrutura de repetição for ......................................................................................................................... 71 As listas do tipo range() ........................................................................................................................ 71 A sintaxe da estrutura for ..................................................................................................................... 72 O laço for com uma string .................................................................................................................... 72 Uso do laço for com qualquer sequência ............................................................................................. 72 Estrutura de repetição while ..................................................................................................................... 73 O laço while infinito .............................................................................................................................. 74 As instruções auxiliares break, continue e pass ....................................................................................... 74 A instrução break ................................................................................................................................. 74 A instrução continue ............................................................................................................................. 74 A instrução pass ....................................................................................................................................... 75 Verificando o aprendizado ........................................................................................................................ 75 Principais conceitos de subprogramas e a sua utilização em Python ............................................ 76 Características gerais dos subprogramas ................................................................................................ 76 Definições básicas ................................................................................................................................... 76 Parâmetros ............................................................................................................................................... 77 Procedimentos e funções ......................................................................................................................... 78 Ambientes de referenciamento local ........................................................................................................ 78 Variáveis locais .................................................................................................................................... 78 Subprogramas aninhados .................................................................................................................... 80 Métodos de passagens de parâmetros .................................................................................................... 80 Recursividade........................................................................................................................................... 80 A função recursiva fatorial .................................................................................................................... 81 A sequência de Fibonacci .................................................................................................................... 82 Docstrings ................................................................................................................................................ 82 Verificando o aprendizado ........................................................................................................................ 83 Uso correto de recursos de bibliotecas em Python ......................................................................... 83 Biblioteca padrão python .......................................................................................................................... 83 Como usar uma função de módulo importado.......................................................................................... 84 Módulo math............................................................................................................................................. 84 Módulo random ........................................................................................................................................ 84 Distribuições de valores reais ............................................................................................................... 85 Apostila de Paradigmas de Linguagensde Programação em Python Marcio Quirino - 5 Funções para números inteiros ............................................................................................................ 85 Funções para sequências .................................................................................................................... 85 Módulo smtplib ......................................................................................................................................... 85 Módulo time .............................................................................................................................................. 86 Módulo tkinter ........................................................................................................................................... 87 Usando pacotes externos ......................................................................................................................... 89 Criação do próprio módulo ....................................................................................................................... 91 Verificando o aprendizado ........................................................................................................................ 91 Formas de tratamento de exceções e eventos em Python ............................................................. 92 Erros e exceções ...................................................................................................................................... 92 Captura e manipulação de exceções ....................................................................................................... 93 Captura de exceções de determinado tipo ........................................................................................... 94 Captura de exceções de múltiplos tipos ............................................................................................... 94 O tratamento completo das exceções .................................................................................................. 94 Tratamento de eventos ............................................................................................................................ 94 Verificando o aprendizado ........................................................................................................................ 95 Considerações finais ................................................................................................................................ 95 Referências .............................................................................................................................................. 95 Explore+ ................................................................................................................................................... 95 Python Orientado a Objetos ................................................................................................................ 96 Definição .................................................................................................................................................. 96 Propósito .................................................................................................................................................. 96 Preparação ............................................................................................................................................... 96 Introdução ................................................................................................................................................ 96 Conceitos gerais da orientação a objetos ........................................................................................ 96 Conceitos de POO - programação orientada a objetos ............................................................................ 96 Pilares da orientação a objetos ................................................................................................................ 97 Objetos ................................................................................................................................................. 97 Atributos ............................................................................................................................................... 97 Operações ............................................................................................................................................ 97 Classes................................................................................................................................................. 98 Encapsulamento ....................................................................................................................................... 99 Herança .................................................................................................................................................... 99 Herança simples ................................................................................................................................. 100 Herança múltipla ................................................................................................................................ 101 Polimorfismo........................................................................................................................................... 101 Verificando o aprendizado ...................................................................................................................... 102 Conceitos básicos da programação orientada a objetos na linguagem Python ........................... 103 Classes e objetos em Python ................................................................................................................. 103 Definição de classe ................................................................................................................................ 103 Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 6 Construtores e self ............................................................................................................................. 103 Métodos.............................................................................................................................................. 104 Métodos com retorno.......................................................................................................................... 105 Referências entre objetos na memória ............................................................................................... 105 Agregação .............................................................................................................................................. 107 Composição ........................................................................................................................................... 108 Encapsulamento ..................................................................................................................................... 110 Atributos públicos e privados .............................................................................................................. 110 Decorator @property .......................................................................................................................... 111 Atributos de classe ............................................................................................................................. 111 Métodos de classe ............................................................................................................................. 112 Métodos públicos e privados .............................................................................................................. 113 Métodos estáticos ..............................................................................................................................113 Verificando o aprendizado ...................................................................................................................... 114 Conceitos da orientação a objetos como Herança e Polimorfismo .............................................. 114 Herança e polimorfismo ......................................................................................................................... 114 Herança .................................................................................................................................................. 114 Herança Múltipla ................................................................................................................................ 116 Polimorfismo........................................................................................................................................... 118 Classes abstratas ................................................................................................................................... 120 Exceções ................................................................................................................................................ 121 Verificando o aprendizado ...................................................................................................................... 121 OO aplicados a Python com outras linguagens OO existentes no mercado ................................ 122 Linguagem de programação orientadas a objetos ................................................................................. 122 Comparação com C++ e Java ................................................................................................................ 122 Instanciação de objetos ...................................................................................................................... 122 Construtores de objetos ..................................................................................................................... 122 Modificadores de acesso atributos ..................................................................................................... 123 Herança múltipla de classes................................................................................................................... 123 Classes sem definição formal................................................................................................................. 123 Tipos primitivos ...................................................................................................................................... 123 Interfaces ............................................................................................................................................... 124 Sobrecarga de métodos ......................................................................................................................... 124 Tabela Comparativa ........................................................................................................................... 124 Verificando o aprendizado ...................................................................................................................... 124 Considerações finais .............................................................................................................................. 125 Referências ............................................................................................................................................ 125 Explore+ ................................................................................................................................................. 126 Python em Outros Paradigmas .......................................................................................................... 127 Introdução .............................................................................................................................................. 127 Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 7 Linguagem funcional e sua utilização em Python ......................................................................... 127 Introdução .............................................................................................................................................. 127 Funções puras........................................................................................................................................ 128 Dados imutáveis ..................................................................................................................................... 128 Efeito colateral e estado da aplicação .................................................................................................... 129 Funções de ordem superior.................................................................................................................... 130 Funções lambda ..................................................................................................................................... 131 Não utilizar loops .................................................................................................................................... 132 Map ........................................................................................................................................................ 132 Filter ....................................................................................................................................................... 133 Script FUNÇÃO_FILTRO_ITERABLE.PY .......................................................................................... 134 Script FUNCAO_FILTER.PY .............................................................................................................. 134 Script, FUNÇÃO_FILTER_LAMBDA.PY ............................................................................................ 134 Verificando o aprendizado ...................................................................................................................... 134 Computação concorrente e sua utilização em Python .................................................................. 135 Introdução .............................................................................................................................................. 135 Conceitos ............................................................................................................................................... 135 Programas e processos...................................................................................................................... 135 Concorrência e paralelismo ................................................................................................................ 135 Threads e processos .......................................................................................................................... 136 Criação de threads e processos ......................................................................................................... 137 Múltiplas threads e processos ............................................................................................................ 137 Travas (Lock) ..................................................................................................................................... 139 Compartilhando variáveis entre processos......................................................................................... 142 Verificando o aprendizado ...................................................................................................................... 143 Python como ferramenta para desenvolvimento web ................................................................... 144 Introdução ..............................................................................................................................................144 Frameworks Full-Stack ....................................................................................................................... 144 Frameworks não Full-Stack ................................................................................................................ 145 Conceitos ............................................................................................................................................... 145 Aprimorando rotas .............................................................................................................................. 146 Recebendo parâmetros ...................................................................................................................... 147 Métodos HTTP ................................................................................................................................... 149 Utilizando modelos ............................................................................................................................. 150 Verificando o aprendizado ...................................................................................................................... 153 Python como ferramenta para ciência de dados ........................................................................... 154 Introdução .............................................................................................................................................. 154 Conceitos ............................................................................................................................................... 156 Supervisionado – regressão linear ..................................................................................................... 156 Supervisionado ‒ classificação .......................................................................................................... 158 Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 8 Não supervisionado ‒ agrupamento ................................................................................................... 162 Verificando o aprendizado ...................................................................................................................... 163 Considerações finais .............................................................................................................................. 163 Referências ............................................................................................................................................ 164 Explore+ ................................................................................................................................................. 164 Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 9 Paradigmas de Linguagens de Programação em Python Introdução Desde o surgimento dos computadores, centenas de linguagens de programação vêm sendo criadas com o objetivo de permitir ao programador mais eficiência e conforto ao escrever seus códigos. Muitos são os problemas que hoje em dia são solucionados com a ajuda de softwares, escritos sempre em uma linguagem de programação (que por sua vez também é um software) e muitas vezes esses problemas demandam diferentes pensamentos computacionais em sua solução. A classificação das linguagens em paradigmas permite que entendamos qual é o melhor deles para solucionar determinado problema e, a partir daí, escolher a linguagem de programação (pertencente a esse paradigma) mais adequada, conforme características e especificidades do contexto em que se aplica o problema. A linguagem Python foi escolhida como instrumento para o desenvolvimento desta disciplina, pois além de ser multiparadigma (possibilita escrever programas em diferentes paradigmas) e de uso geral, vem se destacando e sendo cada vez mais utilizada entre os novos desenvolvedores por vários motivos: Facilidade de aprendizado; Boa legibilidade de código; Boa facilidade de escrita; Produtividade e confiabilidade. Possui, ainda, comunidade de desenvolvedores crescente e vasta biblioteca, repleta de funções, aplicada a diversas áreas da ciência, assim como o crescente números de frameworks desenvolvidos para a linguagem. Classificação das linguagens de programação Razões para estudarmos linguagens de programação Linguagem de programação e produtividade do programador Um programa de computador, ou software, é um conjunto de instruções a fim de orientar o hardware do computador para o que deve ser feito. O software pode ser classificado em aplicativo ou básico. Software aplicativo Ou, simplesmente, aplicativo ou app (mundo mobile), que visa oferecer ao usuário facilidades para realizar uma tarefa laboral ou de lazer. Software básico Compreende programas essenciais ao funcionamento do computador. O sistema operacional é o principal exemplo. Uma linguagem de programação é um software básico, que permite ao programador escrever outros programas de computador, seja ela um software aplicativo ou básico. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 10 A codificação de um programa em uma linguagem de programação, chama-se programa-fonte, que ainda não pode ser entendido e executado pelo hardware do computador, pois este apenas entende a linguagem de máquina ou linguagem binária, na qual cada instrução é uma sequência de bits (0 ou 1). Uma linguagem de programação é um formalismo com um conjunto de símbolos, palavras reservadas, comandos, regras sintáticas e semânticas e outros recursos, que permitem especificar instruções de um programa. Atenção As linguagens de programação surgiram da necessidade de livrar o programador dos detalhes mais íntimos das máquinas em que a programação é feita, permitindo a programação em termos mais próximos ao problema, ou em nível mais alto. A produtividade de um programador ao escrever código em uma linguagem de programação está intimamente relacionada à facilidade de aprendizado, leitura e escrita de programas naquela linguagem, somada a expertise do programador no contato com a linguagem. Isto é, quanto mais o programador conhecer as propriedades superlativas daquela linguagem, melhores e mais eficientes serão os códigos escritos. O aspecto mais importante, mas também o mais elusivo de qualquer ferramenta, é sua influência nos hábitos daqueles que se treinam no seu uso. Se a ferramenta é uma linguagem de programação, essa influência é, gostemos ou não, uma influência em nosso hábito de pensar. Edsger W. Dijkstra O papel da abstração nas linguagens de programação Abstração é o processo de identificação das qualidades e/ou propriedades relevantes para o contexto que está sendo analisado e desprezando o que seja irrelevante. Um modelo é uma abstração da realidade. Um programa de computador é um modelo, pois representa a solução de um problema em termos algorítmicos. Assim sendo, a abstração permeia toda a atividade de programação de computadores. A linguagem de máquina foi a primeira a ser criada para a prática de programação. Trata-se da linguagem nativa do computador, a única que ele, de fato, compreende. Uma linguagem muito complicada para ser entendida pelas pessoas, em que um comando que soma 2 números, é formado por uma sequência de 1 e 0, muito difícil de ser memorizada, usada e, mais ainda, de ser entendida por terceiros. As primeiras linguagens de programação, porém, não reconheciam o papel crucial que a abstração desempenha na programação. Por exemplo, no início da década de 1950, o único mecanismo de abstração fornecido pela linguagem de montagem, ou Assembly, em relação às linguagens de máquina eram os nomes simbólicos. Você sabia O programador podia empregar termos relativamente autoexplicativos (nomes simbólicos) para nomear códigos de operação (ADD = soma,SUB = subtração, M = multiplicação e DIV = divisão) e posições de memória. A linguagem de montagem (Assembly) melhorou a vida do programador, porém obrigava-o a escrever 1 linha de código para cada instrução que a máquina deve executar, forçando-o a pensar como se fosse uma máquina. Um pouco mais adiante, visando a aumentar o poder de abstração das linguagens de forma a permitir uma melhor performance dos programadores, surgem as linguagens de alto nível, próximas à linguagem humana e mais distantes das linguagens Assembly e de máquina. A tabela, a seguir, exibe, à esquerda, um programa-fonte, escrito numa linguagem de alto nível, a linguagem Python. Ao centro, temos o código equivalente na linguagem Assembly para o sistema operacional Linux e, à direita, o respectivo código na linguagem de máquina, de um determinado processador. Observe: Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 11 Fonte: Estácio de Sá A imagem abaixo ilustra o conceito de abstração, em que a partir da linguagem de máquina, cria-se camadas (de abstração) para facilitar a vida do programador. Fonte: Estácio de Sá Nível 1 É representado pelo hardware, conjunto de circuitos eletrônicos. Nível 2 É representado pela linguagem de máquina (1 e 0), única que o hardware entende. Nível 3 É representado pela linguagem Assembly (mneumônicos). Nível 4 É representado pelas linguagens de alto nível, próximas à língua do usuário e distantes da linguagem computacional. Python e Java são linguagens de programação representativas da classe LP de alto nível (LP = Linguagem de Programação). Por que estudar linguagens de programação? O estudante e/ou programador que se dispuser a gastar seu tempo aprendendo linguagens de programação terá as seguintes vantagens: Maior capacidade de desenvolver soluções em termos de programas — compreender bem os conceitos de uma LP pode aumentar a habilidade dos programadores para pensar e estruturar a solução de um problema. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 12 Maior habilidade em programar numa linguagem, conhecendo melhor suas funcionalidades e implementações, ajuda para que o programador possa construir programas melhores e mais eficientes. Por exemplo, conhecendo como as LPs são implementadas, podemos entender melhor o contexto e decidir entre usar ou não a recursividade, que se mostra menos eficiente que soluções iterativas. Maiores chances de acerto na escolha da linguagem mais adequada ao tipo de problema em questão, quando se conhece os recursos e como a linguagem os implementa. Por exemplo, saber que a linguagem C não verifica, dinamicamente, os índices de acesso a posições de vetores pode ser decisivo para sua escolha em soluções que usem frequentemente acessos a vetores. Maior habilidade para aprender novas linguagens. Quem domina os conceitos da orientação a objeto, tem mais aptidão para aprender Python, C++, C# e Java. Amplo conhecimento dos recursos da LP reduz as limitações na programação. Maior probabilidade para projetar novas LP, aos que se interessarem por esse caminho profissional: participar de projetos de criação de linguagens de programação. Aumento da capacidade dos programadores em expressar ideias. Em geral, um programador tem expertise em poucas variedades de linguagens de programação, dependendo do seu nicho de trabalho. Isso, de certa forma, limita sua capacidade de pensar, pois ele fica restrito pelas estruturas de dados e controle que a(s) linguagem(ns) de seu dia a dia permitem. Conhecer uma variedade maior de recursos das linguagens de programação pode reduzir tais limitações, levando, ainda, os programadores a aumentar a diversidade de seus processos mentais. Quanto maior for o leque de linguagens que um programador dominar e praticar, maiores as chances de conhecer e fazer uso das propriedades superlativas da(s) linguagem(ns) em questão. Classificação das linguagens de programação Ao longo dos anos, os autores têm criado diferentes classificações para as linguagens de programação, usando critérios diferenciados e agrupando-as sob diferentes perspectivas. Veremos a seguir as classificações das linguagens por nível, por gerações e por paradigmas. Classificação por nível A classificação por nível considera a proximidade da linguagem de programação com as características da arquitetura do computador ou com a comunicação com o homem. Linguagem de baixo nível São linguagens que se aproximam da linguagem de máquina, além da própria, que se comunicam diretamente com os componentes de hardware, como processador, memória e registradores. As linguagens de baixo nível estão relacionadas à arquitetura de um computador. São linguagens escritas usando o conjunto de instruções do respectivo processador. Ou seja, cada processador diferente (ou família de processador, como os I3, I5 e I7 da Intel) tem um conjunto de instruções específicos (instructions set). Abaixo, a imagem ilustra a representação de uma instrução em linguagem de máquina ou binária de um processador específico. A instrução tem palavras (unidade executada pelo processador) de 16 bits, sendo 4 bits para representar a instrução (código da instrução), 6 bits para representar cada operando. Fonte: Estácio de Sá Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 13 Imagine, agora, uma sequência de 0 e 1 para que possamos dizer ao processador cada ação que deve ser realizada conforme ilustrado abaixo. 0001001010001111 1010010001000010 0010101110110111 0101010000000111 Era de fato muito complexa a programação na linguagem de máquina, a linguagem nativa dos processadores. Essa complexidade motivou o desenvolvimento da linguagem Assembly, que deixava de ser a linguagem nativa dos processadores, mas usava das instruções reais dos processadores. Assim, a instrução na linguagem Assembly precisa ser convertida para o código equivalente em linguagem de máquina. Exemplo As três linhas de código na linguagem Assembly, abaixo, que move o numeral 2 para o registrador AX (linha 1), move o numeral 1 para o registrador BX (linha 2) e soma o conteúdo dos 2 registradores (linha 3). MOV AX, 0002 MOV BX, 0001 ADD AX, BX Não chega a ser o ideal em termos de uma linguagem, que é ainda próxima da máquina, mas já foi um grande avanço em relação à memorização da sequência de 0 e 1 de uma instrução de máquina. Linguagens de baixo nível estão distantes da língua humana (escrita). Linguagem de alto nível No outro extremo das linguagens de baixo nível, estão as linguagens de alto nível, na medida em que se afastam da linguagem das máquinas e se aproximam da linguagem humana (no caso, a linguagem escrita e a grande maioria em Inglês). Você sabia Quem programa em uma linguagem de alto nível não precisa conhecer características dos componentes do hardware (processador, instruções e registradores). Isso é abstraído no pensamento computacional. As instruções das linguagens de alto nível são bastante abstratas e não estão relacionadas à arquitetura do computador diretamente. As principais linguagens são: ASP, C, C++, C#, Pascal, Delphi, Java, Javascript, Lua, MATLAB, PHP e Ruby, dentre outras. Exemplo Abaixo, o mesmo código expresso acima, escrito em Assembly, porém usando variáveis, como abstração do armazenamento e codificado na linguagem Python. def main(): num1 = 2 num2 = 1 soma = num1 + num2 Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 14 Abaixo, o mesmo código na linguagem C: int num1, num2, soma; int main() { num1=2; num1=1; soma=num1+num2; } Cada comando de uma linguagem de alto nível precisa ser convertido e equivalerá a mais de uma instrução primária do hardware. Isso significa que, numa linguagem de alto nível, o programador precisaescrever menos código para realizar as mesmas ações, além de outras vantagens, aumentando consideravelmente a sua eficiência ao programar. Saiba mais Há uma curiosidade: C e C++ são classificados por alguns autores como linguagem de médio nível, na medida que estão próximas da linguagem humana (linguagem de alto nível), mas também estão próximas da máquina (linguagem de baixo nível), pois possuem instruções que acessam diretamente memória e registradores. Inicialmente, a linguagem C foi criada para desenvolver o sistema operacional UNIX, que até então era escrito em Assembly. Outro dado que merece ser comentado é que algumas pessoas consideram a existência de linguagens de altíssimo nível, como Python, Ruby e Elixir, por serem linguagens com uma enorme biblioteca de funções e que permitem a programação para iniciantes sem muito esforço de aprendizado. Classificação por gerações Outra forma de classificar as linguagens, amplamente difundida, é por gerações. Não há um consenso sobre as gerações, alguns consideram 5, outros 6. A cada geração, novos recursos facilitadores são embutidos nas respectivas linguagens. Fonte: Estácio de Sá Veja um pouco mais sobre as gerações: Linguagens de 1ª geração (linguagem de máquina) A 1ª geração de linguagens é representa pela linguagem de máquina, nativa dos processadores. Linguagens de 2ª geração (linguagem de montagem – assembly) As linguagens de segunda geração são denominadas Assembly e são traduzidas para a linguagem de máquina por um programa especial (montador), chamado Assembler. A partir dessa geração, toda linguagem vai precisar de um processo de conversão do código nela escrito, para o código em linguagem de máquina. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 15 Acompanhe o exemplo abaixo para uma CPU abstrata. Considere a seguinte sequência de 3 instruções em linguagem Assembly: Fonte: Estácio de Sá Em linguagem de máquina, depois de traduzidas pelo Assembler, as instruções poderiam ser representadas pelas seguintes sequências de palavras binárias: Fonte: Estácio de Sá Houve um aumento significativo no nível de abstração, mas parte da dificuldade permanece, pois o programador, além de necessitar memorizar os mneumônicos, precisa conhecer a arquitetura do computador como forma de endereçamento dos registradores e memória, além de outros aspectos. Linguagens de 3ª geração (linguagens procedurais) São as, também, linguagens de alto nível, de aplicação geral, em que uma única instrução em uma linguagem próxima a do homem pode corresponder a mais de uma instrução em linguagem de máquina. Caracterizam-se pelo suporte a variáveis do tipo simples (caractere, inteiro, real e lógico) e estruturados (matrizes, vetores, registros), comandos condicionais, comando de iteração e programação modular (funções e procedimentos), estando alinhadas à programação estruturada. O processo de conversão para a linguagem de máquina ficou mais complexo e ficaram a cargo dos interpretadores e tradutores. As primeiras linguagens de 3ª geração que foram apresentadas ao mercado são: Fortran, BASIC, COBOL, C, PASCAL, C, dentre outras. Esta geração de linguagens apresenta as seguintes propriedades em comum: Armazenar tipos de dados estaticamente: simples, estruturados e enumerados. Alocar memória dinamicamente, através de ponteiros, que são posições de memória cujo conteúdo é outra posição de memória. Disponibilizar: estruturas de controle sequencial, condicional, repetição e desvio incondicional. Permitir a programação modular, com uso de parâmetros. Operadores: relacionais, lógicos e aritméticos. Ênfase em simplicidade e eficiência. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 16 Linguagens de 4ª geração (linguagens aplicativas) São, também, linguagens de alto nível, com aplicação e objetivos bem específicos. Enquanto as linguagens de 3ª geração são procedurais, ou seja, especifica-se passo a passo a solução do problema, as de 4ª geração são não procedurais. O programador especifica o que deseja fazer e não como deve ser feito. O melhor exemplo de linguagens de 4ª geração é a SQL (Structured Query Language), utilizada para consulta à manipulação de banco de dados. PostScript e MATLAB são outros dois exemplos de linguagens de 4ª geração. Linguagens de 5ª geração (voltadas à inteligência artificial) São linguagens declarativas e não algorítmicas. Exemplos: Lisp e Prolog. As linguagens de 5ª geração são usadas para desenvolvimento de sistemas especialistas (área da IA), de sistemas de reconhecimento de voz e machine learning. A imagem a seguir ilustra as características de cada geração. Fonte: Estácio de Sá Verificando o aprendizado 1. Avalie as assertivas sobre as linguagens de programação: I. Linguagem Assembly é a nativa dos computadores. II. Uma Linguagem deve ser compatível única e exclusivamente com o hardware a que se propôs a atender. III. A abstração traz facilidades ao programador que cada vez menos precisa conhecer o ambiente onde a linguagem opera (composto por sistema operacional e hardware). IV. Um comando em uma linguagem de alto nível faz mais que uma operação primária do hardware. Com base em sua análise, marque a opção que apresenta apenas as assertivas corretas. A. I, II, III e IV B. III e IV apenas C. III apenas D. II e IV apenas Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 17 2. Relacione a coluna A, na qual temos as gerações das linguagens de programação com a coluna B, em que temos as características das gerações de linguagens: A – Gerações B – Características das gerações 1 – 1ª geração a – Linguagem de alto nível 2 – 3ª geração b – Linguagem Assembly 3 – 4ª geração c – Linguagem de máquina 4 – 2ª geração d – Linguagens não procedimentais Com base em sua análise, marque a opção que relaciona corretamente as duas colunas A e B: A. 1-c, 2-a, 3-d, 4-b B. 1-a, 2-c, 3-d, 4-b C. 1-d, 2-b, 3-a, 4-c D. 1-b, 2-c, 3-d, 4-a Critérios de avaliação de linguagens de programação Considerando as diversas linguagens de programação existentes hoje no mercado, atendendo a propósito comuns, vamos destacar neste módulo os domínios da programação, que são seis: 1. Aplicações científicas 2. Aplicações comerciais 3. Aplicações com Inteligência Artificial 4. Programação de sistemas 5. Programação para web 6. Programação mobile Na sequência, apresentaremos critérios que podem ser usados para avaliação de linguagens de programação, claro, dentro do mesmo domínio de programação. Domínios da programação O computador tem sido usado para diversos fins, na ciência, nas forças armadas, nas empresas públicas e privadas, pelos profissionais liberais, pelas pessoas em seus lazeres e onde mais possa ser aplicado. Seu uso vai desde controlar robôs que fazem a montagem de automóveis em suas linhas de montagem até jogos digitais. Em função desse uso adverso, surgiram linguagens de programação com diferentes objetivos. A seguir, discutiremos as principais áreas e as respectivas linguagens de programação em destaque. Aplicações científicas (máquinas de calcular com alta precisão) O primeiro computador, o ENIAC, foi desenvolvido por 3 anos e ficou pronto no ano de 1946. Sua principal finalidade eram cálculos balísticos. Os computadores seguintes, nas décadas de 1940 e 1950, também focaram em cálculos científicos complexos. As linguagens de programação nessa época eram a linguagem de máquina e Assembly. Na década de 1960 surgem as primeiras linguagens de programação de alto nível, com destaque para Fortran (iniciais de FORmula TRANslator) e posteriormente para ALGOL60. As principais características dessas linguagens eram: Estruturas de dados simples. Alto volume de cálculos com aritmética de ponto flutuante (precisão). Preocupaçãocom a eficiência, pois sucederam a linguagem Assembly. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 18 Aplicações comerciais A segunda onda de aplicativos foi para suprir as demandas das empresas a partir de meados da década de 1950. Em 1960, surge a linguagem que seria o ícone das aplicações comerciais de computadores de grande porte, naquele momento, o COBOL. As linguagens de programação que apoiaram o crescimento das aplicações comerciais têm como características: Facilidade para produzir relatórios, fundamentais nos controles das operações contábeis, bancárias, estoque e financeiras (primeiros focos da época). Precisão com números decimais e ponto flutuante, para representar as altas cifras das grandes empresas, as primeiras a investirem nessas aplicações. Capacidade de especificar operações aritméticas comerciais. Cabe destacar que as linguagens destinadas a aplicações comerciais ganham força com a microcomputação a partir dos anos 1980, levando as aplicações comerciais aos médios e pequenos empresários. Aplicações com inteligência artificial As linguagens que sustentam o desenvolvimento de aplicações apoiadas na Inteligência Artificial (IA) ganham força nos dias de hoje. A grande ruptura no pensamento computacional é que as linguagens que apoiam a IA usam a computação simbólica e não numérica, como a maioria das linguagens da época. Em 1959, surge a linguagem Lisp, primeira linguagem projetada para apoio à computação simbólica, primeira referência da computação funcional. Prolog, criada em 1977, foi a primeira linguagem para apoio da computação lógica, essência dos sistemas especialistas (sistemas que usam IA para simular o comportamento humano). Programação de sistemas A programação de sistemas cabe a linguagens de programação que tenham comandos e estruturas para acessar, diretamente, o hardware. Tais linguagens são usadas para desenvolver softwares básicos, como sistemas operacionais, tradutores e interpretadores de linguagens de programação. Antes de surgir a linguagem C, usada para desenvolver o sistema operacional Linux, Assembly era a linguagem usada para esse fim. A linguagem C++ também é usada com essa finalidade. Programação para web Com o crescimento da internet e tecnologias adjacentes, o uso dos sistemas se desloca do ambiente desktop (domínio dos anos 1980 e 1990) para o ambiente Web. No contexto de programação para Web, temos 2 diferentes ambientes de desenvolvimento: a camada de apresentação, que roda no navegador (lado cliente) e a camada de lógica do negócio, que roda nos servidores web (lado servidor), juntamente com a camada de persistência, considerando o modelo de desenvolvimento em 3 camadas (apresentação, lógica do negócio e persistência de dados). Para a camada de apresentação, usa-se as linguagens HTML (linguagem de marcação) e CSS (usada em conjunto com HTML para definir a apresentação da página web), além de JavaScript (programação de scripts), no lado cliente (navegadores). Para o desenvolvimento das camadas de lógica do negócio, as principais LP são: C#, PHP, ASP, .NET, Java, Ruby e Python. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 19 Programação mobile Considerando que hoje em dia, grande parte da população, no Brasil e no Mundo, tem acesso à internet pelo celular, cresceu vertiginosamente a quantidade de apps (aplicativos) para uso de aplicações via celular. Os apps, na verdade, são interfaces que rodam no lado cliente. As principais (não todas) linguagens que apoiam o desenvolvimento de apps para o mundo mobile, oficialmente indicadas por seus fabricantes, são: Android: Java e Kotlin. iOS: Swift (oficial da Apple) e Objective-C (código nativo para iOS). Windows: C#, Visual Basic (VB), C++, HTML, CSS, JavaScript e Java. O desenvolvimento de APP para iOS é baseado numa IDE chamada Xcode que permite o desenvolvimento de APP em várias linguagens, como: C, C++, Java e Python, mas oficialmente orienta o Swift e Objective-C. A Google, por sua vez, tem por base o Android SDK, orienta a usar as linguagens Kotlin, Java e C++, mas as linguagens Python, Shell script, Basic4Android, LiveCode (para iOS e Windows também), App Inventor (não necessita conhecer programação) e Unity (motor para games) e GO, também são usadas para desenvolver app para Android. No contexto de desenvolvimento de APP para Windows, foi lançado no Windows 8.1 e atualizado para atender também ao Windows 10, o App Studio, que permite a qualquer pessoa criar em poucos passos um app Windows e publicá-lo na loja. Importante destacar que hoje existem plataformas de desenvolvimento mobile conectadas a nuvem que fomentam o desenvolvimento de apps nativos para iOS, Android e Windows. Avaliação de linguagens de programação Segundo Sebesta (2018) são quatro grandes critérios para avaliação das linguagens de programação, dentro de um mesmo domínio de programação. Cada critério é influenciado por algumas características da linguagem. Legibilidade Um dos critérios mais relevantes é a “facilidade com que os programas podem ser lidos e entendidos” pelas pessoas que não necessariamente participaram do desenvolvimento. Facilidade de escrita O quão facilmente uma linguagem pode ser usada para desenvolver programas para o domínio do problema escolhido. Confiabilidade Um programa é dito confiável se ele se comporta conforme a sua especificação, repetidas vezes. Custo O custo final de uma linguagem de programação é em função de muitas de suas propriedades e características. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 20 A tabela a seguir exibe as características da linguagem que influenciam cada um dos três principais fatores de avaliação de linguagens. Critérios Características Legibilidade Facilidade escrita Confiabilidade Simplicidade Ortogonalidade Estruturas de controle Tipos de dados Projeto de sintaxe Suporte para abstração Expressividade Verificação de tipos Tratamento de exceções Aliasing Características x Critérios de Avaliação de LPs Legibilidade Um dos critérios mais relevantes para avaliar uma linguagem de programação diz respeito à capacidade com que os programas podem ser lidos e entendidos pela sintaxe e construção da linguagem, sem considerar as possíveis influências da má programação. As características que influenciam a legibilidade de uma linguagem de programação são: Simplicidade Quanto mais simples for uma linguagem, melhor será a legibilidade do código por ela produzido. Uma linguagem com número elevado de construções básicas é mais difícil de ser aprendida do que uma que tenha poucas. Tende a ser subutilizada. Uma segunda característica que afeta negativamente a legibilidade é a multiplicidade de recursos. Por exemplo, em Python, o programador pode incrementar uma variável, de duas formas distintas: cont= cont + 1; cont +=1. Nas linguagens C e Java, ainda podemos usar para incrementar variáveis as seguintes estruturas: ++cont e cont++. Muita simplicidade pode tornar menos legíveis os códigos escritos. Na linguagem Assembly, a maioria das sentenças são simples, porém não são altamente legíveis devido à ausência de estruturas de controle. Uma terceira característica que afeta negativamente a legibilidade é a sobrecarga de operadores, como por exemplo o “+”, usado para somar inteiros, reais, concatenar cadeias de caracteres (strings), somar vetores, dentre outras construções permitidas pela linguagem. Ortogonalidade A ortogonalidade de uma linguagem refere-se a um conjunto relativamente pequeno de construções primitivas que pode ser combinado em um número, também, pequeno de maneiras para construir as estruturas de controle e de dados de uma linguagem de programação. Em outras palavras: possibilidade decombinar, entre si, sem restrições, as construções básicas da linguagem para construir estruturas de dados e de controle. Boa ortogonalidade: Permitir, por exemplo, que haja um vetor, cujos elementos sejam do tipo registro (estrutura heterogênea). Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 21 Má ortogonalidade: Não permitir que um vetor seja passado como argumento para uma rotina (procedimento ou função). Ou que uma função não possa retornar um vetor. Uma linguagem ortogonal tende a ser mais fácil de aprender e tem menos exceções. A falta de ortogonalidade leva a muitas exceções às regras da linguagem e ao excesso, o contrário (menos exceções às regras). Menos exceções implicam um maior grau de regularidade no projeto da linguagem, tornando-a mais fácil de ler, entender e aprender. Instruções de controle Instruções como Goto (desvio incondicional) limitam a legibilidade dos programas, pois essa instrução pode levar o controle do código a qualquer ponto do programa, limitando o entendimento e, consequentemente, a legibilidade do código escrito na linguagem. As linguagens modernas não implementam desvio incondicional, assim sendo, o projeto de estruturas de controle é menos relevante na legibilidade do que anos atrás, quando surgiram as primeiras linguagens de alto nível. Tipos e estruturas de dados A facilidade oferecida pela linguagem para definir tipos e estruturas de dados é outra propriedade que aumenta a legibilidade do código escrito. Por exemplo, uma linguagem que permita definir registros e vetores, mas não permite que um vetor tenha registros como seus elementos, terá a legibilidade afetada. A linguagem C não possui o tipo de dado lógico ou booleano. Muitas vezes, usa-se variáveis inteiras, permitindo apenas que receba os valores 0 e 1 para conteúdo, simulando o tipo booleano. Por exemplo, para localizar um elemento em uma das posições de um vetor, usa-se uma variável lógica se a linguagem permitir e, assim, teríamos a instrução “achou=false” em determinado trecho de código. Em outra linguagem que não permita o tipo de dado lógico, a instrução poderia ser “achou=0”, em que achou seria uma variável inteira. Qual das duas sentenças é mais clara a quem lê o código? A primeira, não é? “achou=false”. Sintaxe A sintaxe tem efeito sobre a legibilidade. Um exemplo é a restrição do tamanho (quantidade de caracteres) para um identificador (tipo, variável, constante, rotina – procedimento e função), impedindo que recebam nomes significativos sobre sua utilidade. Na linguagem Fortran, o nome do identificador pode ser até 6 caracteres. Outra propriedade de sintaxe que afeta a legibilidade é o uso de palavras reservadas da linguagem. Por exemplo, em Pascal, os blocos de instrução são iniciados e encerrados com BEGIN-END, respectivamente. A linguagem C usa chaves para iniciar e encerrar blocos de instruções. Já a linguagem Python usa a endentação obrigatória para marcar blocos de comandos, aumentando a legibilidade, naturalmente. Facilidade de escrita (redigibilidade) A facilidade de escrita é a medida do quão fácil a linguagem permite criar programas para um domínio da aplicação. A maioria das características que afeta a legibilidade também afeta a facilidade de escrita, pois se a escrita do código não flui, haverá dificuldade para quem for ler o código. As características que influenciam na facilidade de escrita são: Simplicidade e ortogonalidade Quanto mais simples e ortogonal for a linguagem, melhor sua facilidade para escrever programas. O ideal são linguagens com poucas construções primitivas. Imagina que uma linguagem de programação possui grande número de construções. Alguns programadores podem não usar todas, deixando de lado, eventualmente, as mais eficientes e elegantes. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 22 Expressividade Uma linguagem de programação com boa expressividade contribui para o aumento da facilidade de escrita dos códigos. Assembly: Baixa expressividade. Pascal e C, boa expressividade: Ricas estruturas de controle. Exemplo: o comando FOR mais adequado que WHILE e REPEAT para representar lações com número fixo de vezes. Da mesma forma que o C, em que o FOR é mais indicado que o WHILE e DO-WHILE. Na linguagem Python, ocorre o mesmo entre os comandos FOR e WHILE. Na linguagem C, temos construções diversas para incremento de variável: i++ é mais simples e conveniente de usar do que i=i+1, sendo i, uma variável inteira. Uma linguagem expressiva possibilita escrever linhas de código de uma forma mais conveniente ao invés de deselegante. Suporte para a abstração O grau de abstração em uma linguagem é uma propriedade fundamental para aumentar a facilidade de escrita. Abstração pode ser de: Processos, como o conceito de subprograma. Dados, como uma árvore ou lista simplesmente encadeada. Confiabilidade Dizemos que um programa é confiável se ele se comportar conforme sua especificação, sob todas as condições, todas as vezes em que for executado. Abaixo, alguns recursos das linguagens que exercem efeito sobre a confiabilidade de programas. Verificação de tipos Significa verificar, em tempo de compilação ou execução, se existem erros de tipo. Por exemplo, atribuir um valor booleano a uma variável do tipo inteira, vai resultar em erro. As linguagens fortemente tipadas, em tempo de compilação, como Python e Java, tendem a ser mais confiáveis, pois apenas valores restritos aos tipos de dados declarados poderão ser atribuídos e diminuem os erros em tempo de execução. Linguagens, como C, em que não é verificado se o tipo de dado do argumento é compatível com o parâmetro, em tempo de compilação, podem gerar erros durante a execução, afetando a confiabilidade. A verificação de tipos em tempo de compilação é desejável, já em tempo de execução é dispendiosa (mais lenta e requer mais memória), e mais flexível (menos tipada). Tratamento de exceção O tratamento de exceção em uma linguagem de programação garante a correta execução, aumentando a confiabilidade. As linguagens Python, C++ e Java possuem boa capacidade de tratar exceções, ao contrário da linguagem C. A linguagem deve permitir a identificação de eventos indesejáveis (estouro de memória, busca de elemento inexistente, overflow etc.) e especificar respostas adequadas a cada evento. O comportamento do programa torna-se previsível com a possibilidade de tratamento das exceções, o que tende a aumentar a confiabilidade do código escrito na linguagem de programação. Aliasing (apelidos) Aliasing é o fato de ter dois ou mais nomes, referenciando a mesma célula de memória, o que é um recurso perigoso e afeta a confiabilidade. Restringir Aliasing é prover confiabilidade aos programas. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 23 Legibilidade e facilidade de escrita Ambos influenciam a confiabilidade. A legibilidade afeta tanto na fase de codificação como na fase de manutenção. Programas de difícil leitura são difíceis de serem escritos também. Uma linguagem com boa legibilidade e facilidade de escrita gera códigos claros, que tendem a aumentar a confiabilidade. Custo O custo de uma linguagem de programação varia em função das seguintes despesas: de treinamento, de escrita do programa, do compilador, de execução do programa, de implementação da linguagem e o de manutenção do código. Custo de Características Treinamento Custo de Treinamento para programadores varia em função da expertise do programador, simplicidade e ortogonalidade da linguagem; F (simplicidade de escrita, ortogonalidade, experiência do programador). Escrever programa Custo para escrever programas na linguagem varia em função da facilidade de escrita. F(Facilidade de escrita). Compilar o programa Esse custo varia em função do custo de aquisição do compilador,hoje minimizado, em linguagens open source, como é o caso do Python. F (custo de aquisição do compilador). Executar o programa Custo para executar programas, varia em função do projeto da linguagem. F (Projeto da linguagem). Implementar a linguagem A popularidade da LP vai depender de um econômico sistema de implementação. Por exemplo, Python e Java possuem compiladores e interpretadores gratuitos. Confiabilidade O custo da má confiabilidade: se um sistema crítico falhar, o custo será elevado. Exemplos: sistema de controle de consumo de água e sistemas de usina nuclear. Manutenção Custo de manutenção: depende de vários fatores, mas principalmente da legibilidade, já que a tendência é que a manutenção seja dada por pessoas que não participaram do desenvolvimento do software. Os custos em treinamento e de escrever o programa podem ser minimizados se a linguagem oferecer bom ambiente de programação. Python é uma linguagem com alta legibilidade, facilidade de escrita, além de confiável. Seu custo não é elevado, pois além de ser open source, é fácil de aprender. Atenção Existem outros critérios, como por exemplo a portabilidade ou a capacidade que os programas têm de rodarem em ambientes diferentes (sistema operacional e hardware), o que é altamente desejável. A reusabilidade, ou seja, o quanto um código pode ser reutilizado em outros programas ou sistemas aumenta o nível de produtividade da linguagem. Além da facilidade de aprendizado, que é fortemente afetada pela legibilidade e facilidade de escrita. Verificando o aprendizado 1. Dos domínios de programação apresentados na aula, quais devem considerar, necessariamente, a arquitetura cliente-servidor? A. Programação web e Mobile. B. Programação de sistemas e aplicações comerciais. C. Aplicações de Inteligência Artificial e aplicações científicas. D. Aplicações comerciais e programação de sistemas. 2. Um dos critérios mais relevantes na avaliação de linguagens de programação diz respeito à facilidade com que os programas podem ser lidos. Estamos falando de qual critério? A. Legibilidade B. Redigibilidade C. Confiabilidade D. Custo Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 24 Os paradigmas e suas características O agrupamento por paradigmas é outra forma de classificar as linguagens de programação. Um paradigma agrupa linguagens com características semelhantes que surgiram em uma mesma época. A imagem a seguir ilustra os cinco paradigmas nos quais as linguagens de programação são classificadas. Esses paradigmas são agrupados em Imperativos e Declarativos, de acordo com a forma com que os programas são estruturados e descritos. Fonte: Estácio de Sá Você também pode acompanhar mais informações sobre a classificação dos paradigmas das linguagens de programação assistindo ao vídeo: Paradigma imperativo O paradigma imperativo agrega três paradigmas: estruturado, orientado a objeto e concorrente, os quais possuem em comum o fato de especificarem passo a passo o que deve ser feito para a solução do problema. As linguagens do paradigma imperativo são dependentes da arquitetura do computador, pois especificam em seus programas como a computação é realizada. Vamos explicar as características de cada um dos paradigmas do subgrupo Imperativo. Paradigma estruturado Caracteriza as principais linguagens de programação da década de 1970 e 1980 que seguiram os princípios da programação estruturada: 1. Não usar desvios incondicionais (Goto, característico de linguagens como BASIC e versões iniciais do COBOL). 2. Desenvolver programas por refinamentos sucessivos (metodologia top down), motivando o desenvolvimento de rotinas (procedimentos e funções) e a visão do programa partindo do geral para o particular, ou seja, o programa vai sendo refinado à medida que se conhece melhor o problema e seus detalhes. 3. Desenvolver programas usando três tipos de estruturas: sequenciais, condicionais e repetição. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 25 4. Visando eficiência, o paradigma estruturado baseia-se nos princípios da arquitetura de Von Neumann, onde: a. Programas e dados residem, na memória (durante a execução). b. Instruções e dados trafegam da memória para CPU e vice-versa. c. Resultados das operações trafegam da CPU para a memória. As linguagens Pascal e C caracterizam bem esse paradigma. A linguagem Python, multiparadigma, tem o estilo básico do paradigma estruturado. Paradigma orientado a objetos Com o crescimento do tamanho do código e complexidade dos programas, o paradigma estruturado começou a apresentar limitações nos sistemas que passaram a ter dificuldade de manutenção e reuso de programas e rotinas padronizadas. A orientação a objetos surge como solução a esses problemas, permitindo, através de propriedades como abstração, encapsulamento, herança e polimorfismo, maior organização, reaproveitamento e extensibilidade de código e, consequentemente, programas mais fáceis de serem escritos e mantidos. O principal foco desse paradigma foi possibilitar o desenvolvimento mais rápido e confiável. As classes são abstrações que definem uma estrutura que encapsula dados (chamados de atributos) e um conjunto de operações possíveis de serem usados, chamados métodos. Os objetos são instâncias das classes. Exemplo Por exemplo, a classe ALUNO encapsula um conjunto de dados que os identifiquem: matrícula, nome, endereço (rua, número, complemento, bairro, estado e CEP) e um conjunto de métodos: Incluir Aluno, Matricular Aluno, Cancelar Matrícula, dentre outros. O paradigma orientado a objetos, por sua vez, usa os conceitos do paradigma estruturado na especificação dos comandos de métodos. Por isso, é considerado uma evolução do paradigma estruturado. Atenção Phyton, Smalltalk, C++, Java, Delphi (oriundo do Object Pascal) são linguagens que caracterizam o paradigma orientado a objetos. Python é orientado a objeto, pois tudo em Python é objeto, permitindo a declaração de classes encapsuladas, além de possibilitar herança e polimorfismo. Paradigma concorrente Caracterizado quando processos executam simultaneamente e concorrem aos recursos de hardware (processadores, discos e outros periféricos), características cada vez mais usuais em sistemas de informação. O paradigma concorrente pode valer-se de apenas um processador ou vários. Um processador Os processos concorrem ao uso do processador e recursos. Vários processadores Estamos caracterizando o paralelismo na medida em que podem executar em diferentes processadores (e de fato, ao mesmo tempo), os quais podem estar em uma mesma máquina ou distribuídos em mais de um computador. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 26 Ada e Java são as linguagens que melhor caracterizam esse paradigma, possibilitando suporte à concorrência. Você sabia Ao contrário de Go, Python não foi originalmente projetada com foco em programação concorrente, muito menos paralela. O modo tradicional de programar concorrência em Python -- threads -- é limitado no interpretador padrão (CPython) por uma trava global (a GIL), que impede a execução paralela de threads escritas em Python. Isso significa que threads em Python são úteis apenas em aplicações I/O bound – em que o gargalo está no I/O (entrada e saída), como é o caso de aplicações na Internet. Paradigma declarativo Diferentemente do paradigma imperativo, no declarativo o programador diz o que o programa deve fazer (qual a tarefa), ao invés de descrever como o programa deve fazer. O programador declara, de forma abstrata, a solução do problema. Essas linguagens não são dependentes de determinada arquitetura de computador. As variáveis são incógnitas, tal qual na Matemática e não células de memória. O paradigma declarativo agrega os paradigmasfuncional e lógico. Vamos explicar as características de cada um. Paradigma funcional Abrange linguagens que operam tão somente funções que recebem um conjunto de valores e retornam um valor. O resultado que a função retorna é a solução do problema (foca o processo de resolução de problemas). O programa resume-se em chamadas de funções, que por sua vez podem usar outras funções. Uma função pode invocar outra, ou o resultado de uma função pode ser argumento para outra função. Usa-se também chamadas recursivas de funções. Naturalmente, esse paradigma gera programas menores (pouco código). Linguagens típicas desse paradigma são: LISP, HASKELL e ML. LISP é a LP funcional mais usada, especialmente em programas que usem os conceitos de Inteligência Artificial (sistemas especialistas, processamento de linguagem natural e representação do conhecimento), devido à facilidade de interpretação recursiva. Exemplo: O código abaixo implementa em Python uma função que calcula quantos números inteiros existem de 0 a n. def conta_numeros(n): p = 0 for num in range(n+1): if num%2 == 0: p += 1 return p Abaixo, o mesmo código usando o conceito de função recursiva. Repare que a função de nome conta_numeros chama ela mesma em seu código (isso é a recursão). def conta_numeros(n): if n == 0: return 1 # 0 é par elif n%2 == 0: return 1 + conta_numeros(n-1) else: return conta_numeros(n-1) Atenção Python não é uma linguagem funcional nativa, seria exagerado afirmar isso, porém sofreu influência desse paradigma ao permitir: recursividade, uso de funções anônimas, com a função lambda, dentre outros recursos, além, claro, de ser uma linguagem com enorme biblioteca de funções. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 27 Paradigma lógico Um programa lógico expressa a solução da maneira como o ser humano raciocina sobre o problema: baseado em fatos, derivam-se conclusões e novos fatos. Quando um novo questionamento é feito, através de um mecanismo inteligente de inferência, deduz novos fatos a partir dos existentes. A execução dos programas escritos em linguagens de programação lógica segue, portanto, um mecanismo de dedução automática (máquina de inferência), sendo Prolog a linguagem do paradigma lógico mais conhecida. O paradigma lógico é usado no desenvolvimento de linguagens de acesso a banco de dados, sistemas especialistas (IA), tutores inteligentes etc. Python não tem características para implementar programas que atendam ao paradigma lógico. Verificando o aprendizado 1. As linguagens de programação podem se enquadrar em um ou vários paradigmas, nos quais a linguagem tem uma grande flexibilidade e potencial de aproveitamento. Nesse contexto, Python é uma linguagem: A. Compatível apenas com o paradigma estruturado. B. Compatível apenas com os paradigmas estruturado e lógico. C. Compatível apenas com o paradigma orientado a objetos. D. Multiparadigma. 2. Relacione a coluna A, em que temos paradigmas das linguagens de programação com a coluna B, em que temos as características dos paradigmas de linguagens: A – Paradigmas B – Características dos paradigmas 1 – Estruturado a – Fomenta a reusabilidade pelos mecanismos de herança e polimorfismo 2 – Orientado a objeto b – Desenvolver programas usando três tipos de estruturas: sequenciais, condicionais e repetição 3 – Concorrente c – Lisp é a linguagem mais significativa desse paradigma 4 – Funcional d – Processos executam simultaneamente e concorrem aos recursos de hardware Com base em sua análise, marque a opção que relaciona corretamente as duas colunas A e B: A. 1-b, 2-a, 3-d, 4-c B. 1-a, 2-c, 3-d, 4-b C. 1-d, 2-b, 3-a, 4-c D. 1-b, 2-c, 3-d, 4-a Métodos de implementação das linguagens Todo programa, a menos que seja escrito em linguagem de máquina, o que hoje em dia está totalmente fora dos propósitos, precisará ser convertido para linguagem de máquina antes de ser executado. Essa conversão precisa de um programa que receba o código-fonte escrito na linguagem e gere o respectivo código em linguagem de máquina. Esse programa, que fará a tradução do código-fonte em linguagem de máquina, é que vai determinar como os programas são implementados e como vão executar. Existem duas formas de realizar essa conversão: tradução e interpretação. É fundamental que se saiba e se entenda qual o processo de conversão usado na respectiva linguagem de programação. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 28 Tradução Nesse processo de conversão, o programa escrito em uma linguagem de alto nível é traduzido para uma versão equivalente em linguagens de máquina, antes de ser executado. O processo de tradução pode ser executado em várias fases, que podem ser combinadas e executadas em simultaneidade. O processo de tradução é erroneamente chamado de compilação, que na verdade é uma de suas fases. As fases que compõem o tradutor, ou seja, iniciando na leitura do programa-fonte (linguagem de alto nível) e terminando com a geração do código executável (entendido pela máquina), são: Compilação, Montagem, Carga e Ligação. A imagem abaixo ilustra o processo de tradução. Compilador: O compilador (detalhes adiante) analisa o código-fonte e estando tudo OK, o converte para um código Assembly (da máquina hospedeira). Montador: O montador traduz o código Assembly para o código de máquina intermediário (Código- objeto), que não é executável pelo computador. O código-objeto pode ser relocável, ou seja, carregado em qualquer posição de memória ou absoluto, carregado em um endereço de memória específico. A opção relocável é mais comum e mais vantajosa. Carregador: O carregador é que torna o código-objeto em relocável. Ligador: O Ligador liga (ou linka) o código-objeto relocável com as rotinas bibliotecas (outros objetos, rotinas do SO, DLLs etc.), usadas nos códigos-fontes. Essa ligação gera o código executável. Fonte: Estácio de Sá Compilador É o elemento central do processo de tradução, responsável pelo custo de compilação, visto no modulo anterior. Em função dessa relevância, muitas vezes o processo como um todo é erroneamente chamado de compilação, uma vez que o ambiente integrado das linguagens atuais já integra todos os componentes (montador, compilador, carregador e ligador) quando necessário. O projeto da linguagem tem no compilador a sua figura central. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 29 A imagem abaixo ilustra os componentes envolvidos na compilação de um programa fonte: Fonte: Estácio de Sá Abaixo, vamos entender cada fase da compilação: Análise léxica Identifica os tokens (elementos da linguagem), desconsidera partes do código-fonte, como espaços em branco e comentários e gera a Tabela de símbolos, com todos esses tokens, que são identificadores de variáveis, de procedimentos, de funções, comandos, expressões etc. Análise sintática Verifica se os tokens são estruturas sintáticas (exemplos: expressões e comandos) válidas, aplicando as regras gramaticais definidas no projeto da linguagem. Análise semântica Verifica se as estruturas sintáticas possuem sentido. Por exemplo, verifica se um identificador de variável ou constante é usado adequadamente, se operandos e operadores são compatíveis. Monta a árvore de derivação conforme ilustrado abaixo para formação das expressões. Árvore de derivação de uma expressão. Fonte: Estácio de Sá Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 30 Gerador de código intermediário, otimizador de código e gerador de código Em distintas fases geram o programa-alvo ou programa-objeto. Gerador de código intermediário, que contém toda a informação para gerar o código-objeto. Na imagem a seguir, o código intermediário está representadono último quadro – código em Assembly: Fonte: Estácio de Sá O otimizador tem por objetivo eliminar redundâncias do código intermediário e tornar o objeto mais enxuto e eficiente. Tratador de erros Em todas as fases existem erros: léxicos, sintáticos e semânticos. Um bom compilador apresenta uma boa tratativa de erros. Gerenciador da tabela de símbolos Mantém a tabela de símbolos atualizada a cada passo do compilador. Atenção As principais características dos compiladores são: • Gerar código-objeto mais otimizado. • Execução mais rápida que o processo de interpretação. • Traduz um mesmo comando apenas uma vez, mesmo que usado em várias partes do programa – tanto iterações como repetição de código. • Processo de correção de erros e depuração é mais demorado. • A programação final (código-objeto) é maior. • O programa-objeto gerado é dependente de plataforma — processador + SO (Sistema Operacional) — necessitando de um compilador diferente para cada família de processadores/sistema operacional. Interpretação A imagem abaixo ilustra o simples processo de Interpretação: Fonte: Estácio de Sá Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 31 Na conversão por interpretação, cada comando do programa-fonte é traduzido e executado (um a um) imediatamente. O interpretador traduz um comando de cada vez e chama uma rotina para completar a sua execução. O interpretador é um programa que executa repetidamente a seguinte sequência: 1. Obter a próxima instrução do código-fonte. 2. Interpretar a instrução (conversão para comandos em linguagem de máquina). 3. Executar a instrução. Perceba que o procedimento, acima descrito, é bastante similar àquele executado por computadores que implementam a máquina de Von Neumann, na execução de uma instrução, conforme a seguir: • Obter a próxima instrução. • CI → endereço da próxima instrução. CI = contador de instruções. • RI → instrução a ser executada. RI = registrador de instruções. • Decodificar a instrução. • Executar a instrução. Principais características do interpretador Dentre as principais características do interpretador, podemos citar: • Atua a cada vez que o programa precisa ser executado. • Não produz programa-objeto persistente. • Não traduz instruções que nunca são executadas. • O resultado da conversão é instantâneo: resultado da execução do comando ou exibição de erro – interpretador puro. • Útil ao processo de depuração de código devido a mensagens de erros em tempo de execução (tanto análise sintática como semântica). • Execução mais lenta do que outros processos de tradução (compilação), pois toda vez que o mesmo programa é executado, os mesmos passos de interpretação são executados. • Consome menos memória. • O Código-fonte é portátil. o Não é gerado um código de máquina. o Pode executar o comando em alto nível diretamente ou gerar um código intermediário, que neste caso é interpretado por uma máquina virtual (VM). – Interpretador híbrido. o Se a máquina virtual foi desenvolvida para diferentes plataformas, temos a portabilidade do código-fonte. Este é o caso da linguagem Java. Tradução x interpretação Vantagens Desvantagens Tradutores 1. Execução mais rápida 2. Permite estruturas de programas mais complexas. 3. Permite a otimização de código. 1. Várias etapas de conversão. 2. Programação final é maior, necessitando de mais memória para sua execução. 3. Processo de correção de erros e depuração é mais demorado. Interpretadores 1. Depuração mais simples. 2. Consome menos memória. 3. Resultado imediato do programa (ou parte dele). 1. Execução do programa é mais lenta. 2. Estruturas de dados demasiado simples. 3. Necessário fornecer o código fonte ao utilizador. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 32 Sistemas híbridos O processo híbrido de implementação de uma linguagem de programação combina a execução rápida dos tradutores (compiladores) com a portabilidade dos interpretadores. O segredo é a geração de um código intermediário mais facilmente interpretável, porém não preso a uma plataforma (SO/Hardware). Esse código intermediário não é específico para uma plataforma, possibilitando aos programas já compilados para esse código serem portados em diferentes plataformas, sem alterar e nem fazer nada. Para cada plataforma desejada devemos ter um interpretador desse código. Duas importantes linguagens implementaram essa solução, com diferentes formas usando máquinas virtuais: Python e Java. Sistema de implementação de Python Python usa um sistema híbrido, uma combinação de interpretador e tradutor (compilador). O compilador converte o código-fonte Python em um código intermediário, que roda numa máquina virtual, a PVM (Python Virtual Machine). Comentário Curioso saber que o código Python pode ser traduzido em Bytecode Java usando a implementação Jython. O interpretador converte para código de máquina, em tempo de execução. O compilador traduz o programa inteiro em código de máquina e o executa, gerando um arquivo que pode ser executado. O compilador gera um relatório de erros e o interpretador interrompe o processo na medida em que localiza um erro. CPython é uma implementação da linguagem Python, um pacote com um compilador e um interpretador Python (Máquina Virtual Python), além de outras ferramentas para programar em Python. Verificando o aprendizado 1. Como se chamam os dois processos de conversão de linguagens de alto nível em linguagens inteligíveis pelo computador? A. Tradução e interpretação. B. Compilação e interpretação. C. Análise sintática e análise léxica. D. Compilação e montagem. 2. Avalie as assertivas a seguir: I. O compilador analisa o código-fonte e o converte para um executável. II. O montador traduz o código Assembly para o código de máquina intermediário (código objeto), que é executável pelo computador. III. O carregador é que torna o código-objeto em relocável. IV. O ligador liga o código-objeto relocável com as rotinas. Essa ligação gera o código executável. Com base em sua análise, assinale a única opção com todas as assertivas corretas: A. Estão corretas III e IV apenas. B. Estão corretas II, III e IV apenas. C. Está correta apenas a III. D. Está correta apenas a IV. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 33 Considerações finais Saber classificar uma linguagem dentro do(s) paradigma(s) a que pertença(m), assim como conhecer os domínios de programação de uma linguagem, é fundamental para seu entendimento e correta aplicação na hora de decidir pelo uso dessa ou aquela linguagem. É fundamental também que se possa conhecer os critérios de avaliação de uma linguagem e quais propriedades afetam cada um dos quatro critérios vistos. Dessa forma, vamos saber selecionar a que melhor se aplica a determinado contexto de uso. Por fim, e não menos importante, conhecer o mais profundamente possível o ambiente de desenvolvimento da linguagem, contemplando tradutores e/ou interpretadores, para que se possa pôr em prática o desenvolvimento do sistema, no paradigma, domínio do problema, e usando as melhores construções para gerar códigos legíveis, com alta facilidade de escrita, confiabilidade e, preferencialmente, ao menor custo possível. Referências BORGES, Luiz Eduardo. Python para Desenvolvedores. 2. ed. Rio de Janeiro: Edição do Autor, 2010. SEBESTA, R. W. Conceitos de Linguagens de Programação. Tradução de João Eduardo Nobrega Tortello. 11. ed. Porto Alegre: Bookman, 2018. Disponível na biblioteca virtual da Estácio. VAREJÃO, F. M. Linguagem de Programação: conceitos e técnicas. 2ª reimpressão. Rio de Janeiro: Elsevier, 2004. DIJKSTRA EW. A Discipline of Programming. Prentice Hall Inc;1976. 217. Explore+ Para saber mais sobre os assuntos tratadosneste tema, pesquise na internet sobre: • Objective C, que é a linguagem utilizada para desenvolvimento de aplicativos iOS e MacOS. • Plataformas sobre a linguagem Python. • Plataformas de programadores, para acompanhar as linguagens de programação mais usadas no mercado. • Visual Studio, que oferece uma cadeia de ferramentas em dispositivos móveis e na nuvem. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 34 Python Básico Introdução Atualmente, o ensino de disciplinas relacionadas à programação vem crescendo na educação básica e no ensino superior. Nos cursos de graduação voltados à tecnologia da informação, como Engenharia da Computação e Ciência da Computação, é frequente que se tenha contato com mais de uma disciplina focada em programação. Além desses cursos, outras carreiras têm contato com a programação em disciplinas como Introdução à Computação ou similares. Nesses diferentes contextos, várias linguagens de programação são usadas, tanto para o ensino como para o desenvolvimento de projetos. Dentre as diversas linguagens de programação que existem, Python é considerada uma das principais. Por sua simplicidade de aprendizado, ela tem sido utilizada em diversos cursos universitários como a primeira linguagem com que os alunos têm contato ao programar. Atualmente, conta com ampla participação da comunidade, além de ter seu desenvolvimento aberto e gerenciado pela organização sem fins lucrativos Python Software Foundation. Recentemente, a IEEE Computer Society classificou-a como a linguagem mais indicada para aprender em 2020. Isso se deve à sua eficiência no desenvolvimento de machine learning, inteligência artificial, ciência, gestão e análise de dados. IEEE Computer Society Grupo voltado à ciência da computação, engenharia elétrica e eletrônica, apoiando e organizando congressos no mundo todo e divulgando artigos científicos dessas áreas. Linguagem Python e suas principais características Conceitos Python é uma linguagem de programação de alto nível, que permite ao programador utilizar instruções de forma intuitiva, tornando seu aprendizado mais simples do que o aprendizado de uma linguagem de baixo nível. Nas linguagens de baixo nível, o programador precisa se expressar de forma muito mais próxima do que o dispositivo “entende”, levando naturalmente a um distanciamento da linguagem utilizada para comunicação entre duas pessoas. A linguagem Python foi lançada pelo holandês Guido van Rossum no início dos anos 1990 e desde então tem aumentado sua participação no mundo da programação. Permite uma programação fácil e clara para escalas pequenas e grandes, além de enfatizar a legibilidade eficiente do código, notadamente usando espaços em branco significativos. Características da linguagem Python A linguagem Python tem se tornado cada vez mais popular porque, além de permitir um rápido aprendizado inicial, tem características importantes, tais como: É multiparadigma Apesar de suportar perfeitamente o paradigma de programação estruturada, Python também suporta programação orientada a objetos, tem características do paradigma funcional, com o amplo uso de bibliotecas, assim como permite recursividade e uso de funções anônimas. É interativa Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 35 Permite que os usuários interajam com o interpretador Python diretamente para escrever os programas, utilizando o prompt interativo. Esse prompt fornece mensagens detalhadas para qualquer tipo de erro ou para qualquer comando específico em execução, suporta testes interativos e depuração de trechos de código. É híbrida quanto ao método de implementação Python usa uma abordagem mista para explorar as vantagens do interpretador e do compilador. Assim como Java, utiliza o conceito de máquina virtual, permitindo a geração de um código intermediário, mais fácil de ser interpretado, mas que não é vinculado definitivamente a nenhum sistema operacional. É portável Tem a capacidade de rodar em uma grande variedade de plataformas de hardware com a mesma interface. Ele roda perfeitamente em quase todos os sistemas operacionais, como Windows, Linux, UNIX, e Mac OS, sem nenhuma alteração. É extensível Permite que os programadores adicionem ou criem módulos e pacotes de baixo nível / alto nível ao interpretador Python. Esses módulos e pacotes de ferramentas permitem que os desenvolvedores tenham possibilidades amplas de colaboração, contribuindo para a popularidade da linguagem. Suporta bancos de dados Por ser uma linguagem de programação de uso geral, Python suporta os principais sistemas de bancos de dados. Permite escrever código com integração com MySQL, PostgreSQL, SQLite, ElephantSQL, MongoDB, entre outros. Suporta interface com usuário Permite escrever código de tal maneira que uma interface do usuário para um aplicativo possa ser facilmente criada, importando bibliotecas como Tkinter, Flexx, CEF Python, Dabo, Pyforms ou PyGUI wxPython. Pode ser usado como linguagem de script Permite fácil acesso a outros programas, podendo ser compilado para bytecode a fim de criar aplicativos grandes. Permite desenvolvimento de aplicações Web Devido à escalabilidade já citada, Python oferece uma variedade de opções para o desenvolvimento de aplicativos Web. A biblioteca padrão do Python incorpora muitos protocolos para o desenvolvimento da web, como HTML, XML, JSON, processamento de e-mail, além de fornecer base para FTP, IMAP e outros protocolos da Internet. Permite criação de aplicações comerciais É desenvolvido sob uma licença de código aberto aprovada pela OSI, tornando-o livremente utilizável e distribuível, mesmo para uso comercial. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 36 Instalação A linguagem Python tem seu site oficial, onde é possível encontrar as últimas versões lançadas, notícias e documentação, entre outras informações. A versão que utilizaremos neste tema é a 3.7.7. Por isso, recomendamos que ela seja baixada e instalada em seu computador. Também será necessário baixar e instalar a IDE PyCharm, usando a versão disponível em seu site. Utilitários e módulos Apenas como exemplo, na área de Console clique no botão Python Console. No prompt interativo >>> que se abrirá, digite x = 5 e pressione a tecla [ENTER] ou [RETURN] do seu teclado. Observe na figura 2 que, na área Árvore de exibição de variáveis, agora fica disponível a informação que a variável x é do tipo int e tem o valor 5. Fonte: Estácio de Sá Não se preocupe ainda com o conceito de variável, nem com o seu tipo. Veremos tudo isso com detalhes nos próximos módulos deste tema. O utilitário dir apresenta todos os atributos e métodos disponíveis para determinado tipo de dado. No prompt interativo >>>, digite dir(x) e pressione a tecla [ENTER] ou [RETURN] do seu teclado. Fonte: Estácio de Sá O utilitário help apresenta a documentação relativa a determinado tipo de dado. Caso você tenha alguma dúvida sobre o que é possível fazer com determinado tipo, os utilitários dir e help podem ser úteis. Blocos Em Python, os blocos são definidos pela indentação. Diferente de C e Java, que usam as chaves { e } para delimitar os blocos, em Python todos os blocos são iniciados com o símbolo : (dois pontos) na linha superior e representados pelo acréscimo de 4 (quatro) espaços à esquerda. Sem se preocupar por enquanto com o significado das expressões for, if, else ou range, observe a figura 4: Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 37 Fonte: Estácio de Sá A partir dessa figura, podemos destacar alguns pontos: Linha 1: Está mais à esquerda, assim como as linhas 2 e 11. Linha 2: Todas as linhas de 3 a 10 estão dentro do bloco do for da linha 2. Linha 3: Observe que a linha 3 temum if abrindo um bloco, dentro do qual estão as linhas 4 e 5. Linha 6: Por sua vez, a linha 6 tem um else abrindo outro bloco, composto pelas linhas de 7 a 10. Os blocos do if (linha 3) e do else (linha 6) estão no mesmo nível. Linha 7: Mostra outro if abrindo outro bloco – composto apenas pela linha 8 – que está no mesmo nível do bloco do else da linha 9 – composto apenas pela linha 10. Linha 11: Como a linha 11 está no mesmo nível da linha 2, ela não faz parte do bloco do for. Comentários Em Python, os comentários podem ser de uma linha ou de várias linhas. A tabela 1 mostra as formas de limitar um comentário, além de comparar essas formas em Python e C. Python C Comentários de uma linha Iniciados com # Iniciados com // Comentários com várias linhas Limitados por “”” (três aspas duplas) no início e no fim Iniciados com /* e encerrados com */ Atenção É importante lembrar que os comentários não são instruções a serem executadas. Então, você pode escrever de forma simples e objetiva, sem obedecer às regras de sintaxe da linguagem. Boas práticas de programação Ao começar sua jornada como programador, é importante perceber que existem algumas práticas que não são obrigatórias, mas podem ajudar muito no seu aprendizado. Além disso, podem permitir que você corrija mais rapidamente erros que podem surgir no futuro e tornam seu código mais fácil de ser compreendido por outro programador, favorecendo o trabalho em equipe. Vamos conhecer algumas delas: Uma prática muito importante é utilizar comentários no seu programa, explicando o que aquele trecho resolve. Uma característica marcante da comunidade de desenvolvedores Python é manter uma lista de propostas de melhorias, chamadas PEP (Python Enhancement Proposals). Dentre as PEPs, destaca-se a PEP8, que estabelece um guia de estilo de programação. Agora que já vimos os principais conceitos deste módulo, é hora de testar seus conhecimentos. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 38 Verificando o aprendizado 1. Analise as afirmativas a seguir: I. Python é uma linguagem livre de alto nível, orientada a objetos e de difícil leitura, pois não permite indentação de linhas de código. II. Python suporta a maioria das técnicas da programação orientada a objetos. III. A linguagem Python e seu interpretador estão disponíveis para as mais diversas plataformas. São corretas: A. Somente II. B. Somente III. C. Somente II e III. D. Somente I e II. 2. (2018/IF-MT/Informática) Sobre a linguagem Python, é incorreto afirmar que: A. Suporta os paradigmas: imperativo, orientado a objetos e funcional. B. Utiliza indentação para delimitar início e fim de blocos. C. A linguagem Python é distribuída sob licença que proíbe sua incorporação em produtos proprietários. D. Python é um software de código aberto. Uso de variáveis em Python Conceitos As variáveis são abstrações para endereços de memória que permitem que os programas fiquem mais fáceis de codificar, entender e depurar. Podemos entender que ao nomear uma variável com o identificador x, determinado espaço em memória passará a ter esse apelido. Em outras palavras, será possível acessar esse espaço de memória sabendo o seu apelido e, consequentemente, recuperar o valor guardado nele, que no nosso exemplo é 10. Uma analogia possível com o mundo real é com aquelas caixas de correio que ficam em frente às casas. Em Python, o operador de atribuição é o = (símbolo de igual). A instrução x = 10 atribui o valor 10 à variável x. No prompt interativo >>>, digite x = 10 e pressione a tecla [ENTER] ou [RETURN] do seu teclado. Em seguida, digite x e pressione a tecla [ENTER] ou [RETURN]. Fonte: Estácio de Sá Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 39 Atenção Observe na figura 5 que o retorno do Python Console foi 10. Se, posteriormente, você utilizar novamente o prompt interativo >>> para digitar x = 20 e pressionar a tecla [ENTER], você alterará o valor da variável x. Ou seja, você estará mudando o valor armazenado naquele espaço de memória, mas sem alterar seu apelido. Observe a figura 6: Fonte: Estácio de Sá Atenção Diferentemente de outras linguagens, como C ou Java, não é necessário declarar uma variável antes de utilizá- la em Python. Basta atribuir um valor inicial à variável e utilizá-la dali em diante. Embora não seja necessário declarar uma variável para utilizá-la, não é possível utilizar uma variável que não tenha recebido alguma atribuição de valor. No prompt interativo, digite b e pressione a tecla [ENTER] ou [RETURN]. Fonte: Estácio de Sá Veja na figura 7 que a mensagem de erro informa que o nome b não foi definido. Ou seja, não é possível determinar o valor atribuído a esse nome. Identificadores de variáveis Os identificadores das variáveis podem ser compostos por letras, o underline (_) e, com exceção do primeiro caractere, números de 0 a 9. Veja os exemplos: minhaVariavel, _variavel, salario1 e salario1_2 são válidos. 1variavel e salario-1 não são válidos. minhaVariavel e minhavariavel são identificadores de duas variáveis distintas. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 40 Mesmo que seja um identificador permitido, nem sempre um identificador é bom para uma variável. Tente utilizar nomes que ajudem a entender o significado da variável para ganhar tempo quando for entender o código posteriormente. Exemplo salario é um nome de variável melhor que s. Algumas palavras são consideradas reservadas e não podem ser usadas como identificadores de variáveis em Python. São elas: and, as, assert, break, class, continue, def, del, elif, else, except, exec, finally, for, from, global, if, import, in, is, lambda, not, or, pass, print, raise, return, try, while, with e yield. Amarrações Chamamos de amarração (binding) a associação entre entidades de programação. Veja alguns exemplos: • Variável amarrada a um valor • Operador amarrado a um símbolo • Identificador amarrado a um tipo O tempo em que a amarração ocorre é chamado de tempo de amarração. Cada linguagem pode ter os seguintes tempos de amarração: Tempo de projeto da linguagem Os símbolos são amarrados ao operador, como * (multiplicação), ou à definição das palavras reservadas. Tempo de implementação Ocorre em geral nos compiladores, como a definição de faixa de valores para determinado tipo. Tempo de compilação Associação da variável ao seu tipo. Lembre-se de que Python associa a variável ao tipo, como foi explicado na seção anterior. Tempo de ligação A ligação de vários módulos compilados previamente, como a chamada a uma função de um módulo importado. Em C, utilizamos a diretiva #include para termos permissão de utilizar as funções de determinada biblioteca. Em Python, utilizamos o import para isto. Tempo de carga Quando o programa é carregado. Por exemplo: endereços de memória relativos são substituídos por endereços absolutos. Tempo de execução Associação de valores a variáveis que dependam de entradas do usuário, por exemplo. A variável é vinculada ao valor apenas durante a execução do programa. O momento em que ocorre a ligação pode ser classificado como cedo (early binding) ou tardio (late binding). Quanto mais cedo ocorre a ligação, maior a eficiência de execução do programa, porém menor a flexibilidade das estruturas disponibilizadas. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 41 Amarração de tipo As amarrações de tipo vinculam a variável ao tipo do dado. Elas podem ser: Estáticas: Ocorrem antes da execução e permanecem inalteradas. Em C, declaramos int a. Dinâmicas: Ocorrem durante a execução e podem ser alteradas. É o caso do Python. Veja a figura 8: Fonte: Estácio de Sá Perceba que a chamada type (parâmetro) retorna o tipo doparâmetro informado entre parênteses. Observe que a variável valor recebeu 10 e, com isso, ficou vinculada ao tipo int. Porém, ao receber o valor ‘a’, passou a estar vinculada ao tipo str (string). Escopo de visibilidade O escopo define em quais partes do programa uma variável é visível. Cada nome de variável em Python tem seu escopo e fora desse escopo o nome não existe, gerando um erro quando se tenta referenciar esse nome. Quanto ao escopo, chamamos as variáveis de globais ou locais. Variáveis globais Todos os nomes atribuídos no prompt interativo ou em um módulo fora de qualquer função são considerados como de escopo global. Por exemplo, ao executar a instrução da figura 9, a variável x é uma variável global. Fonte: Estácio de Sá Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 42 Variáveis locais Para exemplificar o uso de variáveis com escopo local, vamos utilizar uma função definida pelo desenvolvedor. Não se preocupe com esse tipo de função por enquanto, você aprenderá mais detalhes posteriormente. Observe a figura 10: Fonte: Estácio de Sá As linhas 2, 3 e 4 compõem o bloco interno à função chamada multiplicador(). Embora as variáveis das linhas 2 e 7 tenham o mesmo nome, elas são abstrações a endereços de memória diferentes. Dentro da função multiplicador(), a chamada ao nome a recupera o valor 2. Fora da função multiplicador(), a chamada ao nome a recupera o valor 3. Veja a execução na figura 11: Fonte: Estácio de Sá Agora, observe a função multiplicador() com uma pequena alteração, em que retiramos a inicialização da variável a dentro da função. Fonte: Estácio de Sá Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 43 Na linha 6, ao se chamar a função multiplicador(), a variável a será procurada. Como não existe uma variável a no bloco interno da função, ela é procurada como variável global. Uma vez encontrada, o valor recuperado é 3. Ao executar esse exemplo, você verá: Fonte: Estácio de Sá Usamos este exemplo para mostrar que o interpretador Python pode procurar o mesmo nome de variável em diferentes escopos. A ordem utilizada para a procura é: 1. A chamada da função delimitadora 2. Variáveis globais 3. O módulo builtins Perceba que, se a variável a é inicializada na função multiplicador(), qualquer chamada a esse nome dentro da função resultará na referência a essa variável local. Mas seria possível alterar a variável a global com uma instrução dentro da função multiplicador()? Sim, utilizando-se a palavra reservada global. Veja como isso poderia ser feito na figura 14 e o seu resultado na figura 15: Fonte: Estácio de Sá Tipos de escopo Os tipos de escopo são: Estático O escopo é baseado na descrição textual do programa e as amarrações são feitas em tempo de compilação. É o caso de C, C++ e Java, por exemplo. Dinâmico O escopo é baseado na sequência de chamada dos módulos (ou funções). Por isso, as amarrações são feitas em tempo de execução. É o caso do Python. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 44 O fato de Python ser de escopo dinâmico traz alguns problemas, como a perda de eficiência – uma vez que os tipos precisam ser verificados em tempo de execução – e a redução na legibilidade – porque é difícil determinar a sequência exata de todas as chamadas de função. Tempo de vida Embora escopo e tempo de vida tenham uma relação próxima, eles são conceitos diferentes. Observe: • Escopo é um conceito textual • Tempo de vida é um conceito temporal As variáveis globais têm o tempo de vida que é o de execução do programa, ao passo que as variáveis locais somente existem no intervalo de duração da função ou do bloco a que se limitam. Constantes Em Python, não existe o conceito de constante. Se você precisar de uma constante ao longo de sua jornada como programador, atribua o valor a uma variável e tome cuidado para não mudar esse valor. Dica Uma dica é iniciar o nome dessa variável com c_ ou utilizar todas as letras maiúsculas, o que vai diferenciar essa variável das outras. Por exemplo, é possível utilizar a expressão c_PI = 3.141592 para armazenar o valor de PI e agilizar o cálculo de área e perímetro de um círculo, ou utilizar a expressão PRECISION = 0.001 para armazenar a precisão a ser utilizada em qualquer cálculo matemático no seu programa. É importante ficar atento ao uso correto das variáveis, especialmente observando as questões de escopo e visibilidade, para evitar que algum cálculo seja realizado corretamente, mas com resultado diferente do esperado por você ao programar. Verificando o aprendizado 1. (2017/IF-CE/Técnico de Laboratório/Informática) Considere o trecho do programa Python abaixo: def func(): x = 1 print(x) x = 10 func() print(x) Os valores impressos, ao se executar o programa, são, respectivamente: A. 1 e 1. B. 10. C. 1 e 10. D. 10 e 10. 2. (MS CONCURSOS/2016/Creci 1° Região (RJ)/Analista de TI) Qual alternativa representa a declaração de uma variável na linguagem de programação Python? A. var valor = 3; B. boolean inicio = falso; C. texto = ‘texto de exemplo’ D. int i = 1; Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 45 Tipos de dados e as expressões em Python Conceitos Neste módulo, você será apresentado aos tipos padrão incorporados ao interpretador Python. Os principais tipos internos são numéricos, sequenciais e dicionários. Classes, instâncias e exceções também são tipos padrão, mas não entraremos em detalhes neste tema. Para ter nosso primeiro contato com expressões em Python, use o prompt interativo >>>. Digite a expressão algébrica 5 + 8 e pressione a tecla [ENTER]. Observe o resultado na figura 16: Fonte: Estácio de Sá O Python Console permite que você calcule expressões algébricas como uma calculadora, além de executar instruções básicas em Python. Tipos numéricos Existem três tipos numéricos distintos em Python: inteiros, números de ponto flutuante e números complexos. Além disso, os booleanos são um subtipo dos inteiros. O tipo int É o tipo usado para manipular números inteiros. Fazendo uma analogia com a Matemática, o tipo int é usado para elementos do conjunto dos inteiros (Z). Diferentemente de outras linguagens, como C ou Java, a linguagem Python não limita o tamanho de uma variável de qualquer tipo, logo, não existe um valor inteiro máximo definido. O limite depende da quantidade de memória disponível no computador. De volta ao Python Console, veja algumas coisas interessantes. Digite 1_000_000 e pressione a tecla [ENTER]. Fonte: Estácio de Sá Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 46 Python permite que você utilize o underline (_) como separador de milhar. Isso ajuda a visualizar números com muitos dígitos. Para encerrarmos este primeiro contato com o tipo int, verifique que o valor do exemplo anterior é inteiro. Digite type(1_000_000) e pressione a tecla [ENTER]. Fonte: Estácio de Sá O tipo float É o tipo usado para manipular números com parte inteira e parte decimal, chamados de números de ponto flutuante. Fazendo uma analogia com a Matemática, o tipo float é usado para elementos do conjunto dos reais (R). Para diferenciar um número real de um inteiro, é possível utilizar a parte decimal zerada. No prompt interativo >>>, digite type(50.0) e pressione a tecla [ENTER]. Fonte: Estácio de Sá Atenção Vale ressaltar que devemos usar o ponto para separar a parte inteira da parte decimal, e não a vírgula. No prompt, digite 50.0 (com ponto) e pressione [ENTER]. Em seguida, digite 50,0 (com vírgula) e pressione a tecla [ENTER]. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 47 Fonte: Estácio de Sá Ao usara vírgula como separador em Python, o que ocorre, na verdade, é a criação de uma tupla de dois elementos, e não o tipo float. Você verá mais detalhes sobre tuplas em um momento posterior. Embora os tipos int e float sejam semelhantes, por tratarem de números, eles têm propriedades um pouco diferentes. Em expressões algébricas, sempre que somamos, subtraímos ou multiplicamos apenas elementos do tipo int, o resultado é int. Porém, basta um operando do tipo float para que o resultado seja float. Observe a figura 21: Fonte: Estácio de Sá Vamos analisar a exponenciação. Para realizar essa operação matemática, utilizamos o operador (**). Veja a figura 22: Fonte: Estácio de Sá Veja que basta que a base seja float para que o resultado também o seja. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 48 Atenção Diferentemente de outras linguagens, como C, a divisão de dois números inteiros não necessariamente tem resultado inteiro. Digite 5/2 e pressione [ENTER]. Fonte: Estácio de Sá Para obter o quociente inteiro e o resto, quando dois inteiros são divididos, é necessário utilizar os operadores // e %, respectivamente. Ao dividir 21 por 2, temos quociente 10 e resto 1. Observe a figura 24: Fonte: Estácio de Sá O tipo complex É o tipo utilizado para manipular números complexos, na forma x + yj, sendo x a parte real e y a parte imaginária do complexo. Veja dois exemplos de variáveis do tipo complex nas figuras 25 e 26, em que a parte real é 2 e a parte imaginária é 5: Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 49 Fonte: Estácio de Sá A chamada r.conjugate() retorna o conjugado do número complexo r, em que a parte real é mantida e a parte imaginária tem o seu sinal trocado. O tipo bool Uma expressão algébrica, como vimos nos exemplos dos tipos int e float, é avaliada como um número, seja desses tipos ou de outro tipo numérico admitido em Python. Porém, utilizar expressões não algébricas também é bastante comum. E uma boa notícia é que Python pode avaliar expressões desse tipo também. Essa é uma diferença entre Python e outras linguagens, como C, por exemplo, em que não existe o tipo bool. No prompt interativo >>>, digite a expressão 2 < 3 e pressione [ENTER]. Observe o resultado na figura 27: Fonte: Estácio de Sá Repare que o resultado dessa expressão não é um número, mas sim a palavra True. Caso você colocasse a expressão 2 > 3, o resultado seria False, como pode ver na figura 28. Fonte: Estácio de Sá Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 50 Saiba mais As expressões que você viu nos dois exemplos são chamadas de expressões booleanas. Trata-se de expressões que podem ser avaliadas com um dos dois valores booleanos: True ou False. Assim, em Python, existe o tipo bool, utilizado para permitir o tratamento de expressões como essas. Agora, vamos ver o operador not, que é um operador unário, ou seja, só precisa de um operando. Esse operador inverte o valor booleano, ou seja, se o valor original for True, not(valor) terá o valor False. E vice-versa. No prompt interativo >>>, digite a expressão not(2 < 3) e pressione [ENTER]. Fonte: Estácio de Sá É possível também escrever expressões booleanas compostas, utilizando conectivos como E OU. Vamos ver mais detalhes sobre essas expressões ainda neste módulo. Operadores numéricos Operadores matemáticos Os operadores matemáticos são muito semelhantes àqueles que vimos ao longo de nossa jornada como estudantes, aprendendo Álgebra e Aritmética na escola. Existem algumas pequenas diferenças, como a divisão (que pode ser a usual ou a divisão inteira). Mas é possível identificar operações que fizemos ao longo de toda nossa vida. A tabela 2 lista os operadores de expressão aritmética disponíveis em Python. Operação matemática Símbolo usado Exemplo Equação Resultado Soma + 2.5 + 1.3 3.8 Subtração - 2.5 - 1.3 1.2 Multiplicação * 2.5 * 1.3 3.25 Divisão / 2.5/1.3 1.923076923076923 Divisão inteira // 9/2 4 Resto na divisão inteira % 9%2 1 Valor absoluto abs(parâmetro) abs(-2.5) 2.5 Exponenciação ** 2**4 16 Além das operações algébricas, é possível realizar operações de comparação. Os operadores de comparação têm como resultado um valor booleano (True ou False). Observe a tabela 3: Símbolo usado Descrição < Menor que <= Menor ou igual a > Maior que >= Maior ou igual a == Igual != Não igual Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 51 Atenção Cabe observar que o operador utilizado para comparar se dois valores são iguais é o ==, ou seja, duplo sinal de igual. Tome cuidado para não confundir com o operador de atribuição, que é representado pelo sinal de igual apenas uma vez (=). Existe outra lista de operadores que executam operações matemáticas, mas, além disso, atualizam o valor da variável utilizada. Eles são chamados de operadores compostos e serão detalhados no módulo 4. Para mais funções matemáticas, você pode utilizar os módulos matemáticos math e fractions. Operadores booleanos As expressões booleanas são aquelas que podem ter como resultado um dos valores booleanos: True ou False. É comum utilizarmos os operadores de comparação em expressões booleanas, mas não só eles. Assim como é possível escrever expressões algébricas complexas concatenando diversas expressões menores, podemos escrever expressões booleanas grandes, com os operadores and, or e not. Observe o comportamento dos operadores booleanos nas tabelas 4, 5 e 6. p not (p) True False False True p q p and q True True True True False False False True False False False False p q p or q True True True True False True False True True False False False Tipos sequenciais Existem três tipos sequenciais básicos em Python: listas, tuplas e objetos range. Além dos tipos básicos citados, existe um tipo especial criado para tratamento de dados textuais: o tipo str (string). Assim como em C ou Java, a indexação dos itens é iniciada com 0 e cada item tem o seu índice incrementado uma unidade em relação ao item anterior. Porém, Python também permite a indexação com valores negativos. O valor -1 é o índice do último item, e cada item anterior é decrementado de uma unidade em relação ao sucessor. Observe a tabela 7: índice 0 1 2 3 4 s t e s t e índice negativo -5 -4 -3 -2 -1 Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 52 Strings Em uma variável do tipo str, é possível armazenar letras, números, espaços, pontuação e diversos símbolos. Diferentemente da linguagem C, não existe o tipo char. Cada caractere em Python é uma string. Para delimitar uma string, podemos utilizar: 1. Aspas simples: ‘uma string' 2. Aspas duplas: “uma string” 3. Aspas simples triplas: ‘’’uma string’’’ 4. Aspas duplas triplas: “””uma string””” A Figura 30 ilustra um exemplo de delimitadores de strings: Fonte: Estácio de Sá Existem alguns métodos interessantes para tratar strings em Python. Entre eles, ressaltamos: 1. upper: Transforma todas as letras em maiúsculas. 2. lower: Transforma todas as letras em minúsculas. 3. split: Quebra a string em substrings. Veja os exemplos a seguir: Fonte: Estácio de Sá Saiba mais A lista gerada com o método split() tem três elementos, porque a string original tinha três palavras. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 53 Listas Listas são sequências mutáveis, normalmente usadas para armazenar coleções de itens homogêneos. Uma lista pode ser criada de algumas maneiras, tais como: [ ] → Usando um par de colchetes para denotar uma lista vazia. [a], [a, b, c] → Usando colchetes, separando os itens por vírgulas. [x for x in iterable] → Usando a compreensãode lista. list() ou list(iterable) → Usando o construtor do tipo list. Saiba mais iterable pode ser uma sequência, um container que suporte iteração ou um objeto iterador. Por exemplo, list('abc') retorna ['a', 'b', 'c'] e list( (1, 2, 3) ) retorna [1, 2, 3]. Se nenhum argumento for passado, o construtor cria uma lista vazia: [ ]. Tuplas Tuplas são sequências imutáveis, tipicamente usadas para armazenar coleções de itens heterogêneos. Elas são aplicadas também quando é necessário utilizar uma sequência imutável de dados homogêneos. Uma tupla pode ser criada de algumas maneiras, tais como: ( ) → Usando um par de parênteses para denotar uma tupla vazia. a, b, c ou (a, b, c) → Separando os itens por vírgulas. tuple() ou tuple(iterable) → Usando o construtor do tipo tuple. Novamente, iterable pode ser uma sequência, um container que suporte iteração ou um objeto iterador. Por exemplo, tuple('abc') retorna ('a', 'b', 'c') e tuple( [1, 2, 3] ) retorna (1, 2, 3). Se nenhum argumento for passado, o construtor cria uma tupla vazia: (). Atenção Note que o uso das vírgulas é o que gera a tupla, e não o uso de parênteses. Os parênteses são opcionais, exceto no caso em que queremos gerar uma tupla vazia. Range O tipo range representa uma sequência imutável de números e frequentemente é usado em loops de um número específico de vezes, como o for. Ele pode ser chamado de maneira simples, apenas com um argumento. Nesse caso, a sequência começará em 0 e será incrementada de uma unidade até o limite do parâmetro passado (exclusive). Por exemplo, range(3) cria a sequência (0, 1, 2). Para que a sequência não comece em 0, podemos informar o início e o fim como parâmetros, lembrando que o parâmetro fim não entra na lista (exclusive o fim). O padrão é incrementar cada termo em uma unidade. Ou seja, a chamada range(2, 7) cria a sequência (2, 3, 4, 5, 6). Saiba mais Também é possível criar sequências mais complexas, indicando os parâmetros de início, fim e passo, nessa ordem. O passo é o valor que será incrementado de um termo para o próximo. Por exemplo, range(2, 9, 3) cria a sequência (2, 5, 8). Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 54 Operadores sequenciais comuns Os operadores sequenciais permitem a manipulação dos tipos sequenciais, inclusive as strings. Vale ressaltar a sobrecarga dos operadores + e *, que realizam operações diferentes quando os operandos são numéricos ou sequenciais. Exemplo O operador == verifica se as strings dos dois lados são iguais. Porém, os operadores < e > comparam as strings usando a ordem do dicionário. A tabela a seguir traz um pequeno conjunto dos operadores disponíveis em Python para manipulação de sequências. Lembre-se de que você pode utilizar o utilitário help no Python Console para verificar a lista completa. Para isso, basta digitar help(str) e pressionar [ENTER] no teclado. Uso Resultado x in s True se x for um subconjunto de s x not in s False se x for um subconjunto de s s + t Concatenação de s e t n*s Concatenação de n cópias de s s[i] Caractere de índice i em s len(s) Comprimento de s min(s) Menor item de s max(s) Maior item de s Dicionários Os dicionários permitem que itens de uma sequência recebam índices definidos pelo usuário. Um dicionário contém pares de (chave, valor). O formato geral de um objeto dicionário é: {<chave 1>:<valor 1>, <chave 2>:<valor 2>, ..., <chave i>:<valor i>} Exemplo Poderíamos criar um dicionário em que cada pessoa fosse representada pelo seu CPF, com nome e sobrenome. Para isso, teríamos: Fonte: Estácio de Sá Na figura 32, o dicionário tem 3 entradas. Observe como foi possível recuperar nome e sobrenome de uma entrada, baseado na chave informada ‘111222333-44’. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 55 Precedência de operadores Ao escrever uma expressão algébrica, o programador pode utilizar a precedência de operadores existente em Python (implícita) ou explicitar a ordem em que ele deseja que a expressão seja avaliada. Exemplo Por exemplo, a expressão 3 + 2 * 5 tem como resultado 25 ou 13? Aprendemos no ensino fundamental que as operações de produto e divisão têm precedência sobre as operações de soma e subtração. Ou seja, um produto será realizado antes de uma soma, na mesma expressão. Assim, a expressão acima tem como resultado 13. Isso ocorre sempre que não forem explicitadas outras relações de precedência com o uso de parênteses. Caso o programador quisesse forçar que a soma ocorresse primeiro, ele deveria escrever assim: (3 + 2) * 5. Sempre que o programador quiser forçar a ocorrência de uma operação antes de outras, ele pode utilizar os parênteses para aumentar a prioridade sobre ela. A tabela a seguir traz as relações de precedência entre os operadores, com as linhas mais altas tendo prioridade sobre as linhas mais baixas. Ou seja, elas ocorrem primeiro. Dentro da mesma linha, a precedência é da esquerda para a direita. Operador Descrição [expressões ...] Definição de lista x[ ], x[índice : índice] Operador de indexação ** Exponenciação +x, -x Sinal de positivo e negativo *, /, //, % Produto, divisão, divisão inteira, resto +, - Soma, subtração in, not in, <, <=, >, >=, <>, !=, == Comparações, inclusive a ocorrência em listas not x Booleano NOT (não) and Booleano AND (e) or Booleano OR (ou) Atenção É importante ficar atento ao uso correto dos operadores, respeitando a precedência entre eles, para evitar que algum cálculo seja realizado corretamente, mas com resultado diferente do esperado por você ao programar. Conversões de tipos Quando temos tipos diferentes envolvidos na mesma expressão, o Python converte implicitamente cada operando para o tipo mais abrangente envolvido na expressão. Estamos usando a palavra abrangente, mas poderíamos falar que existem tipos que englobam (ou contêm) outros. Exemplo Um número do tipo int pode ser visto como um float com a parte decimal nula. Porém, o inverso não é verdade. Ou seja, o conjunto dos inteiros (int) é um subconjunto do conjunto dos reais (float). Assim, a expressão 5 + 0.68 – que envolve um int e um float – tem como resultado 5.68. O inteiro 5 é convertido pelo Python para o número de ponto flutuante 5.0 antes que a soma (de dois valores float) seja realmente efetuada. Uma conversão implícita não intuitiva é a dos valores booleanos True e False em inteiros, respectivamente, 1 e 0. Veja os exemplos a seguir: Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 56 Fonte: Estácio de Sá Com isso, podemos perceber a seguinte relação entre os tipos bool, int e float: Fonte: Estácio de Sá Além das conversões implícitas, o programador também pode usar as conversões explícitas, quando ele força que o valor seja tratado como de determinado tipo. Para isso, é necessário usar o construtor do tipo desejado, com o valor passado como parâmetro (entre parênteses). Veja o exemplo a seguir: Fonte: Estácio de Sá O int 2 pode ser tratado naturalmente como o float 2.0, basta acrescentar a parte decimal nula. Porém, ao tentar tratar um float como int, ocorre a remoção da parte decimal. Atenção Fique atento, porque não é uma aproximação para o inteiro mais próximo, e sim o truncamento. Agora que você já viu os principais tipos de dados suportados em Python, vamos exercitar e verificar o aprendizado. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 57 Verificando o aprendizado 1. Considere a expressão a seguir: 2 + 3 – 4 ** 2 + 5 / 2 – 5 // 2. Assinale a opção com o valor correto dessa expressão em Python. A. -10.5 B. -1 C. 1.5 D. 2 2. (Adaptada de COMPERVE/2019/UFRN/Engenharia da Computação) Python é uma linguagem interpretada muito utilizada. Não requer tipagem de variáveis e sua sintaxeindentada favorece a organização do código. Uma das suas funcionalidades mais poderosas são as listas. Considere o código em Python do quadro abaixo: Fonte: Estácio de Sá A saída correta correspondente às linhas 2 e 4 do código é: A. 2 e 4. B. 4 e 16. C. 2 e 16. D. 4 e 4. O operador + realiza operações de soma para tipos numéricos e concatenação para tipos sequenciais. Assim, a variável a na linha 1 passa a ser composta dos itens ‘UF’ e ‘RN’. Assim, a chamada len(a) retorna o tamanho 2, número de elementos de a. De forma semelhante, o operador * realiza operações de multiplicação para tipos numéricos e concatenação de cópias para tipos sequenciais. Assim, a variável b na linha 3 passa a ser a lista ['4', '4', '4', '4']. E a chamada len(b) retorna o tamanho 4, número de elementos de b. Formas de atribuição, de entrada e saída de dados em Python Conceitos Já vimos, de maneira básica, como podemos atribuir valor a uma variável, no módulo 2. Vamos agora conhecer outras formas de atribuição. Sentenças de atribuição Atribuição simples Chamamos de atribuição simples a forma que já utilizamos neste tema, com uma expressão parecida com x = 10. Nessa atribuição, a variável x recebe o valor 10. Atribuição múltipla Python também permite a atribuição múltipla, ou seja, mais de uma variável receber atribuição na mesma linha. Veja o exemplo na figura 36: Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 58 Fonte: Estácio de Sá Atenção Observe que as variáveis x e y receberam atribuição na mesma instrução, com a variável x armazenando o valor 2, e a variável y armazenando o valor 5. Operadores de atribuição compostos Os operadores de atribuição compostos executam operações matemáticas e atualizam o valor da variável utilizada. Por exemplo, veja a figura 37: Fonte: Estácio de Sá A variável x inicialmente recebeu o valor 10. Em seguida, a instrução x = x + 1, que causa estranheza quando lembramos da matemática aprendida ao longo da vida, é muito comum quando estamos programando. Essa instrução significa “acrescente uma unidade ao valor de x e guarde este resultado na própria variável x”. Como x valia 10, o resultado do lado direito do operador (=) é 11. Esse resultado é, então, armazenado na própria variável x. Essa operação de acrescentar determinado valor a uma variável e armazenar o resultado na própria variável poderia ser feita com o operador += (mais igual). Veja a figura 38: Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 59 Fonte: Estácio de Sá Na tabela 10, estão os operadores compostos disponíveis em Python. Considere a variável x, com o valor inicial 10, para verificar os resultados. Nome Símbolo usado Exemplo Instrução Resultado Mais igual += x += 2 x passa a valer 12 Menos igual -= x -= 2 x passa a valer 8 Vezes igual *= x *= 2 x passa a valer 20 Dividido igual /= x /= 2 x passa a valer 5 Módulo igual %= x %= 3 x passa a valer 1 Atenção Diferente de C, em Python não é possível incrementar ou decrementar uma variável com um operador unário, como o ++ ou --. Troca de variáveis Um dos problemas iniciais que envolvem atribuição de valores a variáveis é a troca entre duas delas. Suponha que as variáveis a e b armazenem, respectivamente, os valores 1 e 2. Caso quiséssemos inverter os valores em linguagens como C ou Java, seria necessário usar uma variável auxiliar, com uma sequência de instruções exibida na figura a seguir: Fonte: Estácio de Sá Em Python, é possível fazer essa troca de uma maneira muito mais fácil. Veja o uso da atribuição múltipla, nesse caso, na figura a seguir: Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 60 Fonte: Estácio de Sá O primeiro programa em python Para escrever um programa em Python, será essencial utilizar as formas de saída de dados para exibir ao usuário mensagens e resultados de operações. Caso você deseje que o usuário informe algum dado para que seu programa processe, será necessário utilizar as formas de entrada de dados. Para criar seu primeiro programa, clique com o botão direito do mouse no nome do projeto, na guia de navegação do lado esquerdo. Em seguida, escolha a opção New > File. Fonte: Estácio de Sá Atribua um nome ao seu arquivo. Neste primeiro exemplo, vamos chamar de primeiro_programa.py Fonte: Estácio de Sá Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 61 Ao nomear o arquivo, será aberta uma nova aba, do lado direito, com o espaço para que você efetivamente digite as instruções. Fonte: Estácio de Sá Saída de dados com a função print() A função print() em Python atua de forma semelhante à printf() em C. Para um programador iniciante, as maiores diferenças entre elas são: • Duas chamadas da print() em Python são impressas na tela em linhas diferentes, sem a necessidade do uso do caractere ‘\n’ para pular a linha, como ocorre na printf() em C. • Uma chamada da print() em Python permite a impressão de valores de variáveis sem a indicação do formato, como ocorre na printf() em C, quando precisamos escrever %c, %d ou %f, por exemplo. Para escrever seu Hello World em Python, digite a seguinte linha, exatamente como está escrita: print(“Hello World”) Em seguida, clique com o botão direito do mouse sobre o nome do programa e escolha a opção Run ‘primeiro_programa’. Também é possível executar com a combinação de teclas CTRL+Shift+ F10. Após executar, observe o console na figura 44: Fonte: Estácio de Sá Veja que foi impresso no console exatamente o que colocamos entre aspas, ao chamar a função print(). Essa é a primeira forma de saída de dados: usar a função print() com uma string sendo passada como parâmetro (entre os parênteses). É importante perceber que a função print(), além de imprimir a string, também salta o cursor para a próxima linha. Como você deve ter percebido, o que a função print() recebeu entre parênteses foi uma string. Ou seja, poderíamos ter passado para ela uma string definida anteriormente, como no exemplo a seguir: Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 62 Fonte: Estácio de Sá Também poderíamos ter passado como parâmetro uma variável definida anteriormente. A função print() vai trabalhar com o valor dessa variável. Observe as figuras 46 e 47: Fonte: Estácio de Sá Entrada de dados com a função input() Quando o programador quiser que o usuário entre com algum valor, ele deverá exibir na tela o seu pedido. Em C, é necessário utilizar a função printf() para escrever a solicitação ao usuário e a função scanf() para receber a entrada e armazenar em uma variável. Em Python, é possível utilizar a função input(). Ela tanto exibe na tela o pedido, como permite que o valor informado pelo usuário seja armazenado em uma variável do seu programa. Analise a figura 48: Fonte: Estácio de Sá A linha 1 fará com que a frase Entre com seu nome: seja exibida no console, mas a execução do programa fica travada até que o usuário aperte [ENTER] no teclado. Tudo o que foi digitado até o [ENTER] Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 63 vai ser armazenado na variável nome. A linha 2 fará a exibição do conteúdo da variável nome. Veja o resultado no console, com o usuário tendo digitado Fulano de Tal. Fonte: Estácio de Sá Atenção É importantíssimo perceber que a função input() trata tudo o que for digitado pelo usuário como uma string, armazenando na variável designada pelo programador para isso. Mesmo que o usuário entre com apenas uma letra ou um número, isso será armazenado como uma string na variável. Vamos analisar o exemplo a seguir: Fonte: Estácio de Sá Veja o console quando o programa é executado: Fonte: Estácio de Sá Apostila de Paradigmasde Linguagens de Programação em Python Marcio Quirino - 64 O usuário digitou 3 e [ENTER]. Mesmo sendo um valor, a variável numero trata como a string ‘3’. Isso impede que seja realizada a operação de soma com o inteiro 2, por exemplo. Poderíamos também usar a instrução print(type(numero)) na linha 2 para confirmar. Veja: Fonte: Estácio de Sá A função eval() A função eval() recebe uma string, mas trata como um valor numérico. Veja o exemplo a seguir: Fonte: Estácio de Sá Mesmo tendo recebido a string ‘1+2’ como parâmetro, a função eval() efetuou a soma de 1 com 2. Observe que confirmamos que s é uma string com a instrução type(s). Para tratar a entrada do usuário como um número e, com isso, realizar operações algébricas, por exemplo, é necessário utilizar a função eval() em conjunto com a input(). Veja o próximo exemplo: Fonte: Estácio de Sá Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 65 Mão na massa Como exercício prático, tente escrever um programa para calcular e informar o IMC (índice de massa corpórea) do usuário, que deverá fornecer seu peso e sua altura. Lembre-se que o IMC é calculado pela fórmula: Uma solução simples é a da figura 57: Fonte: Estácio de Sá Saída formatada de dados Quando desejamos que a saída siga determinado padrão – por exemplo, de hora ou de data – existem algumas possibilidades para usar a função print(). É sempre possível utilizar a concatenação de strings, com o operador +, para montar a frase como quisermos. Suponha que tenhamos as seguintes variáveis: hora = 10 minutos = 26 segundos = 18 Poderíamos chamar a função print() com o separador : da seguinte forma: Fonte: Estácio de Sá Porém, existe outra possibilidade, usando o método format(). Ele permite que a chamada à função print() fique muito parecida com as chamadas à função printf() em C, com passagem de parâmetros a serem colocados em ordem na string. Com o método format(), podemos montar a string com as chaves {} indicando onde entrarão valores, passados como parâmetros separados por vírgulas. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 66 Fonte: Estácio de Sá É importante observar que a quantidade de chaves precisa ser igual à quantidade de variáveis passadas como parâmetros no método format(). Seria muito bom se não precisássemos nos preocupar com essa correspondência para evitar erros bobos. E isso é possível! Para tornar a saída formatada ainda mais intuitiva, basta utilizar a letra ‘f’ no início dos parâmetros da função print() e colocar cada variável dentro das chaves na posição em que deve ser impressa. Veja como fica ainda mais fácil entender: Fonte: Estácio de Sá Também é possível especificar a largura de campo para exibir um inteiro. Se a largura não for especificada, ela será determinada pela quantidade de dígitos do valor a ser impresso. Veja a figura 62: Fonte: Estácio de Sá Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 67 Observe que os valores 10 e 100 foram impressos com espaços em branco à esquerda. Isso ocorreu porque definimos que a primeira variável deveria ser impressa com 4 espaços com {:4} (2 foram ocupados e 2 ficaram em branco), e que a segunda variável deveria ser impressa com 5 espaços com {:5} (3 foram ocupados e 2 ficaram em branco). Saiba mais Também é válido perceber que o padrão é alinhar os valores à direita do espaço reservado para a impressão da variável. O método format() também pode ser usado para imprimir valores de ponto flutuante com a precisão definida. Veja a figura 63: Fonte: Estácio de Sá Ao usar {:8.5}, estamos determinando que a impressão será com 8 espaços, mas apenas 5 serão utilizados. Impressão de sequências Python também permite a impressão de sequências com mais possibilidades que C, incluindo as strings. Para imprimir um vetor em C, por exemplo, precisamos chamar a printf() item a item. Em Python, basta chamar a função print() passando como parâmetro a sequência. Veja a figura 64: Fonte: Estácio de Sá Para imprimir uma substring, por exemplo, basta utilizar os colchetes para indicar o intervalo de índices que deve ser impresso. Vale lembrar que o primeiro caractere da string é indexado com 0. Veja a figura 65: Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 68 Fonte: Estácio de Sá Atenção Usar [0:4] provoca a impressão dos índices 0, 1, 2 e 3, mas não do índice 4. Analogamente, usar [2:8] provoca a impressão dos índices de 2 a 7, mas não do 8. Também é possível imprimir a string como lida da direita para a esquerda. Para isso, deve-se utilizar [: : -1]. Esse valor -1 indica que a leitura dos caracteres será feita no sentido oposto ao tradicional. Observe a figura 66: Fonte: Estácio de Sá Atenção Fique atento quando utilizar o intervalo na impressão no sentido inverso, porque os limites do intervalo devem respeitar esse sentido. Verificando o aprendizado 1. (2015/PGE-RO/Técnico da Procuradoria/Tecnologia da Informação) Na linguagem Python, um comando como a=input("XXX") provoca: A. A associação à variável “a" de uma função denominada “XXX" que pertence à biblioteca “input". B. A criação de uma lista de valores denominada “a" cujo elemento inicial é a string “XXX". C. A leitura de um valor do arquivo de entrada correntemente designado de acordo com um formato expresso pela string “XXX". D. Um prompt no dispositivo de saída e a leitura de um valor que é armazenado na variável “a". Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 69 2. (2015/TJ-BA/Analista Judiciário/Tecnologia da Informação/Reaplicação) Analise o trecho de programa Python apresentado a seguir. L = [1,2,3,4,5,6,7,8] print L[::-1] Ao ser executado, o resultado exibido é: A. [1, 2, 3, 4, 5, 6, 7, 8] B. [8] C. [ ] D. [8, 7, 6, 5, 4, 3, 2, 1] A impressão da sequência L com a chamada L[::-1] é feita percorrendo toda a sequência L, em sentido inverso. Considerações finais Neste tema, você conheceu as principais características da linguagem Python e alguns conceitos relativos ao uso de variáveis, bem como aspectos concernentes à vinculação, como tempo e ambientes. Além disso, viu o conceito de escopo de visibilidade, tanto estático como dinâmico. Nos módulos, foi feita uma referência a tipos de dados, como int, float ou string. Você estudou ainda as formas de atribuição, além de ter escrito seu primeiro programa em Python, utilizando o que aprendeu e, também, as formas de entrada e saída de dados que a linguagem oferece. O uso correto desses conceitos é essencial na sua jornada de formação como programador. Recomendamos que fique atento aos detalhes e procure sempre programar de forma organizada. Isso vai evitar erros bobos e tornar sua experiência mais agradável. Referências BELANI, G. Programming Languages You Should Learn in 2020. Consultado em meio eletrônico em: 26 mai. 2020. PERKOVIC, L. Introdução à computação usando Python: um foco no desenvolvimento de aplicações. Rio de Janeiro: LTC, 2016. PYCHARM. The Python IDE for Professional Developers. Consultado em meio eletrônico em: 15 jun. 2020. PYTHON. PEP 0 – Index of Python Enhancement Proposals (PEPs). Consultado em meio eletrônico em: 2 jun. 2020. PYTHON. Fractions – Rational Number. Consultado em meio eletrônico em: 16 jun. 2020. PYTHON. Math – Mathematical Functions. Consultado em meio eletrônico em: 16 jun. 2020. Explore+ Se você quiser ter mais exercícios para treinar e desafios mais complexos, recomendamos a visita ao site da Python Brasil. Como vimos, dentre as PEPs, destaca-se a PEP8, que estabelece um guia de estilo de programação. Sugerimos que você pesquise mais sobre isso. Para mais funções matemáticas, você pode utilizar os módulos matemáticos mathe fractions. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 70 Python Estruturado Introdução Programar significa, como em qualquer disciplina, aprender ferramentas que permitam desenvolver melhor a sua atividade. Ao aprender os conceitos básicos de programação, o estudante desenvolve habilidades iniciais para escrever seus primeiros programas. Porém, é difícil imaginar que aplicações profissionais sejam feitas totalmente baseadas apenas nesses conceitos básicos. Ao pensarmos em aplicações mais complexas, é essencial considerar a necessidade de ganhar tempo, com o computador executando as tarefas repetitivas, e as demandas de manutenção e tratamento de erros. Para avançar no aprendizado da programação, você conhecerá novas ferramentas, entre elas as estruturas de controle, como decisão e repetição, além dos subprogramas e bibliotecas, bem como as formas de tratar exceções e eventos. Vamos começar nossa jornada acessando os códigos-fontes originais propostos para o aprendizado de Python estruturado. Baixe o arquivo aqui, descompactando-o em seu dispositivo. Assim, você poderá utilizar os códigos como material de apoio ao longo do tema! Estruturas de decisão e repetição em Python As estruturas de controle permitem selecionar quais partes do código serão executadas – chamadas de estruturas de decisão – e repetir blocos de instruções com base em algum critério, como uma variável de controle ou a validade de alguma condição – chamadas de estruturas de repetição. Neste módulo, vamos conhecer as estruturas de decisão e de repetição em Python. Tratamento das condições As estruturas de decisão e de repetição possuem sintaxes bastante semelhantes em C e em Python. Mesmo com essa grande semelhança, existe uma diferença crítica no tratamento das condições. Diferentemente da linguagem C, Python oferece o tipo bool. Por isso, cabe ressaltar a diferença de comportamento das duas linguagens nesse tratamento. Python C Existe o tipo bool Não existe o tipo bool True Qualquer valor diferente de 0 (zero) False 0 (zero) ou vazio Atenção Observe que o fato de haver o tipo bool em Python permite que as condições sejam tratadas como verdadeiras ou falsas, o que não é exatamente igual em C. As Estruturas de decisão If, If-Else E Elif Em Python, é possível utilizar as estruturas de decisão if e if-else da mesma forma que em C. A diferença principal é o modo de delimitar os blocos de instruções relativos a cada parte da estrutura. Observe a Tabela 2 e a Tabela 3: Python C if <condição>: if <condição>{ Instruções com 4 espaços de indentação A indentação não é exigida Instrução fora do if } Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 71 Python C if <condição>: if <condição>{ Instruções com 4 espaços de indentação (caso a condição seja verdadeira) Bloco 1 (caso a condição seja verdadeira). A indentação não é exigida else: } else { Instruções com 4 espaços de indentação (caso a condição seja falsa) Bloco 2 (caso a condição seja falsa). A indentação não é exigida Instrução fora do if } Python também oferece a estrutura elif, que permite o teste de duas condições de forma sequencial. Essa estrutura não existe em C, sendo necessário o encadeamento de estruturas if-else. Em geral, o formato da estrutura elif é: if <condição 1>: Bloco de código que será executado caso condição seja True elif <condição 2>: Bloco de código que será executado caso condição 1 seja False e condição 2 seja True else: Bloco de código que será executado caso condição 1 seja False e condição 2 seja False Instrução fora do if Veja uma implementação possível com a estrutura elif na Figura 1: idade = eval(input('Informe a idade da criança: ')) if idade < 5: print('A criança deve ser vacinada contra a gripe.') print('Procure o posto de saúde mais próximo.') elif idade == 5: print('A vacina estará disponível em breve.') print('Aguarde as próximas informações.') else: print('A vacinação só ocorrerá daqui a 3 meses.') print('Informe-se novamente neste prazo.') print('Cuide da saúde sempre. Até a próxima.') Perceba que a indentação precisa ser ajustada, uma vez que o último else é relativo ao elif. Por isso, eles precisam estar alinhados. Estrutura de repetição for A estrutura de repetição for tem funcionamento muito semelhante nas linguagens C e Python. Porém, a sintaxe é diferente nas duas linguagens. Além disso, em Python existe maior flexibilidade, já que a repetição pode ser controlada por uma variável não numérica. Antes de detalhar o for, vamos conhecer uma função de Python que gera uma lista de valores numéricos. Essa lista ajudará a verificar a repetição e deixará mais claro o entendimento do laço. As listas do tipo range() Ao chamar o método range(), Python cria uma sequência de números inteiros, de maneira simples à mais complexa. Veja a seguir: Simples Ela pode ser chamada de maneira simples, apenas com um argumento. Nesse caso, a sequência começará em 0 e será incrementada de uma unidade até o limite do parâmetro passado (exclusive). Por exemplo: range(3) cria a sequência (0, 1, 2). Não iniciadas em 0 Para que a sequência não comece em 0, podemos informar o início e o fim como parâmetros, lembrando que o parâmetro fim não entra na lista (exclusive o fim). O padrão é incrementar cada termo em uma unidade. Ou seja, a chamada range(2, 7) cria a sequência (2, 3, 4, 5, 6). Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 72 Indicando início, fim e passo Também é possível criar sequências mais complexas, indicando os parâmetros de início, fim e passo, nessa ordem. O passo é o valor que será incrementado de um termo para o próximo. Por exemplo, range(2, 9, 3) cria a sequência (2, 5, 8). A sintaxe da estrutura for A estrutura for tem a seguinte sintaxe em Python: for <variável> in <sequência>: Bloco que será repetido para todos os itens da sequência Instrução fora do for Cabe ressaltar a diferença de sintaxe entre as linguagens C e Python. Veja a Tabela 4: Python C for <variável> in <sequência>: for (inicialização; condição; incremento ou decremento){ Instruções com 4 espaços de indentação Bloco de instruções a ser repetido. A indentação não é exigida Instrução fora do for } Vamos analisar um exemplo simples em Python: imprimir todos os elementos de uma sequência criada com a chamada range(). Veja uma possível implementação desse exemplo na Figura 2: for item in range(2, 9, 3): print(item) sequência 2 5 8 Iteração 1 do laço item = 2 Iteração 2 do laço item = 5 Iteração 3 do laço item = 8 O laço for com uma string Python também permite que a repetição aconteça ao longo de uma string. Para isso, basta lembrar que a string é uma sequência de caracteres individuais. Suponha que você queira soletrar o nome informado pelo usuário. Uma possível implementação está na Figura 3: nome = input("Entre com seu nome: ") for letra in nome: print(letra) A linha 1 faz com que a palavra inserida pelo usuário seja armazenada na variável nome; A linha 2 mostra a criação do laço, com a variável letra percorrendo a sequência de caracteres armazenada na variável nome; A linha 3 indica a instrução que será executada para cada repetição desse laço. O laço for executa a instrução da linha 3 tantas vezes quantos forem os elementos da sequência que está na variável nome. Veja um exemplo de execução na Figura 4: Entre com o seu nome: Laura L a u r a Uso do laço for com qualquer sequência Até agora, estudamos o uso do laço for com iterações sobre strings e sobre sequências numéricas, mas Python permite ainda mais que isso! Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 73 Podemos utilizar o laço for com iteraçõessobre qualquer sequência, não somente as numéricas e as strings. Observe o exemplo da Figura 5: nomes = ['Laura', 'Lis', 'Guilherme', 'Enzo', 'Arthur'] for nome in nomes: print(nome) Veja o resultado da execução na Figura 6: Laura Lis Guilherme Enzo Arthur Estrutura de repetição while A estrutura de repetição while tem funcionamento e sintaxe muito semelhantes nas linguagens C e Python. Observe a comparação entre as duas linguagens na Tabela 6: Python C while <condição>: while <condição>{ Instruções com 4 espaços de indentação Bloco de instruções a ser repetido. A indentação não é exigida Instrução fora do while } Como exemplo inicial do uso do laço while, vamos analisar um programa em que o usuário precisa digitar a palavra “sair” para que o laço while seja encerrado. Uma possível implementação desse exemplo em Python está na Figura 7: palavra = input('Entre com uma palavra: ') while palavra != 'sair': palavra = input('Digite sair para encerrar o laço: ') print('Você digitou sair e agora está fora do laço') A linha 1 representa a solicitação ao usuário para que ele insira uma palavra, que será armazenada na variável palavra; A linha 2 cria o laço while, que depende da condição <valor da variável palavra ser diferente de ‘sair’>; A linha 3 será repetida enquanto a condição for verdadeira, ou seja, enquanto o valor da variável palavra for diferente de ‘sair’. Quando esses valores forem iguais, a condição do laço while será falsa e o laço será encerrado; A linha 4 representa a impressão da mensagem fora do laço while. Veja uma execução desse programa na Figura 8: Entre com uma palavra: teste Digite sair para encerrar o laço: Oi? Digite sair para encerrar o laço: Estou tentando... Digite sair para encerrar o laço: Aff... Digite sair para encerrar o laço: Blz, entendi... Digite sair para encerrar o laço: Sair Digite sair para encerrar o laço: Ah! o 'S' foi maiúsculo Digite sair para encerrar o laço: sair Você digitou sair e agora está fora do laço Observe agora outra execução do mesmo programa na Figura 9: Entre com uma palavra: sair Você digitou sair e agora está fora do laço Perceba que ao digitar ‘sair’ logo na primeira solicitação, a linha 3 do nosso programa não é executada nenhuma vez. Ou seja, o programa nem chega a entrar no laço while. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 74 Em C, existe outra estrutura muito semelhante ao while, chamada do-while. A diferença básica entre elas é o momento em que a condição é testada, como vemos a seguir: 1. No laço while, a condição é testada antes da iteração. 2. O laço while testa e executa caso a condição seja verdadeira. 3. No laço do-while, a condição é testada após a iteração. 4. O laço do-while executa e testa. Infelizmente, a estrutura do-while não existe em Python. Isso não chega a ser um grande problema, porque podemos adaptar nosso programa e controlar as repetições com o laço while. O laço while infinito Laços infinitos são úteis quando queremos executar um bloco de instruções indefinidamente. O laço while infinito tem o seguinte formato: while True: Bloco que será repetido indefinidamente Exemplo Suponha que você deseje criar uma aplicação que permaneça por meses ou anos sendo executada, registrando a temperatura ou a umidade de um ambiente. Logicamente, estamos supondo que você tenha essa informação disponível a partir da leitura de algum sensor. Deve-se tomar cuidado e ter certeza de que seu uso é realmente necessário para evitar problemas de consumo excessivo de memória. As instruções auxiliares break, continue e pass A instrução break A instrução break funciona da mesma maneira em C e em Python. Ela interrompe as repetições dos laços for e while. Quando a execução do programa chega a uma instrução break, a repetição é encerrada e o fluxo do programa segue a partir da primeira instrução seguinte ao laço. Para exemplificar o uso da instrução break, vamos voltar ao primeiro exemplo do laço while, utilizando o laço infinito. O laço será encerrado quando o usuário inserir a palavra ‘sair’. Veja a Figura 10: while True: palavra = input('Entre com uma palavra: ') if palavra == 'sair': break print('Você digitou sair e agora está fora do laço') Caso haja vários laços aninhados, o break será relativo ao laço em que estiver inserido. Veja a Figura 11: while True: print('Você está no primeiro laço.') opcao1 = input('Deseja sair dele? Digite SIM para isso. ') if opcao1 == 'SIM': break # este break é do primeiro laço else: while True: print('Você está no segundo laço.') opcao2 = input('Deseja sair dele? Digite SIM para isso. ') if opcao2 == 'SIM': break # este break é do segundo laço print('Você saiu do segundo laço.') print('Você saiu do primeiro laço') A instrução continue A instrução continue também funciona da mesma maneira em C e em Python. Ela atua sobre as repetições dos laços for e while, como a instrução break, mas não interrompe todas as repetições do laço. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 75 A instrução continue interrompe apenas a iteração corrente, fazendo com que o laço passe para a próxima iteração. O exemplo a seguir imprime todos os números inteiros de 1 até 10, pulando apenas o 5. Veja sua implementação na Figura 12: for num in range(1, 11): if num == 5: continue else: print(num) print('Laço encerrado') Para ressaltar a diferença entre as instruções break e continue, vamos alterar a linha 3 do nosso programa, trocando a instrução continue pela instrução break. Veja a nova execução na Figura 13: 1 2 3 4 Laço encerrado A instrução pass A instrução pass atua sobre a estrutura if, permitindo que ela seja escrita sem outras instruções a serem executadas caso a condição seja verdadeira. Assim, podemos concentrar as instruções no caso em que a condição seja falsa. Suponha que queiramos imprimir somente os números ímpares entre 1 e 10. Uma implementação possível está na Figura 14: for num in range(1, 11): if num % 2 == 0: pass else: print(num) print('Laço encerrado') Veja a execução desse programa na Figura 15: 1 3 5 7 9 Laço encerrado Claramente, seria possível reescrever a condição do if-else para que pudéssemos transformá-lo em um if simples, sem else. Porém, o objetivo aqui é mostrar o uso da instrução pass. Agora que já vimos os principais conceitos relativos às estruturas de decisão e de repetição, vamos testar seus conhecimentos. Verificando o aprendizado 1. Considere o seguinte trecho de um programa escrito em Python: s = 0 for i in range(5): s += 3*i print(s) Assinale a opção que apresenta corretamente o que será impresso na tela. A. 0 3 9 18 30 B. 0 3 6 9 12 C. 30 D. 45 Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 76 2. Considere o seguinte trecho de um programa escrito em Python: s = 0 a = 1 while s < 5: s = 3*a a += 1 print(s) Assinale a opção que apresenta corretamente o que será impresso na tela. A. 9 B. 3 6 C. 3 3 D. 3 6 9 12 Principais conceitos de subprogramas e a sua utilização em Python Os subprogramas são elementos fundamentais dos programas e por isso são importantes no estudo de linguagens de programação. Neste módulo, abordaremos os conceitos de subprogramas, como características gerais, passagem de parâmetros e recursividade, além da utilização de subprogramas em Python. Características gerais dos subprogramas Todos os subprogramas estudados neste módulo, com base em Sebesta (2018), têm as seguintes características: 1. Cada subprograma tem um único ponto de entrada. 2. A unidade de programa chamadora é suspensa durante a execução do subprograma chamado, o que significa que existe apenas umsubprograma em execução em determinado momento. 3. Quando a execução do subprograma termina, o controle sempre retorna para o chamador. Definições básicas As definições básicas, conforme Sebesta (2018), estabelecem que: 1. Um subprograma é definido quando o desenvolvedor descreve a interface e as ações da abstração desse subprograma. 2. O subprograma foi chamado quando uma instrução traz um pedido explícito para sua execução. 3. O subprograma está ativo após o início de sua execução, a partir da sua chamada e enquanto ele não foi concluído. O cabeçalho do subprograma é a primeira parte da definição, em que podem ser especificados o nome, os parâmetros e o tipo de retorno do subprograma. Em C, o cabeçalho dos subprogramas – sendo chamados de funções – traz, em ordem: o tipo de retorno, o nome e a lista de parâmetros, como a seguir: float calculaIMC (int peso, float altura) Em Python, as funções definidas pelo desenvolvedor devem ser precedidas pela palavra reservada def. Não são especificados o tipo de retorno nem os tipos dos parâmetros, como no exemplo a seguir: def calculaIMC (peso, altura) Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 77 Em Python, as sentenças de função def são executáveis. Isso implica que a função só pode ser chamada após a execução da sentença def. Veja o exemplo na Figura 16: escolha = input("Escolha uma opção de função: 1 ou 2") if escolha == 1: def func1(x): return x + 1 else: def func2(x): return x + 2 s = func1(10) print(s) A função func1() só pode ser chamada caso a variável escolha seja igual a 1. Ou seja, o usuário deverá inserir 1 quando solicitado (na linha 1), para que a linha 9 possa ser executada sem que seja gerado um erro. Parâmetros Usualmente, um subprograma executa cálculos e operações a partir de dados que ele deve processar. Existem duas maneiras de o subprograma obter esses dados: acessando variáveis não locais, mas visíveis para o subprograma, ou pela passagem de parâmetros. Quando o subprograma recebe os parâmetros adequados, ele pode ser executado com quaisquer valores recebidos. Porém, quando ele manipula variáveis não locais, uma forma de evitar alterações indevidas nessas variáveis é fazendo cópias locais delas. De acordo com Sebesta (2018), o acesso sistemático a variáveis não locais pode diminuir a confiabilidade do programa. São denominados parâmetros formais aqueles do cabeçalho do subprograma. Quando o subprograma é chamado, é necessário escrever o nome do subprograma e a lista de parâmetros a serem vinculados aos parâmetros formais dele, que são denominados parâmetros reais ou argumentos. No exemplo da Figura 16, existe o cabeçalho da função func1 na linha 3, com o parâmetro formal x. Na linha 9, a função func1 é chamada com o parâmetro real 10. Em Python, é possível estabelecer valores padrão para os parâmetros formais. O valor padrão é usado quando a chamada da função ocorre sem nenhum parâmetro real. Veja o exemplo de definição e chamada da função taxímetro na Figura 17: def taximetro(distancia, multiplicador=1): largada = 3 km_rodado = 2 valor = (largada + distancia * km_rodado) * multiplicador return valor pagamento = taximetro(3.5) print(pagamento) Observe que mesmo com a definição da linha 1 de dois parâmetros formais, a chamada da função na linha 8 ocorre apenas com um parâmetro real. A palavra reservada return indica que a função retorna algum valor. Isso implica que o valor retornado seja armazenado em uma variável do programa chamador (como ocorre na linha 8), ou utilizado como parâmetro para outra função. Atenção Retornar um valor é diferente de imprimir na tela. Ao utilizar a função print(), ocorre apenas a impressão de algo na tela, o que não significa que tenha havido retorno de qualquer função definida pelo usuário. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 78 Procedimentos e funções Os subprogramas podem ser, distintamente, procedimentos e funções. De acordo com Sebesta (2018): Procedimentos → São aqueles que não retornam valores. Funções → São aquelas que retornam valores. Na maioria das linguagens que não explicita a diferença entre eles, as funções podem ser definidas sem retornar qualquer valor, tendo comportamento de procedimento. Esse é o caso de Python. Veja o exemplo da Figura 18: def func1(x): x = 10 print(f'Função func1 - x = {x}') def func2(x): x = 20 print(f'Função func2 - x = {x}') x = 0 func1(x) func2(x) print(f'Programa principal - x = {x}') As funções func1(x) e func2(x) não possuem qualquer retorno. Ou seja, são funções com comportamento de procedimentos. Ambientes de referenciamento local Variáveis locais Quando um subprograma define suas próprias variáveis, estabelece ambientes de referenciamento local. Essas variáveis são chamadas de variáveis locais, com seu escopo usualmente sendo o corpo do subprograma. As variáveis locais podem ser: Dinâmicas da pilha São vinculadas ao armazenamento no início da execução do subprograma e desvinculadas quando essa execução termina. As variáveis locais dinâmicas da pilha têm diversas vantagens, e a principal delas é a flexibilidade. Suas principais desvantagens são o custo do tempo – para alocar, inicializar (quando necessário) e liberar tais variáveis para cada chamada ao subprograma – e o fato de que os acessos a essas variáveis locais devem ser indiretos, enquanto os acessos às variáveis estáticas podem ser diretos. Estáticas São vinculadas a células de memória antes do início da execução de um programa e permanecem vinculadas a essas mesmas células até que a execução do programa termine. Elas são um pouco mais eficientes que as variáveis locais dinâmicas da pilha, já que não é necessário tempo para alocar ou liberar essas variáveis. Sua maior desvantagem é a incapacidade de suportar recursão, como vai ser explicado adiante. Atenção Nas linguagens C e C++, as variáveis locais são dinâmicas da pilha, a menos que sejam especificamente declaradas como static. Todas as variáveis locais em Python são dinâmicas da pilha. As variáveis globais são declaradas em definições de método, e qualquer variável declarada global em um método precisa ser definida fora dele. Caso haja uma atribuição à variável local com mesmo nome de uma variável global, esta é implicitamente declarada como local. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 79 Voltando ao exemplo da Figura 18, vamos detalhar as funções func1(x) e func2(x): As linhas 1, 2 e 3 definem a função func1(x), que recebe o parâmetro x, mas tem uma variável local de nome x, cujo valor atribuído é 10; Analogamente, a função func2(x) – definida nas linhas 6, 7 e 8 – que recebe o parâmetro x e tem uma variável de mesmo nome, mas com valor atribuído 20; O programa principal tem uma variável global de mesmo nome x, cujo valor atribuído é 0, na linha 11; Veja que as chamadas às funções func1(x) e func2(x) ocorrem nas linhas 12 e 13, quando a variável x global já recebeu o valor 0. Porém, ao serem executadas, cada uma dessas funções tem a sua própria variável local, a quem todas as referências internas são feitas. Confira a execução desse exemplo: Função func1 - x = 10 Função func2 - x = 20 Programa principal - x = 0 Mesmo com a variável global tendo valor nulo, cada variável local das funções func1(x) e func2(x) tem seu próprio valor, e não ocorrem alterações na variável global mesmo com as atribuições das linhas 2 e 7. Para alterar a variável global x, seria necessário explicitar dentro de cada função que o nome x é referente a ela. Isso pode ser feito com a palavra reservada global. Além de explicitar a referência à variável global, as funções func1(x) e func2(x) não recebem mais os parâmetros de mesmo nome, já que fazemreferência à variável global. Veja como ficaria o nosso exemplo com essa pequena alteração na Figura 20: def func1(): global x x = 10 print(f'Função func1 - x = {x}') def func2(): global x x = 20 print(f'Função func2 - x = {x}') x = 0 func1() func2() print(f'Programa principal - x = {x}') Observe agora a execução desse exemplo alterado: Função func1 - x = 10 Função func2 - x = 20 Programa principal - x = 20 Percebe-se que o print() do programa principal está na linha 16, depois da chamada à função func2(x). Dessa forma, a variável global x foi alterada na execução da func2(x) e fica com o valor 20 quando a execução volta ao programa principal. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 80 Subprogramas aninhados Em Python, e na maioria das linguagens funcionais, é permitido aninhar subprogramas. Porém, as linguagens C e C++ não permitem essa prática. Veja o exemplo: def taximetro(distancia):: def calculaMult(): if distancia < 5: return 1.2 else: return 1 multiplicador = calculaMult() largada = 3 km_rodado = 2 valor = (largada + distancia * km_rodado) * multiplicador return valor dist = eval(input("Entre com a distancia a ser percorrida em km: ")) pagamento = taximetro(dist) print(f'O valor a pagar é R$ {pagamento}') A função taximetro() tem, dentro de sua definição, a definição de outra função denominada calculaMult(). Na linha 7, a função calculaMult() é chamada e o seu retorno é armazenado na variável multiplicador. Métodos de passagens de parâmetros Os métodos de passagem de parâmetros são as maneiras que existem para transmiti-los ou recebê- los dos subprogramas chamados. Os parâmetros podem ser passados principalmente por: Valor O parâmetro formal funciona como uma variável local do subprograma, sendo inicializado com o valor do parâmetro real. Dessa maneira, não ocorre alteração na variável externa ao subprograma, caso ela seja passada como parâmetro. Referência Em vez de passar o valor do parâmetro real, é transmitido um caminho de acesso (normalmente um endereço) para o subprograma chamado. Isso fornece o caminho de acesso para a célula que armazena o parâmetro real. Assim, o subprograma chamado pode acessar o parâmetro real na unidade de programa chamadora. Saiba mais Na linguagem C, utilizamos ponteiros para fazer a passagem de parâmetros por referência. As transmissões de parâmetros que não sejam ponteiros utilizam a passagem por valor. O método de passagem de parâmetros de Python é chamado passagem por atribuição. Como todos os valores de dados são objetos, toda variável é uma referência para um objeto. Ao se estudar orientação a objetos, fica mais clara a diferença entre a passagem por atribuição e a passagem por referência. Por enquanto, podemos entender que a passagem por atribuição é uma passagem por referência, pois os valores de todos os parâmetros reais são referências. Recursividade Uma função recursiva é aquela que chama a si mesma. Veja o exemplo da função regressiva(), como mostrado na Figura 23: def regressiva(x): print(x) regressiva(x - 1) Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 81 Na implementação da função regressiva(), tendo x como parâmetro, ela própria é chamada com o parâmetro x – 1. Vamos analisar a chamada regressiva(2): 1. Ao chamar regressiva(2), o valor 2 é exibido na tela pela linha 2, e ocorre uma nova chamada da função regressiva() na linha 3, com o parâmetro 1. Vamos continuar com esse caminho de execução da regressiva(1). 2. Ao chamar regressiva(1), o valor 1 é exibido na tela pela linha 2, e ocorre uma nova chamada da função regressiva() na linha 3, com o parâmetro 0. 3. Ao chamar regressiva(0), o valor 0 é exibido na tela e ocorre uma nova chamada da função regressiva, com o parâmetro -1, e assim sucessivamente. Atenção Conceitualmente, essa execução será repetida indefinidamente até que haja algum erro por falta de memória. Perceba que não definimos adequadamente uma condição de parada para a função regressiva(), o que leva a esse comportamento ruim. Em Python, o interpretador pode interromper a execução indefinida, mas essa não é uma boa prática. Uma forma de contornar esse problema é definir adequadamente uma condição de parada, como no exemplo da Figura 24: def regressiva(x): if x <= 0: print("Acabou") else: print(x) regressiva(x-1) Uma função recursiva que termina tem: 1. Um ou mais casos básicos, que funcionam como condição de parada da recursão. 2. Uma ou mais chamadas recursivas, que têm como parâmetros valores mais próximos do(s) caso(s) básico(s) do que o ponto de entrada da função. Alguns exemplos clássicos de funções que podem ser implementadas de forma recursiva são o cálculo do fatorial de um inteiro não negativo e a sequência de Fibonacci, que serão explorados a seguir. A função recursiva fatorial A função matemática fatorial de um inteiro não negativo n é calculada por: Fonte: Estácio de Sá Além disso, ela pode ser definida recursivamente por: Fonte: Estácio de Sá Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 82 Uma implementação recursiva da função fatorial em Python está na Figura 25: def fatorial(n): if n == 0 or n == 1: return 1 else: return n*fatorial(n-1) Vale ressaltar que a função fatorial também poderia ter sido implementada de forma não recursiva, como mostrado na Figura 26: def fatorial(n): fat = 1 if n == 0 or n == 1: return fat else: for x in range(2, n + 1): fat = fat*x return fat Porém, neste tópico, o intuito principal é explorar a recursividade. A sequência de Fibonacci A sequência de Fibonacci é: 1, 1, 2, 3, 5, 8, 13, 21... Os dois primeiros termos são 1 e, a partir do 3º termo, cada termo é a soma dos dois anteriores. Uma possível implementação recursiva de função que determina o n-ésimo termo da sequência de Fibonacci está na Figura 27: def fibo(n): if n == 1 or n == 2: return 1 else: return fibo(n - 1) + fibo(n - 2) A linha 2 traz as condições de parada. A linha 5 traz as chamadas recursivas para calcular os dois termos anteriores da sequência. Docstrings Em Python, é possível definir uma string que serve como documentação de funções definidas pelo desenvolvedor. Ao chamar o utilitário help() passando como parâmetro a função desejada, essa string é exibida. Veja a Figura 28 e a Figura 29: def fibo(n): 'Determina o n-ésimo termo da sequência de Fibonacci' if n == 1 or n == 2: return 1 else: return fibo(n - 1) + fibo(n - 2) print(help(fibo)) ****************************************************************************************************** fibo(n) Determina o n-ésimo termo da sequência de Fibonacci Na Figura 28, a linha 2 mostra a declaração da docstring. A linha 8 mostra a impressão na tela da chamada help(fibo). Na Figura 29, está o resultado da execução desse programa. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 83 Verificando o aprendizado 1. Considere o seguinte trecho de um programa escrito em Python: def func1(x): x = 10 print(x) x = 0 print(x) func1(x) print(x) O que acontecerá quando o usuário tentar executar esse programa? A. Ocorrerá um erro e o programa não será executado. B. Ocorrerá um erro durante a execução. C. Será impresso na tela: 0 10 0 D. Será impresso na tela: 0 10 10 2. Considere o seguinte trecho de um programa, com uma implementação de função recursiva, escrito em Python: def rec(n): if n < 2: return rec(n - 1) print(rec(1)) Quando o usuário tentou executar esse programa, houve um erro. Qual é a causa? A. Na linha 2, o if está escrito de maneira errada. B. A função não tem condição de parada. C. A função estásem retorno. D. A função não poderia ter sido definida com uma chamada a ela própria. Uso correto de recursos de bibliotecas em Python Python oferece, em seu núcleo, algumas funções que já utilizamos, como print() e input(), além de classes como int, float e str. Logicamente, o núcleo da linguagem Python disponibiliza muitas outras funções (ou métodos) e classes além das citadas. Mas, ainda assim, ele é pequeno, com objetivo de simplificar o uso e ganhar eficiência. Para aumentar a disponibilidade de funções, métodos e classes, o desenvolvedor pode usar a biblioteca padrão Python. Neste módulo, apresentaremos alguns dos principais recursos dessa biblioteca e a forma de utilizá-los. Biblioteca padrão python A biblioteca padrão Python consiste em milhares de funções, métodos e classes relacionados a determinada finalidade e organizados em componentes chamados módulos. São mais de 200 módulos que dão suporte, entre outras coisas, a: • Operações matemáticas. • Interface gráfica com o usuário (GUI). • Funções matemáticas e geração de números pseudoaleatórios. Atenção É importante lembrar dos conceitos de classes e objetos, pois eles são os principais conceitos do paradigma de programação orientada a objeto. As classes são fábricas, que podem gerar instâncias chamadas objetos. Uma classe Pessoa, por exemplo, pode ter como atributos nome e CPF. Ao gerar uma instância de Pessoa, com nome João da Silva e CPF 000.000.000-00, temos um objeto. Saiba mais Para melhor compreensão dos conceitos de classe e objeto, pesquise sobre paradigma orientado a objeto. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 84 Como usar uma função de módulo importado Para usar as funções e os métodos de um módulo, são necessários dois passos: Fazer a importação do módulo desejado com a instrução: import nome_modulo Chamar a função desejada, precedida do nome do módulo, com a instrução: nome_modulo.nome_funcao(paramêtros) Como exemplo, vamos importar o módulo math (dedicado a operações matemáticas) e calcular a raiz quadrada de 5, por meio da função sqrt(). Observe a Figura 30: >>> import math >>> math.sqrt(5) 2.23606797749979 A partir desse ponto, serão apresentados os principais aspectos dos seguintes módulos: math → usado para operações matemáticas. random → usado para gerar números pseudoaleatórios. smtplib →usado para permitir envio de e-mails. time → usado para implementar contadores temporais. tkinter → usado para desenvolver interfaces gráficas. Módulo math Esse módulo provê acesso à funções matemáticas de argumentos reais. As funções não podem ser usadas com números complexos. O módulo math tem as funções listadas na Tabela 7, entre outras: Função Retorno sqrt(x) Raiz quadrada de x ceil(x) Menor inteiro maior ou igual a x floor(x) Maior inteiro menor ou igual a x cos(x) Cosseno de x sin(x) Seno de x log(x, b) Logaritmo de x na base b pi Valor de Pi (3.141592...) e Valor de e (2.718281...) Saiba mais Para mais informações sobre o módulo math, visite a biblioteca Python. Módulo random Esse módulo implementa geradores de números pseudoaleatórios para várias distribuições. Números inteiros • Para inteiros, existe: o Uma seleção uniforme a partir de um intervalo. Sequências • Para sequências, existem: o Uma seleção uniforme de um elemento aleatório; o Uma função para gerar uma permutação aleatória das posições na lista; o Uma função para escolher aleatoriamente sem substituição. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 85 Distribuições de valores reais A Tabela 8 mostra algumas das principais funções disponíveis para distribuições de valores reais no módulo random. Função Retorno random() Número de ponto flutuante no intervalo [0.0, 1.0) uniform(a, b) Número de ponto flutuante N tal que a ≤ N ≤ b gauss(mu, sigma) Distribuição gaussiana. mu é a média e sigma é o desvio padrão. normalvariate(mu, sigma) Distribuição normal. mu é a média e sigma é o desvio padrão. Funções para números inteiros A Tabela 9 mostra algumas das principais funções disponíveis para inteiros no módulo random. Função Retorno randrange(stop) Um elemento selecionado aleatório de range(start, stop, step), mas sem construir um objeto range. randrange(start, stop [,step]) randint(a, b) Número inteiro N tal que a ≤ N ≤ b Funções para sequências A Tabela 10 mostra algumas das principais funções disponíveis para sequências no módulo random. Função Retorno choice(seq) Elemento aleatório de uma sequência não vazia seq. shuffle(x[, random]) Embaralha a sequência x no lugar. sample(pop, k) Uma sequência de tamanho k de elementos escolhidos da população pop, sem repetição. Usada para amostragem sem substituição. Saiba mais Para mais informações sobre o módulo random, visite a biblioteca Python. Módulo smtplib Esse módulo define um objeto de sessão do cliente SMTP que pode ser usado para enviar e-mail para qualquer máquina da internet com um serviço de processamento SMTP ou ESMTP. O exemplo a seguir vai permitir que você envie um e-mail a partir do servidor SMTP do Gmail. Como a Google não permite, por padrão, realizar o login com a utilização do smtplib por considerar esse tipo de conexão mais arriscada, será necessário alterar uma configuração de segurança. Para resolver isso, siga as instruções a seguir: 1. Acesse sua conta no Google. 2. Depois Segurança. 3. Acesso a app menos seguro. 4. Mude para "ativada" a opção de "Permitir aplicativos menos seguros". Para fazer seu primeiro envio, crie um programa novo no seu projeto. A Figura 31 mostra as importações necessárias para o envio: #import dos pacotes necessários from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText import smtplib Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 86 A Figura 32 mostra a criação da mensagem, com o corpo e seus parâmetros. 6 #criação de um objeto de mensagem 7 msg = MIMEMultipart() 8 texto = "Estou enviando um email com Python" 9 10 #parâmetros 11 senha = "SUA SENHA" 12 msg['From'] = "SEU E-MAIL" 13 msg['To'] = "E-MAIL DESTINO" 14 msg['Subject'] = "ASSUNTO" 15 16 #criação do corpo da mensagem 17 msg.attach(MIMEText(texto, 'plain')) A linha 7 mostra a criação de um objeto de mensagem. A linha 8 mostra o corpo da mensagem em uma string. As linhas de 11 a 14 devem ser preenchidas com os valores adequados para que seu programa seja executado com sucesso. A linha 17 anexa o corpo da mensagem (que estava em uma string) ao objeto msg. A Figura 33 mostra os passos necessários para o envio em si. 19 #criação do servidor 20 server = smtplib.SMTP('smtp.gmail.com: 587') 21 server.starttls() 22 23 #Login na conta para envio 24 server.login(msg['From'], senha) 25 26 #envio da mensagem 27 server.sendmail(msg['From'], msg['To'], msg.as_string()) 28 29 #encerramento do servidor 30 server.quit() 31 32 print('Mensagem enviada com sucesso') 33 As linhas 20 e 21 mostram a criação do servidor e a sua conexão no modo TLS. A linha 24 mostra o login na conta de origem do e-mail. A linha 27 representa o envio propriamente dito. A linha 30 mostra o encerramento do servidor. Saiba mais Para mais informações sobre o módulo smtplib, visite a biblioteca Python. Módulo time Esse módulo provê diversas funções relacionadas a tempo. Também pode ser útil conhecer os módulos datetime e calendar. A Tabela 11 mostra algumas das principais funções disponíveis no módulo time. Função Retorno time() Número de segundos passados desde o início da contagem (epoch). Por padrão, o início é 00:00:00 do dia 1 de janeiro de 1970. ctime(segundos) Uma string representando o horário local, calculado a partir do número de segundos passado como parâmetro. gmtime(segundos) Converteo número de segundos em um objeto struct_time descrito a seguir. localtime(segundos) Semelhante à gmtime(), mas converte para o horário local. sleep(segundos) A função suspende a execução por determinado número de segundos. A Figura 34 mostra um exemplo de chamada das funções time() e ctime(). >>> x = time.time() >>> print(f'Local time: {time.ctime(x)}') Local time: Tue Jun 30 23:38:55 2020 Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 87 A variável x recebe o número de segundos desde 00:00:00 de 01/01/1970 pela função time(). Ao executar ctime(x), o número de segundos armazenado em x é convertido em uma string, com o horário local. A classe time.struct_time gera objetos sequenciais com valor de tempo retornado pelas funções gmtime() e localtime(). São objetos com interface de tupla nomeada: os valores podem ser acessados pelo índice e pelo nome do atributo. Existem os seguintes valores que estão apresentados na Tabela 12: Índice Atributo Valores 0 tm_year Por exemplo, 2020 1 tm_mon range [1, 12] 2 tm_mday range [1, 31] 3 tm_hour range [0, 23] 4 tm_min range [0, 59] 5 tm_sec range [0, 61] 6 tm_wday range [0, 6], Domingo é 0 7 tm_yday range [1, 366] 8 tm_isdst 0, 1 ou -1 N/A tm_zone abreviação do nome da timezone Saiba mais Para mais informações sobre o módulo time, visite a biblioteca Python. Módulo tkinter O pacote tkinter é a interface Python padrão para o Tk GUI (interface gráfica com o usuário) toolkit. Na maioria dos casos, basta importar o próprio tkinter, mas diversos outros módulos estão disponíveis no pacote. A biblioteca tkinter permite a criação de janelas com elementos gráficos, como entrada de dados e botões, por exemplo. O exemplo a seguir vai permitir que você crie a primeira janela com alguns elementos. Para isso, crie um programa novo no seu projeto. A Figura 35 mostra a criação da sua primeira janela, ainda sem qualquer elemento gráfico. 1 from tkinter import * 2 3 janelaPrincipal = Tk() 4 janelaPrincipal.mainloop() A linha 1 mostra a importação de todos os elementos disponíveis em tkinter. O objeto janelaPrincipal é do tipo Tk. Um objeto Tk é um elemento que representa a janela GUI. Para que essa janela apareça, é necessário chamar o método mainloop(); Para exibir textos, vamos usar o elemento Label. A Figura 36 mostra as linhas 4 e 5, com a criação do elemento e o seu posicionamento. O tamanho padrão da janela é 200 X 200 pixels, com o canto superior esquerdo de coordenadas (0,0) e o canto inferior direito de coordenadas (200,200). 1 from tkinter import * 2 3 janelaPrincipal = Tk() 4 texto = Label(master = janelaPrincipal, text = "Minha janela exibida") 5 texto.place(x = 50 y = 100) 6 janelaPrincipal.mainloop() Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 88 Fonte: Estácio de Sá Vamos agora incrementar um pouco essa janela. Para isso, vamos acrescentar uma imagem e um botão. A imagem precisa estar na mesma pasta do seu arquivo .py. 1 def funcClicar():2 3 def funcClicar(): 4 print("Botão pressionado") 5 6 janelaPrincipal = Tk() 7 texto = Label(master = janelaPrincipal, text = "Minha janela exibida") 8 texto.pack() 9 10 pic = PhotoImage(file="logoEstacio.gif") 11 logo = Label(master = janelaPrincipal, image = pic) 12 logo.pack() 13 14 botao = Button(master = janelaPrincipal, text = 'Clique', command = funcClicar) 15 botao.pack() 16 17 janelaPrincipal.mainloop() 18 Na Figura 38, temos a inserção do elemento de imagem e o botão. Nas linhas 10, 11 e 12, é feita a criação do objeto Label para conter a imagem e seu posicionamento. Observe que passamos a utilizar o método pack(), que coloca o elemento centralizado e posicionado o mais perto possível do topo, depois dos elementos posicionados anteriormente. O elemento botão é criado na linha 14, com os atributos text e command, que, respectivamente, são o texto exibido no corpo do botão e a função a ser executada quando o botão for clicado. Para o funcionamento correto do botão, precisamos definir a função funcClicar(), nas linhas 3 e 4. Essa função serve apenas para imprimir na tela a string “Botão pressionado”. Fonte: Estácio de Sá Botão pressionado Saiba mais Para mais informações sobre o tkinter, visite a biblioteca Python. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 89 Usando pacotes externos Além dos módulos fornecidos de forma integrada pela biblioteca padrão do Python, a linguagem possui uma grande vantagem: sua característica open-source permite que qualquer desenvolvedor, com o conhecimento adequado, desenvolva sua própria biblioteca e os seus próprios módulos, que chamaremos a partir de agora de pacotes. Dica Veremos como criar módulos mais adiante neste conteúdo. Com um pacote pronto, o desenvolvedor pode disponibilizar esse material na internet, de forma que qualquer pessoa possa aproveitar o código implementado. Isso foi um dos fatores que fez com que o Python se tornasse uma das linguagens mais utilizadas no mundo atualmente. Como forma de facilitar a distribuição dos pacotes entre os usuários, existe um grupo dentro da comunidade Python que mantém o chamado Python Package Index, ou PyPI, que é um grande servidor no qual os desenvolvedores podem hospedar os seus pacotes, contendo bibliotecas e/ou módulos para que sejam baixados e instalados por outras pessoas. É possível acessar o PyPI através do pip, um programa que pode ser instalado juntamente com a distribuição do Python. Para isso, certifique-se de que a caixa “pip” está marcada durante a instalação, conforme ilustrado a seguir. Fonte: Estácio de Sá Dica Para verificar se a sua instalação Python incluiu o pip, procure pela pasta Scripts dentro do diretório de instalação que você escolheu para o Python. Dentro dessa pasta, localize o arquivo pip.exe. Caso não o encontre, pesquise sobre como instalar o pip. O mais recomendado é tentar reinstalar o Python, lembrando de marcar a opção de incluir o pip durante o processo de instalação. Além do pip, é necessário termos o endereço para acessar o pip dentro da variável de ambiente PATH. O processo de instalação do python também permite incluir automaticamente o Python no seu PATH, porém, caso não tenha feito isso, siga os passos abaixo: 1. Clique na tecla do Windows e escreva “Editar as variáveis de ambiente para a sua conta”. 2. Na janela que abrir, procure pela variável Path, selecione-a e clique no botão “Editar”. 3. Clique no botão “Novo” e digite o endereço da sua instalação do Python (por exemplo, D:\Python). 4. Clique no botão “Novo” uma segunda vez e digite o endereço da pasta Scripts dentro da sua instalação do Python (por exemplo, D:\Python\Scripts). 5. Aperte Ok até fechar todas as janelas. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 90 É importante que você se certifique de que a opção “Add Python to PATH” está marcada durante a instalação, como ilustrado a seguir. Fonte: Estácio de Sá Com essas etapas concluídas, já conseguimos instalar nossos pacotes externos! Atenção Quando estiver trabalhando com pacotes externos, é extremamente recomendado o uso de ambientes virtuais (em inglês virtual environments ou simplesmente virtualenvs). Esses ambientes isolam o projeto em que você está trabalhando, e uma das vantagens disso é que você consegue saber exatamente quais pacotes externos estão sendo usados no projeto. É possível usar pacotes externos sem o uso de ambientes virtuais, porém isso pode causar uma confusão caso você tenha vários projetos Python no seu computador. Pesquise mais sobre ambientes virtuais e configure um em cada projeto. Não é muito difícil e vai te ajudar a deixar o seu código mais profissional! Para instalar um pacote externo disponível no PyPI, basta abrir oseu terminal (clique no botão do Windows e digite “cmd” ou “prompt de comando”), ative o seu ambiente virtual (se você estiver usando um) e digite o seguinte comando: d: \Projects\exemplo_pacotes>pip install <nome_do_pacote> Fonte: Estácio de Sá Substitua <nome_do_pacote> pelo pacote que você deseja usar. Temos inúmeros pacotes prontos à nossa disposição. Cada pacote normalmente possui um site que apresenta a sua documentação, de forma similar à documentação oficial do Python. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 91 Abaixo você encontra uma lista com alguns dos pacotes externos mais comuns e utilizados no mercado: Nome do módulo Para que serve? numpy Cálculos, operações matemáticas e simulações pandas Manipulação de dados scikit-learn Modelos de aprendizado de máquina matplotlib Visualização de dados requests Biblioteca de comandos de comunicação pelo protocolo HTTP flask Construção de aplicações web Dica Antes de começar o seu próprio módulo, é sempre recomendado pesquisar se o que você quer fazer já existe em algum pacote popular. Se existir, procure pela documentação e instale esse pacote. O uso de módulos oriundos de pacotes externos é idêntico ao uso dos módulos da biblioteca padrão, basta utilizar o import nome_do_modulo no seu código. Criação do próprio módulo Os desenvolvedores podem criar seus próprios módulos de forma a reutilizar as funções que já escreveram e organizar melhor seu trabalho. Para isso, basta criar um arquivo .py e escrever nele suas funções. É importante observar que o arquivo do módulo precisa estar na mesma pasta do arquivo para onde ele será importado. Verificando o aprendizado 1. Sabemos que é possível importar módulos e chamar funções desses módulos em Python. Considere o módulo math, que oferece diversas funções matemáticas. Uma dessas funções é a ceil(x), que retorna o menor inteiro maior ou igual a x. Suponha que um estudante queira usar uma variável n, que recebe o valor 5.9, e em seguida imprimir na tela o menor inteiro maior ou igual a ela. 1 import math 2 n = 5.9 3 print(ceil(n)) 1 import math 2 n = 5.9 3 math.ceil(n) 4 print(n) 1 import math 2 n = 5.9 3 print(ceil.math(n)) 1 import math 2 n = 5.9 3 print(math.ceil(n)) 2. Sobre a linguagem Python e sua biblioteca padrão, é correto afirmar que: A. Só permite a utilização dos módulos contidos na biblioteca padrão Python. B. Tem o módulo de interface gráfica tkinter, que não permite a criação de janelas com botões. C. Tem módulo de interface de e-mails smtplib, que não permite envio de e-mails por servidores gratuitos. D. Tem módulo de operações matemáticas math, que não permite operações com números complexos. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 92 Formas de tratamento de exceções e eventos em Python Até agora, consideramos que nossos programas tiveram seu fluxo de execução normal. Neste módulo, vamos analisar o que acontece quando o fluxo de execução é interrompido por uma exceção, além de controlar esse fluxo excepcional. Erros e exceções Dois tipos básicos de erros podem acontecer em um programa em Python. Os erros de sintaxe são aqueles que ocorrem devido ao formato incorreto de uma instrução. Esses erros são descobertos pelo componente do interpretador Python, chamado analisador ou parser. Veja exemplos na Figura 41 e na Figura 42: >>> print 'hello' File "<input>", line 1 print 'hello' ˆ SyntaxError: Missing parenthesesin call to 'print'. Did you mean print('hello')? Figura 41 - Erro de sintaxe 1 - Fonte: O autor (2020) >>> lista = [1 ; 2 ; 4] File "<input>", line 1" lista = [1 ; 2 ; 4] ˆ SyntaxError: invalid sytax Figura 42 - Erro de sintaxe 2 - Fonte: O autor (2020) Além desses, existem os erros que ocorrem em tempo de execução do programa, que não são devidos a uma instrução escrita errada, mas sim ao fato de que o programa entrou em um estado indevido. Temos os exemplos: A divisão por 0. A tentativa de acessar um índice indevido em uma lista. Um nome de variável não atribuído. Um erro causado por tipos incorretos de operando. Em cada caso, quando o programa atinge um estado inválido, dizemos que o interpretador Python levanta uma exceção. Isso significa que é criado um objeto que contém as informações relevantes sobre o erro. A Tabela 13 traz alguns tipos comuns de exceção: Exceção Explicação KeyboardInterrupt Levantado quando o usuário pressiona CTRL + C, a combinação de interrupção. OverflowError Levantado quando uma expressão de ponto flutuante é avaliada como um valor muito grande. ZeroDivisionError Levantado quando se tenta dividir por 0. IOError Levantado quando uma operação de entrada/saída falha por um motivo relacionado a isso. IndexError Levantado quando um índice sequencial está fora do intervalo de índices válidos. NameError Levantado quando se tenta avaliar um identificador (nome) não atribuído. TypeError Levantado quando uma operação da função é aplicada a um objeto do tipo errado. ValueError Levantado quando a operação ou função tem um argumento com o tipo correto, mas valor incorreto. Em Python, as exceções são objetos. A classe Exception é derivada de BaseException, classe base de todas as classes de exceção. BaseException fornece alguns serviços úteis para todas as classes de exceção, mas normalmente não se torna uma subclasse diretamente. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 93 Captura e manipulação de exceções Para evitar que os programas sejam interrompidos quando uma exceção for levantada, é possível planejar um comportamento alternativo. Assim, o programa não será interrompido e a exceção poderá ser tratada. Chamamos esse processo de captura da exceção. Vamos considerar um exemplo de programa que solicita ao usuário, com a função input(), um número inteiro. Embora essa função trate a entrada do usuário como string, é possível utilizá-la em conjunto com a função eval() para que os dados inseridos sejam avaliados como números. A Figura 43 mostra uma implementação simples desse exemplo. 1 num = eval(input("Entre com um número inteiro: ")) 2 print(num) Mas o que aconteceria se o usuário digitasse uma palavra em vez de números? Veja a Figura 44. Entre com um número inteiro: dez Traceback (most recent call last): File "C:/Users/Humberto/PycharmProjects/estacio_ead/excessoes.py", line 1, in <module> num = eval(input("Entrecom um número inteiro: ")) File "<string>, line 1, in <module> NameError: name 'dez'is not defined Veja que o programa foi encerrado com uma exceção sendo levantada. Uma forma de fazer a captura e a manipulação de exceções é usar o par de instruções try / except. O bloco try é executado primeiro, no qual devem ser inseridas as instruções do fluxo normal do programa. O bloco except somente será executado se houver o levantamento de alguma exceção. Isso permite que o fluxo de execução continue de maneira alternativa. A Figura 45 mostra uma implementação possível desse exemplo: 1 try: 2 num = eval(input("Entre com um número inteiro: ")) 3 print(num) 4 except: 5 print("Entre com o valor numérico e não letras") A execução pode ser conferida na Figura 46: Entre com um número inteiro: dez Entre com valor numérico e não letras O formato padrão de uso do par try/except é: 1 try: 2 Bloco 1 3 except: 4 Bloco 2 5 Instrução fora do try/except O bloco 1 representa o fluxo normal do programa. Caso uma exceção seja levantada, o bloco 2 será executado, permitindo o tratamento adequado dela. Esse bloco 2 é chamado de manipulador de exceção. Atenção Em Python, o manipulador de exceção padrão é quem executa o trabalho de captura da exceção caso não haja um tratamento explícito feito pelo desenvolvedor. É essemanipulador padrão o responsável pela exibição das mensagens de erro no console, como visto na Figura 44. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 94 Captura de exceções de determinado tipo Python permite que o bloco relativo ao except só seja executado caso a exceção levantada seja de determinado tipo. Para isso, o except precisa trazer o tipo de exceção que se deseja capturar. A Figura 47 traz uma possível variação do exemplo anterior, com a captura apenas das exceções do tipo NameError. 1 try: 2 num = eval(input("Entre com um número inteiro: ")) 3 print(num) 4 except NameError: 5 print("Entre com o valor numérico e não letras") Captura de exceções de múltiplos tipos Python permite que haja diversos tratamentos para diferentes tipos possíveis de exceção. Isso pode ser feito com mais de uma cláusula except vinculada à mesma cláusula try. A Figura 48 mostra um exemplo de implementação da captura de exceções de múltiplos tipos. 1 try: 2 num = eval(input("Entre com um número inteiro: ")) 3 print(num) 4 except ValueError: 5 print("Mensagem 1") 6 except IndexError: 7 print("Mensagem 2") 8 except: 9 print("Mensagem 3") A instrução da linha 5 somente será executada se a exceção levantada no bloco try for do tipo ValueError, e se, na instrução da linha 7, a exceção for do tipo IndexError. Caso a exceção seja de outro tipo, a linha 9 será executada. O tratamento completo das exceções A forma geral completa para lidar com as exceções em Python é: 1 try: 2 Bloco 1 3 except Exception1: 4 Bloco tratador para Exception1 5 except Exception2: 6 Bloco tratador para Exception1 7 ... 8 else: 9 Bloco 2 – executado caso nenhuma exceção seja levantada 10 finally: 11 Bloco 3 – executado independente do que ocorrer 12 Instrução fora do try/except As cláusulas else e finally são opcionais, como foi possível perceber nos exemplos iniciais. Tratamento de eventos O tratamento de eventos é similar ao tratamento de exceções. Assim como podemos tratar as exceções ocorridas em tempo de execução, podemos tratar os eventos criados por ações externas, como interações de usuário realizadas por meio de uma interface gráfica de usuário (GUI). Um evento é a notificação de que alguma coisa aconteceu, como um clique de mouse sobre um elemento botão. O tratador do evento é o segmento de código que será executado em resposta à ocorrência do evento. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 95 Verificando o aprendizado 1. Considere o seguinte trecho de um programa escrito em Python: 1 try: 2 num = eval(input("Entre com um número inteiro: ")) 3 print(num) 4 except ValueError: 5 print("Mensagem 1") 6 except IndexError: 7 print("Mensagem 2") 8 except: 9 print("Mensagem 3") Suponha que durante a execução o usuário entre com a palavra numero quando solicitado. Assinale a opção que mostra o resultado imediato dessa ação. A. O programa deixará de ser executado. B. Será impresso na tela Mensagem 1. C. Será impresso na tela Mensagem 2. D. Será impresso na tela Mensagem 3. 2. Sobre o tratamento de exceções em Python, é incorreto afirmar que: A. É possível implementar tratamentos diferentes de acordo com a exceção levantada. B. Não é possível utilizar a cláusula finally. C. Não é possível utilizar a cláusula catch. D. É possível implementar um tratamento geral para todas as exceções levantadas. Considerações finais Neste tema, você aprendeu a usar as estruturas de controle, sejam de decisão ou de repetição, foi apresentado aos conceitos de subprogramas em Python, implementou suas próprias funções, além de conhecer e utilizar as bibliotecas. Por fim, analisou as formas de tratamento de exceções e eventos. Com esse conteúdo, você com certeza terá condições de desenvolver aplicações muito mais complexas e com muito mais recursos. Referências PERKOVIC, L. Introdução à computação usando Python: um foco no desenvolvimento de aplicações. Rio de Janeiro: LTC, 2016. SEBESTA, R. W. Conceitos de linguagens de programação. 11. ed. São Paulo: Bookman, 2018. Explore+ Para ter desafios mais complexos e exercícios para treinar, recomendamos uma visita ao website Python Brasil. Acesse o site das bibliotecas apresentadas e busque pela documentação para que você possa conhecer todas as funcionalidades que estão disponíveis. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 96 Python Orientado a Objetos Definição Introdução aos conceitos da orientação a objetos; Classes e Encapsulamento; Herança e Polimorfismo; Construtores; Atributos e Métodos; Implementação de Herança; Implementação de Polimorfismo; Classes Abstratas; Tratamento de Exceções; Python e linguagens OO. Propósito Compreender o desenvolvimento de software orientado a objetos, utilizando uma linguagem de programação como Python com grande aceitação no meio comercial e acadêmico. Entender os conceitos e pilares da orientação a objetos e saber contextualizar o Python entre as outras linguagens tradicionais orientadas a objetos como Java e C++. Preparação Para este módulo, é necessário conhecimentos de programação em linguagem Python, incluindo modularização e utilização de bibliotecas em Python. Antes de iniciar o conteúdo deste tema é necessário que tenha o interpretador Python na versão 3.7.7 e o ambiente de desenvolvimento PyCharm ou outro ambiente que suporte o desenvolvimento na linguagem Python. Introdução O paradigma de programação orientado a objetos é largamente utilizado para o desenvolvimento de software devido à sua implementação ser próxima dos conceitos do mundo real. Essa proximidade facilita a manutenção dos softwares orientados a objetos. A linguagem Python implementa os conceitos do paradigma orientado a objetos e, devido à sua sintaxe simples e robusta, torna-se uma ferramenta poderosa para a implementação de sistemas orientados a objetos. Na apostila, serão apresentados os conceitos da orientação a objetos, como implementá-los em Python e uma comparação com as linguagens Java e C++ - em relação às principais características da orientação a objetos. Conceitos gerais da orientação a objetos Conceitos de POO - programação orientada a objetos A programação orientada a objetos foi considerada uma revolução na programação, pois mudou completamente a estruturação dos programas de computador. Essa mudança se estendeu, inclusive, para os modelos de análise do mundo real e depois para a implementação dos respectivos modelos nas linguagens de programação orientadas a objetos. Conforme apresentado por Rumbaugh et al (1994): “A tecnologia baseada em objetos é mais do que apenas uma forma de programar. Ela é mais importante como um modo de pensar em um problema de forma abstrata, utilizando conceitos do mundo real e não ideias computacionais”. Jaeger, 1995. Muitas vezes, analisamos um problema do mudo real pensando no projeto, que, por sua vez, é influenciado por ideias sobre codificação, que são fortemente influenciadas pelas linguagens de programação disponíveis. A abordagem baseada em objetos permite que os mesmos conceitos e a mesma notação sejam usados durante todo o processo de desenvolvimento de software, ou seja, não existem conceitos de análise de projetos diferentes dos conceitos de implementação dos projetos. A abordagem procura refletir os problemas do mundo real através de interação de objetos modelados Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 97 computacionalmente. Portanto, o desenvolvedor do software não necessita realizar traduções para outra notação em cada etapa do desenvolvimento de um projeto de software (COSTA, 2015). O software é organizado como uma coleção de objetos separados que incorporam, tanto a estrutura, quantoo comportamento dos dados. Contrasta com a programação convencional, segundo a qual a estrutura e o comportamento dos dados têm pouca vinculação entre si. Os modelos baseados em objetos correspondem mais aproximadamente ao mundo real. Em consequência, são mais adaptáveis às modificações e evoluções dos sistemas. Pilares da orientação a objetos Objetos Um objeto é a representação computacional de um elemento ou processo do mundo real. Cada objeto possui suas características (informações) e uma série de operações (comportamento) que alteram as suas características (estado do objeto). Todo o processamento das linguagens de programação orientadas a objetos se baseia no armazenamento e na manipulação das informações (estados). São exemplos de objetos do mundo real e computacional: Aluno, Professor, Livro, Empréstimo e Locação. (Costa, 2015) É importante, durante a etapa de levantamento dos objetos, analisar apenas os objetos relevantes (abstrair) com as respectivas características mais importantes para o problema a ser resolvido. Por exemplo, as características de uma pessoa para um sistema acadêmico podem ser formação, nome do pai e da mãe, enquanto as características de um indivíduo para o sistema de controle de uma academia são altura e peso. Atributos São propriedades do mundo real que descrevem um objeto. Cada objeto possui suas respectivas propriedades do mundo real, os quais possuem valores. A orientação a objetos define as propriedades como atributos. O conjunto de valores dos atributos de um objeto definem o seu estado naquele momento (RUMBAUGH, 1994). Veja nas tabelas a seguir a diferença entre os atributos de duas mulheres: Fonte: Estácio de Sá Operações Uma operação é uma função ou transformação que pode ser aplicada a objetos ou dados pertencentes a um objeto. Importante dizer que todo objeto possui um conjunto de operações, que podem ser chamadas por outros objetos de modo a colaborarem entre si. Esse conjunto de operações é conhecido como interface. A única forma de colaboração entre os objetos é por meio das suas respectivas interfaces (FARINELLI, 2020). Utilizando o exemplo acima, podemos alterar nome, idade e peso da pessoa por meio de um conjunto de operações. Portanto, normalmente, essas operações alteram o estado do objeto. Vamos ver a seguir outros exemplos de operações: Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 98 Exemplo Classe empresa = Contratar_Funcionario, Despedir_Fucionario; Classe janela = Abrir, fechar, ocultar. O desenvolvimento de um sistema orientado a objetos consiste em realizar um mapeamento, conforme apresentado na imagem a seguir. Fonte: Estácio de Sá Basicamente, deve-se analisar o mundo real e identificar quais objetos devem fazer parte da solução do problema. Para cada objeto identificado, levantam-se os atributos que descrevem as propriedades dos objetos e as operações que podem ser executadas sobre esses objetos. Classes A classe descreve as características e os comportamento de um conjunto de objetos. De acordo com a estratégia de classificação, cada objeto pertence a uma única classe e possuirá os atributos e as operações definidos na classe. Durante a execução de um programa orientado a objetos, são instanciados os objetos a partir da classe, portanto, um objeto é chamado de instância de sua classe. A classe é o bloco básico para a construção de programas OO – Orientados a Objetos (COSTA, 2015). Atenção Importante ressaltar que cada nome de atributo é único dentro de uma classe, no entanto, essa premissa não é verdadeira quando se consideram todas as classes. Por exemplo, as classes Pessoa e Empresa podem ter um atributo comum chamado de Endereço. Com base na Tabela 1, vista anteriormente, deve ser definida uma classe Pessoa com os atributos Nome, Idade, Peso e Altura. A partir da classe Pessoa, pode-se instanciar uma quantidade ilimitada de objetos contendo os mesmos atributos. Os objetos de uma classe sempre compartilham o conjunto de operações que atuam sobre seus dados, alterando o estado do objeto. Um programa orientado a objetos consiste basicamente em um conjunto de objetos que colaboram entre si, por meio de uma troca de mensagens, para a solução de um problema computacional. Cada troca de mensagens significa a chamada de uma operação feita pelo objeto receptor da mensagem. (COSTA, 2015) Comunicação entre objetos diferentes Fonte: Estácio de Sá Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 99 De acordo com a Figura 2, vista anteriormente, um objeto motorista 1, instanciado a partir da classe Motorista, envia a mensagem “Freia” para um objeto carro 1, instanciado a partir da classe Carro. O objeto carro 1, ao receber a mensagem, executa uma operação para acionar os freios do automóvel. Essa operação também diminuirá o valor do atributo velocidade do objeto carro 1. Encapsulamento O conceito de encapsulamento consiste na separação dos aspectos externos (operações) de um objeto acessíveis a outros objetos, além de seus detalhes internos de implementação, que ficam ocultos dos demais objetos (RUMBAUGH, 1994). Algumas vezes, é conhecido como o princípio do ocultamento de informação, pois permite que uma classe encapsule atributos e comportamentos, ocultando os detalhes da implementação. Partindo desse princípio, a interface de comunicação de um objeto deve ser definida de modo a revelar o menos possível sobre o seu funcionamento interno. Exemplo Foi desenvolvido um objeto ApresentaçãoMapa, pertencente a um aplicativo de entrega móvel, que possui a responsabilidade de apresentar um mapa com o menor caminho entre dois pontos. Porém, o objeto não “sabe” como calcular a distância entre os dois pontos. Para resolver esse problema, ele precisa colaborar com um objeto Mapa que “sabe” calcular e, portanto, possui essa responsabilidade. O objeto Mapa implementa essa responsabilidade por meio da operação Calcula Melhor Caminho, cujo resultado é a menor rota entre duas coordenadas geográficas. Utilizando o encapsulamento, o objeto Mapa calcula e retorna o melhor caminho para o objeto ApresentaçãoMapa de maneira transparente, escondendo a complexidade da execução dessa tarefa. Fonte: Estácio de Sá Uma característica importante do encapsulamento é que pode surgir um modo diferente de se calcular o melhor caminho entre dois pontos. Por conta disso, o objeto Mapa deverá mudar o seu comportamento interno para implementar esse novo cálculo. Porém, essa mudança não afetará o objeto ApresentaçãoMapa, pois a implementação foi realizada isoladamente (encapsulamento) no objeto Mapa, sem impacto para outros objetos no sistema. Resumindo Os objetos clientes têm conhecimento apenas das operações que podem ser requisitadas e precisam estar cientes apenas do que as operações realizam, e não de como elas estão implementadas. Herança Na orientação a objetos, a herança é um mecanismo por meio do qual classes compartilham atributos e comportamentos, formando uma hierarquia. Uma classe herdeira recebe as características de outra classe para reimplementá-las ou especializar de uma maneira diferente da classe pai. A herança permite capturar similaridades entre classes, dispondo-as em hierarquias. As similaridades incluem atributos e operações sobre as classes (FARINELLI, 2020). Essa estrutura reflete um mapeamento entre classes, e não entre objetos, conforme esquema a seguir: Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 100 Fonte: Estácio de Sá No esquema anterior, as classes Carro, Moto, Caminhão e Ônibus herdam características em comum da classe Veículo, como os atributos chassis, ano, cor e modelo. Uma classe pode ser definida genericamente como uma superclasse e, depois, especializada em classes mais específicas (subclasses). A herança permitea reutilização de código em larga escala, pois possibilita que se herde todo o código já implementado na classe pai e se adicione apenas o código específico para as funcionalidades novas implementadas pela classe filha. A evolução dos sistemas orientados a objetos também é facilitada, pois, caso surja uma classe nova com atributos e/ou operações comuns a outra, basta inseri-la na hierarquia, acelerando a implementação. Veja a seguir os tipos de herança: Herança simples A herança é considerada simples quando uma classe herda as características existentes apenas de uma superclasse. Na figura a seguir, temos uma superclasse Pessoa que possui os atributos CPF, RG, Nome e Endereço. Em seguida, a classe Professor precisa herdar os atributos da superclasse Pessoa, além de adicionar atributos específicos do contexto da classe Professor, como titularidade e salário. Figura 5 – Exemplo de herança Pessoa -> Professor. Considerando um sistema acadêmico, a classe Aluno também se encaixaria na hierarquia acima, tornando-se uma subclasse de Pessoa. Porém, precisaria de outros atributos associados a seu contexto, como curso e Anoprevformatura. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 101 Figura 6 – Exemplo de herança Pessoa -> Professor e Pessoa -> Aluno. Herança múltipla A herança é considerada múltipla quando uma classe herda características de duas ou mais superclasses. Por exemplo, no caso do sistema acadêmico, o docente também pode desejar realizar um outro curso de graduação na mesma instituição em que trabalha. Portanto, ele possuirá os atributos da classe Professor e os da classe Aluno. Além disso, haverá também um atributo DescontoProfessor, o qual apenas quando houver a associação professor e aluno com a universidade. Para adaptar essa situação no mundo real, deve ser criada uma modelagem de classes. Uma nova subclasse ProfessorAluno precisa ser adicionada, herdando atributos e operações das classes Professor e Aluno. Isso configura uma herança múltipla. Essa nova subclasse deverá ter o atributo DescontoProfessor, que faz sentido apenas para essa classe. Figura 7 – Exemplo de herança Pessoa -> Professor e Pessoa -> Aluno e Professor/Aluno ->ProfessorAluno. Polimorfismo O Polimorfismo é a capacidade de um mesmo comportamento diferente em classes diferentes. Uma mesma mensagem será executada de maneira diversa, dependendo do objeto receptor. O polimorfismo acontece quando reimplementamos um método nas subclasses de uma herança (FARINELLI, 2020). Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 102 Fonte: Estácio de Sá Como exemplificado na figura 1.8, o comportamento mover() em um objeto instanciado pela classe Aereo será diferente do mover() em um objeto da classe Terrestre. Um objeto poderá enviar uma mensagem para se mover e o objeto receptor decidirá como isso será feito. Verificando o aprendizado 1. Na programação orientada a objetos, temos conceitos como Herança e Polimorfismo. Sobre esses conceitos analise as assertivas e assinale a alternativa que aponta a(s) correta(s). I. Para evitar código redundante, o paradigma de orientação a objetos oferece uma estrutura hierárquica e modular para reutilização de código através de uma técnica conhecida como herança. II. Herança permite projetar classes genéricas que podem ser especializadas em classes mais particulares, onde as classes especializadas reutilizam o código das mais genéricas. III. Literalmente, “polimorfismo” significa “muitas formas”. No contexto e projeto orientado a objetos, entretanto, refere-se à habilidade de uma variável de objeto de assumir formas diferentes. IV. Polimorfismo permite que os atributos de uma classe não tenham acesso diretamente. A. Apenas I. B. Apenas I e III. C. Apenas I, II e III. D. Apenas II, III e IV. 2. Os bancos são instituições que investem fortemente na área de Tecnologia da Informação, inclusive com a contratação de milhares de profissionais e a construção de grandes ambientes de datacenter. Por isso, é um domínio de conhecimento bastante importante e que deve ser utilizado como exemplo durante um curso de graduação. Sabendo disso, analise a seguinte situação em um sistema bancário: A ContaBancaria(CB) especializa as classes ItemSuportado (IS) e ItemSujeitoAJuros (ISJ) e generaliza as classes ContaCorrente (CC) e Poupança (PP). Nesse sentido, é correto afirmar que ocorre (0,5) A. Relação de dependência entre IS e ISJ. B. Relação de dependência entre CC e PP. C. Herança múltipla de CB em relação a CC e PP. D. Herança múltipla de CB em relação a IS e ISJ. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 103 Conceitos básicos da programação orientada a objetos na linguagem Python Classes e objetos em Python Uma classe é uma declaração de tipo que encapsula constantes, variáveis e métodos que realizam manipulação dos valores dessas variáveis. Cada classe deve ser única em um sistema orientado a objetos. Definição de classe Como boa prática, cada classe deve fazer parte de um único arquivo.py para ajudar na estruturação do sistema orientado a objetos em Python. Outra boa prática consiste no nome da classe semelhante ao do arquivo. Por exemplo, definir no ambiente de desenvolvimento Python a classe Conta. O nome final do arquivo deve ser Conta.py. Deve-se ressaltar que todos esses exemplos podem ser executados no terminal do Python. Arquivo Conta.py class Conta: pass Uma classe é definida utilizando a instrução class e: para indicar o início do bloco de declaração da classe. A palavra reservada pass indica que a classe será definida posteriormente, servindo apenas para permitir que o interpretador execute a classe até o final sem erros. Recomendação É recomendável que você reproduza e execute todos os exemplos que serão apresentados a seguir. Construtores e self A classe conta foi criada, porém não lhe foram definidos atributos e instanciados objetos, característica básica dos programas orientados a objetos. Nas linguagens orientadas a objetos, para se instanciar objetos, devemos criar os construtores da classe. Em Python, a palavra reservada __init__() serve para inicialização de classes, como abaixo: class Conta: def__init__(self, numero, cpf, nomeTitular, saldo): self.numero = numero self.cpf = cpf self.nomeTitular = nomeTitular self.saldo = saldo Figura 9 - Classe Conta Inicial. Diferentemente de outras linguagens de programação orientadas a objetos, o Pyhton constrói os objetos em duas etapas. A primeira etapa é utilizada com a palavra reservada __new__, que, em seguida, executa o método __init__. O __new__ cria a instância e é utilizado para alterar as classes dinamicamente para casos de sistemas que envolvam metaclasses e frameworks. Após __new__ ser executado, este método chama o __init__ para inicialização da classe como seus valores iniciais (MENEZES, 2019). Para efeitos de comparação com outras linguagens de programação, e por questões de simplificação, consideraremos o __init__ como o construtor da classe. Portanto, toda vez que instanciarmos objetos da classe conta, será chamado o método __init__. Analisando o nosso exemplo anterior, vimos a utilização da palavra self como parâmetro no construtor. Como o objeto já foi instanciado implicitamente pelo __new__, o método __init__ recebe uma referência do objeto instanciado como self. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 104 Analisando o restante do construtor da Figura 9, o código possui diversos comandos self, o qual indica referência ao próprio objeto. Por exemplo, o comando self.numero indica que o número é um atributo do objeto, ao qual é atribuído um valor. O restante dos comandos self indicam que foram criados os atributos cpf, nomeTitular e saldo referentesa classe Conta. Vamos instanciar o nosso objeto com o método __init__: >>>from Conta import Conta >>>c1 = Conta(1,1,"Joao",1000) Importante ressaltar que, em Python, não é obrigatório ter um método construtor na classe, apenas se for necessária alguma ação na construção do objeto, como inicialização e/ou atribuição de valores. Segue um exemplo de uma classe sem um método construtor: class A(): def f(): print ‘foo’ Figura 10 – Exemplo de classe sem construtor. Métodos Toda classe deve possuir um conjunto de métodos para manipular os atributos e, por consequência, o estado do objeto. Por exemplo, precisamos depositar dinheiro na conta para aumentar o valor da conta corrente: def __depositar__(self,valor) self.saldo += valor No código acima, foi definido um método __depositar__ que recebe a própria instância do objeto através do self e de um parâmetro valor. O número passado por meio do parâmetro será adicionado ao saldo da conta do cliente. Vamos supor que o estado anterior do objeto representasse o saldo com o valor zero da conta. Após a chamada desse método passando como parâmetro o valor 300, através da referência self, o estado da conta foi alterado com o novo saldo de 300 reais. Segue o novo código: class Conta: def__init__(self, numero, cpf, nomeTitular, saldo): self.numero = numero self.cpf = cpf self.nomeTitular = nomeTitular self.saldo = saldo def depositar(self, valor): self.saldo += valor def sacar(self, valor): self.saldo -= valor Figura 11 – Classe com métodos depositar e sacar. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 105 No exemplo anterior, adicionamos mais um método sacar(self, valor), onde subtraímos o valor, passado como parâmetro, do saldo do cliente. Pode ser adicionado um método extrato para avaliar os valores atuais da conta corrente, ou seja, o estado atual do objeto. Por exemplo, a conta tinha saldo de 300 reais após o primeiro depósito. Após a chamada de sacar (100), o saldo da conta será 200 reais. >>>from Conta import Conta >>>c1 = Conta(1,1,"Joao",0) .....c1.depositar(300) .....c1.sacar(100) class Conta: def__init__(self, numero, cpf, nomeTitular, saldo): self.numero = numero self.cpf = cpf self.nomeTitular = nomeTitular self.saldo = saldo def depositar(self, valor): self.saldo += valor def sacar(self, valor): self.saldo -= valor def gerarextrato(self): print(f"numero: {self.numero} \n cpf: {self.cpf}\nsaldo: {self.saldo") Figura 12 – Classe com métodos depositar/sacar/gerarextrato. Se o método gerarextrato() for executado, o valor impresso será: ....c1.geratextrato() 200 Métodos com retorno Em Python, não é obrigatório um comando para indicar quando o método deve ser finalizado. Porém, na orientação a objetos, como na programação procedural, é bastante comum retornar um valor a partir da análise do estado do objeto. Conforme exemplificado a seguir, não é permitido o saque de um valor maior do que o saldo atual do cliente, portanto, retorna a resposta “False” para o objeto que está executando o saque. O método deve ficar da seguinte forma: def sacar(self,valor): if self.saldo < valor: return False else: self.saldo -= valor return True Figura 13 – Método sacar com retorno. ....c1.sacar(100) True Agora, questione-se: ao executar o comando c1.sacar(300), qual será o retorno? Referências entre objetos na memória Uma classe pode ter mais de uma ou várias instâncias de objetos na memória, como exemplificado na imagem a seguir: from Conta import Conta conta1 = Conta(1, 123, ‘Joao’,0) conta2 = Conta(3, 456, ‘Maria’,0) Figura 14 – Método sacar com retorno. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 106 O Resultado na memória após a execução é apresentado na Figura 15: Figura 15 – Estado da memória conta1 e conta2. Fonte: Estácio de Sá Na memória, foram criados dois objetos diferentes referenciados pelas variáveis conta1 e conta2 do tipo conta. Os operadores “==” e “!=” comparam se as duas variáveis de referência apontam para o mesmo endereço de memória (CAELUM, 2020). >>>if (conta1 != conta2): ... print("Endereços diferentes de memória") Pelo esquema da memória, apresentado na Figura 15, realmente conta1 e conta2 apontam para endereços diferentes de memória. O comando “=” realiza o trabalho de igualar a posição de duas referências na memória. Fazendo conta1 = conta2, podemos ver o resultado: conta1 = conta2 if (conta1 == conta2): ... print("enderecos iguais de memoria") Figura 16 – Estado da memória conta1 e conta2 no mesmo endereço. Fonte: Estácio de Sá Se executarmos os comandos referenciando o CPF da conta, verificamos que possuem os mesmos valores, conforme mostrado acima. >>>conta1.cpf 456 >>> conta2.cpf 456 Esse exemplo pode ser estendido para realizar uma transferência de valores de uma conta para outra. A segunda conta deve ser passada como parâmetro do método para ser executada a transferência. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 107 O método deve ficar da seguinte maneira: class Conta: def__init__(self, numero, cpf, nomeTitular, saldo): self.numero = numero self.cpf = cpf self.nomeTitular = nomeTitular self.saldo = saldo def depositar(self, valor): self.saldo += valor def sacar(self,valor): if self.saldo < valor: return False else self.saldo -= valor return True def gerarextratp(self): print(f"numero:{self.numero}\n cpf:{self.cpf}\nsaldo:{selfsaldo}") def transfereValor(self,contaDestino,valor): if self.saldo < valor: return ("Não existe saldo suficiente") else: contaDestino.depositar(valor) self.saldo -= valor return("Transferencia Realizada") Figura 17 – Classe conta com transferência. >>>from Conta import Conta ... conta1 = Conta(1, 123,'Joao',0) ... conta2 = Conta(3, 456,'Maria',0) ... conta1.depositar(1000) ... conta1.transfereValor(conta2,500) ... print(conta1.saldo) ... print(conta2.saldo) 500 500 Em resumo, foi depositado 1000 na conta1 e realizada uma transferência de valor de 500 para conta2. No final, o saldo ficou 500 para conta1 e 500 para conta2. Importante ressaltar que, no comando conta1.transfereValor(conta2,500), é passada uma referência da conta2 para o objeto contaDestino por meio de um operador “=”. O comando contaDestino = conta2 é executado internamente no Python. Agregação Para atender novas necessidades do sistema de conta corrente do banco, agora se faz necessário adicionar uma funcionalidade para gerenciamento de conta conjunta, ou seja, uma conta corrente pode ter um conjunto de clientes associados. Isto pode ser representado como uma agregação, conforme apresentado no esquema a seguir. Uma observação: o losango na imagem tem a semântica da agregação. Fonte: Estácio de Sá Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 108 Para agregação, devemos criar dois arquivos contas.py e cliente.py: class Clientes: def__init__(self,cpf,nome,endereco): self.cpf = cpf self.nome = nome self.endereco = endereço Figura 19 – Classe clientes.py. class Conta: def__init__(self, clientes, numero, saldo): self.clientes = clientes self.numero = numero self.saldo = saldo def depositar(self, valor): self.saldo += valor def sacar(self,valor): if self.saldo < valor: return False else: self.saldo -= valor return True def transfereValor(self,contaDestino,valor): if self.saldo < valor: return ("Não existe saldo suficiente") else: contadestino.depositar(valor) self.saldo -= valor return("Transferencia Realizada") def gerarsaldo(self): print(f"numero:{self.numero\n saldo: {self.saldo}") Figura 20 – Classe contas.py Um programa testecontas.py deve ser criado para o usarmos na instanciação dos objetosdas duas classes e gerarmos as transações realizadas nas contas dos clientes. from contas import Conta from clientes import Cliente cliente1 = Cliente(123, “Joao”, “Rua 1”) cliente2 = Cliente(345, “Maria”,”Rua 2”) conta1 = Conta([cliente1,cliente2], 1,0) (1) conta1.gerarsaldo() conta1.depositar(1500) conta1.sacar(500) conta1.gerarsaldo() Figura 21 – Classe contasclientes.py. Atenção Em (1) foi instanciado um objeto conta1 com dois clientes agregados: cliente1 e cliente2. Esses dois objetos são passados como parâmetros em (1). Qual o resultado dessa execução? Qual valor final na conta? Sugestão: altere o programa do código anterior para criar mais uma conta para dois clientes diferentes. Como desafio, tente, através do objeto conta, imprimir o nome e o endereço dos clientes associados às contas. Composição A classe conta ainda não está completa de acordo com as necessidades do sistema de conta corrente do banco. Isso ocorre porque o banco precisa gerar extratos contendo o histórico de todas as operações realizadas para conta corrente. Para isso, o sistema deve ser atualizado para adicionar uma composição de cada conta com o histórico de operações realizadas. O diagrama a seguir representa a composição entre as classes Conta e Extrato. Esta composição representa que uma conta pode ser composta por vários extratos. Uma observação: o losango preenchido tem a semântica da composição. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 109 Figura 22 – Classe Conta composta de 1 ou mais extratos. Fonte: Estácio de Sá A classe Extrato tem as responsabilidades de armazenar todas as transações realizadas na conta e de conseguir imprimir um extrato com a lista dessas transações. class Extrato: def__init_(self): self.transacoes = [] def extrato(self, numeroconta): print(f”Extrato : {numeroconta} \n”) for p in self.transacoes: print(f”{p[0]:15s} {p[1]:10.2f} {p[2]:10s} {p[3].strftime(‘%d/%b/%y)}”) Figura 23 – Classe extrato.py. A classe conta possui todas as transações como sacar, depositar e transferir_valor. Cada transação realizada deve adicionar uma linha ao extrato da conta. Inclusive, a composição Conta->Extrato deve ser inicializada no construtor da classe Conta, conforme exemplificado na Figura 23. No construtor de Extrato, foi adicionado um atributo transações, o qual foi inicializado para receber um array de valores – transações da conta. A classe Conta alterada deve ficar da seguinte maneira: import datetime from Extrato import Extrato class Conta: def__init__(self,clientes,numero,saldo): self.clientes = clientes self.numero = numero self.saldo = saldo self.sata abertura = datetime.satetime.today() self.extrato = Extrato () (1) def depositar(self, valor): self.saldo += valor self.extrato.trasacoes.append(["DEPOSITO", valor, "Data",datetime.datetime.today ()])(2) def sacar(self, valor): if self.saldo < valor: return False else: self.saldo -= valor self.extrato.transacoes.append(["SAQUE", valor, "Data", datetime.datetime.today()]) (3) return True def transfereValor(self, contadestino, valor): if self.saldo < valor: return "Não existe saldo suficiente" else: contadestino.depositar(valor) self.saldo -= valor self.extrato.transacoes.append(["TRANSFERENCIA", valor, "Data", datetime.datetime.today()]) (4) return "Transferencia Realizada" def gerarsaldo(self): print(f"numero: {self.unmero}\n saldo:{self.saldo}") Figura 24 – Classe conta.py composta. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 110 Alterações da classe conta: Adição da linha (1) – criação de um atributo extrato, fazendo referência a um objeto Extrato Adição das linhas (2), (3) e (4) – adição de linhas ao array de transações do objeto Extrato através do atributo extrato. Execução no terminal: >>>from clientes import Cliente ... from ContasClientesExtrato import Conta ... cliente1 = Cliente("123","Joao","Rua X") ... cliente2 = Cliente ("456","Maria","Rua W") ... conta1 = Conta([cliente1,cliente2],1,2000) ... conta1.depositar(1000) ... conta1.sacar(1500) conta1.extrato.extrato(conta1.numero) Extrato : 1 DEPOSITO 1000.00 Data 14/Jun/2020 SAQUE 1500.00 Data 14/Jun/2020 Dica Experimente adicionar mais uma conta e realize uma transferência de valores entre ambas. Depois, crie uma classe Banco para armazenar todos os clientes e todas as contas do banco. Encapsulamento Conforme apresentado no módulo anterior, o encapsulamento é fundamental para a manutenção da integridade dos objetos e proibir qualquer alteração indevida nos valores dos atributos (estado) do objeto (CAELUM, 2020). Este ponto foi fundamental para a popularização da orientação aos objetos: reunir dados e funções em uma única entidade e proibir a alteração indevida dos atributos. Exemplo No caso da classe conta, imagine que algum programa tente realizar a seguinte alteração direta no valor do saldo: conta1.saldo = -200 Esse comando viola a regra de negócio do método sacar(), que indica para não haver saque maior do que o valor e deixar a conta no negativo (estado inválido para o sistema). def sacar(self, valor): if self.saldo < valor: return False else: self.saldo -= valor self.extrato.transacoes.append(["SAQUE", valor, "Data", datetime.datetime.today()]) return True Figura 25 – Método sacar no estado inválido. Como proibir alterações indevidas dos atributos em Python? Atributos públicos e privados Para seguir o encapsulamento e proibir alterações indevidas dos atributos, devemos definir atributos privados para a classe. Por default, em Python, os atributos são definidos como público, ou seja, podem ser acessados diretamente sem respeitar o encapsulamento - acesso feito apenas através de métodos do objeto. Para tornar um atributo privado, devemos iniciá-lo com dois underscores (‘__’). A classe conta possui todos os atributos privados: >>>class Conta: ... def__init__(self,numero,saldo): ... self.numero = __numero ... self.saldo = __saldo: Figura 26 – Classe Conta com atributos privados. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 111 Qual o retorno do interpretador ao se acessar um atributo privado para classe Conta? >>> conta = Conta(1,1000) >>> conta.saldo Traceback (most recent call last): File "<input>", line 1, in <module> AttributeError: 'Conta' object has no attribute 'saldo' É importante ressaltar que, em Python, não existe realmente atributos privados. O interpretador renomeia o atributo privado para _nomedaClasse__nomedoatributo. Portanto, o atributo ainda pode ser acessado. Embora funcione, é considerado uma prática que viola o princípio de encapsulamento da orientação a objetos. >>> conta._Conta__saldo 1000 Na prática, deve existir uma disciplina para não serem acessados diretamente os atributos como __ ou _ definido nas classes. Decorator @property Uma estratégia importante disponibilizada pelo Python são as properties. Utilizando o decorator property nos métodos, mantém-se os atributos como protegidos, e estes são acessados apenas através dos métodos “decorados” com property (CAELUM, 2020). No caso da classe conta, não se pode acessar o atributo saldo (privado) para leitura. Com o código, ele será acessado pelo método decorator @property: @property def saldo(self): return self._saldo Figura 27 – Definição de uma propriedade. Os métodos decorados com a property @setter forçam que todas alterações de valor dos atributos privados devem passar por esses métodos. Notação: @<nomedometodo>.setter @saldo.setter def saldo(self, saldo): if (self.saldo < 0): print(“saldo inválido”) else: self._saldo = saldo Figura 28 – Definição de um método setter. Os properties ajudam a garantir o encapsulamento no Python. Uma boa prática implementada emtodas as linguagens orientadas a objetos é definir esses métodos apenas se realmente houver regra de negócios diretamente associada ao atributo. Caso não haja, deve-se deixar o acesso aos atributos conforme definido na classe. Atributos de classe Existem algumas situações que os sistemas precisam controlar valores associados à classe, e não aos objetos (instâncias) das classes; por exemplo, ao desenvolver um aplicativo de desenho, como o Paint, que precisa contar o número de círculos criados na tela. Inicialmente, a classe Circulo vai ser criada: class Circulo(): def__init__(self,pontox,pontoy,raio): self.pontox = pontox self.pontoy = pontoy self.raio = raio Figura 29 – Classe Circulo. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 112 No entanto, conforme mencionado, é necessário controlar a quantidade de círculos criados. class Circulo(): total_circulos = 0 (1) def__init__(self,pontox, pontoy, raio): self.pontox = pontox self.pontoy = pontoy self.raio = raio self.total_circulos +=1 (2) Figura 30 – Classe Circulo com atributo de classe. Em (1), indicamos para o interpretador que seja criada uma variável total_circulos. Como a declaração está localizada antes do init, o interpretador “entende” que se trata de uma variável de classe, ou seja, terá um valor único para todos objetos da classe. Em (2), é atualizado o valor da variável de classe a cada instanciação de um objeto da classe Circulo. Como acessar os valores armazenados em uma variável de classes? >> from Circulo import Circulo >>>circ1 = Circulo(1,1,10) >>>circ1.total_circulos >>>circ2 = Circulo(2,2,20) >>>circ2.total_circulos >>>Circulo.total_circulos Esse acesso direto ao valor da variável de classe não é desejado. Deve-se colocar a variável com atributo privado com o underscore ‘_’. Como fica agora? O resultado é apresentado a seguir. class Circulo: _total_circulos = 0 def__init__(self,pontox, pontoy, raio): self.pontox = pontox self.pontoy = pontoy self.raio = raio circulo._total_circulos += 1 Figura 31 – Classe Circulo com atributo de classe privado Repetindo o mesmo código: >>>from Circulo import Circulo >>>circ1 = Circulo(1,1,10) >>>circ1._total_circulos 1 >>>Circulo.total_circulos Traceback (most recent call last): File "<input>", line 1, in <module> AttributeError: type object 'Circulo' has no attribute 'total_circulos' Métodos de classe Os métodos de classe são a maneira indicada para se acessar os atributos de classe. Eles têm acesso diretamente à área de memória que contém os atributos de classe. O esquema é apresentado na imagem a seguir. Figura 32 – Memória Estática. Fonte: Estácio de Sá Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 113 Para definir um método como estático, deve-se usar o decorator @classmethod. Assim, segue a classe Circulo alterada: class Circulo: _total_circulos = 0 def__init__(self, pontox, pontoy, raio): self.pontox = pontox self.pontoy = pontoy self.raio = raio type(self)._total_circulos +=1 @classmethod def get total_circulos(cls): (1) return cls.total_circulos (2) Figura 33 – Método de classe. Em (1), é criado o parâmetro cls como referência para classe. Em (2), é retornado o valor do atributo de classe _total_circulos. Métodos públicos e privados As mesmas regras definidas para atributos são válidas para os métodos das classes. Assim, o método pode ser declarado como privado, mas ainda pode ser chamado diretamente, como se fosse um método público. Os dois underscores antes do método indicam que ele é privado: class Circulo: def__init__(self,clientes,numero,saldo): self.clientes = clientes self.numero = numero self.saldo = saldo def __gerarsaldo(self): print(f"numero: {self.numero}\n saldo:{self.saldo}") Figura 34 – Método privado. No código acima, foi definido o método geralsaldo como privado. Portanto, pode ser acessado apenas internamente pela classe Conta. Um dos principais padrões da orientação a objetos consiste nos métodos públicos e nos atributos privados. Desse modo, respeita-se o encapsulamento. Métodos estáticos São métodos que podem ser chamados sem ter uma referência para um objeto da classe, ou seja, não existe obrigatoriedade da instanciação de um objeto da classe. O método pode ser chamado diretamente: import math class Math: @staticmethod def sqrt(x): return math.sqrt(x) Figura 35 – Método estático. >>> Math.sqrt(20) 4.47213595499958 No caso acima, foi chamado o método sqrt da classe Math sem haver instanciado um objet da classe Math. Os métodos estáticos não são uma boa prática na programação orientada a objetos. Devem ser utilizados apenas em casos especiais, como classes de log em sistemas. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 114 Verificando o aprendizado 1. Analise o seguinte código escrito em Python, que define a estrutura da classe ContaBancaria: class ContaBancaria: num_contas = 0 def__init__(self,clientes,numero,saldo): self.agencia = agencia self.numero = numero self.saldo = saldo ContaBancaria.num_contas += 1 def _del_(self): ContaBancaria.num_contas -= 1 def depositar(self,valor): self.saldo = self.valor + valor def sacar(self, valor): self.saldo = self.valor - valor def consultarSaldo(Self): return self.saldo Sobre a classe acima e as regras de programação orientada a objetos em Python, a opção INCORRETA: A. A criação de objetos chama primeiro o método __new__() e, em seguida, o __init__(). B. A palavra self deve ser fornecida como argumento em todos os métodos públicos de instâncias. C. A variável num_contas é encapsulada e individual para cada instância da classe. D. O método __del__ é privado. A instância do objeto (self) deve ser passada implicitamente em cada chamada do método. 2. Qual a diferença na utilização dos decorators @staticmethod e @classmethod: A. O decorator @staticmethod definem métodos de classe que manipulam atributos de classe. B. O decorator @classmethod definem métodos estáticos que permite acesso a métodos sem instanciação da classe. C. O decorator @classmethod permite que que os atributos de classe sejam alterados na área de memória. D. Os decorators @staticmethod e @classmethod podem ser usados de forma intercambiáveis. O decorator @classmethod fica armazenado na mesma área de memória dos atributos de classe. Portanto, pode alterar os valores dos atributos de classe. Conceitos da orientação a objetos como Herança e Polimorfismo Herança e polimorfismo As linguagens orientadas a objeto oferecem recursos que permitem o desenvolvimento de sistemas de uma forma mais ágil e eficiente. Esses recursos são a herança, polimorfismo e o tratamento das exceções. A herança permite uma redução da repetição de código, permitindo que uma classe filho herde métodos e atributos da classe pai. O polimorfismo permite que, em determinadas situações, os objetos possam ter comportamentos específicos. Caso haja algum problema durante a execução do código, o tratamento de exceções facilita a manipulação do erro. Herança É um dos princípios mais importantes da programação orientada a objetos, pois permite a reutilização de código com a possiblidade de extensão para se ajustar às regras de negócio dos sistemas. Exemplo A área de produtos do banco define quais clientes podem ter acesso a um produto chamado Conta especial, o qual busca atender quem possui conta na instituição há mais de um ano. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 115 Na visão de negócios, a conta especial possui a funcionalidade de permitir o saque da conta corrente até um certo limite, determinado durante a criação da conta. Nesse ponto, há um dos grandes ganhos da orientação a objetos. O objeto do mundo real ContaEspecial será mapeado para uma classe ContaEspecial que herdará todo código de Conta, conforme a figura 36: Figura 36 - Herança Conta -> ContaEspecial. Fonte: Estácio de Sá Este é o código da implementação da nova classe Conta Especial: from ContasClientesExtrato import Conta import datetime class ContaEspecial(Conta): def__init__(self,clientes,numero,saldo,limite): Conta.__init__(self,clientes,numero,saldo) (1) self.limite = limite (2) def sacar(self, valor): (3) if (self.saldo + self.limite) < valor: return False else: self.saldo -= valor self.extrato.transacoes.append(["SAQUE", valor "Data", datetime.datetime.today()]) return True Figura 37 - Classe ContaEspecial. Analisando o código ContaEspecial acima, tem-se as seguintes modificações: Método construtor __init__ A classe tem que ser instanciada com passagem do limite como um parâmetro da construção do objeto. O método __Init__ foi sobrescrito da superclasse Conta. O método super() foi utilizado para chamar um método da superclasse e pode ser utilizado em qualquer método da subclasse (REAL PYTHONON, 2020). A orientação a objetos permite, inclusive, a reutilização do construtor da classe pai (1). Atributo limite Foi adicionado apenas na subclasse ContaEspecial, onde será utilizado para implementar a regra de saques além do valor do saldo (2). Método sacar() O método precisa verificar se o valor a ser sacado, passado como parâmetro, é menor do que a soma do saldo atual mais o limite da conta especial. Nesse caso, a classe Conta Especial reescreveu o método sacar da superclasse Conta. Essa característica é conhecida como sobrescrita (override) dos métodos (3). Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 116 A execução no terminal gera o seguinte: >>>from clientes import Cliente ... from ContasClientesExtrato import Conta ... from ContaEspecial import ContaEspecial ... cliente1 = Cliente("123","Joao","Rua X") ... cliente2 = Cliente ("456","Maria","Rua W") ... cliente3 = Cliente ("789","Joana", "Rua H") ... conta1 = Conta([cliente1,cliente2],1,2000) ... conta2 = ContaEspecial([cliente3],3,1000,2000) ... conta2.depositar(100) ... conta2.sacar(3200) Valor do Saque 3200.00 maior que o valor do saldo mais o limite 3100.00 >>> conta2.extrato.extrato(conta2.numero) Extrato : 3 DEPOSITO 100.00 Data 14/Jun/2020 Veja o diagrama atualizado com a implementação dos métodos na subclasse: Figura 38. Herança Conta -> ContaEspecial. Fonte: Estácio de Sá Importante ressaltar que ContaEspecial é uma classe comum e pode ser instanciada como todas as outras classes independentes da instanciação de objetos da classe Conta. Uma análise comparativa com a programação procedural indica que, mesmo em caso com código parecido, o reuso era bastante baixo. O programador tinha que criar um programa Conta Corrente Especial repetindo todo o código já definido no programa Conta Corrente. Com a duplicação do código, era necessário realizar manutenção de dois códigos parecidos em dois programas diferentes. Herança Múltipla A herança múltipla é um mecanismo que possibilita uma classe herdar código de duas ou mais superclasses. Esse mecanismo é implementado por poucas linguagens orientadas a objetos e insere uma complexidade adicional na arquitetura das linguagens. Para nosso sistema, vamos considerar a necessidade de um novo produto que consiste em uma conta corrente, similar a definida anteriormente no sistema, com as seguintes características: Deverá ter um rendimento diário com base no rendimento da conta poupança. Deverá ser cobrada uma taxa de manutenção mensal, mesmo se o rendimento for de apenas um dia. A Classe Poupança também será criada para armazenar a taxa de renumeração e o cálculo do rendimento mensal da poupança. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 117 Este será o diagrama com as novas classes criadas no sistema de conta corrente: Figura 39. Herança Múltipla. Fonte: Estácio de Sá A implementação ficou detalhada nos códigos a seguir: import datetime class ContaPoupanca: def__init__(self,taxaremuneracao): self.taxaremuneracao = taxaremuneracao self.data_abertura = datetime.datetime.today() def remuneracaoConta(self) self.saldo +=self.saldo * self.taxaremuneracao Figura 40 - Conta Poupança. from ContasClientesExtrato import Conta from ContaPoupanca import Poupanca class ContaRemuneradaPoupanca(Conta, Poupanca): (1) def__init__(self, taxaremuneracao, clientes, numero, saldo, taxaremuneracao): Conta.__init__(self,clientes,numero,saldo) (2) Poupanca.__init__(self,taxaremuneracao) (2) self.taxaremuneracao = taxaremuneracao def remuneraConta(Self): self.saldo += self.saldo * (self.taxaremuneracao/30) self.saldo -= self.taxaremuneracao Figura 41 - Conta Remunerada. Alguns pontos importantes sobre a implementação: Declaração de herança múltipla: em (1) indica que a classe é herdeira de Conta e Poupança nessa ordem. Essa ordem tem importância, pois existem dois métodos no pai com o mesmo nome. O Python dá prioridade para a primeira classe que implementa esse método na ordem da declaração – (PYTHON COURSE, 2020) Construtor da classe: Deve ser chamado o construtor explicitamente das superclasses com o seguinte formato: nomeclasse.__init__(construtores) Execução no terminal: >>>from clientes import Cliente ... from ContasClientesExtrato import Conta ... from ContaPoupanca import Poupanca ... from ContaRemuneradaPoupanca import ContaRemuneradaPoupanca ... cliente1 = Cliente("123","Joao","Rua X") ... cliente2 = Cliente ("456","Maria","Rua W") ... conta1 = Conta([cliente1,cliente2],1,2000) ... contapoupanca1 = Poupanca(0.1) ... contarenumerada1 = ContaRemuneradaPoupanca(0.1,cliente1,5,1000,5) ... contarenumerada1.remuneraConta() ... contarenumerada1.geralsaldo() numero: 5 saldo: 998.3333333333334 Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 118 Polimorfismo Polimorfismo é o mecanismo que permite que um método com o mesmo nome seja executado de modo diferente, dependendo do objeto que está chamando o método. A linguagem define em tempo de execução (late binding) qual método deve ser chamado. Essa característica é bastante comum em herança de classes devido à redefinição da implementação dos métodos nas subclasses. Vamos assumir que agora teremos uma entidade Banco que controla todas as contas criadas no sistema de conta corrente. As contas podem ser do tipo conta, contacomum ou contarenumerada. O cálculo do rendimento da conta Cliente desconta IOF e IR; a conta Renumerada desconta apenas o IOF; a conta Cliente não tem desconto nenhum. Todos os descontos são realizados em cima do valor bruto após o rendimento mensal. Uma vez por vez o Banco executa o cálculo do rendimento de todos os tipos de contas. O diagrama é apresentado na Figura 42. Figura 42 - Diagrama Banco. Fonte: Estácio de Sá Vamos analisar agora a implementação das classes: class ContaCliente: def__init__(self, numero, IOF,IR,valorinvestido,taxarendimento): self.numero = numero self.IOF = IOF self.IR = IR self.valorinvestido = valorinvestido self.taxarendimento = taxarendimento def CalculoRendimento(self): self.valorinvestido += (self.valorinvestido * self.taxarendimento) self.valorinvestido = (self.valorinvestido – (self.taxarendimento * self.IOF* self.IR) def Extrato(Self): (1) print (f"O saldo atual da conta {self.numero} é {self.valorinvestido:10.2f}") Figura 43 - Classe ContaCliente. from ContaCliente import ContaCliente class ContaComum(ContaCliente) def__init__(self,numero,IOF,IR,valorinvestido,taxarendimento): super().__init__(numeroIOFIR,valorinvestido,taxarendimento) def CalculoRendimento(Self): (2) sef.valorinvestido += (self.valorinvestido* self.taxarendimento) Figura 44 - Classe ContaComum. from ContaCliente import ContaCliente class ContaRemunerada(ContaCliente): def__init__(self,numero,IOF,IR,valorinvestido,taxarendimento): super().__init__(numeroIOFIR,valorinvestido,taxarendimento) def CalculoRendimento(Self): (3) sef.valorinvestido += (self.valorinvestido * self.taxarendimento) sef.valorinvestido -= self.valorinvestido * self.IOF Figura 45 - Classe ContaCliente. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 119 Em (1), foi definido um método Extrato que é igual para as 3 classes, ou seja, as subclasses herdarão o código completo desse método. Em (2) e (3), as subclasses possuem regras de negócios diferentes, portanto sobrescreveram o método CalculoRendimento para atender às suas necessidades. Vamos analisar a implementação da classe Banco e do programa que a utiliza: class Banco(): def__init__(self, codigo,nome): self.codigo = codigo self.nome = nome self.contas = [] def adicionaconta(self,contacliente): self.contas.append(contacliente) def calcularendimentomensal(self): (7) for c in self.contas: c.CalculoRendimento() def imprimesaldocontas(self): for c in self.contas: Figura 46 - Classe Banco. banco1 = Banco(999,"teste") contacliente1 = ContaCliente (1,0.01,0.1,1000,0.05) contacomum1 = ContaComum(2,0.01,0.1,2000,0.05) contaremunerada1 = ContaRemunerada(3,0.01,0.1,2000,0.05) banco1.adicionaconta(contacliente1) (4) banco1.adicionaconta(contacomum1) (5) banco1.adicionaconta(contaremunerada1) (6) banco1.calcularendimentomensal(7) banco1.imprimesaldocontas() (8) Figura 47 - Classe BancoContas. Em (4), (5) e (6), banco adiciona todas as contas da hierarquia em um único método devido ao teste “É-UM” das linguagens orientadas a objetos. No método, isso é definido para receber um objeto do tipo ContaCliente. Toda vez que o método é chamado, a linguagem testa se o objeto passado “É- UM” objeto do tipo ContaCliente. Em (4), o objeto é da própria classe Conta Cliente. Em (5), o objeto contacomoum1 passado é uma ContaComum, que passa no teste “É-UM”, pois uma ContaComum é uma ContaCliente também. Em (6), o objeto contarenumerada1 “É-UM” objeto ContaComum. Essas ações são feitas de forma transparente para o programador. Em (7), acontece a “mágica” do Polimorfismo. Pois, em (4), (5) e (6) são adicionadas contas de diferentes tipos para o array conta da classe Banco. Assim, no momento da execução do método c.calculorendimentomensal(), o valor de c recebe, em cada loop, um tipo de objeto diferente da hierarquia da classe ContaCliente. Portanto, na instrução c.CalculoRendimento(), o interpretador tem que identificar dinamicamente de qual objeto da hierarquia Conta Cliente deve ser chamado o método CalculoRendimento. Em (8), acontece uma característica que vale ser ressaltada. Pelo polimorfismo, o interpretador irá verificar o teste “É-UM”, porém esse método não foi sobrescrito pelas subclasses da hierarquia ContaCliente. Portanto, será chamado o método Extrato da superclasse. Se executarmos a classe o programa banco_contarenumerada, o resultado será: O saldo atual da conta 1 é: 1048.95 O saldo atual da conta 2 é: 2100.00 O saldo atual da conta 3 é: 2079.00 O polimorfismo é bastante interessante em sistemas com dezenas de subclasses herdeiras de uma única classe, assim todas as subclasses redefinem esse método. Sem o polimorfismo, haveria a Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 120 necessidade de “perguntar” para a linguagem qual a instância do objeto em execução para chamar o método correto. Com base no polimorfismo, essa checagem é feita internamente pela linguagem de maneira transparente. Classes abstratas Definir uma classe abstrata é uma característica bastante utilizada pela programação orientada a objetos. Esse é um tipo de classe que não pode ser instanciado durante a execução do programa orientado a objetos, ou seja, não podem existir objetos dessa classe sendo executados na memória. O termo abstrato remete a um conceito do mundo real, considerando que preciso apenas de objetos concretos no programa. A classe abstrata se encaixa perfeitamente no problema do sistema de conta corrente do banco. Nesse sistema, o banco não quer tratar clientes do tipo ContaCliente, e sim apenas os objetos do tipo ContaComum e Conta VIP. Figura 48 - Diagrama de Classes abstratas. Fonte: Estácio de Sá Houve apenas adição de esteréotipo <<abstract>> para indicar que Conta Cliente é uma classe abstrata. O Python utiliza um módulo chamado abc para definir uma classe como abstrata, a qual será herdeira da superclasse ABC (Abstract Base Classes). Toda classe abstrata é uma subclasse da classe ABC (CAELUM, 2020). Para tornar a classe Conta Cliente abstrata, muda-se a definição para: from abc import ABC class ContaCliente(ABC): Figura 49 - Definição de classe abstrata. Para uma classe ser considerada abstrata, tem de ter pelo menos um método abstrato. Esse método abstrato pode ter implementação, embora não faça sentido, pois ele deverá, obrigatoriamente, ser implementado pelas subclasses. Em nosso exemplo, como não teremos o ContaCliente, esta conta não terá Calculo do rendimento. O decorator @abstractmethod indica para a linguagem que o método é abstrato (STANDARD LIBRARY, 2020), conforme o código apresentado na Figura 3.15: from abc import ABC, abstractmethod class ContaCliente(ABC) def__init__(self,sumero,IOF,IR,valorinvestido,taxarendimento): self.numero = numero self.IOF = IOF self.IR = IR self.valorinvestido = valorinvestido self.taxarendimento = taxarendimento @abstractmethod def CalculoRendimento(self): pass Figura 50 - Definição de método abstrato. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 121 Quando se tentar instanciar um objeto da classe, obtém-se um erro indicando que essa classe não pode ser instanciada.. >>> from ContaClienteAbstrata import ContaCliente cc1 = ContaCliente(1,0.1,0.25,0.1) Traceback (most recent call last): File "<input>", line 2, in <module> TypeError: Can't instantiate abstract class ContaCliente with abstract methods CalculoRendimento Apenas as subclasses ContaComum e ContaVIP podem ser instanciadas. Atenção Um alerta importante é que as classes mencionadas devem, obrigatoriamente, implementar os métodos. Caso contrário, serão classes abstratas e delegarão para as suas subclasses a implementação concreta do método abstrato. Fica como sugestão criar as classes concretas ContaComum e ContaVIP sem implementar o método abstrato do superclasse ContaCliente. Exceções Como diversas linguagens orientadas a objetos, o Python permite criar novos tipos de exceções para diferenciar os erros gerados pelas bibliotecas da linguagem dos erros gerados pelas aplicações desenvolvidas. Devemos usar a característica da herança para herdar novas exceções a partir classe Exception do Python (MENEZES, 2020). class ExcecaoCustomizada(exception): (1) pass def throws(): (2) raise ExcecaoCustomizada try: (3) throws() except ExcecaoCustomizada as ex: print ("Excecao lançada") Figura 51 - Exceção customizada. No código acima: Em (1), definimos a exceção customizada ExcecaoCustomizada com o método pass, pois não executa nada relevante. Em (2), é definido um método que, se for chamado, cria a exceção na memória ExcecaoCustomizada. Em (3), é utilizado o try...except, que indica para o interpretador que a área do código localizada entre o try e o except poderá lançar exceções que deverão ser tratadas nas linhas de código após o except. Ao final da execução, será impresso “Exceção lançada” pela captura da exceção lançada pelo método throws(). Verificando o aprendizado 1. Sobre a linguagem de programação PYTHON, marque a alternativa incorreta. A.Python suporta a maioria das técnicas da programação orientada a objetos. B. Python suporta e faz uso constante de tratamento de exceções como uma forma de testar condições de erro e outros eventos inesperados no programa. C. A linguagem Python permite a utilização do conceito de sobrecarga de métodos através do polimorfismo dos métodos. D. A separação de blocos de código em Phyton é feita utilizando a endentação de código. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 122 A questão abrange uma visão geral sobre os conceitos de Orientação a Objetos implementados em Python. O Python não permite a sobrecarga de métodos nativamente. 2. Em relação aos conceitos da orientação a objetos e à implementação em Python, podemos afirmar que: A. O Poliformismo permite a sobrecarga de métodos em uma classe Python. B. Objetos diferentes podem ser agrupados na mesma classe, sempre que tenham o mesmo número de bytes. C. Na linguagem Python existe o conceito de classes abstratas, que fornecem uma capacidade de reutilização para programas orientados a objetos. D. A linguagem Python implementa parcialmente herança múltipla através do poliformismo. A questão abrange uma visão geral a implementação dos conceitos orientados a objetos implementados em Python. Uma classe em Python pode representar um objeto abstrato do mundo real, pois esse possui propriedades e responsabilidades que serão utilizados por outros objetos da hierarquia no programa orientado a objetos. OO aplicados a Python com outras linguagens OO existentes no mercado Linguagem de programação orientadas a objetos As linguagens orientadas a objetos C++ e Java são bastante utilizadas assim como Python. Inclusive, estão em posições subsequentes no ranking das linguagens mais utilizadas de todos os paradigmas (TIO- BE, 2020). Portanto, é interessante fazer um paralelo das principais características de cada linguagem em relação a orientada a objetos: Python (RAMALHO, 2020), C++ (C++, 2020) e Java (JAVA, 2020) Comparação com C++ e Java Instanciação de objetos As linguagens Java e C++ obrigam utilizar a palavra reservada new e indicar o tipo do objeto. Porém, a linguagem C++ referencia todos os objetos explicitamente através de ponteiros. Na tabela a seguir, o objeto Conta instanciado será referenciado pelo ponteiro *c1. A linguagem Python tem uma forma simplificada de instanciação de objetos. Java C++ Python Conta c1 = new Conta() Conta *c1 = new Conta() c1 = Conta() Tabela 3 - Instanciação de Objetos em Java/C++/Python. Construtores de objetos As linguagens Java e C++ exigem um método definido como público e com o mesmo nome da classe. O Python obriga ser um método privado __init__, conforme apresentado na tabela 4. Java C++ Python Utilizado um método público com o mesmo nome da classe sem tipo de retorno: Public Conta() Utilizado um método com o mesmo nome da classe sem tipo de retorno: Conta::Conta(void) Utilizando um método público com a obrigatoriedade da passagem do objeto com self: def __init__(self) Tabela 4 – Construtores de Objetos em Java/C++/Python. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 123 Modificadores de acesso atributos As linguagens C++ e Python possuem apenas os modificadores público e privado para atributos. No entanto, ao contrário de Python, C++ e Java, garantem que um atributo definido como privado seja acessado estritamente pela própria classe. O Python permite burlar um atributo privado, conforme detalhado na Tabela 5. Java C++ Python Possui os seguintes modificadores: public private protected default Possui os seguintes modificadores: public private Possui os seguintes modificadores: público privado – iniciado com “_” Modificador privado pode ser burlado e acessado diretamente Tabela 5 – Modificadores de acesso em atributos em Java/C++/Python. Herança múltipla de classes As linguagens C++ e Python implementam herança múltipla diretamente através de classes, enquanto Java implementa através de herança múltipla de interfaces. Java C++ Python Não implementa. Utiliza herança múltipla de interfaces para substituir essa característica. Implementa com a referência das classes herdadas na declaração da classe: Class ContaRemunerada: public Conta, public Poupanca) Implementa com a referência das classes herdadas na declaração da classe: class ContaRemuneradaPoupanca(Conta, Poupanca): Tabela 6 – Herança Múltipla em Java/C++/Python. Classes sem definição formal A linguagem Java implementa o conceito como classes anônimas. Todas as linguagens implementam o conceito de classes sem definição formal. Java C++ Python protected void Calcular(){ class Calculo{ private int soma; public void setSoma(int soma) { this.soma = soma; } public int getSoma() { return soma; } } } Existe o conceito de classes locais – internos a funções: void func() { class LocalClass { } } Existe o conceito de classes locais: def scope_test(): def do_local(): spam = "local spam" Tabela 7 – Classes informais em Java/C++/Python. Tipos primitivos As linguagens Java e C++ possuem um conjunto parecido de tipos primitivos, enquanto, em Python, todos as variáveis são definidas como objetos. Java C++ Python Possui vários tipos primitivos: int byte short long float double boolean char Possui vários tipos primitivos: bool char int float double void wchar_t Todas as variáveis são consideradas objetos: >>>5.__add__(3) 8 Tabela 8 - Tipos Primitivos em Java/C++/Python. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 124 Interfaces As linguagens Java e C++ implementam interfaces simples e múltiplas, enquanto, em Python, não existe o conceito de interfaces. Java C++ Python Implementa interfaces simples e múltiplas: Simples - Class Funcionario implements Autenticavel Múltipla - Class Funcionario implements Autenticavel, Descontavel Implementa interfaces simples e múltiplas: Simples - Class Funcionario: public Autenticavel Múltipla - Class Funcionario: public Autenticavel, public Descontavel Não implementa Tabela 9 – Interfaces em Java/C++/Python. Sobrecarga de métodos As linguagens Java e C++ implementam sobrecarga de métodos nativamente, enquanto Python não implementa nativamente. Java C++ Python Implementa sobrecarga de métodos: calculaimposto(salario) calculaImposto(salario,IR) Implementa sobrecarga de métodos: calculaimposto(salario) calculaImposto(salario,IR) Não implementa Tabela 10 – Sobrecarga de métodos em Java/C++/Python. Tabela Comparativa A Tabela 11 apresenta um resumo das características das linguagens Java, C++ e Python. A linguagem Python não possui atributos privados, interfaces e sobrecarga de métodos, que são necessidades fundamentais para a construção de sistemas orientados a objetos robustos baseado em design patterns. Portanto, Java e C++ levam uma vantagem considerável para construção de sistemas puramente orientados a objetos. Por sua simplicidade, o Python vem crescendo como a primeira linguagem de programação ensinada na graduação e, devido a quantidade de bibliotecas estatísticas e frameworks web existentes, é utilizada para o desenvolvimento de algoritmos de Data Science e sistemas Web. Tabela 11 – Tabela Comparativa. Características Linguagens Java C++ Python Instanciação de objetos X X X Construtores de objetos X X X Modificadores de acesso atributos X X X(*) Modificadores de acesso métodos X X X Herança múltipla X X Classes informais X X Tipos primitivos X X Interfaces X X Sobrecarga de métodos X X (*) Pode ser burlado e ser acessado diretamente Verificando o aprendizado 1. Em Python, como trabalhar sempre com tipos objetos: A. x = c + ADD(a+ b). B. obj(8).__. C. 5.__add__(3). D. sub(5).__add__(3). Na alternativa C, é realizada a soma seguindo a nota Python. A questão abrange o tratamento do Python em relação a todos os tipos serem considerados objetos. 2. Analisando as características Orientada a Objetos de C++, Java e Python, consideramos as seguintes afirmações: I. Em Java, é permitida a criação de herança múltipla de classes através das classes denominadas interfaces Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 125 II. Em Python, como em C++ e Java, existem os tipos primitivos e os objetos para serem utilizados pelos programas III. Em Python, o encapsulamento não segue os princípios da orientação a objetos A. II, apenas. B. II e III, apenas. C. I e II, apenas. D. III, apenas. Python não possui tipos privados. Os tipos privados que garantem o encapsulamento são em Java e C++. Considerações finais O paradigma orientado a objetos é largamente utilizado no mercado devido a facilidade na transformação de conceitos do mundo real para objetos implementados em uma linguagem de software. A linguagem Python implementa o paradigma utilizando uma sintaxe simples e robusta. Portanto, vem ganhando mercado, tanto na área acadêmica, quanto na área comercial, para o desenvolvimento de sistemas orientado a objetos. Outro ponto importante é que, devido a sua simplicidade, Python vem sendo utilizada como a primeira linguagem a ser aprendida nos primeiros períodos dos cursos de tecnologia e engenharia, inclusive para fixar os conceitos da orientação a objetos. A quantidade de bibliotecas opensource disponíveis para a implementação de algoritmos de Data Science tornou a linguagem importante para a implementação de sistemas de aprendizado e visualização de dados. Referências ANAYA, M. Clean Code in Python: Refactor Your Legacy Code Base. Packt Publishing Ltd, 29 ago. 2018. CAELUM. Python e Orientação a Objetos. Consultado em meio eletrônico em: 14 jun. 2020. COSTA, M. Análise Orientada a Objetos, março de 2017, 100 f. Notas de Aula. FARINELLI, F. Conceitos básicos de programação orientada a objetos. Consultado em meio eletrônico em: 14 jun. 2020. GIRIDHAR, C. Learning Python Design Patterns. Packit Publishing, 2018. JAVA. Java Tutorial. Consultado em meio eletrônico em: 14 jun. 2020. MENEZES, N. C. Introdução a programação com Python. 3. ed. São Paulo: Novatec, 2019. PAJANKAR, A. Python Unit Test Automation: Practical Techniques for Python Developers and Testers. Apress, 2017. PYTHON COURSE, Blog Python. Consultado em meio eletrônico em: 14 jun. 2020. QCONCURSOS, Questões de Concursos. Consultado em meio eletrônico em: 18 jun. 2020. RAMALHO, L. Fluent Python. Sebastopol, CA: O´Reilly Media, 2015. RAMALHO, L. Introdução à Orientação a Objetos em Python (sem sotaque). Consultado em meio eletrônico em: 14 jun. 2020. REAL PYTHON, Blog Python, Consultado em meio eletrônico em: 14 jun. 2020. RUMBAUGH, J. et al. Modelagem e projetos baseados em objetos, Rio de janeiro: Campus, 1994. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 126 STANDARD LIBRARY, The Python Standard Library. Consultado em meio eletrônico em: 24 jun. 2020. TIO-BE. TIO-BE Index. Consultado em meio eletrônico em: 14 jun. 2020. TUTORIALSPOINT. C++ Tutorial. Consultado em meio eletrônico em: 14 jun. 2020. Explore+ Para saber mais sobre os assuntos tratados neste tema: O desenvolvimento de frameworks com metaclasses é um assunto importante e bastante utilizado no mercado. Para estudar um pouco mais esse tema, indicamos o livro Fluent Python, de Luciano Ramalho, publicado em 2015 pela O´Reilly Media. Os designs patterns devem ser estudados e aplicados em sistemas orientados a objetos de qualquer porte. Para se aprofundar nesse assunto, indicamos a obra Clean Clean Code in Python: Refactor Your Legacy Code Base, de Mariano Anaya, publicado em 2018 pela Packit Publishing e o livro Learning Python Design Patterns, de Giridhar publicado em 2018 Packit Publishing. Os testes unitários buscam garantir a qualidade da implementação de acordo com as regras de negócios. Para se aprofundar nesse assunto, indicamos a obra Python Unit Test Automation: Practical Techniques for Python Developers and Testers de Ashwin Pajankar publicado em 2017 pela Apress. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 127 Python em Outros Paradigmas Introdução Neste tema, veremos a utilização da linguagem Python em outros paradigmas, como linguagem funcional, computação concorrente, desenvolvimento web e ciência de dados. Apesar do Python não ser uma linguagem puramente funcional, ela fornece ferramentas para que possamos programar utilizando esse paradigma. Neste tema, veremos como isso é possível e quais as vantagens desse estilo de programação. Vamos aprender também conceitos relacionados à computação concorrente e à computação paralela, suas diferenças e como podem ser implementadas em Python. Embora o Python não seja a primeira escolha como linguagem de programação para servidores web, é possível encontrar diversos frameworks que facilitam, e muito, a sua utilização para tal finalidade. Alguns são minimalistas, como o — Flask — e permitem criar servidores web escritos em Python em minutos. Em contrapartida, essa linguagem de programação é a escolha número 1 para a ciência de dados. Neste tema, iremos conhecer um pouco sobre o processo de extração de conhecimento e como implementá- lo em Python utilizando a biblioteca Scikit-Learn. Linguagem funcional e sua utilização em Python Introdução A programação funcional teve seu início no final dos anos 1950, com a linguagem LISP. À diferença do que muitos pensam, esse tipo de programação não é apenas a utilização de funções em seu código-fonte, mas um paradigma e um estilo de programação. Na programação funcional, toda ação realizada pelo programa deve ser implementada como uma função ou uma composição de funções, mas estas devem seguir algumas regras: 1. Devem ser puras, ou seja, em qualquer ponto do programa, sempre produzem o mesmo resultado quando passados os mesmos parâmetros; 2. Os dados devem ser imutáveis, e uma vez definida uma variável, seu valor não pode ser alterado; 3. Os loops não devem ser utilizados, mas sim a composição de funções ou recursividade. A utilização dessas regras visa garantir, principalmente, que não haja um efeito indesejável e imprevisto quando executamos um programa ou parte dele. Muitas linguagens de programação (como Python, Java e C++) dão suporte para a programação funcional, porém são de propósito geral, dando base para outros paradigmas, como programação imperativa e orientada a objetos. Outras linguagens, como Haskell, Clojure e Elixir, são predominantemente de programação funcional. A seguir, ensinaremos como utilizar o Python para programação funcional. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 128 Funções puras São aquelas que dependem apenas dos parâmetros de entrada para gerar uma saída. Elas sempre retornam um valor, um objeto ou outra função. Em qualquer ponto do programa, ao chamar uma função pura, com os mesmos parâmetros, devemos obter sempre o mesmo resultado. Veja os dois scripts, funcao1.py e funcao2.py, no Codigo 1 seguir: Codigo 1 - Scripts funcao1.py 1 valor = 20 2 3 def multiplica(fator): 4 global valor 5 valor = valor * fator 6 print("Resultado", valor) 7 8 multiplica(3) 9 multiplica(3) Codigo 1 - Scripts funcao2.py 1 valor = 20 2 3 def multiplica(valor, fator): 4 valor = valor * fator 5 return valor 6 7 print("Resultado", multiplica(valor, 3)) 8 print("Resultado", multiplica(valor, 3)) SCRIPT 1 funcao1.py, definimos uma função chamada multiplica, que multiplica a variável global valor por umfator passado como parâmetro. O valor do resultado é atribuído à variável valor novamente (linha 5), que é impresso em seguida (linha 6). Ao chamarmos a função multiplica(3) pela primeira vez (linha 8), obtemos a saída “Resultado 60”. Como modificamos a própria variável global valor no corpo da função, ao chamarmos novamente a função multiplica(3) (linha 9), obtemos um resultado diferente para a saída: “Resultado 180”. Além de não depender apenas dos parâmetros, essa função não retorna valor algum. A função multiplica deste script não é pura. SCRIPT 2 funcao2.py, utilizamos a variável valor como mais um parâmetro para a função multiplica. As duas vezes que executamos essa função (linha 7 e 8), elas retornam o mesmo valor, 60. A função multiplica deste script é um exemplo de função pura, pois depende apenas de seus parâmetros para gerar o resultado, e não acessa ou modifica nenhuma variável externa à função e retorna um valor. Dados imutáveis São aqueles que não podem ser alterados após sua criação. Apesar do Python disponibilizar algumas estruturas de dados imutáveis, como as tuplas, a maioria é mutável. Na programação funcional, devemos tratar todos os dados como imutáveis! As funções puras devem utilizar apenas os parâmetros de entrada para gerar as saídas. Além dessa característica, as funções puras não podem alterar nenhuma variável fora de seu escopo. Observe os scripts do Codigo 2 a seguir, em que passamos a lista valores como argumento para a função altera_lista. Lembre-se que, no Python, ao passar uma lista como argumento, apenas passamos sua referência, ou seja, qualquer mudança feita no parâmetro dentro da função, também altera a lista original. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 129 Codigo 2 - Scripts funcao3.py 1 valores = [10, 20, 30] 2 3 def altera_lista(lista): 4 lista[2] = lista[2] + 10 5 return lista 6 7 print("Nova lista", altera_lista(valores)) 8 print("Nova lista", altera_lista(valores)) Codigo 2 - Scripts funcao4.py 1 valores = [10, 20, 30] 2 3 def altera_lista(lista): 4 nova_lista = list(lista) 5 nova_lista[2] = nova_lista[2] + 10 6 return nova_lista 7 8 print("Nova lista", altera_lista(valores)) 9 print("Nova lista", altera_lista(valores)) Na programação funcional, devemos evitar alterar qualquer dado que já tenha sido criado. No exemplo anterior, no script funcao3.py, ao alterar o terceiro elemento do parâmetro lista (linha 4), alteramos também a variável global valores. Com isso, ao chamar a mesma função duas vezes (linha 7 e 8), com, teoricamente, o mesmo parâmetro, obtemos um efeito indesejável, resultando em saídas diferentes, como no Codigo 3. Codigo 3 - Saída do script funcao3.py 1 C:\Users\ftoli\PycharmProjects\estac 2 Nova lista [10, 20, 40] 3 Nova lista [10, 20, 50] 4 5 Process finished with exit code 0 No exemplo do script funcao4.py, muito similar ao anterior, ao invés de alterarmos o valor do próprio parâmetro, criamos uma cópia dele (linha 4), sendo assim, não alteramos a variável valores e obtemos o mesmo resultado para as duas chamadas da função (linha 8 e 9). A saída desse script está no Codigo 4. Codigo 4 - Saída do script funcao4.py 1 C:\Users\ftoli\PycharmProjects\estac 2 Nova lista [10, 20, 40] 3 Nova lista [10, 20, 40] 4 5 Process finished with exit code 0 Efeito colateral e estado da aplicação As funções puras e dados imutáveis buscam evitar os efeitos indesejáveis, como ocorreu no script funcao3.py. Na terminologia de programação funcional, chamamos isso de efeito colateral (side effect). Além de evitar o efeito colateral, a programação funcional evita a dependência do estado de um programa. A dependência apenas dos parâmetros para gerar saídas garante que o resultado será sempre o mesmo, independentemente do estado da aplicação, por exemplo, valores de outras variáveis. Ou seja: não teremos diferentes comportamentos para uma função baseado no estado atual da aplicação. Dica Ao garantir que uma função utilizará apenas os dados de entrada para gerar um resultado e que nenhuma variável fora do escopo da função será alterada, temos a certeza de que não teremos um problema escondido, ou efeito colateral em nenhuma outra parte do código. O objetivo principal da programação funcional não é utilizar funções puras e dados imutáveis, mas sim evitar o efeito colateral. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 130 Funções de ordem superior Na programação funcional, é muito comum utilizar funções que aceitem outras funções, como parâmetros ou que retornem outra função. Essas funções são chamadas de funções de ordem superior (higher order function). No exemplo do Codigo 5 a seguir, vamos criar uma função de ordem superior chamada multiplicar_por. Ela será utilizada para criar e retornar novas funções. Essa função, ao ser chamada com um determinado multiplicador como argumento, retorna uma nova função multiplicadora por aquele multiplicador e que tem como parâmetro o número a ser multiplicado (multiplicando). Codigo 5 - Scripts funcao5.py 1 def multiplicar_por(multiplicador): 2 def multi(multiplicando): 3 return multiplicando * multiplicador 4 return multi 5 6 multiplicar_por_10 = multiplicar_por(10) 7 print(multiplicar_por_10(1)) 8 print(multiplicar_por_10(2)) 9 10 multiplicar_por_5 = multiplicar_por(5) 11 print(multiplicar_por_5(1)) 12 print(multiplicar_por_5(2)) Dentro da função “pai” multiplicar_por, definimos a função multi (linha 2), que espera um parâmetro chamado multiplicando, que será multiplicado pelo multiplicador passado como parâmetro para a função “pai”. Ao chamar a função multiplicar_por com o argumento 10 (linha 6), definimos a função interna multi como: def multi(multiplicando): return multiplicando * 10 Essa função é retornada e armazenada na variável multiplicar_por_10 (linha 6), que nada mais é que uma referência para a função multi recém-criada. Dessa forma, podemos chamar a função multiplicar_por_10, passando um número como argumento, o multiplicando, para ser multiplicado por 10 (linha 7 e 8). Produzindo os resultados 10 e 20. Da mesma forma, criamos a função multiplicar_por_5, passando o número 5 como argumento para a função multiplicar_por (linha 10), que recebe uma referência para a função: def multi(multiplicando): ‘return multiplicando * 5 Com isso, podemos utilizar a função multiplicar_por_5 para multiplicar um número por 5 (linhas 11 e 12). Observe a saída do programa no console abaixo. Codigo 5 - Saída do script funcao5 1 C:\Users\fotoli\PycharmProjects\estac 2 10 3 20 4 5 5 10 6 7 Process finished with exit code 0 Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 131 Funções lambda Assim como em outras linguagens, o Python permite a criação de funções anônimas. Estão são definidas sem identificador (ou nome) e, normalmente, são utilizadas como argumentos para outras funções (de ordem superior). Em Python, as funções anônimas são chamadas de funções lambda. Para criá-las, utilizamos a seguinte sintaxe: lambda argumentos: expressão Iniciamos com a palavra reservada lambda, seguida de uma sequência de argumentos separados por vírgula, dois pontos e uma expressão de apenas uma linha. As funções lambda SEMPRE retornam o valor da expressão automaticamente. Não é necessário utilizar a palavra return. Exemplo Considere a função para multiplicar dois números a seguir: def multiplicar(a, b): return a*b A função lambda equivalente é: lambda a, b: a*b Temos os parâmetros a e b e a expressão a*b, que é retornado automaticamente. As funções lambda podem ser armazenadas em variáveis para depois serem chamadas como uma função qualquer. Retornando ao exemplo da função multiplicar_por, podemos trocar a função multi poruma função lambda. Observe no Codigo 6, a seguir, em que abaixo (anonima.py), temos a utilização da função lambda e após a utilização da função original (funcao5.py): Codigo 6 - Script anonima.py 01 def multiplicar_por(multiplicador): 02 return lambda multiplicando: multiplicando * multiplicador 03 04 multiplicar_por_10 = multiplicar_por(10) 05 print(multiplicar_por_10(1)) 06 print(multiplicar_por_10(2)) 07 08 multiplicar_por_5 = multiplicar_por(5) 09 print(multiplicar_por_5(1)) 10 print(multiplicar_por_5(2)) Codigo 6 - Script funcao5.py 01 def multiplicar_por(multiplicador): 02 def multi(multiplicando): 03 return multiplicando * multiplicador 04 return multi 05 06 multiplicar_por_10 = multiplicar_por(10) 07 print(multiplicar_por_10(1)) 08 print(multiplicar_por_10(2)) 09 10 multiplicar_por_5 = multiplicar_por(5) 11 print(multiplicar_por_5(1)) 12 print(multiplicar_por_5(2)) Ao executar o script anonima.py, obtemos o mesmo resultado mostrado anteriormente: 10, 20, 5 e 10. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 132 Não utilizar loops Outra regra, ou boa prática, da programação funcional é não utilizar laços (for e while), mas sim composição de funções ou recu1rsividade. A função lambda exerce um papel fundamental nisso, como veremos a seguir. Para facilitar a composição de funções e evitar loops, o Python disponibiliza diversas funções e operadores. As funções internas mais comuns são map e filter. Map A função map é utilizada para aplicar uma determinada função em cada elemento de um iterável (lista, tupla, dicionários etc.), retornando um novo iterável com os valores modificados. A função map é pura e de ordem superior, pois depende apenas de seus parâmetros e recebe uma função como parâmetro. A sua sintaxe é a seguinte: map(função, iterável1, iterável2...) O primeiro parâmetro da map é o nome da função (sem parênteses) que será executada para cada item do iterável. Os demais parâmetros são os iteráveis separados por vírgula. A função map SEMPRE retorna um novo iterável. No exemplo do Codigo 7, a seguir, vamos criar três scripts. Todos executam a mesma operação. Recebem uma lista e triplicam cada item, gerando uma nova lista com os valores triplicados. Codigo 7 - Script funcao_iterable.py 01 lista = [1, 2, 3, 4, 5] 02 03 def triplica_itens(iterable): 04 lista_aux = [] 05 for item in iterable: 06 lista_aux.append(item*3) 07 return lista_aux 08 09 nova_lista = triplica_itens(lista) 10 print(nova_lista) Codigo 7 - Script funcao_map.py 1 lista = [1, 2, 3, 4, 5] 2 3 def triplica(item): 4 return item * 3 5 6 nova_lista = map(triplica, lista) 7 print(list(nova_lista)) Codigo 7 - Script funcao_map_lambda.py 1 lista = [1, 2, 3, 4, 5] 2 3 nova_lista = map(lambda item: item * 3, lista) 4 print(list(nova_lista)) Primeiro Script funcao_iterable.py, definimos uma função chamada triplica_item, que recebe um iterável como parâmetro (linha 3), cria uma lista auxiliar para garantir imutabilidade (linha 4), percorre os itens do iterável passado como parâmetro (linha 5), adiciona os itens triplicados à lista auxiliar (linha 6) e retorna a lista auxiliar (linha 7). Essa função é chamada com o argumento lista (linha 9) e o resultado é impresso (linha 10). Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 133 Segundo Script funcao_map.py, definimos a função triplica, que triplica e retorna o item passado como parâmetro (linha 3 e 4). É utilizada, assim como a variável lista, como argumentos para a função map (linha 6). A map vai aplicar internamente a função passada como parâmetro em cada item da lista, retornando um novo iterável (que pode ser convertido em listas, tuplas etc.). O resultado da função map é armazenado na variável nova_lista, para então ser impresso (linha 7). A função map garante a imutabilidade dos iteráveis passados como argumento. Como a função map retorna um iterável, utilizamos o construtor list(iterável) para imprimir o resultado (linha 7). Terceiro Script funcao_map_lambda.py, substituímos a função triplica pela função lambda (lambda item: item*3), que foi utilizada como argumento para a função map (linha 3). Esta vai aplicar a função lambda em cada item da lista, retornando um novo iterável que foi impresso posteriormente (linha 4). Observe como o código foi reduzido e mesmo assim preservamos a utilização de funções puras (lambda), alta ordem (map) e que preservaram a imutabilidade dos dados. Tudo isso para garantir que não haja efeitos colaterais e dependência de estados. Todos os scripts utilizam funções puras e geraram o mesmo resultado: [3, 6, 9, 12, 15]. Filter É utilizada para filtrar elementos de um iterável (lista, tupla, dicionários etc.). O filtro é realizado utilizando uma função, que deve ser capaz de retornar true ou false para cada elemento do iterável. Atenção Todo elemento que for avaliado como true será incluído em um novo iterável retornado pela função filter, que é pura e de alta ordem, pois depende apenas dos parâmetros e recebe uma função como parâmetro. A sua sintaxe é a seguinte: filter(função, iterável) O primeiro parâmetro da função filter é o nome da função (sem parênteses), que será executada para cada item do iterável. O segundo parâmetro é o iterável. A função filter SEMPRE retorna um novo iterável, mesmo que vazio. No exemplo do Codigo 8 a seguir, vamos criar três scripts. Todos fazem a mesma filtragem. Recebem uma lista e retornam os elementos ímpares, gerando uma nova lista, de forma a garantir a imutabilidade. Codigo 8 - Script funcao_filtro_iterable.py 01 lista = [1, 2, 3, 4, 5] 02 03 def impares(iterable): 04 lista_aux = [] 05 for item in iterable: 06 if item % 2 != 0: 07 lista_aux.append(item) 08 return lista_aux 09 10 nova_lista = impares(lista) 11 print(nova_lista) Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 134 Codigo 8 - Script funcao_filter.py 1 lista = [1, 2, 3, 4, 5] 2 3 def impar(item): 4 return item % 2 != 0 5 6 nova_lista = filter(impar, lista) 7 print(list(nova_lista)) Codigo 8 - Script funcao_filter_lambda.py 1 lista = [1, 2, 3, 4, 5] 2 3 nova_lista = filter(lambda item: item % 2 != 0, lista) 4 print(list(nova_lista)) Script FUNÇÃO_FILTRO_ITERABLE.PY Definimos uma função chamada ímpares, que recebe um iterável como parâmetro (linha 3), cria uma lista auxiliar para garantir imutabilidade (linha 4), percorre os itens do iterável passados como parâmetros (linha 5), adiciona os itens ímpares à lista auxiliar (linha 6 e 7) e retorna a lista auxiliar (linha 8). Essa função é chamada com o argumento lista (linha 10) e o resultado é impresso (linha 11). Script FUNCAO_FILTER.PY Definimos a função ímpar, que retorna true se o item passado como parâmetro é ímpar ou false caso contrário (linha 3 e 4). Utilizamos essa função, assim como a variável lista, como argumentos para a função filter (linha 6). A filter vai aplicar, internamente, a função passada como parâmetro em cada item da lista, retornando um novo iterável (que pode ser convertido em listas, tuplas etc.). O resultado da função filter é armazenado na variável nova_lista, para então ser impresso (linha 7). A função filter garante a imutabilidade dos iteráveis passados como argumento. Como a função filter retorna um iterável, utilizamos o construtor list(iterável) para imprimir o resultado (linha 7). Script, FUNÇÃO_FILTER_LAMBDA.PY Substituímos a função ímpar pela função lambda (lambda item: item%2 != 0) que foi utilizada como argumento para a função filter (linha 3). Esta vai aplicar a função lambda em cada item da lista, retornando um novo iterável que foi impresso posteriormente (linha 4). Todos os scripts geraram o mesmo resultado:[1, 3, 5]. Verificando o aprendizado 1. Observe as afirmativas relacionadas à programação funcional e responda. I - As funções puras sempre produzem o mesmo resultado quando passados os mesmos parâmetros. II - Dados imutáveis são aqueles nos quais seus valores podem ser alterados após a sua definição. III - Não se deve utilizar loops, mas composição de funções. IV - A programação funcional é um paradigma e um estilo de programação. Das afirmativas anteriores, quais são verdadeiras? A. II e III. B. I e III. C. II. D. I, III e IV. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 135 Qual é o resultado impresso pelo programa a seguir? Script lambda1.py minha_funcao = lambda x: x ** 2 resultado = minha_funcao(4) print(resultado) A. 4. B. 8. C. 16. D. 32. Computação concorrente e sua utilização em Python Introdução No início do ano 2000, poucas pessoas tinham acesso a computadores com mais de um processador. Além disso, os processadores dos desktops e notebooks daquela época continham apenas um núcleo, ou seja, só podiam executar uma operação por vez. Atualmente, é raro ter um computador, ou até mesmo um celular, com um processador de apenas um núcleo. Com isso, é muito importante aprendermos alguns conceitos sobre programação concorrente e paralela e como implementá-los, de forma a extrair o máximo de performance dos nossos programas. O dicionário on-line, DICIO, define a palavra “concorrente” como: “Simultâneo; o que acontece ao mesmo tempo que outra coisa [...]”. Na computação, temos dois conceitos relacionados à execução simultânea, concorrência e paralelismo, que serão apresentados no decorrer deste módulo. A seguir, definiremos alguns conceitos relacionados à computação concorrente e paralela, e, posteriormente, mostraremos como o Python implementa tais funcionalidades. Conceitos Programas e processos Os conceitos iniciais que precisamos definir são programas e processos. Mas o que seria um programa e um processo na visão do sistema operacional? Programa é algo estático e permanente. Contém um conjunto de instruções e é percebido pelo sistema operacional como passivo. Em contrapartida, o processo é dinâmico e tem uma vida curta. Ao longo da sua execução, tem seu estado alterado. Ele é composto por código, dados e contexto. Em suma, o processo é um programa em execução. O mesmo programa executado mais de uma vez gera processos diferentes, que contêm dados e momento de execução (contexto) também diferentes. Concorrência e paralelismo Em computação, o termo concorrente se refere a uma técnica para tornar os programas mais usáveis. Ela pode ser alcançada mesmo em processadores de apenas um núcleo, pois permite compartilhar o processador de forma a rodar vários processos. Os sistemas operacionais multitarefas suportam concorrência. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 136 Exemplo Por exemplo, é possível compactar um arquivo e continuar usando o processador de texto, mesmo em um processador de um núcleo, pois o processador divide o tempo de uso entre os processos dessas duas aplicações. Ou seja: essas duas aplicações executam de forma concorrente. Apesar da concorrência não necessariamente precisar de mais de um núcleo, ela pode se beneficiar se houver mais deles, pois os processos não precisarão compartilhar tempo de processador. Já o paralelismo é uma técnica para permitir que programas rodem mais rápido, executando várias operações ao mesmo tempo. Ela requer várias máquinas ou um processador com mais de um núcleo. Também é possível realizar o paralelismo nas placas de vídeo. Threads e processos Relembrando O processo é uma instância de um programa em execução. Ele é composto pelo código que está sendo executado, os dados utilizados pelo programa (exemplo: valores de variável) e o estado ou contexto do processo. Em um programa com múltiplos processos, cada um tem seu próprio espaço de memória e cada um pode executar em um núcleo diferente, melhorando consideravelmente a performance do programa. Você sabia? A thread pertence a um determinado processo. Múltiplas threads podem ser executadas dentro de um mesmo processo. As de um mesmo processo compartilham a área de memória e podem acessar os mesmos dados de forma transparente. Em Python, podemos criar tanto processos quanto threads em um programa. Antes de falarmos sobre cada abordagem em Python, vamos falar sobre o global interpreter lock (GIL). No CPython — que é a implementação principal da linguagem de programação Python, escrita em linguagem C — o GIL existe para proteger o acesso aos objetos da linguagem. Isso acontece objetivando a prevenção para que múltiplas threads não possam executar os bytecodes do Python de uma vez. Essa “trava” é necessária, pois visa garantir a integridade do interpretador, uma vez que o gerenciamento de memória no CPython não é thread-safe. Na prática, para um mesmo processo, o GIL só permite executar uma thread de cada vez, ou seja, elas não executam de forma paralela, mas concorrentes. Sem a “trava” do GIL, uma operação simples de atribuição de variável poderia gerar um dado inconsistente caso duas threads atribuíssem um valor a uma mesma variável ao mesmo tempo. Para os processos, por sua vez, o funcionamento é diferente. Para cada um, temos um GIL separado. Ou seja: podem ser executados paralelamente. Cabe ao programador garantir o acesso correto aos dados. Múltiplos processos podem rodar em paralelo, enquanto múltiplas threads (de um mesmo processo) podem rodar concorrentemente. Atenção Normalmente, utilizamos thread para interface gráfica, acesso ao banco de dados, acesso à rede etc., pois o programa não pode parar, ou a interface congelar, enquanto esperamos baixar um arquivo, por exemplo. Porém, quando temos um programa que necessita muito dos recursos do processador e temos como paralelizar nosso programa, devemos optar pela criação de múltiplos processos. Criar novos processos pode ser lento e consumir muita memória, enquanto a criação de threads é mais rápida e consome menos memória. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 137 Criação de threads e processos Exemplo Como criar threads e processos em Python: Vamos utilizar a classe thread e process, dos módulos threading e multiprocessing, respectivamente. Para facilitar a transição entre processos e threads simples, o Python fez os construtores e métodos das duas classes muito parecidos. No script principal.py do Codigo 9, vamos criar uma thread e um processo que executam a mesma função. Na linha 8, criamos uma thread para executar a função funcao_a utilizando a classe thread. O construtor tem como parâmetros a função que deverá ser executada (target) e quais parâmetros serão passados para essa função (args). O parâmetro args espera um iterável (lista, tupla etc.), onde cada elemento será mapeado para um parâmetro da função target. Como a funcao_a espera apenas um parâmetro, definimos uma tupla de apenas um elemento (‘Minha Thread”). O primeiro elemento da tupla, a string Minha Thread, será passada para o parâmetro nome da funcao_a. Na linha 9, enviamos o comando para a thread iniciar sua execução, chamando o método start() do objeto t do tipo thread. Codigo 9 - Script principal.py 01 from threading import Thread 02 from multiprocessing import Process 03 04 def funcao_a(nome): 05 print(nome) 06 07 if __name__ == '__main__': 08 t = Thread(target=funcao_a, args=("Minha Thread",)) 09 t.start() 10 11 p = Process(target=funcao_a, args=("Meu Processo",)) 12 p.start() A criação do processo é praticamente igual, porém utilizando a classe process, conforme a linha 11. Para iniciar o processo, chamamos o método start() na linha 12. Verifique a saída desse script no console abaixo.Codigo 9 - Saída do script principal(1) 1 C:\Users\ftoli\PycharmProjects\estac 2 Minha Thread 3 Meu Processo 4 5 Process finished with exit code 0 Múltiplas threads e processos No exemplo do Codigo 10 a seguir, vamos criar múltiplas threads e processos usando na criação de para criar threads e processos para compararmos as saídas de cada um. Vamos aproveitar para mostrar que todas as threads estão no mesmo contexto, com acesso às mesmas variáveis, enquanto o processo não. Vamos mostrar, também, como fazer o programa aguardar que todas as threads e processos terminem antes de seguir a execução. Atenção Observe que os códigos dos scripts são praticamente idênticos, com exceção das classes construtoras thread e process na linha 16 dos dois scripts. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 138 Neste exemplo, a função que será paralelizada é a funcao_a (linha 7). Ela contém um laço que é executado cem mil vezes e para cada iteração adiciona o elemento 1 à lista minha_lista, definida globalmente na linha 5. Vamos criar 10 threads (e processos) para executar 10 instâncias dessa função, na qual, esperamos que o número de elementos na lista ao final da execução do programa seja de 1 milhão (10 threads X cem mil iterações). Para criar essas 10 threads ou processos, temos um laço de 10 iterações (linha 15), em que criamos (linha 16) e iniciamos (linha 18) cada thread ou processo. O objetivo da criação de threads ou processos é justamente a computação concorrente ou paralela, o Python não fica “parado” aguardando dela após chamar o método start(), ele imediatamente começa a próxima iteração do laço for. Codigo 10 - Script threads_var.py 01 from threading import Thread, Lock 02 from multiprocessing import Process 03 import time 04 05 minha_lista = [] 06 07 def funcao_a(indice): 08 for i in range(100000): 09 minha_lista.append(1) 10 print("Termino thread ", indice) 11 12 if __name__ == '__main__': 13 tarefas = [] 14 for indice in range(10): 15 tarefa = Thread(target=funcao_a, args=(indice,)) 16 tarefas.append(tarefa) 17 tarefa.start() 18 19 print("Antes de aguardar o termino!", len(minha_lista)) 20 21 for tarefa in tarefas: 22 tarefa.join() 23 24 print("Após aguardar o termino!", len(minha_lista)) Codigo 10 - Script processos_var.py 01 from threading import Thread, Lock 02 from multiprocessing import Process 03 import time 04 05 minha_lista = [] 06 07 def funcao_a(indice): 08 for i in range(100000): 09 minha_lista.append(1) 10 print("Termino processo ", indice) 11 12 if __name__ == '__main__': 13 tarefas = [] 14 for indice in range(10): 15 tarefa = Process(target=funcao_a, args=(indice,)) 16 tarefas.append(tarefa) 17 tarefa.start() 18 19 print("Antes de aguardar o termino!", len(minha_lista)) 20 21 for tarefa in tarefas: 22 tarefa.join() 23 24 print("Após aguardar o termino!", len(minha_lista)) Atenção É função do programador armazenar uma referência para as suas threads ou processos, de maneira que possamos verificar seu estado ou interrompê-las. Para isso, armazenamos cada thread ou processo criado em uma lista chamada tarefas (linha 17). Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 139 Logo após a criação das threads ou processos, vamos imprimir o número de itens na variável minha_lista (linha 20); aguardar o término da execução das threads ou processos pela iteração da lista tarefas e utilizando o método join() (linha 22 e 23); e, por fim, imprimimos o número de itens final da variável minha_ lista (linha 25). No Codigo 11, a seguir, temos o resultado de cada script. Codigo 11 - Saída do script threads_var.py 01 C:\Users\ftoli\PycharmProjects\estacio_ead\venv\Scripts\python.e 02 Termino thread Termino thread 0 1 03 04 Termino thread Termino thread 23Termino thread 4 05 06 Termino thread 5 07 Termino thread 6 08 Termino thread 7 09 Termino thread Antes de guardar o termino! Termino thread 9 10 970012 11 8 12 Apos guardar o termino! 1000000 13 14 Process finished with exit code 0 Codigo 11 - Saída do script processos_var.py 01 C:\Users\ftoli\PycharmProjects\estacio_ead\venv\ 02 Antes guardar o termino! 0 03 Termino thread 0 04 Termino thread 2 05 Termino thread 1 06 Termino thread 4 07 Termino thread 3 08 Termino thread 5 09 Termino thread 7 10 Termino thread 6 11 Termino thread 8 12 Termino thread 9 13 Apos guardar o termino! 0 14 15 Process finished with exit code 0 Apesar da saída do script thread.py ter ficado confusa (já iremos retornar nesse problema), observe que o número de itens da variável minha_lista muda durante a execução do programa quando usamos thread e não muda quando usamos processos. Isso acontece, pois a área de memória das threads é compartilhada, ou seja, elas têm acesso às mesmas variáveis globais. Em contrapartida, as áreas de memória dos processos não são compartilhadas. Cada processo acaba criando uma versão própria da variável minha_lista. Travas (Lock) No exemplo anterior, a função funcao_a incluía o elemento 1 à lista a cada iteração, utilizando o método append(). No exemplo a seguir, a funcao_a irá incrementar a variável global contador. Observe o código do Codigo 12 e verifique o resultado impresso no console seguinte. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 140 Codigo 12 - Script threads_inc.py 01 from threading import Thread, Lock 02 from multiprocessing import Process 03 import time 04 05 contador = 0 06 07 def funcao_a(indice): 08 global contador 09 for i in range(1000000): 10 contador += 1 11 print("Termino thread ", indice) 12 13 if __name__ == '__main__': 14 tarefas = [] 15 for indice in range(10): 16 tarefa = Thread(target=funcao_a, args=(indice,)) 17 tarefas.append(tarefa) 18 tarefa.start() 19 20 print("Antes de aguardar o termino!", contador) 21 22 for tarefa in tarefas: 23 tarefa.join() 24 25 print("Após aguardar o termino!", contador) Codigo 12 - Saída do script threads_inc 01 C:\Users\ftoli\PycharmProjects\estacio_ead\venv\S 02 Termino thread 1 03 Termino thread Termino thread 3 2 04 05 Termino thread 0 Termino thread 06 Antes de aguardar o termino! Termino thread 4 07 5Termino thread 6 08 2235554 09 10 Termino thread Termino thread 9 11 Termino thread 7 12 8 13 Apos aguardar o termino! 3316688 14 15 Process finished with exit code 0 O valor impresso ao final do programa deveria ser 10.000.000 (10 threads X 1.000.000 de iterações), porém foi impresso 3.316.688. Você deve estar se perguntado por que aconteceu isso, se o GIL garante que apenas uma thread esteja em execução por vez. E por que não aconteceu isso no exemplo anterior? Vamos começar pelo exemplo anterior. O método utilizado para inserir um elemento na lista (append), na visão do GIL, é atômico, ou seja, ele é executado de forma segura e não pode ser interrompido no meio de sua execução. O incremento de variável (+=1), que está sendo usando no exemplo atual (linha 10), não é atômico. Ele é, na verdade, composto por duas operações, a leitura e a atribuição, e não temos como garantir que as duas operações serão realizadas atomicamente. Veja a figura 1, em que temos um contador inicialmente com o valor 0 e três threads incrementando esse contador. Para incrementar, a thread realiza duas operações: lê o valor do contador e depois atribui o valor lido incrementado de um ao contador. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 141 Fonte: Estácio de Sá Cada thread é executada em um determinado tempo t. Em t1, a thread1 lê e incrementa o contador, que passa a valer 1. Em t2, a thread2 lêo contador (valor 1). Em t3, a thread3 lê e incrementa o contador, que passa a valer 2. Em t4, a thread2 incrementa o contador, porém a partir do valor que ela leu, que era 1. No final, o contador passa a valer 2, erroneamente. Esse resultado inesperado devido à sincronia na concorrência de threads (ou processos) se chama condição de corrida. Para evitar a condição de corrida, utilizamos a primitiva lock (trava). Um objeto desse tipo tem somente dois estados: travado e destravado. Quando criado, ele fica no estado destravado. Esse objeto tem dois métodos: acquire e release. Quando no estado destravado, o acquire muda o estado dele para travado e retorna imediatamente. Quando no estado travado, o acquire bloqueia a execução até que outra thread faça uma chamada ao método release e destrave o objeto. Veja como adaptamos o código anterior para utilizar o lock no Codigo 13. Codigo 13 - Script threads_inc_lock.py 01 from threading import Thread, Lock 02 from multiprocessing import Process 03 import time 04 05 contador = 0 06 lock = Lock() 07 print_lock = Lock() 08 09 def funcao_a(indice): 10 global contador 11 for i in range(1000000): 12 lock.acquire() 13 contador += 1 14 lock.release() 15 print_lock.acquire() 16 print("Termino thread ", indice) 17 print_lock.release() 18 19 if __name__ == '__main__': 20 tarefas = [] 21 for indice in range(10): 22 tarefa = Thread(target=funcao_a, args=(indice,)) 23 tarefas.append(tarefa) 24 tarefa.start() 25 26 print("Antes de aguardar o termino!", contador) 27 28 for tarefa in tarefas: 29 tarefa.join() 30 31 print("Após aguardar o termino!", contador) Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 142 Fonte: Estácio de Sá Codigo 13 - Saída do script threads_inc_lock 01 C:\Users\ftoli\PycharmProjects\estacio 02 Antes de aguardar o termino! 73844 03 Termino thread 1 04 Termino thread 7 05 Termino thread 8 06 Termino thread 9 07 Termino thread 2 08 Termino thread 3 09 Termino thread 0 10 Termino thread 4 11 Termino thread 6 12 Termino thread 5 13 Após aguardar o termino! 10000000 14 15 Process finished with exit code 0 Aproveitamos este exemplo para corrigir o problema de impressão no console de forma desordenada. Esse problema ocorria, pois o print também não é uma operação atômica. Para resolver, envolvemos o print da linha 16 com outra trava, print_lock, criada na linha 7. Compartilhando variáveis entre processos Para criar uma área de memória compartilhada e permitir que diferentes processos acessem a mesma variável, podemos utilizar a classe value do módulo multiprocessing. No exemplo do Codigo 14 a seguir, vamos criar uma variável do tipo inteiro e compartilhá-la entre os processos. Essa variável fará o papel de um contador e a função paralelizada vai incrementá-la. Codigo 14 - Script processos.py 01 from threading import Thread 02 from multiprocessing import Process, Value 03 04 def funcao_a(indice, cont): 05 for i in range(100000): 06 with cont.get_lock(): 07 cont.value += 1 08 print("Termino processo ", indice) 09 10 if __name__ == '__main__': 11 contador = Value('i', 0) 12 tarefas = [] 13 for indice in range(10): 14 tarefa = Process(target=funcao_a, args=(indice, contador)) 15 tarefas.append(tarefa) 16 tarefa.start() 17 18 print("Antes de aguardar o termino!", contador.value) 19 20 for tarefa in tarefas: 21 tarefa.join() 22 23 print("Após aguardar o termino!", contador.value) Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 143 Codigo 14 - Saída do script processos 01 C:\Users\ftoli\PycharmProjects\estacio_e 02 Antes de aguardar o termino! 0 03 Termino processo 2 04 Termino processo 1 05 Termino processo 0 06 Termino processo 3 07 Termino processo 4 08 Termino processo 5 09 Termino processo Termino processo 7 10 6 11 Termino processo 8 12 Termino processo 9 13 Após aguardar o termino! 1000000 14 15 Process finished with exit code 0 Para permitir que a variável seja compartilhada, declaramos uma variável chamada contador utilizando o construtor da classe value, onde passamos como primeiro argumento um caractere com o tipo da variável compartilhada (‘i’ 🡪 inteiro) e seu valor inicial (0) (linha 11). Como não temos acesso a variáveis globais entre os processos, precisamos passar esta para o construtor process por meio do parâmetro args. Como a passagem de parâmetros é posicional, o parâmetro indice da funcao_a recebe o valor da variável índice e o parâmetro cont recebe uma referência para a variável contador (linha 14). Isso já é o suficiente para termos acesso a variável contador por meio do parâmetro cont da função funcao_a. Porém, não resolve a condição de corrida. Para evitar esse problema, vamos utilizar uma trava (lock) para realizar a operação de incremento, assim como fizemos no exemplo anterior. Você sabia? O Python já disponibiliza essa trava nos objetos do tipo value, não sendo necessário criar uma variável específica para a trava (lock). Observe como foi realizada a trava utilizando o método get_lock() (linha 6). Observe pelo console que agora nossa variável está com o valor certo: um milhão! Para corrigir o problema da impressão desordenada, basta criar uma variável do tipo lock e passá-la como parâmetro, assim como fizemos no exemplo anterior e com a variável contador. Verificando o aprendizado 1. Observe as afirmativas e responda: I – É possível alcançar a concorrência em processadores de apenas um núcleo. II – O paralelismo é uma técnica para permitir executar várias operações ao mesmo tempo. III – Programas e processos têm a mesma definição. IV – As threads pertencem a um determinado processo. Das afirmativas anteriores, quais estão corretas? A. II e III. B. I e IV. C. I, II e IV. D. I, II. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 144 2. Qual o valor impresso pelo script a seguir:? Codigo 14 - Script processos.py minha_lista = [] def adiciona(): for i in range(100): minha_lista.append(1) if __name__ == '__main__': tarefas = [] for indice in range(10): t = Thread(target=adiciona) tarefas.append(t) t.start() for indice in range(10): p = Thread(target=adiciona) tarefas.append(t) p.start() for tarefa in tarefas: tarefa.join() print(len(minha_lista)) A. 100. B. 1000. C. 2000. D. 10000. Apenas a thread consegue acessar a variável global minha_lista. São executadas 10 threads X 100 iterações = 1000. Python como ferramenta para desenvolvimento web Introdução Atualmente, existem diversas opções de linguagem de programação para desenvolver aplicações web, sendo as principais: PHP, ASP.NET, Ruby e Java. De acordo com o site de pesquisas W3Techs, o PHP é a linguagem mais utilizada nos servidores, com um total de 79% de todos os servidores utilizando essa linguagem. Grande parte da utilização do PHP se deve aos gerenciadores de conteúdo, como WordPress, Joomla e Drupal, escritos em PHP e que tem muita representatividade na web hoje. Você sabia? O Python também pode ser utilizado para desenvolver aplicações web? Atualmente existem diversos frameworks que facilitam a criação de aplicações web em Python. Os frameworks podem ser divididos em, basicamente, dois tipos: full-stack e não full-stack. Frameworks Full-Stack Os frameworks full-stack disponibilizam diversos recursos internamente, sem a necessidade de bibliotecas externas. Dentre os recursos, podemos citar: • Respostas à requisição; • Mecanismos de armazenamento (acesso a banco de dados); • Suporte a modelos (templates); • Manipulação de formulários; • Autenticação; • Testes; • Servidor para desenvolvimento, entre outros. O principal framework Pythonfull-stack é o Django. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 145 Frameworks não Full-Stack Estes oferecem os recursos básicos para uma aplicação web, como resposta a requisição e suporte a modelos. Os principais frameworks dessa categoria são o Flask e o CherryPy. Normalmente, para aplicações maiores, são utilizados os frameworks full-stack, que também são um pouco mais complexos de se utilizar. Para este módulo, usaremos o Flask. Conceitos O Flask é um micro framework web em Python. Pela descrição de seus desenvolvedores, micro não quer dizer que toda a aplicação precisa ficar em apenas um arquivo ou que falta alguma funcionalidade, mas que o núcleo de funcionalidades dele é limpo e simples, porém extensível. O desenvolvedor fica livre para escolher qual suporte a modelos (templates) utilizar, qual biblioteca de acesso ao banco de dados e como lidar com formulários. Existem inúmeras extensões compatíveis com o Flask. Iniciar o desenvolvimento com Flask é muito simples e mostraremos isso ao longo deste módulo. Apesar de não ser full-stack, o Flask disponibiliza um servidor web interno para desenvolvimento. A configuração padrão inicia um servidor local na porta 5000, que pode ser acessado por: http://127.0.0.1:5000. Iremos começar com uma aplicação web básica que retorna “Olá, mundo.” quando acessamos pelo navegador o nosso servidor em http://127.0.0.1:5000. O nome do nosso script é flask1.py e está apresentado no Codigo 15 a seguir. Codigo 15 - Script flask1.py 01 from flask import Flask 02 03 app = Flask(__name__) 04 05 @app.route('/') 06 def ola_mundo(): 07 return "Olá, mundo." 08 09 if __name__ == '__main__': 10 app.run() Codigo 15 - Saída do script flask1 1 C:\Users\ftoli\PycharmProjects\estacio_ead\venv\Scripts\phyton.exe C:/Users/ftoli/Py 2 * Serving Flask app "principal" (lazy loading) 3 * Enviroment: production 4 WARNING: This is a development server. Do not use it in a production deployment. 5 Use a production WSGI server instead. 6 * Debug mode: off 7 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) Na primeira linha, importamos a classe flask, que é a classe principal do framework. É a partir de uma instância dessa classe que criaremos nossa aplicação web. Na linha 2, é criada uma instância da classe flask, onde passamos __name__ como argumento para que o Flask consiga localizar, na aplicação, arquivos estáticos, como css e javascript, e arquivos de modelos (templates), se for o caso. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 146 Você sabia? O Flask utiliza o conceito de rotas para direcionar o acesso às páginas (ou endpoints). As rotas servem para guiar as requisições feitas ao servidor para o trecho de código que deve ser executado. Os nomes das rotas são os nomes utilizados pelo usuário para navegar na sua aplicação. Por isso, utilize nomes com significado e de fácil memorização. No caso do Flask, as rotas são direcionadas para funções, que devem retornar algum conteúdo. Na linha 5, utilizamos o decorador @app.route(‘/’) para criar uma rota para a URL raiz da nossa aplicação (‘/’). Esse decorador espera como parâmetro a rota, ou URL, que será utilizada no navegador, por exemplo. Toda requisição feita para uma rota é encaminhada para a função imediatamente abaixo do decorador. No nosso caso, para a função ola_mundo (linha 6). O retorno dessa função é a resposta que o nosso servidor enviará ao usuário. Pela linha 7, a função ola_mundo retorna a string “Olá, mundo.”. Ao iniciar a execução do script, recebemos no console algumas informações, incluindo em qual endereço o servidor está “escutando”, observe à direita do Codigo 16. Ao digitar no navegador a URL http://127.0.0.1:5000, será exibida a frase “Olá, mundo.”, como na imagem da figura 2. Figura 2 - Resultado do acesso a http://127.0.0.1:5000. Aprimorando rotas Para ilustrar melhor o uso de rotas, vamos alterar o exemplo anterior de forma que a rota (URL) para a função ola_mundo seja http://127.0.0.1:5000/ola. Observe o Codigo 16 e compare com a anterior. Veja que o parâmetro do decorador @app.route da linha 5 agora é ‘/ola’. Codigo 16 - Script flask2.py 01 from flask import Flask 02 03 app = Flask(__name__) 04 05 @app.route('/ola') 06 def ola_mundo(): 07 return "Olá, mundo." 08 09 if __name__ == '__main__': 10 app.run() Se acessarmos no navegador http://127.0.0.1:5000/ola, recebemos como resposta “Olá, mundo.” (figura 3 A), porém, ao acessar http://127.0.0.1:5000, recebemos um erro (404) de página não encontrada (Not Found), pois a rota para a URL raiz da aplicação não existe mais (figura 3 B). http://127.0.0.1:5000/ Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 147 Figura 3 A - Resultado do acesso a http://127.0.0.1:5000/ola. Figura 3 B - Resultado do acesso a http://127.0.0.1:5000. Vamos acertar nossa aplicação para criar, também, uma rota para a URL raiz da aplicação (@app.route(‘/’)). Vamos chamar a função dessa rota de index e criar essa rota conforme linhas 5 a 7. Veja o resultado no Codigo 17 a seguir. Codigo 17 - Script flask3.py 01 from flask import Flask 02 03 app = Flask(__name__) 04 05 @app.route('/') 06 def index(): 07 return "Página principal." 08 09 @app.route('/ola') 10 def ola_mundo(): 11 return "Olá, mundo." 12 13 if __name__ == '__main__': 14 app.run() Veja, na imagem a seguir, que agora podemos acessar tanto a rota para função índice (URL: /) (figura 4 A), onde é retornado “Página principal”, quanto a rota da função ola_mundo (URL: /ola) ( figura 4 B), que é retornado “Olá, mundo.”. Figura 4 A - Resultado do acesso a http://127.0.0.1:5000. Figura 4 B - Resultado do acesso a http://127.0.0.1:5000/ola. Recebendo parâmetros O decorador de rota (route) também permite que sejam passados parâmetros para as funções. Para isso, devemos colocar o nome do parâmetro da função entre < e> na URL da rota. No exemplo do Codigo 18 a seguir, vamos mudar a rota da função ola_mundo de forma que seja possível capturar e retornar o nome passado como parâmetro. http://127.0.0.1:5000/ http://127.0.0.1:5000/ola Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 148 Codigo 18 - Script flask4.py 01 from flask import Flask 02 03 app = Flask(__name__) 04 05 @app.route('/') 06 def index(): 07 return "Página principal." 08 09 @app.route('/ola/<nome>') 10 def ola_mundo(nome): 11 return "Olá, " + nome 12 13 if __name__ == '__main__': 14 app.run() Observe a URL da rota na linha 9, em que utilizamos a variável nome entre < e>, que é o mesmo nome do parâmetro da função ola_mundo. Isso indica que qualquer valor que for adicionado à URL após ‘/ola/’ será passado como argumento para a variável nome da função ola_mundo. Veja na figura 5 a seguir, em que acessamos a URL http://127.0.0.1:5000/ola/Amigo. Figura 5 - Resultado do acesso a http://127.0.0.1:5000/ola/Amigo. Porém, se tentarmos acessar a URL http://127.0.0.1:5000/ola, receberemos um erro, pois removemos a rota para essa URL. Para corrigir esse problema, vamos adicionar uma segunda rota para a mesma função, bastando adicionar outro decorador @app.route para a mesma função ola_mundo, conforme Codigo 19 a seguir. Codigo 19 - Script flask5.py 01 from flask import Flask 02 03 app = Flask(__name__) 04 05 @app.route('/') 06 def index(): 07 return "Página principal." 08 09 @app.route('/ola/') 10 @app.route('/ola/<nome>') 11 def ola_mundo(nome="mundo"): 12 return "Olá, " + nome 13 14 if __name__ == '__main__': 15 app.run() Observe que agora temos duas rotas para a mesma função (linha 9 e 10). Em qualquer uma das URL, o usuário será direcionado para a função ola_mundo.Atenção OBS: A rota com URL ‘/ola/’ aceita requisições tanto para ‘/ola’ quanto ‘/ola/’. Como nessa nova configuração, o parâmetro nome pode ser vazio quando acessado pela URL ‘/ola’, definimos o valor padrão “mundo” para ele (linha 11). Ao acessar essas duas URLs ‘/ola/’ e ‘/ola/Amigo’, obtemos os resultados exibidos nos resultados exibidos na figura 6 A e 6 B, respectivamente. http://127.0.0.1:5000/ola/Amigo http://127.0.0.1:5000/ola/Amigo Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 149 Figura 6 A - Resultado do acesso a http://127.0.0.1:5000/ola/. Figura 6 B - Resultado do acesso a http://127.0.0.1:5000/ola/Amigo. Métodos HTTP As aplicações web disponibilizam diversos métodos para acessar uma URL: GET, POST, PUT e DELETE. Por padrão, as rotas do Flask somente respondem às requisições GET. Para responder a outros métodos, é necessário explicitar, na rota, quais métodos serão aceitos. Para isso, precisamos utilizar o parâmetro methods do decorador @app.route(), que espera uma lista de strings com o nome dos métodos aceitos. Observe no Codigo 20, a seguir, em que criamos a rota para a função logar e passamos como argumento uma lista contendo duas strings, ‘GET’ e ‘POST’ (linha 15). Isso indica que essa rota deve responder às requisições do tipo GET e POST. Codigo 20 - Script flask6.py 01 from flask import Flask 02 03 app = Flask(__name__) 04 app.debug = True 05 06 @app.route('/') 07 def index(): 08 return "Página principal." 09 10 @app.route('/ola/') 11 @app.route('/ola/<nome>') 12 def ola_mundo(nome): 13 return "Olá, " + nome 14 15 @app.route('/logar', methods=['GET', 'POST']) 16 def logar(): 17 if request.method == 'POST': 18 return "Recebeu post! Fazer login!" 19 else: 20 return "Recebeu get! Exibir FORM de login." 21 22 if __name__ == '__main__': 23 app.run() Para verificar o método que foi utilizado na requisição, usamos o atributo method do objeto request, que retorna uma das strings: GET, POST, PUT ou DELETE. O objeto request é uma variável global disponibilizada pelo Flask e pode ser utilizada em todas as funções. Para cada requisição, um novo objeto request é criado e disponibilizado. Com o objeto request, temos acesso a muitas outras propriedades da requisição, como: cookie, parâmetros, dados de formulário, mimetype etc. Neste exemplo, utilizamos o atributo method do objeto request para verificar o método passado na requisição e retornar conteúdos diferentes dependendo do método (linhas 17 a 20). http://127.0.0.1:5000/ola/Amigo Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 150 Atenção Caso seja requisitada uma rota que exista, porém o método não seja aceito pela rota, o erro retornado é o 405 - Method Not Allowed (método não permitido), e não o 404 – Not Found (não encontrado), como ocorre quando a rota não existe. Observe que ativamos o modo debug do servidor interno do Flask (linha 5). Isso nos permite visualizar melhor os avisos e erros durante o desenvolvimento. Utilizando modelos As funções no Flask precisam retornar algo. O retorno das funções pode ser uma string simples e até uma string contendo uma página inteira em HTML. Porém, criar páginas dessa maneira é muito complicado, principalmente devido à necessidade de escapar (scape) o HTML, para evitar problemas de segurança, por exemplo. Para resolver esse problema, o Flask, por meio da extensão Jinja2, permite utilizar modelos (templates) que são arquivos texto com alguns recursos a mais, inclusive escape automático. No caso de aplicações web, os modelos normalmente são páginas HTML, que são pré-processadas antes de retornarem ao requisitante, abrindo um novo leque de possibilidades no desenvolvimento web. A cada requisição, podemos alterar uma mesma página de acordo com um contexto (valores de variáveis, por exemplo). Com a utilização de marcadores (tags) especiais, é possível injetar valores de variáveis no corpo do HTML, criar laços (for), condicionantes (if/else), filtros etc. 1. Para criar e utilizar os modelos, por convenção, os HTMLs precisam ficar dentro da pasta templates, no mesmo nível do arquivo que contém a aplicação Flask. 2. Para ilustrar, no próximo exemplo (Codigo 21), vamos alterar nossa aplicação de forma que seja retornada uma página HTML ao se acessar a raiz da aplicação (‘/’). 3. Para isso, vamos criar um arquivo chamado indice.html na pasta templates, no mesmo nível do script flask7.py, conforme vemos na árvore de diretório abaixo. O conteúdo do modelo indice.html está exibido abaixo do Codigo 21 4. Para o modelo ser acessado na URL raiz (‘/’), a função index() deve ser a responsável por retorná-lo. Para isso, vamos utilizar a função render_template disponibilizada pelo Flask, que recebe o nome do arquivo que desejamos retornar. Observe a linha 7 do script flask7.py, em que utilizamos essa função e passamos “indice.html” como parâmetro. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 151 Codigo 21 - Script flask7.py 01 from flask import Flask, render_template 02 03 app = Flask(__name__) 04 05 @app.route('/') 06 def index(): 07 return render_template('indice.html') 08 09 @app.route('/ola/') 10 @app.route('/ola/<nome>') 11 def ola_mundo(nome="mundo"): 12 return "Olá, " + nome 13 14 if __name__ == '__main__': 15 app.run() Codigo 21 - Modelo indice.html 1 <!DOCTYPE html> 2 <html> 3 <body> 4 <h1>Página principal!</h1> 5 <h2>Usando templates</h2> 6 </body> 7</html> Ao acessar http://127.0.0.1:5000, recebemos o resultado exibido na figura 7. Figura 7 - Resultado do acesso a http://127.0.0.1:5000. Como dito anteriormente, os modelos são páginas HTML turbinadas, nas quais podemos utilizar delimitadores especiais para alterar nossa página. Os dois tipos de delimitadores são: Expressões: {{ ... }} Que serve para escrever algo no modelo, como o valor de uma variável. Declarações: {% ... %} Utilizado em laços e condicionantes, por exemplo. Antes de serem retornadas ao usuário, essas páginas são renderizadas, ou seja, os delimitadores são computados e substituídos. No próximo exemplo, Código 22, vamos alterar a nossa aplicação de forma que o nome passado como parâmetro para a rota ‘/ola/<nome>’ seja exibido dentro do HTML. Para isso, vamos criar um arquivo chamado ola.html na pasta templates, conforme abaixo no Codigo 22. Observe que utilizamos o delimitador de expressões com a variável ({{nome_recebido}}), indicando que vamos escrever o conteúdo dessa variável nesse local após a renderização (linha 4 do arquivo ola.html). http://127.0.0.1:5000/ Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 152 Codigo 22 - Script flask8.py 01 from flask import Flask, render_template 02 03 app = Flask(__name__) 04 05 @app.route('/') 06 def index(): 07 return render_template('indice.html') 08 09 @app.route('/ola/') 10 @app.route('/ola/<nome>') 11 def ola_mundo(nome="mundo"): 12 return "Olá, ", nome_recebido = nome 13 14 if __name__ == '__main__': 15 app.run() Codigo 22 - Modelo ola.html 1<!DOCTYPE html> 2<html> 3 <body> 4 <h1>Olá, {{ nome_recebido }}</h1> 5 </body> 6</html> Além de informar a página que queremos renderizar na função render_templates, podemos passar uma ou mais variáveis para essa função. Essas variáveis ficarão disponíveis para serem utilizadas em expressões ({{ }}) e declarações ({% %}) dentro do modelo (template). No exemplo, desejamos exibir o nome passado via URL na página. Para passar a variável nome para o HTML, precisamos chamar a função render_template com um parâmetro a mais, conforme linha 12 e destacado a seguir (figura 8). Figura 8 - Destaque da sintaxe da função render_template do Flask. Fonte: Estácio de SáA variável nome_recebido estará disponível para ser usada dentro do modelo ola.html. Esse nome pode ser qualquer outro escolhido pelo programador, mas lembre-se de que ele será usado, também, dentro do modelo. Para finalizar, utilizamos o delimitador de expressões envolvendo o nome da variável (linha 4 do html). Ao ser renderizada, essa expressão será substituída pelo valor da variável, conforme figura 9 a seguir, onde passamos a string “Amigo” para a variável nome e, consequentemente, nome_recebido. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 153 Figura 9 - Resultado do acesso a http://127.0.0.1:5000/ola/Amigo. Verificando o aprendizado 1. Considere o código a seguir, em que temos um servidor Flask escutando na porta 5000, e responda: exercicio1.py from flask import Flask app = Flask(__name__) @app.route('/ola') def ola_mundo(): return "Olá, mundo" @app.route('/ola') def ola_mundo(nome="mundo"): return "Olá, " + nome if __name__ == '__main__': app.run() O que será apresentado no navegador se acessarmos a URL http://127.0.0.1:5000/ola/EAD? A. Olá, mundo. B. Olá, mundo. Olá, EAD. C. Olá, EAD. D. O programa vai apresentar um erro. A URL acessada está de acordo com a rota da linha 9. 2. Considere o código a seguir, no qual temos um servidor Flask escutando na porta 5000, e responda: exercicio2.py from flask import Flask app = Flask(__name__) @app.route('/ola', methods=['POST']) def ola_post(): return "Olá, GET" @app.route('/ola') def ola_get(nome="mundo"): return "Olá, POST" if __name__ == '__main__': app.run() O que será apresentado no navegador se acessarmos a URL http://127.0.0.1:5000/ola? A. Olá, GET. B. Olá, GET. Olá, POST. C. Olá, POST. D. O programa vai apresentar um erro. O navegador utiliza, por default, o método GET. Com isso, será executada a rota para a função ola_get, da linha 10. http://127.0.0.1:5000/ola/Amigo Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 154 Python como ferramenta para ciência de dados Introdução Desde o século XVII, as ciências experimentais e teóricas são reconhecidas pelos cientistas como os paradigmas básicos de pesquisa para entender a natureza. De umas décadas para cá, a simulação computacional de fenômenos complexos evoluiu, criando o terceiro paradigma, a ciência computacional. A ciência computacional fornece ferramentas necessárias para tornar possível a exploração de domínios inacessíveis à teoria ou experimento. Com o aumento das simulações e experimentos, mais dados são gerados e um quarto paradigma emerge, que são as tecnologias e técnicas associadas à ciência de dados. A ciência de dados é uma área de conhecimento que envolve a utilização de dados para gerar impactos em uma instituição, seja uma universidade, uma empresa, um órgão federal etc., de forma a resolver um problema real utilizando os dados. Em 1996, Fayyad (1996) apresentou a definição clássica do processo de descoberta de conhecimento em bases de dados, conhecido por KDD (Knowledge Discovery in Databases): “KDD é um processo, de várias etapas, não trivial, interativo e iterativo, para identificação de padrões compreensíveis, válidos, novos e potencialmente úteis a partir de grandes conjuntos de dados”. As técnicas de KDD (FAYYAD, 1996), também conhecidas como mineração de dados, normalmente se referem à extração de informações implícitas, porém úteis, de uma base de dados. Essas aplicações, tipicamente, envolvem o uso de mineração de dados para descobrir um novo modelo, e então os analistas utilizam esse modelo em suas aplicações. O processo de KDD é basicamente composto por três grandes etapas: pré-processamento, mineração de dados e pós-processamento. A figura 10, a seguir, mostra todo o processo de KDD. Figura 10 - Visão geral dos passos que compõe o processo de KDD. Fonte: Estácio de Sá A primeira etapa do processo de KDD, conhecida como pré-processamento, é responsável por selecionar, preparar e transformar os dados que serão utilizados pelos algoritmos de mineração. Algumas atividades envolvidas no reprocessamento são: Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 155 Coleta e integração: Quando é necessário que dados provenientes de diversas fontes sejam consolidados em uma única base de dados. Esta atividade é bastante encontrada na construção de data warehouses; Codificação: Significa transformar a natureza dos valores de um atributo. Isto pode acontecer de duas diferentes formas: uma transformação de dados numéricos em categóricos — codificação numérico- categórica, ou o inverso — codificação categórico-numérica; Construção de atributos: Após a coleta e integração dos dados, pode ser necessário criar colunas em uma tabela, por exemplo, refletindo alguma transformação dos dados existentes em outras colunas; Limpeza dos dados: Pode ser subdividida em complementação de dados ausentes, detecção de ruídos, e eliminação de dados inconsistentes; A partição dos dados: Consiste em separar os dados em dois conjuntos disjuntos. Um para treinamento (abstração do modelo de conhecimento) e outro para testes (avaliação do modelo gerado). A segunda etapa do KDD, conhecida como mineração de dados, é a aplicação de um algoritmo específico para extrair padrões de dados. Hand (2001) define a etapa de mineração de dados da seguinte forma: “Mineração de Dados é a análise de (quase sempre grandes) conjuntos de dados observados para descobrir relações escondidas e para consolidar os dados de uma forma tal que eles sejam inteligíveis e úteis aos seus donos.” Esta etapa, normalmente, é a que atrai maior atenção, por ser ela que revela os padrões ocultos nos dados. Os algoritmos de mineração podem ser classificados como supervisionados e não supervisionados. Nos primeiros, os algoritmos “aprendem” baseados nos valores que cada dado já possui. Os algoritmos são treinados (ajustados), aplicando uma função e comparando o resultado com os valores existentes. Já nos não supervisionados, os dados não foram classificados previamente e os algoritmos tentam extrair algum padrão por si só. A seguir, serão apresentados alguns algoritmos que podem ser realizados durante a etapa de mineração de dados. 1. Não supervisionados: A. Regras de associação • Uma das técnicas de mineração de dados mais utilizada para comércio eletrônico, cujo objetivo é encontrar regras para produtos comprados em uma mesma transação. Ou seja, a presença de um produto em um conjunto implica a presença de outros produtos de outro conjunto; com isso, sites de compras nos enviam sugestões de compras adicionais, baseado no que estamos comprando. B. Agrupamento • Reúne, em um mesmo grupo, objetos de uma coleção que mantenham algum grau de afinidade. É utilizada uma função para maximizar a similaridade de objetos do mesmo grupo e minimizar entre elementos de outros grupos. 2. Supervisionados: A. Classificação • Tem como objetivo descobrir uma função capaz de mapear (classificar) um item em uma das várias classes predefinidas. Se conseguirmos obter a função que realiza esse Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 156 mapeamento, qualquer nova ocorrência pode ser também mapeada, sem a necessidade de conhecimento prévio da sua classe; B. Regressão linear • É uma técnica para se estimar uma variável a partir de uma função. A regressão, normalmente, tem o objetivo de encontrar um valor que não foi computado inicialmente. A última etapa do KDD, o pós-processamento, tem como objetivo transformar os padrões dos dados obtidos na etapa anterior, de forma a torná-los inteligíveis, tanto ao analista de dados quanto ao especialista do domínio da aplicação (SOARES, 2007). Conceitos Vamos apresentar algumas situaçõesde forma a explicar alguns algoritmos de mineração e como eles podem ser implementados em Python. Utilizaremos a biblioteca Pandas para realizar a leitura de dados, a biblioteca Scikit-Learn para realizar o treinamento e utilização dos algoritmos de mineração de dados e a biblioteca matplotlib para gerar a visualização de resultados. Supervisionado – regressão linear Neste exemplo, vamos utilizar uma série histórica fictícia de casos de dengue de uma determinada cidade e, com o auxílio do algoritmo supervisionado de regressão linear, predizer casos futuros. A série está em uma planilha (arquivo CSV) com duas colunas, ano e casos (número de casos). Na planilha, temos o número de casos de 2001 a 2017. Vamos utilizar essa série histórica e aplicar o algoritmo de regressão linear para estimar os casos de dengue para o ano de 2018. No Codigo 23, a seguir, temos o código do script regressao.py, o arquivo CSV (dados_dengue.csv) e a saída do console. Codigo 23 - Script regressao.py 01 import matplotlib.pyplot as plt 02 from sklearn.linear_model import LinearRegression 03 import pandas 04 05 ############# Pré-processamento ############### 06 # Coleta e Integração 07 arquivo = pandas.read_csv('dados_dengue.csv') 08 09 anos = arquivo[['ano']] 10 casos = arquivo[['casos']] 11 12 ############## Mineração ################# 13 regr = LinearRegression() 14 regr.fit(X=anos, y=casos) 15 16 ano_futuro = [[2018]] 17 casos_2018 = regr.predict(ano_futuro) 18 19 print('Casos previstos para 2018 ->', int(casos_2018)) 20 21 ############ Pós-processamento ################ 22 plt.scatter(anos, casos, color='black') 23 plt.scatter(ano_futuro, casos_2018, color='red') 24 plt.plot(anos, regr.predict(anos), color='blue') 25 26 plt.xlabel('Anos') 27 plt.ylabel('Casos de dengue') 28 plt.xticks([2018]) 29 plt.yticks([int(casos_2018)]) 30 31 plt.show() Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 157 Codigo 23 - Dados_dengue.csv 01 ano,casos 02 2017,450 03 2016,538 04 2015,269 05 2014,56 06 2013,165 07 2012,27 08 2011,156 09 2010,102 10 2009,86 11 2008,42 12 2007,79 13 2006,65 14 2005,58 15 2004,39 16 2003,23 17 2002,15 18 2001,28 Codigo 23 - Saída do script regressao.py 1 C:\Users\fotoli\PycharmProjects\estac 2 Casos previstos para 2018 -> 330 Após importar os módulos necessários, vamos passar para a primeira etapa do KDD, o pré- processamento. Neste caso simples, vamos realizar apenas a coleta e integração que, na prática, é carregar a planilha dentro do programa. Para isso, utilizamos a função read_csv da biblioteca Pandas, passando como parâmetro o nome do arquivo (linha 7). A classe da biblioteca Scikit-Learn utilizada para realizar a regressão linear se chama LinearRegression. Para realizar a regressão, ela precisa dos dados de treinamento (parâmetro X) e seus respectivos resultados (parâmetro y). No nosso exemplo, como desejamos estimar o número de casos, vamos utilizar os anos como dado de treinamento e o número de casos como resultado. Ou seja, teremos os anos no parâmetro X e os casos no parâmetro y. Como estamos usando apenas uma variável para o parâmetro X, o ano, temos uma regressão linear simples e o resultado esperado é uma reta, onde temos os anos no eixo x e os casos no eixo y. Após carregar o arquivo, vamos separar os dados das colunas nas respectivas variáveis. Observe a sintaxe para obter os anos (linha 9) e casos (linha 10). O Pandas detecta, automaticamente, o nome das colunas e permite extrair os elementos das colunas utilizando o nome. Atenção O próximo passo é criar o objeto do tipo LinearRegression e atribuí-lo a uma variável (linha 13). Esse objeto será utilizado para treinar (ajustar) a equação da reta que será gerada pela regressão. Para realizar o treinamento (fit), precisamos passar os parâmetros X e y (linha 14). Após a execução do método fit, o objeto regr está pronto para ser utilizado para predizer os casos para os anos futuros, utilizando o método predict (linha 17). Ao chamar o método predict passando o ano 2018 como argumento, recebemos como retorno o número casos previsto para 2018, conforme impresso no console da figura 33 (330). A partir da linha 21, temos a etapa de pós-processamento, na qual utilizamos a biblioteca matplotlib para exibir um gráfico com os dados da série (pontos em preto), a reta obtida pela regressão, em azul, e o valor predito para o ano de 2018, em vermelho (figura 11). Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 158 Figura 11 - Gráfico da série dos casos de dengue. Fonte: Estácio de Sá Supervisionado ‒ classificação Os algoritmos de classificação são do tipo supervisionado, nos quais passamos um conjunto de características sobre um determinado item de uma classe de forma que o algoritmo consiga compreender, utilizando apenas as características, qual a classe de um item não mapeado. Para este exemplo, vamos utilizar um conjunto de dados (dataset) criado em 1938 e utilizado até hoje: o dataset da flor de íris (Iris Dataset). Ele contém informações de cinquenta amostras de três diferentes classes de Flor de Íris (Iris setosa, Iris virginica e Iris versicolor). No total, são quatro características para cada amostra, sendo elas o comprimento e a largura, em centímetros, das sépalas e pétalas de rosas. Por ser um dataset muito utilizado e pequeno, o Scikit-Learn já o disponibiliza internamente. Vamos treinar dois algoritmos de classificação, árvore de decisão e máquina de vetor suporte (suport vector machine – SVM) para montar dois classificadores de flores de íris. A forma como são implementados esses algoritmos estão fora do escopo deste módulo. Confira o script a seguir, Codigo 24, em que utilizamos esses dois algoritmos. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 159 Codigo 24 - Script classificacao.py 01 from sklearn.datasets import load_iris, fetch_kddcup99 02 from sklearn.metrics import accuracy_score 03 from sklearn.model_selection import train_test_split 04 from sklearn.tree import DecisionTreeClassifier, export_text, plot_tree 05 from sklearn.svm import SVC 06 07 ################## Pré-processamento ################### 08 # Coleta e Integração 09 iris = load_iris() 10 11 caracteristicas = iris.data 12 rotulos = iris.target 13 14 print("Caracteristicas:\n", caracteristicas[:2]) 15 print("Rótulos:\n", rotulos[:2]) 16 print('########################################################') 17 18 # Partição dos dados 19 carac_treino, carac_teste, rot_treino, rot_teste = train_test_split(caracteristicas, rotulos) 20 21 ################### Mineração ##################### 22 23 ############---------- Arvore de Decisão -----------############ 24 arvore = DecisionTreeClassifier(max_depth=2) 25 arvore.fit(X=carac_treino, y=rot_treino) 26 27 rot_preditos = arvore.predict(carac_teste) 28 acuracia_arvore = accuracy_score(rot_teste, rot_preditos) 29 ############-------- Máquina de Vetor Suporte ------############ 30 clf = SVC() 31 clf.fit(X=carac_treino, y=rot_treino) 32 33 rot_preditos_svm = clf.predict(carac_teste) 34 acuracia_svm = accuracy_score(rot_teste, rot_preditos_svm) 35 36 ################ Pós-processamento #################### 37 print("Acurácia Árvore de Decisão:", round(acuracia_arvore, 5)) 38 print("Acurácia SVM:", round(acuracia_svm, 5)) 39 print('########################################################') 40 41 r = export_text(arvore, feature_names=iris['feature_names']) 42 print("Estrutura da árvore") 43 print(r) Codigo 24 - Saída do script classificacao.py 01 C:\Users\fotoli\PycharmProjects\estacio_ead\ 02 Caracteristicas: 03 [[5.1 3.5 1.4 0.2] 04 [4.9 3. 1.4 0.2]] 05 Rótulos: 06 [0 0]07 ########################################### 08 Acurácia Árvore de Decisão: 0.92105 09 Acurácia SVM: 0.97368 10 ########################################### 11 Estrutura da árvore 12 |--- petal width (cm) <= 0.80 13 | |--- class: 0 14 |--- petal width (cm) > 0.80 15 | |--- petal width (cm) <= 1.75 16 | | |--- class: 1 17 |--- petal width (cm) > 1.75 18 | | |--- class: 2 19 Process finished with exit code 0 Etapa 01 Na etapa de pré-processamento, vamos começar pela coleta e integração, que é a obtenção do dataset de flores utilizando a função load_iris() (linha 9). Esta função retorna um objeto onde podemos acessar as características das flores pelo atributo data e os rótulos, ou classes das flores, pelo atributo target. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 160 Etapa 02 Na linha 11, separamos as características das flores na variável características. Ela contém uma lista com 150 itens, onde cada item contém outra lista com quatro elementos. Observe o conteúdo dos dois primeiros itens desta lista no console. Cada um dos quatro elementos corresponde ao comprimento da sépala, largura da sépala, comprimento da pétala e largura da pétala, respectivamente. Etapa 03 Na linha 12, separamos os rótulos (ou classes) na variável rótulo. Ela contém uma lista com 150 itens que variam entre 0, 1 ou 2. Cada número corresponde a uma classe de flor (0: Iris-Setosa; 1:Iris-Versicolour; 2:Iris-Virginica). Como dito na introdução deste módulo, esse mapeamento entre categorias e números se chama codificação categórico-numérica. Essa etapa de pré- processamento já foi realizada e disponibilizada pela função load_iris(). Etapa 04 Outra etapa de pré-processamento que precisaremos realizar é a partição dos dados. Ela nos permitirá verificar a qualidade do algoritmo de classificação. Para isso, precisamos particionar nossos dados em treino e teste. Os dados de treino são, como o próprio nome diz, utilizados para treinar (ajustar) o algoritmo, enquanto os dados de testes são utilizados para verificar a acurácia dele, comparando o valor calculado para os testes com os valores reais. Dica Para separar as amostras em treino e teste, o Scikit-Learn disponibiliza uma função chamada train_test_split, que recebe como primeiro parâmetro uma lista com as características e segundo parâmetro uma lista com os rótulos. Essa função retorna quatro novas listas: • De treino; • De teste das características; • De treino; • De teste dos rótulos. Observe a linha 19, onde utilizamos essa função para gerar quatro novas variáveis: características para treino (carac_treino); características para teste (carac_teste); rótulos para treino (rot_treino); e rótulos para teste (rot_teste). Com a etapa de pré-processamento concluída, o próximo passo é treinar um algoritmo de classificação com os dados de treino. Para isso, criamos uma instância do classificador DecisionTree passando como parâmetro a profundidade máxima da árvore, ou seja, o número máximo de níveis, ou camadas, a partir do nó raiz, que a árvore encontrada poderá ter (linha 24). Veremos como a árvore ficou ao término do script. Esse objeto será utilizado para treinar (ajustar) a árvore de decisão. Para realizar o treinamento (fit), precisamos passar os parâmetros X e y, que conterão as características de treino e rótulos de treino respectivamente (linha 25). Após treinado o algoritmo, vamos utilizar o método predict do objeto árvore, passando como argumento as características para teste. Como resultado, receberemos uma lista com os rótulos preditos (linha 27). Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 161 Esse resultado será utilizado como parâmetro para a função accuracy_score, que calcula a acurácia do classificador, comparando os resultados preditos com os resultados reais (linha 28). Analogamente, faremos o treinamento de um algoritmo de classificação utilizando o SVM, por meio da classe SVC (support vector classification) em que utilizaremos os valores padrão do construtor para o classificador (linha 30 a 34). No pós-processamento, vamos imprimir a acurácia de cada classificador (linha 37 e 38) e uma representação textual da árvore, utilizando a função export_text (linha 41). Observe que a acurácia do classificador SVC foi ligeiramente melhor que da árvore de decisão, 0,97 contra 0,92 da árvore. Uma representação gráfica da árvore de decisão gerada pode ser vista na figura 12. Figura 12 - Representação da árvore de decisão com profundidade 2 Alterando a profundidade da árvore para 3 e executando novamente o programa, encontramos uma acurácia de 0,97 e a seguinte árvore é exibida na figura 13: Figura 13 - Representação da árvore de decisão com profundidade 3 Durante o treinamento de algoritmos, devemos experimentar diferentes parâmetros, a fim de encontrar o melhor resultado. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 162 Não supervisionado ‒ agrupamento O objetivo de um algoritmo de agrupamento é reunir objetos de uma coleção que mantenham algum grau de afinidade. É utilizada uma função para maximizar a similaridade de objetos do mesmo grupo e minimizar entre elementos de outros grupos. Exemplos de algoritmo de agrupamento são k-means (k-medias) e mean-shift. No próximo exemplo (Codigo 25), vamos utilizar o algoritmo k-medias para gerar grupos a partir do dataset de flor de íris. Porém, como o agrupamento é um algoritmo não supervisionado não utilizaremos os rótulos para treiná-lo. O algoritmo vai automaticamente separar as amostras em grupos, que serão visualizados em um gráfico. Na etapa de pré-processamento, vamos começar pela coleta e integração que é a obtenção do dataset de flores utilizando a função load_iris() (linha 8). Na linha 10, separamos as características das flores na variável características. Lembrando que as características das flores são: comprimento da sépala (índice 0), largura da sépala (índice 1), comprimento da pétala (índice 2) e largura da pétala (índice 3). Na etapa de mineração de dados, vamos treinar o algoritmo de agrupamento K-medias com as características das flores. Para isso, criamos uma instância da classe KMeans passando como parâmetro o número de grupos (ou classes) que desejamos que o algoritmo identifique (n_clusters) (linha 13). Passamos o número 3, pois sabemos que são 3 classes de flor, mas poderíamos alterar esse valor. O objeto grupos criado será utilizado para treinar (ajustar) o algoritmo. Para realizar o treinamento (fit), precisamos passar apenas parâmetros X, que conterá as características das flores (linha 14). Após o treino, podemos utilizar o atributo labels_ do objeto grupos para retornar uma lista com o índice do grupo ao qual cada amostra pertence. Como o número de grupos (n_clusters) é 3, o índice varia entre: 0, 1 e 2. Codigo 25 - Script agrupamento.py 01 import matplotlib.pyplot as plt 02 from mpl_toolkits.mplot3d import Axes3D 03 from sklearn.cluster import KMeans 04 from sklearn.datasets import load_iris 05 06 ################## Pré-processamento ################### 07 # Coleta e Integração 08 iris = load_iris() 09 10 caracteristicas = iris.data 11 12 ################### Mineração ##################### 13 grupos = KMeans(n_clusters=3) 14 grupos.fit(X=caracteristicas) 15 labels = grupos.labels_ # indice do grupo ao qual cada amostra pertence 16 17 ################ Pós-processamento #################### 18 fig = plt.figure(1) 19 ax = Axes3D(fig) 20 ax.set_xlabel('Comprimento Sépala') 21 ax.set_ylabel('Largura Sépala') 22 ax.set_zlabel('Comprimento Pétala') 23 ax.scatter(caracteristicas[:, 0], caracteristicas[:, 1], caracteristicas[:, 2], c=grupos.labels_, edgecolor='k') 24 25 target = iris.target 26 fig = plt.figure(2)27 ax = Axes3D(fig) 28 ax.set_xlabel('Comprimento Sépala') 29 ax.set_ylabel('Largura Sépala') 30 ax.set_zlabel('Comprimento Pétala') 31 ax.scatter(caracteristicas[:, 0], caracteristicas[:, 1], caracteristicas[:, 2], c=target, edgecolor='k') 32 33 plt.show() Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 163 A partir da linha 12, temos a etapa de pós-processamento, em que utilizamos a biblioteca matplotlib para exibir dois gráficos, onde objetos do mesmo grupo apresentam a mesma cor. O gráfico da figura 14 A contém os resultados do agrupamento e o gráfico figura 14 B contém os resultados reais da amostra. Figura 14 A - Resultado do agrupamento e Figura 14 B - Resultados reais da amostra. Fonte: Estácio de Sá Verificando o aprendizado 1. De acordo com o processo de descoberta de conhecimento em base de dados (KDD) e analisando as assertivas a seguir, quais atividades podem fazer parte da etapa de pré-processamento? I. Coleta e Integração. II. Codificação. III. Construção de atributos. IV. Visualização dos dados. Agora, assinale a alternativa correta: A. I e II B. I, II e III C. I, III e IV D. II, III e IV Todas fazem parte do pré-processamento, exceto a visualização, que faz parte do pós-processamento. 2. Em algumas situações, precisamos transformar um atributo ou característica de uma amostra de categoria para um número. Qual o nome dessa atividade? A. Coleta e integração. B. Codificação. C. Construção de atributos. D. Partição dos dados. A codificação categório-numérica transforma string em números. Considerações finais Como visto neste tema, a linguagem funcional pode resolver alguns problemas relacionados à execução do programa, chamados de efeitos colaterais. Apostila de Paradigmas de Linguagens de Programação em Python Marcio Quirino - 164 Mesmo que não utilizemos todas as diretrizes da programação funcional, como imutabilidade dos dados, é uma boa prática utilizarmos funções puras, de forma a evitar a dependência do estado da aplicação e alteração de variáveis fora do escopo da função. Em Python, podemos criar tanto programas concorrentes quanto paralelos. Apesar do GIL (Global Interpreter Lock) permitir executar apenas uma thread de cada vez por processo, podemos criar múltiplos processos em uma mesma aplicação. Com isso, conseguimos tanto gerar códigos concorrentes (múltiplas threads), quanto paralelos (múltiplos processos). Apesar do Python não ser a primeira escolha para desenvolvimento web, mostramos como é simples a utilização do framework Flask para essa finalidade. Assim como o Flask, outros frameworks mais completos, como Django, facilitam o desenvolvimento desse tipo de aplicação. Sites como Instagram e Pinterest utilizam a combinação Python+Django, mostrando que essa combinação é altamente escalável! A utilização do Python como ferramenta para ciência de dados evolui a cada dia. As principais bibliotecas para análise e extração de conhecimento de base de dados, mineração de dados e aprendizado de máquinas têm uma implementação em Python. Apesar de não ter sido mostrado, neste tema, o console do Python é muito utilizado para realizar experimentações nesta área. Referências DICIONÁRIO ONLINE DE PORTUGUÊS. Definições e significados de mais de 400 mil palavras. Todas as palavras de A a Z. 2009-2020. FAYYAD, U. M.; PIATETSKY-SHAPIRO, G.; SMYTH, P.; UTHURUSAMY, R. (Eds.). From data mining to knowledge discovery in databases. AI Magazine, v. 17, n. 3, p. 37-54, 1996. FLASK. Web development, one drop at a time. 2010. HAND, D.; MANNILA, H.; SMYTH, P. Principles of data mining. Massachusetts: MIT Press, 1991. HUNT, J. A beginners guide to Python 3 programming. Basileia: Springer, 2019. MATPLOBIT. Visualization with Python. John Hunter, Darren Dale, Eric Firing, Michael Droettboom and the Matplotlib development team. 2012-2020. PANDAS. Sponsored project of NumFOCUS. 2015. PYTHON. Python Software Foundation. Copyright 2001-2020. SCIKIT-LEARN. Machine Learning in Python. 2007. W3TECHS. Web Technology Surveys. 2009-2020. Explore+ Acesse a documentação oficial do Python sobre as funções e os operadores que se integram bem ao paradigma de programação funcional, para aprender mais sobre o assunto. Pesquise no site da Jinja2 e conheça mais detalhadamente as funcionalidades da linguagem de modelos (templates) para Python.