Blog em português onde relato a minha aprendizagem de Python. Sendo um blog, ele deve ser lido de baixo para cima e é escrito orientado a uma audiência com alguns conhecimentos de programação. Não tentando ser uma fonte exaustiva de conhecimento sobre Python, pode, no entanto, servir como crash course a esta linguagem.

Tuesday, June 30, 2009

Lambdas e geradores

A expressão lambda cria uma função sem associar-lhe qualquer nome. Assim, a expressão
lambda parametros : expressão

constroi uma função com código equivalente a:
def anonimo(parametros):
  return expressao

excepto pelo facto de alterar a ligacao ao nome anonimo

Exemplos:
>>> quadrado=(lambda x : x*x)
>>> quadrado
at 0x028DA6B0>
>>> quadrado(3)
9
>>>



Geradores (Generators):
Um gerador é qualquer função que contenha pelo menos uma keywork yield. Ao chamar uma função deste tipo, o corpo da função não é executada. Pelo contrário, ao chamar um gerador, é criado um iterador especial que contêm a função, as suas variáveis locais (e parâmetros) e o ponto actual de execução, que é inicialmente definido como o início da função. Ao chamar o método next deste gerador, a execução da função retoma do ponto actual até ao primeiro yield. De uma forma muito práctica, um yield é como um return pois permite retornar um valor para o contexto mas quando a sua execução retoma esta é retomada a partir do yield em vez do início da função. Exemplo:
Imagine-se que queremos uma sequência de números de 1 a N e depois até 1 de novo. (Tirado de Python in a nutshell - Alex Martelli):
def updown(n):
  for x in xrange(1, n): 
    yield x
  for x in xrange(n, 0, -1):
    yield x

Exemplo de utilização:
for i in updown(3): print i
imprime 1,2,3,2,1

Programação orientada a objectos em Python

Em Python uma classe é definida com a keyword class. Exemplo: 
 
class Basket:

    # Always remember the *self* argument
    def __init__(self,contents=None):
        self.contents = contents or [] 
        #esta funcao nunca pode retornar nada 

     def add(self,element):
        self.contents.append(element)

     def print_me(self):
        result = ""
        for element in self.contents:
            result = result + " " + `element`
        print "Contains:"+result

Novos conceitos:
  1. Todos os métodos têm de receber um argumento adicional explícitamente que corresponde ao objecto sobre o qual este método deve ser executado. É normal que esse argumento seja chamado self mas isto não é obrigatório. No entanto tem de ser sempre o primeiro argumento.
  2. Alguns métodos, como __init__ (com dois underscores antes e depois), são prédefinidos (podendo ser redefinidos) e têm significados especiais. __init__ é o nome do construtor da classe e é portanto o método que é chamado quando se escreve algo Basket(args), logo após o espaço para o objecto ser criado.
  3. Para aceder aos campos de uma classe é necessário escrever self.campo, isto porque escrever só campo mesmo dentro do código das funções da classe, seria uma construção ambígua (pelo facto de as variáveis locais não serem declarada).
  4. O método print_me deveria ser chamado __str__ que é o método "especial" implícitamente chamado sempre que uma representação, do objecto, em string é desejada. Assim seria possível chamar print para uma instância de Basket.
  5. A partir de Python 2.5 todas as classes devem herdar de object. Assim, Basket deveria ser class Basket(object). O facto de isto ser explícito é mau porque implica que se escreva um pouco mais em relação a outras linguagens mas, é também mais extensível.

Não existem mecanismos de protecção de variáveis ou métodos (não existem formas de dizer que algo é private ou protected ou mecanismos similares). A encapsulação é deixada a cargo do programador. No entanto, existe a convenção de que métodos e variáveis cujo nome começa com 1 underscore devem ser privados. Na minha opinião este é um dos defeitos do Python pois a linguagem em si não proíbe que se violem as abstracções. Existem outros defeitos mas lá iremos a seu tempo.

Para definir a superclasse de uma classe deve-se usar parentesis a seguir ao nome. Assim:

class SpamBasket(Basket):
#...

Python suporta também herança múltipla. Para dizer que A é um subtipo de 
B e C usa-se:
class A(B, C):
 
É necessário chamar o construtor da superclasse mesmo que não seja 
necessário passar-lhe argumentos especiais. Desta forma, no __init__ 
do SpamBasket é necessário chamar o __init__ da sua superclasse,
passando explícitamente o argumento self:
Basket.__init__(self)

