Byte of Python

Em: 05/01/2008 Tags: Referencie do seu blog (Trackback)

Eu acabei de ler o livro online Byte of Python, de Swaroop C H, um programador indiano que trabalha na Adobe. Comecei lendo o capítulo sobre orientação a objetos, pois queria ver qual era a abordagem do Python para essa questão fundamental. Como muitas coisas em Python, a abordagem OO é simples e direta, com uma sintaxe muito legal.

Como esse capítulo era muito bem escrito acabei me empolgando e li o restante do livro, dessa vez seguindo a ordem de capítulos proposta pelo autor. Posso lhe garantir que valhe a pena, se você está chegando ao mundo onde a indentação é obrigatória e as declarações “não terminam”.

Se você ainda não se convenceu a ler o livro talvez se interesse pela minha listagem dos “melhores momentos” de Byte of Python (ela acabou ficando um pouco extensa, pois foi escrita como uma forma de eu mesmo estudar aquilo que estava lendo no livro). Ela é feita do meu ponto de vista e do meu background principal (PHP e Ruby).

A indentação do código é obrigatória

Certas expressões Python impõe restrições sobre as expressões seguintes. Por exemplo, uma expressão que termina com : (dois pontos), impõe que a expressão seguinte seja um bloco de expressões (e blocos de expressões devem ser, obrigatoriamente, indentados 1 nível a mais que a expressão anterior/externa).

Exemplo:

if len('caio') == 4:
    print 'OK'

Saída:

OK

Agora se você não indentar a segunda linha teremos um erro de sintaxe:

  File "<stdin>", line 2
    print 'OK'
        ^
IndentationError: expected an indented block

Quando o bloco de expressões possuir apenas um comando, esse comando pode parecer na mesma linha que inicia o bloco, por exemplo:

if len('caio') == 4: print 'OK'

O autor do livro não recomenda esse tipo de uso (mas não justifica essa opinião).

A quebra de linha termina blocos de expressões

Você não verá isto em Python:

class Dog:
    def __init__(self, name):
        self.name = name
    end
    def bark(self, times=1):
        print 'au' * times
    end
end

snoopy = Dog('Snoopy')
snoopy.bark()

Mas isso (note a ausência de todos os end):

class Dog:
    def __init__(self, name):
        self.name = name
    def bark(self, times=1):
        print 'au' * times

snoopy = Dog('Snoopy')
snoopy.bark()

No início eu achava essa sintaxe muito alienígena. Mas quando se trata de escrever menos e obter o mesmo resultado, por que não?

Isso traz algumas conseqüências interessantes. Por exemplo, como declarar a mesma classe Dog, agora sem nenhum daqueles dois métodos? Tente desta forma:

class Dog:

O interpretador Python vai reclamar dessa declaração. Ele vai lhe dizer que esperava um “bloco indentado”, e você deu a ele apenas uma quebra de linha. Ou seja, uma declaração de classe (class Dog:) possui o caracter : (dois pontos), e isso indica ao interpretador que em seguida você vai declarar um conjunto de expressões (ou bloco indentado).

O que você pode fazer, portanto, para dizer ao interpretador que você não vai declarar um bloco indentado é utilizar a declaração pass no lugar do conjunto de blocos. Ou seja, a definição mínima para a classe Dog é a seguinte:

class Dog:
    pass

Não há blocos switch

Python adota a seguinte filosofia: “There should be one — and preferably only one — obvious way to do it” (”Deve haver uma - e de preferência apenas uma - maneira de se fazer alguma coisa”). Como a construção switch pode ser traduzida em um bloco if-else, a linguagem não o suporta.

Suporta herança múltipla

Python suporta a herança múltipla diretamente (ao contrário de Ruby, por exemplo, que a suporta por meio de mixins). A seguinte listagem de código Python declara Dog como herdeiro de duas classes, Animal e Mammal.

class Animal:
    pass

class Mammal:
    pass

class Dog(Animal, Mammal):
    pass

Variáveis globais

Variáveis globais podem ser acessadas dentro de uma função diretamente (apenas pelo nome da variável) ou utilizando a palavra-chave global. Há uma diferença entre utilizar uma variável no escopo da função após a declaração dela como global e sem sua declaração como global, como apontado pelo Luciano Ramalho nos comentários deste post:

