Python

O que é Python?

  • Linguagem interpretada.
  • Dinamicamente tipada.
  • Principais usos em pesquisas e web.
    • Assim como o Linux "vazou" dos servidores para os Desktops, Python parece estar "vazando" do meio acadêmico/pesquisa para os servidores.

O Zen de Python

Começou em uma brincadeira do Tim Peters (um dos desenvolvedores do Python), mas acabou virando um PEP (PEP 20) e hoje faz parte da filosofia de desenvolvimento do 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!

As Esquisitices de Python

PEP8

  1. PEPs (Python Enhancement Proposal) são as RFCs que os desenvolvedores Python utilizam para comunicar novas funcionalidades.
  2. Uma funcionalidade não é simplesmente "aceita": É preciso escrever uma proposta de melhoria explicando o problema encontra e como corrigí-lo.
  3. PEP8 define a melhor forma de se programar em Python.
  4. PEP8 não é forçado pelo compilador.
  5. PEP8 garante que qualquer programador Python consiga facilmente encontrar coisas em códigos criados por terceiros.

Um pouco do PEP8:

Variáveis e funções utilizam a notação separado_por_underscores; classes usam CamelCase.

Correto:


MinhaClasse

minha_funcao()
                        

Errado:


minhaClasse

minhaFuncao()
                        

CONSTANTES
                        
Blocos são marcados com 4 espaços e não tabulações.

A maior parte dos editores de código hoje permitem configurar isso facilmente e fazem com que os 4 espaços no começo sejam tratados como tabulação.

Isso garante que o código irá sempre aparecer da mesma forma em qualquer editor, não importando a configuração do autor.

Coluna máxima é 80.

Nenhum código deve passar da coluna 80, não importa o tamanho do seu monitor.

Facilita a leitura e a movimentação dentro do código.

Duas linhas em branco antes de classes, 1 linha em branco antes de funções.
Espaços entre operadores (+, -, /, *), nada de espaços antes de parentêses, colchetes ou chaves.

Correto:


(1 + 2)
array[1]
                        

Errado:


(1+2)
( 1 + 2 )
array [ 1 ]
                        
Nada de espaços entre parâmetros nomeados.

Correto:


funcao(param=1)
                        

Errado:


funcao(param = 1)
                        

Blocos

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

Não existe { / }, não existe end, nada. Só identação.

Um bloco sem nada dentro (por algum motivo), deve ser preenchido com, pelo menos, pass.

O interpretador Python


$ 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.
>>>
                        

Python 2 (todas as versões), deixarão de ser suportadas em 2020!

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.

None: O nada. Tipo simples.


>>> a = None
                        

bool: Tipo booleano. Tipo simples.


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

int: Um inteiro. Simples.


>>> a = 1
                        

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/documento. Objeto mutável.


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

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


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

set: Conjunto de elementos não repetíveis. Objeto mutável.


>>> a = set()
>>> a.add('a')
>>> a
set(['a'])
>>> a.add('a')
>>> a
set(['a'])

                        

E ainda (mas menos importantes):

  • Long (a = 1L)
  • Lambdas (a = lambda a: a + 2)
  • Raw strings (r'string\s', usadas para regex)

Estruturas de Controle

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

if [condição]


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

while [condição]


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

[condição]

Comparação pode ter mais de um elemento:


>>> a = 1; b = 5; c = 3; d = 1
>>> a == b == c == d
False
>>> a < b > c
True
                        

[condição] (cont.)

Valores "em branco" são considerados falsos:

  • None é falso.
  • "", string vazia, é falso.
  • 0 é falso em qualquer precisão.
  • [], lista vazia, é falso.
  • {}, dicionário vazio, é falso.
  • set(), set vazio, é falso.

[condição] (cont.)

Como valores em branco são falsos, para garantir None usa-se is.


>>> a = 0
>>> not a
True
>>> a is None
False
>>> a = None
>>> not a
True
>>> a is None
True
                        

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 ].

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
                        

Forma considerada "correta" é a anterior.

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.

Todos os iteráveis podem ser "medidos" com len():

  • len(list) ⇛ Número de elementos na lista.
  • len(tuple) ⇛ Número de elementos na tupla.
  • len(str) ⇛ Número de caracteres na string.
  • len(dict) ⇛ Número de chaves no dicionário.
  • len(set) ⇛ Número de elementos no set.

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
                        

Funções podem conter funções


>>> def funcao(a, b):
...    def inner(b):
...        return b * 2 if b > 0 else b * -2
...    return inner(a) + b
                        

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.

(Existe, mas ninguém usa.)

Existem ainda outras funções mágicas, 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
                        

Propriedades podem ser injetadas a qualquer momento.


>>> class A(object):
...     def __init__(self):
...         self.value = 10
>>>
>>> a = A()
>>> a.name = 'Julio'
                        

Inserção de propriedades pode ser barrada com o uso da variável mágica __slots__, mas raramente é usado.

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__().

super() é mais usado para resolução hierárquia com herança múltipla.


