Python de nível seguinte: Torne-se um especialista em Python | ArjanCodes | Skillshare
Pesquisar

Velocidade de reprodução


1.0x


  • 0.5x
  • 0.75x
  • 1x (Normal)
  • 1.25x
  • 1.5x
  • 1.75x
  • 2x

Python de nível seguinte: Torne-se um especialista em Python

teacher avatar ArjanCodes, Become a Better Software Developer

Assista a este curso e milhares de outros

Tenha acesso ilimitado a todos os cursos
Oferecidos por líderes do setor e profissionais do mercado
Os temas incluem ilustração, design, fotografia e muito mais

Assista a este curso e milhares de outros

Tenha acesso ilimitado a todos os cursos
Oferecidos por líderes do setor e profissionais do mercado
Os temas incluem ilustração, design, fotografia e muito mais

Aulas neste curso

    • 1.

      Visão geral do curso: o que você vai aprender

      1:02

    • 2.

      Como diferentes idiomas de programação se relacionam com tipos

      7:25

    • 3.

      Digite anotações no Python: básico ao avançado

      4:21

    • 4.

      Digitação de pato com cursos de protocolo

      5:28

    • 5.

      Como o Dataclasses ajuda você a escrever cursos melhores

      10:56

    • 6.

      Desbloquear o poder das propriedades do curso de inglês

      4:44

    • 7.

      Qual é a diferença entre str e repr?

      2:33

    • 8.

      Como melhorar o desempenho de membros do curso de acesso para os cursos

      3:17

    • 9.

      Callables e funções de ordem mais alta

      5:29

    • 10.

      Aplicação de função parcial

      3:10

    • 11.

      Propriedades em cache

      2:01

    • 12.

      Envio único

      2:58

    • 13.

      O que é programação simultânea?

      4:07

    • 14.

      Async, espera e se reúne

      5:21

    • 15.

      Como transformar código não concorrente em código concorrente em simultâneo

      3:46

    • 16.

      Iteradores e Iterables: os conceitos básicos

      5:44

    • 17.

      Usando Iteradores para introduzir abstração

      1:20

    • 18.

      Itertools: uma álgebra de alternância de nível superior

      7:27

    • 19.

      Geradores também conhecidos como Interatores preguiçosos

      1:39

    • 20.

      Funções de gerador, rendimento e retorno

      2:18

    • 21.

      Expressões de gerador

      1:42

    • 22.

      Geradores e concorrência

      3:39

    • 23.

      O que é um gerente de contexto?

      1:32

    • 24.

      Curso de gerenciamento de contexto

      2:33

    • 25.

      Decorador de gerenciador de contexto (muito mais fácil!)

      1:10

    • 26.

      Gestores de contexto e Concurrency

      1:58

    • 27.

      Resumo

      0:30

  • --
  • Nível iniciante
  • Nível intermediário
  • Nível avançado
  • Todos os níveis

Gerado pela comunidade

O nível é determinado pela opinião da maioria dos estudantes que avaliaram este curso. Mostramos a recomendação do professor até que sejam coletadas as respostas de pelo menos 5 estudantes.

718

Estudantes

2

Projetos

Sobre este curso

Você quer expandir seu conhecimento do Python e levar suas habilidades de codificação para o próximo nível? Se sim, este curso é para você! Este curso vai te dar uma compreensão mais profunda do Python e permitirá que você escreva um código melhor muito mais rápido.

Este curso é curado e contém os tópicos que eu acho que será o mais benéfico para você se quiser se tornar mais qualificado no Python.

É isso que você vai aprender:

  • Quais são as anotações de tipo e por que é importante que você as use
  • Uso avançado de cursos, incluindo dataclasses, propriedades e muito mais
  • Programação funcional avançada usando funções da Lambda, aplicação de função parcial e muito mais
  • Técnicas de programação simultâneas para otimizar como seu código interage com APIs
  • Lógica de controle avançada usando iteradores e geradores
  • E muito mais!

Quer você esteja usando principalmente o Python para processar e analisar dados ou esteja usando o Python para criar aplicativos de software complexos como APIs, você vai realmente desfrutar deste curso e se tornar um verdadeiro especialista em Python.

Então, se você estiver pronto — vamos mergulhar no!

Conheça seu professor

Teacher Profile Image

ArjanCodes

Become a Better Software Developer

Professor

Hi there!

I'm Arjan - I'm a YouTuber, entrepreneur and online teacher. I've completed both a Master and PhD in Computer Science and I've been a university teacher for over 20 years, I've launched several and designed and built complete software products from scratch.

On my YouTube channel ArjanCodes I teach software design, development and testing - specifically in Python. A lot of people are learning how to code in Python. They often start with writi... Visualizar o perfil completo

Level: Intermediate

Nota do curso

As expectativas foram atingidas?
    Superou!
  • 0%
  • Sim
  • 0%
  • Um pouco
  • 0%
  • Não
  • 0%

Por que fazer parte da Skillshare?

Faça cursos premiados Skillshare Original

Cada curso possui aulas curtas e projetos práticos

Sua assinatura apoia os professores da Skillshare

Aprenda em qualquer lugar

Faça cursos em qualquer lugar com o aplicativo da Skillshare. Assista no avião, no metrô ou em qualquer lugar que funcione melhor para você, por streaming ou download.

Transcrições

