Descritores de atributos em Python

Documentos relacionados
Produtividade e qualidade em Python através da metaprogramação

Python para quem sabe Python

Luciano Ramalho dezembro/2012. Objetos Pythonicos. Orientação a objetos e padrões de projeto em Python

Curso de Python em 5 Horas

Luciano Ramalho setembro/2012. Objetos Pythonicos. Orientação a objetos e padrões de projeto em Python

Computação II (MAB 225)

Orientação a Objetos parte 2 ENCAPSULAMENTO, CLASSES, HERANÇAS

Python: Exceções, Iteradores e Geradores. Claudio Esperança

Padrão para a codificação em Python

Luciano Ramalho setembro/2012. Objetos Pythonicos. Orientação a objetos e padrões de projeto em Python

Computação 1 - Python Aula 10 - Teórica: Estrutura de Dados - Dicionário. João Carlos, Carla Delgado, Ana Luisa Duboc 1/ 18

Luciano Ramalho setembro/2012. Objetos Pythonicos. Orientação a objetos e padrões de projeto em Python

Linguagens de Programação

Aplicações de Dicionários. Prof. Alberto Costa Neto Programação em Python

Python: Classes. Claudio Esperança

Luciano Ramalho setembro/2012. Objetos Pythonicos. Orientação a objetos e padrões de projeto em Python

Encapsulamento e Métodos (Construtores e Estáticos) João Paulo Q. dos Santos

Python para quem sabe Python

DURAÇÃO DA PROVA: 2 horas

Classes e Objetos. Prof. Fernando V. Paulovich 9 de agosto de 2010

Conceitos de Programação Orientada a Objetos

Linguagem de Programação Orientada a Objeto Abstração - Encapsulamento

Fundamentos da Programação

Aula 5 POO 1 Encapsulamento. Profa. Elaine Faria UFU

Tipo Abstrato de Dados (TAD) Algoritmos e Estruturas de Dados I Prof. Tiago Eugenio de Melo

Computação 2. Aula 7 Teórica professor: Leonardo Carvalho

Computação 1 - Python Aula 6 - Teórica: Listas 1/ 28

Programação I Aula 19 Aritmética com racionais Pedro Vasconcelos DCC/FCUP

Programação orientada a objetos

Projeto de Linguagem. Linguagens de Programação

Programação procedimental

Funções. Prof. Alberto Costa Neto Programação em Python

Computadores e Programação Engª Biomédica Departamento de Física Faculdade de Ciências e Tecnologia da Universidade de Coimbra Ano Lectivo 2003/2004

A Linguagem Python: Uma visão geral. Prof. Alberto Costa Neto Programação em Python

Computação I - Python Aula 4 - Teórica: Variáveis e Atribuição, Strings

Computação 2. Aula 6 Teórica professor: Leonardo Carvalho

Computação II (MAB 225)

Linguagens de Programação

1/ 26. Computação 1 - Python Aula 1 - Prática: Primeiros Passos - Função

Laboratório de Programação 1 Aula 04

Programação Orientada a Objetos em Python

Luciano Iteráveis, geradores & cia: o caminho pythonico

Interfaces. Universidade Católica de Pernambuco Ciência da Computação. Prof. Márcio Bueno.

QCon Rio Matando um Sistema Monolítico Rumo Aos Microservices. Bernardo Fontes. Rio de Janeiro/RJ 27 de Agosto de 2015

Computação 1 - Python Aula 4 - Teórica Variáveis e Atribuição, Strings. João Carlos, Carla Delgado, Ana Luisa Duboc 1/ 30

PROGRAMAÇÃO ORIENTADA A OBJETOS: OCULTAR INFORMAÇÕES E ENCAPSULAMENTO

MC-102 Aula 02 Shell Interativa, Programa Básico, Variáveis, Atribuições, Tipos Simples. Instituto de Computação Unicamp

Herança. Universidade Católica de Pernambuco Ciência da Computação. Prof. Márcio Bueno.

Computação I - Python Aula 1 - Prática: Primeiros Passos- Função

Linguagem de Programação Orientada a Objeto Polimorfismo, Classes Abstractas e Interfaces