>>> class Adao(object): pass
>>> class Eva(object): pass
>>> class AvoPaterno(Adao, Eva): pass
>>> class AvohPaterna(Adao, Eva): pass
>>> class AvoMaterno(Adao, Eva): pass
>>> class AvohMaterna(Adao, Eva): pass
>>> class Pai(AvoPaterno, AvohPaterna): pass
>>> class Mae(AvoMaterno, AvohMaterna): pass
>>> class Filho(Pai, Mae): pass
                        

>>> help(Filho)
                        

Help on class Filho in module __main__:

class Filho(Pai, Mae)
 |  Method resolution order:
 |      Filho
 |      Pai
 |      AvoPaterno
 |      AvohPaterna
 |      Mae
 |      AvoMaterno
 |      AvohMaterna
 |      Adao
 |      Eva
 |      __builtin__.object
                        

A ordem de resolução pode ser usada para inserir mocks sem o uso de mocks.

... embora só sirva para mockar objetos "é um" e não "contém um".


>>> class Robo(object):
...    def pegar(self, ferramenta):
...        print 'Pegando', ferramenta
...    def pra_frente(self):
...        print 'Movendo pra frente'
...    def pra_tras(self):
...        print 'Voltando'
...    def largar(self):
...        print 'Largando ferramenta'
                        

>>> class RoboDeLimpeza(Robo):
...    def clean(self, repeticoes=10):
...        super(Robo, self).pegar('vassoura')
...        for _ in xrange(repeticoes):
...            super(Robo, self).pra_frente()
...            super(Robo, self).pra_tras()
...        super(Robo, self).largar()
                        

>>> class MockRobo(Robo):
...     def __init__(self):
...         self.acoes = []
...     def pegar(self, ferramenta):
...         self.acoes.append('Pegar {}'.format(ferramenta))
...     def pra_frente(self):
...         self.acoes.append('frente')
...     def pra_tras(self):
...         self.acoes.append('tras')
...     def largar(self):
...         self.acoes.append('largar')
                        

>>> class MockRoboDeLimpeza(RoboDeLimpeza, MockRobo):
...     pass
                        

Help on class MockRoboDeLimpeza in module __main__:

class MockRoboDeLimpeza(RoboDeLimpeza, MockRobo)
 |  Method resolution order:
 |      MockRoboDeLimpeza
 |      RoboDeLimpeza
 |      MockRobo
 |      Robo
 |      __builtin__.object
                        

Mais informações no vídeo de Raymond Hettinger: Super Considered super!

Assim como funções, classes podem conter classes:


>>> class Outer(object):
...    class Inner(object):
...        def __init__(self):
...            self.value = 1
...    def __init__(self):
...        self.value = Inner()
                        

Módulos, imports e Namespaces

Existem dois tipos de módulos:

  • Arquivos com funções;
  • Diretórios com arquivos Python.

Arquivos com funções

modulo.py


def funcao(a, b):
    return a * b
                        

Arquivos com funções


>>> import modulo
>>> modulo.funcao(1, 2)
                        

No caso, o "namespace" do módulo (modulo) foi importando para o namespace atual; as funções permanecem dentro do namespace original.

Arquivos com funções


>>> from modulo import funcao
>>> funcao(1, 2)
                        

Importado apenas uma função do módulo modulo; a função é adicionada no namespace atual.

Diretórios com arquivos

Para um diretório ser reconhecido como módulo, é preciso que exista um arquivo __init__.py dentro dele.

... apenas para Python 2; Python 3 removeu esse requisito.

Diretórios com arquivos

utils/modulo.py


def funcao(a, b):
    return a * b
                        

Diretórios com arquivos

Sem o arquivo __init__.py, o import falha (no Python 2)


>>> import utils.module
Traceback (most recent call last):
  File "", line 1, in 
ImportError: No module named utils.module
                        

Diretórios com arquivos

Com o arquivo __init__.py, o import funciona.


>>> import utils.module
>>> utils.module.funcao(1, 2)
                        

Diretórios com arquivos

Importando o módulo sem o nome do arquivo, é importado o conteúdo do próprio __init__.py:

__init__.py


def mult(a, b):
    return a * b
                        

>>> import utils
>>> utils.mult(1, 2)
                        

Mais 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
                        

Saída de uma chamada desta função fica a cargo do leitor.

for tem else


>>> for i in xrange(10):
...     if i == 1:
...         break
... else:
...     print 'Oh no'
                        

WAT

for tem else

else é chamado se a execução do loop chegar ao final.


>>> for record in all_content:
...     if record.name = search:
...         found = record
...         break
... else:
...     found = None
                        

try tem else


>>> try:
...    func_that_raises_exception()
... else:
...     print 'Yay!'
                        

else é chamado quando exceções não ocorrerem.

finally SEMPRE é chamado.

"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)
>>>
                        

Como funções são cidadãos de primeira classe e classes podem ter funções injetadas, pode-se extender uma classe em tempo de execução.


>>> class A(object):
...     def __init__(self):
...         self.value = 10
... def show_name(self):
...     print 'Julio'
>>> a = A()
>>> a.show = show_name
>>> a.show()
                        

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.

