Aprendendo Python do zero

Aprendendo Python com teoria e de uma forma prática.


O que de fato é Python? De acordo com o Guido van Rossum, Python é

“high-level programming language, and its core design philosophy is all about code readability and a syntax which allows programmers to express concepts in a few lines of code.”

Ou seja, é uma linguagem de programação com a filosofia de que a legibilidade do código e a sintaxe permitem que o desenvolvedor consiga se expressar em poucas linhas de código.

Para mim, o primeiro grande motivo de ter aprendido Python foi o fato de que é uma linguagem bonita, elegante. Eu me sinto bem expressando meus pensamentos e lógicas em Python. É natural.

Outro grande motivo é que podemos usar Python em diferentes áreas: ciência de dados, desenvolvimento web, aprendizado de máquina são exemplos que brilham aqui. Google, Netflix, Quora, Pinterest e Spotify estão usando Python, por exemplo. E, falando de empresas brazucas, temos a Geekie, Globo.com, Nuveo, Loggi, entre muitas outras. Vamos aprender um pouco sobre isso.


O Básico

1. Variáveis

Podemos pensar em variáveis como palavras que guardam um valor. Simples assim.

Em Python, é facinho definir uma variável e atribuir um valor à ela. Vamos imaginar que queremos armazenar um valor 1 em uma variável chamada one:

Prontinho! Simples, não? A gente atribuiu o valor 1 à variável one.

E conseguimos ter inúmeras variáveis.

Como vimos no código acima, temos a variável two e atribuímos o valor 2 (um número inteiro). E nossa outra variável some_number com o valor 10000 armazenado.

Além disso, conseguimos atribuir vários tipos de valores em nossas variáveis, além de números inteiros. Conseguimos lidar com valores booleanos (True / False), strings, números reais e outros tipos de dados.

Os valores booleanos são True e False. Temos a variável true_boolean e false_boolean que vão representar esses valores. A variável my_name guarda o valor do tipo string, que no caso, o valor é Leandro Tk. E, por último, um número com ponto flutuante armazenado na variável book_price.

Mas por hora, vamos apenas entender que existem vários tipos de dados e mais para frente vamos falar como vamos usar esses dados de diferentes maneiras.

2. Controle de fluxo: condicionais

A expressão if avalia se a declaração é True ou False. Se a declaração for True, o código dentro do if é executado. Vamos ver um exemplo:

No primeiro código, temos a declaração com o valor True. Ou seja, nosso print("Hello Python If") é executado.

Já no segundo, temos a declaração com a "pergunta" 2 é maior que 1?. A resposta óbvia é que sim, ou seja, é avaliada para True. Logo nosso código dentro do if é executado.

Temos o else também. E esse código é apenas executado, caso a expressão seja False.

Nesse caso, 1 é menor que 2, então o código dentro do if não é executado. E o código do else é executado.

Temos também o elif, que basicamente significa else if, ou seja, espera uma expressão, como nesse próximo exemplo:

  • if pergunta se 1 é maior que 2?: False, então não executa o código e passa para o elif
  • elif pergunta se 2 é maior que 1?: True, logo o código print("1 is not greater than 2") é executado

Caso o elif também tivesse um valor False, o código executado seria o else.

3. Looping / Iterador

Em Python, temos várias formas de iterar, ou seja, fazer uma repetição do código. Vamos conversar aqui sobre 2 deles: while e for.

Iterando com while: Enquanto a declaração tiver o valor True, o código dentro do bloco vai continuar a ser executado.

Nesse exemplo debaixo, nosso código vai fazer o print dos números de 1 a 10

O while precisa de um loop condition, ou seja, uma condição, uma declaração que significa continuar ou parar a iteração. Se o loop condition for True, continua o loop. Se for False, o loop para de executar o código dentro do bloco do while.

Nesse exemplo, o num é inicializado com 1 e o primeiro loop condition é 1 <= 10 e o resultado é True, ou seja, continuamos a executar o bloco. Para cada iteração o num é incrementado.

Quando o num chega à 11, o loop condition se torna False e chega a hora de deixar o bloco de código.

Outro código para entendermos melhor o while:

O loop_condition é inicializado com True, logo continua a iterar, até que atualizamos a variável loop_condition para ser False e saírmos do loop.


Iterando com for: definimos uma variável que representará o valor de cada iteração. Vamos ver esse exemplo:

Nesse caso, definimos a variável i que vai representar cada valor da iteração, começando por 1 e terminando em 10. Dentro do bloco do for, executamos o print de cada valor i.


Listas (ou coleções, ou vetores, ou arrays)

Imaginemos que vamos armazenar o número inteiro 1 em alguma variável. Mas talvez queiramos armazenar o inteiro 2 também. E o 3, 4, 5

Será que temos alguma outra maneira de armazenar valores inteiros sem precisar de 1 milhão de variáveis? Yeap! Vamos falar sobre listas.

Listas são Estruturas de Dados, ou seja, podemos armazenar uma lista de valores dentro delas. Vamos usá-las!

Bem simples, não?! Aqui criamos uma lista com nome my_integers com o valores inteiros de 1 a 5.

Mas você pode estar perguntando: "como podemos pegar um valor dessa lista?"

Boa pergunta! A lista tem o conceito de índice (ou index). O primeiro valor da lista tem o índice 0. O segundo tem o 1. E assim por diante.

Para ficar claro, vamos representar essa lista e cada elemento com seu respectivo índice:

Aqui temos uma lista com os valores 5, 7, 1, 3 e 4. O valor 5 tem o índice 0 e assim por diante.

Vamos usar Python para entender melhor essa ideia na prática:

Armazenamos os valores na lista my_integers. E usamos o código my_integer[0] para acessar o valor que está no índice 0, ou seja, o valor 5. Testamos para o índice 1 e 4 também.

Agora vamos imaginar que não queremos apenas armazenar números inteiros. Vamos guardar uma lista com os nomes das pessoas da minha família:

Criamos a lista relatives_names e guardamos todos os nomes. Depois acessamos o nome que está no índice 4, nesse caso, o Kaio. Legal, funcional igualmente aos números inteiros.

Bom, acabamos de aprender como os índices de uma lista funcionam. Mas ainda preciso mostrar como adicionar mais valores para uma lista.

O método mais comum para adicionar um novo elemento na lista se chama append. Vamos ver como funciona:

append é super simples. Só precisamos passar o novo valor como parâmetro do método.

Nesse exemplo, definimos a lista bookshelf. Uma lista vazia, nenhum valor pré-adicionado. Depois usamos o método append 2 vezes. Um para adicionar o livro The Effective Engineer e depois para adicionar outro livro The 4 Hour Work Week. Se acessarmos o valor no índice 0, vamos obter o livro The Effective Engineer, por exemplo.

Bom, por hora é o suficiente sobre listas. Agora vamos falar sobre outra estrutura de dados.


Dicionário: Estrutura de Dados de chave-valor

Agora sabemos que as listas usam números inteiros como índices. Mas e se não quisermos usar números inteiros nos nossos índices? Alguma estrutura de dados que possamos usar índices como número, string ou outro tipo de valor.

Vamos aprender sobre Dicionários. Um dicionário é uma estrutura de dados, uma coleção de pares chave-valor. Vamos ver como funciona na prática:

A chave é o "índice" que aponta para um valor. Nesse exemplo, a chave "key1" aponta para o valor "value1".

Como acessamos o valor no dicionário? Usando a chave.

Aqui criamos um dicionário dictionary_tk. Com informações sobre mim: meu nome, apelido e nacionalidade. Cada atributo é uma chave no dicionário.

Assim como aprendemos como acessar um valor da lista usando índices, também podemos usar índice (no caso dos dicionários — chaves) para acessar um valor do dicionário.

Nesse exemplo, acessamos todos os atributos do dicionário.

  • Pegar o nome: dictionary_tk["name"]
  • Pegar o apelido: dictionary_tk["nickname"]
  • Pegar a nacionalidade: dictionary_tk["nationality"]

Bem simples, não?!

Outro conceito legal de dicionários, é que podemos usar qualquer outro tipo de dados como valor. Nesse próximo exemplo vamos usar um inteiro como valor:

Complementando aquele primeiro exemplo, agora adicionamos a chave "age" e colocamos 24 como valor.

E assim como adicionamos novos valores na lista, podemos fazer isso também em dicionários. Lembra que uma chave sempre aponta para um valor? Esse conceito é uma parte essencial do dicionário. E isso também é verdade quando falamos sobre adicionar novos valores para essa estrutura de dados.

Nesse exemplo temos o dicionário dictionary_tk com os valores name, nickname e nationality. Mas agora queremos adicionar um novo par age com o valor 24. Simplesmente atribuímos o valor 24 para a chave age no dicionário dictionary_tk. Nada muito complicado aqui.


Repetição: Iterando Estruturas de Dados

Assim como aprendemos no Python Básico, a iteração em listas é bem simples. Nós, desenvolvedores Python, usamos comumente o for loop:

A primeira coisa é definir a nossa lista, no caso, bookshelf, que contêm livros como strings. Então iteramos com o for. Para cada livro (book) no bookshelf, vamos imprimir esse valor.

book é uma variável que definimos dentro do escopo do for. Ou seja, é apenas um nome que definimos. Podemos trocar esse nome por qualquer outra coisa, por exemplo, bla. Mas usamos book nesse caso por semântica.


Agora, para iterar um dicionário, também podemos usar o for loop, mas temos acesso à chave, ao invés do valor:

Nesse exemplo definimos a variável key, que representa cada chave do dicionário. Para cada iteração, vamos imprimir a chave e o valor correspondente àquela chave.

Outra forma de iterar um dicionário é utilizar o método items() para que tenhamos acesso não apenas à chave, mas também ao valor:

Nesse exemplo vemos que agora definimos duas variáveis para cada iteração: key e value. Agora temos acesso a ambos os atributos do dicionário.

Vamos a outro exemplo:

Aqui voltamos àquele dicionário dictionary_tk com as minhas informações. Usamos o método items() para acessar a chave e valor, que definimos como attribute e value respectivamente. Para cada iteção estamos imprimindo novamente.


Classes & Objetos

Um pouco de teoria:

Objetos são uma tentativa de representação de objetos do mundo real como, por exemplo, carros, cachorros ou bicicletas. Os objetos compartilham duas principais características: dados e comportamentos.

Vamos pegar o carro como exemplo. Carros têm dados como número de rodas, número de portas, a capacidade de assentos. E também possui comportamentos: eles aceleram, param, mostra a quantidade de combustível e muitas outras coisas.

Nós identificamos dados como atributos e comportamentos como métodos em programação orientada a objetos.

DadosAtributos

ComportamentosMétodos

Outro conceito importante são as classes. A Classe é um modelo, uma abstração, um molde que os objetos se baseam para serem criados. No mundo real, comumente achamos muitos objetos que se agrupam no mesmo "tipo". Como carros. Todos com o mesmo modelo (todos têm um motor, rodas, portas, e assim por diante). Cada carro é criado por esse mesmo modelo com os mesmos componentes.

Programação Orientada a Objetos em Python

Python, como uma linguagem orientada a objetos, tem esses conceitos: classe e objeto.

Relembrando: a classe é um modelo para um objeto, uma maneira para definir atributos e métodos.

Agora queremos definir uma classe veículo (Vehicle) em Python. Vamos ver como a sintaxe de uma classe é definida em Python:

Definimos uma classe com o termo class — e apenas isso. Simples, não?

E agora vamos criar objetos a partir desse modelo, dessa classe que definimos.

Aqui temos o car, um objeto da classe Vehicle. (Um objeto também é comumente chamado de instância de uma classe).

Lembrando que a nossa classe Vehicle tem 4 atributos: número de rodas, tipo de tanque, capacidade de assentos e velocidade máxima. Atribuímos esses valores quando criamos o objeto. Esse é um exemplo de como passamos os dados dos atributos quando inicializamos o objeto:

Usamos o método init. Chamamos ele de método construtor. Ele constrói um objeto a partir da classe definida. Então quando criamos um novo objeto, precisamos passar para esse método os dados dos atributos.

