Programação para Advogados - Apostila 1










Daniel Chada & Ivar Hartmann
2014



























Índice









Introdução

Esta apostila visa prover resumos do conteúdo visto em sala durante as aulas teóricas da Atividade Complementar 'Programação para Advogados'. Não se visa que esta seja uma referência completa nem aprofundada de nenhum dos temas abordados em seu texto, mas sim uma introdução amena a certos conceitos de utilidede a alunos de graduação de Direito ou de outros temas de 'Humanas'.

Almeja-se que os temas aqui abordados formem uma base conceitual para posterior aprofundamento tanto na teoria quanto na pratica do desenvolvimento de software.

Módulo 0 - Boas Vindas

Bem-vindos Advo* ...programadores!

Esta apostila segue o material coberto nas aulas teóricas da ATC "Programação para Advogados". Teremos exemplos práticos toda semana. O formato desta apostila tem dupla funcionalidade: i. de ser uma referência impressa para acompanhamento e ii. ser um repositório de exemplos de código a serem explorados e editados.

Com este fim, escolhemos oferecer não só a versão impressa da apostila, mas também uma versão em iPython Notebook. iPython Notebook é um framework que permite a edição de texto, junto com a execução de código Python, em células auto-contidas. Desta forma é possível executar e editar, no seu próprio computador, os códigos dos exemplos expostos aqui.

A homepage do Ipython Notebook contém instruções para instalação para Windows, OSX e Linux.

Nosso desejo é que você, leitor e aluno da ATC, use esta funcionalidade para melhor entender o código provido a cada exemplo e tente explorar diferentes aspectos do Python.

Boa Sorte!

Módulo 1 - Variáveis, Números, Strings (pte. 1)

Literais

São as expressões e a notação usadas para gerar os valores de tipos built-in como números inteiros, longos (que ocupam mais espaço em memória, mas são maiores), decimais (também conhecidos como floating point ou float), números complexos e strings. Exemplos: 4, 3.2e212, 3.14159, 1+3j,'Ivar'.

Estes são chamados literais pois os objetos são literalmente aquilo que representam: 3, "spam", 7e4

Literais podem ser declarados, ou convertidos (dentro do possível) com operadores (que veremos abaixo) built-in do Python. isto é:


In [1]:
int(4.5) # gera um inteiro (que geralmente usa até 32 bits de memória)


Out[1]:
4

In [2]:
str(4.5) # gera uma string


Out[2]:
'4.5'

In [3]:
long(4.5e21) # gera um número longo, que tem precisão ilimitada (note o L ao final)


Out[3]:
4500000000000000000000L

In [4]:
float(3) # gera um decimal, também chamado de 'ponto flutuante' (floating point)


Out[4]:
3.0

In [5]:
bool(4.5) # gera um booleano


Out[5]:
True

In [6]:
bool(0.000) # zero é sempre falso no Python


Out[6]:
False

Python permite uma série de outros formatos literais muito úteis na computação e ciência, mas que não cabem no escopo desta apostila, como números complexos (a + b√-1) octais (0-7), hexadecimais (1-9,A-F) e binários (0,1).

Veremos binários brevemente em uma das últimas unidades.


In [7]:
complex(1,3.14159)


Out[7]:
(1+3.14159j)

In [8]:
oct(500) # um zero sempre o precede


Out[8]:
'0764'

In [9]:
hex(500) # um '0x' sempre o precede


Out[9]:
'0x1f4'

In [10]:
bin(500) # um '0b' sempre o precede


Out[10]:
'0b111110100'

Variáveis

Variáveis são nomes que o programador dá a objetos no seu código. Podemos pensar em objetos como espaços de memória alocados no computador. Estes espaços têm diferentes tamanhos e são grandes o suficiente para representar o que quer que cada objeto seja. Um inteiro no Python, por exemplo, geralmente ocupa 32 bits de memória.

Assim, uma variável referencia um objeto. Uma referência é um ponteiro, isto é, uma seta que aponta da variável (nome) ao objeto (lugar).

Veja a figura 1.1: internamente, a variável aponta para o espaço de memória criado por se executar a expressão (veja o que é uma expressão abaixo) do operador 3.

Variáveis são criadas no momento que você as atribue um valor (tecnicamente algumas são criadas antes disso, mas para nós basta esta definição). Atribuições subsequentes no código mudam o valor da variável (i.e. mudam para onde ela aponta). Variáveis nunca guardam em si nenhum tipo de informação, elas simplesmente se referem a um lugar onde algo fica. [1]

Quando uma variável aparece em uma expressão, ela é imediatamente substituída pelo objeto para qual aponta:


In [11]:
x = 3   # crio o nome 'x' e mando ele apontar para o objeto '3' gerado na memória
print x # não imprimo 'x', e sim o valor para o qual 'x' aponta


3

In [12]:
y = "Python para advogados é mole!"
print y


Python para advogados é mole!

A nomenclatura de variáveis deve seguir algumas regras:
i. o primeiro caractére da variável deve ser uma letra ou o _ (underscore);
ii. nomes de variáveis são case-sensitive;
iii. nomes de variáveis só podem conter caracréres alfanuméricos ou o underscore (a-z,A-Z,0-9,_)


In [13]:
_oi_1 = '0'; Oi = 1; oi = '2' # exemplos de variáveis (dica: o ';' funciona igual à quebra de linha: permite um novo comando)
print _oi_1, Oi, oi


0 1 2

Operadores

Operadores computam algum valor, quando executadas pelo Python. A tabela 1.1 lista todos os operadores do Python, em ordem de precedência.

operadordescriçãoveremos?
yield xprotocolo de envio de uma função geradoratalvez
lambda args: expressãogeração de função anônimasim
x if y else zseleção ternáriatalvez
x or y'ou' lógicosim
x and y'e' lógicosim
not xnegação lógicasim
x in y; x not in ypertence (iteráveis, conjuntos)sim
x is y; x is not yteste de identidade de objetosim
x >,>=,<,<= ycompara magnitude, ou sub/super-conjuntosim
x == y, x != yigualdade/desigualdade de valoressim
x | y'ou' bitwise, união de conjuntossim
x & y'e' bitwise, interseção de conjuntossim
x ^ yxor bitwisesim
x <<,>> yshiftar x, y bits para a esquerda/direitasim
x + yadição, concatenaçãosim
x - ysubtração, deferença de conjuntossim
x ymultiplicação, repetiçãosim
x % ymódulo (resto), formataçãosim
x /,// ydivisão: real e pisosim
-x,+xnegação, identidadesim
~x'não' bitwise, inversãosim
x ** ypotênciaçãosim
x[i]indexação (sequências, strings, etc)sim
x[i:j:k]recorte (slicing)sim
x(...)chamada (função, método, classe, 'chamáveis')sim
x.attrreferência a attrsim
(...)tupla, expressão, expressão geradorasim
[...]lista, list comprehensionsim
{...}dicionáriosim
fonte: tutorialspoint

Expressões

Expressões são combinações de objetos (literais ou outros) e operadores que computam algum valor, quando executadas pelo Python.

Expressões compostas seguem as regras de precedência de operadores. Expressões compostas podem, porém, ser agrupadas por parênteses em subexpressões, ignorando as regras de prioridade normalmente aplicadas aos operadores (veja a Tabela 1.1 e o código abaixo).


In [14]:
x = 2 + 3 * 4 # o lado direito do igual é uma expressão
y = (2 + 3) * 4 # aqui também
print x, y


14 20

*Statements* (Declarações)

Uma statement no Python é qualquer linha de código que o Python consiga interpretar e executar. Expressões podem ser declarações ou parte delas, mas nem toda expressão é uma statement.

Essa distinção se tornará mais importante quando estudarmos funções anônimas com o operador lambda e as expressões geradas com ele.

Strings

Strings são cadeias de caracteres, delimitadas por aspas (simples ou duplas). Strings são literais de enorme utilidade na programação, e vamos usá-los constantemente neste curso. Esta 'parte 1' sobre strings expõe o básico do seu funcionamento, para que possamos começar a criá-los e manipulá-los.

A primeira característica a mencionar é que strings, no Python, podem ser delimitados por aspas simples ('') ou duplas (""), desde que se mantenha a conformidade, isto é, se abrimos uma string com aspas simples, o Python só entenderá que a string se fechou ao ver a próxima aspa simples. Veja:


In [15]:
x = ("A",'A') # aspas e aspas duplas são a mesma coisa, isto é só uma conveniência
print x


('A', 'A')

Strings comportam caracteres especiais e difíceis de digitar com sequências de escape. No Python, toda sequência de escape é iniciada pela 'barra invertida' (backslash): \

Sequências de escape são consideradas como um único caractere, apesar de, em geral, serem digitadas com dois (a barra invertida e mais um). As principais sequências de escape são \n (nova linha), \t (tab), \' (aspas simples, \" (aspas duplas), \\\\ (a própria barra invertida).