1. Apresentação: Olá, desculpe, sou bem-vindo a este curso que ensina programação Python intermediária a avançada. Este é um curso com curadoria. Não é uma lista completa de todos os recursos avançados possíveis que estão disponíveis no Python. Ele contém as coisas que eu acho que serão mais úteis para você se você quiser dar o próximo passo em sua carreira de desenvolvimento de software. Este curso é para você, se você tem experiência em Python, mas gostaria de aprender um pouco mais sobre os recursos avançados que a linguagem oferece. Então, vou falar sobre coisas como dicas de tipo, código simultâneo , classes de protocolo, iteradores, geradores e algumas outras coisas. Você pode encontrar todos os exemplos de código usados no curso, na guia Projetos e Recursos. E também há um pequeno projeto divertido para você mergulhar no final. Se você gosta deste curso, talvez também goste do meu canal no YouTube ou dos códigos de barra de ferro do youtube.com para jovens potros . Lá eu falei principalmente sobre design de software em vários modos de conteúdo em Python. Então dê uma olhada. Espero que você tenha gostado do curso , pois está pronto. Vamos mergulhar. 2. Anotações de tipo 1/3: Vou começar este curso falando sobre sistemas de tipos. Muitas linguagens de programação têm tipos embutidos em sua sintaxe e há maneiras diferentes de ver os sistemas de tipos. E eu quero falar sobre alguns deles hoje e como isso se relaciona com a forma como os tipos são tratados em Python. Portanto, a primeira é que temos sistemas do tipo estático versus dinâmico. E essa distinção é sobre quando o tipo de informação é adquirido, o que aconteceria estaticamente? Isso significa que isso acontece em tempo de compilação ou interpretação quando é dinâmico, isso significa que os tipos são determinados em tempo de execução. Outra distinção da qual você já deve ter ouvido falar é a digitação forte versus a fraca. E isso é mais sobre o quão rigoroso é amarrar de forma consistente. Então, por exemplo , ele converte automaticamente números inteiros em strings ou vice-versa? Não há uma definição clara do que exatamente é um sistema de digitação forte versus um sistema de digitação fraco. É por isso que geralmente procuramos outras maneiras de distinguir sistemas de tipos sobre as quais falarei em um minuto. Cada linguagem de programação aborda os tipos de forma diferente. Por exemplo, Java é tipado estaticamente. Isso significa que, se você declarar uma variável do tipo string e uma string, e então tentar atribuir um número a ela. Isso não é permitido. E a razão pela qual isso não é permitido é porque definimos em tempo de compilação que a variável será do tipo string, então não podemos mais mudar isso. O Python, por outro lado, é um duto dinâmico, então não há problema em definir uma variável e atribuir a ela um valor de string. E, posteriormente, atribuímos ele um valor de um tipo diferente. Basicamente, o tipo não está associado à variável , está associado ao valor i. programa Python muito simples aqui. Então, se eu definir uma variável x e atribuir uma string, e então porque o Python é digitado dinamicamente, posso simplesmente atribuir a ela um valor de tipos diferentes, neste caso um número inteiro, e tudo bem. Não tem problema algum. Em Java, isso, é claro, resultaria em erro. Agora, embora o Python seja digitado dinamicamente, ele também é bastante rígido. Na verdade, você poderia chamá-lo de fortemente digitado se tivéssemos uma definição para isso. Então, por exemplo o que eu posso fazer é se tivermos a mesma variável X, podemos fazer olá mais cinco. Porque se eu tentar executar isso, você verá que receberá um erro de que, ei, só podemos concatenar cadeias de caracteres e não números inteiros. Isso significa que o Python não converte automaticamente esse número cinco em uma string cinco. Claro, eu posso fazer isso explicitamente. Então, quando eu executo o programa novamente, agora você vê que isso não vai resultar em problemas porque, ao desfazer a conversão de tipo sozinho, isso é diferente de uma linguagem como JavaScript, que permite que você faça olá mais cinco. E então eu simplesmente vou converter automaticamente o número em uma string. E também faz sentido do ponto de vista do JavaScript, porque o JavaScript é, obviamente, a linguagem da web. E queremos garantir que o cliente sempre mostre pelo menos algo razoável. Portanto, o JavaScript faz o possível para fazer o que pode com os valores que obtém. Nem sempre funciona, mas isso significa que, mesmo que uma página da web tenha alguns erros, ela ainda pode mostrar algo significativo. Quais são algumas outras maneiras de analisar os sistemas de tipos enquanto um é manifesto versus inferido manifesto significa que você precisa especificar explicitamente o tipo de variável, por exemplo C ou Java tem tipagem de manifesto, precisamos especificar qual é o tipo. Inferido. Digitar significa que o tipo da variável será inferido do valor. E é exatamente isso que está acontecendo em Python. Portanto, em Python, não precisamos indicar que, nesse caso, x é uma string e não outra coisa. No entanto, outra forma de ver os sistemas de tipos é nominal versus estrutural. Nominal significa que, para comparar se os tipos correspondem, o sistema de tipos está examinando completamente os nomes. Então, se temos coisas que são cadeias de caracteres, ambas são do mesmo tipo. Se tivermos duas coisas da mesma classe ou classes, digite também, então os tipos correspondem. Isso é nominal. Estrutural significa que vamos examinar a estrutura dos objetos para comparar se esses tipos coincidem. Então, por exemplo, se temos um objeto que tem uma propriedade x, então os tipos coincidem com outro objeto se o objeto também tiver a mesma propriedade x. Então, realmente observando a estrutura do objeto versus o nome do objeto. Então, nesse caso, o que o Python tem? Bem, não é realmente uma tipagem estrutural, mas é um pouco semelhante. É algo chamado digitação de pato. Digitar com pato é como uma estrutura reforçada, mas não é exatamente a mesma coisa. tipagem estrutural é um sistema de tipo estático que compara as estruturas dos objetos quando você compila o código. Doctype é dinâmico, mas só analisa as partes que importam. Portanto, temos uma certa parte do objeto que você está acessando em tempo de execução. Digamos que você tenha um objeto com uma propriedade x e uma propriedade por quê? E em tempo de execução, você só está acessando a propriedade X do que os tipos correspondem se o objeto tiver essa propriedade e não examinarmos a propriedade. Por que, mesmo que possa ser parte do tipo estático, digitar pato é, na verdade, o que o Python está usando. Então, ele simplesmente analisa as peças que são necessárias durante o tempo de execução. Combine. Python está pronto para usar. Eu tenho um exemplo aqui que mostra como a digitação de pato funciona. Então, eu tenho algumas variáveis aqui, minha string ou lista, meu dicionário. E embora cada um tenha alguns valores, você vê que temos um comprimento de função que pode imprimir o comprimento dessas coisas. Portanto, esta não é uma nota 11 porque tem 11 caracteres. Isso se chama quatro porque há quatro elementos na lista e isso imprimirá três. E a forma como isso funciona é que a função de comprimento espera um objeto. Você já pode vê-lo aqui do tamanho do tipo. E isso significa que esse objeto precisa ter métodos de Lynn Dunder associados a ele. E esse é o caso das sequências de caracteres, das listas, dos dicionários. Então, se eu executar isso, você verá que não há problema em executar esse código e simplesmente imprimirá o que eu queria imprimir. Então, o que acontece é que dicionários, listas e cadeias de caracteres. Claro que são tipos de objetos muito diferentes, certo? Mas a digitação de pato os aceita como parte dos argumentos da função length porque a única coisa que é necessária aqui ou que é acessada são os métodos length dunder. Portanto, desde que essa seja a digitação do pato deles e o Python esteja pronto e aceite isso, mesmo que esse tipo não corresponda estruturalmente exatamente porque uma string não é uma lista e a lista não é o dicionário. Mas em um sistema de digitação de pato funciona sem problemas. Você pode até mesmo dar esse passo adiante, por exemplo , aqui, se o livro de aula que não tem inicializadores é um autor, título e páginas. E eu mesmo defini aqui o método de tamanho, e isso só retorna o número de páginas. E então eu imprimo chamando a função de comprimento em um objeto de livro. Isso é o que você está vendo aqui. E aqui você vê que quando eu executo isso, você obtém isso como resultado. Então, neste caso, funciona exatamente da mesma forma. A única coisa que a função de comprimento precisa é de um objeto do tamanho do tipo. E isso significa que deveria ter um método de doação enxuta, que essa classe representa pela metade. Então, esse código é executado sem problemas, porque é assim que a digitação com pato funciona. 3. Anotações de tipo 2/3: Com o Python, você não precisa especificar explicitamente nenhum tipo ao escrever uma meta. entanto, é uma boa ideia fazer isso, porque isso ajudará você a tornar seu código mais fácil de ler. E, na verdade, é muito fácil fazer isso. Por exemplo, aqui temos uma string que é HelloWorld, mas você pode realmente indicar usando dicas de tipo ou um tipo de notação qual é o tipo dessa variável específica. E você simplesmente faz isso escrevendo uma coluna e, em seguida, digita o tipo, você digita o tipo aqui. Nós podemos fazer a mesma coisa. Portanto, esta é uma lista de números inteiros. Então, vou escrever uma lista de números inteiros. E se você tiver um tipo genérico como esse, poderá usar os colchetes para digitar com o botão direito do mouse atrás dele. E para o dicionário, isso é muito parecido. Então, temos um ditado e o primeiro, o tipo de chave é uma string, certo? Temos 12,3, essas são cadeias de caracteres e o que isso aponta para o valor é um número inteiro. , aqui temos o tipo de dicionário propósito, aqui temos o tipo de dicionário para usar esse tipo de tipo. E eu vou dizer que você precisa do Python 3.9 ou mais recente. Caso contrário, você terá que escrever digitando import, dict com uma letra maiúscula e, em seguida, poderá usar isso. Mas se você usa uma versão recente do Python , pode simplesmente escrever isso. É muito mais rápido. Então, é assim que você especifica os tipos. É claro que a aula também é um tipo. Eu posso escrever algo assim, ser um subtipo de livro. E isso vai ser um livro. E então podemos passar alguns valores para ele. Algo parecido com isso. Então, é assim que você adiciona dicas de tipo ao declarar variáveis. Agora, isso não é muito útil porque quando você olha para a linha de código, você já pode ver que é um livro. Então, por que deveríamos escrever isso, certo? Não é realmente necessário, mas isso é realmente útil se você tiver funções ou métodos e quiser especificar dicas de tipo para argumentos. Isso é realmente o que você está vendo aqui. Então, vemos que temos um autor do tipo string. Temos um título do tipo string e páginas do tipo inteiro. Isso é muito útil porque, quando chamamos o inicializador do livro aqui, você pode ver que estou usando o código VS nesse caso. Então, eu já estou recebendo as informações de tipo no cabeçalho. Agora eu sei exatamente o que preciso oferecer. Esse autor deveria ser uma string e vital também deveria ser uma string. E páginas e um interior. Você verá isso como um tipo de devolução aqui. Portanto, o inicializador do livro não retorna nada, e isso está correto. Não há declaração de devolução aqui. Na verdade, você também pode escrever isso explicitamente, se quiser, assim. Portanto, ele retorna nenhum e comprimento. Se você observar isso, ele retornará um número inteiro. Portanto, também podemos especificar isso explicitamente. Uma coisa que você pode se perguntar é o que acontece se especificarmos o tipo errado, por exemplo, aqui temos uma variável do tipo string, certo? E atribuímos a string, então está tudo correto. Mas o que acontece se eu transformar isso em um número inteiro, por exemplo, então isso claramente não é um número inteiro, certo? Então, se executarmos isso, você verá que o código realmente é executado sem problemas, mesmo que o tipo esteja completamente errado. E é por isso que o Python não analisa esses tipos quando está realmente executando o programa. Na verdade, eles estão lá apenas para nos ajudar, como desenvolvedores, a entender onde estão os problemas em nosso programa Você vê que nem mesmo recebemos um erro aqui e é aí que as configurações do VS Code podem nos ajudar a especificar o quão rigoroso queremos que nosso sistema de verificação de tipo em nosso IDE seja aberto. Agora, nas configurações do espaço de trabalho no VS Code e nos produtos lácteos, há um molde de verificação do tipo de ponto de análise de pontos em Python que está desativado. Então, o que você pode fazer é alterar esse valor para determinar o quão rigorosa deve ser a verificação de tipos. E há basicamente três opções. Existe o Off básico e o mais rigoroso. Então, deixe-me mudar isso para estrito. Assim. Agora vamos voltar ao meu arquivo e ousar ver, ei, agora estamos tendo um problema. Veja, minha string é um número inteiro, mas estamos atribuindo a ela uma string. E você também vê que recebemos um erro de incompatibilidade fornecido pelos pilotos, que é a superfície da linguagem Python do VSCode. Então, deixe-me mudar isso de volta para uma string ou você, aliás, você também a verá aqui. Essa é a minha string é um número inteiro. Vejo que aqui também temos um problema que int pode ser atribuído porque não está fora do tamanho de um tipo e não tem um método de comprimento dunder. Então, vamos mudar isso de volta para string e então veremos se o problema foi resolvido. 4. Digitação de pato com cursos de protocolo: Um tipo bastante específico de Python o qual eu quero falar com você, e essa é a classe de protocolo. Então, vimos no outro exemplo que podemos ter classe. E a aula é, obviamente, um tipo. Mas o que acontece com frequência é que você está usando classes e objetos de um tipo específico em todo lugar do seu código, certo? Então, aqui eu tenho um exemplo disso em que temos uma classe chamada clientes de e-mail ou digamos que este seja um sistema de envio de e-mail. E você vê que o que está acontecendo aqui é que temos o inicializador que obtém um monte de informações, como logins , senhas, etc. Mas dentro do inicializador, criamos um servidor SMTP. Em seguida, tentamos obter o host e a porta do objeto do servidor SMTP que acabamos de criar. E então estamos fazendo algumas coisas, como verificar o nome de usuário e a senha e armazenar esses dois endereços e assim por diante. E então temos alguns métodos como conectar-se ao servidor, sair do servidor e enviar uma mensagem, enviar um e-mail por meio desse surfista com algumas coisas úteis como adicionar sistematicamente os dois endereços e coisas assim. Portanto, temos muitos tipos aqui. Você vê que temos aqui os tipos de devolução que, em sua maioria, não são. Também estamos usando outros tipos de anotações de tipo aqui, por exemplo, isso significa que o login pode ser uma string ou pode ser conhecido. Portanto, isso também é chamado de sintaxe do tipo de união. Isso significa que você pode combinar vários tipos e, para um padrão do tipo or, atribuímos a ele o valor none. Você também vê que confiamos aqui no tipo SMTP, que vem da biblioteca SMTP, certo? E isso não é um problema por si só, mas significa que não há dependência entre seus clientes de e-mail e uma classe SMTP muito específica. Se você quisesse usar uma biblioteca para isso, teria que entrar na implementação da classe e corrigir as referências ao SMTP e, em seguida, remover essa importação. Outra forma de fazer isso é usando protocolos. E com o protocolo, o que você faz é uma extensão muito boa do mecanismo de digitação de pato. Em Python com protocolo, você especifica qual será a estrutura do objeto. Então essa é a parte estrutural da digitação de pato e o que você espera. E então o sistema de digitação de patos do Python vai igualar isso em tempo de execução. Então, eu tenho outro exemplo aqui em eu usei esse mecanismo. E essa é exatamente a mesma classe. Então, temos novamente aqui o cliente de e-mail, mas agora você vê que há outra classe escrita acima dele chamada Onde estamos? Servidor de e-mail? Um servidor de e-mail é uma classe de protocolo. Você vê que importamos isso da digitação. E então há algumas propriedades e métodos aqui que eu defini. Então, há o host e as setas para conectar os métodos. Portanto, esses são os métodos padrão que também estão na essência da classe de servidor B. Mas agora o que fazemos é passar para os clientes de e-mail uma instância desse servidor SMTP e, em seguida, simplesmente usá-la. E o que é bom é que, como agora simplesmente fornecemos o protocolo como um tipo, podemos usar o servidor SMTP da mesma forma que fizemos antes. Simplesmente precisamos criá-lo fora do cliente de e-mail. propósito, isso se chama injeção de dependência. E se quisermos substituí-lo por outra coisa, por exemplo, queremos usar outro servidor SMTP ou talvez queiramos usar um servidor SMTP falso para que possamos escrever alguns testes de software para essa classe. Bem, então podemos simplesmente fornecer isso. E desde que a classe que fornecemos, esse objeto que fornecerá adira a esse protocolo. Portanto, desde que tenha esses métodos e o doctype corresponda a eles , podemos usá-lo em combinação com o cliente de e-mail. E também perdemos a importação muito específica aqui da biblioteca SMTP porque agora estamos simplesmente confiando em um protocolo. Portanto, o protocolo permite que você introduza alguma abstração em seu código, para que você não precise se preocupar muito em ter muitos e muitos acoplamentos, muitas e muitas dependências diretas em bibliotecas de baixo nível, calças justas, como acabei de mostrar algo muito útil para aumentar a legibilidade do seu código, que pode ser útil se você trabalhar em a legibilidade do seu código, que pode ser equipe nesse código ou se você escrever o código e, alguns meses depois voltar a ele e tentar descobrir o que está acontecendo e o que você deve passar como argumento para uma função ou algo assim. Dicas de digitação são muito úteis para esclarecer isso e garantir que você cometa menos erros como desenvolvedor. Por exemplo, aqui está uma função compute stats que recebe alguns argumentos. Na verdade, não temos ideia de que, se você observar a disfunção tratada, o que devemos fazer com isso, existem usuários, plantas e produtos, mas o que são essas coisas? Eles são dicionários ou listas? É uma célula para um número? Nós simplesmente não sabemos. Se você fornecer dicas de tipo. Isso é muito mais fácil de ver. Podemos ver imediatamente o que precisamos fornecer e o verificador de tipo embutido nos postes do VSCode. Mas outros editores também têm um verificador de tipos como esse. Isso ajuda você a entender onde estão as setas e resolvê-las antes mesmo de precisar executar seu programa para descobrir. Portanto, os tipos ajudam na legibilidade do seu código e também ajudam a detectar erros no início do processo de desenvolvimento As mãos digitadas pelos indianos economizarão muito tempo. Portanto, eu recomendo fortemente que você comece a usá-los mais, se ainda não tiver feito isso. Então, isso conclui esta lição sobre dicas de digitação. Na próxima lição, vou me aprofundar nas aulas e mostrar algumas coisas muito poderosas que você pode fazer com elas em Python. 5. Aulas de nível seguinte 1/4: Nesta lição, falarei sobre classes e mostrarei algumas coisas diferentes que você pode fazer com elas em Python. Então, eu tenho um exemplo muito básico aqui. É uma pessoa de classe, nada de especial. copo quase vazio tem apenas um inicializador e recebe um nome e um endereço. Como você pode ver, são cordas. Estou usando dicas de digitação aqui. Eu provavelmente deveria acrescentar, já que o inicializador não retorna nada. E então eu estou usando uma função de geração de ID. Você pode ver isso funcional aqui para gerar algum tipo de ID. Então você pode imaginar que isso é como um banco de dados simplificado. E então temos um nome e endereço, e também temos uma lista, uma lista vazia de endereços de e-mail. Então, esta é a classe individual, Básica. E então eu tenho a função principal em que eu crio uma pessoa com um nome e um endereço, e então imprimo essa pessoa. Então, quando eu executar esse código, é isso que vamos obter resultados. Então, estou imprimindo um objeto pessoal. Você pode ver isso aqui. Agora, essa informação não é muito útil, certo? Porque simplesmente obtemos algum endereço de memória. Não temos ideia do que realmente está contido no objeto Person seria bom se pudéssemos realmente ver isso. E, claro, isso é possível em Python. A principal forma de fazer isso é adicionando um método STR ou wrapper dunder. Vou falar mais sobre isso em um minuto. Mas, no geral, há uma maneira diferente de definir classes em Python. No geral, isso é um pouco mais simples. Eu me sinto um pouco mais fácil de ler, usando classes de dados. Mas você poderia dizer que as classes de dados são realmente mais orientadas para classes orientadas a dados, certo? Pontos e vetores de dados estruturados e coisas do tipo. Em vez de um botão ou uma superfície de pavimento ou mais outra classe orientada ao comportamento. Mas ele adiciona muitos mecanismos convenientes, como uma maneira muito mais fácil de definir atributos. Você pode compará-los com muito mais facilidade. Você pode imprimi-los com muito mais facilidade. Então, essas coisas são úteis para quase qualquer tipo de aula. Como transformar pessoa em classe de dados? Na verdade, isso é muito simples. Então, o que vou fazer é, a partir das classes de dados, importar a classe de dados, que é o tipo que vamos usar aqui. E então, em vez de definir uma pessoa dessa forma, vamos escrever uma classe de dados acima dela. E agora o que posso fazer é especificar as variáveis da instância aqui. Então, temos um nome, que é uma string, e temos um endereço, que também é primavera. E teremos uma lista de endereços de e-mail. Essa é uma lista de sequências, certo? E a identidade. Nós também podemos fazer, eu vou te mostrar como fazer isso em um minuto. Mas se eu remover isso, agora você pode ver que é muito mais fácil entender o que uma pessoa realmente é porque podemos simplesmente olhar o topo e ver imediatamente nomes das variáveis de instância e seus tipos. E é por isso que as classes de dados são realmente úteis. E eu voltarei a isso em um minuto. Deixe-me comentar isso. Mas agora, se eu criar um pessoalmente dessa forma, isso acontece exatamente da mesma maneira. Portanto, as classes de dados realmente geram um inicializador com base nos valores dessas variáveis de instância. Quando eu executo isso, você pode ver que agora imprime a pessoa. Mas você também vê que a classe de dados já resolveu que, se você imprimir o objeto, imprimirá algo significativo, implementando os métodos wrapper dunder. falarei mais sobre isso em um minuto. Então, como adicionamos essas outras coisas à classe de dados? É claro que temos o ID, que é uma string, mas agora gostaríamos de atribuir a ele um valor gerado por essa função. Então, para fazer isso, precisamos usar a função de campo que também está nas classes de dados. E o que estou fazendo aqui é que id é igual a campo e a fábrica padrão, que é basicamente a função que cria o valor para nós, será gerar ID. Então, agora o que acontece é que, para calcular esse valor, a classe de dados chamará essa função para computá-lo. E como isso agora tem um valor padrão, temos que colocá-lo abaixo. As outras células não têm um valor padrão. Então lá vamos nós. Agora, quando eu executar isso novamente, você verá que agora minha pessoa tem nome, endereço, mas agora também tem um id que foi gerado chamando a função generate ID. Você também pode simplesmente fornecer um valor padrão, por exemplo, digamos que temos uma variável de instância ativa aqui que é booleana. E o padrão será definir isso como verdadeiro. Então, agora uma pessoa está ativa por padrão. E você vê que agora também está incluído no objeto Person aqui. Agora, o que mais podemos fazer? Bem, também temos os endereços de e-mail listados aqui, então vamos acrescentar isso. E então vamos também adicionar um valor padrão aqui. Agora você pode ficar tentado a escrever isso. Isso não é uma boa ideia porque a forma como o Python funciona é que esse valor é realmente gerado uma vez na interpretação. Portanto, se você atribuí-lo como um valor padrão, esse é o mesmo motivo pelo qual você não deve atribuir uma lista vazia como um valor padrão em funções. Você pode ter um comportamento inesperado porque todas essas listas vazias basicamente se referirão ao mesmo objeto e você não quer isso. Então, o que você pode fazer também é definir um campo aqui e simplesmente dizer que o valor padrão, aquela fábrica padrão, será o inicializador da lista, assim. Agora, cada pessoa também terá uma lista de endereços de e-mail que por padrão, ficarão vazios. Outras coisas que você pode fazer com classes de dados que são muito boas. Bem, uma é que, como vimos, ele gera o inicializador. Então, aqui temos uma pessoa. Portanto, ele tem um nome, endereço, ID, ativo e lista de endereços de e-mail. Mas talvez você não queira que o ID esteja lá, você quer que ele seja sempre gerado. O que faz sentido, certo? Não sabíamos, não queremos especificar esses IDs explicitamente. Queremos que ele seja gerado automaticamente. Então, o que você pode fazer é simplesmente fornecer ao campo a função em que ela é igual a false, assim. E agora, quando você olha novamente para a definição de Denise lives, você vê que o campo ID não faz mais parte do inicializador, mas é não faz mais parte do inicializador, claro que ainda faz parte da pessoa aqui que vemos que está sendo gerada automaticamente. Portanto, isso permite que você tenha algum controle sobre a aparência do inicializador e, ao mesmo tempo, tenha uma visão geral muito boa de todos os atributos da instância. Outra coisa que você talvez queira fazer é adicionar uma sequência de pesquisa a essa pessoa. Então, digamos que temos uma string de pesquisa. Variável de instância, sim, é uma string. E o que queremos agora é que o valor aqui seja derivado do nome e do endereço. Então é isso que queremos ser capazes, Sergio. Mas é claro que aqui ainda podemos definir esse valor porque não temos esses valores. Isso é simplesmente uma especificação dos atributos da instância. Então, o que podemos fazer em vez disso é simplesmente fornecer o campo e defini-lo como falso porque não queremos poder fornecer a string de pesquisa como parte do inicializador. Mas então o que podemos fazer é definir um impulso nela. Os métodos Dunder não retornam nenhuma sequência de caracteres de pesquisa de pontos próprios igual. E então vamos simplesmente construir a string self.name e o endereço de ponto automático, assim. Agora, o que acontece é que ambos inseridos são chamados após a criação do objeto . Então, é quando temos o valor do nome e do endereço e, em seguida, podemos calcular a string de pesquisa. Então, quando executo esse código, novamente, você vê que agora temos todos esses valores aqui, mas também vemos que temos uma string de origem que é o nome mais espaço e depois o endereço. Então, o que mais podemos fazer? Bem, talvez não queiramos que a sequência de pesquisa apareça quando imprimimos a pessoa, porque isso é espécie de inflamação dupla, certo? Você pode imaginar que, se a classe de pessoas crescer, essa sequência de pesquisa ficará muito longa. Então, em vez de imprimi-lo, toda vez que imprimimos uma pessoa, também podemos adicionar aqui que um rapper é igual a falso. E isso significa simplesmente que, quando imprimimos a pessoa a sequência de pesquisa não fará parte da representação. Não será incluído. Isso é o que você vê aqui. E, para esclarecer, essa sequência de pesquisa é na verdade, algo interno à classe. Você também pode decidir colocar um sublinhado na frente dele, dessa forma. E agora estamos fazendo essa distinção de forma ainda mais clara na própria definição de classe. Outras coisas que podemos fazer é controlar como os objetos são criados. Então, aqui temos uma pessoa, fornecemos um nome e endereço, mas se você quiser, podemos remover esses argumentos, nomes de palavras-chave aqui e agora, é claro, isso ainda funcionará. É assim que as classes funcionam por padrão em Python, certo? Mas e se você quisesse ter certeza de que só pode fornecê-lo usando argumentos de palavras-chave que você sempre precisa escrever, nome igual e endereço igual. Bem, você pode fazer isso passando um argumento aqui para o decorador da classe date, e isso é apenas uma palavra-chave e padrão, isso é falso, mas podemos defini-lo como verdadeiro. E agora vemos aqui que, na verdade, estamos recebendo um erro porque ele espera palavras-chave. Agora temos que escrever o nome é igual a John e o endereço é igual a 123 ruas principais. Para que isso funcione, isso não tem nenhum efeito, qual é o resultado real, mas afeta a forma como podemos criar pessoas. E ter esse rigor extra às vezes pode ser uma ferramenta útil. Outra coisa que você pode fazer é congelar uma classe de dados. E isso significa que você pode modificar o objeto depois que ele for criado. E fazemos isso fornecendo as passas congeladas dessa forma. Mas agora algo interessante está acontecendo. Você vê que nossa ideia com a string de pesquisa na verdade não funciona mais porque, é claro agora temos um objeto congelado, que significa que podemos modificá-lo, mas ainda o estamos modificando depois de criado. Eu vou te mostrar outra maneira de abordar isso em um minuto. Então, por enquanto, deixe-me deletar isso. Usaremos outra coisa mais tarde para resolver esse problema. Agora, quando executo isso, é claro que ainda há trabalhos, mas agora, quando tento modificar a pessoa, o nome do ponto da pessoa é igual, agora você vê que na verdade não temos permissão para fazer isso. A pessoa está congelada e recebe um erro ao executar isso Você também vê que recebemos um erro de instância congelada. Isso é muito útil se você quiser ter certeza de que, se estiver usando alguns dados, não os modifique acidentalmente. Mas como resolvemos esse problema de cadeia de caracteres de pesquisa agora, porque não temos permissão para modificar esse objeto depois que ele foi criado. Bem, uma coisa que você pode fazer é usar propriedades em vez disso. 6. Aulas de próximo nível 2/4: Essa é outra adição muito interessante às aulas que permitem torná-las muito mais poderosas. O que você pode fazer é, em vez de usar isso, usar uma propriedade para definir um termo de pesquisa. Então, eu vou ter uma propriedade aqui, e vamos chamar isso de cordão de camisetas. É quase como um método. E isso retornará uma string. E simplesmente vou remover isso aqui. E a string de pesquisa será uma string f contendo self.name, self.age, address, assim. Então, ainda está congelado. Mas agora vamos remover isso novamente. Quando eu imprimo a pessoa. Bem, a string de pesquisa não existe por causa da propriedade, mas nós a temos para que eu possa imprimir ponto pessoal, sequência de pesquisa, assim. Está reclamando que agora é um membro privado, mas, como você vê, ele realmente funciona em suas impressões digitais. Claro, se você quiser que a string de pesquisa esteja mais disponível publicamente, é claro que você pode remover esse sublinhado deste lado da seguinte forma. E agora que o erro dos postes também ocorre ao nosso redor novamente, ainda obtemos os mesmos resultados. Então, uma vez que adicionamos aqui está uma propriedade somente para leitura que também é chamada de getter. E é lido apenas porque não podemos atribuir nada a ele. Se eu fizer com que a string de pesquisa por pontos por pessoa seja igual a alta, então você vê que recebemos um erro aqui de que não podemos atribuir um membro a uma propriedade que não é permitida. O que você pode fazer se quiser que a string de pesquisa seja modificável é realmente adicionar um setter. E como fonte, por exemplo , propriedade, isso é muito fácil em Python. Simplesmente escrevemos a sequência de caracteres de pesquisa ponto e, em seguida, temos aqui o setter de pontos. E então simplesmente definimos o mesmo nome de propriedade novamente. Mas aceita um argumento que é o valor. Esse caso é uma string. Vai retornar nulo. E então aqui podemos definir o valor. Agora, é claro, não vai funcionar porque nossas classes de dados estão congeladas, certo, então podemos mudar qualquer coisa. Então, se removermos isso e agora poderemos armazenar coisas. E como você poderia armazenar um valor no objeto, se quisesse. Mas é claro que realmente não precisamos disso no caso da string de origem, porque estamos simplesmente retornando um valor computado aqui Tenho outro exemplo aqui que faz um pouco mais de sentido em termos de propriedade. Então, eu tenho um videoclipe de aula que tem minutos e segundos, um título. E você vê que somos uma propriedade chamada duração. E basicamente, dependendo do número de minutos e do número de segundos, retorna um total de segundos. E então também podemos ter um setter que define um número de segundos. E isso basicamente usa a função div mod para calcular qual deveria ser o valor de minutos e segundos. Então, aqui você vê um exemplo do uso de uma propriedade, um getter e um setter para controlar como os valores estão sendo armazenados. Objeto, nesse caso, minutos e segundos do videoclipe. E aqui temos um projeto de vídeo que tem uma lista de videoclipes. E, novamente, esse é um campo usado como lista de fábrica padrão. Temos um valor padrão para nosso título e também temos uma propriedade na duração total, que é a soma de cada um desses clipes, o número de minutos e segundos. Então eu tenho uma função principal simples em que tenho dois clipes. Um videoclipe de um minuto e 30 segundos, outro videoclipe de 2 min e 30 segundos. E então eu crio um projeto que tem esses dois clipes. Eu imprimo o comprimento total, mas depois uso o configurador de duração para alterar o clipe, duração de 220 s, que na verdade é 2 min. E então podemos imprimir o comprimento total do projeto. E quando eu executo isso , é isso que obtemos. Então, obtemos 240, mas depois passamos de 1 min para 2 min. Então, são trinta segundos a mais. Então, são 270 s que estão sendo impressos. Ou você também pode ver que, se eu imprimir o clipe aqui, Friends recorta um e eu executo esse código novamente. Você vê que depois de atualizar o clipe, você vê que temos 2 min e 0 s. E é exatamente isso que nossa dose de duração cetera. E se você olhar para o projeto de vídeo, podemos realmente simplificar isso ainda mais, porque aqui estão os minutos de pontos do Eclipse, bloqueia os segundos de pontos do clipe. Bem, podemos simplesmente usar a duração que a leva até aqui para torná-la ainda mais curta. E então obteremos exatamente o mesmo resultado de antes. Então, essas são propriedades que podem ser úteis para obter um valor computado sem realmente ter que armazenar esse valor. E também ajudam a ocultar alguns dados de baixo nível em uma classe, como se você tivesse minutos e segundos internamente, mas simplesmente usasse os segundos finalizados externamente, um criador e configurador de propriedades o ajudarão a conseguir isso. 7. Aulas de próximo nível 3/4: Quero falar sobre mais duas coisas antes de terminar esta lição. Uma é string versus wrapper. Então, vimos que, se você tem uma classe de dados como essa classe pessoal e imprimimos a pessoa que vemos, obtemos uma espécie de versão amigável para desenvolvedores do que a pessoa realmente é , útil para depurar ou se você quiser bloquear algo ou qualquer outra coisa. E é exatamente isso que o wrapper deve fazer. O Wrapper deve fornecer uma representação amigável de um objeto para desenvolvedores. A ideia é até mesmo que você possa pegar essa representação armazenada em um arquivo e ela deve conter informações suficientes para depois, se você quiser lê-la de um arquivo novamente e recriar o objeto que é diferente de ter uma representação fácil de usar. Isso não é algo que você queira que um usuário veja porque é meio difícil de ler e escrever. E é aí que o método dominante da string é quatro. Portanto, se você também quiser ter uma representação mais fácil de usar de um objeto, você pode realmente usar os métodos string dunder. classe de dados não adiciona isso, mas você mesmo pode adicioná-la simplesmente adicionando os métodos string dunder. E é claro que isso vai retornar uma string, certo? E o que queremos devolver? Bem, digamos que simplesmente queremos imprimir o nome da pessoa. Então esse é o nome próprio, assim. Agora, quando executamos esse programa novamente, você vê que ele agora simplesmente imprime o nome. E isso porque string dunder substitui rapper, certo? Então, se você não tem uma string, mas tem um rapper, o Python usará o wrapper. Se você tiver uma string , ela a usará em vez disso. Mas e se você ainda quiser imprimir a versão embrulhada da pessoa? Bem, há algumas maneiras de fazer isso. Uma é que você pode usar a função wrapper dessa forma. E isso simplesmente chamará o método wrapper dunder e o retornará como uma string que agora podemos imprimir. Então, se eu fizer essa margarida, teremos nossa pessoa, uma pessoa amigável para desenvolvedores. Novamente, o que você também pode fazer é usar cordas. Então, se você tem uma string f como essa, e agora, é claro, simplesmente imprime a pessoa. Então, novamente, isso imprimirá o método string dunder, mas você pode realmente usar o ponto de exclamação R. E em uma string f, isso também imprimirá a versão em invólucro dos objetos que você tem algum controle sobre o tipo de coisa que está sendo impressa, mesmo que você tenha talvez os métodos wrapper e string dunder. É bom saber. 8. Aulas de próximo nível 4/4: A última coisa que quero mostrar é uma maneira simples de melhorar a velocidade de acesso aos elementos em seus objetos. E isso é usando slots. Normalmente, o Python usa um dicionário para armazenar os valores das variáveis de instância de um objeto. E o acesso ao dicionário é rápido, mas não é incrivelmente rápido. E a razão pela qual não é tão rápido é porque é muito dinâmico. Você pode adicionar chaves e valores a qualquer momento. Assim, você pode basicamente decidir modificar dinamicamente o objeto e adicionar novas variáveis de instância a qualquer momento. Python é uma linguagem muito dinâmica. Lembre-se de que, com os slots, você concorda com o intérprete que será um pouco mais rigoroso. Você terá um conjunto fixo de variáveis de instância. E por causa disso, Python pode otimizar as coisas e acessá-las muito mais rápido. Agora, na maioria dos casos, você realmente terá um número fixo de variáveis de instância. Você não quer mudar seus objetos o tempo todo, em todo lugar, certo? Vai se transformar em uma bagunça. Então, se você fizer isso e usar muito, na verdade será muito mais rápido. Então, um exemplo muito simples aqui que novamente usa classes de dados, que tem uma opção de slots. Portanto, se você tiver uma classe de dados, pode simplesmente definir os slots como verdadeiros, como o que estou fazendo aqui. E então ele vai usar slots em vez do dicionário tradicional. Então, eu tenho uma versão da classe pessoal, não usa muitos e uma versão da primeira classe que os usa. E então eu tenho uma função aqui, chego na elite que recebe vagas para uma pessoa ou uma pessoa, e isso vai definir o endereço, ler o endereço e excluir o endereço apenas como uma espécie de referência. E então eu tenho a função principal de criar algumas pessoas, pessoas caça-níqueis e pessoas normais. E então eu estou usando o timeit to repeat para chamar a função get set delete na pessoa. Muitas vezes, neste caso, 1 milhão de vezes. Na verdade, eu pego a mediana disso para obter o desempenho médio e vou imprimir quanto tempo. Tudo bem, as melhorias de desempenho. A propósito, essa coisa aqui é parcial. Eu vou falar sobre isso na próxima lição. Portanto, fique atento a isso. Então, deixe-me fazer isso agora que podemos ver o que está acontecendo. Agora vemos que, quando não temos slots fazer todas essas operações de acesso, leitura, gravação e exclusão leva 0,06 s com slots, leva 0,05 s. Então, isso representa uma melhoria de quase 20% no desempenho, especialmente se você estiver lidando com muitos dados, isso realmente terá um grande impacto se você estiver fazendo essas operações em qualquer lugar. Portanto, é muito simples, basta definir os slots como verdadeiros ao definir sua classe de dados. Agora você pode se beneficiar dessa melhoria, pois os slots de melhoria de velocidade também usam um pouco menos de memória. entanto, existem algumas ressalvas. Uma é que, se você estiver usando vários slots de herança , pode causar problemas. Portanto, tenha muito cuidado com isso, mas você deve, em qualquer caso, ter cuidado ao usar herança múltipla. E outra coisa é que você os conta adiciona ou remove dinamicamente variáveis de instância, dois objetos, porque com slots estamos sendo um pouco mais rígidos. Mas acho que, no geral, isso é realmente uma coisa boa. Então, isso conclui esta lição em que mostrei algumas coisas mais poderosas que você pode fazer com classes em Python. Na próxima lição, falarei sobre o que você pode fazer com as funções. E também vou examinar mais de perto algumas ferramentas funcionais em Python para ajudá-lo a fazer isso. 9. Funções de nível seguinte 1/4: Nesta lição, quero falar sobre funções e algumas das coisas mais poderosas que você pode fazer com funções em Python, semelhantes ao que fizemos com as classes e com a lição anterior. Agora, em Python, basicamente, tudo é um objeto, certo? Temos números inteiros, strings, classes das quais você pode criar objetos. Até mesmo uma função é um objeto, é um objeto do tipo double. E você poderia, na verdade, se quisesse criar uma classe que tenha uma chamada de métodos dunder, essa classe também é uma classe de retorno de chamada que também se comporta como uma função. Na verdade. Um exemplo muito simples aqui, eu tenho uma classe de cliente, que é uma classe de dados. Se você está dormindo com um nome, uma idade e sua função aqui, chamada promoção de Enviar e-mail, que recebe uma lista de clientes e é usada para analisar os clientes que verificam o cliente. Se a idade for de cerca de 50 anos, o cliente está qualificado para a promoção. E se a idade do cliente estiver acima de 50 anos , ele estará qualificado para a promoção. E dependendo desse valor, imprimimos algo diferente na tela. Então, eu tenho uma função principal que cria uma lista de clientes e envia promoções por e-mail para esses clientes. Pelo menos aqueles com mais de 50 anos contornam isso, então é isso que estamos recebendo. Então, ele verifica cada cliente e depois imprime onde o cliente está qualificado ou não para enviar promoção por e-mail, que é uma função que pode ser chamada, que obtém uma lista de clientes e devolve, nenhuma. E, claro, esse é um uso muito básico do funcional em Python, certo? Mas como a função é um objeto, você pode realmente fazer muito mais coisas com ela. Por exemplo, você pode passar uma função para outra função ou até mesmo fazer com que uma função retorne outra função como resultado. E nós fazemos isso. É quando você usa algo chamado funções de pedidos superiores, por exemplo , aqui, verificamos se a idade do cliente é de cerca de 50 anos , ele está qualificado para a promoção. Mas talvez queiramos fazer uma verificação mais complexa aqui que também verifique as coisas do cliente. Agora, é claro, poderíamos estender a declaração if aqui com cada vez mais complexidade da condição. Mas também seria bom que a promoção por e-mail pudesse, de alguma forma, obter um mecanismo para verificar a elegibilidade chamando outra função. E é isso que podemos fazer com funções de ordem superior. Então, o que podemos fazer em vez disso é definir que uma função aqui é elegível para promoção E a função atrairá um cliente. E ele retornará um booleano. E isso simplesmente fará com que o ponto h do cliente esteja acima de 50. Como usamos essa função aqui? Bem, podemos simplesmente chamá-lo, certo? Poderíamos fazer isso se fosse elegível para promoção. Cliente, assim. E então, quando executarmos isso e depois o que obteremos exatamente os mesmos resultados. Mas isso também nos dá muito pouco controle, porque agora não há como definir outras funções e determinar dinamicamente se devemos enviar um e-mail aos clientes ou não. É sempre uma função específica do disco. Então, em vez de chamá-lo diretamente aqui, também podemos apresentá-lo como um argumento para enviar uma promoção por e-mail. Então, digamos que a função é elegível para escrever isso corretamente. Obviamente, como isso é uma função, esperamos algo do tipo objetivo, que é uma função. E como especificamos que tipo de doença funcional? Bem, isso é usar os colchetes e depois usamos outro par de colchetes para indicar quais são os argumentos dessa função com os tipos de parâmetros. Então, o que essa expressão é um cliente e o que ela retorna é booleano. E então, em vez de chamar a disfunção diretamente aqui, chamamos a função que passamos como um parâmetro da seguinte forma. E agora, é claro, temos que ter certeza de que, quando realmente chamamos a função aqui, você vê que também há um erro aqui que realmente passamos. Essa função específica é elegível para promoção. Assim. Agora, quando executamos isso, obtemos exatamente o mesmo resultado, mas agora o dividimos de forma um pouco diferente, e agora estamos usando funções de ordem superior para dividir a promoção de envio de e-mail e a função de verificação de elegibilidade. Agora, em vez de precisar definir uma função como essa, também há outra coisa que você pode fazer que é usar uma função lambda. função lambda em Python é uma função anônima. Então, aqui não temos uma função anônima porque essa função tem um nome. função Lambda não tem um nome e você especifica diretamente como uma expressão. Então, em vez de ter essa função aqui, à qual nos referimos pelo nome, podemos usar uma função lambda. Isso vai atrair um cliente e ele retornará c, H é pelo menos 50. E agora, quando executamos isso , obtemos novamente exatamente o mesmo resultado. Mas agora estamos usando uma função lambda anônima. E isso é bom porque agora temos uma maneira muito fácil de mudar a forma como as promoções por e-mail são tratadas com código de erro, por exemplo, também podemos torná-la uma idade diferente, por exemplo, digamos que apenas acima de 60 anos. E agora vamos obter um resultado diferente. E não precisávamos mudar nada na função de promoção Enviar e-mail, o que é muito bom. 10. Funções de próximo nível 2/4: Vamos tornar esse exemplo um pouco mais complicado. Vou remover a função Lambda aqui novamente, e vou ligar para se qualificar mais uma vez e vou mostrar mais uma maneira de ter mais controle sobre o que acontece quando você chama uma função. Agora, é claro que, se tivermos , é elegível para promoção, , é elegível para promoção, não temos como realmente controlar esse valor porque ele está codificado na função. Portanto, talvez queiramos ter uma opção aqui para ter uma idade limite, que será um número inteiro. E então, em vez de usar o ano 50, podemos usar a cruz de oito, certo? Então, isso é muito bom. Mas agora temos um problema aqui que é elegível para promoção. É claro que não adere mais ao tipo dessa ingênua que tínhamos aqui. E isso porque agora existe esse parâmetro extra que precisamos fornecer. Você pode fazer uma coisa que é, por exemplo , fornecer, um valor padrão como esse. E então isso é uma espécie de solução alternativa, porque agora funciona novamente. Mas agora ainda não há como alterar a idade aqui de forma alguma, porque só podemos usar o valor padrão, certo? Agora, é aqui que o pacote de ferramentas do funk pode nos fornecer uma boa solução que é chamada de aplicação de função parcial. O que significa aplicação da função de parcela? Basicamente, significa que se você tem uma função com alguns parâmetros diferentes, como o que temos aqui, é que com parcial, você já pode aplicar alguns desses argumentos. E então você recupera outra função. Quando você chama essa função , os argumentos que você aplicou anteriormente serão usados. Portanto, ele permite que você altere a assinatura do cabeçalho da função já fornecendo alguns dos valores. Então, por exemplo o que podemos fazer aqui, vamos primeiro importar das ferramentas, que é o modelo que vamos usar. E então o que você pode fazer é remover esse valor padrão aqui novamente, para que eliminemos o erro. Você pode fazer agora é dizer, bem, nós temos, é elegível. E digamos que queiramos cortar em 60, então vamos chamá-lo de elegível de 60, que é uma inscrição parcial ou é elegível para promoção. Mas vamos fornecê-lo com uma idade limite de 60 anos. E agora, quando enviamos uma promoção por e-mail, podemos enviar, podemos fornecer a ela a função elegível de 60 parcialmente aplicada. Então, parcial recebe uma função. Ele aplica um valor a um de seus argumentos e retorna uma nova função, que agora é chamada de elegível 60. Infelizmente, mergulhe aqui não preciso qual é o tipo dessa função parcialmente aplicada. Talvez em uma versão futura do Python que venha, mas isso é novamente uma função e agora está sendo chamada por promoções enviadas por e-mail. Quando eu executar isso, você obterá clientes com mais de 60 anos. Portanto, este é um exemplo de aplicação de função parcial que é realmente poderosa e permite modificar funções, simplificá-las e usá-las, tornando-as compatíveis com outras áreas do seu aplicativo já aplicando alguns dos valores. 11. Funções de próximo nível 3/4: Outra coisa interessante que você pode fazer com as ferramentas func é a chamada propriedade em cache. Você deve se lembrar do exemplo que mostrei em uma das lições anteriores em que eu tinha uma pessoa e tínhamos uma propriedade de string de pesquisa, que era uma propriedade computada. Então, toda vez que você liga para a propriedade, o valor é computadores. Talvez nem sempre seja isso que você deseja se o valor de calcular um três, complexo de computar, bem, com a string de pesquisa, não fosse tão ruim. Mas se for realmente complexo, evite ter que computar isso toda vez que ligar para a propriedade. Uma maneira de resolver isso é realmente armazenar o valor no objeto que você calculou ao criar o objeto. Mas se você estiver usando uma classe de dados congelada, isso não é possível. É aqui que a propriedade em cache entra em ação a partir das funções. Então, aqui eu tenho um exemplo de como ele está sendo usado. Eu tenho um conjunto de dados de classe que tem uma sequência de números. E nesse caso, estou simplesmente armazenando isso como uma tupla em uma variável de instância de dados. E então eu tenho a propriedade em dinheiro, que é o desvio padrão desses dados específicos. E estou usando a função de desvio padrão das estatísticas. E depois da função principal, onde basicamente cria uma instância desse conjunto de dados. E então eu imprimo o desvio padrão. E o que é realmente interessante é que, se eu executar isso agora, você vê que só obtemos uma vez as impressões dessa mensagem computando o desvio padrão. E isso porque isso está sendo armazenado em cache. A propriedade é calculada uma vez e toda vez que você chama posteriormente, simplesmente usando o valor em cache. Portanto, seria muito poderoso se eu usasse uma propriedade normal para isso. Assim. E agora eu executo isso novamente, você vê que o desvio padrão é calculado três vezes. Essa é a diferença. Então, vou transformar essa propriedade real em dinheiro. E agora, quando executamos isso novamente, ele é novamente computado apenas uma vez. Portanto, você usa uma propriedade em dinheiro para memorizar determinado valor, para não precisar computá-los repetidamente, mas ainda assim ter a flexibilidade de acessá-los como uma propriedade normal em seu objeto. 12. Funções de nível seguinte 4/4: última coisa legal que quero mostrar a vocês fontanelas é o envio único. Single Dispatch é um decorador que você pode usar para ter uma espécie de sobrecarga de funções. Então, o que você faz é definir uma função genérica e variedades de registro de dados dessa função que podem lidar com diferentes tipos. Aqui está um exemplo. Eu tenho uma única função de despacho chamada add que tem um x e y, dois números inteiros, e ela retorna a soma de dois inteiros. Mas então eu adiciono uma segunda versão dessa função. Então, essa é, digamos, a versão padrão da função que recebe números inteiros. O segundo que recebe cordas e cordas. Não quero adicioná-los diretamente assim. Eu quero adicionar um espaço. Então, eu estou usando a formatação de strings aqui para inserir um espaço entre o x e o y. Vou voltar a eles em um minuto, mas se tivermos aqui imprimido mas se tivermos aqui 12 e imprimindo hello world, então você vê que a primeira linha que será impressa é de fato a soma e as sequências concatenadas com o espaço entre elas. E há mais coisas que você pode fazer com isso. Por exemplo, você também pode usar o tipo de união sobre o qual falei no vídeo de digitação. Então, aqui eu registro uma versão dessa função que recebe um x e um y, que é uma lista ou um conjunto. E isso retorna uma lista, simplesmente encadeando os elementos na lista de conjuntos. E também há uma forma funcional. Então, esses usam o chamado decorador com o sinal de adição na frente. Mas você também pode simplesmente chamar o registrador de pontos como uma função e fornecer o tipo e , em seguida, a função que deve lidar com isso. Então é isso que você vê sendo feito aqui. Então, posso simplesmente executar isso novamente e a lista de devolução da OCDE. E o último retorna uma tupla porque foi isso que especificamos aqui. Emita uma tupla mais rápida e você também retornará uma tupla. Assim, você pode usar o envio único para lidar com objetos de diferentes tipos sem problemas em Python. Agora, para ser honesto, eu não usei muito isso, mas acho que é útil saber que isso existe. E, em alguns casos , pode ser útil simplificar um pouco seu código. Então, no geral, acho que a biblioteca de ferramentas do funk é uma coleção de ferramentas muito interessante. Também há algumas outras coisas que não foram abordadas nesta lição de hoje. Você pode dar uma olhada na documentação. Mas, no geral, acho que é muito interessante. Como também notei, muitas pessoas tendem a se limitar às aulas, enquanto as funções também são muito poderosas. Portanto, eles também costumam permitir que você simplifique seu código em vez de usar classes em qualquer lugar. Então, da próxima vez, você estiver escrevendo um programa em Python e lidando com classes complexas e tudo mais. Pense em como você pode transformar isso em uma versão mais funcional. E muitas vezes você notará que o código será mais curto e fácil de ler. Então, essas são ferramentas divertidas e uma espécie de programação funcional em Python. Na próxima lição, vou dar uma olhada outro conceito que você quer achar realmente útil, que é a programação simultânea. 13. Programação simultânea 1/3: Especialmente se você está começando a usar o Python de forma mais profissional, repente você vai interagir com uma API, um banco de dados, qualquer coisa basicamente em uma rede. E se você fizer isso, significa que se torna importante para seu aplicativo lidar com esses tipos de solicitações com eficiência. Porque se você não fizer isso, seu aplicativo ficará muito lento. Em Python, você pode usar o pacote de IO assíncrono para ajudá-lo com isso. E isso depende de algo chamado programação simultânea. Você já deve ter ouvido o termo simultaneidade de programação simultânea antes e o termo relacionado paralelismo, programação paralela. Essas coisas, na verdade, não são a mesma coisa. Existem diferentes. A diferença é que por meio da computação paralela, você realmente tem unidades de processamento paralelo separadas para realizar tarefas separadas. Então, se você tem um aplicativo que faz várias coisas ao mesmo tempo, essas coisas estão realmente sendo feitas em paralelo ao mesmo tempo. Isso é paralelismo e simultaneidade significa que um aplicativo está progredindo em várias tarefas ao mesmo tempo. Mas, em vez de ter verdadeiras operações paralelas, ele realmente alternará entre essas tarefas de forma dinâmica. Então, digamos que um aplicativo da tarefa a para a tarefa B, ele simplesmente começará a fazer um pouco sem parar com a, continuará com B, um pouco de B e depois voltará para a e assim por diante. Portanto, não é paralelo, mas é simplesmente alternar entre os dois. Porque isso acontece muito rápido. Como usuário, você pode ter a ideia de que isso acontece em paralelo, mas na verdade acontece simultaneamente. É simplesmente uma troca. É como a diferença entre as linhas na frente de um caixa paralelo. Isso significa que temos vários caixas e cada caixa tem uma linha que está processando. Concorrência significa que há um caixa, mas há várias linhas e todo mundo está se revezando. As linhas nesse caso são as tarefas que um computador deve resolver e os caixas são as da CPU. Os computadores modernos usam uma combinação de paralelismo e simultaneidade. Você sabe, sua CPU pode ter 246810 núcleos que podem fazer coisas em paralelo, mas seu sistema operacional terá dezenas a centenas de tarefas diferentes. E ele executará um subconjunto dessas tarefas paralelamente nesses diferentes núcleos de CPU, mas também alternará entre elas simultaneamente. paralelismo em Python tem uma ressalva de que o Python tem o chamado bloqueio global do interpretador. E isso significa que se você tiver, digamos, vários segmentos que deveriam ser paralelos, que poderiam ser paralelos em Python, na verdade, isso não funciona porque eles estão bloqueados para o interpretador. Há razões para isso. Eu não vou falar sobre isso nesta aula. Mas as consequências disso, por meio do paralelismo em Python, não são realmente possíveis. Há algumas soluções alternativas, por exemplo, você poderia, em vez de ter um único processo, usar o backedge de multiprocessamento para criar vários processos. E Dan, você tem paralelismo. Você também pode mudar para um interpretador Python diferente que não tenha esse bloqueio de intérprete global. Mas, na verdade, não há muitos casos em que você precise passar pelo paralelismo. Muitas vezes, a simultaneidade já é muito boa e Python tem um suporte muito bom para simultaneidade, especialmente desde o Python 3.7, que melhorou muito o pacote Async IO. Agora, a simultaneidade é muito importante porque isso permite que você tenha várias tarefas, por exemplo, uma tarefa para recuperar alguns dados de uma API. Enquanto você espera pela resposta da API, você já pode mudar para outra tarefa, e isso também é útil, por exemplo, se você tem um aplicativo de GUI e a GUI está esperando que você digite texto nos campos de texto ou pressione um botão. Bem, como é um programa simultâneo, você pode realizar outras tarefas ao mesmo tempo, como limpar parte da memória, fazer uma pré-busca de dados ou o que quiser fazer para o aplicativo funcione com mais facilidade. E dado que quase todos os aplicativos hoje em dia se comunicam pela Internet e recuperam vários dados. É muito importante lidarmos com a simultaneidade de maneira adequada. E é isso que o pacote Async IO em Python permite que você faça. 14. Programação simultânea 2/3: Há duas palavras-chave importantes que você deve saber escrever código simultâneo ou assíncrono, e isso é esperar por async. Você pode escrever async na frente de uma função ou método para indicar que essa será uma função ou método que você pode chamar simultaneamente. E você pode usar um peso para indicar na frente de uma declaração que a próxima declaração deve esperar até que a declaração anterior seja concluída. E essa é a segunda parte que também é muito útil, porque muitas vezes você precisa esperar por uma determinada resposta para fazer alguma coisa. Por exemplo, se você recuperar dados de uma API, precisará esperar até ter dois dados de volta para poder realmente fazer algo com esses dados. Então async e await têm um exemplo muito simples aqui. Você pode ver que temos uma função de obtenção de Pokémon que usa a API Pokémon para obter nomes de Pokémon. E estamos passando, nesse caso, um id e temos um URL para o qual fornecemos o ID. É assim que devemos chamar essa API. E então eu estou voltando para aguardar HTTP GET. E então a URL http get é uma função auxiliar que eu criei. Isso é assíncrono. Eu vou te mostrar como isso funciona em um minuto. Mas aqui vemos a função assíncrona Get Pokemon que retorna os pesos e, em seguida, recupera os dados da URL. E usamos um garçom porque é claro que só podemos retornar quando tivermos esses dados. A função principal aqui, como você pode ver aqui, também é assíncrona porque, bem, ela usa a função rent int para criar um número inteiro aleatório entre um e o maior ID de Pokémon, que atualmente é 898. E então eu uso um peso para pegar o Pokémon desse ID específico, e então imprimo o nome do Pokémon. E aqui, o peso também é muito importante, porque é claro que tenho que esperar até obter o valor do Pokémon para poder imprimir o nome. Isso é o que você vê aqui. Eu dei uma olhada nisso. Veja, pegamos alguns Pokémon aleatórios e você vê que demorou cerca de meio segundo ou segundo para obter esse valor específico de Pokémon. Vamos tentar isso de novo. Você vê que demora um pouco e então obtemos o resultado. Isso é falso porque temos que esperar a API nos responda e isso pode levar algum tempo. Aqui temos outro exemplo que mostra como é valioso usar a programação simultânea. Então, o que eu fiz aqui foi ter duas versões de como fazer isso. Receba a solicitação da API. Eu tenho uma versão assíncrona e uma versão síncrona. Aqui você tem uma função que usa a versão síncrona. Então, o que estou fazendo é obter Pokémon desse URL específico. E aqui eu não tenho uma versão assíncrona que receba esse Pokémon. E nesse caso eu uso a função HTTP GET assíncrona. Na minha função principal, tenho duas versões após chamadas síncronas. Então, isso basicamente pesa cada vez para os resultados, certo? Então eu faço um for-loop e obtenho 20 nomes locais aleatórios, e então eu calculo o tempo total. E na chamada assíncrona, estou usando algo chamado Async IO dot gather. E o que isso faz é que me permite fornecer chamadas e várias chamadas assíncronas, depois lançará elas sempre querem formigas, em vez de esperar que cada mês termine antes de começar o próximo coleta assíncrona de aorta nos permite executar todas essas coisas simultaneamente. Então, quando você executa isso, você vê isso enquanto esta é a primeira vez. Agora você viu que, no caso síncrono, demoramos quase 2 segundos para conseguir esses 20 nomes de Pokémon porque esperamos pelo primeiro antes de solicitarmos o segundo. E no caso assíncrono, levou apenas 0,9 s, então isso é menos da metade. E isso porque estamos executando as coisas ao mesmo tempo. Não precisamos esperar pelos resultados da primeira chamada de API. Para iniciar a segunda chamada de API, é preciso ter cuidado, que geralmente essas APIs têm limites de raid. Portanto, você não quer lançar cerca de 1.000 chamadas ou desejos de API porque isso não será aceito. Portanto, você precisa fazer a solicitação da API dentro de certos limites de taxa, como um número fixo de chamadas, como o número máximo de chamadas por segundo, depende da API, deve verificar isso. Mas, como você pode ver , pensando um pouco sobre como podemos executar nosso código de forma assíncrona, como ele pode ser executado simultaneamente. Isso nos poupará muito tempo de espera e fará com que nosso aplicativo funcione com muito mais facilidade. Então, isso está reunido, reunido Basicamente, o que você vê aqui, estou descompactando uma lista de diferentes chamadas de funções assíncronas. Então isso é async io.gov, que é uma ferramenta muito útil para executar coisas simultaneamente. E tudo o que podemos ver aqui nesses exemplos é que temos essa função principal assíncrona, mas a executamos usando o async IO dot rum, que diz ao interpretador Python que execute essa função principal de forma assíncrona. E é necessário, porque se eu simplesmente escrever main , na verdade isso não funcionará conforme o esperado. Porque se eu operar esse ar condicionado, receberemos todos os tipos de avisos de que a co-rotina do Maine nunca foi esperar. E isso é exatamente, claro, o que está acontecendo aqui. Estamos simplesmente ligando para isso. Então, se você quiser evitar isso, então você deve chamar async IO dot run e escrever essa doutrina corretamente, é claro. E então chamamos a função principal assim. Quando eu executo isso novamente, você vê que agora não temos mais o erro. 15. Programação simultânea 3/3: A última coisa que quero mostrar é como transformar código síncrono, código não simultâneo, em código que é executado simultaneamente. E isso pode acontecer às vezes, você pode ter um pacote de biblioteca que está usando e não é assíncrono, mas você quer transformá-lo em uma chamada assíncrona. Como você faz isso? Então, eu tenho um exemplo aqui. Portanto, há uma função assíncrona aqui, contador, e também há uma função de solicitações de envio síncrono aqui. E isso é simplesmente usar o pacote requests write requests, não o pacote de solicitações na verdade, não é assíncrono e síncrono. Isso significa que, se enviarmos uma solicitação aqui, precisaremos aguardar o resultado e simplesmente retornar o código de status. Nesse caso, tenho minha função principal aqui e estou enviando uma solicitação para que nossos álcoois tenham desaparecido, que é meu site. E então eu estou imprimindo, recebi a resposta HTTP com esse status e ligo para o contador. Outra coisa é, claro, por que devemos esperar pela resposta aqui para iniciar o contador? Isso é tudo. Preciso disso, certo? Então, se eu executar isso, você vê que enviamos a solicitação, obtemos a resposta e começamos a contra-atacar. E se quiséssemos usar o gather para iniciar um contador ao mesmo tempo em que enviamos a solicitação. Como fazemos isso? Bem, se quisermos fazer isso, significa que precisamos ativar a solicitação de envio e assim por diante. Função simultânea assíncrona em vez de uma função síncrona, se você quiser fazer isso, isso é realmente muito fácil. Então, em vez de chamar solicitações de envio como esta, que você pode fazer é assincronizar o ponto IO em dois segmentos. Isso vai transformá-lo em um tópico? E então estamos ligando para enviar solicitação. Também precisamos fornecer os argumentos, que é o URL. E então precisamos escrever um peso na frente dele porque a ameaça transforma essa função em uma função assíncrona. Então, deixe-me fazer isso e você vê que ainda obtemos exatamente o mesmo resultado, certo? Porque não temos peso. Então, enviamos a solicitação, obtemos a resposta e iniciamos o contador. Mas agora que temos a versão assíncrona aqui, podemos realmente usar a coleta de pontos assíncrona para fazer as duas coisas ao mesmo tempo. Então, aqui temos um exemplo em que eu configurei isso. Então, o que eu fiz foi criar agora outra função chamada solicitação assíncrona enviada que usa um ponto de IO assíncrono para ameaçar chamá-la de forma assíncrona. E então, na minha função principal, eu não tenho coleta de pontos IO assíncrona. E então eu enviei a solicitação assíncrona ou que causa disfunção, que costuma ameaçar transformar isso em uma função assíncrona. E eu também estou usando o contador. E quando eu executar isso, você verá que temos o contador. E começa ao mesmo tempo em que envia uma solicitação. E então esperamos e, no final, obtemos a resposta. Então isso é IO assíncrono. A propósito, aqui tínhamos nosso pessoal assíncrono do Get Pokémon que estava usando esse HTTP GET, que é uma função auxiliar que eu criei. Na verdade, isso está neste arquivo. E você vê que usamos Async IO dot two thread aqui para transformar a função request dot gets em uma chamada de função assíncrona. Então, isso usa exatamente o mesmo mecanismo para concluir que o IO assíncrono é um recurso muito poderoso do Python. A programação simultânea em geral é muito útil, especialmente se você estiver se comunicando em uma rede com outros serviços e quiser que seu aplicativo se comporte sem problemas. Então, espero que esta lição tenha lhe dado algumas ideias de como você pode pegar seu próprio código e transformá-lo em código assíncrono, especialmente se você estiver fazendo muitas dessas comunicações de API ou banco de dados. Nas poucas lições restantes deste curso, às vezes eu poderia me referir à programação simultânea. Mostre como você pode usar esse recurso em particular, com a programação simultânea em mente. Agora, a próxima lição serão os iteradores. 16. Iteradores 1/3: Nesta lição, falarei sobre iteradores em Python. O que não é iterador, embora seja basicamente um objeto que pode ser iterado, você pode percorrer todos os valores. Você pode reconhecer um iterador em Python porque ele implementa o protocolo iterador, e isso significa que ele tem os próximos métodos de dominó. Os iteradores estão por toda parte em Python, são usados para fazer loops, usam compreensões de listas e em usam compreensões de listas muitos outros lugares em que você provavelmente já os usou algumas vezes sem nem mesmo perceber que eram iteradores. Você pode ter ouvido os termos iterador e iterável. Eles não são a mesma coisa. Um objeto iterável que pode fornecer um iterador. E isso significa que ele tem o método itr dunder porque ele fornece um iterador. Um iterador próximo ao método doador itr também tem um próximo método dominante que nos permite obter o próximo elemento na iteração. Alguns desses iteradores são finitos, por exemplo, você pode iterar por meio de uma lista fixa de itens. Alguns desses iteradores são infinitos. Por exemplo, você pode iterar todos os valores inteiros. Obviamente, em Python, não há valor máximo para um número inteiro, pelo menos algumas considerações de que memória do seu computador é limitada. Então, listas, tuplas, dicionários, conjuntos, cadeias de caracteres, essas são coisas que são todas iteráveis. E você pode obter um iterador deles e, em seguida, você pode iterar por meio de uma sequência específica. Vamos dar uma olhada em alguns exemplos. Eu tenho uma função principal aqui, e há um item de classe que tem um nome e um peso para cada item ter peso. Obviamente, estou usando classes de dados. E depois da função principal, qualquer que seja a lista de itens, estou chamando isso de inventário. Agora, o que eu posso fazer é que o inventário é uma lista, então isso é iterável. Então, posso obter um iterador com isso. Para que eu possa escrever o inventário. Iterator é igual ao inventário dot dr hitter. Isso vai me dar o iterador e então eu posso imprimir, por exemplo ponto do iterador de inventário e, em seguida, o Dahmer. Então, isso está usando o protocolo iterador. Se eu executar isso, você verá que ele imprimirá o primeiro elemento nessa sequência iterativa. Se eu copiar essa linha, desse jeito , ela ligará no máximo duas vezes e passará pelo segundo item. Isso é o que você vê aqui. Há uma maneira um pouco mais simples de fazer isso, em vez de chamar esses métodos idiotas, o que também podemos fazer é o correto. É ela, que é uma função auxiliar que simplesmente chama o método dominante para nós. Da mesma forma, também podemos usar next, que chama o próximo método dominante para nós. Assim. E agora obtemos exatamente o mesmo resultado. Então, eu posso continuar fazendo isso. Então, há seis itens aqui. Então, agora vou imprimir todos os itens dessa lista específica. Então, o que acontece se eu adicionar mais um? Bem, então isso vai gerar um erro de interrupção da iteração. Então você vê que chegamos aqui rastreando, há uma iteração de parada. Então é assim que um iterador indica: Ei, não há mais, eu posso te dar qualquer outra coisa. Então, agora você pode, por exemplo criar um loop while e, em seguida , testar as instruções exceto e, em seguida capturar essas iterações de interrupção, então, você sabe, você é idiota. Mas é claro que ninguém está usando iteradores dessa maneira. Não é uma maneira muito mais simples de fazer isso, simplesmente usando um loop for. Então, deixe-me remover todas essas coisas aqui. E então eu vou escrever para o item no inventário. E então eu vou simplesmente imprimir o item ponto e vírgula, na verdade. Lá entramos e simplesmente imprimimos todos os itens. Então, o que o loop for faz nos bastidores é usar o método iter e next, então ele primeiro obtém um iterador e depois chama next várias vezes até encontrar o erro de interrupção da iteração e depois parar. É isso que o loop for faz. Nada mais. O que é bom na função iter que chama o método doador é que você também pode fornecer um valor sentinela. E é basicamente um valor que deve ser fornecido para indicar o final do iterador. Isso é útil, por exemplo, se você estiver lendo dados de um arquivo ou rede, basicamente um fluxo de dados e quiser saber se o final do fluxo foi atingido. Então, eu tenho um exemplo aqui. Portanto, este é um arquivo chamado country que contém alguns países. E então eu posso usar o valor sentinela para indicar qual é o final do arquivo. Portanto, posso usar declarações de largura. Isso é um gerenciador de contexto. Falarei sobre isso em uma aula posterior. E eu vou usar o Open e depois vou abrir arquivo TXT de pontos de países. E então eu estou fazendo a fila em uma entrada divertida. E então ele chamará a linha de leitura de pontos phi, que retorna alguns iteráveis. E o valor da sentinela será a string vazia. E então eu vou imprimir a linha. Então, quando fazemos isso, você vê que temos todos esses países. E como o arquivo de texto já contém novas linhas, posso simplesmente removê-las da instrução print indicando que o final da instrução print é a string vazia. E então vai ficar um pouco melhor. Portanto, a string vazia aqui é usada como um valor sentinela para indicar que estamos chegando ao final do arquivo. 17. Iteradores 2/3: Como temos esses iteráveis e iteradores, eles também nos permitem introduzir alguma abstração. Então, aqui eu tenho outro exemplo. Então, aqui está uma classe de item de linha. É uma classe de dados congelada. Eu falei sobre isso no início do curso. E cada item da linha tem um preço e uma quantidade. Esse é o preço total, isso retorna um int, e isso é simplesmente o preço multiplicado pela quantidade. E então f para a função imprime os totais que obtêm itens. E vejo que não estou passando uma lista de itens de linha e passando a ela um iterável de itens e princípios de linha. A única coisa que eu faria seria usar o for-loop para item em itens e um príncipe, o preço total na minha função principal, eu tenho uma lista de itens de linha. Veja isso aqui, e então eu estou chamando os totais impressos. Então, quando eu executo isso, ele simplesmente imprime todos os totais. Mas o bom agora é que, os totais impressos esperam algo iterável, não importa se é uma lista, uma tupla ou qualquer outra coisa. Então, por exemplo, aqui, se minha lista de itens de linha, posso substituí-la por uma tupla, assim. Então, agora não é mais uma lista, mas os totais impressos não se importam. Desde que seja iterável, ele pode fazer seu trabalho. Portanto, os iteradores permitem que você também introduza alguma abstração porque as impressões Altos não se importam com a estrutura de dados. Só se importa que tenha algo sobre o qual possa repetir e isso é tudo de que precisa. 18. Iteradores 3/3: Se você realmente quiser levar os iteradores para o próximo nível, o que você também pode fazer é usar as ferramentas do GitHub, que é um pacote, pacote padrão do Python que tem mecanismos iteradores muito poderosos. É uma espécie de álgebra de iterações. Como você pode pegar, todos esses iteradores os combinam de várias maneiras para obter um comportamento realmente complexo. Por exemplo, suponha que você queira primeiro filtrar elementos de uma lista e depois multiplicar esses elementos por outro valor de outra lista e, em seguida , vinculá-los com outro conjunto de outra lista e, em seguida , vinculá-los com outro conjunto de valores. Talvez você normalmente usasse uma combinação de funções e, para loops, para isso. Mas você também pode usar outras ferramentas para combinar essas operações em uma grande operação usando álgebra iteradora. Vou mostrar alguns exemplos de como isso funciona. Então, para não usá-lo, as ferramentas, é claro, teremos que importá-lo. Isso vai acabar. E depois há algumas coisas que você pode fazer. Parece muito básico, mas há algumas coisas realmente avançadas que você pode fazer com iteradores. Então, vou começar de novo com minha lista de inventário porque isso será útil para mostrar o que você pode fazer com iteradores. Vou apenas remover essas linhas aqui. Bem, vamos começar com algo muito simples. Um exemplo muito simples é count, que é uma função da itertools que permite contar a partir de um determinado número. Então, podemos usar um for-loop porque é iterável, certo? Temos contagens e então podemos indicar, por exemplo, o ponto de partida. Então, queremos começar a contar até dez. E você pode até indicar uma etapa, por exemplo, com etapas de cinco e imprimir I. Agora, se eu parasse aqui, isso basicamente continuará indefinidamente. Então, se chi é igual a, digamos, 50, então vamos quebrar. Então, quando eu executo isso, é isso que você obtém. Então essa é a função de contagem, counts Iterable. Não é um exemplo simples que se repita. Então, agora isso vai se repetir 105 vezes. Vamos executar isso e então você verá que isso é exatamente o que está acontecendo. Outra coisa que você pode fazer é acumular, que calcula as somas parciais. Então, digamos que temos subtotais, que são, digamos, uma lista de alguns números. Estou apenas digitando coisas aleatórias aqui, assim. E então o que podemos fazer é usar acumular. E vamos fornecer a lista de subtotais. Mas você pode realmente fornecê-lo, basicamente qualquer outro iterável qual você possa usar outras funções de It's Tools, para o qual você possa usar outras funções de It's Tools, e então eu simplesmente vou imprimir I e isso não precisamos neste caso. Então, o que vamos fazer agora é examinar essa lista de subtotais e sempre calcular a soma parcial, e assim por diante. Isso é o que obtemos como resultado. Mais coisas que você pode fazer com ele se resolvem. Então, digamos que temos cartas de baralho, que é uma lista de, digamos, sequências de caracteres. Estou usando o co-piloto do GitHub aqui para gerar isso para mim, para que eu não tenha realmente digitado isso. Mas agora o que você pode fazer é usar permutações. Funcionamento de permutações. São as ferramentas de todas essas cartas de baralho. E digamos que queremos ter todas as permutações de, isso é tudo para jogar cartas. E então vamos imprimi-los. Como as fermentações estão novamente em seu ribossomo, pode usar um loop for para iterar sobre isso. Eles irão. E quando executamos isso, bem, isso é o que obtemos. Agora temos todas as combinações possíveis de quatro cartas de baralho diferentes. E vamos tornar isso um pouco mais simples para que seja um pouco mais fácil ver o que está acontecendo. Então, vou criar um a, B e C. Então, digamos que queremos ter todas as combinações de dois. E então isso é o que obtemos, certo? Então você vê que a ordem realmente importa aqui. Então AB é outra coisa do que ba, se a ordem não importa, você não usa permutações, mas usa combinações, então é isso que obtemos lá. Obviamente, há muito menos combinações desses três caracteres, AB, AC e BC. Em vez de um loop for para imprimir cada valor, também podemos usar outra coisa. Podemos usar o inicializador de lista para transformar os resultados dessas combinações iteráveis em uma lista que podemos simplesmente imprimir e é isso que obtemos. Há mais coisas que você pode fazer com ferramentas, por exemplo, você pode usar uma corrente. Então, se mudarmos os valores com, digamos, um analista d , e, f, obteremos uma única lista que é uma cadeia dessas duas listas separadas. E, claro, você pode passar qualquer tipo de iterável aqui. Outra coisa que também é útil é o filtro false. Assim, você pode imprimir, digamos, uma lista de It's a tool start, filter false. E eu quero filtrar todos os itens com peso inferior a 1 kg. Então, vou usar uma função lambda aqui x dot wait to write that right is less to write that right is less than one. E, claro, vou passar meu inventário para ele. E então deve haver duas colunas aqui. Acho que não, não estou perdendo nenhum parêntese. Então, quando eu executo isso, é isso que obtemos. Então, vemos que só temos o laptop, o livro e a câmera, que pesam um quilo ou mais. Por último, quero mostrar um mapa estelar, que é outra função da itertools que permite pegar uma lista ou um iterável de tuplas, neste caso de vários valores. E então, para cada valor você pode aplicar algum tipo de operação. Então, aqui estou fazendo uma multiplicação. Então, eu tenho uma lista de tuplas aqui. Então, o que isso vai fazer é criar uma nova lista de duas vezes 68 vezes 4,5 vezes três. E esse é o resultado que você vê aqui. Então esse é o mapa estelar. E agora podemos imaginar que com elas em duas funções, você pode realmente combinar as coisas de várias maneiras. Portanto, você pode usar o filtro false para criar um novo iterável com um subconjunto dos elementos e da lista. E se você usar acumulações para calcular o peso total ou algo assim, poderá usar o mapa estelar para calcular tempos de espera, quantidade, etc., etc. Você pode usar todas essas combinações para criar uma espécie de álgebra que produza um comportamento complexo. E essa é uma maneira diferente de fazer isso do que, digamos, usando for-loops ou dividindo coisas e funções e coisas assim. E você também pode, é claro, criar seus próprios iteradores personalizados que fazem algo específico para o que você precisa para combiná-los perfeitamente com itertools porque tudo segue o protocolo iterável. Então, aqui está um pequeno exercício para você. Dê uma olhada em alguns dos seus códigos anteriores e veja se você consegue encontrar alguns exemplos em que você poderia, em vez de usar um loop for para percorrer a lista, usar as ferramentas para realizar essa operação específica. É muito divertido fazer anúncios que às vezes podem levar a um código muito mais curto. Mas tenha cuidado, porque se você usar uma combinação realmente complexa de ferramentas, também será muito difícil de entender. Portanto, você precisa garantir que seu código ainda esteja legível. E, claro, você ainda pode dividir bem as coisas em diferentes funções, mesmo se estiver combinando diferentes funções de ferramentas. Na próxima lição, abordarei os iteradores preguiçosos, também chamados de geradores. 19. Geradores 1/4: Nesta lição, vou falar sobre geradores e há algo especial em Python dados criam o chamado iterador preguiçoso e eles foram introduzidos e o pepper 255. Então, o que realmente significa um iterador preguiçoso? Bem, é quase o mesmo um iterador normal, exceto que ele só cria os valores quando você solicita o valor. Então, em vez de ter, digamos que, se você tiver uma lista com valores, defina a lista antes de poder realmente iterá-la. Então você tem que criar a lista completa. Com o iterador preguiçoso. Você só cria esses itens à medida que os examina. Isso significa que o iterador preguiçoso não armazena seu conceito na memória. Ele cria o conteúdo em tempo real quando você o solicita. E isso significa que, em alguns casos, essa é uma solução mais adequada do que computar tudo antemão antes de começar de antemão antes de começar a iterar. Ela também pode ser considerada uma maneira simples de criar iteradores, porque basicamente gera uma classe com os métodos iter e next dunder que você pode repetir, em vez de ter que escrever todas essas coisas sozinho. E como você faz isso? Bem, você simplesmente escreve uma função, assim como escreveria uma função regular, exceto que os moldes usam uma declaração de retorno para retornar um valor, mas você usa yield, tanto return quanto yields retornam algum tipo de valor, exceto que return realmente encerra a função. Rendimentos. Ele armazena o estado da função como está atualmente e retorna o valor. E então, na próxima vez que você chamar a função, ela simplesmente recuperará esse estado novamente e continuamente de onde parou. 20. Geradores 2/4: Aqui eu tenho um exemplo muito simples de uma função geradora. Então você também vê que o tipo de retorno é um gerador e sobre o que essas coisas fazem, falarei em um minuto. Mas isso é muito simples. Temos uma string e produzimos o valor dessa string. Então essa é a primeira vez que chamamos isso. Mas então adicionamos um ponto de exclamação e o emitimos novamente. Isso cria um iterador que você pode chamar duas vezes antes que a corrida interrompa a iteração. E então, na função principal, estou usando for-loop para passar pelo iterador que esse gerador simples nos fornece. E então eu estou imprimindo a corda. Então, aqui você vê qual é o resultado dessa operação. Portanto, você usa isso como um iterador normal , exceto que esse código só é executado quando você chama o próximo método pela segunda vez. Então, o que é esse tipo exatamente? Bem, isso degenera o tipo que você pode importar da digitação, e isso nos diz qual é o valor do rendimento. Então você pode ver que na verdade é uma string. E o que isso significa quando você realmente tem um mecanismo de envio de informações para gerar uma função, e então essa informação é usada novamente na próxima rodada. Aqui está um exemplo da documentação do Python que mostra como isso funciona. É um pouco avançado, mas embora este seja um curso de Python de próximo nível, por que não? Mas basicamente o que acontece aqui é que armazenamos o resultado da expressão yield, que é o que enviamos para essa função. E isso é do tipo float. E então, enquanto obtemos um valor usando o centro, retornaremos a versão arredondada disso, que é um número inteiro, e depois a versão final, que é o valor de retorno. Então, você pode realmente combinar rendimento e retorno em um gerador e é aí que a função termina e ela retornará. Ok? Então, quando eu executo isso, você pode ver que eu tenho aqui uma função principal onde eu chamo isso do que eu faço a seguir. E então eu envio esses valores para ele. E então você pode ver que ele passa por essas coisas e termina com, ok, é basicamente assim que funciona. Então, outro caso de uso real para isso. Bem, não muitos, para ser honesto, talvez você tenha um, mas nunca encontrei uma situação em que eu precisasse usar isso. Também tenha cuidado ao combinar rendimentos e retornos. Dessa forma, acho que complica o dourado, torna mais difícil entender o que realmente está acontecendo. 21. Geradores 3/4: A próxima coisa que quero mostrar são as chamadas expressões geradoras. E isso também é muito poderoso. Isso permite que você crie uma expressão que seja, na verdade um gerador e especifique o cálculo imediatamente na expressão. É um pouco como funções lambda, função anônima, mas depois são geradores anônimos. Você vê um exemplo aqui. Eu tenho um gerador de energia em CC. Quando eu passo o mouse sobre esse valor, é, na verdade, um gerador, mas uma expressão divertida aqui entre parênteses, essa é a expressão geradora. E a expressão é dois elevado a I para I na faixa de dez. Portanto, é um gerador finito. E então eu estou usando um for-loop para iterar sobre esse gerador de potências e, em seguida, imprimir os geradores de valor, assim como ints ou floats ou funções são objetos que você pode passar para outras funções, por exemplo, a função sum aceita um gerador e, portanto, também uma expressão geradora. Assim, você pode criar facilmente a soma de todas essas coisas simplesmente passando-a para a função sum. Então, quando eu executo isso, é isso que obtemos. A soma é 1023. As expressões geradoras também são muito poderosas porque permitem fazer cálculos como esse, mas só as fazem quando você realmente precisa avaliar. Então, isso é diferente, digamos, de uma compreensão de lista. Se eu mudasse esses parâmetros por colchetes , teríamos uma compreensão da lista. Você também pode repetir isso, mas a compreensão da lista já é calculada quando você a define como isso, o gerador. Então, isso é feito preguiçosamente, o que é muito útil se você quiser economizar algum tempo de computação. 22. Geradores 4/4: Outra coisa interessante sobre os geradores é que eles realmente se integram bem com o código simultâneo. Tudo isso é uma das lições anteriores que eu falei sobre async, IO e código simultâneo. Bem, na verdade, você também pode ter geradores de concorrência assíncronos. Aqui está um exemplo. Eu tenho uma função aqui. É uma função assíncrona que recebe um nome de Pokémon aleatório. Portanto, é baseado no exemplo Async IO que usei anteriormente. E isso simplesmente retorna o nome do Pokémon. E então eu tenho aqui uma função geradora que, depois do total, é chamada de próximo Pokémon. Então, eu passo uma volta onde estou analisando o total e toda vez que estou usando um peso para obter o nome aleatório do Pokémon e , em seguida, estou produzindo esse nome. Você também pode pedalar Yield Away. Portanto, você não precisa variar aqui. Mas, como você pode ver, ele se integra muito bem com o código assíncrono. Você simplesmente escreve async na frente dele e pode usar um peso em seu gerador, e então ele será simultâneo. Mas o tipo é diferente. Há um async que gera um tipo como você vê aqui. Mas, em princípio, a ideia é que funcione da mesma forma. E então, se você olhar na função principal, que também é síncrona, temos um loop for assíncrono que passa pelo próximo gerador de Pokémon e recebe os próximos nomes de Pokémon. E você pode usar geradores assíncronos de maneiras diferentes e Python, por exemplo, aqui eu tenho uma compreensão de lista que usa o próximo gerador de Pokémon que é assíncrono. E então eu estou usando uma compreensão de lista assíncrona para obter todos esses dados. Então, quando eu executar isso, você verá que agora ele passa por essas chamadas de API e recebe todos esses nomes de Pokémon. Isso não reúne todas as respostas porque é um gerador, então é preguiçoso. Ele só executará o comando quando você o solicitar. É por isso que os vemos aparecendo um por um. Mas isso significa que, se você precisar de apenas mais cinco nomes chame isso apenas cinco vezes, são apenas cinco solicitações de API. Em vez de fazer todas as solicitações de API e, em seguida, pegar apenas cinco valores. Essa é uma diferença entre as dicas do tipo gerador e a geração assíncrona de um tipo. E, na verdade, tive que corrigir isso há um minuto porque estava errado porque a assíncrona gerada não tem o tipo para o valor de retorno porque isso não é possível com o assíncrono, só temos rendimentos. É por isso que ele tem apenas dois argumentos em vez de três. Mas, novamente, isso não é algo que você normalmente usaria muito em seu código. Então, por que isso é útil? Bem, existe uma maneira fácil de criar iteradores sem precisar criar uma classe com métodos iter e next dunder. Obviamente, eles também se integram bem com crianças ou ferramentas mencionadas na lição anterior, economizam memória e evitam computação desnecessária. Se você não solicitar o valor, ele não está sendo computado. E, em geral, é uma ótima maneira de representar, por exemplo, fluxos de dados em uma rede, onde toda vez que por exemplo, fluxos de dados em uma rede, você obtém o próximo item da rede, algumas coisas a serem observadas é que os geradores podem resultar em uma os geradores podem resultar camada mais difícil de entender, especialmente se você começar a combinar declarações de rendimento e retorno em um único gerador. Em segundo lugar, como o código é executado somente mediante solicitação, é possível que você encontre um erro somente depois tempo, o que pode levar a algum tempo, o que pode levar a uma caixa inesperada e mais tarde, então você precisa se certificar de que está realmente testando corretamente. Portanto, no geral, os geradores são uma ferramenta muito boa, podem não usá-los o tempo todo, mas em alguns casos são muito úteis. Agora, vamos para a lição final sobre gerenciadores de contextos. 23. Gestores de contexto 1/4: O recurso final do Python que eu quero abordar neste curso é o chamado gerenciador de contexto. E isso se baseia em outros recursos sobre os quais falei nas lições anteriores, como iteradores e geradores. E também é compatível com código assíncrono Como mostrarei em um minuto, contextos gerentes são muito úteis porque permitem que você controle o que acontece quando você cria ou destrói recursos. E você pode garantir que, por exemplo se houver uma exceção ou se houver algum outro motivo pelo qual o programa tenha que parar de fazer o que está fazendo , você pode usar um gerenciador de contexto para controlar o que acontece a fim de liberar um recurso. E isso é útil, por exemplo, se você precisar fechar um arquivo, se precisar fechar a conexão com o banco de dados pois precisa deixar outra superfície observar que seu aplicativo está interrompendo uma tarefa e você quer ter certeza de que isso sempre acontece. É aí que os gerenciadores de contextos são realmente úteis. É em uma das lições anteriores que mostrei como abrir um arquivo usando a declaração de largura. Na verdade, esse é um exemplo de gerenciador de contexto. E a razão pela qual usamos uma declaração de largura é que ela permite que o arquivo do Gerenciador de Contexto, o Gerenciador de Contexto, se feche adequadamente após você terminar de ler o conteúdo. E há dois métodos Dahmer, novamente, que são importantes. Existem os métodos dominantes de entrada e saída. Enter é onde você coloca o que precisa acontecer quando você cria um recurso e sai sempre que precisar destruir o recurso. 24. Gestores de contexto 2/4: Aqui você vê outro exemplo do uso de um gerenciador de contexto. Estou usando o SQL lite three aqui, que se conecta a um banco de dados que é apenas um arquivo local. E então eu estou selecionando itens de uma tabela de blocos. E você vê que aqui simplesmente criamos uma conexão com conexões de pontos sqlite, mas você pode realmente usar isso como um gerenciador de contexto. Quando executo esse código , é isso que vemos. Então, veja, basicamente temos uma lista de blogs. Mas o fato é que precisamos ter certeza de que estamos sempre fechando a conexão, certo? Então é isso que está acontecendo aqui. É por isso que isso não é um bloqueio de tentativa final. Então, se eu, por exemplo, cometer um erro de digitação aqui, então blogs isso não existe. Então, vamos receber algum tipo de erro que não é tabelado. Isso ainda garante que a conexão esteja fechada corretamente. Agora, se você quiser evitar ter escrever isso, tente finalmente, o que também pode fazer é usar o sqlite dot connect como gerenciador de contexto. Então, simplesmente escrevemos width sqlite dot connect como coleção. E então isso, é claro, eu tenho que recuar e deve haver uma coluna aqui. O que posso fazer agora é fazer a mesma coisa, imprimir os blogs, mas depois posso introduzir um erro. Mas a única coisa é que agora você pode ver isso no registro é que na verdade, isso não fecha a conexão corretamente. Isso é usado principalmente para reverter transações no banco de dados e coisas assim. Então, o que é bom sobre o Python é que podemos realmente criar nossos próprios gerenciadores de contextos. Só precisamos fornecer uma classe que tenha um método dunder de entrada e saída e seguida, usá-la em vez disso. Então você vê um exemplo disso. Eu criei uma classe sqlite. Estou fornecendo o nome do arquivo, então isso é aplicação de TB. E estou armazenando uma nova conexão para conectar ao banco de dados no método enter, então pego o cursor e depois a saída. Eu não confirmo as alterações e depois fecho a conexão. Então, em nossa função principal, temos com o SQL light. Então, agora estou usando esse gerenciador de contexto aqui, e isso vai me dar o cursor e então eu posso executar minha consulta e retornar o resultado. Então, quando eu executar isso, obteremos novamente exatamente os mesmos resultados, certo? Vamos imprimir esses blogs, mas depois ele chama de resposta e chama de saída quando estiver concluído. Portanto, temos certeza de que a conexão está sempre fechada porque agora, por exemplo deixe-me apresentar aqui novamente algum erro. Se eu executar isso novamente, você verá que, na verdade, ele ainda chamou enter e exit. O gerenciador de contexto fez o trabalho de fechar corretamente a conexão, mesmo se tivermos um erro. 25. Gestores de contexto 3/4: Agora, em vez dessa classe com métodos de entrada e saída, o que você também pode fazer é usar um decorador que, na verdade se baseia na geração de uma função mencionada na lição anterior. Então isso é o que parece. Estou importando o decorador do Context Manager de contextos ativos e divertidos aqui para indicar que essa função será um gerenciador de contexto. E então você vê que estou usando uma estrutura geradora com rendimento para realmente definir o que deve acontecer nas partes de resposta e saída. Então, o que funciona é que tudo antes da produção são as partes principais. Tudo depois disso é a parte de saída. Então, isso é basicamente o que você vê aqui. E isso tem exatamente o mesmo efeito que usar uma classe. Ou se eu executar isso, você vê que estamos fechando uma conexão aqui e uma conexão criando e imprimindo os blogs. Se eu remover aqui novamente, vamos remover outro personagem só por diversão. E então vamos executar isso novamente. Você vê que ainda recebemos esse erro, mas agora ele ainda fecha a conexão corretamente porque estamos usando um gerenciador de contexto, decorador. 26. Gestores de contexto 4/4: A última coisa que quero mostrar é que os gerenciadores de contexto também suportam a sintaxe async and await da integração bem com código simultâneo, assim como os geradores, se ela quiser conceder um gerenciador de contexto assíncrono, usaram o decorador assíncrono do Context Manager. Agora, há alguns problemas de digitação aqui porque esse é um exemplo mais aproximado. Mas a ideia é que você esteja usando nesse caso assíncrono em vez de um gerador síncrono regular. Portanto, os gerentes de contextos também trabalham nesse sentido. Então, por exemplo, aqui, digamos que você tenha uma camada de banco de dados assíncrona. Você pode usar a sintaxe await async para chegar aqui, por exemplo , conexão de banco de dados, nós usamos a conexão e liberação também é assíncrona e colocamos async na frente dela. E então você pode usar isso no resto do seu código, como estou fazendo aqui, por exemplo, estou recebendo todos os usuários. Sql light em si não é assíncrono, mas há um pacote chamado sclerites que é assíncrono que você pode usar para acessar um banco de dados SQL light de forma assíncrona. Você vê um exemplo simples de como isso funciona. Então eu tenho, em vez de sqlite dot connect, eu faço, eu todos sqlite dot connect. Mas isso é assíncrono, mas ainda usa as declarações de largura. E você faz a mesma coisa para obter um cursor para buscar todos os dados da tabela. Então, quando você usa isso, bem, os lugares mais importantes onde você precisa ter certeza de que está fechando ou limpando adequadamente sua bagunça, mesmo que algo dê errado. E esse é particularmente o caso dos bancos de dados que monitoramos as conexões. Ou você quer ter certeza de fechar essa conexão. Ou talvez você precise deixar uma API dizer que você concluiu algum tipo de processo. Você quer ter certeza de que, mesmo que ocorra uma exceção, essa chamada final ainda ocorra. E é aí que os gerenciadores de contexto são realmente úteis. E, finalmente, do ponto de vista do design de software, é muito bom que você possa agrupar recursos de criação e destruição em um único lugar. 27. Resumo: Muito obrigado por seguir este curso. Espero que tenha lhe dado um pouco de reflexão e algumas ideias sobre como melhorar seu código Python existente. Eu me diverti muito gravando esse curso, mas é claro que há muitas outras coisas nas quais poderíamos mergulhar. Um pouco mais sobre Python, design de software em particular, como mencionei no início, confira meu canal no YouTube, youtube.com Slash ion codes. Se você tiver alguma dúvida ou sugestão, basta escrever um comentário aqui. Eu adoraria ouvir de você. Obrigado por assistir e cuidar.