Imagine que amamos o Tesla Model S e queremos criar um objeto desse modelo. Tem quatro rodas, roda com energia elétrica, com cinco espaços para assento e a sua velocidade máxima é de 250km/hora. Hora de criar!

  • number_of_wheel: 4
  • type_of_tank: 'electric'
  • seating_capacity: 5
  • maximum_velocity: 250

Todos os valores atribuídos. Mas como acessamos esses valores? Usamos os métodos, que são os comportamentos dos objetos. Vamos implementar isso:

Aqui tem a implementação de dois métodos: number_of_wheels e set_number_of_wheels. Chamamos de getter & setter.

  • getter: retorna o valor do atributo
  • setter: atribui um novo valor ao atributo

Em Python, podemos usar o @property (decorator) para definir getters e setters:

E assim podemos usar esses métodos para acessar ou setar atributos:

Agora vamos implementar um método que produz o som do carro. Vamos chamar esse método de make_noise:

Agora vamos chamar esse método com nosso objeto e ele vai imprimir a string VRRRRUUUUM.


Encapsulamento: ocultando informações

Encapsulamento é uma técnica para restringir acesso direto aos dados e métodos de um objeto. Mas, ao mesmo tempo, ele facilita operações em cima desses dados através de métodos (públicos — já já vamos falar sobre isso).

“Encapsulation can be used to hide data members and members function. Under this definition, encapsulation means that the internal representation of an object is generally hidden from view outside of the object’s definition.” — Wikipedia

Toda representação de um objeto fica ocultado do mundo externo. Apenas o próprio objeto consegue interagir com seus dados internos.

Então primeiro precisamos entender como atributos e métodos públicos e não-públicos funcionam.

Atributos públicos

Para uma classe Python, podemos inicializar um atributo público dentro do nosso método construtor.

Dentro do método construtor:

Nesse exemplo passamos o valor de first_name como parâmetro e atribuímos ao nosso atributo público (no caso self.first_name).

Dentro da classe:

Nesse exemplo não passamos o valor no método construtor. Apenas definimos esse atributo dentro da classe. E, nesse caso, todos os objetos instanciados da classe (criados a partir da classe) terão esse atributo de classe inicializado com o valor "TK".

Legal! Agora aprendemos como podemos usar atributos públicos tanto a nível de instâncias, quanto de classes. Outra coisa interessante sobre a parte pública é que podemos gerenciar o valor desse atributo. O que eu quero dizer com isso? Nosso objeto pode gerenciar os valores do seus atributos: usando Get e Set para esses atributos.

Continuando com a ideia da classe Person, agora queremos atribuir um novo valor para o atributo first_name:

Pronto, atualizamos o nosso atributo first_name para um novo valor (Kaio). Bem simples. Dado que é um atributo público, podemos atribuir novos valores sem tantos problemas.

Atributos não-públicos

We don’t use the term “private” here, since no attribute is really private in Python (without a generally unnecessary amount of work). — PEP 8
OBS: A PEP 8 é um Style Guide for Python Code, ou seja, um guia de boas práticas para escrever código Python. E como boa prática, desenvolvedores Python não usam o termo "privado" para atributos, dado que um atributo em Python não é realmente privado. Por isso usamos o termo "não-público".

Assim como nosso atributo público, também podemos definir o nosso atributo não-público no método construtor e dentro da classe.A única diferença de sintaxe é que para atributos não-públicos, usamos o underscore (_) antes do nome do atributo.

“‘Private’ instance variables that cannot be accessed except from inside an object don’t exist in Python. However, there is a convention that is followed by most Python code: a name prefixed with an underscore (e.g. _spam) should be treated as a non-public part of the API (whether it is a function, a method or a data member)” — Python Software Foundation

Como escrito na Python Software Foundation, não existem atributos privados e sim atributos não-públicos com a convenção de usar underscore.

Aqui um exemplo:

Veja o atributo self._email. É dessa forma que definimos um atributo não-público.

We can access and update it. Non-public variables are just a convention and should be treated as a non-public part of the API.

E, no texto da Python Software Foundation, é enfatizado que podemos acessar e atualizar esses valores não-públicos, dado que é apenas uma convenção. Mas como convenção, deve ser respeitada como uma parte não-pública de uma classe.