Então, se temos uma string definida com aspas duplas ...e precisamos inserir aspas duplas dentro da mesma string, podemos usar a sequencia de escape \" que dirá ao Python que esta aspa dupla não deve ser interpretado como o fim da minha string.

Uma lista das sequências de escape permitidas pelo Python está no tutorial de strings do site TutorialsPoint:


In [16]:
x = 'o advogado'
y = 'o advo\ngado'
# a função built-in len() dá o comprimento do objeto passado
print x,':',len(x)
print y,':',len(y)

# a sequência de escape \" faz com que a aspa dupla 
# não seja interpretada como delimitador da string!
x = "\ndisse\t\"oi\""
print x,':',len(x)


o advogado : 10
o advo
gado : 11

disse	"oi" : 11

Strings são iteráveis (como veremos na seção sobre iteração) e implementam diferentes operadores, incluindo concatenação (via o mais, '+'), indexação e slicing (com '[]') e repetição (via o operador de multiplicação, '*':


In [17]:
x = "Dani" + "el" # concatenação com +
print x

x = 'oi! ' * 3 # repetição com *
print x

x = "Oi, me chamo Daniel" # slicing com []
print x[0:13] + 'Felipe'


Daniel
oi! oi! oi! 
Oi, me chamo Felipe


Veremos mais sobre estes operadores no módulo sobre listas.

O None

O None é uma palavra reservada do Python e uma constante built-in. None implica a ausência de valor. None pode ser usado quando queremos declarar uma variável, mas não lhe dar nenhum valor. Vale ressaltar que None sempre é avaliado como falso (veja o exemplo abaixo).

Veremos mais sobre o None na seção sobre funções.


In [18]:
x = None
print x
print bool(x)
x = 3
print x


None
False
3

Funções Úteis: dir(), type(), locals(), globals()

Existem certas funcionalidades muito úteis no Python, que são disponibilizadas como funções do próprio Python, não atreladas a algum módulo ou objeto. Veremos muitas destas no decorrer desta apostila. Vamos começar explorando quatro:

type(x): a função type retorna um objeto type que representa o tipo da variável x em questão (veja a seção tipagem dinâmica para exemplos).

dir(x): retorna uma lista dos atributos (variáveis e funções) internos da variável x em questão

locals(): retorna uma lista co os nomes de todas as variáveis no escopo local (veremos mais sobre escopo no módulo sobre funções)

globals(): retorna uma lista co os nomes de todas as variáveis no escopo global (veremos mais sobre escopo no módulo sobre funções)

print é um statement tão utilizado no Python, que os criadores da linguagem não requerem o uso de parênteses para sua chamada (isso mudou no Python 3). Vale notar que o print no Python 2.x não é um operador nem chamada de função.

Tipagem Dinâmica

O Python utiliza uma forma de designação de tipos chamada 'tipagem dinâmica' (em contraste à 'tipagem estática'). Isto significa que toda a informação sobre o tipo de ma variável fica guardada com o objeto ao qual ela aponta, não com a própria variável.

Veja:


In [19]:
x = 3            # x é um número
print type(x)
x = 'e agora?'   # x é um string
print type(x)
x = None         # x agora é o None! (única coisa no Python que tem o tipo NoneType)
print type(x)
x = len          # x agora é a função len, logo seu tipo é 'função built-in'
print type(x)
x = type         # finalmente, x agora é a própria função type, seu tipo então é 'type'
print x


<type 'int'>
<type 'str'>
<type 'NoneType'>
<type 'builtin_function_or_method'>
<type 'type'>

Encontramos uma lista dos tipos built-in do Python em python.org

Módulo 2 - Tupla, Lista, Indexing e Slicing, Condicionais com if

Estruturas de Dados

Uma estrutura de dados é uma forma de se organizar os dados para utilização eficiente. Seja consulta, manipulação ou adição posterior de novos dados de forma rápida, ou até utilizações altamente específicas. Toda linguagem de computação se utiliza de estrutura de dados para alcançar seus fins.

O Python (como toda outra linguagem) permite a criação de estruturas de dados por parte do programador. Mas o Python também provê uma série de estruturas genéricas e de forte utilidade. Já vimos uma destas: a string, que é uma forma de se organizar characteres individuais em sequência.

Veremos agora duas outras estruturas que também organizam dados de forma sequencial: a lista e a tupla.

Lista e Tupla

A lista é uma das estruturas de dados mais utilizadas no Python, e se define por uma sequência de variáveis, separadas por vírgula e delimitadas por colchetes. Os itens de uma lista podem ser quaisquer variáveis ou objetos do Python, e uma lista não requer que seus elementos sejam uniformes. Veja o código abaixo:


In [20]:
lista1 = ['lista','de','strings']
lista2 = [3.14, 'heterogeneidade', 'de', type(lista1), 'valores', lista1]

A tupla é uma sequência imutável de objetos, separados por vírgula e delimitados por parênteses. Esta é a principal diferença entre a lista e a tupla: uma vez criada, seus membros não podem mais ser alterados. Veja o código abaixo:


In [21]:
tupla1 = ('lista1',lista1,'lista2',lista2)
tupla1[0] = 'blah!' # vai dar erro!


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-21-abd7796095f2> in <module>()
      1 tupla1 = ('lista1',lista1,'lista2',lista2)
----> 2 tupla1[0] = 'blah!' # vai dar erro!

TypeError: 'tuple' object does not support item assignment


O erro indica que um item da tupla não pode ser designado, isto é, após a sua criação, seus itens não podem ser modificados!

...note que colocamos uma lista dentro de uma tupla ..este tipo de composição de objetos e estruturas de dados não só é permitido como pode ser muito útil!

Indexação (*indexing*) e Recorte (*slicing*)

Vimos que listas e tuplas no Python estruturas de dados que permitem guardar coleções de itens, sejam estes variáveis, literais, objetos, funções e até outras listas e tuplas! Estes itens ficam guardados em ordem, na lista ou tupla, e podemos acessá-los via seu índice, isto é sua posição na lista ou tupla.

Este índice, por convenção dos criadores do Python (e seguindo a tradição de outras linguagens) sempre começa com o ZERO...vejamos:


In [22]:
# aqui, Daniel é o segundo item da tupla
equipeSen = ('Ivar', 'Daniel', 'Felipe', 'Pedro', 'Bianca', 'Luan', 'Mariana')

# como todas as coleções em Python começam com índice ZERO, acessamos o SEGUNDO item com o índice 1
print 'oi!, meu nome é', equipeSen[1]


oi!, meu nome é Daniel

Note que acessamos os itens contido na lista/tupla usando colchetes após o nome da variável que representa a lista/tupla. É assim que indexamos os valores guardados em listas e tuplas. Vale ressaltar que strings também funcionam da mesma forma (podemos pensar neles como listas de caracteres!)


In [23]:
cidade = 'Rio de Janeiro'

print cidade[7]


J

Se quisermos acessar uma sublista dos valores guardados em uma lista ou tupla, podemos vazer um recorte dela:


In [24]:
lideresSen = equipeSen[0:2] # note que eu defino até o dois para pegar o índice 1, pois o python é indexado em ZERO
print lideresSen


('Ivar', 'Daniel')

A indexação pode ser feita da esquerda para a direita, começando do zero, ou da direita para a esquerda, começando do -1. Assim, o último item do objeto iterável (lista, tupla ou string) pode ser acessado pelo seu valor positivo (que podemos pegar com len()) ou pelo número -1 ...vejamos um exemplo:


In [25]:
ultimoSen = equipeSen[-1]
print ultimoSen
penultimoSen = equipeSen[-2]
print penultimoSen

novosSen = equipeSen[-2:]
print novosSen


Mariana
Luan
('Luan', 'Mariana')

Algumas das principais funções de listas são:


In [26]:
"""Easter Egg: os primeiros 4 que me mandarem um e-mail explicando 
corretamente o que estou fazendo na linha abaixo e porque, ganham um chocolate"""
SeNList = list(equipeSen) 

# adiciona um objeto ao final da lista. CUIDADO: se adicionar uma lista, a própria lista, e não seus membros, será adicionada
SeNList.append('Fabio') 
print 'adicionei mesmo:\n',SeNList

# extend extrai os MEMBROS de uma coleção, e os adiciona ...note que é uma TUPLA sendo passada
SeNList.extend(('Gabriel', 'Daniel'))
print '\nadicionei os membros da tupla:\n',SeNList

# podemos concatenar listas
SeNList = SeNList + ['Fred','Livia']

# podemos repetir seus membros
print '\nmultiplicando:',['Oi','SeN'] * 3

# remove retira o objeto
SeNList.remove('Pedro')
print '\nremovi:\n',SeNList

# acho a posição de um objeto
aSair = 'Fabio'
print '\nQual a posição do membro '+aSair+'?',SeNList.index(aSair)

# insere o objeto passado na posição x (opcional, default 0)
SeNList.insert(-1,'Pedro')

# podemos deletar um objeto de dentro da lista
del(SeNList[SeNList.index(aSair)])

# podemos contar?
print '\nquantos Danieis?', SeNList.count('Daniel')

print '\npor fim:', SeNList


adicionei mesmo:
['Ivar', 'Daniel', 'Felipe', 'Pedro', 'Bianca', 'Luan', 'Mariana', 'Fabio']

adicionei os membros da tupla:
['Ivar', 'Daniel', 'Felipe', 'Pedro', 'Bianca', 'Luan', 'Mariana', 'Fabio', 'Gabriel', 'Daniel']

multiplicando: ['Oi', 'SeN', 'Oi', 'SeN', 'Oi', 'SeN']

removi:
['Ivar', 'Daniel', 'Felipe', 'Bianca', 'Luan', 'Mariana', 'Fabio', 'Gabriel', 'Daniel', 'Fred', 'Livia']

Qual a posição do membro Fabio? 6

quantos Danieis? 2

por fim: ['Ivar', 'Daniel', 'Felipe', 'Bianca', 'Luan', 'Mariana', 'Gabriel', 'Daniel', 'Fred', 'Pedro', 'Livia']

Condicionando a sequência do programa: if, elif, else

Vamos mudar o foco agora de estruturas de dados para o fluxo dos nossos programas.

Até agora, os nossos programas todos seguem uma sequência lógica única, isto é, executamos um comando por vez, um atrás do outro (A), contudo, podemos usar a estrutura condicional if para que nosso programa possa tomar caminhos alternativos (B) a sintaxe é:

if [[condição a ser avaliada]] : <- note o 'dois pontos'
[[instruções avaliadas...]]
[[...se a condição for verdadeira...]]
[[...ficam dentro de um novo escopo (com tab)]]

ao terminar, retorna-se ao escopo normal, escrevendo instruções sem tab (ou com um tab a menos!)

Vejam o código abaixo:


In [27]:
minhalista = ['Lucas', 16, 'RS']
if minhalista[1] < 18: # não esqueçam o 'dois pontos'!
    minhalista.append('menor de idade') # notem que uso a função append() para colocar uma nova informação na lista
print minhalista


['Lucas', 16, 'RS', 'menor de idade']

Podemos adicionar mais condicionais, com elif e else.

Com elif [[nova condicional]]:, podemos encadear condicionais de forma que se a condicional do primeiro if der falso, a condicional do primeiro elif será avaliada. Se esta der falso também, a segunda será avaliada, e assim por diante. Veja o código abaixo:


In [28]:
genteNaSala = [] # inicio uma lista vazia

if 'Ivar' in genteNaSala: 
    genteNaSala.extend(['Luan','Gabriel','Livia'])
elif 'Daniel' in genteNaSala:
    genteNaSala.extend(['Mariana','Fred','Bianca'])
    
print genteNaSala # como nem o Daniel nem o Ivar estão, a sala fica vazia!


[]

Aproveite para brincar com os valores iniciais da lista e com as condicionais do if e do elif.

O else por sua vez, oferece uma última opção de instruções para o caso que nenhuma das condicionais de true. Seri o caso do senão ...passamos pelo if e quaisquer elifs dizendo "se isso acontecer, ou então se isso acontecer, ou então se isso acontecer" ...com o else, colocamos um senão ao final, caso nenhuma outra condicional seja preenchida. Vamos ver um exemplo:


In [29]:
genteNaSala = [] # inicio uma lista vazia

if 'Ivar' in genteNaSala: #
    genteNaSala.extend(['Luan','Gabriel','Livia','Felipe'])
elif 'Daniel' in genteNaSala:
    genteNaSala.extend(['Mariana','Fred','Bianca','Felipe'])
else:
    genteNaSala.append('Felipe')
    
print genteNaSala # o Felipe está SEMPRE na sala.


['Felipe']
PERGUNTA: Qual a diferença entre usar elif e encadear condições if? Parece dar no mesmo!


Boa pergunta! vamos ver qual a diferença...

Digamos que eu tenho uma lista dos professores que vão sair para almoçar. Como cada professor tem o seu apelido favorito para o professor Ivar, vamos reeditar a lista com ifs encadeados e com elifs, para ver qual será o apelido utilizado em um dado almoço para o prof. Ivar.


In [30]:
# notem que aqui uso uma lista, não uma tupla, pois vou alterar os seus valores!
quemAlmoca = ['Ivar', 'Daniel', 'Leandro']

if 'Daniel' in quemAlmoca:
    quemAlmoca[0] = 'Ivair' # note aqui uma mudança para escopo interno
if 'Leandro' in quemAlmoca:
    quemAlmoca[0] = 'Ivan' # o else também
if 'Fernando' in quemAlmoca:
    quemAlmoca[0] = 'Ismar'
    
print quemAlmoca # qual será o nome do Ivar neste almoço??


['Ivan', 'Daniel', 'Leandro']

...se usarmos outra lista, vejam:


In [31]:
quemAlmoca = ['Ivar', 'Daniel', 'Leandro', 'Fernando'] # mudei a lista!

if 'Daniel' in quemAlmoca:
    quemAlmoca[0] = 'Ivair' 
elif 'Leandro' in quemAlmoca: # só mudei o if para elif aqui...
    quemAlmoca[0] = 'Ivan' 
elif 'Fernando' in quemAlmoca: # ...e aqui
    quemAlmoca[0] = 'Ismar'
    
print quemAlmoca # qual será o nome do Ivar neste almoço??


['Ivair', 'Daniel', 'Leandro', 'Fernando']


Os dois exemplos de código mostram a diferença entre usar elif ao invés de encadear ifs.

Operadores Lógicos: not, or, and

Os operadores lógicos nos permitem encadear avaliações lógicas. Já sabemos que True e False são literais essenciais à programação e que podemos modificar o curso do nosso programa com condicionais e avaliações lógicas.

Com operadores lógicos podemos criar avaliações mais complexas, e de maior utilidade.

vejamos:


In [32]:
idade = 79
sexo = 'feminino'
hc = ""

# posso fazer...
if (idade > 65 and sexo == "feminino"):
    hc = hc + "a senhora, sendo de idade avançada..."
elif(idade > 65 and sexo == "masculino"):
    hc = hc + "o senhor, sendo de idade avançada..."

print hc


a senhora, sendo de idade avançada...


a ordem de avaliação de operadores é: not, and, or

not: é usado para inverter a avaliação de uma proposição, se é True o not o tornará falso.

and: retorna True somente se ambos os seus operandos forem verdadeiros.

or: retorna True se qualquer um ou ambos os operandos foram verdadeiros.

No caso do and as proposições são avaliadas da esquerda para a direita, sendo que a primeira que dê False interrompe a avaliação.

O caso do or é similar mas oposto: avalia-se da esquerda para a direita, sendo que o primeiro True interrompe a avaliação.

Podemos brincar um pouco com essas propriedades:


In [33]:
x = 25
y = None #lembrem que é igual a falso!

z = x or y
print z


25

In [34]:
z = x and y
print z


None

In [35]:
l = []
s = ""
n = None

z = l and s and n
print z

z = l or s or n
print z


[]
None


Surerimos que vocês usem a funcionalidade do iPython Notebook para explorar as possibilidade de operadores lógicos, partindo dos exemplos acima.

Módulo 3 - Funções (pte. 1)

Funções são blocos de código reutilizável, organizado de forma a ser reutilizado e reduzir a repetição de código.

Uma função pode ser vista como uma unidade de código que executa uma única tarefa.

O Python disponibiliza uma série de funções ao programador (veja as built-in functions aqui), mas podemos também criar as nossas próprias funções, a fim de organizar, e poder reutilizar o nosso código.

Já viemos trabalhando informalmente com funções desde o Módulo 1, porém agora vamos definir mais rigorosamente a sintaxe e funcionamento de uma função:

Definindo ('declarando'):

Definimos uma função com a palavra reservada def, seguida do nome que queremos dar à função, depois colocamos quaisquer argumentos que queiramos passar à função entre parênteses (ou deixamos eles vazios). Por fim, usamos dois pontos para fechar a linha de definição e abrir o novo escopo (vide seção abaixo, para a definição de 'escopo').


In [36]:
def foo(): # def <nome da função> <parênteses vazios ou com parâmetros> <dois pontos>
    # <<instruções aqui>>
    # <<instruções aqui>>      <- note o novo escopo! (veja abaixo)
    # <<instruções aqui>>
    pass # posso usar 'pass' como placeholder, vamos falar sobre o pass já já

Usando ('chamando'):

Após declarar uma função, podemos utilizar o seu código simplesmente chamando a função por nome, e passando quaisquer argumentos que lhe sejam necessários:


In [37]:
def mostrar_frase(nome, sobrenome, idade):
    print 'Oi, meu nome é',nome, sobrenome, 'e tenho',idade,'anos'
    
mostrar_frase('Daniel','Chada','34')
mostrar_frase('Ivar','Hartmann','28')
mostrar_frase('Felipe','Silva','25')


Oi, meu nome é Daniel Chada e tenho 34 anos
Oi, meu nome é Ivar Hartmann e tenho 28 anos
Oi, meu nome é Felipe Silva e tenho 25 anos


Aqui passamos os argumentos 'nome' e 'idade' a cada vez que chamamos a função mostrar_frase. Como mostrar_frase foi definida para receber dois parâmetros, que dentro dela são chamados pelos nomes nome e idade se a chamarmos sem passar os argumentos necessários, receberemos um erro!


In [38]:
mostrar_frase('Mari') # uh oh! falta argumento!


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-38-a5ee942457ef> in <module>()
----> 1 mostrar_frase('Mari') # uh oh! falta argumento!

TypeError: mostrar_frase() takes exactly 3 arguments (1 given)

Além de passarmos os argumentos definidos utilizando a ordem (no caso de mostrar_frase 'nome' em primeiro, depois 'idade') também podemos passar argumentos 'dando nome aos bois'. Isso se chamar usar keyword arguments ...vejamos


In [39]:
mostrar_frase(idade='25',nome='Mari', sobrenome='Bedran')


Oi, meu nome é Mari Bedran e tenho 25 anos

Mas cuidado, uma vez que começamos a nomear, não podemos voltar a utilizar a ordenação (até porque o Python não saberia poe onde recomeçar).


In [40]:
mostrar_frase(idade='25', 'Mari', 'Bedran') # po Mari, para de quebrar o Python!


  File "<ipython-input-40-164c6b628553>", line 1
    mostrar_frase(idade='25', 'Mari', 'Bedran') # po Mari, para de quebrar o Python!
SyntaxError: non-keyword arg after keyword arg

O que é '**escopo**'?

Vimos anteriormente que variáveis são somente nomes dados a objetos, isto é, tags que damos a objetos e referenciam os objetos para nós. Porém, nem toda referência que fazemos fica válida por todo o decorrer do nosso programa.

Uma definição intuitiva de escopo é a parte, ou subseção do nosso programa em que certas junções nome-->objeto se mantém válidas.

Variáveis declaradas dentro de um determinado escopo em geral não se mantém válidas uma vez que o processamento daquele escopo termina.

O Python tem regras muito bem definidas de escopo, que são fáceis de lembrar via o mnemonico: LEGB: Local, Enclosing, Global, Built-in.

-Local: nomes de variável definidos dentro da função na qual estamos trabalhando
-Enclosing: nomes de variável definidos na função que cerca o atual escopo local (no caso de aninhamento de funções, ou funções lambda que veremos
-Global: o escopo global do arquivo .py em que estamos trabalhando
-Built-in: nativas ao Python, acessíveis de qualquer programa Python


In [41]:
# CUIDADO COM O ESCOPO LOCAL!
var = 'foo'
def ex1():
    var = 'bar'
    print 'dentro temos', var

ex1()
print 'fora temos', var


dentro temos bar
fora temos foo

Este blog post: http://www.saltycrane.com/blog/2008/01/python-variable-scope-notes/ contém um ótimo tutorial sobre escopo no Python.

Revendo operadores

Agora que já conhecemos o suficiente sobre funções, podemos revelar a verdade! (TAM TAM TAAAAAM!)

Os operadores no Python são o que chamamos de "açúcar sintático" (syntactic sugar) ...eles são atalhos: formas mais rápidas e fáceis de digitar coisas que na realidade, têm outro nome.

o operador '+' para números, na realidade pega o objeto referenciado pelo primeiro número e chama a sue função interna \_\_add\_\_(), passando o segundo número

Já o operador '*' chama a função \_\_mul\_\_().

...veja:


In [42]:
x = 3
y = x.__add__(4)
z = y.__mul__(x)
print x, y
print z


3 7
21


Listas, tuplas e strings, ao usar operadores, também usam syntactic sugar:


In [43]:
l = ['syntactic','sugar']
print l.__getitem__(1)
print l.__getitem__(1) == l[1]

l.__setitem__(0,'salt')
print l


sugar
True
['salt', 'sugar']

Podemos dizer que quase tudo no Python se reverte a executar alguma função.

A palavra reservada pass

A palavra reservada pass no Python é uma operação nula (como multiplicar por 1). Ela é útil como preenchimento quando algum comando é necessário sintaticamente mas não temos código para executar naquele lugar.


In [44]:
def foo():
    pass # não dá erro como daria se só continuassemos o código
print '123'


123

Mudando variáveis dentro de um escopo --- *Pass by Value, Pass by Reference*, ** *pass-by-object reference* **

Para literais, Python usa pass-by-value , enquanto para estruturas de dados, pass-by-reference ...vejamos as diferenç

Em pass-by-value, passamos o valor, isto é, o lugar onde o objeto está guardado. Em pass-by-reference passamos, como o nome implica, a referência, isto é, o ponteiro que aonte para onde o objeto está.

A diferença deve ficar mais clara nos exemplos abaixo


In [45]:
# pass-by-value

def ex1(nome_interno):
    print 'primeiro dentro temos',nome_interno
    var = 'bar'
    print 'depois temos', nome_interno
    
var = 'foo'
ex1(var)
print 'fora temos', var


primeiro dentro temos foo
depois temos foo
fora temos foo

In [46]:
# exemplo de pass-by-reference
def ex2(aqui_dentro_chamo_disso):
    print 'aqui temos',aqui_dentro_chamo_disso
    aqui_dentro_chamo_disso[1] = 'ih, mudou?'
    print 'depois temos', aqui_dentro_chamo_disso

var = ['foo','bar']
ex2(var)
print 'e aqui fora',var


aqui temos ['foo', 'bar']
depois temos ['foo', 'ih, mudou?']
e aqui fora ['foo', 'ih, mudou?']

Este comportamento do Python, na literatura, o define como nem pass-by-value nem pass-by-reference ..mas omo *pass-by-object-reference. No fundo não importa muito o que se chama, desde que os conceitos estejam sólidamente compreendidos.

Este link pode ajudar, e este também

Retornando valores (ou não)

A palavra-chave return imediatamente finaliza a execução de código dentro de qualquer função e efetua a saída do escopo vigente.

Isto é, o return termina e retorna ao escopo externo (de onde se chamou a função) o valor que o sucede. Um return sem nada em sua frente implica em retornar None.

por default a finalização de qualquer função, sem o uso da palavra-chave return tem o mesmo efeito que return None

vejamos exemplos:


In [47]:
def eh_string(input):
    if isinstance(input, str):
        # caso seja uma string, não preciso continuar com a função
        return True 
    elif input:
        print '\t...not empty, not False, not None, but not a string!'
        return False
    elif input is None:
        print '\t...recebi um None.'
        return False
    print '\t...recebi algo, mas conta como falso.'
    return False
    
x = eh_string(None)
y = eh_string("oi!")
z = eh_string(21234)
w = eh_string(()) # veremos mais sobre o que é False e o que é True em breve!

print x,y,z,w


	...recebi um None.
	...not empty, not False, not None, but not a string!
	...recebi algo, mas conta como falso.
False True False False


Veja que a execução pára quando se chega a um return.

vejamos outro exemplo:


In [48]:
def find_x(input):
    for item in input:
        if item == 'x':
            return True 
    print 'cheguei ao fim e nada...nada de xiiiiiis!!!'
    return False

print find_x([123,'Daniel','x','Ivar'])

print '\nsegunda tentativa:'
print find_x([123,'Daniel','y','Ivar'])


True

segunda tentativa:
cheguei ao fim e nada...nada de xiiiiiis!!!
False

Módulo 4 - Iteração com for, range(); a "Sua Amiga a Internet"

O loop for:

O loop for nos permite iterar uma série de comandos e funções, sem precisar repeti-los seguidamente. Isto é uma ferramenta tremendamente útil em qualquer tarefa repetitiva, especialmente em tarefas que não sabemos quantas vezes teremos que repetir um dado grupo de comandos.

O loop for introduz uma nova possibilidade no caminho do nosso programa, em que voltamos e repetimos um dado conjunto de comandos. Veja a fig_2.

A sintaxe de um loop for é:

for [[nome de uma ou mais variáveis que serão iteradas]] in [[coleção iterável]]:
[[instruções em um novo escopo interno aqui]]
[[instruções em um novo escopo interno aqui]]
[[instruções em um novo escopo interno aqui]]
[[instruções sem tab indicam a volta saída do escopo do for]]

Vejamos na prática:


In [49]:
equipeSen = ('Ivar', 'Daniel', 'Felipe', 'Pedro', 'Mariana', 'Bianca', 'Luan', 'Gabriel', 'Livia')

for y in equipeSen:
    print 'oi, '+ y


oi, Ivar
oi, Daniel
oi, Felipe
oi, Pedro
oi, Mariana
oi, Bianca
oi, Luan
oi, Gabriel
oi, Livia

Usando range()

a função range() é uma função built-in do Python (como a len(), locals() ou str()) que retorna uma lista de numeros, em sequência:


In [50]:
print range(10)


[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

A função range() pode ser usada para listar números de um certo x a um certo y, ou até pular certos intervalos:


In [51]:
print range(3,15)   # a função range cria uma lista de 3 a 14 e a retorna como entrada da função print.

print range(3,15,3) # 'imprima do 2 ao 15 (parando um antes), com intervalo de 3'


[3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
[3, 6, 9, 12]

O principal uso da função range() é em conjunto à iteração com um loop for (e a função len())


In [52]:
SeNDev = ['Daniel', "Felipe", "Mariana"]
SeNJur = ['Ivar', 'Livia', 'Gabriel', 'Bianca', 'Luan']

def digaOi(x):
    # posso usar o range() em conjunto com len() se não sei o tamanho
    for i in range(len(x)): 
        # note que o for tem seu próprio escopo!
        print 'oi, ' + x[i] +"!",
    # para que serve este print? tente comentá'-lo!
    print '' 

digaOi(SeNDev)
digaOi(SeNJur)


oi, Daniel! oi, Felipe! oi, Mariana! 
oi, Ivar! oi, Livia! oi, Gabriel! oi, Bianca! oi, Luan! 

Sua Amiga a Internet" ou "Olhe no Stack Overflow Antes!"

Python é uma linguagem muito rica, com uma extensa comunidade de desenvolvedores do mundo todo que contribuem para o seu desenvolvimento. Enquanto alguns contribuem para o desenvolvimento do core da linguagem, outros contribuem escrevendo módulos e disponibilizando-os gratuitamente na internet.

Assim, existe uma enorme quantidade de ferramentas prontas em python, na forma de módulos que podem ser baixados e instalados no seu python local.

Quase nenhum projeto no Python precisa ser iniciado do zero. Com um pouco de pesquisa na internet, vocês provavelmetne encontrarão algum módulo ou pedaço de código que já resolve alguma parte do problema que você tem a reselver.

Recomendamos que vocês utilizem o site http://stackoverflow.com e leiam a documentação oficial da linguagem python http://bit.ly/Pe9lfA.

O Stack Overflow é um repositório de dúvidas e respostas sobre todas as linguagens e áreas da programação (não só Python!), mas tudo é identificado via tags, e todos os membros (tanto os que perguntam quanto os que respondem) prezam pela clareza e legibilidade, fazendo do site uma ótima primeira instância para pesquisa sobre qualquer dúvida.

O site oficial do Python http://python.org e a documentação do Python http://python.org/doc são as referências oficiais sobre o core da linguagem.

Finalmente, na data de escrita desta apostila - v1.0 (Set/2014) o site Tutorials Point http://www.tutorialspoint.com/python/ oferece bons e rápidos tutoriais sobre diferentes aspectos do Python.

Módulo 5 - Importação de Módulos, Abrindo e Manipulando Arquivos, Dicionários

Neste módulo vamos ver as duas últimas estruturas de dados do conjunto fundamental provido pelo python. Nós vimos números, strings, listas e tuplas ...hoje veremos arquivos e dicionários.

Veremos também como importar módulos, para utilizar código criado por outros dentro de nossos programas

Arquivos - abrindo, e manipulando, de dentro e de fora

Arquivos são outra estrutura de dados importantíssima para o programador Python. Tudo que se manipula como usuário de computador toma a forma de um arquivo: imagens, PDFs, planilhas e até outros programas. De fato, todos os dados que ficam guardados em um computador de forma permanente (isto é, no disco e não na memória) tomam a forma de arquivos. Até os próprios sistemas operacionais (Windows, Linux, OSX, etc) são coleções de arquivos que ficam guardados no disco rígido (hard disk, HD) são carregados quando ocomputador se inicia.

O Acesso e manipulação de arquivos permitem ao nossos programas interagir com bases de dados, imagens e outros até programas no computador. Em Python abrimos arquivos com a chamada open que retorna um objeto arquivo.

meu_arq = open([[nome do arquivo]], [[modo]])

Outro modo de abertura de arquivo (ntem que inicializa um novo escopo!) é:

with open([[nome do arquivo]], [[modo]]) as meu_arq: [[expressões em novo escopo]] [[expressões em novo escopo]] [[expressões em novo escopo]]

O parâmero modo lida com o que será feito com o arquivo, a tabela 5.1 lista os principais parâmetros que podem ser passados. Se nada for passado, o arquivo é aberto para leitura, isto é, o default é 'r':

ModesDescription
rOpens a file for reading only. The file pointer is placed at the beginning of the file. This is the default mode.
rbOpens a file for reading only in binary format. The file pointer is placed at the beginning of the file. This is the default mode.
r+Opens a file for both reading and writing. The file pointer will be at the beginning of the file.
rb+Opens a file for both reading and writing in binary format. The file pointer will be at the beginning of the file.
wOpens a file for writing only. Overwrites the file if the file exists. If the file does not exist, creates a new file for writing.
wbOpens a file for writing only in binary format. Overwrites the file if the file exists. If the file does not exist, creates a new file for writing.
w+Opens a file for both writing and reading. Overwrites the existing file if the file exists. If the file does not exist, creates a new file for reading and writing.
wb+Opens a file for both writing and reading in binary format. Overwrites the existing file if the file exists. If the file does not exist, creates a new file for reading and writing.
aOpens a file for appending. The file pointer is at the end of the file if the file exists. That is, the file is in the append mode. If the file does not exist, it creates a new file for writing.
ab Opens a file for appending in binary format. The file pointer is at the end of the file if the file exists. That is, the file is in the append mode. If the file does not exist, it creates a new file for writing.
a+ Opens a file for both appending and reading. The file pointer is at the end of the file if the file exists. The file opens in the append mode. If the file does not exist, it creates a new file for reading and writing.
ab+Opens a file for both appending and reading in binary format. The file pointer is at the end of the file if the file exists. The file opens in the append mode. If the file does not exist, it creates a new file for reading and writing.
fonte: http://www.tutorialspoint.com/python/python_files_io.htm

In [53]:
f = open('arq.csv','r+') # neste exemplo, o arquivo deve estar na mesma pasta do programa sendo rodado
# a função 'open' retorna um objeto tipo arquivo
print f
print type(f)


<open file 'arq.csv', mode 'r+' at 0x103e67930>
<type 'file'>


Uma vez 'abertos', podemos usar as funções dos objetos arquivo para manipular o conteúdo dos arquivos, os exemplos abaixo mostram as principais funções para manipular o conteúdo de arquivos:


In [54]:
f = open('arq.csv','r')

# read() le e retorna o conteudo inteiro do arquivo ..se o arquivo for maior que a memória utilizável, corta a leitura
# podemos também passar um número, que será o número de bytes a serem lidos do arquivo.
x = f.read()

print x
f.close()


Ivar,28,Harvard
Daniel,34,PUC
Felipe,24,FGV

In [55]:
f = open('novoarq.csv','w+')
x = 'Mariana,25,UFF'

# write() (sobre)escreve o conteudo da variável passada para o arquivo. Não retorna nada.
nada = f.write(x)

print nada 
f.close()


None


Quando um arquivo é aberto para leiura, o Python aponta um 'pointeiro de arquivo' (eu chamo de 'cursor') para o conteúdo do arquivo. À medida que lemos ou editamos o conteúdo do arquivo, este 'cursor' é movido para a frente (ou para trás). Cada função de leitura ou escrita move o 'cursor' para frente, de forma que uma leitura (ou escrita) subsequente retornará outro resultado.

Dependendo do modo utilizado ao abrir o arquivo, podemos já abri-lo com o cursor apontando para o final (modo 'a')
Outras funções de escrita e leitura de arquivo são:

readline() - retorna uma linha do arquivo, e passa o cursor para a próxima linha
readlines() – devolve lista em que cada item é uma linha
writelines() – recebe uma lista ou tupla ou iterável e cada item será inserido como se chamando writeline()
f.tell() – retorna em que parte do arquivo o cursor parou
f.seek(from, offset) – muda o cursor do from (0 pega do começo do arquivo, 1 usa a posição atual e 2 usa o final do arquivo como referência ..o default é 0) e avança tantos bytes quanto indicado no offset

Encorajamos o leitor a explorar a manipulação dos dados de arquivos com estas funções, e buscar mais aprofundadamente sobre seu funcionamento na seção sobre arquivos da python.org

Dicionários

Dicionários trazem uma nova forma de guardar coleções de dados/objetos. Listas e tuplas nos dão uma forma ordinal de guardar dados, e podemos acessálos pela sua posição na ordem (i.e. nomes[3] retorna o segundo item da lista nomes). Já dicionários indexam seus valores de outra forma: por chaves. Ao invés de utilizar ordem, o dicionário usa uma função chamad função hash para decidir onde melhor colocar cada novo item que é adicionado. Isso torna o seu tempo de resposta muito mais rápido ao localizar um valor associado a uma chave.

Há algumas restrições no que é passível de ser uma chave (quem quiser estudar mais as premissas, pode ler em https://wiki.python.org/moin/DictionaryKeys) mas aqui explicitamos que números, tuplas e strings podem ser chaves, enquanto listas não podem.

Note que isso não implica o que pode ser guardado como valor, qualquer variável ou objeto pode ser guardado em um dicionário, até funções inteiras e módulos ...somente a chave tem restrições.

Definimos dicionários com: {} e acessamos seus itens da mesma forma que listas:


In [56]:
x = {
'Ivar' : [27,'Nova Hamburgo', 'Inter'],
'Daniel' : [34, 'Rio de Janeiro', 'Flamengo']
}

# podemos adicionar valores aos dicionários, eles são *mutáveis*
x['Felipe'] = [24, 'Matrix', 'C']

print x['Felipe']

print x['Ivan'] # notem que dá erro, o Python nos diz que não existe a chave 'Ivan'


[24, 'Matrix', 'C']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-56-bc4cee0fac11> in <module>()
      9 print x['Felipe']
     10 
---> 11 print x['Ivan'] # notem que dá erro, o Python nos diz que não existe a chave 'Ivan'
     12 

KeyError: 'Ivan'

Como em todas as estruturas que guardam múltiplos dados, podemos guardar outros dicionários dentro de dicionários:


In [57]:
x = {
'Ivar' : {'idade':28,'cidade':'Nova Hamburgo', 'time':'Inter', 'lingua':'Legagese'},
'Daniel' : {'idade':float('Inf'), 'cidade':'Barad Dur', 'lingua':'Black Speech', 'time':'Mordor'},
'Felipe' : {'cidade':'Xion', 'time':'Morpheus', 'idade':float('NaN') , 'lingua':'CUDA'}
}

print x['Felipe']['time'] 
print x['Daniel']['idade'] # professores malvados são eternos! 
# PS: quem consegue explicar a minha idade e a do Felipe??


Morpheus
inf


Dicionários têm funções muito úteis, a saber:


In [58]:
# a função get() evita que o erro anterior aconteça, retornando None quando a chave não é encontrada
print x.get('Ivar')
# para obter todas as chaves do dicionário, usamos keys(), que retorna uma lista
print '\nKEYS:\t',x.keys()
# para obter os valores, a função values(), que retorna uma lista
print '\nVALUES:\t',x.values()
# já a função items() retorna uma lista de tuplas na forma (chave,valor)
print '\nITEMS:\t',x.items()


{'idade': 28, 'cidade': 'Nova Hamburgo', 'lingua': 'Legagese', 'time': 'Inter'}

KEYS:	['Felipe', 'Daniel', 'Ivar']

VALUES:	[{'idade': nan, 'cidade': 'Xion', 'lingua': 'CUDA', 'time': 'Morpheus'}, {'idade': inf, 'cidade': 'Barad Dur', 'lingua': 'Black Speech', 'time': 'Mordor'}, {'idade': 28, 'cidade': 'Nova Hamburgo', 'lingua': 'Legagese', 'time': 'Inter'}]

ITEMS:	[('Felipe', {'idade': nan, 'cidade': 'Xion', 'lingua': 'CUDA', 'time': 'Morpheus'}), ('Daniel', {'idade': inf, 'cidade': 'Barad Dur', 'lingua': 'Black Speech', 'time': 'Mordor'}), ('Ivar', {'idade': 28, 'cidade': 'Nova Hamburgo', 'lingua': 'Legagese', 'time': 'Inter'})]


Vale notar que o print das chaves e valores não é nem alfabético, nem em função da ordem da adição dos registros, mas sim de acordo com a função hash mencionada.

Estas listas retornadas pelas funções permitem iterar as chaves, os valores, ou AMBOS com loops. Os dicionários em si, porém, não são iteráveis (uma explicação básica seria que eles não têm ordenação intrínseca, como as listas e as tuplas).

Importando Módulos

Modulos são a forma de importar código de outros programas seus e de outros para dentro do seu programa. Nós importamos módulos para usar funcionalidades já programados (por nós mesmos ou outros) dentro do nosso código, sem re-escrever.

o jeito de importar módulos é:


In [59]:
import sys # importando o módulo inteiro
from glob import glob # importando uma função específica de um módulo
from math import * # importando todas as funções do módulo para o escopo local
FAQ: Qual é a diferença entre importar o módulo todo e importar todas as funções com o asterisco?

Ao importar um módulo inteiro usando só o comando import, importa-se o módulo como um objeto, e as funções do módulo ficam acessíveis via este objeto:


In [60]:
# um detalhe: posso renomear o módulo ao importar, para facilitar a sua chamada!
import os as o
print o.getcwd() # getcwd() foi importada como função do objeto 'os'!

from math import sqrt, pi
print sqrt(pi) # note que não preciso usar 'math<ponto>'!


/Users/cyg/Documents/work/code/atc_2014
1.77245385091

In [61]:
# mas cuidado!
from math import *

# aqui eu redefino a variavel 'pi' importada antes do math, e perco a original!
pi = 'principal investigação'
print pi

# já aqui, não corro este risco, pois fica como parte do módulo!
import math 
print pi, math.pi


principal investigação
principal investigação 3.14159265359

Manipulando arquivos em si, não só seu conteúdo (o módulo os)

Já vimos como manipular o conteúdo de arquivos, e como trazer código Python externo na forma de módulo para dentro do nosso próprio código. Agora usaremos um módulo do Python para manipular arquivos e diretórios ('pastas' para os que falam Windows®)

Import os .mkdir() – make directory .chdir() – change directory .getcwd() – get current working directory .remove() .rename(a,b) .rmdir() – remove directory

Módulo 6 - Funções (pte. 2); Built-ins: zip, map, reduce, filter; usando lambda; usando \*args, \*\*kwargs

Outras Funcões Built-in

Vamos agora continuar a ver funções built-in úteis no dia-a-dia da programação Python. Uma lista completa pode ser encontrada aqui.

a função map recebe um objeto função e um objeto iterável (i.e. uma sequência...tupla, lista, string, etc) e aplica a função a cada membro do iterável, um de cada vez, coletando os resultados em uma lista. veja:


In [62]:
equipe = ('Ivar','Felipe', 'Daniel', 'Mariana', 'Fred', 'Luan')

# passo a função len e a minha tupla
tamanhos_de_cada = map(len, equipe)

print tamanhos_de_cada


[4, 6, 6, 7, 4, 4]

A função zip retorna uma lista de tuplas, onde a n-ésima tupla contém os n-ésimos itens de cada iterável passado para ela:


In [63]:
idades = [28, float('NaN'), float('Inf'), 25, 22, 22]

# passo dois iteráveis
agrupados = zip(equipe,idades)
print 'agrupados:\t', agrupados

abc = 'abcdefghijklmnopqrstuvwxyz'
# agora um terceiro, note o que acontece quando os tamanhos são 
# diferentes e quando os itens JÁ SÃO iteráveis em si
agrup = zip(agrupados, abc)
print '\nnovo agrup:\t',agrup

# posso DESAGRUPAR com o '*':
desagrup = zip(*agrupados)
print '\ndesagrup:\t',desagrup

# assim...
final = zip(zip(*agrupados)[0],zip(*agrupados)[1],abc)
print '\nfinal:\t',final
# qual a diferença entre 'final' e 'novo agrup'??


agrupados:	[('Ivar', 28), ('Felipe', nan), ('Daniel', inf), ('Mariana', 25), ('Fred', 22), ('Luan', 22)]

novo agrup:	[(('Ivar', 28), 'a'), (('Felipe', nan), 'b'), (('Daniel', inf), 'c'), (('Mariana', 25), 'd'), (('Fred', 22), 'e'), (('Luan', 22), 'f')]

desagrup:	[('Ivar', 'Felipe', 'Daniel', 'Mariana', 'Fred', 'Luan'), (28, nan, inf, 25, 22, 22)]

final:	[('Ivar', 28, 'a'), ('Felipe', nan, 'b'), ('Daniel', inf, 'c'), ('Mariana', 25, 'd'), ('Fred', 22, 'e'), ('Luan', 22, 'f')]


reduce aplica uma função que recebe dois valores e retorna um a um iterável, cada vez pegando o resultado da iteração anterior omo primeiro parâmetro:


In [64]:
def add(x,y):
    return x+y

print reduce(add, [2,4,6,8])


20


filter recebe uma função que retorna True ou False e um iterável. Constrói uma lista com os membros do iterável para os quais a função responde True


In [65]:
CUT_OFF = 5
def maior(x):
    return x >= CUT_OFF

y = filter(maior,tamanhos_de_cada)
print y

def maior_que(x):
    return x[0] >= CUT_OFF

# agora posso fazer algo mais desafiador... o que estou fazendo aqui?? 
print zip(*filter(maior_que, zip(tamanhos_de_cada, equipe)))[1]
#seja um dos 5 primeiros e um email explicando com o subject [explicação do filter]


[6, 6, 7]
('Felipe', 'Daniel', 'Mariana')

Valores *default* em funções

O Python permite que argumentos de funções tenham valores default, de forma que, se nada for passado naquele parametro, a função provê um valor pre-definido ao invés de dar erro.

Em código:


In [66]:
def meu_time(nome, time='Mordor'):
    return nome + ' torce por ' + time

print meu_time('Ivar','Inter')
#  na ausência do valor, vai o default
print meu_time('Daniel')


Ivar torce por Inter
Daniel torce por Mordor

Cuidado que passar None ainda é passar alguma coisa:


In [67]:
print meu_time('Felipe', None) # uh oh!


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-67-387ca642fad3> in <module>()
----> 1 print meu_time('Felipe', None) # uh oh!

<ipython-input-66-c198612e6fc7> in meu_time(nome, time)
      1 def meu_time(nome, time='Mordor'):
----> 2     return nome + ' torce por ' + time
      3 
      4 print meu_time('Ivar','Inter')
      5 #  na ausência do valor, vai o default

TypeError: cannot concatenate 'str' and 'NoneType' objects

IMPORTANTE: É um erro comum utilizar um objeto mutável como valor default. Lembra, de pass-by-value, pass-by-reference?

Vejamos um exemplo:


In [68]:
def bad_append(new_item, a_list=[]):
    a_list.append(new_item)
    return a_list

print bad_append('one')

print bad_append('two')


['one']
['one', 'two']


Uh oh! Por que que o 'one' ainda está lá?!

O problema é que o valor default, isto é, a lista vazia, é definida na declaração da função, e para sempre aponta para o mesmo objeto default, ou seja, o mesmo lugar na memória ...que é modificado!

A forma correta de se criar uma lista (ou dicionário, ou qualquer objeto mutável) default é criá-lo durante a execução ...dentro de função.


In [69]:
def good_append(new_item, a_list=None):
    if a_list is None:
        a_list = []
    a_list.append(new_item)
    return a_list

print good_append('one')
print good_append('two')


['one']
['two']

Funções anônimas com lambda

Há casos em que podemos querer aplicar uma expressão a uma coleção de objetos. Ou usar uma função simples apenas uma vez. Para estes casos, existe o operador lambda.

Podemos usar o operador lambda para criar funções pequenas anônimas. Estas funções são chamadas anônimas pois não precisam de um def e um nome de chamada, isto é, não precisam ser declaradas na forma padrão.

Expressões lambda podem aceitar qualquer quntidade de argumentos (como funções) mas devem conter apenas uma única expressão. Elas não podem aceitar statements ou múltiplas expressões.

Aqui se torna importante que uma chamada print não se qualifica como uma expressão, e sim um statement mais abrangente; logo, chamadas print não são permitidas dentro de lambda.

An anonymous function cannot be a direct call to print because lambda requires an expression.

funções lambda têm seus próprios escopos internos, mas conseguem acessar variáveis da função ou escopo que a chama.

Vamos ver um exemplo:


In [70]:
li = ['Ivar','Daniel','Felipe']

# ao invés de definir uma função só para chamar dentro 
# do meu map ou fazer um for loop, uso uma função anônima
quad = map(lambda inp: inp + ' ama Python', li)

quad


Out[70]:
['Ivar ama Python', 'Daniel ama Python', 'Felipe ama Python']

IMPORTANTE: É necessário tomar cuidado ao usar lambda e def dentro de loops for.

Este cuidado é necessário pois, se a função aninhada referencia algum valor que é modificado pelo loop, a regra de escopo dita que este será modificado e os ponteiros (isto é, as variáveis) criados acabarão por apontar ao valor final do loop.

Vejamos:


In [71]:
def make():
    items = []
    for i in ['Ivar','Daniel','Felipe','Mari']:
        # cada item é um FUNÇÃO criada com lambda
        items.append(lambda x: i + x)
    return items

 # populei uma lista onde cada item é uma ***FUNÇÃO***
 functions = make()
# note...
 functions[0]
# mas agora ao chamar as funções...


Out[71]:
<function __main__.<lambda>>

In [72]:
# esta deve me dar 'Ivar ama python'
functions[0](' ama Python')


Out[72]:
'Mari ama Python'

In [73]:
# esta deve me dar 'Daniel ama C'
functions[1](' ama C')


Out[73]:
'Mari ama C'

In [74]:
# esta deve me dar 'Felipe ama LISP'
functions[2](' ama LISP')


Out[74]:
'Mari ama LISP'

Mas todas só se lembram do último i que foi criado no for, pois este fica no escopo externo e foi modificado lá.

Para reslver este problema, podemos usar um valor default, como vimos acima:


In [75]:
def make():
    items = []
    for i in ['Ivar','Daniel','Felipe','Mari']:
        # cada item é um FUNÇÃO criada com lambda
        #
        # só que agora i é passado para dentro do 
        # lambda como valor default, e existe lá dentro!!
        items.append(lambda x, i=i: i + x)
    return items

 # populei uma lista onde cada item é uma ***FUNÇÃO***
 functions = make()
# agora ao chamar as funções...
 functions[0](' ama Python'),functions[1](' ama C'), functions[2](' ama LISP')


Out[75]:
('Ivar ama Python', 'Daniel ama C', 'Felipe ama LISP')
FAQ: Mas se lambda só aceita uma expressão, qual a diferença entre 'expression' e 'statement'?

Volte e leia a parte sobre expressões e *statements*

Parâmetros Variáveis com \*args, \*\*kwargs

O Python nos permite a flexibilidade de montar funções que não precisam saber o números de argumentos que lhes serão passadas. Para isso usamos os operadores '\*' e '\*\*' ao declarar a função.

O operador '\*' faz com que a variável que o segue (quase sempre chamada 'args' pela comunidade Python) seja interpretada como uma tupla contendo todos os argumentos da função passados dali em diante. Veja:


In [76]:
def foo(primeiroarg, *args):
    print 'eis o primeiro:',primeiroarg,'\n'
    # todos os outros args são empacotados em uma tupla por cause do '*'!
    for i in args: 
        # EASTER-EGG: quem me explica a vírgula aqui? 
        # os primeiros 5 a mandar um email para daniel.chada<arroba>fgv<ponto>br 
        # explicando (corretamente) ganham chocolate!
        print i,   
 
# note que estou passando 6 argumentos, mas a função só recebe dois! 
# ...não dá erro??
foo(3, 'Daniel','é', 10, 'ou', 3.14)


eis o primeiro: 3 

Daniel é 10 ou 3.14

Já o operador '\*\*' acolhe todos os valores nomeados, isto é, que recebem nome na chamada (não na declaração) em um dicionário (sempre nomeado 'kwargs' pela comunidade Python), com as chaves sendo os nomes das variáveis, e os valores seus valores.

veja:


In [77]:
def bar(primeiroarg, **kwargs):
    print 'eis o primeiro:',primeiroarg,'\n'
    
    for key,val in kwargs.items():
        print 'a chave:',key,'...tem valor:',val
        
bar('1o', trabalho='diversão', python='legal', Daniel='melhor professor DO MUNDO!', Ivar='Ivan')


eis o primeiro: 1o 

a chave: python ...tem valor: legal
a chave: Ivar ...tem valor: Ivan
a chave: trabalho ...tem valor: diversão
a chave: Daniel ...tem valor: melhor professor DO MUNDO!

Módulo 7 - Strings Avançados

Neste módulo veremos um pouco mais sobre strings, e as diferentes formas de interpretá-los.

String é um subtipo de sequência, de fato, uma string é um sequência imutável, tal qual uma tupla.

String não são apenas para guardar e procesar informação em texto. No Python, string podem servir para guardar e processar informação em qualquer formato binário, até imagens e outras mídias. Isso os torna fortemente flexíveis, mas um pouco mais difíceis de lidar quando saímos das interpretações triviais (isto é, 8 bits = um caractére).

OBS: para referência, eis todos os tipos Python que são sequências: str, unicode, list, tuple, bytearray, buffer, xrange. Leia mais sobre eles em https://docs.python.org/2/library/stdtypes.html#sequence-types-str-unicode-list-tuple-bytearray-buffer-xrange

Unicode strings

Python também nos permite utilizar caracteres fora do 'básico' provido pelo código ASCII (veja a lista ASCII completa em http://www.asciitable.com), permitindo quaisquer caracteres do Unicode (cuja espcificação fica em http://www.unicode.org). O site da Unicode explica: "Unicode provides a unique number for every character, no matter what the platform, no matter what the program, no matter what the language".

O Livro Referência [1], descreve: "non-Unicode strings are sequences of 8-bit bytes that print with ASCII characters when possible, and Unicode strings are sequences of Unicode code points - identifying numbers for characters, which do not necessarily map to single bytes when encoded to files or stored in memory. In fact, the notion of bytes doesn't apply to Unicode".

Vejamos um exemplo um pouco mais concreto:


In [78]:
# o caractere em Unicode não é automaticamente lido no Python 2.x
print 'sp\xc4m' 
# se colocarmos o 'u' na frente da string, o Python sabe que deve ler como
# Unicode
print u'sp\xc4m'


sp�m
spÄm


As funções encode() e decode() funcionam para mapear strings para que sejam interpretados de certo jeito.

Vejamos alguns outros exemplos de mapeamento:


In [79]:
'\xc3\xa1' # um string de um bando de caracteres


Out[79]:
'\xc3\xa1'

In [80]:
print '\xc3\xa1' # a função print é esperta e interpreta o utf-8
>>> print 'á'


á
á

In [81]:
u'\xe1' # um unicode sem muito sentido


Out[81]:
u'\xe1'

In [82]:
print u'\xe1' # mas a função print já interpreta diferente


á

In [83]:
u'\xe1'.encode('utf-8') # mapeando


Out[83]:
'\xc3\xa1'

In [84]:
print u'\xe1'.encode('utf-8')


á

In [85]:
'\xc3\xa1'.decode('utf-8') # ...e de volta


Out[85]:
u'\xe1'

In [86]:
print '\xc3\xa1'.decode('utf-8')


á

Para quem quiser ler um pouco mais sobre a função print, busque aqui: https://docs.python.org/2/library/functions.html#print


In [87]:
s = 'ö'
>>> s.decode('utf-8') # estou usando o '>>>' para emular o terminal python que imprime as operações sem usar o print


Out[87]:
u'\xf6'

In [88]:
>>> 'spam'.encode('utf-16') # mapeie para utf-16 (16 bytes por caractere)


Out[88]:
'\xff\xfes\x00p\x00a\x00m\x00'

In [89]:
>>> print 'sp\xc4m' # uh oh, estou tentando considerar como string normal


sp�m

In [90]:
>>> print u'sp\xc4m' # considere como um unicode


spÄm

In [91]:
>>> u'sp\xc4\u00c4\U000000c4m' # unicode hexadecimal, unicode 'curto' (16) e unicode 'longo' (32)


Out[91]:
u'sp\xc4\xc4\xc4m'

In [92]:
>>> print u'sp\xc4\u00c4\U000000c4m'


spÄÄÄm

codecs (mapeamentos) que não tem nada à ver com texto, mas sim com como interpretar uma sequência de bytes (i.e. arquivos zip), veja https://docs.python.org/2/library/codecs.html#python-specific-encodings

Uma lista dos codecs padrão que o Python lida como built-in existe aqui: https://docs.python.org/2/library/codecs.html#standard-encodings


In [93]:
>>> s.encode('zip')


Out[93]:
'x\x9c;\xbc\r\x00\x02>\x01z'

Formatação

Python permite a formatação de strings via a função str.format(). O string sendo formatado (isto é, que chama a sua própria função format() poe conter texto literal (como sempre) ou campos de 'substituição', delimitados por chaves {}.

Cada campo de substituição contém ou o índice numérico de um argumento posicional ou o nome de um argumento (veja o exemplo).

A função retorna uma cópia da string onde cada campo de substituição é trocado pelo valor string (str()) do argumento correspondente.

Vejamos:


In [94]:
x = 'O melhor professor de Python é {} e o triste segundo é {}.'
print x

y = x.format('Daniel','Ivar')
print y


O melhor professor de Python é {} e o triste segundo é {}.
O melhor professor de Python é Daniel e o triste segundo é Ivar.


Vejamos colocando informação, junto ao delimitador:


In [95]:
print 'O melhor professor de Python é {1}\
 e o triste segundo é {0}.'.format('Daniel', 'Ivar')


O melhor professor de Python é Ivar e o triste segundo é Daniel.


Também aceita-se valores nomeados, vejamos:


In [96]:
at1 = 'fazer aluno sofrer'
at2 = 'fazer aluno chorar'

s = """olá, sou {nome} e tenho {idade} anos! \
Gosto muito de {ativ1} e {ativ2}!"""

print s.format(nome='Daniel'
            , idade='34'
            , ativ1=at1
            , ativ2=at2)


olá, sou Daniel e tenho 34 anos! Gosto muito de fazer aluno sofrer e fazer aluno chorar!


Também podemos usar híbridos de posicionamento e nomeação (lembrem da seção sobre \*args e \*\*kwargs)


In [97]:
s = "olá, sou {0} e tenho {1} ...gosto do {at1} e {at2}!"
at1 = "programar"
at2 = 'dar zero a alunos'

print s.format('Daniel','34', at1=at1, at2=at2)


olá, sou Daniel e tenho 34 ...gosto do programar e dar zero a alunos!

</br>A documentação do Python contém varios exemplos de formatação ...

aqui: (https://docs.python.org/2/library/string.html#format-examples)

e aqui: (https://docs.python.org/2/tutorial/inputoutput.html#fancier-output-formatting)

Multi-line strings

String multi-linha são bastante simples no Python: usamos três aspas para defini-los:


In [98]:
>>> x = """Ei! Eu

sou uma

string multi-linha!"""
>>> x


Out[98]:
'Ei! Eu\n\nsou uma\n\nstring multi-linha!'

In [99]:
print x


Ei! Eu

sou uma

string multi-linha!

Enfim ...O porque do "#coding: utf-8" no início dos arquivos!

O coding: utf-8 no início dos nossos arquivos .py indica que aquele código python deve ser interpretado como uma série de caracteres utf-8, não ASCII (que não permite acentos, e uma série de outros caracteres).

a definição formal pela comunidade Python descreve: "The encoding information is then used by the Python parser to interpret the file using the given encoding. Most notably this enhances the interpretation of Unicode literals in the source code and makes it possible to write Unicode literals using e.g. UTF-8 directly in an Unicode aware editor".

Assim, o Python, ao ler nosso arquivo .py, consegue entender caracteres fora do ASCII.

Módulo 8 - Números Binários, Tabelas Verdade, OR, AND, XOR, NAND

A computação como um todo se baseia em números binários. Mas o que queremos dizer quando dizemos 'binário', ou 'base dois'? Sabemos intuitivamente que, da mesma forma que a base decimal usa somente zero a nove {0,9}, a base binária se restringe a zeros e uns {0,1}.

Na base decimal, quando chegamos ao final, adicionamos um à próxima casa 'decimal' e começamos de novo. Esta próxima casa implica uma outra potência de dez. por exemplo, 243 significa

243
10^210^110^0

243 é igual a 2\*100 + 4\*10 + 3\*1

No binário, como só temos o 0 e o 1, fazemos a mesma coisa, mas muito mais vezes... ao chegar ao 'final' (o 1) adicionamos uma casa 'binária' e começamos de novo. Esta próxima casa implica outra potência de dois. Por exemplo 1011 significa:

1011
2^32^22^12^0

Assim, 1011 é igual a 1\*8 + 0\*4 + 1\*2 + 1\*1 = 8+2+1 = 11

Mais informação e exemplos podem ser encontrados em: https://docs.python.org/2/library/stdtypes.html#truth-value-testing

Operadores Bitwise: not, or, xor, and, nand

Com binários, podemos utilizar operadores (iguais aos operadores lógicos que já vimos, mas que funcionam bit a bit), chamados bitwise. Vamos encontrar os mesmos operadores, mas vamos ver alguns outros importantes na computação também.

O ou exclusivo (exclusive or) também chamado xor ...dadas duas proposições A e B, o xor funciona assim:

ABA xor B
000
011
101
110

O xor é uma operação composta, que pode ser expressa como (A or B) and not(A and B)

O and negado (negated and), também chamado nand ...dadas duas proposições A e B, o nand oferece os resultados opostos a um operador and assim:

ABA nand B
001
011
101
110

In [100]:
a = 0b1011
b = 0b1001

print bin(a | b) # operador 'or' bitwise

print bin(a & b) # operador 'and' bitwise


0b1011
0b1001

Módulo 9 - Usando APIs Externas

O Python package index é o principal repositório de módulos livremente disponíveis na internet.

no momento da edição desta apostila, o site https://pypi.python.org/pypi descreve: "The Python Package Index is a repository of software for the Python programming language. There are currently 49551 packages here."

Como baixar e instalar? No Linux e OSX a ferramenta pip do terminal torna trivial a instalação de novos pacotes Python. No Windows, ela também pode ser usada, como descrito aqui.

Muitos módulos disponibilizam instaladores específicos para o Windows, facilitando o processo. Podemos encontrar uma lista extra-oficial de módulos com instaladores para o Windows aqui.

Em certos módulos do Python é necessário construir o módulo a partir do código-fonte original (isso se chama build from source).

Anexo A - Recursos Online

Referências

[1] Lutz, M.; Learning Python 5th ed.


In [100]: