Artigo original: https://www.freecodecamp.org/news/learning-python-from-zero-to-hero-120ea540b567/

Antes de mais nada, o que é Python? De acordo com seu criador, Guido van Rossum, Python é:

“Uma linguagem de programação de alto nível que tem em sua essência uma filosofia de design de código legível e uma sintaxe que permite aos programadores expressar conceitos em poucas linhas de código.”

Para mim, o primeiro motivo para aprender Python foi o fato de ser, realmente, uma bela linguagem de programação. Programar com ela e expressar meus pensamentos foi muito natural para mim.

Outro motivo foi a possibilidade de usar Python para diversas coisas: ciência de dados, desenvolvimento para a web e aprendizado de máquina são as que mais se destacam. Serviços como Quora, Pinterest e Spotify desenvolveram seus back-ends usando Python. Então, vamos aprender um pouco sobre ele.

O básico

1. Variáveis

Você pode pensar nas variáveis como palavras que armazenam um valor. Simples assim.

Em Python, é muito fácil criar uma variável e definir um valor para ela. Imagine que você deseja armazenar o número 1 em uma variável chamada “one”. Vamos fazer isso:

one = 1

Não foi simples? Você acabou de atribuir o valor 1 à variável “one”.

two = 2
some_number = 10000

E você pode atribuir qualquer outro valor a toda variável que desejar. Como você vê na tabela acima, a variável “two” armazena o número inteiro 2 e “some_number” armazena 10.000.

Além de números inteiros, também podemos usar booleanos (True/False), strings, float e outros tipos de dados.

# booleanos
true_boolean = True
false_boolean = False

# strings
my_name = "Leandro Tk"

# float (números com ponto flutuante)
book_price = 15.80

2. Fluxo de controle: declarações condicionais

O “if” usa uma expressão para avaliar se uma declaração é True (Verdadeira) ou False (Falsa). Se for True, a instrução dentro de “if” é executada. Por exemplo:

if True:
  print("Hello Python If")

if 2 > 1:
  print("2 is greater than 1")

2 é maior que 1, então o código “print” é executado.

A instrução dentro de “else” será executada se a expressão de “if” for falsa.

if 1 > 2:
  print("1 is greater than 2")
else:
  print("1 is not greater than 2")

1 não é maior que 2, então o código dentro da instrução “else” será executado.

Você também pode usar uma declaração “elif”:

if 1 > 2:
  print("1 is greater than 2")
elif 2 > 1:
  print("1 is not greater than 2")
else:
  print("1 is equal to 2")

3. Loop / Iterador

Em Python, podemos fazer uso da iteração de diferentes formas. Falarei de duas delas: while e for.

While: enquanto a instrução for True (verdadeira), o código dentro do bloco será executado. Portanto, o seguinte código vai imprimir o número 1 ao 10.

num = 1

while num <= 10:
    print(num)
    num += 1

O loop while precisa de uma “condição de loop”. Se a condição permanecer True (verdadeira), ele continuará fazendo a iteração. No exemplo, quando a variável num for igual a 11, a condição de loop será igual a False (falsa).

Aqui temos outro código básico para entender melhor:

loop_condition = True

while loop_condition:
    print("Loop Condition keeps: %s" %(loop_condition))
    loop_condition = False

A variável loop_condition é True, então o while continuará iterando até definirmos a variável como False.

For: você aplica a variável num  ao bloco e a instrução for  fará a iteração dela para você. O código a seguir produz o mesmo resultado que o código while anterior de 1 a 10.

for i in range(1, 11):
  print(i)

Vê? É muito fácil. O range (intervalo) começa no e vai até o 11º elemento (10 é o 10º elemento).

Listas: Coleções | Array | Estrutura de Dados

Imagine que você deseja armazenar o número inteiro 1 em uma variável. Agora, você talvez queira armazenar o número 2. E o 3, 4, 5…

Existe uma maneira de armazenar todos os números inteiros que eu quero, mas que não seja em milhões de variáveis? Com certeza. Existe uma outra maneira para armazená-los.

Uma lista é uma coleção que pode ser usada para armazenar uma lista de valores (como os números inteiros citados acima). Então vamos usar uma lista:

my_integers = [1, 2, 3, 4, 5]

É muito fácil. Criamos um array e o armazenamos em uma variável chamada my_integers.

Porém, você pode estar se perguntando: “Como obtenho um valor desse array?”

Ótima pergunta. Uma lista tem um conceito chamado index (índice). O primeiro elemento tem o valor de index 0 (zero). O segundo recebe o valor de index 1 e assim por diante. Você entendeu a ideia.

Para deixar mais claro, podemos representar o array e cada elemento com seu valor de index. Confira o seguinte desenho:

1_ReMk6NgghLII20vPD6uNEA

Usando a sintaxe do Python, também é simples de entender:

my_integers = [5, 7, 1, 3, 4]
print(my_integers[0]) # 5
print(my_integers[1]) # 7
print(my_integers[4]) # 4

Agora imagine que você não deseja armazenar números inteiros. Você só quer armazenar strings, como uma lista de nomes de seus parentes. A minha lista ficaria mais ou menos assim:

relatives_names = [
  "Toshiaki",
  "Juliana",
  "Yuji",
  "Bruno",
  "Kaio"
]

print(relatives_names[4]) # Kaio

Funciona exatamente da mesma forma que os números inteiros. Legal.

Acabamos de aprender como funcionam os índices de uma lista. Contudo, ainda precisamos aprender a adicionar um elemento à estrutura de dados da lista, ou seja, adicionar um item a uma lista.

O método mais comum utilizado para adicionar um novo valor a uma lista é o append. Vamos ver como ele funciona:

bookshelf = []
bookshelf.append("The Effective Engineer")
bookshelf.append("The 4 Hour Work Week")
print(bookshelf[0]) # The Effective Engineer
print(bookshelf[1]) # The 4 Hour Work Week

O append é muito simples. Você só precisa inserir o elemento (por exemplo, “The Effective Engineer”) como um parâmetro do append.

Certo, agora chega de listas. Vamos falar sobre outra estrutura de dados.

Dicionário: Estrutura de dados de chave-valor

Já sabemos que as Listas são indexadas com números inteiros. Mas e se não quisermos usar números inteiros como valores de index? Neste caso, saiba que temos algumas outras estruturas de dados que podemos utilizar como numéricas, strings ou outros tipos de índices.

Vamos aprender sobre a estrutura de dados do Dicionário. Dicionário é uma coleção de pares de chave-valor. É assim que ele se parece:

dictionary_example = {
  "key1": "value1",
  "key2": "value2",
  "key3": "value3"
}

A chave (key) serve como o índice que aponta para o valor (value). E como acessamos um valor em um Dicionário? Você adivinhou - usando a chave. Vamos testar:

dictionary_tk = {
  "name": "Leandro",
  "nickname": "Tk",
  "nationality": "Brazilian"
}

print("My name is %s" %(dictionary_tk["name"])) # My name is Leandro
print("But you can call me %s" %(dictionary_tk["nickname"])) # But you can call me Tk
print("And by the way I'm %s" %(dictionary_tk["nationality"])) # And by the way I'm Brazilian

Criei um dicionário sobre mim. Meu nome, apelido e nacionalidade. Esses atributos são as chaves (keys) do Dicionário.

Assim como usamos o número de index para acessar uma Lista, também usamos um índice (mas ao invés de números, as chaves) para acessar os valores armazenados em um Dicionário.

No exemplo, imprimi uma frase sobre mim usando todos os valores armazenados no Dicionário. Bem simples, certo?

Outra coisa legal sobre o Dicionário é que podemos usar qualquer coisa como valor. No Dicionário que criei, vou adicionar a chave age e atribuir a minha idade (um número inteiro) nela:

dictionary_tk = {
  "name": "Leandro",
  "nickname": "Tk",
  "nationality": "Brazilian",
  "age": 24
}

print("My name is %s" %(dictionary_tk["name"])) # My name is Leandro
print("But you can call me %s" %(dictionary_tk["nickname"])) # But you can call me Tk
print("And by the way I'm %i and %s" %(dictionary_tk["age"], dictionary_tk["nationality"])) # And by the way I'm Brazilian

Aqui temos um par de chave (age) e valor (24) . Uma string é a chave e um número inteiro é o valor.

Da mesma forma como fizemos com as Listas, agora vamos aprender como adicionar um novo elemento a um Dicionário. A chave que aponta para um valor é parte fundamental de um Dicionário. Portanto, quando adicionamos um novo elemento, também precisamos considerar a chave e o valor.

dictionary_tk = {
  "name": "Leandro",
  "nickname": "Tk",
  "nationality": "Brazilian"
}

dictionary_tk['age'] = 24

print(dictionary_tk) # {'nationality': 'Brazilian', 'age': 24, 'nickname': 'Tk', 'name': 'Leandro'}

Basta atribuir um valor e uma chave ao Dicionário. Nada complicado aqui, certo?

Iteração: Fazendo um loop através das estruturas de dados

Como aprendemos em Python Basics (texto em inglês), a iteração de uma Lista é algo muito simples. Nós, desenvolvedores de Python, geralmente usamos o loop for. Vamos experimentar:

bookshelf = [
  "The Effective Engineer",
  "The 4-hour Workweek",
  "Zero to One",
  "Lean Startup",
  "Hooked"
]

for book in bookshelf:
    print(book)

Portanto, para cada book na lista bookshelf, nós imprimimos (podemos fazer qualquer coisa aqui) o seu valor. Bem simples e intuitivo. Isso é Python.

Para uma estrutura de dados hash, também podemos usar o loop for, mas aplicamos a chave:

dictionary = { "some_key": "some_value" }

for key in dictionary:
    print("%s --> %s" %(key, dictionary[key]))
    
# some_key --> some_value

Este é um exemplo de como funciona. Para cada key em dictionary, imprimimos a key (chave) e seu value (valor) correspondente.

Outra maneira de fazer isso é usar o método iteritems.

dictionary = { "some_key": "some_value" }

for key, value in dictionary.items():
    print("%s --> %s" %(key, value))

# some_key --> some_value

Nós nomeamos os dois parâmetros como key e value, mas não é necessário. Podemos chamar eles de qualquer coisa. Vejamos:

dictionary_tk = {
  "name": "Leandro",
  "nickname": "Tk",
  "nationality": "Brazilian",
  "age": 24
}

for attribute, value in dictionary_tk.items():
    print("My %s is %s" %(attribute, value))
    
# My name is Leandro
# My nickname is Tk
# My nationality is Brazilian
# My age is 24

Podemos ver que usamos attribute como parâmetro para a chave do dicionário e funcionou corretamente. Ótimo!!

Classes e objetos

Um pouco de teoria:

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

Um carro possui dados, como número de rodas, número de portas e quantidade de assentos. Ele também tem comportamentos: podem acelerar, parar, mostrar quanto resta de combustível e tantas outras coisas.

Na programação orientada a objetos, nós identificamos dados como atributos e comportamentos como métodos. Novamente:

Dados → Atributos | Comportamento → Métodos

Já uma classe é o projeto a partir do qual os objetos individuais são criados. No mundo real, muitas vezes encontramos objetos do mesmo tipo, como os carros. Todos da mesma marca e modelo (e todos têm motor, rodas, portas e assim por diante). Cada carro foi construído a partir do mesmo projeto e tem os mesmos componentes.

Modo de programação orientada a objetos para Python: ATIVADO

Python, como uma linguagem de programação orientada a objetos, também possui esses conceitos: classe e objeto.

Uma classe é um projeto, um modelo para seus objetos.

Então, novamente, uma classe é apenas um modelo, ou uma maneira de definir atributos e comportamento (como falamos na seção de teoria). Como exemplo, uma classe Vehicle (Veículo) tem seus próprios atributos que definem quais objetos são veículos. O número de rodas, o tipo de tanque, a capacidade de assentos e a velocidade máxima são todos atributos de um veículo.

Com isso em mente, vamos ver como funciona a sintaxe de classes no Python:

class Vehicle:
    pass

Definimos uma classe fazendo a declaração class e pronto. Fácil, né?

Os objetos são instâncias de uma classe. Criamos uma instância nomeando a classe.

car = Vehicle()
print(car) # <__main__.Vehicle instance at 0x7fb1de6c2638>

