Python

O que é Python?

  • Linguagem interpretada.
  • Dinamicamente tipada.
  • Principais usos em pesquisas e web.

O Zen de Python

  • Bonito é melhor que feio.
  • Explícito é melhor que implícito.
  • Simples é melhor que complexo.
  • Complexo é melhor que complicado.
  • Plano é melhor que aninhado.
  • Esparço é melhor que denso.
  • Legibilidade conta.
  • Casos especiais não são especiais o suficiente para quebrar as regras.
    • Embora praticabilidade ganhe de puridade.
  • Erros nunca devem passam silenciosamente.
    • A não ser que sejam explicitamente silenciados.
  • Em caso de ambiguidade, evite a tentação de adivinhar.
  • Deve haver um -- e preferencialmente apenas um -- modo óbvio de fazer algo.
    • Embora talvez não seja tão óbvio de primeira a não ser que você seja Holandês.
  • Agora é melhor do que nunca.
    • Embora nunca seja melhor que agora mesmo.
  • Se a implementação é difícil de explicar, é uma péssima idéia.
  • Se a implementação é fácil de explicar, pode ser uma boa idéia.
  • Namespaces são uma grande idéia - vamos fazer mais desses!

O interpretador Python


Python 2.7.5 (default, Jun 25 2014, 10:19:55) 
[GCC 4.8.2 20131212 (Red Hat 4.8.2-7)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>
                        

Executando scripts Python:


python meuscript.py
                        

Tipos de Variáveis

Tipos Mutáveis e Tipos Imutáveis

Em Python, o tipo da variável pode ser mutável ou imutável, mas a definição é data pelo tipo e não pelo usuário.

Uma variável do tipo "imutável" não pode ser alterada depois de criada. Tentar modificar o conteúdo da variável vai criar uma nova instância.

Uma variável do tipo "mutável" é o contrário: tentar alterar vai alterar o objeto, não criar um novo.

A importância disto será visto mais pra frente, mas tenha isso em mente.

... ainda...

Existem tipos que são, na verdade, objetos e tem toda uma gama de funções para alterar/manipular/editar o conteúdo de uma variável.

Outros são tipos simples que não são objetos.

bool: Tipo booleano. Tipo simples.


>>> a = True
>>> b = False
                        

int: Um inteiro. Simples.


>>> a = 1
                        


>>> 1 + 1
2
                        

float: Um número com ponto flutuante. Simples.


>>> a = 1.1
>>> b = 1.0
                        

str: Strings. Objeto imutável.


>>> a = 'Python'
>>> b = "Python"
>>> c = """Python
>>> Rocks!"""
                        

unicode: Strings em Unicode. Objeto imutável.


>>> a = u'Python'
                        

list: Listas. Objeto mutável.


>>> a = [1, 2, 'Python', ['Outra lista']]
                        

dict: Um dicionário/objeto/mapa. Objeto mutável.


>>> a = {'Python': 'Rocks',
>>> 1: 1.0}
                        

tuple: Um conjunto de elementos. Objeto imutável.


>>> a = ('Python', 1)
>>> b = (2,)
                        

E ainda (mas menos importantes):

  • None
  • Long (a = 1L)
  • Lambdas ( a = lambda a: a + 2)

Estruturas de Controle

(... que é o nome bonito para coisas tipo if, for...)

Antes de mais nada...

Blocos

Em Python, uma identação define um bloco.

Não tem { / }, não tem end, nada. Só blocos.

if [condição]


>>> if a == 1:
>>>     b = 2
>>> c = 3
                        

while [condição]


>>> a = 1
>>> while True:
>>>     a += 1
>>>     if a > 10:
>>>         break
                        

for [iterável]


>>> soma = 0
>>> for valor em [345, 123, 123, 34]:
>>>     soma += valor
                        

The fuck "ITERÁVEL"?

Um objeto "iterável" é aquele que pode ter elementos acessados usando [ e ].

(Na verdade, o objeto tem que ter um generator; para acesar elementos diretamente, o objeto tem que implementar a função __getitem__.)

Tipos iteráveis:

  • Listas (a[2])
  • Tuplas (a[2])
  • Dicionários (a['Python'])
  • Strings/Unicodes (a[2])

Strings como iteráveis:


>>> for l in 'Python':
>>>     print l
                        

Dicionários como iteráveis:


>>> d =  {'Python': 'Rocks', 'Parrot': 'Dead', 'Favorite Color': 'Blue'}
>>> for key in d:
>>> 	print key, d[key]
						

Ou ainda:


>>> d =  {'Python': 'Rocks', 'Parrot': 'Dead', 'Favorite Color': 'Blue'}
>>> for (key, value) in d.iteritems():
>>>     print key, value
					    

Slices

Slice é uma "extensão" de indíces de acesso.

Com slices, é possível "cortar" iteráveis, retornando um novo iterável.


iterável[start:end:step]
                        

(end é exclusívo.)


>>> a = [1, 2, 3, 4]
>>> print a[1:2]
[2]
                        

Deixar um índice em branco indica que:

  • start = 0
  • end = len(iterável)
  • step = 1


>>> a = [1, 2, 3, 4]
>>> print a[:2]
[1, 2]
                        

Índices negativos começam do final do iterável.


>>> a = [1, 2, 3, 4]
>>> print a[1:-1]
[2, 3]
                        

Lembre-se que strings também são iteráveis.


>>> a = 'Python Rocks'
>>> print a[7:-1]
'Rock'
                        

Deixar os dois índices em branco cria uma cópia "flat".


>>> a = [1, 2, 3, 4]
>>> print a[:]
[1, 2, 3, 4]
                        

Para fazer uma cópia de uma lista com outros iteráveis internos, existe o módulo deepcopy.

Funções

def [nome_da_função]([parâmetro], [parâmetro], ...):


>>> def funcao(a, b, c):
>>>     return (a + b) / c
						

Parâmetros podem ser nomeados.


>>> def funcao(a, b, c):
>>>     return (a + b) / c
>>>
>>> funcao(b=2, c=3, a=10)
4
						

Classes

Ok, algumas coisas a serem vistas antes de entrar em classes:

Existem dois tipos de classes: old-style e new-style.

A diferença é que classes "new-style" sempre extendem da classe object, enquanto que "old-style" não extendem ninguém.

Por baixo dos panos, "new-style" e "old-style" funcionam de forma diferente, mas isso não é visível para o programador.

Para todos os casos e efeitos, "old-style" não deve mais ser usado.

No Python 3, não existem mais classes "old-style", mas a sintaxe removeu a necessidade de extender object.

(Ou seja, no Python 3 uma classe se parece com o "old-style" do Python 2.)

this/self não é uma variável implícita da classe: Ela tem que constar sempre na definiçào do método.

O construtor da classe é chamado __init__.

Não existe função para o destrutor.

Existem ainda outras funções (como o __getitem__ comentado anteriormente), mas não vamos falar sobre elas nesse momento.


>>> class MyClasse(object):
>>>     def __init__(self):
>>>         self.valor = 0
>>>     def show(self):
>>>         print self.valor
    				    

Para instanciar uma classe, basta chamar a classe como se fosse uma função.


>>> my = MyClasse()
>>> my.show()
0
				        

Se o construtor tiver parâmetros, estes devem ser passados durante a instanciação, como se a "função" classe tivesse parâmetros.


>>> class MyClasse(object):
>>>     def __init__(self, name):
>>>         self.name = name        
>>>     def show(self):
>>>         print self.name
        
>>> my = MyClasse('Julio')
>>> my.show()
Julio
				        

Herança


>>> class A(object):
>>>     def __init__(self):
>>>         self.value = 10
>>> class B(A):
>>>     def __init__(self):
>>>         super(B, self).__init__()
>>>         self.name = 'AAAA'
				        

Herança Múltipla


>>> class A(object):
>>>     def __init__(self):
>>>         self.value = 10
>>> class B(object):
>>>     def __init__(self):
>>>         self.name = 'AAAA'
>>> class C(A, B):
>>>     def __init__(self):
>>>         super(C, self).__init__()
				        

No Python 3, basta usar super().__init__().

As Esquisitices de Python

Strings São Imutáveis


>>> a = 'string 1'
>>> b = 'string 2'
>>> c = a + ' ' + b
				        

  • Cria um objeto que é o conteúdo de "a" com um espaço.
  • Cria um novo objeto que é o novo objeto mais o conteúdo de "b".
  • Atribui o novo objeto à "c".

Forma correta de concatenar strings:


>>> a = 'string 1'
>>> b = 'string 2'
>>> c = ' '.join([a, b])
				        

Listas São Mutáveis


>>> def a(l=[]):
>>>     l.append(1)
>>>     print l
>>>
>>> a()
[1]
>>> a()
[1, 1]
				        

Forma correta de lidar com parâmetros mutáveis:


>>> def a(l=None):
>>>     if not l:
>>>         l = []
>>>     l.append(1)
>>>
>>> a()
[1]
>>> a()
[1]
				        

Stars

"Stars" servem para empacotar e desempacotar parâmetros indefinidos.


>>> def a(*args):
>>>     print args
>>>
>>> a(1)
[1]
>>> a(1, 2, 3, 4, 5)
[1, 2, 3, 4, 5]
				        

* pega somente os parâmetros que não tem nome.


>>> def a(**kwargs):
>>>     print kwargs
>>>
>>> a(a=1)
{a: 1}
>>> a(value1=10, a=2)
{value1: 10, a: 2}
				        

** pega somente os parâmetros que tem nome.


>>> def a(*args, **kwargs):
>>>     print args
>>>     print kwargs
>>>
>>> a(a=1)
[]
{a: 1}
>>> a(1, 2, 3, a=5)
[1, 2, 3]
{a: 5}
				        


>>> def a(a, b, *args, name=None, **kwargs):
>>>     print 'a =', a
>>>     print 'b =', b
>>>     print 'args =', args
>>>     print 'name = ', name
>>>     print 'kwargs =', kwargs
				        

BORING!

A parte legal dos stars não é usar para criar funções que aceitam qualquer parâmetro (embora isso seja legal em alguns casos).

A parte legal é fazer chamadas de funções com dicionários.


>>> def funcao(a, b, c):
>>>     return (a + b) / c
>>>
>>> params = {b: 2, c: 3, a:10}
>>> funcao(**params)
				        

"Functions are First Class Citizens"


>>> def funcao(a, b, c):
>>>     return (a + b) / c
>>>
>>> def check(a, b, c, condition, function):
>>>     if condition:
>>>         print function(a, b, c)
>>>
>>> check(1, 2, 3, True, funcao)
1
>>> check(1, 2, 3, False, funcao)
>>>
				        

Decorators

A idéia dos decorators é cria uma função que altera a funcionalidade de uma função.

A forma mais simples de entender decorators é pensar neles como funções que encapsulam callbacks.


>>> def retrieve(connection):
>>>     # faz algo com a conexão para recuperar dados.
				        

Problema: antes de sair executando algo na conexão, tem que ser verificado se a conexão está ativa.

Solução menos óbvia: Criar uma função que verifica a conexão e, se ela estiver ok, chama a função.


>>> def retrieve(connection):
>>>     # faz algo com a conexão para recuperar dados.
>>>
>>> def update(connection):
>>>     # atualiza algo usando a função
>>>
>>> def check(connection, call):
>>>     if not connection.is_connected:
>>>         connection.retry()
>>>     call(connection)
				        

Novo problema: Todo lugar onde antes era chamado retrieve agora precisa ser alterado para check(connection, retrieve) e todo lungar onde era chamado update precisa ser alterado para check(connection, update).

Solução mais simples: decorators.


>>> from functools import wrap
>>>
>>> def check(func):
>>>     def check_conn(*args, **kwargs):
>>>         # acha a conexão em args ou kwargs
>>>         if not connection.is_connected:
>>>             connection.retry()
>>>         return func(*args, **kwargs)
>>>     return check_conn
>>>
>>> @check
>>> def retrieve(connection):
>>>     # faz algo com a conexão para recuperar dados
						

Não precisa alterar nenhuma chamada de retrieve.