Python Iluminado

Módulos

Módulo é um arquivo que contém definições e comandos/instruções Python.

Um módulo pode conter tanto instruções executáveis quanto definições de funções e classes. Essas instruções servem para inicializar o módulo. Eles são executados somente na primeira vez que o módulo é encontrado em uma instrução de importação.

Podemos dizer que os módulos são bibliotecas que podem nos ajudar com funções definidas para uso imediato, em outras palavras, para usarmos um módulo devemos compreender como utilizar suas funções, sem a necessidade de mergulharmos em detalhes específicos de implementação.

Intuição dos Módulos

Também podemos imaginar um módulo como um conjunto de ferramentas disponíveis para usarmos para solucionar determinados problemas.

A figura a seguir ilustra esta ideia, onde temos um módulo chamado de ferramentas:

img

Imagine agora que desejamos importar todas as ferramentas do módulo ferramentas, para isso, podemos usar o seguinte comando:

import ferramentas

Agora pense em uma situação em que desejamos apenas importar uma ferramenta específica. Podemos expressar essa ideia da seguinte forma:

from ferramentas import martelo

Também podemos importar mais de uma ferramenta, separando-as por vírgula:

from ferramentas import tesoura, alicate

Uma outra maneira de obtermos todas as ferramentas é usando o *, neste caso, expressamos da seguinte forma:

from ferramentas import *

Dessa forma temos todas as ferramentas em mãos e não precisamos usar o prefixo ferramentas quando desejarmos usá-las.

Definindo Módulos

Vejamos agora como estes conceitos funcionam na prática, para isso vamos definir duas simples funções:

def add(x, y):
"""Recebe dois números x e y e retorna sua soma"""
return x + y
def multiply(x, y):
"""Recebe dois números x e y e retorna sua multiplicação"""
return x * y

Salvamos ambas as funções em um arquivo de nome modulo.py, agora vamos criar um novo arquivo no mesmo diretório e importaremos nosso módulo para utilizá-lo:

import modulo
print(modulo.add(4,5)) # 9
print(modulo.multiply(3,3)) # 9

Veja que utilizamos o comando import para buscarmos o nosso módulo e posteriormente invocamos as funções dele prefixando elas com a palavra modulo.

Importando Módulos

Imagine agora que temos um módulo chamado matematica. Os principais formatos para importarmos nossos módulos são:

import matematica
  • Tudo que está no módulo matematica é importado
  • Para se referir aos métodos de matematica, adicionamos o prefixo "matematica" em frente ao seu nome
matematica.nomeclasse.metodo()
matematica.funcao()

Importando usando o asterisco *:

from matematica import *
  • Tudo no módulo matematica é importado
  • Para se referir a qualquer método no módulo, basta usar seu nome, sem precisarmos prefixar
  • Lembre de ter atenção ao usar esse tipo de import, pois ele pode sobrescrever a definição de uma variável ou função já existente
nomeclasse.metodo()
funcao()

Importando uma classe ou função específica:

from matematica import nomeclasse
  • Apenas o item nomeclasse será importado
  • Após importar nomeclasse você pode usar ele sem o prefixo do módulo, ele é colocado no namespace atual
  • Tome cuidado ao usar, pois pode sobrescrever a definição do nome, caso ele já esteja no namespace
nomeclasse.metodo()
funcao()

Além de funções, os módulos também podem conter variáveis e estruturas de dados (listas, tuplas, dicionários, objetos). Vamos então criar mais um módulo para testarmos, nomearemos o nosso arquivo como estudantes.py.

estudantes = [
{'nome':'Uriel','idade':27,'aprovado':True},
{'nome':'Emanuel','idade':25,'aprovado':True},
{'nome':'Malthael','idade':29,'aprovado':False}
]
def imprimir_aprovados():
for estudante in estudantes:
if estudante['aprovado']:
print(f'{estudante["nome"]} está aprovado')

Iremos agora importar o módulo estudantes e podemos acessar a lista de dicionários estudantes, perceba que fornecemos o mesmo nome para ambos, mas isso é uma escolha livre que temos. Também temos acesso a função para imprimir os estudantes que estão aprovados.

from estudantes import *
estudante = estudantes[2]['nome']
print(estudante) # Malthael

Vamos agora invocar a função imprimir() para vermos quais estudantes estão aprovados:

imprimir()
# Uriel está aprovado
# Emanuel está aprovado

Observe que usamos o * para importar o módulo por completo, por este motivo não foi necessário prefixarmos a função imprimir da seguinte forma estudantes.imprimir().

Nomeando um Módulo

Podemos dar um apelido para um módulo, tornando-o mais fácil de nos referirmos a ele, para isso usamos a palavra-chave as:

import estudantes as e
print(e.estudantes[0]['idade']) # 27
e.imprimir()
# Uriel está aprovado
# Emanuel está aprovado

Módulos Construídos do Python

Existem diversos módulos que já são construídos no Python e nós podemos importá-los quando quisermos sem a necessidade de instalação.

Módulo platform

O módulo platform nos permite o acesso aos dados de identificação da plataforma subjacente.

import platform
s = platform.system()
print(s) # Linux

A função dir()

Existe uma função muito importante chamada dir() que lista todos os nomes das funções (e nome de atributos e variáveis) no módulo.

Vamos testá-la com o módulo de math:

import math
conteudo = dir(math)
print(conteudo) # ['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc']

Observe que nos é retornado uma grande quantidade de opções, entre atributos e funções, vejamos como podemos usar algumas delas.

Calculando a raiz quadrada de um número:

print(math.sqrt(4)) # 2.0

Calculando o logaritmo na base 2 de um número:

print(math.log2(8)) # 3.0

Calculando o logaritmo na base 10 de um número:

math.log10(100) # 2.0

Calculando o máximo divisor comum entre dois números:

math.gcd(50,85) # 5

Obtendo os valores de pi e e:

print(math.e) # 2.718281828459045
print(math.pi) # 3.141592653589793

Módulo collections

Este módulo implementa tipos de dados de contêiner especializados, fornecendo alternativas aos contêineres integrados de uso geral do Python, dict, list, set e tuple.

Imagine que temos por exemplo uma lista com diversos itens repetidos:

x = [1,1,1,2,2,3,3,5,9,9]

Desejamos contar o número de ocorrências de cada elemento desta lista, podemos usar o Counter do módulo collections:

from collections import Counter
Counter(x) # Counter({1: 3, 2: 2, 3: 2, 9: 2, 5: 1})

Um Counter é uma subclasse de dict para a contagem de objetos hashable. É uma coleção onde os elementos são armazenados como chaves de dicionário e suas contagens são armazenadas como valores de dicionário. As contagens podem ser qualquer valor inteiro, incluindo zero ou contagens negativas.

Observe que o número 1 ocorre 3 vezes, o 2 ocorre 2 e assim por diante.

Se quisermos saber os dois números com mais entradas, podemos utilizar o método most_common() passando como argumento o número 2:

Counter(x).most_common(2) # [(1, 3), (2, 2)]

Nos será retornado uma lista de tuplas de pares de valores, neste caso indicando que o número 1 ocorre 3 vezes e o 2 apenas 2 vezes.

Módulo statistics

O módulo statistics fornece funções para calcular estatísticas matemáticas de dados numéricos (com valor real).

Com ele podemos fazer cálculos como média, mediana, moda, variância e desvio padrão, essenciais na estatísticas.

Vamos então importar essas funções específicas e definir uma lista de números reais para exemplificar:

from statistics import mean, median, mode, variance, stdev
y = [-1.0, 2.5, 3.25, 5.75, 12.0, 37.5, 22.8, 99.13, -5.8, 2.5]

Calculando a média:

media = mean(y)
print(media) # 17.863

Calculando a mediana:

mediana = median(y)
print(mediana) # 4.5

Calculando a moda:

moda = mode(y)
print(moda) # 2.5

Calculando a variância:

variancia = variance(y)
print(variancia) # 977.4160233333333

Calculando o desvio padrão:

desvio_padrao = stdev(y)
print(desvio_padrao) # 31.263653390692095
from math import sqrt
desviopadrao = sqrt(variancia)
print(desviopadrao) # 31.263653390692095