Aqui car é um objeto (ou instância) da classe Vehicle.

Lembre-se de que nossa classe Vehicle tem quatro atributos: número de rodas, tipo de tanque, capacidade de assentos e velocidade máxima. Devemos definir todos esses atributos ao criar o objeto. Então, a seguir definimos nossa classe para receber esses dados quando ela for iniciada:

class Vehicle:
    def __init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity):
        self.number_of_wheels = number_of_wheels
        self.type_of_tank = type_of_tank
        self.seating_capacity = seating_capacity
        self.maximum_velocity = maximum_velocity

Veja que usamos o método init. Nós o chamamos de método construtor. Assim, quando criamos o objeto veículo, podemos definir os atributos. Imagine que adoramos o Tesla Model S e queremos criar um objeto dele. Ele tem quatro rodas, funciona com energia elétrica, tem espaço para cinco pessoas e a velocidade máxima é de 250 km/h. Vamos criar este objeto:

tesla_model_s = Vehicle(4, 'electric', 5, 250)

Quatro rodas + “tipo de tanque” elétrico + cinco lugares + 250 km/h de velocidade máxima.

Todos os atributos foram definidos. Mas como fazemos para acessar os valores desses atributos? Simplesmente enviando uma mensagem para o objeto perguntando sobre eles. Chamamos isso de método. É o comportamento do objeto. Vamos implementar isso:

class Vehicle:
    def __init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity):
        self.number_of_wheels = number_of_wheels
        self.type_of_tank = type_of_tank
        self.seating_capacity = seating_capacity
        self.maximum_velocity = maximum_velocity

    def number_of_wheels(self):
        return self.number_of_wheels

    def set_number_of_wheels(self, number):
        self.number_of_wheels = number

Esta é uma implementação de dois métodos: number_of_wheels e set_number_of_wheels. Chamamos isso de getter e setter. O primeiro método obtém o valor do atributo e o segundo define um novo valor para o atributo.

Em Python, podemos fazer isso usando @property (decorators) para definir getters e setters. Vamos ver com o código:

class Vehicle:
    def __init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity):
        self.number_of_wheels = number_of_wheels
        self.type_of_tank = type_of_tank
        self.seating_capacity = seating_capacity
        self.maximum_velocity = maximum_velocity
    
    @property
    def number_of_wheels(self):
        return self.__number_of_wheels
    
    @number_of_wheels.setter
    def number_of_wheels(self, number):
        self.__number_of_wheels = number

E podemos usar esses métodos como atributos:

tesla_model_s = Vehicle(4, 'electric', 5, 250)
print(tesla_model_s.number_of_wheels) # 4
tesla_model_s.number_of_wheels = 2 # setting number of wheels to 2
print(tesla_model_s.number_of_wheels) # 2

Isso é um pouco diferente de definir métodos. Os métodos funcionam como atributos. Por exemplo, para definirmos um novo número de rodas, não passamos o número 2 como parâmetro, mas atribuímos o valor 2 para number_of_wheels. Esta é uma maneira de escrever um código pythônico getter e setter.

Também podemos usar métodos para outras coisas, como o método make_noise. Vejamos:

class Vehicle:
    def __init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity):
        self.number_of_wheels = number_of_wheels
        self.type_of_tank = type_of_tank
        self.seating_capacity = seating_capacity
        self.maximum_velocity = maximum_velocity

    def make_noise(self):
        print('VRUUUUUUUM')

Quando chamamos esse método, ele retorna a string “VRRRRUUUUM”.

tesla_model_s = Vehicle(4, 'electric', 5, 250)
tesla_model_s.make_noise() # VRUUUUUUUM

Encapsulamento: Ocultando informações

O encapsulamento é um mecanismo que restringe o acesso direto aos dados e aos métodos dos objetos. Ao mesmo tempo, facilita a operação nesses dados (métodos dos objetos).

“O encapsulamento pode ser usado para ocultar membros de dados e funções de membros. De acordo com esta definição, o encapsulamento significa que a representação interna de um objeto é geralmente ocultada fora da definição do objeto.” — Wikipédia

