Dados em Memória no Python, Dicionários ou Listas?
28 de Junho de 2010, 0:00 - sem comentários aindaEsses dias eu estava desenvolvendo um modulo para o meu novo projeto. Esse novo modulo será responsável por "criar" um banco de dados e uma interface de comunicação. Esse banco de dados irá armazenar informações de alguns hosts de rede e seus detalhes. A interface de comunicação deve ser capaz de prover acesso a todas as informações de um dado host bem como definir métodos de adição, remoção e alteração de hosts.
Algumas pessoas podem se perguntar "por que não usar um banco de dados pronto?". Bem, eu acho que essa aplicação não exige um banco de dados de verdade. Ela só precisa realizar a persistência de alguns dados, buscas e alterações de registro, nada muito complexo.
Em Python, você dispõe de alguns módulos básicos que disponibilizam a possibilidade da serialização de dados. Dentre elas eu destaco o Shelve, o Pickle e o cPickle. Dentre essas eu escolhi o cPickle, uma reimplementação da biblioteca Pickle, que realiza a serialização de objetos ecrito em C. Ele é capaz de ser 1000 vezes mais rápido que o módulo Pickle. Diferentemente do Shelve o cPicke não define uma estrutura padrão para armazenar e recuperar dados, o que me levou a uma questão: Devo recuperar esses dados em uma lista ou em um dicionário?
Antes de responder, nós devemos entender a pergunta. Quais são os parâmetros para decidir qual desses objetos é mais vantajoso? Eu resalto dois quesitos: Tamanho em memória e "tempo de pesquisa".
Primeiramente vamos analisar a ocupação de memória por esses dois objetos:
No código acima, primeiramente eu importei a função getsizeof do módulo sys renomeando-a para size. Depois eu criei um dicionário com o conteúdo variado, conforme mostrado na linha a seguir. Depois podemos verificar que esse objeto ocupou 6284 bytes. Em seguida criei uma estrutura semelhante utilizando uma lista, que ocupou 840 bytes. Dessa forma podemos ver que uma lista ocupa menos espaço em memória que um dicionário. Ao realizar uma comparação podemos ver que o dicionário chegou a ocupar quase 7.5 vezes mais espaço em memória que uma lista.Código PHP:
>>> from sys import getsizeof as size
>>> d = {}
>>> for n in range(200):
... d[str(n)] = ['192.168.1.'+str(n), 'icon'+str(n)+'.jpg', 'campo'+str(n)]
...
>>> d['0']
['192.168.1.0', 'icon0.jpg', 'campo0']
>>> size(d)
6284
>>>
>>>
>>> l = []
>>> for n in range(200):
... l.append([str(n), '192.168.1.'+str(n), 'icon'+str(n)+'.jpg', 'campo'+str(n)])
...
>>> l[0]
['0', '192.168.1.0', 'icon0.jpg', 'campo0']
>>> size(l)
840
>>>
>>> float(size(d))/size(l)
7.480952380952381
Continuando a execução do código anterior, vamos fazer testes de consulta nessas estrutura.
Primeiro eu criei duas funções busca_na_lista e busca_no_dicionario. Em seguida criei duas funções de teste teste_lista, teste_dicionario. Em ambas são passados como argumento um numero de buscas e a estrutura que será buscada. A função time.time() é utilizada para gravar o tempo em segundos daquele instante, a subtração desse valores informa o tempo de busca. A função random.randrange é utilizado para gerar números aleatórios entre 0 e o tamanho da lista (nesse caso é 200) durante a busca. Podemos ver que ao realizar 200 buscas não é notado diferença de desempenho. Eu realizei mais alguns testes e até 800 buscas não havia diferença de tempo. Como esse teste depende muito das configurações da máquina é possível que os testes apresentem resultados diferentes. Com 2000 pesquisas é possível ver que a diferença ainda é pouca (0.016 segundos). Já no teste com 200000 buscas na lista vemos um aumento no tempo de resposta para pouco mais de 1 segundo (1.655 segundos) enquanto a mesma busca no dicionário leva 0.078 segundos.Código PHP:
>>> import random
>>> import time
>>>
>>> def busca_na_lista(nome, lista):
... for item in lista:
... if item[0] == nome:
... return item
...
>>>
>>> def busca_no_dicionario(nome, dicionario):
... return dicionario[nome]
...
>>>
>>> def teste_lista(n_buscas, lista):
... tamanho = len(lista)
... vetor_aleatorio = [random.randrange(0, tamanho) for i in range(n_buscas)]
... inicio = time.time()
... for n in vetor_aleatorio:
... x = busca_na_lista(str(n), lista)
... fim = time.time()
... print 'Inicio em: %s\tFim em: %s'%(inicio, fim)
... return fim - inicio
...
>>>
>>> def teste_dicionario(n_buscas, dicionario):
... tamanho = len(dicionario)
... vetor_aleatorio = [random.randrange(0, tamanho) for i in range(n_buscas)]
... inicio = time.time()
... for n in vetor_aleatorio:
... x = busca_no_dicionario(str(n), dicionario)
... fim = time.time()
... print 'Inicio em: %s\tFim em: %s'%(inicio, fim)
... return fim - inicio
...
>>>
>>> teste_lista(200, l)
Inicio em: 1277733024.04 Fim em: 1277733024.04
0.0
>>> teste_dicionario(200, d)
Inicio em: 1277733041.37 Fim em: 1277733041.37
0.0
>>>
>>>
>>> teste_lista(2000, l)
Inicio em: 1277735153.49 Fim em: 1277735153.5
0.016000032424926758
>>> teste_dicionario(2000, d)
Inicio em: 1277735159.19 Fim em: 1277735159.19
0.0
>>>
>>>
>>> teste_lista(200000, l)
Inicio em: 1277735175.41 Fim em: 1277735177.06
1.6559998989105225
>>> teste_dicionario(200000, d)
Inicio em: 1277735181.16 Fim em: 1277735181.24
0.078000068664550781
>>>
>>>
>>> teste_lista(200000, l) - teste_dicionario(200000, d)
Inicio em: 1277735213.7 Fim em: 1277735215.22
Inicio em: 1277735215.52 Fim em: 1277735215.6
1.437999963760376
>>> teste_dicionario(200000, d)/teste_lista(200000, l)
Inicio em: 1277735219.72 Fim em: 1277735219.8
Inicio em: 1277735220.1 Fim em: 1277735221.61
0.051485190272963291
>>>
>>>
Com esses dados tenho algumas conclusões:
1. Buscas em listas são mais rápidas do que eu imaginei;
2. Buscas em dicionários são incrivelmente rápidas;
3. Dicionários ocupam grandes espaços em memória;
4. Listas são muito boas para economia de memória.
Dado o conhecimento que tenho, arrisco afirmar que a lentidão das buscas em listas é causada somente pela forma que a busca é realizada. Como o dicionário já possui a busca embutida ela é extremamente mais rápida por ser um código escrito em C e compilado. O lado negativo do dicionário é que você pode acabar consumindo todo o recurso de memória da sua plataforma e prejudicando assim o tempo de busca, levando a um desempenho inferior ao que seria com as listas.
Desta forma, quem busca muito desempenho e possui recursos de memória sobrando utilize dicionários (com moderação). Se sua aplicação deve rodar em dispositivos com pouca memória desaconselho o uso de dicionários. Se quiser algo realmente rápido, talvez seja interessante escrever esse "trecho de interface" em Cython o que possivelmente garantirá um pouco mais de desempenho.
[Curso de Python] Funções e Argumentos Nomeados
24 de Junho de 2010, 0:00 - sem comentários aindaAntes de prosseguir leia os artigos anteriores aqui.
No último post eu mostrei o básico de uma função com argumentos. Só que em Python há algumas funcionalidades a mais nos argumentos.
Funções e Argumentos Nomeados
Como já apresentei, Python utiliza palavras para designar os argumentos. Essas palavras não são utilizadas somente dentro do escopo da função, elas podem ser utilizadas também para a identificar um argumento. Vamos tomar como exemplo a função abaixo:
Conforme exemplo acima, ao repassar os argumentos 2, 1 e 3 eles são vinculados às variáveis num1, num2 e num3 respectivamente. Até aqui nada de novo, mas vamos ver agora uma possibilidade que o Python nos fornece:Código PHP:
>>> def mostra_numeros(num1, num2, num3):
... print 'Numero 1:',num1
... print 'Numero 2:',num2
... print 'Numero 3:',num3
...
>>> mostra_numeros(2, 1, 3)
Numero 1: 2
Numero 2: 1
Numero 3: 3
>>> mostra_numeros(7,07, 8)
Numero 1: 7
Numero 2: 0
Numero 3: 8
Desta forma eu posso passar os argumentos desordenadamente para o Python. Eu tenho a liberdade de escolher quem será o num1, o num2 ou o num3. Para usar isso devemos apenas nos atentar para certos detalhes:Código PHP:
>>> def mostra_numeros(num1, num2, num3):
... print 'Numero 1:',num1
... print 'Numero 2:',num2
... print 'Numero 3:',num3
...
>>> mostra_numeros(num2=2, num1=1, num3=3)
Numero 1: 1
Numero 2: 2
Numero 3: 3
>>> mostra_numeros(num2=7, num1=0, num3=8)
Numero 1: 0
Numero 2: 7
Numero 3: 8
1. um argumento não nomeado não deve suceder um argumento nomeado:
2. Quando não nomeamos alguns argumentos eles atomaticamente são atribuídos às sequências da função:Código PHP:
>>> mostra_numeros(num2=7, num1=0, 8)
File "<stdin>", line 1
SyntaxError: non-keyword arg after keyword arg
Nesse último exemplo o Python entende que 7 é o num1, e logo em seguida eu tento vincular o num1 ao numero 0, ocorrendo o erro de que o num1 foi utilizado multiplas vezes.Código PHP:
>>> mostra_numeros(7, num1=0, num3=8)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: mostra_numeros() got multiple values for keyword argument 'num1'
Desta forma, ao misturar argumentos nomeados e não nomeados devemos tomar cuidado para que os nomeados sejam os últimos e não devem vincular a argumentos que não foram nomeados.
Como esse assunto não traz nenhum novo conceito, não deixarei exercícios. Vamos guardar exercício para o próximo assunto: Argumentos com valores padrões.
Até mais...
[Curso de Python] Correção - Funções com Argumentos
24 de Junho de 2010, 0:00 - sem comentários aindaAntes de prosseguir leia os artigos anteriores aqui.
Dessa vez dois colegas apresentaram soluções (PEdroArthurJEdi e o neiesc). Cada um abordou o problema de uma forma. Como ainda estamos estudando o básico do Python, vou adotar uma solução mais simplista e que se utiliza somente das noções básicas de programação.
1. Faça uma programa que utilize uma função para calcular a média dos alunos do dicionário a baixo:
notas = {'aluno1':[5,6,5], 'aluno2':[7,8,6], 'aluno3':[6,6,8], 'aluno4':[5,9,8], 'aluno5':[5,6,3], 'aluno6':[6,6,6]}
Dica: Crie uma função que tenha como argumento um lista de notas.
Código da solução:
Saída do programa:Código PHP:
def calcula_media(notas):
total = 0
tamanho = 0
for nota in notas:
total += nota
tamanho += 1
total = float(total)/tamanho
print 'Media:', total
for aluno in notas:
print 'Aluno',aluno,
calcula_media(notas[aluno])
2. Faça uma função que calcule o quadrado de uma dado número. Exemplo:Código:Aluno aluno1 Media: 5.33333333333 Aluno aluno3 Media: 6.66666666667 Aluno aluno2 Media: 7.0 Aluno aluno5 Media: 4.66666666667 Aluno aluno4 Media: 7.33333333333 Aluno aluno6 Media: 6.0
Código da solução:Código:>>> quadrado(2) 4 >>> quadrado(4) 16 >>> quadrado(3) 9
3. Faça uma função que encontre o maior número de uma determinada lista. Teste-a com as listas a baixo:Código:>>> def quadrado(numero): ... print numero**2 ... >>> quadrado(2) 4 >>> quadrado(4) 16 >>> quadrado(5) 25 >>> quadrado(3) 9 >>>
Código da solução:Código:l1 = [1, 2, 3] l2 = [2,7,5,3] l3 = [7,3,9,5] l4 = [0, 1, -1] l5 = [-4, -2, -6]
Execução do programa:Código PHP:
def maior(lista):
maior_n = lista[-1]
for numero in lista:
if numero > maior_n:
maior_n = numero
print 'Maior numero em',lista,':',maior_n
maior([1, 2, 3])
maior([2,7,5,3])
maior([7,3,9,5])
maior([0, 1, -1])
maior([-4, -2, -6])
Código:Maior numero em [1, 2, 3] : 3 >>> maior([2,7,5,3]) Maior numero em [2, 7, 5, 3] : 7 >>> maior([7,3,9,5]) Maior numero em [7, 3, 9, 5] : 9 >>> maior([0, 1, -1]) Maior numero em [0, 1, -1] : 1 >>> maior([-4, -2, -6]) Maior numero em [-4, -2, -6] : -2
[Curso de Python] Funções com Argumentos
17 de Junho de 2010, 0:00 - sem comentários aindaAntes de prosseguir leia os artigos anteriores aqui.
No último post sobre funções em Python eu mostrei o básico do básico com as funções. De uma certa forma aquela abordagem é errada e deve ser evitada. Ainda não irei explicar exatamente o porque pois isso depende de um conceito de variáveis locais e globais que eu estarei explicando após ensinar os argumentos das funções.
Funções com Argumentos
As funções se tornam praticamente inúteis se não forem utilizados os argumentos. Mas antes de mais nada, o que é um argumento?
Um argumento é uma variável passada para um função. Dessa forma a função se utiliza desse argumento para trabalhar. Vamos ver um exemplo:
Ao definir a função 'ola' eu informei que seria passado um argumento chamado 'nome' e que a função deveria imprimir a palavra 'ola' seguida pelo conteúdo da variável nome. Para quem já trabalhou com outras linguagens como C, C++ ou Java, isso parece algo absurdo pois não foi especificado o tipo da variável. Como vimos a muito tmepo atrás, o Python possui tipagem dinâmica. Com isso não é necessário informar o tipo da variável no argumento.Código PHP:
>>> def ola(nome):
... print 'Ola',nome
...
>>>
>>> ola('joão')
Ola joão
>>> ola('Maria')
Ola Maria
>>> ola('José')
Ola José
>>>
Esse comportamento pode ser vantajoso ou não, depende do desenvolvedor. O desenvolver Python geralmente é um programador disciplinado, ou seja, suas funções são concisas e bem definidas evitando problemas com o tipo do argumento. A baixo um exemplo de algo que devemos ter cuidado para não ocorrer:
Por enquanto não se preocupem com esses problemas, eu irei mostrar uma forma de evitar esse tipo de ocorrências num futuro próximo. Por enquanto vamos ver alguns exemplo de funções.Código PHP:
>>> def ola(nome):
... print 'Ola',nome
...
>>>
>>> ola('joão')
Ola joão
>>> ola(12)
Ola 12
>>> ola([1,2,3])
Ola [1, 2, 3]
>>>
Vamos fazer uma função que busque uma determinada letra dentro de uma frase:
Execução do código:Código PHP:
def conta_letra(letra_esperada, frase):
contador = 0
for letra in frase:
if letra == letra_esperada:
contador += 1
print 'Foram encontradas',contador,'ocorrências da letra',letra_esperada
conta_letra('a', 'este e um teste')
conta_letra('e', 'este e um teste')
conta_letra('m', 'mais uma vez')
conta_letra('s', 'mais uma vez')
Como podemos ver, uma função pode ter um ou mais argumentos. Nesse exemplo nossa função possui dois argumentos, uma letra e uma frase. A função percorre todas as letras da frase informada e conta quantas vezes a letra esperada aparece na frase. No final ela imprime o resultado da contagem.Código:Foram encontradas 0 ocorrências da letra a Foram encontradas 5 ocorrências da letra e Foram encontradas 2 ocorrências da letra m Foram encontradas 1 ocorrências da letra s
Criar a função dessa forma é uma grande vantagem pois se podemos contar qualquer letra em qualquer frase. Se criássemos uma função que conta a letra 'a' e precisássemos de uma função que conta as ocorrências da letra e, teríamos que uma nova função. Isso é algo que deve ser planejado pelo programador. Devemos sempre tentar criar funções que sejam adaptáveis e reutilizáveis.
Exercícios:
1. Faça uma programa que utilize uma função para calcular a média dos alunos do dicionário a baixo:
Dica: Crie uma função que tenha como argumento um lista de notas.Código:notas = {'aluno1':[5,6,5], 'aluno2':[7,8,6], 'aluno3':[6,6,8], 'aluno4':[5,9,8], 'aluno5':[5,6,3], 'aluno6':[6,6,6]}
2. Faça uma função que calcule o quadrado de uma dado número. Exemplo:
3. Faça uma função que encontre o maior número de uma determinada lista. Teste-a com as listas a baixo:Código:>>> quadrado(2) 4 >>> quadrado(4) 8 >>> quadrado(3) 9
Código:l1 = [1, 2, 3] l2 = [2,7,5,3] l3 = [7,3,9,5] l4 = [0, 1, -1] l5 = [-4, -2, -6]
[CUrso de Python] Correção - O for e Outras Palavras Mais
16 de Junho de 2010, 0:00 - sem comentários aindaAntes de prosseguir leia os artigos anteriores aqui.
No último post eu deixei alguns exercícios a serem resolvidos. O nosso colega neiesc postou uma solução que pode ser conferida aqui.
A solução dele foi bem interessante mas utilizou algumas funções que ainda não estudamos aqui (como o sum), dessa forma vou postar uma solução somente utilizando loops for. Um pequeno detalhe que nosso colega esqueceu foi de especificar o numero como float na divisão das notas, o que pode causar certos problemas em um programa.
Vamos às soluções.
1. Dado o dicionário abaixo, faça programa que verifique qual dos alunos foi reprovado por falta. O critério de reprovação é ter mais faltado mais de 10 vezes.
Código:
Execução:Código PHP:
faltas = {'aluno1':2, 'aluno2':3, 'aluno3':7, 'aluno4':11, 'aluno5':0, 'aluno6':10}
for aluno in faltas:
if faltas[aluno] > 10:
print 'O aluno',aluno,'foi reprovado por faltas'
2. Tendo o exemplo anterior como base, modifique o programa de forma que ele cruze os dados do dicionário de faltas com o dicionário de notas (a baixo) e informe se o aluno foi aprovado, usando o critério de faltas e a média de suas notas (maior que 6).Código:O aluno aluno4 foi reprovado por faltas
Execução:Código PHP:
faltas = {'aluno1':2, 'aluno2':3, 'aluno3':7, 'aluno4':11, 'aluno5':0, 'aluno6':10}
notas = {'aluno1':[5,6,5], 'aluno2':[7,8,6], 'aluno3':[6,6,8], 'aluno4':[5,9,8], 'aluno5':[5,6,3], 'aluno6':[6,6,6]}
for aluno in faltas:
if faltas[aluno] > 10:
print 'O aluno',aluno,'foi reprovado por faltas'
else:
media = 0
for nota in notas[aluno]:
media += nota
media = float(media)/3
if media >= 6:
print 'O aluno',aluno,'foi aprovado com média:',media
else:
print 'O aluno',aluno,'foi reprovado com média:',media
Como eu comentei, esses são exercícios simples apenas para fixação dos conceitos estudados até aqui. De agora para frente os exercícios serão bem diferentes.Código:O aluno aluno1 foi reprovado com média: 5.33333333333 O aluno aluno3 foi aprovado com média: 6.66666666667 O aluno aluno2 foi aprovado com média: 7.0 O aluno aluno5 foi reprovado com média: 4.66666666667 O aluno aluno4 foi reprovado por faltas O aluno aluno6 foi aprovado com média: 6.0
Até mais...