Desenvolvimento

16 jan, 2019

Aprendendo Python do zero

Publicidade

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 ser uma linguagem bonita e 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 e aprendizado de máquina (são exemplos que brilham aqui). Google, Netflix, Quora, Pinterest e Spotify estão usando Python, por exemplo. E, entre as 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, é bastante fácil definir uma variável e atribuir um valor à ela. Vamos imaginar que queremos armazenar um valor 1 em uma variável chamada one:

one = 1

Prontinho! Simples, não? Atribuímos o valor 1 à variável one. Conseguimos ter inúmeras variáveis.

two = 2
some_number = 10000

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.

# booleans
true_boolean = True
false_boolean = False

# string
my_name = "Leandro Tk"

# float
book_price = 15.80

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 usaremos 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:

if True:
  print("Hello Python If")

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

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 somente é executado caso a expressão seja False.

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

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

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

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")
  • 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("1is not greater than 2") é executado

Caso o elif 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 dois deles: while e for.

Iterando com while: enquanto a declaração tiver o valor True, o código dentro do bloco continuará sendo executado.

No exemplo a seguir nosso código fará o print dos números de 1 a 10:

num = 1

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

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.

Neste 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 a 11, o loop condition se torna False e é hora de deixar o bloco de código. Outro código para entendermos melhor o while:

loop_condition = True

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

O loop_condition é inicializado com True, logo, continua a iterar, até que atualizemos a variável loop_condition para ser False e saiamos do loop.

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

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

Neste 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)

Imagine 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, etc.

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

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

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

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

Mas você pode estar se 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.

Usaremos Python para entender melhor essa ideia na prática:

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

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 imagine que não queremos apenas armazenar números inteiros. Vamos guardar uma lista com os nomes das pessoas da minha família:

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

print(relatives_names[4]) # Kaio

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:

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 é super simples. Só precisamos passar o novo valor como parâmetro do método.

Neste exemplo, definimos a lista bookshelf – uma lista vazia, nenhum valor pré-adicionado. Depois usamos o método append duas 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:

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

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

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

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

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.

Neste 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:

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

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.

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

dictionary_tk['age'] = 24

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

Neste 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:

bookshelf = [
  "The Effective Engineer",
  "The 4 hours work week",
  "Zero to One",
  "Lean Startup",
  "Hooked"
]

for book in bookshelf:
    print(book)

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. 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:

dictionary = { "some_key": "some_value" }

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

Neste 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:

dictionary = { "some_key": "some_value" }

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

# some_key --> some_value

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

Vamos a outro exemplo:

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

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 iteraçã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 e capacidade de assentos, e também possui comportamentos: aceleram, freiam, mostram 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.

  • Dados → Atributos
  • Comportamentos → Métodos

Outro conceito importante são as classes. A Classe é um modelo, uma abstração, um molde que os objetos se baseiam 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. Veremos como a sintaxe de uma classe é definida em Python:

class Vehicle:
    pass

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.

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

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 quatro 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:

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

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, movido à energia elétrica, possui cinco assentos e a sua velocidade máxima é de 250km/h. Hora de criar!

tesla_model_s = Vehicle(4, 'electric', 5, 250)
  • 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:

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

Aqui temos 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:

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

Dessa forma podemos usar esses métodos para acessar ou setar 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

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

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')

Agora chamaremos esse método com nosso objeto e ele vai imprimir a string “VRRRRUUUUM.”

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

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:

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

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

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

Dentro da classe:

class Person:
    first_name = 'TK'

Neste 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“.

tk = Person()
print(tk.first_name) # => 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 de 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:

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

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

Observação: 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:

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

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

tk = Person('TK', 'tk@mail.com')
print(tk._email) # tk@mail.com
  • 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:

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 acessar esses atributos não-públicos usando métodos (públicos):

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 – Inicializamos um novo objeto com o first_name TK e e-mail tk@mail.com
  • 2 – Imprimimos o e-mail acessando o atributo não-público com o método
  • 3 – Tentamos atribuir um novo valor para o atributo e-mail 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:

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:

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

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:

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

    def _show_age(self):
        return self._age

Agora tentaremos chamar esse método com nosso objeto:

tk = Person('TK', 25)
print(tk._show_age()) # => 25
  • 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:

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

Neste 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 (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á oculta e 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:

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

Instanciamos o objeto e podemos acessar normalmente nossos atributos:

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

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).

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. 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!

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

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

E é isso!

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 artigos sobre desenvolvimento de software, Python e programação em geral, me acompanhe no portal.

Have fun, keep learning, and always keep coding