Toda representação interna de um objeto é ocultada da parte externa. Apenas o objeto pode interagir com seus dados internos.

Primeiro, precisamos entender como as variáveis e métodos públicos e não públicos de instâncias funcionam.

Variáveis públicas de instância

Para uma classe Python, podemos inicializar uma variável pública de instância dentro de nosso método construtor. Vejamos:

Dentro do método construtor:

class Person:
    def __init__(self, first_name):
        self.first_name = first_name

Aqui passamos o valor de first_name como um argumento para a variável pública de instância:

tk = Person('TK')
print(tk.first_name) # => TK

Dentro da classe:

class Person:
    first_name = 'TK'

Aqui, não precisamos passar o valor de first_name como argumento. Todas as instâncias deste objeto serão inicializadas com o valor TK no atributo da classe.

tk = Person()
print(tk.first_name) # => TK

Legal. Aprendemos que podemos usar variáveis públicas de instância e atributos de classe. Outra coisa interessante sobre a parte pública é que podemos gerenciar o valor da variável. O que eu quero dizer com isso? Nosso objeto pode gerenciar seu valor de variável: Usar Get e Set nas variáveis.

Seguindo o exemplo da classe Person, digamos que desejamos definir outro valor para a variável first_name:

tk = Person('TK')
tk.first_name = 'Kaio'
print(tk.first_name) # => Kaio

Pronto. Acabamos de definir outro valor (kaio) para a variável de instância first_name. Simples assim. Como é uma variável pública, podemos fazer isso.

Variáveis não públicas de instância

Não usamos o termo “privado”, pois nenhum atributo é realmente privado em Python (sem uma quantidade de trabalho geralmente desnecessária). - PEP 8

Assim como na variável pública de instância, podemos definir a variável não pública de instância tanto no método construtor quanto na classe. A diferença de sintaxe é: para variáveis não públicas, usamos um sublinhado (_) antes do nome da variável.

“As variáveis ‘privadas’ de instância que não podem ser acessadas exceto de dentro de um objeto não existem em Python. No entanto, existe uma convenção que é seguida pela maioria dos programadores e códigos de Python: um nome prefixado com um sublinhado (por exemplo, _spam) deve ser tratado como uma parte não pública da API (seja uma função, um método ou um membro de dados)” — Python Software Foundation

Veja um exemplo:

class Person:
    def __init__(self, first_name, email):
        self.first_name = first_name
        self._email = email

Observe a variável do e-mail _email. É assim que definimos uma variável não pública:

tk = Person('TK', 'tk@mail.com')
print(tk._email) # tk@mail.com
Podemos acessá-la e atualizá-la. As variáveis não públicas são apenas uma convenção e devem ser tratadas como uma parte não pública da API..

Então usamos métodos dentro da definição de nossa classe para acessar e alterar uma variável não pública. Vamos implementar dois métodos (email e update_email) para entendermos melhor:

class Person:
    def __init__(self, first_name, email):
        self.first_name = first_name
        self._email = email

    def update_email(self, new_email):
        self._email = new_email

    def email(self):
        return self._email

Agora podemos atualizar e acessar as variáveis não públicas usando esses métodos. Vejamos:

tk = Person('TK', 'tk@mail.com')
print(tk.email()) # => tk@mail.com
# tk._email = 'new_tk@mail.com' -- treat as a non-public part of the class API
print(tk.email()) # => tk@mail.com
tk.update_email('new_tk@mail.com')
print(tk.email()) # => new_tk@mail.com
  1. Iniciamos um novo objeto que terá o nome TK e o e-mail tk@mail.com;
  2. Imprimimos o endereço de e-mail acessando a variável não pública através do método email;
  3. Tentamos definir um novo endereço de e-mail;
  4. Precisamos tratar a variável não pública como uma parte não pública da API;
  5. Atualizamos a variável não pública com o método update_email da nossa instância;
  6. Sucesso! Conseguimos atualizá-la dentro de nossa classe com o método auxiliar

Método público

Os métodos públicos também podem ser usados fora de nossa classe:

class Person:
    def __init__(self, first_name, age):
        self.first_name = first_name
        self._age = age

    def show_age(self):
        return self._age

Vamos testar isso:

tk = Person('TK', 25)
print(tk.show_age()) # => 25

Ótimo - podemos usá-lo sem nenhum problema.

Método não público

Mas não podemos fazer isso com métodos não públicos. Vamos implementar a mesma classe Person, mas agora com o método não público show_age, usando o sublinhado (_) na frente.

class Person:
    def __init__(self, first_name, age):
        self.first_name = first_name
        self._age = age

    def _show_age(self):
        return self._age

E agora, vamos tentar chamar esse método não público com nosso objeto:

tk = Person('TK', 25)
print(tk._show_age()) # => 25
Podemos acessar e atualizar o valor. Os métodos não públicos são apenas uma convenção e devem ser tratados como uma parte não pública da API.

Aqui está um exemplo de como usar um método não público:

class Person:
    def __init__(self, first_name, age):
        self.first_name = first_name
        self._age = age

    def show_age(self):
        return self._get_age()

    def _get_age(self):
        return self._age

tk = Person('TK', 25)
print(tk.show_age()) # => 25

Aqui temos o método não público _get_age e o método público show_age. O show_age pode ser usado pelo nosso objeto (fora da nossa classe) e o _get_age pode ser usado apenas dentro da definição da nossa classe (dentro do método show_age). Mas novamente: é apenas uma questão de convenção.

Resumo do encapsulamento

Com o encapsulamento, podemos garantir que a representação interna do objeto seja ocultada do lado externo.

Herança: comportamentos e características

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

Por exemplo, herdei algumas características e comportamentos do meu pai. Herdei seus olhos e cabelos como características, e sua impaciência e introversão como comportamentos.

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

Vamos ver um exemplo disso e como implementar em Python.

Imagina um carro. Número de rodas, capacidade de assentos e velocidade máxima são todos atributos de um carro. Podemos dizer que uma classe ElectricCar herda esses mesmos atributos da classe Car regular.

class Car:
    def __init__(self, number_of_wheels, seating_capacity, maximum_velocity):
        self.number_of_wheels = number_of_wheels
        self.seating_capacity = seating_capacity
        self.maximum_velocity = maximum_velocity

Nossa classe Car implementou:

my_car = Car(4, 5, 250)
print(my_car.number_of_wheels)
print(my_car.seating_capacity)
print(my_car.maximum_velocity)

Uma vez iniciado, podemos usar todas as variáveis da instância criadas. Legal.

Em Python, aplicamos uma classe pai à classe filha como parâmetro. A classe ElectricCar pode herdar de nossa classe Car:

class ElectricCar(Car):
    def __init__(self, number_of_wheels, seating_capacity, maximum_velocity):
        Car.__init__(self, number_of_wheels, seating_capacity, maximum_velocity)

Simples assim. Não precisamos implementar nenhum outro método, pois essa classe já o possui os métodos herdados da classe Car. Vamos testar isso:

my_electric_car = ElectricCar(4, 5, 250)
print(my_electric_car.number_of_wheels) # => 4
print(my_electric_car.seating_capacity) # => 5
print(my_electric_car.maximum_velocity) # => 250

Lindo.

É isso aí!

Aprendemos muitas coisas sobre o básico do Python:

  • Como as variáveis do Python funcionam
  • Como as instruções condicionais do Python funcionam
  • Como funciona o loop do Python (while & for)
  • Como usar Listas: Coleção | Array
  • Dicionário de chave-valor
  • Como podemos iterar por essas estruturas de dados
  • Objetos e classes
  • Atributos como dados de objetos
  • Métodos como comportamentos dos objetos
  • Utilização de getters e setters do Python e o decorator property
  • Encapsulamento: ocultando informações
  • Herança: comportamentos e características

Parabéns! Você concluiu a leitura deste conteúdo denso sobre Python.

Se você busca por um curso completo de Python, quer aprender mais habilidades de programação utilizadas no mundo real e criar projetos, experimente o One Month Python Bootcamp. Vejo você lá ☺

Para mais histórias e posts sobre minha jornada aprendendo e dominando programação, siga aa publicação do autor, The Renaissance Developer.

Divirta-se, continue aprendendo e siga programando.

O Twitter e Github do autor. ☺