Para tornar isto ainda mais complexo (e confuso), o Python suporta metaclasses.
Metaclasses são, como o nome indica, classes de classes. Não vou aqui discutir
os mecanismos relacionados com as metaclasses pois são um mecanismo
avançado para esta fase (e, honestamente, ainda não tive tempo para os practicar
e perceber os seus pontos fortes e fracos). Mais tarde vou tentar voltar
a este ponto pois acho que as linguagens que suportam metaclasses são 
"potentíssimas", apesar de ser um mecanismo raramente utilizado. Um bom uso
para metaclasses é, por exemplo, a criação de uma metaclasse cujas instâncias
das suas classes, bem como os campos "static" da classe, são implicitamente
tornados permanentes numa base de dados.

Quanto aos campos partilhados por todas as instâncias eles são definidos no corpo da classe:
class Foo(object):
  x=23
  y=x+22


  def method(self):  
    print Foo.x #nao se pode escrever simplesmente print x

Notar que estes campos static são criados e os seus valores definidos quando a construção sintáctica class é avaliada, o que constrói um objecto do tipo class associado ao nome foo, algo que vem da filosofia de que tudo é um objecto de 1ª classe.

Tuesday, June 16, 2009

Qual é a verdade?

Vou falar de Booleanos em Python. Cada linguagem apresenta a sua interpretação do que é verdade. Python não é excepção.
Em python, tudo é verdade (True) excepto: os containers vazios (listas vazias, sets vazios,...), None (o equivalente ao null noutros mundos) e 0. Espero não estar a esquecer casos. Vou usar a função bool() para exemplificar:
>>> bool(0)
False
>>> bool(1)
True
>>> bool([])
False
(lista vazia é interpretada como falsa)
>>> bool([1])
True 
(lista não vazia é interpretada como verdade)
>>> bool([1,2])
True
>>> bool(())
False
(tuplo vazia é interpretada como falso)
>>> bool((None))
False 
(None é falso)
>>> bool((None,))
True 
(Tuplo com None já é True)
>>> bool(False)
False
>>> bool(not False)
True
>>> bool(list)
True  
(Um tipo também é verdadeiro)
>>> list

>>>


A avaliação de expressões booleanas do tipo a and b tem também as suas peculiariedades:
Primeiro ver se a é true. Se não for então o valor da expressão é a. Se for, então o valor da expressão é b.
Isto é chamado de short circuiting e existe em outras linguagens também, mas a peculiarieadade é que, juntamente com o facto de muitas coisas serem True ou False podemos ter construções muito interessantes.
Por exemplo:


if a:
    print a
else:
    print b

pode ser simplesmente escrito como 
 
print a or b
 
o que imprime a ou b consoante a seja interpretado como verdade ou b caso
contrário.
Veja-se:
>>> print [1] or [2]
[1]
>>> print [1] or []
[1]
>>> print [] or [2]
[2]
>>> 

Thursday, June 11, 2009

Listas, dicionários e tipos de argumentos

Python, sendo uma linguagem de alto nível,  suporta nativamente estruturas de dados complexas como listas e dicionários, entre outros.
Listas
Um aspecto muito interessante é que muitos tipos de estruturas de dados complexas têm uma forma sucinta para serem definidas. No caso das listas, estas podem ser criadas com parênteses rectos:
x=[1,2,3]
 , (como aliás já tinha sido mostrado num post anterior.)