1 Expressões, valores e tipos 1. 2 Variáveis e atribuições 5. cálculo de expressões segue a estrutura de parênteses e as prioridades dos operadores

CIÊNCIA DA COMPUTAÇÃO - LINGUAGEM DE PROGRAMAÇÃO II REVISÃO POO

Introdução à Programação

Computação 2. Aula 8 Teórica professor: Leonardo Carvalho

UNIVERSIDADE FEDERAL DE MATO GROSSO DO SUL SISTEMAS DE INFORMAÇÃO - CÂMPUS DE COXIM FUNDAMENTOS EM ORIENTAÇÃO A OBJETOS

Java para Desktop. Programação Orientada à Objetos 2 JSE

Orientação a Objetos Básica

Computação 1 - Python Aula 4 - Teórica: Variáveis e Atribuição, Strings 1/ 26

INTRODUÇÃO A CLASSES E ORIENTAÇÃO A OBJETOS EM PYTHON. George Gomes Cabral

Informática. Professor: Diego Oliveira. Conteúdo 04: Orientação a Objetos

Programação orientada a objetos: exemplo com frações (conclusão)

Classes e Objetos INTRODUÇÃO À ORIENTAÇÃO A OBJETOS COM JAVA - MÓDULO II. Classes. Objetos. Um modelo para a criação de objetos

Lista 05 Herança. public class PessoaFisica extends Pessoa { private String RG; public PessoaFisica(){ super(); } public String getrg(){ return RG; }

Introdução à Programação / Programação I

Python. 17 Maio 2005, Teste e Qualidade de Software. Faculdade de Engenharia da Universidade do Porto. Ferramentas de teste para a linguagem.

Desenvolvimento Web II

Fundamentos da Programação

Dicionários. Prof. Alberto Costa Neto Programação em Python

MCG126 Programação de Computadores II

O Pronome PARENT 5.1. O Objeto que Declarou o Objeto Corrente da Execução

So she went on, wondering more and more at every step, as everything turned into a tree the moment she came up to it.

Introdução à Programação Aula 3 Primeiros programas

Nomes, vinculações e escopos

VB.NET - Orientação a objetos : conceitos básicos em 10

Linguagem de Programação III

Computação II MAB 225. Brunno Goldstein.

Orientação a Objetos - Herança

Programação de Jogos em Python

1/ 23. Computação 1 - Python Aula 1 - Prática: Primeiros Passos - Função

Computação 1 - Python 1/ 41

Aula de hoje. Tipos de Dados e Variáveis. Constantes literais. Dados. Variáveis. Tipagem dinâmica. SCC Introdução à Programação para Engenharias

Introdução. Atributos em Java. Atributos. Acesso à atributos em Java. Atributo versus variável. Atributos, métodos e encapsulamento.

TÍTULO: UM ESTUDO SOBRE METAPROGRAMAÇÃO: AS LINGUAGENS DE PROGRAMAÇÃO PYTHON E RUBY

Luciano Ramalho setembro/2012. Objetos Pythonicos. Orientação a objetos e padrões de projeto em Python

Herança. Herança. Herança. Herança. Herança. Programação Orientada a Objetos

Modelo do Mundo Real. Abstração. Interpretação

Linguagem de Programação IV Introdução

Daniel Wildt

Aula 4 Encapsulamento e Relacionamento Cleverton Hentz

Programação por Objectos. Java

Algoritmos e estrutura de dados

} Instalando o Ruby. } Conceitos básicos do Ruby. } Métodos. } Classes. } Módulos. } Arrays e Hashes. } Estruturas de Controle. } Expressões regulares

Computação I - Python

Computação 1 - Python Aula 2 - Teórica. João Carlos, Carla Delgado, Ana Luisa Duboc 1/ 39

Nomes, vinculações e escopos

Encapsulamento. Alberto Costa Neto DComp - UFS

Aula passada. Aula passada... Sequências Funções puras e modificadores. Listas Tuplos

Transcrição:

Descritores de atributos em Python outubro/2012 Luciano Ramalho luciano@ramalho.org