Lembre-se: O resultado de uma função decorator é uma função.

Decorators com classes

>>> class CheckConn(object):
...    def __init__(self, func):
...        self.func = func
...
...     def __call__(self, *args, **kwargs):
...         if 'connection' in kwargs:
...             connection = kwargs['connection']
...         else:
...             connection = args[0]
...
...         if not connection.is_connected:
...             connection.retry()
...         self.func(*args, **kwargs)

>>> @CheckCon
>>> def retrieve(connection):
...     # retrieve
                        
Decorators mais comuns:
  • @property
  • @staticmethod
@property

>>> class CheckConn(object):
...    def __init__(self, func):
...        self._func = func
...
...    @property
...    def func(self):
...        return self._func
...
...    @func.setter
...    def func(self, value):
...        self._func = func
                        
@staticmethod

>>> class CheckConn(object):
...    def __init__(self, func):
...        self._func = func
...
...    @staticmethod
...    def from_text(self, text):
...        return CheckConn(getattr(self, text))
                        

Comprehensions e Generators

Python permite criar listas processando listas sem for com list comprehensions.


>>> a = [1, 2, 3]
>>> [item * 2 for item in a]
>>> [2, 4, 6]
                        

Pra quem gosta de coisas "funcionais", é o mesmo que map.


>>> a = [1, 2, 3]
>>> map(lamba f: f * 2, a)
>>> [2, 4, 6]
                        

Comprehensions (contd.)

É possível filtrar elementos com list comprehensions.


>>> a = [1, 2, 3]
>>> [item for item in a if item > 2]
>>> [3]
                        

Funcionalmente, é o mesmo que filter.


>>> a = [1, 2, 3]
>>> filter(lambda f: f > 2, a)
>>> [3]
                        

Generators

Enquanto que comprehensions criam novas listas, generators geram elementos sob demanda.


>>> a = [1, 2, 3]
>>> (item * 2 for item in a)
<generator object <genexpr> at 0x7f8673dfc050>
                        
Generators (contd.)

>>> [item for item in range(5000000)]
                        

vs


>>> (item for item in xrange(5000000))
                        
Generators (contd.)

>>> [item for item in range(5000000)][:5]
                        

vs


>>> (item for item in xrange(5000000))[:5]
                        
Generators (contd.)

>>> def gen(max_value):
...    for value in xrange(max_value):
...        yield value * 2
                        

Generator functions não podem ter return!

Context Managers

Context Managers são usados como "marcadores" para entrada e saída de pontos específicos.

Context Managers


>>> class Connection(object):
...    def __init__(self):
...        self._conn = None
...
...    def __enter__(self):
...        self._conn = self._make_connection()
...        return self._conn
...
...    def __exit__(self, exc_type, exc_value, traceback):
...        self._conn.close()
...        if exc_type:    # then exc_value and traceback
...            print "Exception!", exc_type, exc_value
...            print traceback
                        

>>> with Connection() as connection:
...     connection.request('Value')
                        

Context Managers vs Exceptions


>>> try:
...     conn = self._make_connection()
...     conn.request('value')
... finally:
...     conn.close()
... except Exception as exc:  # Bare exceptions are BAAAADDD!
...     print 'Exception!', exc
                        

Docstrings

Documentação de funções/classes é feita com uma string logo abaixo da declaração da mesma.


>>> def func(a):
...     """Função mágica"""
...     return a

>>> print func.__doc__
Função mágica

>>> help(func)
                        

Sphinx

Sphinx é o sistema de geração de documentação do Python.

PEP 257

PEP 257 fala sobre formato de documentação de funções.

PEP 257
  • Docstrings devem ter três aspas duplas.
  • Para classes, uma linha em branco antes e uma depois.
  • Para funções, sempre logo depois.
  • Se a documentação passar da coluna 80, as três aspas que fecham a docstring devem ficar em uma linha em separado.

class MyClass(object):

    """This is my class. It is very long and I should break it
    into several lines.
    """

    def __init__(self):
        """Initialization."""
        self.value = None
                        

Sphinx Format

O Sphinx tem um formato específico para documentação.

  • Parametros são documentados com :param [nome]:
  • Tipo do parametro é documetnado com :type [tipo]:
  • Retorno é marcado como :return:
  • Tipo de retorno é definido em :rtype:
  • Exceções podem ser descritas em :raises [exceção]:

Sphinx Format (contd.)


def send(sender, recipient, message_body, priority):
   """Send a message to a recipient

   :param str sender: The person sending the message
   :param str recipient: The recipient of the message
   :param str message_body: The body of the message
   :param priority: The priority of the message, can be a number 1-5
   :type priority: integer or None
   :return: the message id
   :rtype: int
   :raises ValueError: if the message_body exceeds 160 characters
   :raises TypeError: if the message_body is not a basestring
   """
   pass
                        

(como gerar documentação Sphinx não será apresentado por questões de espaço.)

(mas é simples: um arquivo conf com os diretórios a serem pesquisados e um makefile com a opção html para geração de arquivos HTML.)

VirtualEnv