├── LICENSE ├── README.md ├── imagens ├── clone.png └── fork.png └── orientacao-a-objetos ├── 1-escopos-e-namespaces ├── README.md ├── __pycache__ │ ├── funcoes_do_log.cpython-37.pyc │ └── funcoes_do_log_colorido.cpython-37.pyc ├── conflito.py ├── escopos_de_variaveis.py ├── from_import_especifico.py ├── from_import_tudo.py ├── funcoes_do_log.py ├── funcoes_do_log_colorido.py ├── import.py ├── pacote.py └── requirements.txt ├── 2-classes-e-objetos ├── README.md ├── lista-de-exercicios-1.py └── televisao.py ├── 3-entendendo-orientacao-a-objetos ├── README.md ├── estacionamento.py └── modelagem.py ├── 4-construtores-e-destrutores ├── 01_construtores.py ├── 02_destrutores.py └── README.md ├── 5-atributos-de-visibilidade-e-encapsulamento ├── README.md ├── encapsulamento.py └── propriedades.py ├── 6-heranca ├── README.md ├── heranca.py └── heranca_multipla.py ├── 7-classes-abstratas ├── README.md ├── classes_abstratas.py └── lista-de-exercicios-2.py ├── 8-tipagem-pato ├── README.md └── tipagem_pato.py ├── 9-erros_e_excecoes ├── README.md ├── conta_bancaria.py ├── debug.py ├── erros_excecoes.py └── lista-de-exercicios-3.py └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 WoMakersCode 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WoMakersCode - Bootcamp Python Backend para Iniciantes 2 | 3 | ## Visão Geral 4 | Bem vinda ao repositório do curso Bootcamp Python Backend para Iniciantes. 5 | Aqui vamos manter os exemplos utilizados durante as aulas. 6 | Você poderá rodar os exemplos em sua própria máquina após ter seguido a configuração de ambiente proposta em aula. 7 | Além disso, ao realizar um `fork` do nosso repositório para a sua própria conta no GitHub, você poderá publicar suas soluções para os exercícios propostos. 8 | 9 | ## Como utilizar este repositório 10 | Você deverá realizar um _fork_ desse repositório para sua conta no GitHub e utilizar sua própria versão do repositório para trabalhar. 11 | ![Como fazer um fork do repositório](imagens/fork.png "Como fazer um fork do repositório") 12 | O próximo passo é clonar o seu repositório em seu computador. 13 | Após criar sua própria versão "forkada" do repositório original, vá até sua página de github e abra seu novo repositório. 14 | Clique no botão "Code" e em seguida, no botão para copiar o endereço do seu repositório. 15 | ![Clique no botão de copiar (2) para copiar a URL do repositório](imagens/clone.png "Clonando o seu repositório para seu computador") 16 | Para começar, abra uma ferramenta de terminal (PowerShell se você estiver utilizando Windows ou Terminal se estiver utilizando Linux ou Mac). Escolha uma pasta para onde seu repositório será clonado e utilize o comando `git` para clonar o repositório em seu computador. 17 | 18 | ``` bash 19 | cd ~\workspace 20 | git clone 21 | ``` 22 | 23 | Você poderá utilizar seu repositório de duas maneiras: 24 | 25 | 1. Rodar os códigos de exemplo realizados durante as aulas. 26 | 2. Resolver os desafios propostos e postar sua solução no seu GitHub pessoal. 27 | 28 | Ao clonar o repositório, realize o primeiro `commit` incluindo seu nome neste arquivo README. 29 | 30 | Bons estudos e divirta-se! 31 | -------------------------------------------------------------------------------- /imagens/clone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WoMakersCode/back-end-python/26c6d9b0b732d41736f0c37ee06a1b807df905a7/imagens/clone.png -------------------------------------------------------------------------------- /imagens/fork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WoMakersCode/back-end-python/26c6d9b0b732d41736f0c37ee06a1b807df905a7/imagens/fork.png -------------------------------------------------------------------------------- /orientacao-a-objetos/1-escopos-e-namespaces/README.md: -------------------------------------------------------------------------------- 1 | # Aula 01 - Escopos e _Namespaces_ 2 | 3 | ## Módulos e namespaces 4 | ### Slide 5 | **Módulos e Namespaces - Slide** 6 | * Suponha que você criou alguma funções que quer poder reutilizar em arquivos diferentes do seu sistema. **Módulos** são locais onde você define os nomes e funções que quer quer fiquem visíveis para o resto do seu sistema. 7 | * Falando tecnicamente, um módulo é um “espaço que serve para a declaração de nomes”, ou seja, um _namespace_. 8 | * Na prática, um módulo é um arquivo Python. O nome do módulo é o nome do arquivo tirando a extensão .py. 9 | * Em um módulo podem ser definidos componentes reutilizáveis em outros arquivos Python. Ex: variáveis, funções, classes, etc. 10 | * Módulos nos permitem quebrar o nosso código em blocos de código menores e reutilizáveis. 11 | * Para criar um módulo, você só precisa criar um arquivo python (.py) e escrever suas funções lá dentro. 12 | * Cuidado: se você importar um módulo ou função cujo nome tem conflito com algum objeto definido no seu namepsace local, o objeto do seu namespace local vai sobrescrever o que foi importado. 13 | * Isso acontecerá se o objeto do namespace local for declarado depois daquele que foi importado. 14 | * Como geralmente deixamos os imports no início do arquivo, esse é o caso mais comum. 15 | 16 | ### funcoes_do_log.py 17 | * Neste exemplo vamos criar o módulo "funcoes_do_log" (note que o nome do módulo é o nome do arquivo omitindo sua extensão). 18 | * Declaramos em nosso módulo um atributo e uma função. 19 | 20 | ### import.py 21 | ```python 22 | # importar um módulo na forma de um namespace separado 23 | import meu_modulo 24 | meu_modulo.mostrar_mensagem('Aviso: bateria fraca.') 25 | ``` 26 | * Assim como vimos no slide, a primeira forma de importar um módulo é utilizando apenas a palavra-chave `import`. 27 | * Ao importar o módulo desta forma, para usar o que foi declarado em nosso módulo, precisamos sempre usar a sintaxe `nome_do_modulo.atributo` ou `nome_do_modulo.funcao`. 28 | * Na prática, precisamos sempre dizer para o interpretador de qual _namespace_ vem os itens que estamos querendo usar no namespace corrente. 29 | 30 | 31 | ### from_import_tudo.py 32 | ```python 33 | # importar tudo que existe no módulo para o namespace atual 34 | from meu_modulo import * 35 | mostrar_mensagem('Aviso: bateria fraca.') 36 | ``` 37 | * Para importar todo o conteúdo do módulo "funcoes_do_log" para o _namespace_ local, podemos utilizar a palavra-chave `from` junto com o `import *`. 38 | * O asterisco indica que queremos importar todo o conteúdo deste módulo. 39 | * Não é necessário especificar o _namespace_ de onde vêm as coisas que você quer usar porque os nomes são importados para o _namespace_ corrente. 40 | * Esse tipo de abordagem costuma não ser uma boa prática porque "polui" muito o seu ambiente trazendo vários nomes que nunca serão usados em seu programa. 41 | * Para solucionar este problema, podemos especificar importar apenas os itens que queremos usar de um determinado módulo para o _namespace_ corrente. 42 | 43 | ### from_import_especifico.py 44 | ```python 45 | # importar itens específicos do módulo para o namespace atual 46 | from meu_modulo import mostrar_mensagem 47 | mostrar_mensagem('Aviso: bateria fraca.') 48 | ``` 49 | * Para importar partes específicas do módulo "funcoes_do_log" para o _namespace_ local, podemos utilizar a palavra-chave `from` junto com o `import`. 50 | * Ao invés de usar o asterisco, especificamos os nomes que queremos importar para o _namespace_ corrente. 51 | * Dessa maneira, o _namespace_ corrente fica mais limpo e o "autocompletar" do VS code também :) 52 | * Lembram do `datetime`? Quando nós usamos `from datetime import datetime`, o primeiro item é o nome do módulo e o segundo é o nome de uma classe. 53 | 54 | ### conflito.py 55 | * Um risco de importar tudo que existe em um módulo é ter uma outra função ou variável no _namespace_ corrente com o mesmo nome. 56 | * Neste caso, é fácil ter um bug porque você pode ao invés de usar um método, o programador acaba usando o outro. 57 | 58 | ## Pacotes 59 | * Um **pacote** é uma coleção de módulos que foi disponibilizada publicamente. 60 | * Pacotes são arquivos externos ao sistema que estamos desenvolvendo. 61 | * Provavelmente todas as aplicações em que você for trabalhar precisarão usar pacotes. 62 | * Já existem pacotes para incontáveis funcionalidades: analisar dados (pandas, numpy, matplotlib), colorir o que é impresso no terminal (colorama), lidar com geração de boletos, converter moedas, etc. 63 | * Na prática, antes de começar a implementar uma funcionalidade nova, sempre é bom buscar se já não existem pacotes prontos que podemos usar para fazer o que queremos. 64 | * O [Python Package index](https://pypi.org/) contém a lista de todos os pacotes públicos que podem ser instalados utilizando o comando `pip` no terminal. 65 | * Importar módulos que estão em um pacote funciona da mesma forma que importar módulos criados diretamente no sistema, exceto que precisamos baixar o pacote primeiro para o ambiente. 66 | 67 | ## Instalação de pacotes - Slide 68 | * Para instalar pacotes, a nossa instalação do Python conta com o comando `pip` 69 | * Importante: O comando `pip` deve ser usado no terminal, não em um script python. 70 | * Para instalar um pacote individualmente, podemos usar `pip install [nome_do_pacote]`, ou manter um um arquivo com uma lista de todos os pacotes que são utilizados no projeto. 71 | * Geralmente é uma boa prática manter um arquivo de pacotes necessários para fazer aquele projeto funcionar. Desta forma, outros desenvolvedores e usuários do sistema têm acesso a mesma lista. 72 | * O nome deste arquivo chamado costuma ser _requirements.txt_ 73 | * O conteúdo desse arquivo é o nome de um pacote por linha 74 | * Para instalar as dependências listadas no arquivo _requirements.txt_, é preciso rodar no terminal o comando `pip install -r requirements.txt` 75 | 76 | ### funcoes_do_log_colorido.py 77 | * Neste arquivo alteramos um pouco a nossa função de imprimir mensagens no log para ela imprima o nível da mensagem com a fonte colorida. 78 | * Depois de instalar o pacote `colorama`, nós podemos importá-lo de forma semelhante ao que fizemos anteriormente. 79 | * O pacote exige que a função `colorama.init()` seja chamada antes do seu uso. 80 | * Como eu sei disso? Procurei no Google :) [veja o exemplo](https://www.delftstack.com/pt/howto/python/python-print-colored-text/) 81 | 82 | ### pacote.py 83 | * O neste arquivo vamos importar o módulo que nós criamos e usá-lo para imprimir mensagens com o início colorido. 84 | 85 | ### Slide - Escopos de variáveis 86 | * Quando usamos funções, começamos a trabalhar com variáveis internas ou locais e com variáveis externas ou globais. A diferença entre elas é a **visibilidade** ou **escopo**. 87 | ```python 88 | variavel_global = 'global teste' 89 | def minha_funcao(): 90 | global variavel_global 91 | variavel_local = 'local_teste' 92 | variavel_global = 'outro valor' 93 | ``` 94 | * Variáveis declaradas dentro de uma função não podem ser acessadas fora dela. Neste caso, dizemos que a variável é **local** porque ela só existe dentro do seu escopo, que é a delimitado pela função onde é declarada. 95 | * Uma variável local a uma função existe apenas dentro dela, sendo normalmente inicializada a cada chamada. 96 | * Não podemos acessar o valor de uma variável local fora da função que a criou e, por isso, passamos parâmetros e retornamos valores nas funções, de forma a possibilitar a troca de dados no programa. 97 | * Variáveis declaradas fora de qualquer função são chamadas de **globais**. Elas se encontram em um escopo que é acessível em qualquer parte do seu script e também por outros módulos. 98 | * Uma variável global é definida fora de uma função, pode ser vista por todas as funções do módulo e por todos os módulos que importam o módulo que a definiu. 99 | * Uma aplicação comum de variáveis globais é o armazenamento de valores constantes no programa, que ficam acessíveis para a todas as funções. 100 | * Para alterar variáveis globais dentro de funções, precisamos indicar a função que estamos querendo alterar a variável do escopo global. Caso contrário, outra variável de mesmo nome é criada dentro do escopo da função e é alterada apenas localmente. 101 | 102 | ### escopos_de_variaveis.py 103 | * Começamos definindo uma variável **local** na função `cardapio`. Tentar acessar a variável local `comida` fora dela vai gerar um erro. 104 | * Declaramos a variável `bebida` no escopo `global` do módulo, então ela pode ser vista dentro de todas as funções daquele módulo. 105 | * Se criarmos outra variável `bebida` dentro da função `cardapio`, na verdade estamos criando uma nova variável local, e não alterando a variável global. A variável local tem prioridade porque está declarada no escopo da função. 106 | * Para alterar o valor de uma variável global dentro de uma função, precisamos informar a função que vamos utilizar a variável do escopo global. 107 | -------------------------------------------------------------------------------- /orientacao-a-objetos/1-escopos-e-namespaces/__pycache__/funcoes_do_log.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WoMakersCode/back-end-python/26c6d9b0b732d41736f0c37ee06a1b807df905a7/orientacao-a-objetos/1-escopos-e-namespaces/__pycache__/funcoes_do_log.cpython-37.pyc -------------------------------------------------------------------------------- /orientacao-a-objetos/1-escopos-e-namespaces/__pycache__/funcoes_do_log_colorido.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WoMakersCode/back-end-python/26c6d9b0b732d41736f0c37ee06a1b807df905a7/orientacao-a-objetos/1-escopos-e-namespaces/__pycache__/funcoes_do_log_colorido.cpython-37.pyc -------------------------------------------------------------------------------- /orientacao-a-objetos/1-escopos-e-namespaces/conflito.py: -------------------------------------------------------------------------------- 1 | # Um risco de importar tudo é ter uma outra função ou variável no namespace 2 | # atual com o mesmo nome. Neste caso, é fácil ter um bug porque você pode 3 | # ao invés de usar um método, o programador acaba usando o outro. 4 | from funcoes_do_log import * 5 | 6 | imprimir_no_log(f'Bem vinda, {nome_de_usuario}!') # usa a função que foi importada 7 | print() 8 | 9 | def imprimir_no_log(texto): # sobrescreve a função importada do módulo "funcoes_do_log" 10 | print(texto) 11 | 12 | imprimir_no_log(f'Bem vinda, {nome_de_usuario}!') 13 | -------------------------------------------------------------------------------- /orientacao-a-objetos/1-escopos-e-namespaces/escopos_de_variaveis.py: -------------------------------------------------------------------------------- 1 | # Quando usamos funções, começamos a trabalhar com variáveis internas ou locais e 2 | # com variáveis externas ou globais. A diferença entre elas é a visibilidade ou escopo. 3 | 4 | # 'comida' é uma variável é LOCAL porque só existe dentro do escopo da função "cardapio". 5 | def cardapio(): 6 | comida = 'hamburguer' 7 | print(comida) 8 | 9 | # print(comida) # essa linha vai gerar um erro 10 | 11 | # Variáveis declaradas fora de qualquer função são chamadas de GLOBAIS. Elas 12 | # se encontram em um escopo que é acessível por qualquer função neste script. 13 | bebida = 'refrigerante' 14 | def cardapio(): 15 | comida = 'hamburguer' 16 | print('comida:', comida) 17 | print('bebida:', bebida) 18 | 19 | cardapio() 20 | print() 21 | 22 | # Ao alterar o valor de uma variável global dentro de uma função, na verdade é criada 23 | # outra variável com o mesmo nome dentro do escopo da função e a variável global 24 | # permanece intacta. 25 | bebida = 'refrigerante' 26 | def cardapio(): 27 | comida = 'hamburguer' 28 | bebida = 'suco' 29 | print('comida:', comida) 30 | print('bebida:', bebida) 31 | 32 | cardapio() 33 | print('bebida:', bebida) 34 | print() 35 | 36 | # Para alterar o valor de uma variável global dentro de uma função, precisamos informar 37 | # a função que vamos utilizar a variável do escopo global. 38 | bebida = 'refrigerante' 39 | def cardapio(): 40 | global bebida 41 | comida = 'hamburguer' 42 | bebida = 'suco' 43 | print('comida:', comida) 44 | print('bebida:', bebida) 45 | 46 | cardapio() 47 | print('bebida:', bebida) 48 | print() 49 | -------------------------------------------------------------------------------- /orientacao-a-objetos/1-escopos-e-namespaces/from_import_especifico.py: -------------------------------------------------------------------------------- 1 | # Posso importar também partes específicas do módulo ao invés de importar tudo que 2 | # existe dentro dele. Dessa forma, meu namespace corrente fica mais limpo e meu 3 | # "autocompletar" do VS code fica menos cheio de coisas. 4 | from funcoes_do_log import nome_de_usuario, imprimir_no_log 5 | imprimir_no_log(f'Bem vinda, {nome_de_usuario}!') 6 | 7 | # O primeiro datetime é o nome do módulo, o segundo é o nome de uma classe que existe dentro 8 | # desse módulo 9 | from datetime import datetime 10 | agora = datetime.now() -------------------------------------------------------------------------------- /orientacao-a-objetos/1-escopos-e-namespaces/from_import_tudo.py: -------------------------------------------------------------------------------- 1 | # Para importar todo o conteúdo do módulo "funcoes_do_log" sem precisar 2 | # utilizar o namespace para usá-lo, podemos utilizar a palavra chave "from". 3 | # O conteúdo do módulo vai ser todo importado para o namespace corrente. 4 | # O asterisco indica que queremos importar todo o conteúdo deste módulo. 5 | from funcoes_do_log import * 6 | imprimir_no_log(f'Bem vinda, {nome_de_usuario}!') -------------------------------------------------------------------------------- /orientacao-a-objetos/1-escopos-e-namespaces/funcoes_do_log.py: -------------------------------------------------------------------------------- 1 | # Este arquivo consiste no módulo "funcoes_do_log" 2 | 3 | nome_de_usuario = 'Dori' 4 | 5 | def imprimir_no_log(texto, nivel='info'): 6 | if nivel.lower() == 'info': 7 | print(f'Info: {texto}') 8 | elif nivel.lower() == 'aviso': 9 | print(f'Aviso: {texto}') 10 | elif nivel.lower() == 'erro': 11 | print(f'Erro: {texto}') 12 | else: 13 | print('Erro interno - nível desconhecido de mensagem') -------------------------------------------------------------------------------- /orientacao-a-objetos/1-escopos-e-namespaces/funcoes_do_log_colorido.py: -------------------------------------------------------------------------------- 1 | # Este arquivo consiste no módulo "funcoes_do_log_colorido" 2 | import colorama 3 | 4 | # Necessário para fazer o colorama iniciar 5 | colorama.init() 6 | 7 | nome_de_usuario = 'Dori' 8 | 9 | def imprimir_no_log(texto, nivel='info'): 10 | if nivel.lower() == 'info': 11 | print(colorama.Fore.LIGHTBLUE_EX + f'Info: ', end='') 12 | print(colorama.Style.RESET_ALL + texto) 13 | elif nivel.lower() == 'aviso': 14 | print(colorama.Fore.YELLOW + f'Aviso: ', end='') 15 | print(colorama.Style.RESET_ALL + texto) 16 | elif nivel.lower() == 'erro': 17 | print(colorama.Fore.RED + f'Erro: ', end='') 18 | print(colorama.Style.RESET_ALL + texto) 19 | else: 20 | print(colorama.Fore.RED + 'Erro interno - nível desconhecido de mensagem' + colorama.Style.RESET_ALL) -------------------------------------------------------------------------------- /orientacao-a-objetos/1-escopos-e-namespaces/import.py: -------------------------------------------------------------------------------- 1 | # A função "imprimir no log" está no módulo "funcoes_do_log" (nome do arquivo .py). 2 | # Precisamos importar o módulo para poder reutilizar seu conteúdo aqui. 3 | 4 | # A palavra chave import disponibiliza o conteúdo do módulo através do 5 | # namespace "funcoes_do_log". 6 | import funcoes_do_log 7 | funcoes_do_log.imprimir_no_log(f'Bem vinda, {funcoes_do_log.nome_de_usuario}!') -------------------------------------------------------------------------------- /orientacao-a-objetos/1-escopos-e-namespaces/pacote.py: -------------------------------------------------------------------------------- 1 | from funcoes_do_log_colorido import imprimir_no_log 2 | 3 | imprimir_no_log('teste', 'info') 4 | imprimir_no_log('teste', 'aviso') 5 | imprimir_no_log('teste', 'erro') 6 | imprimir_no_log('teste', 'outro') 7 | -------------------------------------------------------------------------------- /orientacao-a-objetos/1-escopos-e-namespaces/requirements.txt: -------------------------------------------------------------------------------- 1 | colorama -------------------------------------------------------------------------------- /orientacao-a-objetos/2-classes-e-objetos/README.md: -------------------------------------------------------------------------------- 1 | # Aula 02 - Classes e Objetos 2 | 3 | ## O que é a orientação a objetos 4 | ### Slides 5 | * Até o momento, nós vimos aqui a programação tradicional. Nela, programas são listas de instruções para o computador que definem dados (através de variáveis e tipos de dados) e trabalham com esses dados usando funções. 6 | * Dados e funções são entidades diferentes, e que precisam ser combinadas para produzir o resultado esperado. 7 | * Por causa dessa separação, muitas vezes a programação tradicional não nos dá uma forma muito intuitiva de representar a o mundo real. 8 | * Isso faz com que a gente crie códigos como esse aqui. 9 | ```python 10 | muda_canal_para_cima(televisao, canal) 11 | ``` 12 | * A programação orientada a objetos é um **paradigma** de programação diferente dessa programação tradicional. 13 | * Podemos pensar na programação orientada a objetos como uma forma de modelar os nossos programas como objetos. Ex: televisão é um objeto, livros, são objetos, mesas são objetos... 14 | * Objetos tem dois componentes principais: **propriedades** e **comportamentos**. 15 | * Propriedades: cor, forma, pesa, tamanho, etc. 16 | * Comportamentos: são coisas que dá pra fazer com aquele objeto. 17 | * Exemplo: considerando o objeto televisão: 18 | * Propriedades: se está ligada ou não, em qual canal está, quais são os canais máximo e mínimo que são permitidos na televisão, volume. 19 | * Comportamentos: mudar o canal pra cima e para baixo, ligar e desligar, aumentar e reduzir o volume. 20 | * Na programação orientada a objeitos, as propriedades e os comportametos não ficam separados, eles ficam associados ao objeto. Isso leva a códigos desse estilo: 21 | ```python 22 | televisao.mudar_canal_para_cima() 23 | ``` 24 | * Na programação orientada a objetos (POO), objetos não são _só_ variáveis (pedaços da memória que guardam valores). Eles também são representações de algo no mundo real (sujeito/ator), e reúnem propriedades e comportamentos desse sujeito. 25 | * Objetos são auto-contidos e reutilizáveis: 26 | ```python 27 | televisao.ligar() 28 | televisao.aumentar_volume() 29 | ``` 30 | * Essa forma de modelar o mundo real cria códigos claros que mostram quem é o sujeito (**objeto**) e qual é o comportamento que está sendo invocado (**método**). 31 | * Métodos são funções associadas a uma classe e atuam sobre um objeto. 32 | * POO não substitui a programação tradicional – ela te dá mais ferramentas para escrever um código limpo, conciso e legível. 33 | * POO traz novos conceitos para a linguagem: classes, herança, encapsulamento, abstrações e polimorfismo. 34 | * Como criar meus próprios objetos em Python? Utilizando classes. 35 | * Classes estruturas usadas para definir um novo tipo de dados (criado pela programadora). Elas descrevem o que um objeto vai ser, mas elas não criam o objeto em si. 36 | 37 | ## televisao.py 38 | * Classes são criadas com a palavra-chave `class`. 39 | * Conveção para nomes de classes em Python: **PascalCasing**. 40 | * Para instanciar um objeto de uma classe, adicionamos parênteses ao nome da classe. 41 | * Uma classe pode ter várias instâncias, e cada uma delas é isolada da outra e tem seus próprios atributos e métodos. 42 | * Chamar um método em uma instância não altera a outra. 43 | * Toda classe tem um método especial `__init__`, que é sempre chamado quando um novo objeto daquela classe é criado. 44 | * O método `__init__()` é chamado de **construtor** porque ele inicializa os valores padrão do objeto. 45 | * O primeiro parâmetro dos métodos de uma classe é chamado de `self`. Ele representa a instância sobre a qual o método vai atuar. 46 | * `self` dá acesso a atributos e outros métodos daquela mesma instância. 47 | * Não é necessário passar o parâmetro `self` ao chamar os métodos da classe porque o interpretador faz isso aumaticamente para nós. 48 | * Entretanto, não se esqueça de declarar `self` como o primeiro parâmetro de seus métodos. 49 | * Quando chamamos o método `ligar()` em uma instância, estamos alterando o estado apenas dela. A outra televisão permanece desligada. 50 | * Se tentamos imprimir o conteúdo do objeto, visualizamos uma representação estranha. O Python mostra o tipo do objeto e o endereço da memória em que aquele objeto foi guardado. 51 | * Quando imprimimos um objeto do tipo `Televisao`, o interpretador Python chama o método especial `__str__()` internamente e imprime o conteúdo retornado por esse método. 52 | * O método especial `__str__()` permite que nós criemos uma representação em `string` para uma instância que tenha o tipo definido pela nossa classe. 53 | * Quando digitamos o nome da variável que contém o objeto no VSCode e incluímos o ponto final `.`, o VSCode mostra uma lista de opções de métodos e atributos relativos a nossa classe. 54 | * No módulo `datetime` da biblioteca padrão, temos as classes `datetime`, `date` e `time`. 55 | * Passando o mouse em cima dos nomes no `import`, podemos ver que eles são classes. -------------------------------------------------------------------------------- /orientacao-a-objetos/2-classes-e-objetos/lista-de-exercicios-1.py: -------------------------------------------------------------------------------- 1 | # Crie uma classe que modele o objeto "carro". 2 | # Um carro tem os seguintes atributos: ligado, cor, modelo, velocidade. 3 | # Um carro tem os seguintes comportamentos: liga, desliga, acelera, desacelera. 4 | 5 | 6 | 7 | # Crie uma instância da classe carro. 8 | 9 | 10 | # Faça o carro "andar" utilizando os métodos da sua classe. 11 | 12 | 13 | # Faça o carro "parar" utilizando os métodos da sua classe. -------------------------------------------------------------------------------- /orientacao-a-objetos/2-classes-e-objetos/televisao.py: -------------------------------------------------------------------------------- 1 | # Definição da classe 2 | class Televisao: # Convenção para nomes de classes: PascalCasing 3 | def __init__(self): 4 | self.ligada = False 5 | self.canal = 3 6 | self.canal_min = 1 7 | self.canal_max = 99 8 | self.volume = 30 9 | self.volume_min = 0 10 | self.volume_max = 100 11 | 12 | def ligar(self): 13 | self.ligada = True 14 | 15 | def desligar(self): 16 | self.ligada = False 17 | 18 | def mudar_canal_para_cima(self): 19 | if not self.ligada: 20 | return 21 | 22 | if self.canal < self.canal_max: 23 | self.canal += 1 24 | 25 | def mudar_canal_para_baixo(self): 26 | if not self.ligada: 27 | return 28 | 29 | if self.canal > self.canal_min: 30 | self.canal -= 1 31 | 32 | def aumentar_volume(self): 33 | if not self.ligada: 34 | return 35 | 36 | if self.volume < self.volume_max: 37 | self.volume += 10 38 | 39 | def reduzir_volume(self): 40 | if not self.ligada: 41 | return 42 | 43 | if self.volume > self.volume_min: 44 | self.volume -= 10 45 | 46 | def __str__(self) -> str: 47 | return f'Televisao - ligada {self.ligada} - canal {self.canal} - volume {self.volume}' 48 | 49 | # Criando instâncias da class Televisao 50 | tv_sala = Televisao() 51 | tv_quarto = Televisao() 52 | 53 | # Quando chamamos o método ligar() em uma instância, estamos alterando o estado apenas 54 | # dela. A outra televisão permanece desligada. 55 | tv_sala.ligar() 56 | print('tv_sala está ligada? {}'.format(tv_sala.ligada)) 57 | print('tv_quarto está ligada? {}'.format(tv_quarto.ligada)) 58 | 59 | for _ in range(3): 60 | tv_sala.aumentar_volume() 61 | 62 | print('tv_sala volume: {}'.format(tv_sala.volume)) 63 | print('tv_quarto volume: {}'.format(tv_quarto.volume)) 64 | print() 65 | 66 | # Se tentamos imprimir o conteúdo do objeto, visualizamos uma representação estranha. 67 | print('tv_sala:', tv_sala) 68 | print('tv_quarto:', tv_quarto) 69 | print() 70 | 71 | # O método especial `__str__()` permite que nós criemos uma representação em string para 72 | # uma instância que tenha o tipo definido pela nossa classe. 73 | print('tv_sala:', tv_sala) 74 | print('tv_quarto:', tv_quarto) 75 | print() 76 | 77 | # Já usamos classes antes quando falamos de tipos de dados relativos a datas 78 | from datetime import date 79 | 80 | # Dia é um objeto, ou seja, uma instância da classe date 81 | dia = date(2019, 2, 22) 82 | print('Dia da semana:', dia.weekday()) -------------------------------------------------------------------------------- /orientacao-a-objetos/3-entendendo-orientacao-a-objetos/README.md: -------------------------------------------------------------------------------- 1 | # Aula 03 - Entendendo Orientação a Objetos 2 | 3 | ## Modelagem OOP - Slides 4 | * Todo software usa a mesma série de etapas para resolver problemas: 5 | 1. **Entrada de dados**: os dados são lidos de algum lugar, que pode ser o armazenamento de dados como um sistema de arquivos ou um banco de dados. 6 | 2. **Processamento**: os dados são interpretados e possivelmente alterados para serem preparados para exibição. 7 | 3. **Saída de dados**: os dados são apresentados para que um usuário físico ou um sistema possa lê-los e interagir com eles. 8 | * A programação orientada a objetos cria **modelos** do mundo em que os dados são operados. 9 | * Os modelos possuem classes que representam objetos ou atores do mundo real, e como eles interagem entre si. 10 | * Durante a fase de modelagem, você examina uma descrição de um domínio e tenta analisar o texto sobre o que ocorre. 11 | * A primeira etapa é identificar os **atores**. Eles são chamados de atores porque atuam no domínio e executam uma ação. 12 | * Por exemplo, uma impressora (ator) imprime (ação). 13 | * Atores serão objetos no nosso programa, geralmente são substantivos que representam do mundo real. 14 | * Em seguida, você examina as descrições dos atores e de todos os **dados** necessários para executar a ação. 15 | * Após identificar atores, você examina o que eles fazem, que é o **comportamento**. 16 | * Os atores são transformados em objetos, as características são codificadas como dados nos objetos e os comportamentos são funções que também são adicionadas ao objeto. 17 | * A ideia é que os dados nos objetos podem ser alterados chamando funções nos próprios objetos. 18 | * Também há a noção de que os objetos interagem uns com os outros para chegar a um resultado tangível, gerando uma saída para o nosso programa. 19 | * Modelagem trata-se de aprender a identificar os atores, os dados necessários e o tipo de interação que está ocorrendo. 20 | * Você pode modelar um sistema investigando sua descrição, que é relacionada as **regras de negócio**. 21 | 22 | ## Modelagem de um estacionamento - slides 23 | * Vamos fazer a modelagem de um sistema de estacionamento. 24 | * Para isso, vamos levantar os requisitos. 25 | * Então a gente conversa com um dono de estacionamento e ele nos explica o seguinte: 26 | * O estacionamento é um pátio de apenas um andar. Ele possui 50 vagas. 27 | * Há 5 vagas para carros e 5 vagas para motos. Vagas para carro são maiores do que as vagas para motos. 28 | * Carros e motos são identificados por suas placas. 29 | * Vagas são identificadas por um número. Cada vaga tem um número identificador único. 30 | * Carros só podem ser estacionado em vagas específicas para carros. 31 | * Motos preferencialmente são estacionadas em vagas de motos, mas se não houver mais vagas exclusivas de motos disponíveis, motos podem ser estacionadas em vagas de carros. 32 | * É preciso ter controle sobre qual carro está em qual vaga para agilizar a saída quando o dono vem buscar. 33 | * É preciso saber o número de vagas livres de carro e de moto para que o estacionamento saiba se pode novos carros e motos. 34 | * O primeiro passo é identificar os atores, ou seja, os substantivos que vão se transformar em classes no nosso sistema. 35 | * Estacionamento, Carro, Moto e Vaga. 36 | * Em seguida, identificamos os atributos de cada ator. 37 | * Estacionamento: vagas de carro, vagas de moto, carro_para_vaga, moto_para_vaga, total_de_vagas_livres_carro, total_de_vagas_livres_moto 38 | * Carro: placa 39 | * Moto: placa 40 | * Vaga: identificador, tipo 41 | * Vamos então olhar identificar quem vao ser os dados de entrada, o processamento e a saída do nosso programa: 42 | * Em nosso sistema, a entrada vai ser um conjunto de carros (pode ser uma lista cheia de objetos dos tipos `Carro` e `Moto`, um dicionário, etc). 43 | * O processamento vai cuidar do estacionamento, sendo responsável por atualizar a entrada e saída de carros. 44 | * Ainda precisamos de algum tipo de controle para evitar que o mesmo objeto de carro o moto seja estacionado duas vezes dentro do estacionamento. Para isso, adicionamos um atributo `estacionado` nas classes `Carro` e `Moto` que será um valor booleano atualizado pelos métodos `estacionar()` e `sair_da_vaga()`. 45 | * Também precisamos de uma forma de mostrar qual é o estado atual do estacionamento, que é a nossa saída. Para isso, adicionamos o método `estado_do_estacionamento()` na classe `Estacionamento`. 46 | 47 | 48 | ## estacionamento.py 49 | * Este módulo define as classes `Estacionamento`, `Carro`, `Moto` e `Vaga` que nós discutimos. 50 | * As classes `Carro` e `Moto` são muito semelhantes. 51 | * A classe `Vaga` recebe em seu construtor qual é o tipo daquela vaga, e possui métodos para realizar a sua ocupação e desocupação, salvando a placa do veículo que está ocupando a vaga no momento. 52 | * A lógica mais interessante se concentra na classe `Estacionamento`: 53 | * O construtor inicializa os atributos que nós vimos anteriormente. 54 | * Utilizamos o método `inicializar_vagas` para preencher os dicionários `vagas_carro` e `vagas_motos` com objetos do tipo `Vaga`, onde cada chave é o identificador de vaga e cada valor é um objeto do tipo `Vaga`. 55 | * O primeiro `id` de vaga de moto é o próximo valor inteiro depois do último `id` da vaga de carro. 56 | * O método `estacionar_carro()` busca a primeira vaga de carro livre na coleção de vagas de carro. 57 | * Se ele não encontra nenhuma vaga livre, uma exceção é lançada. 58 | * Se encontra, ele atualiza todas os atributos necessários para realizar o controle de ocupação da vaga. 59 | * O dicionário `carro_para_vaga` mantém um dicionário para agilizar a busca de em qual vaga o carro foi estacionado. 60 | * O método `estacionar_moto()` funciona de forma semelhante, mas precisa levar em conta que motos podem ser estacionadas em vagas de carro caso todas as vagas de moto já estejam ocupadas. 61 | * O método auxiliar `buscar_id_da_proxima_vaga_livre()` inclui a lógica para buscar vagas de carro e de moto. É nele que incluímos a lógica que tenta buscar o identificador de vagas de moto e, em seguida, de carros caso não tenhamos vagas de moto livres. 62 | * Os métodos `remover_carro()` e `remover_moto()` encontram em qual vaga o veículo está estacionado e atualizam os valores de atributos pertinentes a remoção de veículo da vaga. 63 | * Finalmente, o estado do estacionamento é mostrado através de uma `string`. 64 | * Adicionamos o método especial `__str(self)__` para poder imprimir o estado do estacionamento com a função `print()`. 65 | 66 | 67 | ## modelagem.py 68 | * Agora vamos utilizar as classes que criamos para simular um sistema de estacionamento. 69 | * Começamos incluindo todas as classes do módulo `estacionamento` no _namespace_ corrente, já que vamos utilizar todas elas. 70 | * Criamos duas listas: uma de carros e uma de motos. Utilizamos a biblioteca `random` para gerar valores inteiros aleatórios que representem as placas dos veículos. 71 | * Em seguida, testamos as lógicas que criamos. 72 | -------------------------------------------------------------------------------- /orientacao-a-objetos/3-entendendo-orientacao-a-objetos/estacionamento.py: -------------------------------------------------------------------------------- 1 | class Carro: 2 | def __init__(self, placa): 3 | self.placa = placa 4 | self.estacionado = False 5 | 6 | def estacionar(self): 7 | self.estacionado = True 8 | 9 | def sair_da_vaga(self): 10 | self.estacionado = False 11 | 12 | class Moto: 13 | def __init__(self, placa): 14 | self.placa = placa 15 | self.estacionado = False 16 | 17 | def estacionar(self): 18 | self.estacionado = True 19 | 20 | def sair_da_vaga(self): 21 | self.estacionado = False 22 | 23 | class Vaga: 24 | def __init__(self, identificador, tipo): 25 | self.id = identificador 26 | self.livre = True 27 | 28 | if tipo is not 'carro' and tipo is not 'moto': 29 | raise ValueError(f'O tipo de vaga {tipo} não foi reconhecido') 30 | 31 | self.tipo = tipo 32 | self.placa = None 33 | 34 | def ocupar(self, placa): 35 | if self.livre is False: 36 | raise ValueError(f'A vaga {self.identificador} já está ocupada') 37 | 38 | self.placa = placa 39 | self.livre = False 40 | 41 | def desocupar(self): 42 | if self.livre is True: 43 | raise ValueError(f'A vaga {self.identificador} já está livre') 44 | 45 | self.placa = None 46 | self.livre = True 47 | 48 | 49 | class Estacionamento: 50 | def __init__(self, total_vagas_livres_carro, total_vagas_livres_moto): 51 | self.carro_para_vaga = {} 52 | self.moto_para_vaga = {} 53 | self.total_vagas_livres_carro = total_vagas_livres_carro 54 | self.total_vagas_livres_moto = total_vagas_livres_moto 55 | self.inicializar_vagas() 56 | 57 | def inicializar_vagas(self): 58 | self.vagas_carro = {} # id da vaga para o objeto de Vaga de carro 59 | self.vagas_moto = {} # id da vaga para o objeto de Vaga de moto 60 | 61 | tipo = 'carro' 62 | for i in range(self.total_vagas_livres_carro): # i vai de 0 a 4 63 | self.vagas_carro[i] = Vaga(i, tipo) 64 | 65 | primeiro_id_motos = self.total_vagas_livres_carro # 5 66 | ultimo_id_motos = primeiro_id_motos + self.total_vagas_livres_moto 67 | tipo = 'moto' 68 | for j in range(primeiro_id_motos, ultimo_id_motos): 69 | self.vagas_moto[j] = Vaga(j, tipo) 70 | 71 | def estacionar_carro(self, carro: Carro): 72 | if carro.estacionado is True: 73 | raise ValueError(f'O carro {carro.placa} já está no estacionamento.') 74 | 75 | id_da_proxima_vaga, tipo = self.buscar_id_da_proxima_vaga_livre('carro') # gera o id da próxima vaga 76 | if id_da_proxima_vaga is None: 77 | raise ValueError(f'Não há mais vagas de carro disponíveis no estacionamento.') 78 | elif id_da_proxima_vaga is not None and tipo is 'carro': 79 | vaga = self.vagas_carro[id_da_proxima_vaga] 80 | vaga.ocupar(carro.placa) 81 | carro.estacionar() 82 | self.carro_para_vaga[carro.placa] = vaga.id # permite achar imediatamente em qual vaga está a carro 83 | self.total_vagas_livres_carro -= 1 # reduz o número de vagas livres 84 | else: 85 | raise RuntimeError(f'Erro interno - não foi possível recuperar a próxima vaga de carro.') 86 | 87 | print(f'Carro {carro.placa} estacionado na vaga {vaga.id} do tipo {vaga.tipo}') 88 | 89 | 90 | def estacionar_moto(self, moto: Moto): 91 | if moto.estacionado is True: 92 | raise ValueError(f'O carro {moto.placa} já está no estacionamento.') 93 | 94 | id_da_proxima_vaga, tipo = self.buscar_id_da_proxima_vaga_livre('moto') # gera o id da próxima vaga 95 | 96 | if id_da_proxima_vaga is None: 97 | raise ValueError(f'Não há mais vagas de moto disponíveis no estacionamento.') 98 | elif id_da_proxima_vaga is not None and tipo is 'moto': 99 | vaga = self.vagas_moto[id_da_proxima_vaga] 100 | vaga.ocupar(moto.placa) 101 | moto.estacionar() 102 | self.moto_para_vaga[moto.placa] = vaga.id # permite achar imediatamente em qual vaga está a moto 103 | self.total_vagas_livres_moto -= 1 # reduz o número de vagas livres 104 | elif id_da_proxima_vaga is not None and tipo is 'carro': 105 | vaga = self.vagas_carro[id_da_proxima_vaga] 106 | vaga.ocupar(moto.placa) 107 | moto.estacionar() 108 | self.moto_para_vaga[moto.placa] = vaga.id # permite achar imediatamente em qual vaga está a moto 109 | self.total_vagas_livres_carro -= 1 # reduz o número de vagas livres 110 | else: 111 | raise RuntimeError(f'Erro interno - não foi possível recuperar a próxima vaga de moto.') 112 | 113 | print(f'Moto {moto.placa} estacionada na vaga {vaga.id} do tipo {vaga.tipo}') 114 | 115 | def buscar_id_da_proxima_vaga_livre(self, tipo): 116 | if tipo == 'carro': 117 | if self.total_vagas_livres_carro > 0: 118 | for identificador in self.vagas_carro.keys(): 119 | vaga = self.vagas_carro[identificador] 120 | if vaga.livre is True: 121 | return identificador, 'carro' 122 | else: 123 | # Não achou vaga de carro 124 | return None, '' 125 | elif tipo == 'moto': 126 | if self.total_vagas_livres_moto > 0: 127 | for identificador in self.vagas_moto.keys(): 128 | vaga = self.vagas_moto[identificador] 129 | if vaga.livre is True: 130 | return identificador, 'moto' 131 | if self.total_vagas_livres_carro > 0: 132 | for identificador in self.vagas_carro.keys(): 133 | vaga = self.vagas_carro[identificador] 134 | if vaga.livre is True: 135 | return identificador, 'carro' 136 | # Não achou vaga de carro nem de moto 137 | return None, '' 138 | else: 139 | raise TypeError(f'Tipo {tipo} não reconhecido') 140 | 141 | def remover_carro(self, carro: Carro): 142 | id_vaga = self.carro_para_vaga[carro.placa] 143 | vaga = self.vagas_carro[id_vaga] 144 | vaga.desocupar() 145 | carro.sair_da_vaga() 146 | del self.carro_para_vaga[carro.placa] 147 | self.total_vagas_livres_carro += 1 148 | print(f'Carro {carro.placa} retirado da vaga {vaga.id}') 149 | return True 150 | 151 | def remover_moto(self, moto: Moto): 152 | id_vaga = self.moto_para_vaga[moto.placa] 153 | vaga = None 154 | 155 | if id_vaga in self.vagas_moto: 156 | vaga = self.vagas_moto[id_vaga] 157 | elif id_vaga in self.vagas_carro: 158 | vaga = self.vagas_carro[id_vaga] 159 | else: 160 | raise ValueError(f'Não foi possível encontrar a vaga com identificador {id_vaga}') 161 | 162 | moto.sair_da_vaga() 163 | vaga.desocupar() 164 | del self.moto_para_vaga[moto.placa] 165 | 166 | if vaga.tipo == 'moto': 167 | self.total_vagas_livres_moto += 1 168 | else: 169 | self.total_vagas_livres_carro += 1 170 | 171 | print(f'Moto {moto.placa} retirada da vaga {vaga.id} (tipo {vaga.tipo})') 172 | 173 | def estado_do_estacionamento(self): 174 | num_carros_estacionados = len(self.carro_para_vaga) 175 | num_motos_estacionadas = len(self.moto_para_vaga) 176 | estado = ' ---- Estado do estacionamento ----\n' 177 | estado += f' Num carros estacionados: {num_carros_estacionados}\n' 178 | estado += f' Num motos estacionadas: {num_motos_estacionadas}\n' 179 | estado += f' Total de vagas livres de carros: {self.total_vagas_livres_carro}\n' 180 | estado += f' Total de vagas livres de motos: {self.total_vagas_livres_moto}\n' 181 | estado += f' Vagas de carro:\n ' 182 | for i in range(len(self.vagas_carro)): 183 | placa = self.vagas_carro[i].placa 184 | estado += f'vaga[{i}]: {placa}; ' 185 | estado += f'\n Vagas de moto:\n ' 186 | id_primeira_vaga_moto = len(self.vagas_carro) 187 | id_ultima_vaga_moto = id_primeira_vaga_moto + len(self.vagas_moto) 188 | for i in range(id_primeira_vaga_moto, id_ultima_vaga_moto): 189 | placa = self.vagas_moto[i].placa 190 | estado += f'vaga[{i}]: {placa}; ' 191 | estado += '\n ----------------------------------\n' 192 | return estado 193 | 194 | def __str__(self): 195 | return self.estado_do_estacionamento() -------------------------------------------------------------------------------- /orientacao-a-objetos/3-entendendo-orientacao-a-objetos/modelagem.py: -------------------------------------------------------------------------------- 1 | from estacionamento import * 2 | from random import randint 3 | 4 | # Inicializa duas lista com motos e carros 5 | carros = [] 6 | for i in range(10): 7 | placa = randint(1000, 9999) 8 | carros.append(Carro(placa)) 9 | motos = [] 10 | for i in range(20): 11 | placa = randint(1000, 9999) 12 | motos.append(Moto(placa)) 13 | 14 | # Inicializa o estacionamento e imprime seu estado 15 | estacionamento = Estacionamento(5, 5) 16 | print(estacionamento) 17 | 18 | # Estaciona 4 carros 19 | for i in range(4): 20 | estacionamento.estacionar_carro(carros[i]) 21 | print(estacionamento) 22 | 23 | # Estaciona 6 motos 24 | for i in range(6): 25 | estacionamento.estacionar_moto(motos[i]) 26 | print(estacionamento) 27 | 28 | # Tenta estacionar mais um carro mas o estacionamento está cheio 29 | # estacionamento.estacionar_carro(carros[4]) 30 | 31 | # Um carro sai do estacionamento e libera uma vaga para carros 32 | estacionamento.remover_carro(carros[0]) 33 | print(estacionamento) 34 | 35 | # Tenta estacionar uma moto que já está no estacionamento 36 | # estacionamento.estacionar_moto(motos[5]) 37 | 38 | # Estaciona uma nova moto 39 | estacionamento.estacionar_moto(motos[6]) 40 | print(estacionamento) 41 | 42 | # Um carro e uma moto deixam o estacionamento 43 | estacionamento.remover_carro(carros[1]) 44 | estacionamento.remover_moto(motos[3]) 45 | print(estacionamento) 46 | 47 | # Chega uma nova moto - deve usar preferencialmente a vaga de moto que foi liberada anteriormente 48 | estacionamento.estacionar_moto(motos[7]) 49 | print(estacionamento) -------------------------------------------------------------------------------- /orientacao-a-objetos/4-construtores-e-destrutores/01_construtores.py: -------------------------------------------------------------------------------- 1 | # Construtor padrão 2 | class MinhaClasse1: 3 | def __init__(self): 4 | print('MinhaClasse1: Chamou o construtor padrão') 5 | 6 | 7 | # Construtor implícito - o interpretador python cria o construtor padrão por debaixo dos panos 8 | class MinhaClasse2: 9 | pass # não faz nada 10 | 11 | # Construtor parametrizado 12 | class MinhaClasse3: 13 | def __init__(self, param): 14 | print(f'MinhaClasse3: Chamou o construtor parametrizado com o parâmetro {param}') 15 | 16 | 17 | objeto1 = MinhaClasse1() 18 | objeto2 = MinhaClasse2() 19 | objeto3 = MinhaClasse3('Dori') 20 | 21 | # Ao declarar um construtor parametrizado, o interpretador python não cria mais outro construtor 22 | # objeto4 = MinhaClasse3() # __init__() missing 1 required positional argument: 'param' -------------------------------------------------------------------------------- /orientacao-a-objetos/4-construtores-e-destrutores/02_destrutores.py: -------------------------------------------------------------------------------- 1 | # Construtor padrão 2 | class MinhaClasse: 3 | def __init__(self, nome): 4 | self.nome = nome 5 | print(f'MinhaClasse1: Chamou o construtor padrão de {nome}') 6 | 7 | def __del__(self): 8 | print(f'MinhaClasse1: Chamou o destrutor de {self.nome}') 9 | 10 | # O momento de execução de um destrutor é depois que o programa tem seu encerramento solicitado 11 | print('Começou a execução do programa') 12 | objeto1 = MinhaClasse('objeto1') 13 | print('Vai terminar a execução do programa') 14 | exit() 15 | 16 | # Quando todas as referências a um objeto são excluídas, ele o destrutor é automaticamente chamado 17 | print('Começou a execução do programa') 18 | objeto1 = MinhaClasse('objeto1') 19 | objeto2 = MinhaClasse('objeto2') 20 | del objeto1 21 | print('Vai terminar a execução do programa') -------------------------------------------------------------------------------- /orientacao-a-objetos/4-construtores-e-destrutores/README.md: -------------------------------------------------------------------------------- 1 | # Aula 4 - Construtores e Destrutores 2 | 3 | ## 01_construtores.py 4 | * Nós já vimos exemplos de métodos construtores nas aulas passadas. 5 | * Construtores são métodos especiais que são chamados durante a instanciação de um objeto. 6 | * A função de um construstor é inicializar valores aos dados que são membros da classe quando uma instância é criada. 7 | * O método `__init__()` é chamado construtor e é sempre chamado quando um objeto é criado. 8 | * Se você não definir o método construtor explicitamente, o interpretador faz isso automaticamente por baixo dos panos, criando o **construtor padrão**. 9 | * O construtor **construtor padrão** é o método `__init__(self)` que não aceita nenhum argumento além do `self`. 10 | * O construtor também pode ser customizado para receber parâmetros. Neste caso, ele deixa de ser o construtor padrão e se torna um **construtor parametrizado**. 11 | * Ao declarar um construtor parametrizado, o interpretador python não cria mais outro construtor na classe. 12 | 13 | ## 02_destrutores.py 14 | * Destrutores são métodos análogos aos construtores, só que para o objetivo oposto: eles são executados quando um objeto é destruído. 15 | * A função de um destrutor é liberar a memória que o objeto estava usando quando ela não é mais necessária. 16 | * Objetos desnecessários são excluídos automaticamente. Isso libera o espaço de memória conhecido como **coleta de lixo**. 17 | * O nome de um método destrutor é `__del__(self)`. 18 | * Assim como o construtor padrão, o destrutor é criado automaticamente pelo interpretador Python na classe e também é **chamado automaticamente**. 19 | * Isso tira do programador a responsabilidade sobre o gerenciamento direto da memória ocupada pelo programa. 20 | * O destrutor é chamado quando todas as referências para o objeto são extintas ou quando a execução do programa é encerrada. 21 | * Quando um programa termina, o **coletor de lixo** do python destrói todos os objetos em memória. Esta é a última coisa que acontece durante a execução. 22 | * Em geral, não é comum ver destrutores em códigos Python porque eles são executados automaticamente. Ainda assim é importante ter a noção de que eles existem, porque isso ajuda a entender melhor o que está acontecendo enquanto o seu programa está rodando. -------------------------------------------------------------------------------- /orientacao-a-objetos/5-atributos-de-visibilidade-e-encapsulamento/README.md: -------------------------------------------------------------------------------- 1 | # Aula 5 - Atributos de visibilidade e encapsulamento 2 | 3 | ## Encapsulamento - Slides 4 | * Em Python, todos os atributos e métodos declarados em uma classe são públicos, ou seja, podem ser acessados por outros códigos externos à classe. 5 | * Isso não quer dizer que eles devam ser usados por quem instancia um objeto daquela classe. 6 | * Alguns atributos e métodos só existem na classe para seu funcionamento interno. Se forem alterados, podem gerar mal funcionamento e _bugs_ no código. 7 | * No exemplo abaixo, na classe `Quadrado` há dois atributos: `altura` e `largura`. Para que a classe de fato defina um quadrado, ela precisa ter altura e largura sempre iguais. Por isso, é interessante que quem usa a minha classe entenda que não deve mexer nesses atributos. 8 | ```python 9 | class Quadrado: 10 | def __init__(self, medida): 11 | self.altura = medida 12 | self.largura = medida 13 | 14 | def area(self): 15 | return altura * largura 16 | 17 | quadrado = Quadrado(2) 18 | quadrado.altura = 3 # não é mais um quadrado 19 | ``` 20 | * Outro exemplo: na aula anterior, quando modelamos o funcionamento de um estacionamento, nossa classe tinha um método para buscar qual o `id` da próxima vaga livre para carros e motos. Esse método serve para auxiliar para a lógica interna da classe e também não gostaríamos que ele ficasse exposto, já que não faz sentido um usuário da classe chamar este método diretamente. 21 | * Para indicar ao usuário quais os atributos e métodos que ele não deve alterar na classe, nós utilizamos **convenções** de nomes. 22 | * Existem duas convenções que são utilizadas em Python para se iniciar nomes de métodos e atributos: 23 | * **Protegidos**: Atributos e métodos que têm seus nomes iniciados com **_** (_underscore_) não devem ser acessados pelo mundo externo a não ser que o usuário saiba exatamente o que está fazendo, ou seja, ainda pode existir algum caso de uso em que faça sentido ter acesso a esse método/atributo. Em geral, o caso de uso mais comum para acesso a membros privados é com o uso de **herança**, que vamos ver em um próxima aula. 24 | * **Privados**: Atributos e métodos que têm seus nomes iniciados com **__** (_underscore_ duplo) não devem ser acessados pelo mundo externo de forma nenhuma. Na prática, eles têm seus nomes alterados pelo interpretador Python mas ainda são públicos, e o que garante que eles não vão ser acessados é o bom senso do usuário da classe. 25 | * Essas são o que nós chamamos de **regras de encapsulamento**, porque a ideia é encapsular atributos e métodos que são pertinentes a nossa classe mas não ao mundo externo. 26 | * Em outras linguagens de programação que possuem orientação a objetos como C# e Java, temos palavras-chaves especiais para definir membros privados e protegidos, mas não em Python. Em Python nós utilizamos convenções nos nomes dos métodos. 27 | * Se um usuário da classe quiser acessar os membros protegidos e privados, ele tem como fazer isso, mas vai estar quebrando as regras de encapsulamento, que de novo, são definidas por convenções nos nomes das variáveis. 28 | * Existe uma forma de controlar um pouco melhor como um usuário vai acessar os atributos de uma classe, que é através de **propriedades**. 29 | * Propriedades podem ser definidas pelo uso do _decorator_ `@property`. Esse decorator cria o método `getter` da propriedade. 30 | * Para alterar o valor de uma propriedade externamente à classe, é preciso criar o método `setter`. 31 | 32 | ## encapsulamento.py 33 | * A classe `Pessoa` recebe três parâmetros: nome, profissão e identidade. 34 | * O nome de uma pessoa não é algo que costuma mudar, mas pode ser que em algum caso especial seja necessário alterá-lo. Por exemplo: alguém que se divorcia e quer alterar seu sobrenome. 35 | * Podemos pensar na variável nome como sendo protegida, ou seja, mostramos ao usuário que não esperamos que esse variável seja alterada. 36 | * Já a identidade é um dado sensível e não queremos que ele fique exposto de forma nenhuma. Por isso, podemos criá-lo como um dado privado. 37 | * Se tentarmos alterar um atributo privado, o seu valor não vai ser alterado. 38 | * A profissão é algo que é relativamente comum de mudar, estão criamos ela como um atributo público. 39 | * Nota: métodos especiais do Python começam e terminam com duplo _underline_. Isso não é o mesmo do que um método privado. Métodos privados possuem duplo _underline_ apenas no começo do nome. 40 | 41 | ## propriedades.py 42 | ```python 43 | class Quadrado: 44 | def __init__(self, medida): 45 | self.altura = medida 46 | self.largura = medida 47 | 48 | @property 49 | def altura(self): 50 | return self.__medida 51 | 52 | @altura.setter 53 | def altura(self, valor): 54 | # executa algum tipo de validação 55 | self.__medida = valor 56 | 57 | @property 58 | def largura(self): 59 | return self.__medida 60 | 61 | @largura.setter 62 | def largura(self, valor): 63 | # executa algum tipo de validação 64 | self.__medida = valor 65 | 66 | def area(self): 67 | return self.largura * self.altura 68 | ``` 69 | * A classe `Quadrado` que vimos nos slides está definida aqui, com a adição de uma validação na hora de inicializar os valores das propriedades `altura` e `largura` e alguns prints para mostrar quando os métodos _getter_ e _setter_ de cada propriedade são chamados. 70 | * Quando criamos o objeto `quadrado`, vemos que os _setters_ são chamados. 71 | * Quando acessamos os valores, vemos que os _getters_ são chamados. 72 | * Se removemos um dos _setters_, não conseguimos mais alterar o valor da propriedade diretamente. 73 | -------------------------------------------------------------------------------- /orientacao-a-objetos/5-atributos-de-visibilidade-e-encapsulamento/encapsulamento.py: -------------------------------------------------------------------------------- 1 | # Encapsulamento 2 | class Pessoa: 3 | def __init__(self, nome, profissao, identidade): 4 | self._nome = nome 5 | self.profissao = profissao 6 | self.__identidade = identidade 7 | 8 | def __str__(self): 9 | return f'Nome: {self._nome}, Profissão: {self.profissao}, Identidade: {self.__identidade}' 10 | 11 | 12 | pessoa1 = Pessoa('Marta Lima', 'Astronauta', '12345') 13 | print(pessoa1) 14 | print() 15 | 16 | # Se tentarmos alterar um atributo público, nós vamos conseguir 17 | pessoa1.profissao = 'Programadora' 18 | print(pessoa1) 19 | print() 20 | 21 | # Se tentarmos alterar um atributo protegido, nós vamos conseguir também 22 | pessoa1._nome = 'Marta' 23 | print(pessoa1) 24 | print() 25 | 26 | # Ao tentar alterar um atributo privado, o valor não vai ser alterado 27 | pessoa1.__identidade = '23525' 28 | print(pessoa1) 29 | print() 30 | -------------------------------------------------------------------------------- /orientacao-a-objetos/5-atributos-de-visibilidade-e-encapsulamento/propriedades.py: -------------------------------------------------------------------------------- 1 | class Quadrado: 2 | def __init__(self, medida): 3 | self.altura = medida 4 | self.largura = medida 5 | 6 | @property 7 | def altura(self): 8 | print('getter de altura') 9 | return self.__medida 10 | 11 | @altura.setter 12 | def altura(self, valor): 13 | print('setter de altura') 14 | if valor < 0: 15 | raise ValueError() 16 | self.__medida = valor 17 | 18 | @property 19 | def largura(self): 20 | print('getter de largura') 21 | return self.__medida 22 | 23 | @largura.setter 24 | def largura(self, valor): 25 | print('setter de largura') 26 | if valor < 0: 27 | raise ValueError() 28 | self.__medida = valor 29 | 30 | def area(self): 31 | return self.largura * self.altura 32 | 33 | quadrado = Quadrado(2) 34 | quadrado.altura = 3 35 | print(quadrado.area()) -------------------------------------------------------------------------------- /orientacao-a-objetos/6-heranca/README.md: -------------------------------------------------------------------------------- 1 | # Aula 6 - Herança e Herança Múltipla 2 | 3 | ## heranca.py 4 | * A classe `Estudante` herda da classe `Pessoa`. A sintaxe para indicar isso é colocar o nome da "classe pai" dentro de um parênteses na frente do nome da "classe filha". 5 | * Os métodos e variáveis da classe pai podem ser acessados com a função `super()`. 6 | * É uma boa prática sempre chamar o construtor da classe pai no construtor da classe filha. É ele que garante que as propriedades da classe pai vão ser inicializadas corretamente. 7 | * A classe `Estudante` herda os métodos e as propriedades públicas e protegidas da classe `Pessoa`. 8 | * A classe `Estudante` pode sobrescrever os valores que foram herdados. 9 | * Python provê funções nativas para testar se um objeto é instância de uma determinada classe, e se uma classe é derivada de outra. 10 | * A classe `Trabalhador` também herda de `Pessoa`, e a classe `Professor` herda de trabalhador. 11 | * `Professor` é um `Trabalhador` e também é uma `Pessoa`, portanto herda métodos e atributos de ambos. 12 | * Atributos privados não podem ser alterados em classes filhas. 13 | * Em Python, todas as classes herdam da classe `object`. Isso faz com que toda classe já comece com vários métodos e atributos. 14 | * A função `dir()` mostra os atributos e métodos de um determinado objeto ou classe. Notem que as propriedades privadas aparecem aqui também, mas com nomes diferentes. 15 | 16 | # Herança múltipla - Slide 17 | * Em Python, uma classe pode herdar de mais de uma classe pai. Esse conceito é conhecido como herança múltipla. 18 | * As vezes, classes que herdam de mais de uma classe são chamadas de **mixins**. 19 | * Heraça múltipla é uma funcionalidade muito controversa, porque o uso dessa funcionalidade traz potencialmente muita complexidade para o código. 20 | * Na maior parte das vezes onde alguém tenta resolver um problema com herança múltipla, existiriam soluções melhores e mais simples. 21 | * O caso de uso mais legítimo é na criação de um _framework_. 22 | * Ao trabalhar com Django, vocês podem ver casos onde uma classe vai herdar de duas ou mais classes. 23 | 24 | # heranca_multipla.py 25 | * A classe `Logavel` define o método `logar`. Qualquer classe que herdar dela vai conseguir escrever uma mensagem no log e nós vamos saber de onde a mensagem está vindo pelo atributo `nome_da_classe` que é inicializado no construtor. 26 | * Ter uma classe assim é interessante porque a lógica de criar um arquivo de log, escrever as mensagens dentro dele e fechar o arquivo depois fica todo em um lugar só. 27 | * Quem está escrevendo um software não precisa se preocupar em escrever essa lógica toda vez, é só herdar de `Logavel`. 28 | * A classe `Conexao` serve para conectar a um servidor de banco de dados. 29 | * O servidor costuma ser um endereço de IP com uma porta. 30 | * A classe `MySqlDatabase` é uma classe de exemplo que se conecta ao banco de dados **MySql**, que herda tanto de conexão quanto de logável. 31 | * O meu `framework` super chique aqui é só um metódo que recebe um objeto chamado `item` e testa se esse objeto é uma instância de cada uma das classes. Ele só chama os métodos pertinentes se for. -------------------------------------------------------------------------------- /orientacao-a-objetos/6-heranca/heranca.py: -------------------------------------------------------------------------------- 1 | # Pessoa é a nossa classe base 2 | class Pessoa: 3 | def __init__(self, nome): 4 | self._nome = nome 5 | self._tipo = 'Pessoa' 6 | 7 | def falar_oi(self): 8 | print('Oi! Meu nome é {}'.format(self._nome)) 9 | 10 | def falar_tipo(self): 11 | print('Meu tipo é {}'.format(self._tipo)) 12 | 13 | pessoa = Pessoa('Naira') 14 | pessoa.falar_oi() 15 | pessoa.falar_tipo() 16 | print() 17 | 18 | # A classe estudante é derivada da classe Pessoa. 19 | # Relação é: "Estudante" é uma "Pessoa" 20 | class Estudante(Pessoa): # o nome da classe base vem em parênteses 21 | def __init__(self, name, curso): 22 | super().__init__(name) # chama o construtor da classe base 23 | self._curso = curso 24 | 25 | def falar_curso(self): 26 | print(f'Eu, {self._nome}, estudo o curso "{self._curso}"') # A propriedade self._nome é herdada da classe base 27 | 28 | def falar_tipo(self): # Sobrescreve a função original da classe Pessoa 29 | self._tipo = 'Estudante' 30 | return super().falar_tipo() 31 | 32 | estudante = Estudante('Yasmin', 'Introdução ao Python') 33 | estudante.falar_oi() # o método "falar_oi" é herdado da classe base 34 | estudante.falar_tipo() # o método "falar_tipo" é herdado da classe base, e sobrescrito na classe derivada 35 | estudante.falar_curso() 36 | print() 37 | 38 | # Podemos testar se um objeto é de um determinado tipo em tempo de execução com as funções abaixo 39 | print('O objeto estudante é uma instância da classe Estudante? ', isinstance(estudante, Estudante)) 40 | print('O objeto estudante é uma instância da classe Pessoa? ', isinstance(estudante, Pessoa)) 41 | print('A classe Estudante é uma sub-classe de Pessoa? ', issubclass(Estudante, Pessoa)) 42 | print('A classe Pessoa é uma sub-classe de Estudante? ', issubclass(Pessoa, Estudante)) 43 | print() 44 | 45 | # Herança transitiva 46 | class Trabalhador(Pessoa): # Trabalhador também herda de Pessoa 47 | def __init__(self, nome, profissao): 48 | super().__init__(nome) # chama o construtor da classe base 49 | self.__profissao = profissao # atributo privado - só pode ser acessado dentro da classe Trabalhador 50 | self._tipo = 'Trabalhador' 51 | 52 | def falar_profissao(self): 53 | print(f'Eu, {self._nome}, exerço a profissão "{self.__profissao}"') 54 | 55 | def falar_tipo(self): # Sobrescreve a função original da classe Pessoa 56 | return super().falar_tipo() 57 | 58 | class Professor(Trabalhador): # Professor herda de Trabalhador 59 | def __init__(self, nome, disciplina): 60 | super().__init__(nome, 'Professor') # chama o construtor da classe base 61 | self.__disciplina = disciplina 62 | 63 | def falar_profissao(self): 64 | self.__profissao = 'Instrutora' # variáveis privadas não conseguem ser alteradas pela classe derivada 65 | return super().falar_profissao() 66 | 67 | def falar_disciplina(self): 68 | print(f'Eu, {self._nome}, dou aulas da disciplina "{self.__disciplina}"') 69 | 70 | def falar_tipo(self): # Sobrescreve a função original da classe Pessoa 71 | self._tipo = 'Professor' 72 | return super().falar_tipo() 73 | 74 | trabalhadora = Trabalhador('Beatriz', 'Desenvolvedora') 75 | trabalhadora.falar_oi() 76 | trabalhadora.falar_tipo() 77 | trabalhadora.falar_profissao() 78 | print() 79 | 80 | professora = Professor('Clarisse', 'Python') 81 | professora.falar_oi() 82 | professora.falar_tipo() 83 | professora.falar_profissao() 84 | professora.falar_disciplina() 85 | print() 86 | 87 | # Em Python, todos as classes herdam implicitamente da classe object 88 | class Humano: 89 | pass 90 | 91 | # A classe Humano já começa com vários atributos e métodos 92 | humano = Humano() 93 | print(dir(humano)) 94 | print() 95 | 96 | # Esses mesmos atributos e métodos existem nas classes que declaramos acima 97 | print(dir(professora)) 98 | print() -------------------------------------------------------------------------------- /orientacao-a-objetos/6-heranca/heranca_multipla.py: -------------------------------------------------------------------------------- 1 | class Logavel: 2 | def __init__(self): 3 | self.nome_da_classe = '' 4 | def logar(self, mensagem): 5 | print('Mensagem da classe ' + self.nome_da_classe + ': ' + mensagem) 6 | 7 | class Conexao: 8 | def __init__(self): 9 | self.servidor = '' 10 | def conectar(self): 11 | print('Conectando ao banco de dados no servidor ' + self.servidor) 12 | # Lógica para realizar a conexão aqui 13 | 14 | class MySqlDatabase(Conexao, Logavel): 15 | def __init__(self): 16 | super().__init__() 17 | self.nome_da_classe = 'MySqlDatabase' 18 | self.servidor = 'MeuServidor' 19 | 20 | def framework(objeto): 21 | if isinstance(objeto, Conexao): 22 | objeto.conectar() 23 | if isinstance(objeto, Logavel): 24 | mensagem = 'Olá mulheres maravilhosas do Bootcamp de Python.' 25 | objeto.logar(mensagem) 26 | 27 | conexao_mysql = MySqlDatabase() 28 | framework(conexao_mysql) -------------------------------------------------------------------------------- /orientacao-a-objetos/7-classes-abstratas/README.md: -------------------------------------------------------------------------------- 1 | # Aula 7 - Classes Abstratas 2 | 3 | ## Classes abstratas 4 | * Uma classe abstrata pode ser considerada como um modelo para criar outras classes. 5 | * Em uma classe abstrata, nós criamos um conjunto de métodos que devem ser implementados dentro de qualquer classe filha criada a partir da classe abstrata. 6 | * Um método abstrato é um método que tem uma declaração, mas não tem uma implementação. 7 | * Uma classe que contém um ou mais métodos abstratos é chamada de classe abstrata. 8 | * Não é possível instanciar uma classe abstrata diretamenta. Para criar um objeto do tipo de uma classe abstrata, precisamos instanciar uma das classes filhas. 9 | * Enquanto estamos projetando grandes unidades funcionais, usamos uma classe abstrata. A ideia é fornecer uma interface comum para diferentes implementações de um componente, que vai ser representado pelas suas classes filhas. 10 | * Python tem o módulo `ABC` (_Abstract Base Classes_) que fornece a funcionalidade de classes abstratas. A classe abstrata precisa herdar de `ABC` e métodos abstratos são marcados com o _decorator_ `@abstractmethod`. 11 | * Uma classe filha derivada de uma classe abstrata precisa implementar todos os métodos e propriedades abstratas. Caso contrário, não é possível criar objetos da classe filha. 12 | 13 | **Por que usar classes abstratas?** 14 | * Ao criar classes abstratas, você define uma interface comum para que todas as classes filhas sigam, ou seja, as classes abstratas são usadas para definir a **API** de suas classes filhas. 15 | * Definir uma API é uma capacidade importante na área de projeto e arquitetura de software. 16 | * Você pode estar desenvolvendo uma biblioteca de Python que vai prover a definição de uma API que vai ser implementada por outros desenvolvedores. 17 | * Outro caso de uso é o trabalho em uma equipe/empresa grande que vai precisar criar componentes que fazem coisas semelhantes em diferentes partes do software. Geralmente, um arquiteto define a API que deve ser seguida pelos diferentes componentes. 18 | * Isso vai ficar mais claro com um exemplo. 19 | 20 | ## classes_abstratas.py 21 | * Vamos supor que nós estamos desenvolvendo um jogo da franquia Pokémon. 22 | * Pokémon é uma franquia grande e conhecida por ter vários jogos lançados. Novos jogos costumam trazer novos pokémons, que ainda não eram conhecidos anteriormente. 23 | * O arquiteto de software precisa garantir que todas as classes que definem um pokemon sigam uma determinada API, que vai contér o ataque principal daquele pokemon, um método para garantir sua evolução e uma propriedade que guarda o seu tipo. 24 | * A classe `BasePokemon` herda da classe `ABC` e se torna assim uma classe abstrata. 25 | * Se tentarmos criar um objeto do tipo da classe base, não vamos conseguir. 26 | * Criamos uma classe `Pikachu` que vai herdar de `BasePokemon`. Essa classe vai precisar definir no mínimo todos os métodos e propriedades abstratas da classe base. 27 | * Notem que definimos sem as marcações de abstrato. Essas marcações só existem na classe base. 28 | * Se algum dos métodos não for implementado, não conseguiremos instanciar o objeto da classe `Pikachu`. 29 | * Novos métodos podem ser adicionados nas classes derivadas. 30 | -------------------------------------------------------------------------------- /orientacao-a-objetos/7-classes-abstratas/classes_abstratas.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | # A classe base dos pokemons 4 | class BasePokemon(ABC): 5 | def __init__(self, nome): 6 | self.nome = nome 7 | self._nivel = 1 8 | self._experiencia = 0 9 | 10 | @abstractmethod 11 | def ataque_principal(self): # O uso de métodos abstratos torna a classe abstrata 12 | pass 13 | 14 | @abstractmethod 15 | def passar_de_nivel(self): 16 | pass 17 | 18 | @abstractmethod 19 | def evoluir(self): 20 | pass 21 | 22 | @property 23 | @abstractmethod 24 | def tipo(self): 25 | pass 26 | 27 | # Criar um objeto de uma classe abstrata gera um erro 28 | # pokemon = BasePokemon('Pikachu', 'Elétrico') 29 | 30 | class Pikachu(BasePokemon): 31 | def __init__(self, nome): 32 | super().__init__(nome) 33 | 34 | def ataque_principal(self): 35 | print(f'{self.nome} usou Choque de trovão!') 36 | self._experiencia += 2 37 | self.passar_de_nivel() 38 | 39 | def passar_de_nivel(self): 40 | # Passa de nível a cada 10 pontos de experiência 41 | if self._experiencia % 10 == 0: 42 | self._nivel += 1 43 | print(f'{self.nome} passou para o nível {self._nivel}!') 44 | self.evoluir() 45 | 46 | def evoluir(self): 47 | if self._nivel == 25: 48 | print('!!!! Evoluiu para Raichu !!!!') 49 | self.nome = 'Raichu' 50 | 51 | @property 52 | def tipo(self): 53 | return 'Elétrico' 54 | 55 | def ataque_secundario(self): 56 | print(f'{self.nome} usou Surra de Calda!') 57 | self._experiencia += 2 58 | self.passar_de_nivel() 59 | 60 | pokemon = Pikachu('Pikachu') 61 | print(pokemon.nome + ' é um pokemon do tipo ' + pokemon.tipo) 62 | for _ in range(125): 63 | pokemon.ataque_principal() 64 | pokemon.ataque_secundario() -------------------------------------------------------------------------------- /orientacao-a-objetos/7-classes-abstratas/lista-de-exercicios-2.py: -------------------------------------------------------------------------------- 1 | # O banco Banco Delas é um banco moderno e eficiente, com vantagens exclusivas para clientes mulheres. 2 | # Modele um sistema orientado a objetos para representar contas correntes do Banco Delas seguindo os requisitos abaixo. 3 | 4 | # 1. Cada conta corrente pode ter um ou mais clientes como titular. 5 | # 2. O banco controla apenas o nome, o telefone e a renda mensal de cada cliente. 6 | # 3. A conta corrente apresenta um saldo e uma lista de operações de saques e depósitos. 7 | # Quando a cliente fizer um saque, diminuiremos o saldo da conta corrente. Quando ela 8 | # fizer um depósito, aumentaremos o saldo. 9 | # 4. Clientes mulheres possuem em suas contas um cheque especial de valor igual à sua renda 10 | # mensal, ou seja, elas podem sacar valores que deixam a sua conta com valor negativo até 11 | # -renda_mensal. 12 | # 5. Clientes homens por enquanto não têm direito a cheque especial. 13 | 14 | # Para modelar seu sistema, utilize obrigatoriamente os conceitos "classe", "herança", "propriedades", "encapsulamento" e "classe abstrata". -------------------------------------------------------------------------------- /orientacao-a-objetos/8-tipagem-pato/README.md: -------------------------------------------------------------------------------- 1 | # Aula 7 - Tipagem Pato (_Duck Typing_) 2 | 3 | * Quando inicializamos uma variável em python, podemos começar associando a ela um tipo (por exemplo, um inteiro). 4 | * Depois podemos fazer a mesma variável apontar para um objeto de outro tipo (por exemplo, uma string). 5 | ```python 6 | a = 1 7 | a = "Maria" 8 | ``` 9 | * Isso acontece porque Python é uma linguagem que possui tipagem dinâmica. 10 | * Em linguagens com tipagem dinâmica, o tipo de uma variável é determinado em tempo de execução, ou seja, durante a execução do programa. 11 | * Como as variáveis não ficam "presas" a um tipo específico de dado, quando definimos uma função, não conseguimos ter certeza de qual o tipo de dados que vai ser passado como parâmetro. 12 | * Essa forma de funcionamento é um contraste com o que acontece em linguagens com tipagem estática. 13 | * Em linguagens com tipagem estática, o tipo de uma variável é determinado em tempo de compilação, ou seja, antes da execução do programa. 14 | * Muitas linguagens de programação não funcionam assim. Por exemplo, em Java, o tipo de uma variável é definido no momento em que ela é criada e não pode ser alterado. 15 | 16 | ```python 17 | def processar(animal): 18 | animal.quack() 19 | animal.andar() 20 | animal.voar() 21 | animal.nadar() 22 | ``` 23 | * A função `processar` recebe um parâmetro chamado `animal`. No entanto, a função não tem como garantir que o animal é de algum tipo específico, por exemplo, o tipo `Pato`. 24 | * A única forma de saber se o animal faz tudo que precisamos é testar se o objeto possui todos os métodos que precisamos. 25 | * Se o objeto passado como parâmetro possuir todos os métodos que precisamos, podemos dizer que ele é um pato. 26 | * Esse conceito é chamado de **tipagem pato** (_duck typing_). 27 | ``` 28 | Se andar como um pato, fazer quack como um pato e voar como um pato, então é um pato. 29 | ``` 30 | * Ao fazermos isso, estamos testando durante a execução do programa se o objeto passado como parâmetro possui os métodos que precisamos sem saber se isso é realmente verdade. 31 | * Para nós, o que importa é que o objeto se comporte como um pato. Se ele se comporta como um pato, ou seja, se possui todos os métodos esperados do tipo 'Pato', então isso é o suficiente para dizer que ele é do tipo `Pato`, ao invés de checar o tipo diretamente. -------------------------------------------------------------------------------- /orientacao-a-objetos/8-tipagem-pato/tipagem_pato.py: -------------------------------------------------------------------------------- 1 | # Uma mesma variável em python pode ter valores de tipos diferentes 2 | a = 1 3 | a = "Maria" 4 | 5 | class Ave(): 6 | def andar(self): 7 | print('andando') 8 | 9 | def voar(self): 10 | print('voando') 11 | 12 | class Calopsita(Ave): 13 | def piar(self): 14 | print('piuuuu') 15 | 16 | class Pato(Ave): 17 | def quack(self): 18 | print('quack') 19 | 20 | def nadar(self): 21 | print('nadando') 22 | 23 | # Como as variáveis não são tipadas, não é possível saber o tipo de uma variável em uma função 24 | # A função não tem como garantir que o animal é de algum tipo específico, por exemplo, o tipo `Pato` 25 | def executar_pato(animal): 26 | animal.quack() 27 | animal.andar() 28 | animal.voar() 29 | animal.nadar() 30 | 31 | pato = Pato() 32 | calopsita = Calopsita() 33 | 34 | executar_pato(pato) 35 | executar_pato(calopsita) 36 | -------------------------------------------------------------------------------- /orientacao-a-objetos/9-erros_e_excecoes/README.md: -------------------------------------------------------------------------------- 1 | # Aula 8 - Erros e tratamento de exceções 2 | 3 | ## erros_excecoes.py 4 | * Erros em Python podem ser de dois tipos: **erros de sintaxe** ou **exceções**. 5 | **Erros de sintaxe** 6 | * Quando o interpretador encontra um erro de sintaxe, a execução do programa é interrompida imediatamente. 7 | ```python 8 | quantidade = 10000 9 | 10 | if(quantidade > 2999) 11 | print("Você tem mais de 3000 dinheiros.") 12 | ``` 13 | * No exemplo acima temos dois erros: faltam os dois pontos depois da condição do `if` e a linha abaixo dele não está identada. 14 | * Erros de sintaxe são geralmente fáceis de serem corrigidos, uma vez que você entenda o qual é o problema. 15 | * As mensagens mais comuns são `SyntaxError: invalid syntax` e `SyntaxError: invalid token` 16 | * As mensagens indicam onde no programa o erro ocorreu. Na verdade, ele diz a você onde o Python notou o problema, que não é necessariamente onde está o erro. 17 | * Algumas vezes o erro está localizado em um ponto anterior ao indicado pela mensagem de erro, frequentemente na linha anterior 18 | * Os erros de sintaxe mais comuns: 19 | * Certifique-se que você não está usando uma palavra reservada do Python (Python keyword) como nome de variável. 20 | * Verifique que você colocou um dois-pontos (`:`) no final do cabeçalho de cada comando composto (compound statement), incluindo laço `for`, laço `while`, comandos de seleção `if`, `elif` e `else` e declaração de funções `def`. 21 | * Verifique se a tabulação é consistente. Você pode tabular com espaços ou tabs mas não é aconselhável misturá-los. Cada nível deve ter a mesma quantidade de espaços ou tabs. 22 | * Verifique seas aspas ou apóstrofes de qualquer string no código estão emparelhados. 23 | * Uma falta de fecha parêntese, chave ou colchete – `(`, `{` ou `[` – faz o Python continuar com a próxima linha como parte da expressão corrente. Geralmente, um erro ocorre quase que imediatamente na próxima linha. 24 | * Verifique pelo clássico `=` em vez de `==` em uma condição. 25 | 26 | **Exceções** 27 | * Quando uma exceção acontece, o programa pode ser interrompido ou não dependendo de como ela é _tratada_. 28 | * Quando acontece algum erro que não é relacionado a sintaxe da linguagem, mas sim a lógica da execução, esse erro é chamado de exceção. 29 | * Exceções acontecem em tempo de execução, ou seja, durante a execução de uma linha do programa. 30 | * Por exemplo, na aula anterior vimos como funciona a tipagem pato. 31 | * Se o objeto passado como parâmetro não possuir todos os métodos que precisamos, o programa vai lançar uma exceção. 32 | * Quando a exceção não é tratada, a execução do seu programa é interrompida. 33 | * É possível tornar o seu programa mais robusto através do tratamento de exceções. Ou seja, podemos deixar indicar no nosso código o que fazer quando uma exceção acontecer para evitar que a execução seja interrompida. 34 | * Para isso, utilizamos as palavras chaves `try`, `catch` e `finally`. 35 | * Colocamos o código que pode lançar exceções dentro do bloco `try`. 36 | * Utilizamos o bloco `catch` para capturar e tratar qualquer exceção que possa vir a ser lançada pelo código no bloco `try`. 37 | * Podemos adicionar um bloco `else` com código que só vai ser executado se nenhuma exceção acontecer 38 | * Se existe alguma coisa que precisa ser executada indepedendo ou não do erro acontecer, podemos utilizar opcionalmente o bloco `finally`. 39 | ```python 40 | try: 41 | # Algum código.... 42 | except: 43 | # Bloco opcional para lidar com uma exceção (se necessário) 44 | else: 45 | # Código para ser executado se não ocorrer nenhuma exceção no try 46 | finally: 47 | # Código para sempre ser executado independente de acontecer 48 | # alguma exceção ou não 49 | ``` 50 | * O Python possui diversos tipos de exceções que podem ser encontrados na [documentação oficial](https://docs.python.org/pt-br/3.7/library/exceptions.html#concrete-exceptions). 51 | * Podemos lançar nossas próprias exceções no código para indicar erros de execução utilizando a palavra-chave `raise`. A mensagem do erro pode ser personalizada. 52 | * É possível criar seus próprios tipos de exceções. 53 | 54 | ## debug.py 55 | * O primeiro passo em depurar (_debugging_), ou seja, investigar e eliminar erros de programação, é identificar que tipo de erro você cometeu. 56 | * A forma mais básica de debugar é utilizando o `print` para mostrar até que parte do código rodou e quais eram os valores das variáveis. 57 | * Geralmente, os ambientes de desenvolvimento oferecem suas próprias ferramentas de debug. A outra forma de debugar é utilizando a ferramenta de debug do VSCode. 58 | * Exemplo de código: considere que você trabalha para um banco e está criando um aplicativo que monitore o valor do saldo da conta de um cliente. Este cliente recebe o salário em sua conta e o banco precisa processar o valor bruto para reter o valor referente ao imposto de renda. Depois, se o cliente quiser sacar, o banco precisa controlar o valor do saque para não permitir que o cliente gaste mais do que o valor que existe em sua conta. Seu programa usa o módulo `conta_bancaria`, que contém as seguintes funções: 59 | * `imprimir_no_log` para mostrar mensagens coloridas 60 | * `calcular_inss` e `calcular_imposto_de_renda` para que calcular os valores de impostos 61 | * `processar_salario`, que deve rodar apenas da primeira vez que o usuário tentar sacar o valor para retenção dos valores pagos na fonte 62 | * `sacar_na_conta`, que faz o saque e retorna um boolean indicando se a operação funcionou corretamente. 63 | * O módulo também contém variáveis que indicam o saldo inicial da conta, o valor do salário bruto e o estado do processamento do salário. 64 | * Erros no código: 65 | 1. faltou colocar o namespace "conta_bancaria" para chamar a função "sacar na conta" 66 | 2. Função "sacar_na_conta": variável 'valor_na_conta' é local - não disse para a função que estava usando a variável global 67 | 3. Mesmo erro na funcao 'processar_salario' 68 | 4. Expressão do 'processar_salario' usando o valor errado 69 | 5. `if valor_na_conta <= valor:` -> lógica invertida 70 | 6. Faltou atualizar a variável `salario_processado` que está sendo repetida 71 | -------------------------------------------------------------------------------- /orientacao-a-objetos/9-erros_e_excecoes/conta_bancaria.py: -------------------------------------------------------------------------------- 1 | import colorama 2 | colorama.init() 3 | 4 | salario_bruto = 5000 5 | valor_na_conta = -1 6 | salario_processado = False 7 | 8 | def imprimir_no_log(texto, nivel='info'): 9 | if nivel.lower() == 'info': 10 | print(colorama.Fore.LIGHTBLUE_EX + f'Info: ', end='') 11 | print(colorama.Style.RESET_ALL + texto) 12 | elif nivel.lower() == 'aviso': 13 | print(colorama.Fore.YELLOW + f'Aviso: ', end='') 14 | print(colorama.Style.RESET_ALL + texto) 15 | elif nivel.lower() == 'erro': 16 | raise ValueError(colorama.Fore.RED + 'Erro: ' + colorama.Style.RESET_ALL + texto) 17 | 18 | def calcular_imposto_de_renda(valor): 19 | taxa = 0.27 20 | valor_imposto = taxa * valor 21 | return valor_imposto 22 | 23 | def calcular_inss(valor): 24 | taxa = 0.14 25 | valor_imposto = taxa * valor 26 | return valor_imposto 27 | 28 | def processar_salario(): 29 | valor_irpf = calcular_imposto_de_renda(salario_bruto) 30 | valor_inss = calcular_inss(salario_bruto) 31 | valor_na_conta = valor_na_conta - valor_irpf - valor_inss 32 | 33 | def sacar_na_conta(valor): 34 | if not salario_processado: 35 | imprimir_no_log(f'Processando o salario bruto antes de permitir o saque...', 'aviso') 36 | processar_salario() 37 | imprimir_no_log(f'O novo valor do salário na conta é {valor_na_conta}...', 'info') 38 | 39 | if valor_na_conta <= valor: 40 | valor_na_conta -= valor 41 | imprimir_no_log(f'Sacando {valor}. O novo saldo na conta é de {valor_na_conta}.', 'info') 42 | else: 43 | imprimir_no_log(f'A conta não possui saldo para sacar o valor de {valor}.', 'erro') 44 | return False 45 | 46 | return True -------------------------------------------------------------------------------- /orientacao-a-objetos/9-erros_e_excecoes/debug.py: -------------------------------------------------------------------------------- 1 | import conta_bancaria 2 | import colorama 3 | 4 | colorama.init() 5 | 6 | print('Seu salário bruto é de {}'.format(conta_bancaria.salario_bruto)) 7 | valor_a_sacar = input('Quanto você deseja retirar da sua conta? ') 8 | funcionou = sacar_na_conta(float(valor_a_sacar)) 9 | 10 | # Sacando novamente 11 | valor_a_sacar = input('Quanto você deseja retirar da sua conta? ') 12 | funcionou = sacar_na_conta(float(valor_a_sacar)) -------------------------------------------------------------------------------- /orientacao-a-objetos/9-erros_e_excecoes/erros_excecoes.py: -------------------------------------------------------------------------------- 1 | # Erros de sintaxe sempre interrompem a execução do programa. 2 | quantidade = 10000 3 | print('quantidade:', quantidade) 4 | 5 | # if(quantidade = 3000) 6 | # print("Você tem 3000 dinheiros.") 7 | 8 | # Exceções são lançadas por erros de lógica 9 | # quantidade = quantidade / 0 10 | 11 | # Tratamento de exceções 12 | print('Tratamento de exceções - except genérico') 13 | class Ave(): 14 | def andar(self): 15 | print('andando') 16 | 17 | def voar(self): 18 | print('voando') 19 | 20 | class Calopsita(Ave): 21 | def piar(self): 22 | print('piuuuu') 23 | 24 | class Pato(Ave): 25 | def quack(self): 26 | print('quack') 27 | 28 | def nadar(self): 29 | print('nadando') 30 | 31 | def executar_pato(animal): 32 | try: 33 | animal.quack() 34 | animal.andar() 35 | animal.voar() 36 | animal.nadar() 37 | except AttributeError as e: 38 | print(f'Erro de atributo: {e}') 39 | 40 | pato = Pato() 41 | calopsita = Calopsita() 42 | 43 | executar_pato(pato) 44 | executar_pato(calopsita) 45 | 46 | print('Continuando a execução...') -------------------------------------------------------------------------------- /orientacao-a-objetos/9-erros_e_excecoes/lista-de-exercicios-3.py: -------------------------------------------------------------------------------- 1 | ## O programa abaixo deve calcular a média dos valores digitados pelo usuário. 2 | ## No entanto, ele não está funcionando bem. Você pode consertá-lo? 3 | 4 | def calcular_media(valores): 5 | tamanho = 1 6 | soma = 0.0 7 | for i, valor in enumerate(valores): 8 | soma += valor 9 | i += 1 10 | media = soma / tamanho 11 | 12 | continuar = True 13 | valores = [] 14 | while continuar: 15 | valor = input('Digite um número para entrar na sua média ou "ok" para calcular o valor:') 16 | if valor.lower() == 'ok': 17 | continuar = false 18 | 19 | media = calcular_media(valores) 20 | print('A média calculada para os valores {} foi de {}'.format(valores, media)) -------------------------------------------------------------------------------- /orientacao-a-objetos/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Guia de Conteúdo 3 | 4 | | Módulo | Aula | Tópicos | 5 | | -------------------------------- | ---- | ------------------------------------------------------------ | 6 | | Orientação a Objetos com Python | 1 | Escopos e _Namespaces_ | 7 | | Orientação a Objetos com Python | 2 | Classes e Objetos | 8 | | Orientação a Objetos com Python | 3 | Entendendo o contexto de Orientação a objetos | 9 | | Orientação a Objetos com Python | 4 | Construtores e destrutores | 10 | | Orientação a Objetos com Python | 5 | Atributos de visibilidade e encapsulamento encapsulamento | 11 | | Orientação a Objetos com Python | 6 | Herança, herança múltipla (_mixins_) | 12 | | Orientação a Objetos com Python | 7 | Classes abstratas e a biblioteca ABC | 13 | | Orientação a Objetos com Python | 8 | Tipagem Pato | 14 | | Orientação a Objetos com Python | 9 | Lidando com erros e exceções | 15 | 16 | 17 | ## Referências 18 | * [Microsoft Shows - Python for Beginners](https://docs.microsoft.com/pt-br/shows/intro-to-python-development/) 19 | * [Microsoft Learn](https://docs.microsoft.com/pt-br/learn/) 20 | * [Getting Started with Python - GitHub](https://github.com/microsoft/c9-python-getting-started) 21 | * [Como pensar como um cientista da computação - IME/USP](https://panda.ime.usp.br/pensepy/static/pensepy/index.html) 22 | * [Geek for Geeks - Python Programming Language](https://www.geeksforgeeks.org/python-programming-language/) 23 | * [Python para Análise de Dados - Editora Novatec - Wes McKinney](https://leitura.com.br/python-para-analise-de-dados-L006-9788575226476) 24 | * [CS50 2021 in Harvard - Lecture 6 - Python](https://youtu.be/ky-24RvI57s) 25 | * [Introdução à Programação com Python - Editora Novatec - Nilo Ney Coutinho Menezes](https://www.amazon.com.br/Introdu%C3%A7%C3%A3o-Programa%C3%A7%C3%A3o-com-Python-Algoritmos/dp/8575227181/) 26 | * [Python Examples](https://pythonexamples.org/) --------------------------------------------------------------------------------