[Curso de Python] Funções Com Argumentos Indefinidos - Parte 2/2
4 de Agosto de 2010, 0:00 - sem comentários aindaAntes de prosseguir leia os artigos anteriores aqui.
No último post falamos um pouco sobre argumentos indefinidos que se utilizam do * para receber um vetor de tamanho indefinido como argumento. Hoje vamos ver como utilizar dois asteriscos e sua funcionalidade.
Funções Com Argumentos Indefinidos - Parte 2/2
Anteriormente vimos como usar uma tupla para receber argumentos indefinidos em uma função. Essa abordagem traz um pequeno incômodo, referenciar os argumentos passados através de índices. Hoje vamos ver uma outra forma de receber argumentos porém referenciando-os pelo nome. A referência por nome ou palavras-chave (keywords) é mais intuitiva porém causa mais consumo de memória pois é necessário criar um dicionário para armazenar os argumentos. Um dicionário vazio é 5 vezes maior que uma tupla vazia. Geralmente esse argumento da função é referenciado por **kwargs, um mnemônico de "Keyword Arguments". Vamos ver um exemplo:
Como podemos ver, devemos utilizar 'identificadores' para os argumentos. Esses 'identificadores' são convertidos nas 'chaves' do dicionário.Código:>>> def teste_kwargs(*kwargs): ... print 'kwargs =',kwargs ... >>> teste_kwargs() kwargs = {} >>> teste_kwargs(arg1=1, arg2=2, arg3=3) kwargs = {'arg1':1, 'arg2':2, 'arg3':3) >>> teste_kwargs(arg1=1, arg2=(2,3,4), arg3='teste') kwargs = {'arg1': 1, 'arg2': (2, 3, 4), 'arg3': 'teste'} >>>
Um artifício muito poderoso é a combinação de *args com o **kwargs, o que nos traz a possibilidade de utilizar inúmeros argumentos nomeados e não nomeados. Segue um exemplo:
Como podemos ver, utilizando essa combinação, é possível passar apenas argumentos nomeados, apenas argumentos não nomeados ou ambos, respeitando a "etiqueta" que diz que os argumentos não nomeados devem sempre vir antes dos argumentos nomeados.Código:>>> def teste_kwargs(*args, **kwargs): ... print 'args =',args ... print 'kwargs =',kwargs ... >>> teste_kwargs(1, 2, 3, x='a', y='b', z='c'): args = (1, 2, 3) kwargs = {'y': 'b', 'x': 'a', 'z': 'c'} >>> teste_kwargs('a', 'b', 'c') args = ('a', 'b', 'c') kwargs = {} >>> teste_kwargs(teste1=1, teste2=2, teste3=3) args = () kwargs = {'teste1': 1, 'teste3': 3, 'teste2': 2} >>>
Boa semana a todos e até a próxima...
[Curso de Python] Funções Com Argumentos Indefinidos - Parte 1/2
28 de Julho de 2010, 0:00 - sem comentários aindaAntes de prosseguir leia os artigos anteriores aqui.
No último post falamos mais um pouco sobre os argumentos de uma função mas mesmo assim ainda não acabamos de cobrir todo o assunto. O Python é um linguagem muito extensa e com diversas funcionalidades. Hoje vamos ver a primeira parte de argumentos indefinidos, ou também conhecidos como *args e **kwargs.
Funções Com Argumentos Indefinidos - Parte 1/2
Como vimos, anteriormente podemos declarar funções sem argumentos, funções com argumentos fixos, e funções com argumentos não obrigatórios (valores padrões). Mas se uma função precisa levar muitos argumentos chegando a ser complicado definir todos podemos usar o recurso dos argumentos indefinidos.
Primeiramente vamos aprender a usar uma tupla como "container" de argumentos, geralmente referenciado como *args. Vale ressaltar que o nome da variável após o * pode ser qualquer outra palavra que defina o que serão os seus itens. Vamos a um exemplo:
Como podemos ver, a tupla args recebe qualquer quantidade de argumentos mantendo sua ordem para uma correta decodificação. Desta maneira podemos elaborar uma função que processe todos os argumentos por exemplo, uma função que retorne a soma de todos os argumentos:Código:>>> def teste_args(*args): ... print 'args =',args ... >>> teste_args() args = () >>> teste_args(1,2,3) args = (1, 2, 3) >>> teste_args(1,2,3,4,5,6,7,8,9) args = (1, 2, 3, 4, 5, 6, 7, 8, 9) >>> teste_args(1,2, 'abc', [9,8,7]) args = (1, 2, 'abc', [9, 8, 7]) >>>
Uma vez que temos infinitos argumentos possíveis, pode vir a ser necessário utilizar uma lista/tupla para armazenar todos os argumentos antes de passa-los para a função. Vamos supor que nosso programa levantou uma lista de números (por exemplo, passado pelo usuário) e irá passá-los para a função de soma. Isso implica em um pequeno detalhe. Vamos ver o que ocorreria se passarmos a lista/tupla normalmente:Código:>>> def soma(*args): ... resultado = 0 ... for numero in args: ... resultado = resultado + numero ... print 'resultado =',resultado ... >>> >>> >>> soma(1,2) resultado = 3 >>> soma(1,2,3) resultado = 6 >>> soma(1,2,3,4,5,6,7,8,9,10) resultado = 55 >>>
Podemos ver que o *args "envelopa" a lista l como sendo o seu primeiro elemento. ao tentar realizar essa soma iríamos obter um erro. Para utilizar a funcionalidade do *args e ao mesmo tempo passar uma lista/tupla, precisamos "desempacotá-lo":Código:>>> def teste(*args): ... print 'args =',args ... n = 0 ... for arg in args: ... print 'arg',n,'=',arg ... >>> teste(1,2,3,4) args = (1, 2, 3, 4) arg 0 = 1 arg 0 = 2 arg 0 = 3 arg 0 = 4 >>> l = (1,2,3,4) >>> teste(l) args = ((1, 2, 3, 4),) arg 0 = (1, 2, 3, 4) >>>
como podemos ver, um simples * pode fazer toda a diferença!Código:>>> def teste(*args): ... print 'args =',args ... n = 0 ... for arg in args: ... print 'arg',n,'=',arg ... >>> def soma(*args): ... resultado = 0 ... for numero in args: ... resultado = resultado + numero ... print 'resultado =',resultado ... >>> l = (1,2,3) >>> teste(*l) args = (1, 2, 3) arg 0 = 1 arg 0 = 2 arg 0 = 3 >>> >>> soma(*l) resultado = 6 >>>
Boa semana a todos e até a próxima...
[Curso de Python] Funções com Argumentos e Valores Padrões
21 de Julho de 2010, 0:00 - sem comentários aindaAntes de prosseguir leia os artigos anteriores aqui.
No último post sobre funções com argumentos nomeados, eu mostrei como referenciar argumentos utilizando como base o nome designado a este argumento. Hoje vamos ver como utilizar valores padrões, ou pré-definidos, em um argumento.
Funções com Argumentos e Valores Padrões
Muitas vezes você precisa criar uma função quase idêntica a outra, a única diferença é que uma recebe argumentos enquanto a outra não. O Python nos oferece um recurso interessante para evitar a criar de duas funções com comportamentos similares. Os valores padrões fazem um argumento se tornar opcional. Vamos a um exemplo prático, precisamos de uma função que incremente um numero. Caso não especificado um "valor de incremento", o número será incrementado com o "valor de incremento" igual a um. Ou seja, o comportamento da função deve ser o seguinte:
Em resumo essa função deve somar dois numeros. Como podemos ver o segundo argumento é opcional, caso informado o primeiro número deve ser somado ao segundo, caso não seja informado o segundo número, o primeiro número deve ser somando a um valor fixo que, conforme especificado, é 1. Vamos ao código:Código:>>> incrementa(10) 11 >>> incrementa(2) 3 >>> incrementa(4,2) 6 >>> incrementa(7,10) 17
O valor padrão é definido na declaração da função. Ao declarar um argumento com um valor inicial (num=2) ele se torna opcional, podendo ou não ser especificado. Vamos a outro exemplo: Criar uma função que conte o numero de caracteres que uma frase tem. O argumento opcional é uma letra e, caso seja especificado, a função deve contar as ocorrências dessa letra:Código PHP:
def incrementa(num1, num2=1):
return num1+num2
Como podemos ver, eu iniciei o argumento letra como None. Ao inciar a função verifico se o valor padrão de letra foi alterado (if letra is None), caso negativo, eu conto o número de letras na frase. Caso a letra tenha sido especificado (else) eu conto o número de ocorrências da letra. Antes que alguém fale, eu sei que existem funções prontas que fazem isso, eu estou utilizando apenas como exemplo. Abaixo a execução desse código:Código PHP:
def conta_caracteres(frase, letra=None):
if letra is None:
cont = 0
for l in frase:
cont = cont + 1
else:
cont = 0
for l in frase:
if l == letra:
cont = cont + 1
return cont
Boa semana a todos e até a próxima...Código:>>> conta_caracteres('esta e uma frase') 16 >>> conta_caracteres('esta e uma frase', 'a') 3 >>> conta_caracteres('esta e uma frase', 'f') 1 >>> conta_caracteres('esta e uma frase', ' ') 3 >>>
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...