Para seguir essa convenção de não acessar diretamente nossos atributos não-públicos, usamos métodos para isso. Vamos implementar dois métodos (email e update_email) para entender como isso funciona:

Agora podemos acessar esses atributos não-públicos usando métodos (públicos):

  1. Inicializamos um novo objeto com o first_name TK e email tk@mail.com
  2. Imprimimos o email acessando o atributo não-público com o método
  3. Tentamos atribuir um novo valor para o atributo email de fora da nossa classe
  4. Precisamos tratar e respeitar nossa convenção de atributos não-públicos como uma parte não pública da classe
  5. Atualizamos nosso atributo com nosso método (público)
  6. Sucesso! Conseguimos atualizar o dado do nosso atributo com ajuda do nosso método

Métodos Públicos

Assim como atributos públicos, os métodos públicos também têm acesso direto de fora da classe:

Vamos testar:

Funciona como esperado, podemos acessar sem problemas.

Métodos não-públicos

Mas com métodos não-públicos não podemos acessá-los, por convenção. Vamos implementar a mesma classe Person, mas agora com um método não-público show_age usando underscore:

Agora vamos tentar chamar esse método com nosso objeto:

We can access and update it. Non-public methods are just a convention and should be treated as a non-public part of the API.

Assim como nossos atributos, nosso método não-público é apenas uma convenção.

Esse próximo exemplo mostra como podemos usar esses métodos:

Nesse exemplo implementamos o método _get_age que tratamos como não-público, ou seja, só podemos usá-lo a nível de classe (ou seja, dentro da nossa classe). Depois implementamos o método (nesse caso público) show_age que usa o método _get_age para poder acessar o atributo age.

Agora, fora da nossa classe, podemos usar o show_age, nosso método público, e apenas acessar o _get_age de dentro da nossa classe. Lembrando de novo: é uma questão de convenção.

Resumo do Encapsulamento

Com encapsulamento, podemos garantir que a representação interna (atributos e métodos) do nosso objeto está oculto fora da nossa classe.


Herança: herdando comportamentos e características

Alguns objetos têm algumas coisas em comum: seus comportamentos e características.

Por exemplo, eu herdei algumas características e comportamentos dos meus pais.

Como característica, herdei os olhos e cabelo. Como comportamento, a introversão.

Em programação orientada a objetos, classes podem herdar características (dados) e comportamentos (métodos) comuns de outra classe.

Agora vamos ver um exemplo e implementá-lo em Python.

Imagine um carro. Número de rodas, capacidade de assentos e velocidade máxima são todos atributos de um carro. Podemos falar que um carro elétrico possui esses mesmos atributos, ou seja, ele pode herdar essas características do carro.

Implementando a classe Car:

Agora, instanciamos o objeto e podemos acessar normalmente nossos atributos:

Agora, queremos implementar a classe ElectricCar que vai herdar da classe Car. Em Python, passamos a classe “pai” (no caso, a classe Car) como parâmetro da classe “filha” (ElectricCar).

Simples assim. Passamos o Car como parâmetro de ElectricCar e usamos o método construtor para inicializar os nossos atributos. Não precisamos de mais nenhum método ou definição de atributos, dado que a nossa classe ElectricCar já herda todas essas características. Vamos testar!

Funciona perfeitamente! Podemos acessar nossos atributos sem problemas, apenas herdando da classe Car.


That’s it!

Aprendemos muitas coisas sobre o básico de Python:

  • Como as variáveis em Python funcionam
  • Como funciona o controle de fluxo — condicionais
  • Como Python lida com loop, iteração, mais especificamente while e for
  • Como listas funcionam
  • Dicionários, uma estrutura de dados de chave-valor
  • Como iteramos sobre essas estruturas de dados
  • Objetos e Classes
  • Atributos como dados de um objeto
  • Métodos como comportamento de um objeto
  • Usando getters e setters em Python & decorator property
  • Encapsulamento: ocultando informações
  • Herança: herdando comportamentos e características

Parabéns! Você completou um conteúdo denso sobre Python :)


Para mais posts sobre desenvolvimento de software, Python e programação em geral, me segue aqui Medium.

Have fun, keep learning, and always keep coding.

Meu Twitter & Github. ☺