Obviamente, as listas podem ser aninhadas:
x=[ [1, 2], 3 ]
(uma lista de dois elementos onde o primeiro elemento é ele próprio uma lista (com os elementos 1 e 2) e o segundo elemento é o 3.
Assim, a avaliação da construção sintáctica [ ... ] cria uma lista (uma instância de list) cujos elementos são dados pela avaliação do que está dentro dos parênteses. Uma lista vazia é naturalmente escrita com [].
Também é possível criar uma lista usando list(). Mais, em python, para criar uma instância de uma classe xpto deve-se escrever xpto(). i.e. ao definir uma classe C, fica implicitamente definida uma função com nome C, função essa que tem todas as propriedades das outras funções e é chamada da mesma forma.

Tuplos
Um tuplo é uma sequência ordenada de elementos cujos tipos podem arbitrários e até diferentes. Tal como para as listas, existem formas sucintas de criar tuplos. Os tuplos criam-se com parêntesis.
Exemplo:
() <- tuplo vazio. Os parêntesis neste caso não são opcionais.
(100, 200, 300) <- tuplo com 3 elementos
(3.14, ) <- forma de criar um tuplo com apenas um elemento. a virgula a seguir ao número NÃO é um engano. Ela existe pois de outra forma seria impossível distinguir da aplicação normal de parêntesis para avaliar algo primeiro. Tal como nas listas, pode-se chamar o construtor de tuplo, que é a função tuple(). Esta função recebe no máximo um argumento e, quando esse argumento é um iterável, o tuplo contem os mesmos elementos do iterável.
Exemplo:
tuple('wow'), retorna um tuplo igual a ('w', 'o', 'w') pois uma string é um tipo de iteravel cujos elementos são os seus caracteres. Para ter um tuplo com 1 único elemento igual a 'wow' deve-se usar ('wow', ) ou então
x = 'ola'
tuplo = ( x, )

Dicionários
Um dicionário é um tipo de container (um objecto que contém outros objectos) que permite mapear uma colecção arbitrária de objectos a uma colecção arbitrária de outros objectos, chamados chaves. Noutros contextos, estas estruturas são também chamadas de mapas ou hash tables.
Estes também possuem um açucar sintáctico para a sua definição:
{'x':42, 'y':3.14, 'z':7}
esta expressão tem como valor um dicionário que mapeia a string 'x' ao elemento 42, a string 'y' ao número 3.14 etc. Para definir um dicionário vazio é, como se espera, {}
Também se pode criar um dicionáriochamando o seu construtor. Irei exemplificar para aproveitar e apresentar outra característica da linguagem:
dict(x=42, y=3.14, z=7)
Esta expressão cria exactamente o mesmo dicionário que a anterior. O que quero apresentar é que, na chamada a uma função o python suporta named arguments i.e. para além dos argumentos posicionais normais, pode-se passar também novos argumentos no formato nome=valor. No caso particular da função dict, ela usa esses argumentos nomeados para criar um dicionário contendo um mapeamento entre os seus nomes ('x','y','z') e os seus valores.

No caso geral, imagine-se a seguinte funcão:
def divide(divisor, dividend):
  return dividend / divisor

Esta função pode ser chamada de várias formas:
divide(20,5) , a chamada "normal" onde os parametros formais são ligados aos valores pela sua posição relativa (20 fica ligado a divisor e 5 fica ligado a dividend).
No entanto, ela também pode ser chamada assim:
divide(dividend=94, divisor=12)

Um uso excelente para os named arguments, é a de passar parâmetros opcionais. Considere-se a seguinte função:
def f(middle, begin='init', end='finis'):
  return begin+middle+end

Esta função apresenta vários outros conceitos. Ao fazer a=b na lista de parâmetros formais, estamos a definir o valor de defeito como sendo b para o argumento com nome a, como em C++.
Segundo, tal como em Java, o + pode ser usado para concatenar strings mas, em python, o mecanismo é bastante mais poderoso, sendo parecido com o de C++.
ao fazer a+b, o Python chama o método __add__ sobre o objecto a com argumento b. Claro que este método pode ser chamado explícitamente:
>>> 'a'+'b'
'ab'
>>> 'a'.__add__('b')
'ab'
>>>


Aproveito também para dizer que practicamente todos os mecanismos "especiais" do python podem ser alterados pois por trás existem sempre chamadas a método especiais. Métodos especiais devem ser chamados __nome__ (são dois underscores antes e depois). Todos os operadores, comparações, conversões implícitas entre tipos são executadas por métodos especiais. A única excepção existente é para a atribuição a=b que não pode ser overloaded e tem sempre a mesma semântica.

Voltando ao exemplo da função f. Esta função pode ser chamada das seguintes formas:
>>> f('ola')
initolafinis
>>> f('ola',end=' mundo')
initola mundo
>>> f('ola',end=' mundo', begin='')
ola mundo
>>>


Voltando ainda aos containers (dicionários e listas etc.), podemos obter os seus elementos usando o operador de indexação []. Alguns exemplos:
>>> listaMaravilha=[1,2,3,4]
>>> listaMaravilha
[1, 2, 3, 4]
>>> listaMaravilha[3]
4

Note-se que as listas sao indexadas a partir do 0'
>>> listaMaravilha[-1]
4
'Mas tambem se pode usar indices negativos. O significado é o de comecar a partir do fim. Veja-se:
>>> listaMaravilha[-2]
3

No entanto, os indíces admissíveis são apenas entre [-N, N+1], onde N é o número de elementos.
>>> listaMaravilha[-5]
Traceback (most recent call last):
  File "", line 1, in
IndexError: list index out of range
>>> listaMaravilha[4]
Traceback (most recent call last):
  File "", line 1, in
IndexError: list index out of range
>>>



Da mesma forma se pode indexar e até alterar os valores de um dicionário.
>>> foo={'ola':1, 'mundo':2}
>>> foo[0]
Traceback (most recent call last):
  File "", line 1, in
KeyError: 0
>>> foo['mundo']
2
>>> foo['mundo']=3
>>> foo['mundo']
3
>>>

>>> foo
{'ola': 1, 'mundo': 3}
>>> foo['nova chave']=43
>>> foo
{'ola': 1, 'nova chave': 43, 'mundo': 3}
>>>

Tuesday, June 2, 2009

Controlo de fluxo

Neste post falo do if,while e for para controlo de fluxo de execução.
Sintaxe do if:

if : bloco
elif : bloco ( pode ter 0 ou + ocorrências)
else : bloco (opcional)

Exemplos:
if x==y:
  print "yes"
else:
  print "no"

Sintaxe do while:

while : bloco
else : bloco

O while pode levar um else opcional. O bloco dentro do else é executado apenas quando while termina normalmente, ie, sem break, excepção ou return. Como em C, pode-se usar as keywords continue, e break com o mesmo significado.

Sintaxe do for:

for name in iteravel:
else : bloco

Este for é mais parecido com o foreach do java do que com o for de C. Como o while, ele pode levar um else e a semântica é a mesma. deve ser uma expressão que quando avaliada produz um objecto iterável. Um iterável é algo que pode ser iterado. Regral geral, um iterable é do tipo Iterator. Um iterator i é um objecto com o método next tal que se pode chamar i.next() para obter o próximo valor do iterador, ou a excepção StopIteration quando não existem mais elementos.
Repare-se que a sintaxe para chamar uma função ou método é exactamente a mesma que em C/Java método(argumentos) e que chamar um método sobre uma instância é feito com o "." , i.e. objecto.método(argumentos)

Assim, este for começa por avaliar a expressão que deve produzir um objecto do tipo Iterator. De seguida, para cada objecto retornado pelo método next() do Iterator, ele atribui esse objecto a e executa o bloco de código.
Exemplo:

# Imprimir todos os valores de 0 até 99 inclusivé.
for value in range(100):
  print value

range é uma função standard que devolve uma lista cujos elementos base
são os valores entre 0 e o argumento passado. Python chama implícitamente iter() sobre
a lista retornada para obter um Iterator sobre os elementos da lista.
Note-se que "range(100)" só é avaliado uma vez. Alterar os elementos da lista
de base do iterador pode ser feito mas tem uma semântica esquisita.
Quando o iterador é criado inicialmente, ele pede à lista o número de elementos
e de seguida itera vai buscar os valores da lista usando um índice incremental.
Assim, se se retirar o elemento da lista correspondente à posição actual
na lista, o próximo elemento não será considerado pelo iterador (ao eliminar o elemento
da lista, todos os indices dos elementos na lista são decrementados, i.e.
a lista é shiftada para a esquerda a partir daquele ponto mas o iterador
vai continuar a ir buscar o mesmo indice).
Exemplo:
def exemploIter():
  y = [2,3,4]
  for x in y:
    print x


  for x in y:
    y.remove(x)
    print x

  print y

Copiando isto para um ficheiro exemplo.py e correndo
python -i exemplo.py
faz com que o ficheiro seja avaliado e de seguida se apresente uma shell
para o interpretador.
Chamando exemploIter, escrevendo exemploIter() obtemos o seguinte:

C:\Users\tiago>python -i py.py
>>> exemploIter()
2
3
4
2
4
[3]
>>>

as primeiros 3 linhas 2,3,4 corresponde ao primeiro for a imprimir os elementos
da lista.
De seguida note-se que apenas 2 e 4 são impressos. Isto ocorre, como expliquei,
porque quando o iterador retorna o primeiro elemento (2), este é associado
a x e depois removido da lista. Assim, o elemento 3 que estava no índice 1
passou a estar no índice 0, mas, o iterador vai de seguida buscar o mesmo
índice (1) que corresponde ao valor 4 e imprime-o e elimina-o da lista.
De facto, o último print imprime a lista y , o que faz com que seja impresso
[3], que é a representação textual de uma lista apenas com o elemento 3.