a declaração global não é sempre opcional [...]. Ela é opcional apenas para se acessar a variável global. Porém, se a intenção do programador é atribuir um novo valor ao uma variável global x, ele é obrigado a declarar global x no escopo local, do contrário uma atribuição como x=1 vai apenas criar uma nova variável local, e não alterar o valor da variável global.

Exemplo:

my_global_var = 5

def some_function():
    global my_global_var
    my_global_var = 1

print my_global_var
some_function()
print my_global_var

A saída desse programa será:

5
1

Strings suportam o método *

Exemplo:

`'python' * 2

Saída:

'pythonpython'

Em Ruby isso também é possível, em PHP não (na verdade é, e o resultado é zero, hehe).

Argumentos nomeados são suportados nativamente

Python suporta o conceito de argumentos nomeados (keyword arguments). Isso é útil quando não queremos nos preocupar com a ordem na qual os parâmetros são passados para uma função.

Exemplo:

def my_func(a = 1, b = 5):
    print 'a =', a
    print 'b =', b 

my_func()
my_func(b = 30, a = 100)

Saída:

a = 1
b = 5
a = 100
b = 30

O “nada” é representado por None

Uma função que não retorna valores explicitamente (com o uso da palavra-chave return) retorna, implicitamente, None, que em Python corresponde a nada, ou vazio. Exemplo:

def my_func():
    pass

print my_func()

A saída do programa será:

None

Para retornar valores de uma função use return

Em Ruby, se uma função não retorna um valor explicitamente, então o último valor avaliado é retornado. Com Python, assim como em PHP, você deve retornar valores explicitamente com return.

Tipos boolean são True e False

Python conta com duas instâncias do tipo boolean: True e False. Expressões lógicas retornam um desses dois valores. Exemplos:

print type(True)
print type(False)
print 1 == 1
print 1 == 0

Saída:

<type 'bool'>
<type 'bool'>
True
False

Funções são objetos de primeira classe

Diferentemente de PHP, em Python este código é possível:

def my_func():
    print 'hey'

a = my_func
a()

a, neste caso, passou a ser um objeto do tipo function. Como qualquer outra variável, ela poderá ser passada como argumento de outras funções. Se você não percebe o benefício desse recurso pesquise o assunto first class functions no Google.

DocStrings são documentação e código ao mesmo tempo

O seguinte código:

def my_documented_function():
    '''This is a one-liner description of the function.

    This is a long description of the function.'''
    pass

print my_documented_function()
print my_documented_function.__doc__
help(my_documented_function)

Irá gerar a seguinte saída:

None
This function doesn't do much (this is the docstring header)

    But its documentation takes a few lines

O Python trata, como podemos ver, a documentação como uma propriedade/atributo da função my_documented_function (a linguagem associa a documentação automaticamente a __doc__). Note, ainda, que para acessar o atributo de uma função não a invocamos, pois não estamos utilizando os parênteses após seu nome.

O outro ponto interessante é que help é um atalho que podemos utilizar para acessar a propriedade __doc__ da função.

Ainda é possível associar docstrings também a classes e módulos.

Listas, Tuplas e Dicionários

Listas são coleções de objetos mutáveis.

Exemplos de manipulação de listas:

a = [1,2,3,4]
print type(a)
a.append(5)
print (a)
del a[0]
print a

Saída:

<type 'list'>
[1, 2, 3, 4, 5]
[2, 3, 4, 5]

Tuplas são coleções de objetos imutáveis. Tuplas têm a mesma sintaxe de criação das listas, mas troca-se o [] por (), como em:

a = ('caio', 'moritz')

Dicionários correspondem aos arrays associativos de PHP ou Hashes de Ruby. Sua sintaxe é bem simples:

lang_creators = {
    'Ruby':   'Yukihiro Matsumoto',
    'Python': 'Guido van Rossum',
    'PHP':    'Rasmus Lerdorf'
}

for lang, creator in lang_creators.items():
    print '%s was created by %s' % (lang, creator)

print 'Who created Java?'

if lang_creators.has_key('Java'):
    print lang_creators['Java']
else:
    print 'I have no idea.'

Saída:

Python was created by Guido van Rossum
PHP was created by Rasmus Lerdorf
Ruby was created by Yukihiro Matsumoto
Who created Java?
I have no idea.

Pontos importantes:

  • No loop for, utilize o método items() do dicionário para acessar seus itens
  • Dicionários não guardam a ordem em que seus elementos foram declarados

Interpolação de strings

Em PHP interpolamos strings assim:

echo "My favourite programming language is {$lang}";

Em Ruby assim:

puts "My favourite programming language is #{lang}";

Em Python não temos algo diferente, que atinge o mesmo objetivo:

print "My favourite programming language is %s" % lang

Referências

Uma variável referencia um objeto, e não um valor, conforme o código abaixo:

lang_creators = ['matz', 'guido', 'rasmus']
lc = lang_creators
lc == lang_creators
del(lang_creators[0])
print lc
print lang_creators

Saída:

['guido', 'rasmus']
['guido', 'rasmus']

A segunda linha da listagem faz com que lc e lang_creators passem a apontar para o mesmo objeto (no caso uma lista). Por conseqüência, quando a lista é alterada através da referência lc a mudança também é sentida pela referência lang_creators.

String.Join ou Array.Join?

Em Ruby utiliza-se Array#join para unir todos os elementos de um array, separando-os por um determinado caracter passado como parâmetro.

Exemplo:

arr = ['this', 'is', 'going', 'to', 'be', 'a', 'string']
arr.join(' ')

Saída:

"this is going to be a string"

Com Python o método join está em String, funcionando de forma “inversa” (é a String que une o Array, e não o Array que une seus elementos a partir de uma String):

lst = ['this', 'is', 'going', 'to', 'be', 'a', 'string']
' '.join(lst)

Saída:

'this is going to be a string'

Entrada e saída

Não vou me alongar muito neste tópico: entrada e saída em Python é muito simples, como em PHP e Ruby. Nada me supreendeu nem pro bem e nem pro mal.

Destaque para a iteração sobre todas as linhas de um arquivo, em 2 linhas de código:

for line in file('some_file.txt'):
    print line[:-1]

[:-1] evita que se imprima a quebra de linha original.

Namespaces

Python suporta namespaces de um forma muito legal. Um arquivo externo importado para dentro de um programa Python tem todo o seu código (variáveis, funções etc.) carregado dentro de um namespace próprio, igual ao nome do arquivo importado.

Orientação a Objetos

A orientação a objetos, ao menos até onde o livro explica, é bastante simples. Não compreendi bem como fica a questão do encapsulamento, por exemplo. Gostei da possibilidade da herança múltipla (isso sempre me fez falta no Java), só não gostei de todos os métodos de uma classe precisarem declarar self como o primeiro parâmetro. Sei o que self representa, mas porquê a linguagem não pode tomar conta de torná-lo presente no código interno da classe?

Miscelânea

  • Strings são imutáveis
  • Strings não são Unicode por padrão (confira os comentários do Luciano Ramalho neste post para entender como utilizar strings normais e strings Unicode)

Tópicos importantes não abordados no meu resumo

Tópicos importantes que não abordei no resumo mas que são tratados mais ou menos profundamente no livro:

  • Tratamento de exceções
  • Compreensão de listas (list comprehensions)
  • Métodos especiais (como __str__ e __init__)
  • Expressões Lambda

E agora?

Você continuar aprendendo Python lendo o livro do Swaroop ou seguindo sua recomendação de leituras.

Artigos relacionados:

7 respostas para “Byte of Python”

  1. EduardoWillians disse:

    Ótimo post. Você escreve muito bem, parabéns mesmo! Eu li muitos posts seus, mas esse é o primeiro que vou comentar. Sobre encapsulamento em Python, na verdade ele é desencorajado e não funciona como nas outras linguagens. Em Python só há duas formas de encapsular: “interno” e “privado”. Não existe “protegidos”, isso faz parte da idéia de Rossum de manter a linguagem como uma “Open Kimono Language” — seja lá o que isso queira dizer.

    Todos os nomes (qualquer nome, seja global, de classe, etc) começados por apenas um “_separador”, serão automaticamente tidos pelo interpretador Python como nomes “internos”, e se iniciados por dois “__separadores” serão automaticamente tidos por “privados”.

    “Interno”: Ele basicamente serve para que o leitor do código saiba que ele é usado apenas “internamente” e também para que o caso de importação de todo o conteúdo do objeto — from Isso import * –, o interno não será importado a menos que haja importação explícita — from Isso import _atributointerno. Eu costumo fazer uso do interno, seguindo a orientação do brazuka Gustavo Niemeyer (programador da Canonical).

    “Privado”: Este é largamente desencorajado em Python. Eu nunca vi ninguém usar e só vejo recomendações para não usar. Nomes __privados podem também ser acessados externamente, mas seus nomes são alterados pelo interpretador Python para “_ClassName__method”. Dessa maneira:

    class Wakadoo:
        def __private(self):
            return True
    
    wakadoo = Wakadoo()
    dir(wakadoo) # [’_Wakadoo__private’, ‘__doc__‘, ‘__module__’]
    wakadoo._Wakadoo__private() # True
    

    A declaração explícita de self como primeiro argumento obrigatório de um método é sempre motivo de muita discussão, e muitos pythonistas já questionaram isso (inclusive eu qdo tb aprendia Python). Guido já deixou claro que não irá mudar isso e que acha isso uma boa coisa pq diferencia métodos de funções. No fundo, no fundo acho que essa idéia (emprestada do ModulaB) parecia boa no princípio, mas agora não tem mais como mudar - hehehe. Menos mal que existem IDEs que podem digitar “self” pra gente.

    É isso.

    Té+

  2. Luciano Ramalho disse:

    Caio, bem vindo à comunidade Python! Achei bem legal o seu resumo, mas encontrei nele alguns errinhos. Primeiro, vale dizer que a declaração global não é sempre opcional como seu texto pode dar a entender. Ela é opcional apenas para se acessar a variável global. Porém, se a intenção do programador é atribuir um novo valor ao uma variável global x, ele é obrigado a declarar global x no escopo local, do contrário uma atribuição como x=1 vai apenas criar uma nova variável local, e não alterar o valor da variável global. Outro detalhe: as strings não são unicode por padrão. Existem duas classes: str, cujas constantes são ‘assim’ e unicode, cujas constantes são u’assado’ (note a letra u como prefixo do literal. A classe str na verdade são como sequencias de bytes, como as strings em Ruby. Se você armazenar uma string UTF-8 em um str chamada s, o Python não vai reclamar, mas o len(s) não vai te dar o número de caraceres correto, mas sim o número de bytes. Como em UTF-8 todos os caracteres não-ASCII têm mais de um byte, isso pode te causar problemas. Porém, numa string unicode o len sempre indica corretamente o número de caracteres.

  3. Caio Moritz disse:

    Olá, Eduardo

    Obrigado pelas explicações, são dicas que eu ainda não tinha encontrado por aí. Por exemplo, que os métodos privados são desencorajados. Vou procurar estudar mais a respeito disso para descobrir as melhores práticas da orientação a objetos no Python.

    Já no caso do self como parâmetro obrigatório de todos os métodos, justificar como “é bom para diferenciar funções e métodos” é um pouco estranho, na minha opinião. Parece que há algo além disso, pois, afinal, se estamos dentro da classe é um método, e não uma função. Enfim, vou procurar entender isso também.

    PS 1: Editei seu comentário para formatá-lo de acordo com o Markdown.

    PS 2: Estou aguardando a parte 3 do seu tutorial sobre programação web com Python e PSE ;)

    Abraço

  4. Caio Moritz disse:

    Olá, Luciano

    Obrigado pelas dicas (já atualizei o texto com essas novas informações). O texto do Swaroop realmente explica o funcionamento do global da mesma forma que você, fui eu quem acabou entendendo errado.

    Quanto ao Unicode, mais uma vez o livro do Swaroop estava certo, mas como li rapidamente essa parte li “standard” no texto e assumi que isso era padrão no Python (e não é, como você indica).

    Abraço

  5. Arthur Furlan disse:
    def my_func():
        print 'hey'
    
    a = my_func
    a()

    o mesmo código em PHP

    function my_func() {
        echo 'hey';
    }
    
    $a = 'my_func';
    $a();
    
  6. Caio Moritz Ronchi disse:

    Olá, Arthur. Como o seu código deixa explícito, as duas versões que você demonstra são similares, exceto pelo fato de que na versão Python sua variável a realmente é um objeto função, enquanto em PHP a mesma é apenas uma string, pois funções em PHP não podem ser atribuídas a variáveis, apenas seu nome.

    Uma continuação do seu código Python rodado dentro do shell interativo:

    >>> type(a)
    <type 'function'>
    
  7. Arthur Furlan disse:

    Com certeza Caio, eu só quis mostrar que é possível! Essa diferença que você citou diz respeito a arquitetura das duas linguagens, em Python tudo é apontador/referência enquanto que em PHP o suporte a apontadores é bastante fraco. O interessante aqui é que as duas linguagens consegue implementar a mesma funcionalidade, mesmo que de formas (bastante) distintas! :)

Escreva um comentário (utilize o formato Markdown)