Observe que o desvio padrão é apenas a raiz quadrada da variância.

Módulo itertools

O módulo itertools do Python é uma coleção de ferramentas para lidar com iteradores. Simplificando, iteradores são tipos de dados que podem ser usados em um for loop. O iterador mais comum em Python é a lista.

Para compreendermos melhor o funcionamento deste módulo, vamos considerar alguns exemplos, mas antes vamos importá-lo, abreviando-o com o nome it.

import itertools as it

A função accumulate() recebe um iterável como argumento e opcionalmente uma função. Ele retorna os resultados acumulados. Os próprios resultados estão contidos em um iterável.

dados = [1,3,4,7,9,25,45]
resultados = it.accumulate(dados)
for resultado in resultados:
print(f'{resultado} ',end='') # 1 4 8 15 24 49 94

Veja que por padrão ele nos traz a soma acumula dos números na sequência, se quisermos por exemplo a multiplicação acumulada, podemos usar a função de multiplicação do módulo operator para nos auxiliar nesta tarefa, porém para isso, precisamos importá-lo. Vejamos um exemplo:

import operator
multiplicacoes = it.accumulate(dados,operator.mul)
for multi in multiplicacoes:
print(f'{multi} ',end='') # 1 3 12 84 756 18900 850500

Dessa forma nos será trazida a multiplicação acumulada.

Outra função muito interessante do módulo itertools é a combinations(), esta função recebe um iterável e um inteiro R e criará todas as combinações exclusivas que possuem R membros.

formas = ['círculo','triângulo','quadrado','hexágono']
R = 2
combinacoes = it.combinations(formas,R)
for combinacao in combinacoes:
print(combinacao)
# ('círculo', 'triângulo')
# ('círculo', 'quadrado')
# ('círculo', 'hexágono')
# ('triângulo', 'quadrado')
# ('triângulo', 'hexágono')
# ('quadrado', 'hexágono')

Uma outra função muito interessante de itertools de "força bruta" é permutations(), que aceita um único iterável e produz todas as permutações (rearranjos) possíveis de seus elementos:

alpha = ['x','y','z']
permutacoes = it.permutations(alpha)
for permutacao in permutacoes:
print(permutacao)
# ('x', 'y', 'z')
# ('x', 'z', 'y')
# ('y', 'x', 'z')
# ('y', 'z', 'x')
# ('z', 'x', 'y')
# ('z', 'y', 'x')

As permutações são todas as maneiras diferentes que podemos agrupar um certo número de itens, onde a ordem importa.

Qualquer iterável de três elementos terá seis permutações, e o número de permutações de iteráveis mais longos cresce extremamente rápido. Na verdade, um iterável de comprimento n possui n! permutações, onde:

img

Para compreendermos melhor, vejamos uma tabela desses números, de n = 1 até n = 10:

nn!
22
36
424
5120
6720
75040
840.320
9362.880
103.628.800

O fenômeno de apenas algumas entradas produzindo um grande número de resultados é chamado de explosão combinatória.

A função cycle() nos permite criar um ciclo com um determinado iterador:

i = 0
for item in it.cycle([0,1]):
i += 1
if i == 15:
break
print(f'{item} ',end='')
# 0 1 0 1 0 1 0 1 0 1 0 1 0 1

É importante definirmos um ponto de parada para o nosso ciclo, caso contrário ficaremos preso em um loop infinito.

A função product() cria o produto cartesiano de uma série de iteráveis.

numeros = [1,2,3]
letras = ['a','b','c']
produto = it.product(numeros,letras)
for p in produto:
print(p)
# (1, 'a')
# (1, 'b')
# (1, 'c')
# (2, 'a')
# (2, 'b')
# (2, 'c')
# (3, 'a')
# (3, 'b')
# (3, 'c')

Como podemos ver os módulos nos permitem organizar o nosso código de maneira limpa e adequada e faz com que possamos trabalhar com bibliotecas do próprio Python e até mesmo outras feitas por terceiras pessoas, o que enriquece bastante o leque de possibilidades da linguagem Python, nos trazendo muitas funcionalidades adicionais.