Ritmo desta palestra Recomendação: manter os olhos abertos

Pré-requistos da palestra Para acompanhar os slides a seguir, é preciso saber como funciona o básico de orientação a objetos em Python. Especificamente: contraste entre atributos de classe e de instância herança de atributos de classe (métodos e campos) atributos protegidos: como e quando usar como e quando usar a função super

Roteiro da palestra A partir de um cenário inicial, implementamos uma classe muito simples A partir daí, evoluímos a implementação em seis etapas para controlar o acesso aos campos das instâncias, usando propriedades, descritores e finalmente uma metaclasse Nos slides, as etapas são identificadas por números: ➊, ➋, ➌...

O cenário Comércio de alimentos a granel Um pedido tem vários itens Cada item tem descrição, peso (kg), preço unitário (p/ kg) e sub-total

➊ mais simples, impossível class ItemPedido(object): def init (self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco def subtotal(self): return self.peso * self.preco

➊ a classe produz instâncias classe instâncias

➊ mais simples, impossível class ItemPedido(object): o método inicializador é conhecido como dunder init def init (self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco def subtotal(self): return self.peso * self.preco

➊ porém, simples demais >>> ervilha = ItemPedido('ervilha partida',.5, 7.95) >>> ervilha.descricao, ervilha.peso, ervilha.preco ('ervilha partida',.5, 7.95) >>> ervilha.peso = -10 >>> ervilha.subtotal() -79.5 isso vai dar problema na hora de cobrar...

➊ porém, simples demais >>> ervilha = ItemPedido('ervilha partida',.5, 7.95) >>> ervilha.descricao, ervilha.peso, ervilha.preco ('ervilha partida',.5, 7.95) >>> ervilha.peso = -10 >>> ervilha.subtotal() -79.5 We found that customers could order a negative quantity of books! And we would credit their credit card with the price... Jeff Bezos isso vai dar problema na hora de cobrar... Jeff Bezos of Amazon: Birth of a Salesman WSJ.com - http://j.mp/vz5not

➊ porém, simples demais >>> ervilha = ItemPedido('ervilha partida',.5, 7.95) >>> ervilha.descricao, ervilha.peso, ervilha.preco ('ervilha partida',.5, 7.95) >>> ervilha.peso = -10 >>> ervilha.subtotal() -79.5 Descobrimos que os clientes conseguiam encomendar uma quantidade negativa de livros! E nós creditávamos o valor em seus cartões... Jeff Bezos isso vai dar problema na hora de cobrar... Jeff Bezos of Amazon: Birth of a Salesman WSJ.com - http://j.mp/vz5not

➋ validação com property >>> ervilha = ItemPedido('ervilha partida',.5, 7.95) >>> ervilha.descricao, ervilha.peso, ervilha.preco ('ervilha partida',.5, 7.95) >>> ervilha.peso = -10 Traceback (most recent call last):... ValueError: valor deve ser > 0 parece uma violação de encapsulamento mas a lógica do negócio está preservada: peso agora é uma property

➋ implementar property class ItemPedido(object): def init (self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco def subtotal(self): return self.peso * self.preco @property def peso(self): return self. peso @peso.setter def peso(self, valor): if valor > 0: self. peso = valor else: raise ValueError('valor deve ser > 0')

➋ implementar property class ItemPedido(object): def init (self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco def subtotal(self): return self.peso * self.preco @property def peso(self): return self. peso @peso.setter def peso(self, valor): if valor > 0: self. peso = valor else: raise ValueError('valor deve ser > 0') atributo protegido

➋ implementar property class ItemPedido(object): def init (self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco def subtotal(self): return self.peso * self.preco @property def peso(self): return self. peso no init a property já está em uso @peso.setter def peso(self, valor): if valor > 0: self. peso = valor else: raise ValueError('valor deve ser > 0')

➋ implementar property class ItemPedido(object): def init (self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco def subtotal(self): return self.peso * self.preco @property def peso(self): return self. peso @peso.setter def peso(self, valor): if valor > 0: self. peso = valor else: raise ValueError('valor deve ser > 0') o atributo protegido peso só é acessado nos métodos da property

➋ implementar property class ItemPedido(object): def init (self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco def subtotal(self): return self.peso * self.preco @property def peso(self): return self. peso @peso.setter def peso(self, valor): if valor > 0: self. peso = valor else: raise ValueError('valor deve ser > 0') e se quisermos a mesma lógica para o preco? teremos que duplicar tudo isso?

➌ validação com descriptor peso e preco são atributos da classe ItemPedido a lógica fica em get e set, podendo ser reutilizada

➌ validação com descriptor instâncias classe

➌ class Quantidade(object): def init (self): prefixo = self. class. name chave = id(self) self.nome_alvo = '%s_%s' % (prefixo, chave) def get (self, instance, owner): return getattr(instance, self.nome_alvo) implementação do descriptor def set (self, instance, value): if value > 0: setattr(instance, self.nome_alvo, value) else: raise ValueError('valor deve ser > 0') class ItemPedido(object): peso = Quantidade() preco = Quantidade() def init (self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco def subtotal(self): return self.peso * self.preco

➌ uso do descriptor a classe ItemPedido tem duas instâncias de Quantidade associadas a ela

➌ uso do descriptor class ItemPedido(object): peso = Quantidade() preco = Quantidade() def init (self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco a classe ItemPedido tem duas instâncias de Quantidade associadas a ela def subtotal(self): return self.peso * self.preco

➌ uso do descriptor class ItemPedido(object): peso = Quantidade() preco = Quantidade() def init (self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco def subtotal(self): return self.peso * self.preco cada instância da classe Quantidade controla um atributo de ItemPedido

➌ implementar o descriptor class Quantidade(object): def init (self): prefixo = self. class. name chave = id(self) self.nome_alvo = '%s_%s' % (prefixo, chave) def get (self, instance, owner): return getattr(instance, self.nome_alvo) def set (self, instance, value): if value > 0: setattr(instance, self.nome_alvo, value) else: raise ValueError('valor deve ser > 0') uma classe com método get é um descriptor

➌ implementar o descriptor class Quantidade(object): def init (self): prefixo = self. class. name chave = id(self) self.nome_alvo = '%s_%s' % (prefixo, chave) def get (self, instance, owner): return getattr(instance, self.nome_alvo) def set (self, instance, value): if value > 0: setattr(instance, self.nome_alvo, value) else: raise ValueError('valor deve ser > 0') self é a instância do descritor (associada ao preco ou ao peso)

➌ implementar o descriptor class Quantidade(object): def init (self): prefixo = self. class. name chave = id(self) self.nome_alvo = '%s_%s' % (prefixo, chave) def get (self, instance, owner): return getattr(instance, self.nome_alvo) def set (self, instance, value): if value > 0: setattr(instance, self.nome_alvo, value) else: raise ValueError('valor deve ser > 0') self é a instância do descritor (associada ao preco ou ao peso) instance é a instância de ItemPedido que está sendo acessada

➌ implementar o descriptor class Quantidade(object): def init (self): prefixo = self. class. name chave = id(self) self.nome_alvo = '%s_%s' % (prefixo, chave) def get (self, instance, owner): return getattr(instance, self.nome_alvo) def set (self, instance, value): if value > 0: setattr(instance, self.nome_alvo, value) else: raise ValueError('valor deve ser > 0') nome_alvo é o nome do atributo da instância de ItemPedido que este descritor (self) controla

➌ implementar o descriptor get e set manipulam o atributo-alvo no objeto ItemPedido

➌ implementar o descriptor class Quantidade(object): def init (self): prefixo = self. class. name chave = id(self) self.nome_alvo = '%s_%s' % (prefixo, chave) def get (self, instance, owner): return getattr(instance, self.nome_alvo) def set (self, instance, value): if value > 0: setattr(instance, self.nome_alvo, value) else: raise ValueError('valor deve ser > 0') get e set usam getattr e setattr para manipular o atributo-alvo na instância de ItemPedido

➌ inicialização do descritor class ItemPedido(object): peso = Quantidade() preco = Quantidade() quando um descritor é instanciado, o atributo ao qual ele será vinculado ainda não existe! def init (self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco def subtotal(self): return self.peso * self.preco exemplo: o atributo preco só passa a existir após a atribuição

➌ implementar o descriptor class Quantidade(object): def init (self): prefixo = self. class. name chave = id(self) self.nome_alvo = '%s_%s' % (prefixo, chave) def get (self, instance, owner): return getattr(instance, self.nome_alvo) def set (self, instance, value): if value > 0: setattr(instance, self.nome_alvo, value) else: raise ValueError('valor deve ser > 0') temos que inventar um nome para o atributo-alvo onde será armazenado o valor na instância de ItemPedido

➌ implementar o descriptor >>> ervilha = ItemPedido('ervilha partida',.5, 3.95) >>> ervilha.descricao, ervilha.peso, ervilha.preco ('ervilha partida',.5, 3.95) >>> dir(ervilha) ['Quantidade_4299545872', 'Quantidade_4299546064', ' class ', ' delattr ', ' dict ', ' doc ', ' format ', ' getattribute ', ' hash ', ' init ', ' module ', ' new ', ' reduce ', ' reduce_ex ', ' repr ', ' setattr ', ' sizeof ', ' str ', ' subclasshook ', ' weakref ', 'descricao', 'peso', 'preco', 'subtotal'] nesta implementação, os nomes dos atributos-alvo não são descritivos, dificultando a depuração

➍ usar nomes descritivos ItemPedido. new invoca «quantidade».set_nome para redefinir o nome_alvo

➍ usar nomes descritivos ItemPedido. new invoca «quantidade».set_nome «quantidade».set_nome redefine o nome_alvo

➍ usar nomes descritivos class ItemPedido(object): peso = Quantidade() preco = Quantidade() def new (cls, *args, **kwargs): for chave, atr in cls. dict.items(): if hasattr(atr, 'set_nome'): atr.set_nome(' ' + cls. name, chave) return super(itempedido, cls). new (cls, *args, **kwargs) def init (self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco def subtotal(self): return self.peso * self.preco ItemPedido. new invoca «quantidade».set_nome

➍ usar nomes descritivos class Quantidade(object): def init (self): self.set_nome(self. class. name, id(self)) def set_nome(self, prefix, key): self.nome_alvo = '%s_%s' % (prefix, key) def get (self, instance, owner): return getattr(instance, self.nome_alvo) def set (self, instance, value): if value > 0: setattr(instance, self.nome_alvo, value) else: raise ValueError('valor deve ser > 0') «quantidade».set_nome redefine o nome_alvo

➍ usar nomes descritivos class ItemPedido(object): peso = Quantidade() preco = Quantidade() def new (cls, *args, **kwargs): for chave, atr in cls. dict.items(): if hasattr(atr, 'set_nome'): atr.set_nome(' ' + cls. name, chave) return super(itempedido, cls). new (cls, *args, **kwargs) def init (self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco def subtotal(self): return self.peso * self.preco Quando init executa, o descritor já está configurado com um nome de atributo-alvo descritivo

➍ usar nomes descritivos ItemPedido. new invoca «quantidade».set_nome

➍ usar nomes descritivos «quantidade».set_nome redefine o nome_alvo

➍ nomes descritivos >>> ervilha = ItemPedido('ervilha partida',.5, 3.95) >>> ervilha.descricao, ervilha.peso, ervilha.preco ('ervilha partida', 0.5, 3.95) >>> dir(ervilha) [' ItemPedido_peso', ' ItemPedido_preco', ' class ', ' delattr ', ' dict ', ' doc ', ' format ', ' getattribute ', ' hash ', ' init ', ' module ', ' new ', ' reduce ', ' reduce_ex ', ' repr ', ' setattr ', ' sizeof ', ' str ', ' subclasshook ', ' weakref ', 'descricao', 'peso', 'preco', 'subtotal'] nesta implementação e nas próximas, os nomes dos atributosalvo seguem a convenção de atributos protegidos de Python

➍ funciona, mas custa caro ItemPedido aciona new para construir cada nova instância Porém a associação dos descritores é com a classe ItemPedido: o nome do atributo-alvo nunca vai mudar, uma vez definido corretamente

➍ funciona, mas custa caro Isso significa que para cada nova instância de ItemPedido que é criada, «quantidade».set_nome é invocado duas vezes Mas o nome do atributo-alvo não tem porque mudar na vida de uma «quantidade»

➍ funciona, mas custa caro 1500000.0 1427386 1125000.0 980708 7.3 750000.0 585394 375000.0 0 80004 versão 1 versão 2 versão 3 versão 4 Número de instâncias de ItemPedido criadas por segundo (MacBook Pro 2011, Intel Core i7)

➎ como evitar trabalho inútil ItemPedido. new resolveu mas de modo ineficiente. Cada «quantidade» deve receber o nome do seu atributo-alvo apenas uma vez, quando a própria classe ItemPedido for criada Para isso precisamos de uma...

➎ metaclasses criam classes! metaclasses são classes cujas instâncias são classes

➎ metaclasses criam classes! metaclasses são classes cujas instâncias são classes ItemPedido é uma instância de type type é a metaclasse default em Python: a classe que normalmente constroi outras classes

➎ nossa metaclasse antes depois

➎ nossa metaclasse ModeloMeta é a metaclasse que vai construir a classe ItemPedido ModeloMeta. init fará apenas uma vez o que antes era feito em ItemPedido. new a cada nova instância

➎ nossa metaclasse class ModeloMeta(type): def init (cls, nome, bases, dic): super(modelometa, cls). init (nome, bases, dic) for chave, atr in dic.items(): if hasattr(atr, 'set_nome'): atr.set_nome(' ' + nome, chave) class ItemPedido(object): metaclass = ModeloMeta peso = Quantidade() preco = Quantidade() def init (self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco def subtotal(self): return self.peso * self.preco Assim dizemos que a classe ItemPedido herda de object, mas é uma instância (construida por) ModeloMeta

➎ nossa metaclasse class ModeloMeta(type): def init (cls, nome, bases, dic): super(modelometa, cls). init (nome, bases, dic) for chave, atr in dic.items(): if hasattr(atr, 'set_nome'): atr.set_nome(' ' + nome, chave) class ItemPedido(object): metaclass = ModeloMeta peso = Quantidade() preco = Quantidade() def init (self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco def subtotal(self): return self.peso * self.preco Este init invoca «quantidade».set_nome, para cada descritor, uma vez só, na inicialização da classe ItemPedido

➎ nossa metaclasse em ação

➎ nossa metaclasse em ação

➎ nossa metaclasse em ação +

➎ desempenho melhor 1500000.0 1125000.0 1427386 mesmo desempenho, 980708 + nomes amigáveis 750000.0 585394 585771 375000.0 0 80004 versão 1 versão 2 versão 3 versão 4 versão 5 Número de instâncias de ItemPedido criadas por segundo (MacBook Pro 2011, Intel Core i7)

➏ simplicidade aparente

➏ o poder da abstração from modelo import Modelo, Quantidade class ItemPedido(Modelo): peso = Quantidade() preco = Quantidade() def init (self, descricao, peso, preco): self.descricao = descricao self.peso = peso self.preco = preco

➏ módulo modelo.py class Quantidade(object): def init (self): self.set_nome(self. class. name, id(self)) def set_nome(self, prefix, key): self.nome_alvo = '%s_%s' % (prefix, key) def get (self, instance, owner): return getattr(instance, self.nome_alvo) def set (self, instance, value): if value > 0: setattr(instance, self.nome_alvo, value) else: raise ValueError('valor deve ser > 0') class ModeloMeta(type): def init (cls, nome, bases, dic): super(modelometa, cls). init (nome, bases, dic) for chave, atr in dic.items(): if hasattr(atr, 'set_nome'): atr.set_nome(' ' + nome, chave) class Modelo(object): metaclass = ModeloMeta

➏ esquema final +

➏ esquema final

Oficinas Turing: computação para programadores Próximos lançamentos: 4ª turma de Python para quem sabe Python 3ª turma de Objetos Pythonicos 1ª turma de Aprenda Python com um Pythonista