Semana passada, escrevi um artigo sobre como criar um Daemon HTTP via Python. Hoje, vamos ver como criar um servidor de FTP.
Onde podemos usar e por que fazer um servidor de FTP?
Nos dias de hoje, no mundo Linux, temos alguns servidores de FTP
muito bons (vsftpd, proftpd e outros), mas quando vamos para o ambiente
Microsoft Windows não conheço muitos servidores leves. Por esse motivo,
acho muito interessante termos alguma solução para esse ambiente usando
Python.
Vou explicar como fazer o daemon, depois vai da criatividade de cada um de como aplicar no dia a dia.
Tenho um cluster de servidores onde rodo sistemas de cliente - a parte de FTP rodava com vsFTPd, hoje está rodando com um daemon em Python, o mesmo que vamos fazer aqui no artigo, só que trabalho com uma base de usuário no MongoDB (usuário, senha e pasta).
Por que eu migrei de vsFTPd para um daemon em Python?
- A base de dados onde estão as informações dos meus clientes é MongoDB, onde estou centralizando todas as informações.
- Consigo fazer meu daemon interagir com outros aplicativos Python que rodam no meu servidor
Para escrever este artigo, vamos usar a biblioteca pyftpdlib, que é mantida pelo Giampaolo Rodola. É uma biblioteca muito robusta e fácil de utilizar, onde podemos escrever poderosos servidores FTP.
Instalando o pyftpflibcd /usr/src/
svn checkout http://pyftpdlib.googlecode.com/svn/trunk/ pyftpdlib-read-only
cd pyftpdlib-read-only
python setup.py build python setup.py install
Agora que já está tudo instalado, vamos começar a escrever o código de nosso servidor:
from pyftpdlib import ftpserver
authorizer = ftpserver.DummyAuthorizer() authorizer.add_user("avelino.us", "mudar123", "/home/avelino.us", perm="elradfmw") authorizer.add_anonymous("/home/nobody") handler = ftpserver.FTPHandler handler.authorizer = authorizer address = ("127.0.0.1", 21) ftpd = ftpserver.FTPServer(address, handler) ftpd.serve_forever()
O pyftpdlib tem uma biblioteca chamada ftpserver que usamos para
declarar o servidor. Vamos passar dois parâmetros: o primeiro é o
endereço de onde ele vai abrir a conexão/porta; o segundo é o servidor
Handler.
A última linha é "ftpd.serve_forever()", que mantém o servidor rodando.
Explicando como funciona a parte de user
Temos como adicionar usuários normais ou anônimos.
authorizer.add_user("avelino.us", "mudar123", "/home/avelino.us", perm="elradfmw")
A linha na parte superior adiciona um usuário normal, com nome "avelino.us", senha "mudar123", com acesso à pasta "/home/avelino.us".
authorizer.add_anonymous("/home/nobody")
No usuário anônimo, basta colocar na pasta a qual ele vai ter acesso "/home/nobody".
A parte de log funciona exatamente igual ao de qualquer FTP, vou colocar abaixo um log dessa aplicação rodando local:
Serving FTP on 127.0.0.1:21 []127.0.0.1:56026 Connected. 127.0.0.1:56026 ==> 220 pyftpdlib 0.6.0 ready. 127.0.0.1:56026 <== USER avelino.us 127.0.0.1:56026 ==> 331 Username ok, send password. 127.0.0.1:56026 <== PASS ****** 127.0.0.1:56026 ==> 530 Authentication failed. []@127.0.0.1:56026 Authentication failed. []@127.0.0.1:56026 Disconnected. []127.0.0.1:56562 Connected. 127.0.0.1:56562 ==> 220 pyftpdlib 0.6.0 ready. 127.0.0.1:56562 <== USER avelino.us 127.0.0.1:56562 ==> 331 Username ok, send password. 127.0.0.1:56562 <== PASS ****** 127.0.0.1:56562 ==> 530 Authentication failed. []@127.0.0.1:56562 Authentication failed. []@127.0.0.1:56562 Disconnected. []127.0.0.1:56567 Connected. 127.0.0.1:56567 ==> 220 pyftpdlib 0.6.0 ready. 127.0.0.1:56567 <== USER avelino.us 127.0.0.1:56567 ==> 331 Username ok, send password. 127.0.0.1:56567 <== PASS ****** 127.0.0.1:56567 ==> 230 Login successful. [avelino.us]@127.0.0.1:56567 User avelino.us logged in. 127.0.0.1:56567 <== SYST 127.0.0.1:56567 ==> 215 UNIX Type: L8 127.0.0.1:56567 <== FEAT 127.0.0.1:56567 ==> 211 End FEAT. 127.0.0.1:56567 <== OPTS MLST type;perm;size;modify;unix.mode;unix.uid;unix.gid; 127.0.0.1:56567 ==> 200 MLST OPTS type;perm;size;modify;unix.mode;unix.uid;unix.gid; 127.0.0.1:56567 <== PWD 127.0.0.1:56567 ==> 257 "/" is the current directory. 127.0.0.1:56567 <== TYPE I 127.0.0.1:56567 ==> 200 Type set to: Binary. 127.0.0.1:56567 <== PASV 127.0.0.1:56567 ==> 227 Entering passive mode (127,0,0,1,220,248). 127.0.0.1:56567 <== MLSD [avelino.us]@127.0.0.1:56567 OK MLSD "/". Transfer starting. 127.0.0.1:56567 ==> 150 File status okay. About to open data connection. 127.0.0.1:56567 ==> 226 Transfer complete. 127.0.0.1:56567 <== MKD test [avelino.us]@127.0.0.1:56567 OK MKD "/test". 127.0.0.1:56567 ==> 257 "/test" directory created. 127.0.0.1:56567 <== CWD test [avelino.us]@127.0.0.1:56567 OK CWD "/test". 127.0.0.1:56567 ==> 250 "/test" is the current directory. 127.0.0.1:56567 <== PWD 127.0.0.1:56567 ==> 257 "/test" is the current directory. 127.0.0.1:56567 <== PASV 127.0.0.1:56567 ==> 227 Entering passive mode (127,0,0,1,220,253). 127.0.0.1:56567 <== MLSD [avelino.us]@127.0.0.1:56567 OK MLSD "/test". Transfer starting. 127.0.0.1:56567 ==> 150 File status okay. About to open data connection. 127.0.0.1:56567 ==> 226 Transfer complete. []127.0.0.1:56575 Connected. 127.0.0.1:56575 ==> 220 pyftpdlib 0.6.0 ready. 127.0.0.1:56575 <== USER avelino.us 127.0.0.1:56575 ==> 331 Username ok, send password. 127.0.0.1:56575 <== PASS ****** 127.0.0.1:56575 ==> 230 Login successful. [avelino.us]@127.0.0.1:56575 User avelino.us logged in. 127.0.0.1:56575 <== OPTS MLST type;perm;size;modify;unix.mode;unix.uid;unix.gid; 127.0.0.1:56575 ==> 200 MLST OPTS type;perm;size;modify;unix.mode;unix.uid;unix.gid; 127.0.0.1:56575 <== CWD /test [avelino.us]@127.0.0.1:56575 OK CWD "/test". 127.0.0.1:56575 ==> 250 "/test" is the current directory. 127.0.0.1:56575 <== PWD 127.0.0.1:56575 ==> 257 "/test" is the current directory. 127.0.0.1:56575 <== TYPE A 127.0.0.1:56575 ==> 200 Type set to: ASCII. 127.0.0.1:56575 <== PASV 127.0.0.1:56575 ==> 227 Entering passive mode (127,0,0,1,221,0). 127.0.0.1:56575 <== STOR test.py [avelino.us]@127.0.0.1:56575 OK STOR "/test/test.py". Upload starting. 127.0.0.1:56575 ==> 150 File status okay. About to open data connection. 127.0.0.1:56575 ==> 226 Transfer complete. [avelino.us]@127.0.0.1:56575 /home/avelino.us/test/test.py received in 0.001 seconds. 127.0.0.1:56575 <== TYPE I 127.0.0.1:56575 ==> 200 Type set to: Binary. 127.0.0.1:56575 <== PASV 127.0.0.1:56575 ==> 227 Entering passive mode (127,0,0,1,221,2). 127.0.0.1:56575 <== MLSD [avelino.us]@127.0.0.1:56575 OK MLSD "/test". Transfer starting. 127.0.0.1:56575 ==> 150 File status okay. About to open data connection. 127.0.0.1:56575 ==> 226 Transfer complete.
Se você ler o log, vai reparar que tentei logar duas vez com a senha
errada, depois fiz a autenticação com a correta, criei uma pasta e
mandei um arquivo test.py para o FTP.
Agora vai da criatividade do programador para colocar mais funcionalidade para o serviço. Boa sorte! artigo publicado originalmente no iMasters, por Thiago Avelino
|
O Sphinx é uma
ferramenta que permite criar documentação para qualquer finalidade,
utilizando uma linguagem de marcação (reStructuredText) e alguns scripts
Python para gerar saída em formatos como HTML, LaTeX e PDF.
A maioria dos programadores tem uma certa resistência em criar documentação
Na maioria das vezes, a documentação é parte essencial de projetos e o
uso de uma linguagem de marcação pode tornar a escrita muito mais
confortável, rápida e menos trabalhosa do que usar um editor WYSIWYG.
Como a documentação é feita utilizando uma linguagem de marcação,
facilita a utilização de controle de versão e dividir as seções entre
diversos colaboradores, tornando a tarefa de criação de documentação
ainda mais rápida, evitando problemas de acesso e gravação quando se
trabalha em um único arquivo.
O Sphinx é muito prático e objetivo, faz com que os programadores tenham interesse e vontade em escrever documentações.
Muitas distribuições GNU/Linux já possuem o pacote do python-sphinx
pronto, bastando um apt-get install python-sphinx (para Debian e Ubuntu)
ou yum install python-sphinx (como Fedora/Red Hat/CentOS). Em outras
distribuições e sistemas operacionais como Mac ou Windows, é necessário
ter o Python instalado, pode-se utilizar o comando easy_install -U
Sphinx para instalar o pacote e suas dependências (como Jinja) para ter o
Sphinx e suas dependências instaladas.
Uma vez instalado, basta iniciar a criação de seu projeto de
documentação com o utilitário sphinx-quickstart, a saída será como no
exemplo abaixo:
anderson@yoda:~/tmp$ sphinx-quickstart Welcome to the Sphinx quickstart utility.
Please enter values for the following settings (just press Enter to accept a default value, if one is given in brackets).
Enter the root path for documentation. > Root path for the documentation [.]:
You have two options for placing the build directory for Sphinx output.
Either, you use a directory "_build" within the root path, or you separate "source" and "build" directories within the root path. > Separate source and build directories (y/N) [n]: y
Inside the root directory, two more directories will be created; "_templates" for custom HTML templates and "_static" for custom stylesheets and other static files. You can enter another prefix (such as ".") to replace the underscore. > Name prefix for templates and static dir [_]:
The project name will occur in several places in the built documentation. > Project name: Teste Sphinx > Author name(s): Christiano Anderson
Sphinx has the notion of a "version" and a "release" for the software. Each version can have multiple releases. For example, for Python the version is something like 2.5 or 3.0, while the release is something like 2.5.1 or 3.0a1. If you don't need this dual structure, just set both to the same value. > Project version: 1.0 > Project release [1.0]:
The file name suffix for source files. Commonly, this is either ".txt" or ".rst". Only files with this suffix are considered documents. > Source file suffix [.rst]:
One document is special in that it is considered the top node of the "contents tree", that is, it is the root of the hierarchical structure of the documents. Normally, this is "index", but if your "index" document is a custom template, you can also set this to another filename. > Name of your master document (without suffix) [index]:
Please indicate if you want to use one of the following Sphinx extensions: > autodoc: automatically insert docstrings from modules (y/N) [n]: > doctest: automatically test code snippets in doctest blocks (y/N) [n]: > intersphinx: link between Sphinx documentation of different projects (y/N) [n]: > todo: write "todo" entries that can be shown or hidden on build (y/N) [n]: > coverage: checks for documentation coverage (y/N) [n]: > pngmath: include math, rendered as PNG images (y/N) [n]: > jsmath: include math, rendered in the browser by JSMath (y/N) [n]: > ifconfig: conditional inclusion of content based on config values (y/N) [n]:
A Makefile and a Windows command file can be generated for you so that you only have to run e.g. `make html' instead of invoking sphinx-build directly. > Create Makefile? (Y/n) [y]: > Create Windows command file? (Y/n) [y]: n
Finished: An initial directory structure has been created.
You should now populate your master file ./source/index.rst and create other documentation source files. Use the Makefile to build the docs, like so: make builder where "builder" is one of the supported builders, e.g. html, latex or linkcheck
Depois de ter respondido as perguntas acima, um diretório com um
Makefile e os subdiretórios build e source serão criados. No source,
você vai trabalhar na criação do documento, enquanto que no build,
ficará sua documentação depois de gerada.
Dentro do build, terá um arquivo conf.py, que agrega todos os
parâmetros de configuração. As opções estão comentadas, com um breve
resumo de cada parâmetro, mas recomendo alterar a linha "language" para
pt_BR se estiver criando sua documentação em português. Ficará desse
jeito:
# The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. language = 'pt_BR'
Depois de configurado, basta criar seus documentos, são arquivos .rst
com a formatação bem semelhante de wiki. Um exemplo de documento
(arquivo intro.rst):
Título ======
A linguagem de marcação é semelhante a muitos wikis.
Subtítulo ---------
Você também pode usar **negrito**, *itálico*.
Uma lista enumerada ficaria assim:
#. Primeira linha #. Segunda linha #. Terceira linha
.. hint:: Aqui você pode colocar uma dica
Depois basta referenciar seus arquivos dentro do index.rst, não sendo necessário colocar a extensão .rst. Exemplo:
.. Teste Sphinx documentation master file, created by sphinx-quickstart on Sun Oct 24 18:19:06 2010. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive.
Welcome to Teste Sphinx's documentation! ========================================
Contents:
.. toctree:: :maxdepth: 2
intro
Indices and tables ==================
* :ref:`genindex` * :ref:`modindex` * :ref:`search`
Para gerar a saída em PDF, basta digitar make latex no diretório principal da documentação. Depois basta entrar no diretório build/latex e digitar make all-pdf para que o PDF seja gerado, como no exemplo abaixo (a página do nosso exemplo de documento):

Dicas aos usuários de Debian/Ubuntu (e provavelmente outras
distribuições): instale o pacote texlive-full para que o suporte a
geração de PDF funcione.
Além da tela acima, o pacote gera automaticamente o índice, índice de
imagens, índice de tabelas e demais facilidades utilizadas em
documentação. A qualidade do documento fica realmente muito
profissional, excelente para documentar projetos de software, criar
material para treinamentos, apresentações e outras necessidades com toda
a agilidade de trabalhar diretamente com linguagem de marcação. Para
saber mais, visite os links abaixo:
Veja todas as marcações disponíveis em reStructuredText quickstart.
Visite também o site do projeto: Python Sphinx artigo publicado originalmente no iMasters, por Christiano Anderson
|
Texto original de David Isaacson, traduzido com autorização e licenciado sob Creative Commons, disponível em http://www.siafoo.net/article/52
⁂
Quer escrever um código
mais curto e limpo? Tem uma situação infeliz em que você precisa pôr o máximo que
conseguir dentro de uma expressão? Prefere uma boa e rápida dose de hacks a
passar o resto da sua vida lendo os documentos? Você veio ao lugar certo. Começaremos
com uns truques rápidos que você já deve ter descoberto se já passou um tempo
com o Phyton, mas prometo que há coisas mais aventureiras mais para o final.
Tentei fazer com
que todos os trechos do código se executassem sozinhos. Se você quiser,
cole-os na sua Python Shell e os teste. Você vai notar muitos exemplos
contendo 'true example' e 'false example', com um comentário. Sinta se à
vontade para trocar o
comentário e ver o que acontece.
Você também verá
alguns comentários em branco flutuando em volta do código. Seu objetivo é
melhorar a legibilidade ao adicionar quebras de linhas ao mesmo tempo em que o
intérprete do Phython analisa o código. Isso não seria necessário em um
programa ‘real’ (‘não colados’).
Uma rápida
distinção entre true vs True neste artigo: quando eu disser que
um objeto é 'true', quero dizer que o
objeto, se convertido a um boolean em uma instrução if ou em qualquer
outro
lugar, seria convertido a True, e não
a False. Isso não significa que o
objeto é necessariamente idêntico ou equivalente a True. Da mesma
maneira, se eu disser que um objeto é 'false', quero dizer que o objeto
seria
convertido a False, não que ele seja
necessariamente idêntico ou equivalente a False.
1 - Truques Rápidos
1.1 - Quatro tipos de Quotes
Vamos começar com algo rápido que você provavelmente
já conhece. Se você está vindo de uma linguagem diferente, provavelmente
está acostumado a usar quotes únicos
para uma coisa, e quotes duplos para
outra. O Phyton te permite usar os dois, apesar de eles não serem
intercambiáveis (se você começar com um, você tem que terminar com o mesmo). O
Phyton também tem mais dois tipos de quotes.
Um quote triplo, ''', é criado ao
digitar três quotes únicos. Um quote duplo-triplo, """,
é criado ao digitar três quotes
duplos. Portanto, você pode ter várias camadas de quoting antes de começar a se
preocupar em deixar seus quotes
escaparem. Por exemplo, isto é valido no Python:
print """I wish that I'd never heard him say, '''She said, "He said, 'Give me five dollars'"'''"""
1.2 - A verdade de Vários Objetos
Diferentemente de
outras linguagens de programação (cof, cof, Javascript), os Phyton Types são false se estiverem vazios, e true
se não estiverem vazios. Isso significa que você não tem que checar, por
exemplo, se o comprimento de uma sequência (string),
tupla, lista ou dicionário, é 0 ou equivalente a um vazio. É preciso checar apenas a
verdade do objeto.
Como você já
imaginava, o número zero também é false,
enquanto todos os outros números são true.
Por
exemplo, as expressões a seguir são equivalentes. Aqui, 'my_object'
é um string, mas poderia facilmente
ser outro Python type (com as devidas
modificações para o teste de igualdade).
my_object = 'Test'
if len(my_object) > 0: print 'my_object is not empty'
if len(my_object): print 'my_object is not empty'
if my_object != '': print 'my_object is not empty'
if my_object: print 'my_object is not empty'
Em conclusão, não existe necessidade de checar
comprimentos ou equivalências se você só está interessado em saber se o objeto
é vazio ou não.
1.3 - Checando se um String contém um Substring
Aqui vai uma dica
rápida que pode ser óbvia, mas precisei de um ano de programação em Phyton para
descobri-la.
Você
provavelmente sabe que pode testar se uma lista, tupla, ou dicionário
contém um item ao testar a expressão 'item in list' ou 'item not in list'. Eu
nunca imaginei que isso funcionaria para strings
também. Eu sempre escrevia o código assim:
string = 'Hi there'
if string.find('Hi') != -1: print 'Success!'
Esse é um código feio. É
completamente equivalente a fazer 'if substring in string':
string = 'Hi there'
if 'Hi' in string: print 'Success!'
Muito mais limpo
e simples. Pode ser óbvio para 99% da população, mas eu gostaria de ter
descoberto antes.
1.4 - Imprimindo uma lista de forma bonita
Listas não imprimem bem. É claro que fica óbvio o que
é a lista, mas um usuário razoável não quer ver cochetes em volta de tudo.
Existe uma solução trivial para isso, usando o método ‘join’ na sequência.
recent_presidents = ['George Bush', 'Bill Clinton', 'George W. Bush'] print 'The three most recent presidents were: %s.' % ', '.join(recent_presidents)
O método ‘join’ transforma a lista em string ao moldar cada item
dentro do string, e então os conectando com o string em que o join foi
chamado. Inclusive é inteligente não colocar um depois do
último elemento.
Uma outra
vantagem é que isso é muito rápido, executado em tempo linear. Nunca crie um string utilizando ‘+’ itens juntos para
gerar um for loop: não é somente feio
como também é bem mais demorado.
1.5 - Integer vs. Float Division
Por padrão, se
você dividir um integer por outro, o resultado será truncado dentro de um integer. Por exemplo, executar 5/2 devolve 2.
Existem duas
maneiras de consertar isso. A primeiro e mais simples delas é simplesmente
transformar um dos integers em um float. Se os valores são estáticos, você pode
apenas adicionar um .0 a ele para o
transformar em float. 5.0/2 devolve 2.5. Alternativamente, você
pode apenas moldar um dos valores: float(5) / 2 devolve 2.5.
A outra maneira vai resultar em um código mais limpo, mas você deve
garantir que nenhum dos seus códigos está confiando nessa truncagem.
Você pode fazer uma divisão from __future__ import para que o
Phyton sempre retorne um float como resultado da divisão. Depois dessa
importação, 5/2 vai retornar 2.5. Se você ainda precisar usar a divisão
truncada de integer em outro lugar, você então pode usar o the //
operator: 5//2 vai sempre devolver 2.
5/2 5.0/2 float(5)/2 5//2
from __future__ import division 5/2 5.0/2 float(5)/2 5//2
Nota
Em
algum momento, a divisão de float será
a default (padrão).
Se você quiser que seu código garantidamente funcione em futuras versões do Python, use o // operator se
você quer utilizar a divisão truncada, não importa se você está utilizando a
divisão from
__future__ import ou
não.
1.6 - Funções Lambda
Às vezes, você precisa passar uma função como um
argumento, ou você quer fazer uma operação curta – mas complexa – múltiplas
vezes. Você pode definir sua função da maneira normal, ou você pode fazer uma
função lambda, uma mini-função que devolve o resultado de uma simples
expressão. As duas definições são completamente idênticas.
def add(a,b): return a+b
add2 = lambda a,b: a+b
A vantagem da
função lambda é que ela própria é uma expressão, e pode ser usada ao lado de
outra declaração. Aqui temos um exemplo usando a map function, que chama uma função em cada elemento de uma lista, e
devolve uma lista dos resultados.
squares = map(lambda a: a*a, [1,2,3,4,5])
Sem o lambda,
você teria que definir a função separadamente. Voce apenas salvou uma linha de
código e um nome variável (para a função).
Sintaxe: Funções Lambda
Uma função lambda tem a sintaxe: lambda variable(s) : expression
variable(s) variável(eis)
|
Uma lista, variável ou variáveis, separadas
por uma vírgula, que uma função pode receber. Voce não pode usar
palavras-chave, e você não quer que eles estejam em parênteses (um erro que
eu cometi por alguns meses e me perguntava por que meus lambdas nunca funcionavam).
|
Expression (expressão)
|
Uma expressão inline python. O escopo
inclui escopos locais e variáveis. Isso é o que a função retorna.
|
No próximo artigo, falaremos sobre o uso de listas inteligentes, como
mapeá-las e filtrá-las e quando devemos usar o generator expressions. artigo publicado originalmente no iMasters, por Redação iMasters
|
No artigo anterior,
falamos de alguns truques para aproveitar o máximo de uma expressão com
poucas linhas de código. Continuaremos agora falando sobre as listas
inteligentes.
2 - Listas
2.1 - Compreensão de listas (list comprehensions)
Se você já usa o
Python há muito tempo, você pelo menos já ouviu falar de compreensão de listas.
São uma maneira de fazer caber um for
loop, um if statement, e uma
tarefa, tudo em uma linha. Em outras palavras, você pode mapear e filtrar a lista em uma única expressão.
2.1.1 - Mapeando a lista
Vamos começar com
algo bem simples. Digamos que você está tentando elevar ao quadrado todos os
elementos de uma lista. Um programador recém iniciado no Phyton provavelmente
escreverá o código assim:
numbers = [1,2,3,4,5] squares = [] for number in numbers: squares.append(number*number)
Você eficientemente ‘mapeou’ uma lista para outra lista. Você também pode usar a map function, e fazer algo assim:
numbers = [1,2,3,4,5] squares = map(lambda x: x*x, numbers)
Esse código é definitivamente mais curto (uma linha em vez de três),
mas é bem feio. É difícil dizer, com uma rápida olhada, o que a
function map faz (ela aceita a função
e a lista, e aplica a função a cada elemento daquela lista). Além disso, você
tem que usar uma função que pareça meio bagunçada de alguma maneira. Se tivesse
um outro jeito... talvez uma compreensão de lista:
numbers = [1,2,3,4,5] squares = [number*number for number in numbers]
Ela faz a mesma
coisa que mostramos nos últimos dois exemplos, mas é mais curto (diferente do
primeiro exemplo) e limpo (diferente do segundo exemplo). Ninguém terá
problemas em determinar o que ela faz, mesmo se não conhecer Phyton.
2.1.2 - Filtrando a lista
E se você estiver mais interessado em filtrar a lista?
Digamos que você quer remover cada elemento com valor igual ou maior que 4?
(ok, os exemplos não são muito reais, fazer o quê...). Um novato
no Phyton escreveria:
numbers = [1,2,3,4,5] numbers_under_4 = [] for number in numbers: if number < 4: numbers_under_4.append(number)
Bem simples, certo? Mas você precisou de 4 linhas,
dois níveis de aninhamento, e um anexo para fazer algo completamente trivial.
Você poderia reduzir o tamanho do código com a filter function:
numbers = [1,2,3,4,5] numbers_under_4 = filter(lambda x: x < 4, numbers)
Similar à map function da
qual falamos acima, ela reduz o tamanho do código, mas está muito feio. O que
está acontecendo? Como o map, o filter aceita a função e a lista. Ele
avalia cada elemento da lista e, se eles forem avaliados como true, esse elemento é incluído na lista
final. Claro que você também pode fazer isso com a compreensão de lista também:
numbers = [1,2,3,4,5] numbers_under_4 = [number for number in numbers if number < 4]
Mais uma vez, utilizar compreensão de lista nos dá um
código mais curto, limpo e simples de entender.
2.1.3 - Map e Filter ao mesmo tempo
Agora entendemos
o real poder das listas de compreensão. Se não consegui te convencer de que map e filter são uma grande perda de tempo até aqui, espero que isso resolva.
Digamos
que quero usar o map e o filter em uma lista ao mesmo tempo. Em
outras palavras, gostaria de ver elevado ao quadrado cada elemento da lista que
estiver abaixo de 4. Mais uma vez, a maneira de um novato em Phyton:
numbers = [1,2,3,4,5] squares = [] for number in numbers: if number < 4: squares.append(number*number)
O código está
começando a expandir na direção horizontal agora! O que poderíamos fazer para
simplificar o código? Poderíamos tentar usar map e filter, mas eu não
acho que será uma boa ideia...
numbers = [1,2,3,4,5] squares = map(lambda x: x*x, filter(lambda x: x < 4, numbers))
Enquanto map e filter
estavam feios antes, agora eles estão simplesmente ilegíveis. Obviamente essa
não é uma boa ideia. Mais uma vez, a lista de compreensão salva o dia:
numbers = [1,2,3,4,5] squares = [number*number for number in numbers if number < 4]
Esse é um
pouquinho mais longo que os outros exemplos de listas de compreensão, mas, na
minha opinião, ainda é bem legível. É definitivametne melhor que usar for loop e que usar map e filter.
Como
você pode ver, a lista de compreensão usa o filter
e depois o map. Se você precisa usar o map, e depois o filter, as coisas ficam mais
complicadas. Voce pode até ter que usar listas de compreensão aninhadas, os
comandos map e filter, ou um for loop
regular, dependendo do que é mais limpo. Essa discussão, no entanto, está fora
do escopo deste artigo.
2.1.4 Generator Expressions
Existe um lado
ruim das listas de compreensão: a lista inteira tem que ser armazenada de uma
vez na memória. Isso não é um problema para listas pequenas, como as que
utilizamos nos exemplos acima, nem para listas de maior magnitude. Mas,
eventualmente, elas se tornam bastante ineficientes.
Generator expressions
(gerador de expressões) são novos no Python 2.4, e possivelmente a
coisa mais legal já publicada sobre Phyton. E eu acabei de descobri-las.
Os geradores de expressões NÃO carregam a lista completa para a memória
de uma vez, mas criam um 'generator object', de modo que somente um
elemento da lista tenha que ser carregado de cada vez.
Claro que se você realmente precisa usar toda a lista para algo, isso
não te ajuda muito. Mas se você está apenas a passando para algo que
aceita qualquer objeto iterável – como um for loop – você pode usar a function generator.
O gerador de expressões tem a mesma sintaxe das listas de compreensão, mas usando parênteses em vez de colchetes:
numbers = (1,2,3,4,5) # Since we're going for efficiency, I'm using a tuple instead of a list ;) squares_under_10 = (number*number for number in numbers if number*number < 10) # squares_under_10 is now a generator object, from which each successive value can be gotten by calling .next()
for square in squares_under_10: print square, # prints '1 4 9'
Isso é meramente mais eficiente do que usar uma lista de
compreensão.
Portanto, você deve usar o gerador de expressões para um maior
número de itens. Você sempre vai usar listas de compreensão se precisar de
uma lista inteira de uma vez, por alguma razão. Se nenhuma dessas regras se
aplica, use qualquer um deles. É uma boa prática usar gerador de expressões, a
não ser que exista alguma razão para não usá-lo, mas você não vai ver nenhuma
real diferença em eficiência, a não ser que a lista seja muito grande.
Como nota final, o gerador de expressões só precisa estar dentro
de um conjunto de parênteses. Portanto, se você está chamando uma função
unicamente com o gerador de expressões, só precisa de parênteses. Isso é válido no Phtyon: some_function(item for item in list).
2.1.5 - Usando 'for' para aninhar expressões
Listas de compreensão e geradores de expressões podem
ser usados além de mapping e filtering; você pode criar listas
bastante complexas com eles.
Além de poder usar o map e filter, você pode aninhar (nest) as
expressões for. Um novato no Phyton pode
escrever algo como:
for x in (0,1,2,3): for y in (0,1,2,3): if x < y: print (x, y, x*y),
Você pode ver que esse código é bem maluco. Com a lista de
compreensão, no entanto, você pode fazer isso de maneira mais rápida:
print [(x, y, x * y) for x in (0,1,2,3) for y in (0,1,2,3) if x < y]
Como você pode ver, esse código itera quatro valores de y, e para cada
um desses valores, itera quatro valores de x, e então usa o filter e o map. Cada item da lista, então, é uma lista de x, y, x * y.
Note que o xrange(4) é um pouco mais limpo que (0,1,2,3),
especialmente para listas mais longas, mas nós não chegamos lá ainda.
2.1.6 - Conclusão
Eu odeio dizer, mas nós apenas atingimos a superfície do que listas de
compreensão e geradores de expressões podem fazer. Você realmente tem total
poder de um for loop e de um if statement.
Você pode fazer qualquer coisa (eu acho) que poderia fazer em qualquer um
deles. Você pode operar em qualquer coisa que quer que comece como uma
lista (ou qualquer outro iterável) e termine como uma lista (ou um gerador),
incluindo listas de listas.
Uma lista de compreensão tem a sintaxe: [element for variable(s)
in list if condition ]
Um gerador de expressões
tem a sintaxe: (element for variable(s) in list if condition
)
Lista
|
Qualquer coisa que pode ser tratada como lista ou iterador
|
variáveis(eis)
|
Variavél ou variáveis para atribuir o elemento da lista atual, da
mesma maneira que um for loop
|
Condição
|
Uma expressão inline do Python. O escopo mais uma vez inclui o escopo
local e suas variáveis. Se isso se torna verdadeiro, o item será excluído do resultado
|
Elemento
|
Uma expressão inline do Phyton. O escopo inclui o escopo local e suas
variáveis. Este é o elemento real que será incluído no resultado
|
A(s) variável(eis) for na lista podem ser repetidas indefinidamente.
2.2 - Reduzindo uma lista
Infelizmente, você ainda não pode escrever uma programação inteira
usando listas de compreensão (estou brincando... claro que você pode). Apesar
de elas utilizarem map e filter, não existe uma maneira simples
de usar listas de compreensão para reduzir uma lista. Com isso, quero dizer
aplicar uma função para os dois primeiros elementos da lista, em seguida para
aquele resultado e para o próximo elemento da lista, e assim em diante, até que
um valor único é encontrado. Por exemplo, talvez você queira encontrar o
produto de todos os valores na lista. Você poderia fazer um for loop:
numbers = [1,2,3,4,5] result = 3for number in numbers: result *= number
Ou você poderia usar a função reduce, que
aceita uma função que pegue dois argumentos, e uma lista:
numbers = [1,2,3,4,5] result = reduce(lambda a,b: a*b, numbers)
Essa não é tão bonita quanto uma lista de compreensão,
mas é menor que um for loop.
Definitivamente vale a pena guardar.
2.3 - Iteração em vez de Lista: range, xrange e enumerate
Você se lembra (ou talvez não) de quando você programava em C, e for loops eram contados através de números
indexados em vez de elementos? Você provavelmente já sabe como replicar esse
comportamento no Python,
usando range
ou xrange. Ao passar o valor para range, você tem uma lista de
contagem de números inteiros de 0 ao valor - 1, inclusive. Em outras palavras,
ele te dá os valores do index de uma lista com aquele comprimento. O xrange
faz a mesma coisa, apenas de maneira mais eficiente: ele não carrega a lista
inteira na memória de uma só vez.
Aqui
está um exemplo:
strings = ['a', 'b', 'c', 'd', 'e'] for index in xrange(len(strings)): print index,
O problema aqui é que normalmente você acaba
precisando dos elementos da lista de qualquer jeito. Qual o propósito de ter
somente os valores do index? O Phyton tem uma função muito boa chamada enumerate
que te dará os dois. Ao usar o enumerate em uma lista, ele vai
retornar um iterador de index, com valores pares:
strings = ['a', 'b', 'c', 'd', 'e'] for index, string in enumerate(strings): print index, string,
Outra vantagem do enumerate é que ele é
consideravelmente mais limpo e legível que xrange(len()). Por causa
disso, range e xrange são somente úteis quando você precisa criar uma
lista de valores do zero, por alguma razão, em vez de usar aqueles de uma
lista já existente.
2.4 - Checando a condição em todo e qualquer elemento de uma lista
Digamos que você quer checar para ver se qualquer
elemento na lista satisfaz uma condição (digamos, abaixo de 10). Antes
do Python 2.5, você poderia fazer algo do tipo:
numbers = [1,10,100,1000,10000] if [number for number in numbers if number < 10]: print 'At least one element is over 10'
Se nenhum dos elementos satisfaz a condição, a lista de
compreensão vai criar uma lista vazia, que é avaliada como falsa (false). Caso contrário, uma lista não-vazia
será criada, e avaliada como verdadeira (true).
Estritamente, você não precisa avaliar cada item na lista; você pode parar
depois do primeiro elemento que satisfizer a condição. O método acima é menos
eficiente, mas pode ser sua única escolha se você não puder usar unicamente o
Python 2.5 e precisa espremer toda essa lógica em uma única expressão.
Com
a nova função any introduzida no novo Python 2.5, você pode fazer a
mesma coisa de maneira mais limpa e eficiente. O any é inteligente o
suficiente para retornar True depois
do primeiro item que satisfaz a condição. Aqui, eu usei um gerador
de expressões que retorna um valor True ou False para cada
elemento, e o passa para any. O gerador de expressões somente computa esses
valores na medida em que eles são requisitados, e o any só pede o valor que ele
precisa:
numbers = [1,10,100,1000,10000] if any(number < 10 for number in numbers): print 'Success'
Da mesma maneira, você pode chegar se todo elemento
satisfaz a condição. Sem o Python 2.5, você
teria que fazer algo assim:
numbers = [1,2,3,4,5,6,7,8,9] if len(numbers) == len([number for number in numbers if number < 10]): print 'Success!'
Aqui, usamos o filter com
uma lista de compreensão, e checamos para ver se ainda temos todos aqueles
elementos. Se sim, então todos os elementos satisfizeram a condição. Mais uma
vez, isso é menos eficiente do que poderia ser, porque não existe necessidade
de checar depois do primeiro elemento que não satisfaz a condição. E, mais uma
vez, sem o Python 2.5, essa deve ser sua única opção para que toda a lógica
caiba em uma única expressão.
Com
o Python 2.5, existe uma maneira mais fácil: a função all function. Como
você deve esperar, ela é inteligente o suficiente para parar depois do primeiro
elemento que não combina, retornando False.
Esse método funciona do mesmo modo que o método any descrito acima.
numbers = [1,2,3,4,5,6,7,8,9] if all(number < 10 for number in numbers): print 'Success!'
2.5 - Combinando listas múltiplas, item por item
A função zip pode ser usada para “fechar”
listas juntas. Ela retorna uma lista de tuplas (tuples), na qual a enésima tupla
contém o enésimo item para cada uma das listas passadas. Esse é um caso em que o
exemplo é a melhor explicação:
letters = ['a', 'b', 'c'] numbers = [1, 2, 3] squares = [1, 4, 9]
zipped_list = zip(letters, numbers, squares)
Muitas vezes você vai usar esse tipo de coisa como um
iterador para o for loop, retirando
todos os três valores ao mesmo tempo ('for letter, number, squares in
zipped_list').
2.6 - Alguns outros operadores de lista
A seguir, tem-se funções que podem ser chamadas em qualquer lista
ou iterável:
- max: Devolve o maior elemento da lista
- min: Devolve o menor elemento da lista
- sum: Devolve
a soma de todos os elementos da lista. Aceita um segundo argumento
opcional, o valor para começar quando se está somando (padrão 0).
2.7 - Lógica avançada com conjuntos
Eu admito que uma sessão sobre conjuntos não pertence a uma
sessão sobre listas. Mas ao mesmo tempo que nunca uso conjuntos para muita
coisa, eu ocasionalmente preciso fazer alguma lógica de conjuntos em alguma
lista que tenho por aí. Os conjuntos se diferenciam de listas por imporem
exclusividade (eles podem conter mais de um do mesmo item) e serem desordenados.
Conjuntos também suportam várias operações lógicas diferentes.
A
coisa mais comum que preciso fazer é garantir que minha lista é única. Isso é
fácil: só tenho que convertê-la em um conjunto e checar se o comprimento é o
mesmo:
numbers = [1,2,3,3,4,1] set(numbers)
if len(numbers) == len(set(numbers)): print 'List is unique!'
Claro que você pode converter o conjunto de volta em
uma lista, mas lembre-se de que a ordem não é preservada. Para mais informações
sobre as várias operações que os conjuntos suportam, dê uma olhada nos Python Docs.
Você vai querer usar uma operação ou outra nas suas listas ou nos conjuntos no
futuro.
No próximo artigo, falaremos sobre a construção e sobre manipulação de dicionários. artigo publicado originalmente no iMasters, por Redação iMasters
|
No artigo anterior,
falamos sobre a criação de listas inteligentes. Desta vez, vamos falar
da manipulação de dicionários, como aproveitar bem esse recurso e também
algumas dicas de como trabalhar com os operadores lógicos true e false.
3 - Dicionários
3.1 - Construindo dicionários com argumentos chaves
Quando eu estava aprendendo Python, eu perdi
completamente a alternativa de criar dicionários. Qualquer argumento chave que
você passa ao construtor dict é adicionado ao mais novo dicionário
criado antes de retornar. Claro que você está limitado às chaves que podem ser
feitas dentro dos argumentos chaves: nomes de variáveis válidas no Python. Aqui
está um exemplo:
1dict(a=1, b=2, c=3) 2
Isso pode ser um pouco mais limpo que uma criação "regular" de dicionários, dependendo do seu código; existem menos quotes.
Eu o uso bastante.
3.2 - Dicionários para listas
Transformar um dicionário em uma lista ou iterador é fácil. Para
ter uma lista das chaves, você pode simplesmente elencar o dicionário dentro
da lista. É mais limpo que, no entanto, chamar .keys() no dicionário para ter
uma lista de chaves, ou .iterkeys()para ter um iterador. Da mesma maneira, você
pode chamar .values() ou .itervalues() para ter uma lista ou iterador de
valores do dicionário. Lembre-se de que dicionários são inerentemente desordenados
e então esses valores não estarão em uma ordem relevante.
Para preservar as chaves e os valores, você pode transformar
um dicionário em uma lista ou iterador de 2 itens de tuplas ao usar .items() ou
.iteritems(). Isso é algo que você provavelmente fará muito, e não é muito
animador:
1dictionary = {'a': 1, 'b': 2, 'c': 3} 2dict_as_list = dictionary.items() 3
3.3 - Listas para dicionários
Você pode reverter o processo, transformando uma lista
de 2 elementos de listas ou tuplas em um dicionário:
1dict_as_list = [['a', 1], ['b', 2], ['c', 3]] 2dictionary = dict(dict_as_list) 3
Você também pode combinar isso com o método "keyword arguments" para criar dicionários, como discutido acima:
1dict_as_list = [['a', 1], ['b', 2], ['c', 3]] 2dictionary = dict(dict_as_list, d=4, e=5) 3
A possibilidade de converter um dicionário em lista é
algo que vem a calhar, acho. Mas o que realmente o faz ser bem legal é o
próximo passo.
3.4 - "Compreensões de dicionários"
Apesar de o Phyton não ter compreesões de dicionários, você pode
fazer algo bem próximo com pouca bagunça e código. Apenas use .iteritems() para
transformar seu dicionário em uma lista, jogue nele um gerador de expressões
(ou lista de compreensão), e então a coloque de volta no dicionário.
Por exemplo, digamos que eu tenho o dicionário name:email
pairs, e quero criar um dicionário name:is_email_at_a_dot_com
pairs:
1emails = {'Dick': 'bob@example.com', 'Jane': 'jane@example.com', 'Stou': 'stou@example.net'} 2 3email_at_dotcom = dict( [name, '.com' in email] for name, email in emails.iteritems() ) 4 5
Perfeito. Claro que você não tem que começar E terminar com um
dicionário, você pode jogar umas listas ali também.
Enquanto
isso é um pouco menos legível que uma lista de compreensão em ordem, eu
argumentaria que ainda é melhor que um loop for massivo.
4
- Selecionando Valores
4.1 - The right way (o jeito certo)
Enquanto eu estava escrevendo este artigo, eu vacilei
sobre qual seria o right way
(jeito certo) de selecionar valores inline, nova no Python 2.5 (você achou que
teria mais alarde!). O Python agora suporta a
sintaxe 'value_if_true if test else value_if_false'. Então,
você pode fazer uma simples seleção de valores em uma linha, sem nenhuma
sintaxe estranha ou ressalvas importantes:
1test = True 2 3result = 'Test is True' if test else 'Test is False' 4
Ok, ainda está um pouco feio. Você também pode encadear múltiplos
testes em uma linha:
1test1 = False 2test2 = True 3result = 'Test1 is True' if test1 else 'Test1 is False, test2 is True' if test2 else 'Test1 and Test2 are both False'
O primeiro if/else é avaliado primeiro e, se o if test1 é falso, o
segundo if/else é avaliado. Você também pode fazer coisas mais complicadas,
especialmente se colocar alguns parênteses.
Nota Pessoal
Isso é muito novo no campo, e minha reação é misturada. Realmente
é o Right Way, é mais limpo, e eu
gosto... mas ainda é feio, especialmente se você tem múltiplos if/else's
aninhados.
Claro que a sintaxe para toda a seleção de truques de valor é
feia.
Tenho uma quedinha pelo truque and/or abaixo, eu o acho bastante
intuitivo, agora que entendo como ele funciona. E não é menos eficiente que
fazer as coisas do Right Way.
O que você acha? Sinta-se a vontade para comentar abaixo.
Apesar
de a linha and/or ser o novo método correto, você deveria checar os truques
abaixo. Mesmo se você estiver planejando programar no Python 2.5, ainda
vai encontrá-los em códigos antigos. Claro, se você precisar de compatibilidade
ou não tem o Python 2.5, você realmente precisa checar os truques
abaixo.
4.2 - O truque and/or
No Python, "and" e "or" são criaturas complexas.
Usar o "and" para juntar duas expressões não simplesmente retorna true,
se ambos são verdadeiros, e false, se ambos são falsos. Em vez disso,
o "and" retorna o primeiro valor falso, ou o último valor se
todos são verdadeiros. Em outras palavras, se o primeiro valor é falso,
ele é retornado, senão, o último valor é retornado. O resultado é algo que
você já deve imaginar: se ambos são verdadeiros, o último valor, que é
verdadeiro, será retornado avaliado como true em um boolean test (p.e.
um 'if' statement). Se algum valor é falso, esse valor será retornado e
avaliado como false em um boolean test.
Usar o "or" em duas expressões juntas é similar. O "or"
devolve o primeiro valor verdadeiro, ou o último valor se todos forem falsos.
Em outras palavras, se o primeiro valor é verdadeiro, ele é retornado,
senão, o último valor é retornado. Se ambos são falsos, o último valor, que
é falso, será retornado e avaliado como false em um boolean test.
Se algum valor é verdadeiro, esse valor será retornado e avaliado como true
em um boolean test.
Isso não te ajuda muito se você está apenas testando a "verdade".
Mas você pode usar "and" e "or" para outros objetivos no Python; o meu favorito
é selecionar entre os valores de uma maneira parecida à do C ternary conditional assignment operator 'test ? value_if_true
: value_if_false':
1test = True 2 3result = test and 'Test is True' or 'Test is False' 4
Como isso fuciona? Se o test é true, o
statement and o pula e o devolve pela metade, aqui 'Test is True' or
'Test is False'. Como o processo continua da esquerda para a direita, o statement or retorna
o primeiro valor verdadeiro, 'Test is True'.
Se o test é false, o statement and
devolve test. Como
o processo continua da esquerda para a direita, o statement que resta é test
or 'Test is False'. Como o test é false,
o statement or o pula e retorna sua metade da direita, 'Test is
False'.
Cuidado
Garanta que o valor do meio (if_true)
nunca seja falso. Se for, o statement ‘or’
vai sempre pulá-lo, e sempre retornará o valor mais à direita (if_false), não importando
qual seja o valor de teste.
Depois de me
acostumar com esse método, 'The Right Way' acima se tornou menos
intuitivo para mim. Se você não está preocupado com compatibilidade, sugiro que
você tente ambos e veja qual dos dois você prefere. É bem simples aninhar
truques "and/or" ou jogar um "and" ou "or" extras depois que você entende a lógica por trás deles. Se
você não consegue decidir, ou não quer aprender os dois, então use o "and/or". Faça as coisas do Right Way, e termine com isso.
Claro que, se
você precisa suportar as versões do Python abaixo da 2.5, 'The Right Way' não
vai funcionar. (Eu estava tentado a dizer que era o ‘The Wrong Way’ – ‘ O Jeito
Errado). Nesse caso, o truque "and/or"
é definitivamente sua melhor aposta para a maioria das situações.
Espero que isso
faça sentido; é difícil de explicar. Pode parecer complicado agora, mas se você
usá-lo algumas vezes e brincar com o "and" ou "or", rapidamente eles vão fazer sentido e você será capaz de
criar novos truques "and" ou "or" você mesmo.
4.3 - Usando True e False como Indexes
Um outro jeito de
selecionar valores é usar True e False como indexes de uma lista, tirando
vantagem do fato que False == 0 e True == 1:
1test = True 2 3result = ['Test is False','Test is True'][test] 4
Isso é um pouco
mais avançado que o truque do "and" ou "or", e livre do problema em que o value_if_true deve ser, ele próprio, verdadeiro.
No entanto, ele
também sofre de um defeito significante: ambos os itens devem ser avaliados
antes de sua “verdadeirabilidade” ser checada. Para strings ou outros itens simples, isso não é um poblema. Mas se cada
item envolver uma computação significante ou I/O, você realmente não quer ter
trabalho dobrado. Por esse motivo, eu normalmente prefiro o 'Right Way' ou o
truque do "and" ou "or".
Note também que o
método do index só funciona quando você sabe que o teste é False ou True (ou 0 ou 1,
mas nenhum outro numero inteiro ou objeto arbitrário). Caso contrário, você
deveria escrever bool(test) em vez de test para
obter o mesmo comportamento da expressão do truque "and" ou "or" acima.
No próximo artigo, falaremos
sobre a declaração de funções no Python.
⁂
Texto original licenciado sob Creative Commons e disponível em http://www.siafoo.net/article/52
artigo publicado originalmente no iMasters, por David Isaacson
|
Revendo um velho texto sobre como reformatar datas em python
do formato “máquina” (Y-m-d) para o formato “humano” (d/m/Y), percebi
que estava trabalhando o tempo todo com strings e ainda era preso à
forma PHP de pensar em resolver o problema com Arrays e Strings.
Elcio resolveu a questão com apenas uma linha, o que considero muito
elegante, mas a questão agora é a gama de funcionalidades que você pode
ter com um objeto e uma string. Infelizmente, com string não dá para
fazer muita coisa, como por exemplo converter em maiúsculas e minúsculas
ou verificar se ela começa com uma vogal ou uma consoante.
Objetos em python
são mais legais de trabalhar, pois com eles você tem uma gama de
funcionalidades próprias que o elemento possui só pelo fato de ele
existir. E o mais legal é que essas funcionalidades só vão existir nesse
objeto. Isso quer dizer que você não corre o risco de errar, aplicando
uma funcionalidade que não condiz com o que você deseja.
Vamos considerar o exemplo do outro artigo e reformatá-lo, só que desta vez usando o objeto time.
import datetime,time data = "2008-08-12" ndata = time.strptime(data, "%Y-%m-%d") ndata = datetime.datetime(*ndata[0:5])
Pronto, agora você pode usar a variável ndata no formato que achar necessário. Como no nosso caso, podemos usar o formato ISO padrão:
print ndata.strftime('%d/%m/%Y')
O legal de usar objeto, neste caso, é que podemos fazer contas entre
datas usando o timedelta do próprio objeto datetime. Assim podemos ver
que dia será daqui a duas semanas com um código simples:
print "Daqui ha duas semanas sera " + ( ndata - datetime.timedelta(weeks=+2) ).strftime('%d/%m/%Y')
Note que na mesma linha coloquei o formatador de data para facilitar o
resultado, mas você pode fazer o processo em duas linhas, se achar
necessário.
Script final, completo
import datetime,time data = "2008-08-12" ndata = time.strptime(data, "%Y-%m-%d") ndata = datetime.datetime(*ndata[0:5]) print ndata.strftime('%d/%m/%Y')
print "Daqui a duas semanas sera " + ( ndata - datetime.timedelta(weeks=+2)
artigo publicado originalmente no iMasters, por Michael Granados
|
Este é um tutorial sobre Python e GeeXLab. O objetivo é dar
uma olhada geral nas bibliotecas Python que podem ser úteis para demos GeeXLab E que funcionam no GeeXLab.
O GeeXLab
tem uma maneira muito simples de trabalhar. Superficialmente falando, um demo é
feito a partir de um script de inicialização (INIT, executado uma vez) e de um
script por frame (FRAME, executado a cada frame). Esses scripts podem ser
programados no Lua ou no Python. Em um script, você pode codificar o que quiser
com ambas as linguagens. Não existe restrição: o GeeXLab pode ser visto como
uma máquina virtual para o Lua ou o Python. É por isso que a maioria dos
pacotes Python disponíveis irá funcionar com o GeeXLab.
Para
interagir com os dados de cenário do GeeXLab (texturas, GLSL shaders, meshes
etc) existe o Host-API, que é simplesmente a API do GeeXLab para Lua e para Python. A
descrição das funções do Host-API para GeeXLab está disponível aqui.
A rápida
descrição do scripting do GeeXLab é importante, porque irei focar nas funcionalidades oferecidas por
essas bibliotecas, e tentarei limitar o uso das funções GeeXLab ao mínimo. Dessa maneira, você poderá
rapidamente reutilizar todos os fragmentos de códigos em seus próprios
projetos Python.
Dito
isso, vamos falar sobre a primeira biblioteca Python: PIL.
1 – PIL: biblioteca
de imagens do Python
PIL ou Biblioteca de Imagens do Python é um pacote que expõe muitas
funções para manipular imagens a partir de um script Python. A homepage oficial
do PIL está aqui. Quando escrevi este artigo, a versão atual do PIL era PIL
1.1.7 e estava disponível para Python 2.3 até Python 2.7. Irei usar o PIL 1.1.7 for Python
2.6 neste artigo.
No
Windows (XP, Vista ou 7), a instalação do PIL é bem simples: apenas inicie o PIL Windows installer e pronto. Claro que você precisa
de uma instalação válida do Python 2.6.6 antes.
A
documentação do PIL está disponível aqui:
2 – Carregando uma imagem
Aqui está
um pequeno script INIT que usa o PIL para carregar uma imagem e exibi-la. Se
você precisar, uma versão do PIL pode ser encontrada na variável Image.VERSION.
import HYP_Utils import sys
from PIL import Image
scriptDir = HYP_Utils.GetDemoDir()
PIL_Version = Image.VERSION
img_filename = "%s/flower.jpg" % scriptDir im = Image.open(img_filename) im.show()
No
Windows, a função Image.show() salva a imagem em um arquivo temporário e
chama o visualizador de imagem padrão. No meu sistema, o Irfanview é
chamado para visualizar a imagem:
Demo_Python_PIL_01.xml
3 – Salvando uma imagem
Apenas
chame e função Image.save(). Você quer
salvar no formato JPEG? Apenas adicione a extensão.jpg no nome do seu arquivo… Mesma
coisa para os outros formatos.
Formatos suportados
em leitura E escrita: *.bmp, *.gif, *.jpg, *.msp, *.pcx,
*.png, *.ppm, *.tiff e .xbm.
Aqui está um simples conversor de JPG para BMP:
import HYP_Utils from PIL import Image
scriptDir = HYP_Utils.GetDemoDir()
PIL_Version = Image.VERSION
img_filename = "%s/flower.jpg" % scriptDir im = Image.open(img_filename) im.save("%s/flower.bmp" % scriptDir)
4 – Lendo os pixels
Existem
duas funções que possibilitam a leitura do mapa de pixels (ou pixel data): Image.getpixel()
e Image.getdata().
Image.getpixel()
retorna o valor de um único pixel. Apenas faça a enupla com as coordenadas X e
Y e o getpixel() retornará um 3-tuple RGB para uma imagem RGB ou um único valor
para uma imagem de luminância. O Image.getdata() retorna o
mapa de pixels completo. Você precisa da função list() do Python para criar a lista do mapa de pixels das tuplenuplas RGB.
Aqui está um fragmento de código que carrega uma
imagem com PIL, cria um objeto de textura com o GeeXLab Python API e preenche a
textura com os pixels da imagem.
import HYP_Utils import HYP_Texture import HYP_Material import sys
from PIL import Image
scriptDir = HYP_Utils.GetDemoDir()
PIL_Version = Image.VERSION
img_filename = "%s/flower.jpg" % scriptDir im = Image.open(img_filename)
imageW = im.size[0] imageH = im.size[1]
TEXTURE_2D = 2 RGB_BYTE = 2 texId = HYP_Texture.Create(TEXTURE_2D, RGB_BYTE, imageW, imageH, 0)
matId = HYP_Material.GetId("plane1_mat") HYP_Material.AddTexture(matId, texId)
if (im.mode == "RGB"): for y in range(0, imageH): for x in range(0, imageW): offset = y*imageW + x xy = (x, y) rgb = im.getpixel(xy) HYP_Texture.SetValueTex2DByteRgb(texId, offset, rgb[0], rgb[1], rgb[2]) elif (imout.mode == "L"): for y in range(0, imageH): for x in range(0, imageW): offset = y*imageW + x xy = (x, y) rgb = im.getpixel(xy) HYP_Texture.SetValueTex2DByteRgb(texId, offset, rgb, rgb, rgb)
Com
o Image.getdata(), as últimas linhas do script anterior seriam:
pixels = list(im.getdata()) if (im.mode == "RGB"): for y in range(0, imageH): for x in range(0, imageW): offset = y*imageW + x rgb = pixels[offset] HYP_Texture.SetValueTex2DByteRgb(texId, offset, rgb[0], rgb[1], rgb[2]) elif (imout.mode == "L"): for y in range(0, imageH): for x in range(0, imageW): offset = y*imageW + x rgb = pixels[offset] HYP_Texture.SetValueTex2DByteRgb(texId, offset, rgb, rgb, rgb)
 Demo_Python_PIL_02.xml
5 – Processamento de imagem
Você pode facilmente aplicar filtros comuns de
imagem com o PIL: blur, emboss, sharpen etc. Apenas importe o módulo ImageFilter:
from PIL import Image from PIL import ImageFilter
...
i = Image.open(img_filename) im = i.filter(ImageFilter.EMBOSS)
...
Os
filtros pré-definidos são: BLUR, CONTOUR, DETAIL, EDGE_ENHANCE,
EDGE_ENHANCE_MORE, EMBOSS, FIND_EDGES, SMOOTH, SMOOTH_MORE, e SHARPEN.

Demo_Python_PIL_03.xml
– EMBOSS

Demo_Python_PIL_03.xml
– FIND_EDGES
Existe
também um módulo chamado ImageOps que expõe as funções de processamento
da imagem, como colorize(), flip(), grayscale(), invert(), mirror(),
solarize(), ou posterize().

Demo_Python_PIL_04.xml
– solarise()

Demo_Python_PIL_04.xml
– posterize()
6 – Adicionando uma marca d’água
ImageDraw e ImageFont garantem ao PIL a capacidade de escrever textos em uma imagem, bem como
desenhar linhas ou pontos. Aqui está o fragmento do código que mostra um
simples conversor de lotes com PIL: ele lê todos os arquivos jpg de uma pasta,
adiciona a marca d’água (uma cruz e a faixa “GEEXLAB”) e salva as imagens com o
prefixo gxl_.
import HYP_Utils import os, glob from PIL import Image from PIL import ImageDraw from PIL import ImageFont
scriptDir = HYP_Utils.GetDemoDir()
ft = ImageFont.load("timR24.pil")
os.chdir(scriptDir) file_list = glob.glob("*.jpg") for f in file_list: im = Image.open(scriptDir + str(f)) draw = ImageDraw.Draw(im) draw.line((0, 0) + im.size, fill=(255, 255, 255)) draw.line((0, im.size[1], im.size[0], 0), fill=(255, 255, 255)) wh = ft.getsize("G E E X L A B") draw.text((im.size[0]/2 - wh[0]/2, im.size[1]/2 + 20), "G E E X L A B", fill=(255, 255, 0), font=ft) draw.text((im.size[0]/2 - wh[0]/2, im.size[1]/2 - 60), "G E E X L A B", fill=(255, 255, 0), font=ft) del draw im.save(scriptDir + "gxl_" + str(f))
O
arquivo timR24.pil vem com um pacote de fontes PIL. Você pode fazer o download aqui.

Demo_Python_PIL_05.xml
Existe
também uma função legal ImageFont.truetype(), mas ela não funcionou no meu
sistema porque essa função se apóia na biblioteca _imagingft.pyd (na verdade um
DLL) que não pôde ser carregada devido a um problema de tempo de execução do Visual
C. Aqui está o erro em um console GeeXLab:
# ERROR:
Python – Script [initScene] has a runtime error. Error line: 29 – Error object:
– Error data: DLL load failed: The application has failed to start because its
side-by-side configuration is incorrect. Please see the application event log
or use the command-line sxstrace.exe tool for more detail.. Script disabled.
Este link tem alguns detalhes sobre esse
problema.
7 – Downloads
Se você
quiser brincar com os demos GeeXLab, você pode encontrá-los no pacote de
amostras demo disponíveis aqui. Os demos relacionados ao PIL estão na pasta
Python_PIL/.
Aqui está
um patch para o GeeXLab 0.2.5 + Python 2.6.6 que você deve usar para executar
os demos. Apenas unzip o arquivo e copie o DLL para dentro da pasta GeeXLab_Python_Lua/.
PATCH
DOWNLOAD: GeeXLabCore.dll
⁂
Texto original em
inglês de JeGX, disponível em http://www.geeks3d.com/20100930/tutorial-first-steps-with-pil-python-imaging-library/
|
Este artigo é uma anotação de uma palestra que fiz recentemente na Software Summit Passion em Gotemburgo, Suécia.
Escrever uma especificação em uma linguagem de programação completa como Python possui vantagens e desvantagens. Em relação às desvantagens, o Python não é uma linguagem declarativa, portanto, qualquer tentativa de torná-lo declarativo (além de apenas listar os tipos nativos de dados) exigirá algum tipo de personalização e/ou ferramentas para trabalhar. Como vantagem, tendo uma declaração na linguagem na qual você escreve seus servidores, você pode usar a própria especificação, em vez de um derivado gerado dela, e escrever geradores personalizados para outras linguagens é simples, desde que você possa fazer introspecção em Python atravessar a sua especificação, e a lógica templating de sua escolha para gerar o código-fonte - o que torna possível, por exemplo, atingir um terminal J2ME que só não vai aceitar as soluções existentes, e onde colocar um arquivo jar 150K para implementação do protocolo não é uma alternativa.
Para mim, essa jornada começou por volta de 2006, quando comecei a perder o controle sobre documentação de protocolo e versões de protocolo para o protocolo usado entre terminais e servidores no gerenciamento da solução Visual Units Logistics. Depois de procurar e descartar várias ferramentas existentes, e após ser inspirado pelo fato de que nós geralmente configuramos o JavaScript no JavaScript, comecei a esboçar (como tinta sobre papel) sobre como seria uma especificação de protocolo em Python. Esta é uma transcrição do que eu criei na época:
imei = long log_message = string timestamp = long voltage = float log = Message(imei, timestamp, log_message, voltage) protocol = Protocol(log, ...) protocol.parse(data)
Com isso como uma meta, eu criei a primeira versão de uma implementação de protocolo. Ela se parecia com a versão alvo, mas sofreu com uma abundância de repetição:
#protocol.py LOG = 0x023 ALIVE = 0x021 message = Token('message', 'String', 'X') timestamp = Token('timestamp', 'long', 'q') signal = Token('signal', 'short', 'h') voltage = Token('voltage', 'short', 'h') msg_log = Message('LOG', LOG, timestamp, signal, voltage) msg_alive = Message('ALIVE', ALIVE, timestamp) protocol = Protocol(version=1.0, messages=[msg_log,msg_alive]) #usage from protocol import protocol parsed_data = protocol.parse(data) open('Protocol.java’,'w').write(protocol.java_protocol())
A implementação em torno disso é simples, a classe Token sabe como analisar uma parte de uma mensagem, a classe Message sabe quais Tokens usar (e em que ordem), e a classe Protocol seleciona a instância de Mensagem correta utilizando um mapeamento de marcadores de bytes nas instâncias de Mensagens.
No entanto, nenhum apoio é dado para lidar com várias versões do protocolo e a quantidade de duplicação de nomes torna isso muito complexo - por isso me propus a criar uma versão melhor.
Algumas coisas complicaram a criação de uma versão melhor. O pior problema de todos provou ser somente eu. Nessa época, eu tinha usado Python por alguns anos, e comecei a me interessar pelas ferramentas mais sofisticadas disponíveis. Eu tinha acabado de aprender sozinho sobre metaclasses, e pensei que fossem um aplicativo engenhoso para orientação a objetos - e porque tinha encontrado um martelo novinho em folha, eu estava louco para encontrar um prego.
Infelizmente, eu não tinha uma necessidade urgente para a utilização de metaclasses, então eu inventei uma - eu queria evitar algumas tarefas na especificação de protocolo, então usei metaclasses para arrancar o método (constructor) init e substituí-lo por uma versão que registrou a instância em um mapa global e depois chamei o método init original. Isso foi errado em pelo menos três formas - uma vez que não era genérico, poderia ter sido feito diretamente no método init, se tivesse sido geral, teria sido um trabalho para um decorator, e é realmente uma ótima maneira de ofuscar o código:
__MSG_MAPPING__ = {} def msg_initizer(cls, old_init): def new_init(self, name, marker, *args): __MSG_MAPPING__get(cls, {})[name] = self __MSG_MAPPING__[cls][struct.pack("!B", marker)] = self old_init(self, name, marker, *args) return new_init class RegisterMeta(type): def __new__(cls, name, bases, attrs): attrs['__init__'] = msg_initizer(cls, attrs['__init__']) return super(RegisterMeta, cls).__new__(cls, name, bases, attrs) class Message(object): __metaclass__ = RegisterMeta
A propósito, esse é o tipo de código do qual eu não tenho orgulho. A pior parte? Eu nem sequer removi a duplicação, embora a tenha diminuído um pouco - e o registro global de mensagens durante o carregamento de um protocolo realmente bagunçou qualquer tentativa de suporte para versão múltipla. Esse não foi o único problema; eu também extrapolei e queria suporte especificando a sintaxe do protocolo, usando uma classe Flow que definiu a ordenação coletiva das mensagens. Isso poderia ter sido uma boa ideia se tivéssemos realmente tido esses requisitos em nossos protocolos; já que eles são "autenticar, fazer qualquer coisa", adicionar suporte para isso apenas expandiu a base de código e fez a especificação de protocolo mais complexa para pouquíssimo ganho (especialmente pelo fato de que autenticamos de diferentes maneiras, dependendo do cliente). Adicionando insulto à injúria, isso é ainda mais detalhado do que a primeira tentativa.
#In protocol.py imei = Token('imei', 'long') message = Token('message', 'String') timestamp = Token('timestamp', 'long') signal = Token('signal', 'short') voltage = Token('voltage', 'short') auth = Token('auth', 'String') Markers({'LOG': 0x023, 'ALIVE': 0x021, 'AUTH': 0x028}) Message('LOG', imei, timestamp, signal, voltage) Message('ALIVE', imei, timestamp) Message('AUTH', imei, timestamp, auth) Flow([('AUTH'), ('LOG', 'ALIVE')]) #Usage protocol = Protocol(version=2.0) parsed_data = protocol.parse(data) #error if not auth parsed
Toda essa tentativa se tornou um exemplo de alerta - ela mostra o perigo de encontrar novas tecnologias interessantes e aplicá-las antes de compreendê-las totalmente, e mostra o perigo de engenharia e fluência de recurso. Felizmente, quando eu dei uma boa olhada no que havia criado, até mesmo no que eu fiz uns anos atrás, pude ver que isso era uma abominação, que depois foi retirada calmamente e deixada de lado, sem nem chegar aos testes de integração.
Finalmente, e progressivamente, decidi aplicar uma quantidade cuidadosa de magia de biblioteca padrão para fazer as especificações mais concisas e remover as coisas de que nós não precisávamos. Isso fez com que a especificação parecesse com algo assim:
#In protocol_4.2.py: #Tokens t('message', string) t('timestamp', i64) t('signal', i16) t('voltage', i16) #Messages LOG = ('A log message containing some debug info', 0x023, timestamp, message, signal, voltage) ALIVE = ('A message to signal that the terminal alive', 0x021, timestamp) #Usage protocols = load_protocols('protocols') parsed = protocols[4.2].parse(data) protocols[4.2].write_java() #Writes to Protocol42.java
Ao mesmo tempo, era ainda resumida, mas essa versão realmente não teve sucesso, e a versão de produção é muito semelhante a esta. A duplicação de nomes é evitada usando duas técnicas diferentes - os tokens são definidos ao chamar um método t que cria a instância Token e a introduz de volta para a chamada namespace usando o nome fornecido:
#In types.py from inspect import currentframe def t(name, data_type): """Inserts name = (name, data_type) in locals() of calling scope""" currentframe().f_back.f_locals[name] = (name, data_type)
Para alguns, isso pode parecer uma blasfêmia, mas considere que a aplicação é extremamente simples no conceito, ela faz o trabalho ser feito e é fácil de explicar. Outra mudança é que as mensagens são criadas somente usando inspect para extrair membros do módulo que se parece com mensagens - todos os nomes em letras maiúsculas e uma tupla. Vale notar que pode ser que tenha havido inicialmente um erro de manipulação, mas eu o removi para fazer a análise de falha, em vez de aceitar uma especificação que poderia ou não ter contido erros.
Finalmente, a documentação java de origem e de html é criada percorrendo a instância de protocolo, e alimentando as informações em modelos simples - os experimentos foram feitos usando linguagem de programação literal usando REST para criar documentação, mas que no fim tendem a ofuscar, e não o contrário. Esse pode ser um efeito de implementação ingênua, ou esse problema não se presta bem à programação literal, mas, de qualquer forma, não valeu a pena nesse caso.
Há um trabalho e uma versão ligeiramente generalizada disponíveis no bitbucket, sobre os quais você poderia gostar de saber mais (e mais detalhes sobre a magia usada de Python).
⁂
Texto original disponível em http://blaag.haard.se/Protocol-specifications-written-in-Python/
|
Desde que, em retrospecto, fiz a escolha errada quando reduzi um
curso de Python para quatro horas e baguncei com o exercício do
decorator, prometi aos participantes do curso que escreveria um artigo
sobre closures e decorators para explicá-los melhor - esta é a minha
tentativa de o fazer.
Funções também são objetos. Na verdade, em Python são objetos de
primeira classe - ou seja, eles podem ser tratados como qualquer outro
objeto sem restrições especiais. Isso nos dá algumas opções
interessantes, e eu vou tentar abordá-las sob todos os aspectos.
Um caso muito básico do fato de que funções são objetos é usá-las
como se fosse um ponteiro de função em C; passá-la para outra função que
irá utilizá-la. Para ilustrar isso, vamos dar uma olhada na
implementação de uma função de repetição - ou seja, uma função que
aceita outra como argumento em junto com um número, e depois chama a
função passada um específico número de vezes:
>>>
>>> def greeter():
... print("Hello")
...
>>>
>>> def repeat(fn, times):
... for i in range(times):
... fn()
...
>>> repeat(greeter, 3)
Hello
Hello
Hello
>>>
Esse padrão é utilizado em um grande número de maneiras - passando
uma função de comparação para um algoritmo de classificação, passando
uma função de decodificação para um parser e, em geral, especializando o
comportamento de uma função, ou passando partes específicas de um
trabalho a ser feito para uma função que abstrai o fluxo de trabalho
(por exemplo, sort () sabe como classificar listas, compare () sabe como comparar elementos).
As funções também podem ser declaradas no corpo de outra função, o
que nos dá outra ferramenta importante. No caso mais básico, isso pode
ser usado para "esconder" funções utilitárias no âmbito da função que as
utiliza:
>>> def print_integers(values):
... def is_integer(value):
... try:
... return value == int(value)
... except:
... return False
... for v in values:
... if is_integer(v):
... print(v)
...
>>> print_integers([1,2,3,"4", "parrot", 3.14])
1
2
3
Isso pode ser útil, mas dificilmente é em si uma ferramenta muito
poderosa. Comparado com o fato de que as funções podem ser passadas como
argumentos, podemos adicionar comportamentos a funções depois que elas
são construídas, ao empacotá-las em outra função. Um exemplo simples
seria adicionar uma saída de rastreamento para uma função:
>>> def print_call(fn):
... def fn_wrap(*args, **kwargs):
... print("Calling %s" % (fn.func_name))
... return fn(*args, **kwargs)
... return fn_wrap
...
>>> greeter = print_call(greeter)
>>> repeat(greeter, 3)
Calling fn_wrap
Hello
Calling fn_wrap
Hello
Calling fn_wrap
Hello
>>>
>>> greeter.func_name
'fn_wrap'
Como você pode ver, podemos substituir a função greeter com uma nova função que usa print
para registrar a chamada, e depois chama a função original. Como se vê
nas duas últimas linhas do exemplo, o nome da função reflete que ela foi
substituída, o que pode ou não ser o que nós quisemos. Se quisermos
empacotar uma função, mantendo o nome original, podemos fazê-lo
adicionando uma linha à nossa função print_call:
>>> def print_call(fn):
... def fn_wrap(*args, **kwargs):
... print("Calling %s" % (fn.func_name))
... return fn(*args, **kwargs)
... fn_wrap.func_name = fn.func_name
... return fn_wrap
Uma vez que este está se transformando em um artigo muito longo, vou
parar por aqui e voltar em breve com a parte dois, na qual veremos
closures, partials, e (finalmente) decorators.
Até lá, se tudo isso for novo para você, use print_call como
base para criar uma função que irá imprimir o nome da função e
argumentos passados antes de chamar a função empacotada, e o nome da
função e o valor de retorno antes de retornar.
⁂
Texto original disponível em http://blaag.haard.se/Python-Closures-and-Decorators--Pt--1/
|
Na Parte 01,
nós vimos o envio de funções como argumentos para outras funções, em
funções agrupadas e, finalmente, empacotar uma função em outra. Vamos
começar esta parte, dando um exemplo de implementação para o exercício
que eu dei na Parte 01:
System Message: ERROR/3 (<string>, line 11)
Unknown directive type "code".
.. code:: python
>>> def print_call(fn):
... def fn_wrap(*args, **kwargs):
... print("Calling %s with arguments: \n\targs: %s\n\tkwargs:%s" % (
... fn.__name__, args, kwargs))
... retval = fn(*args, **kwargs)
... print("%s returning '%s'" % (fn.func_name, retval))
... return retval
... fn_wrap.func_name = fn.func_name
... return fn_wrap
...
>>> def greeter(greeting, what='world'):
... return "%s %s!" % (greeting, what)
...
>>> greeter = print_call(greeter)
>>> greeter("Hi")
Calling greeter with arguments:
args: ('Hi',)
kwargs:{}
greeter returning 'Hi world!'
'Hi world!'
>>> greeter("Hi", what="Python")
Calling greeter with arguments:
args: ('Hi',)
kwargs:{'what': 'Python'}
greeter returning 'Hi Python!'
'Hi Python!'
>>>
Então, isso é no mínimo ligeiramente útil, mas vai ficar melhor! Você pode ou não ter ouvido falar de closures, e você pode ter ouvido de um grande número de definições do que uma closure
é - eu não vou entrar em picuinhas, mas apenas dizer que uma closure é
um bloco de código (por exemplo, uma função), que capta variáveis (ou
faz um close over) não-locais (livre). Se tudo isso é meio sem
lógica ou papo furado para você, provavelmente você está precisando de
uma reciclagem, mas não tenha medo - eu vou mostrar, através de exemplo,
e o conceito é bastante fácil de entender: uma função pode fazer
referência a variáveis que são definidas no escopo de fechamento da
função.
Por exemplo, dê uma olhada neste código:
System Message: ERROR/3 (<string>, line 54)
Unknown directive type "code".
.. code:: python
>>> a = 0
>>> def get_a():
... return a
...
>>> get_a()
0
>>> a = 3
>>> get_a()
3
Como você pode ver, a função get_a pode obter o valor de a, e será capaz de ler o valor atualizado. No entanto, existe uma limitação - uma variável capturada não pode ser escrita para:
System Message: ERROR/3 (<string>, line 70)
Unknown directive type "code".
.. code:: python
>>> def set_a(val):
... a = val
...
>>> set_a(4)
>>> a
3
O que aconteceu aqui? Desde que uma closure não pode escrever todas as variáveis capturadas, a = val realmente escreve para uma variável local a que acompanha o nível de módulo a
que queríamos escrever. Para contornar essa limitação (o que pode ou
não ser uma boa ideia), podemos usar um tipo de recipiente:
System Message: ERROR/3 (<string>, line 85)
Unknown directive type "code".
.. code:: python
>>> class A(object): pass
...
>>> a = A()
>>> a.value = 1
>>> def set_a(val):
... a.value = val
...
>>> a.value
1
>>> set_a(5)
>>> a.value
5
Assim, com o conhecimento de que uma função captura variáveis a
partir de seu escopo de fechamento, finalmente estamos aproximando de
algo interessante, e vamos começar pela implementação de um partial. Um partial
é uma instância de uma função na qual você já preencheu alguns ou todos
os argumentos; digamos, por exemplo, que você tem uma sessão com nome
de usuário e senha armazenada, e uma função que consulta alguma camada
de backend que leva argumentos diferentes, mas sempre requer
credenciais. Em vez de passar as credenciais manualmente toda vez,
podemos usar um partial para pré-preencher os valores:
System Message: ERROR/3 (<string>, line 110)
Unknown directive type "code".
.. code:: python
>>>
... def get_stuff(user, pw, stuff_id):
... """Here we would presumably fetch data using the supplied
... credentials and id"""
... print("get_stuff called with user: %s, pw: %s, stuff_id: %s" % (
... user, pw, stuff_id))
>>> def partial(fn, *args, **kwargs):
... def fn_part(*fn_args, **fn_kwargs):
... kwargs.update(fn_kwargs)
... return fn(*args + fn_args, **kwargs)
... return fn_part
...
>>> my_stuff = partial(get_stuff, 'myuser', 'mypwd')
>>> my_stuff(3)
get_stuff called with user: myuser, pw: mypwd, stuff_id: 3
>>> my_stuff(67)
get_stuff called with user: myuser, pw: mypwd, stuff_id: 67
Partials podem ser utilizados em vários lugares para remover
duplicação de código em que uma função é chamada em lugares diferentes
com os mesmos, ou quase os mesmos, argumentos. Claro, você não precisa
implementá-lo, basta fazer from functools import partial.
Finalmente, vamos dar uma olhada em função decorators. Uma função
decorator é (pode ser implementada como) uma função que recebe uma
função como parâmetro e retorna uma nova. Soa familiar? Deveria, porque
já implementamos um working decorator: a nossa função print_call está pronta para ser usada como está:
System Message: ERROR/3 (<string>, line 142)
Unknown directive type "code".
.. code:: python
>>> @print_call
... def will_be_logged(arg):
... return arg*5
...
>>> will_be_logged("!")
Calling will_be_logged with arguments:
args: ('!',)
kwargs:{}
will_be_logged returning '!!!!!'
'!!!!!'
Usar a notação @ é simplesmente uma abreviação conveniente para fazer:
System Message: ERROR/3 (<string>, line 157)
Unknown directive type "code".
.. code:: python
>>> def will_be_logged(arg):
... return arg*5
...
>>> will_be_logged = print_call(will_be_logged)
Mas e se quisermos ser capazes de parametrizar o decorator? Nesse
caso, a função utilizada como um decorator receberá os argumentos, e
deverá retornar uma função que envolve a função decorated:
System Message: ERROR/3 (<string>, line 169)
Unknown directive type "code".
.. code:: python
>>> def require(role):
... def wrapper(fn):
... def new_fn(*args, **kwargs):
... if not role in kwargs.get('roles', []):
... print("%s not in %s" % (role, kwargs.get('roles', [])))
... raise Exception("Unauthorized")
... return fn(*args, **kwargs)
... return new_fn
... return wrapper
...
>>> @require('admin')
... def get_users(**kwargs):
... return ('Alice', 'Bob')
...
>>> get_users()
admin not in []
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in new_fn
Exception: Unauthorized
>>> get_users(roles=['user', 'editor'])
admin not in ['user', 'editor']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in new_fn
Exception: Unauthorized
>>> get_users(roles=['user', 'admin'])
('Alice', 'Bob')
E aí está. Agora você está pronto para escrever decorators, e talvez
usá-los para escrever aspectos orientados a Python; acrescentar @cache,
@trace, @throtlle é bem trivial (e antes que você adicione @cache, faça a
verificação functools mais uma vez se você estiver usando Python 3!).
⁂
Texto original de Fredrik Håård, disponível em http://blaag.haard.se/Python-Closures-and-Decorators--Pt--2/
|
Creio que Python é importante para o desenvolvimento de software.
Embora existam linguagens mais poderosas (por exemplo, Lisp), mais
rápidas (por exemplo C), mais usadas (como Java) e mais estranhas (por
exemplo, Haskell), o Python obtém um monte de coisas diferentes de forma
correta, e em uma combinação que nenhuma outra linguagem que eu conheço
tem feito até agora.
Ele reconhece que você vai gastar muito mais tempo lendo código do
que escrevendo-o e se concentra em orientar os desenvolvedores para
escreverem um código legível. É possível escrever um código obfuscated
em Python, mas a maneira mais fácil de escrever o código (assumindo que
você conheça Python) é quase sempre uma forma que seja razoavelmente
concisa, e mais importante: um código que sinaliza claramente a
intenção. Se você souber Python, você pode trabalhar com quase qualquer
Python sem muita dificuldade. Mesmo as bibliotecas que adicionam
funcionalidades "mágicas" podem ser escritas em Python perfeitamente
legível (compare isso com a compreensão da implementação de um
framework, como o Spring em Java).
O Python também reconhece que a velocidade de desenvolvimento é
importante. Um código legível e conciso é parte disso, e também é o
acesso a construções poderosas que evitam a repetição tediosa de código.
Manutenibilidade também está vinculado a isso - LoC pode ser tudo menos
uma métrica inútil, mas diz algo sobre a quantidade de código que você
tem que verificar, ler e/ou entender para solucionar problemas ou
ajustar comportamentos.
Essa velocidade de desenvolvimento, a facilidade com que um
programador de outras linguagens pode ter habilidades básicas em Python e
a enorme biblioteca padrão são as chaves para outra área onde o Python
se destaca - toolmaking. Qualquer grande projeto terá tarefas para
automatizar, e automatizá-las em Python é, em minha experiência, mais
rápido do que usar linguagens mais populares - na verdade, foi assim que
comecei com Python, criando uma ferramenta para automatizar a
configuração de Rational Purify para um projeto onde antes era uma
tarefa que nunca havia sido executada (e vazamentos de memória não eram
corrigidos). Eu já havia criado ferramentas para extrair informações de
sistemas de registro e apresentá-las de uma forma útil para a equipe,
ferramentas para verificar poms em um projeto Maven, integração Trac,
ferramentas de monitoramento personalizadas... e muito mais. Todas essas
ferramentas foram rápidas de implementar, economizaram muito tempo, e
muitas deles mais tarde foram corrigidas e atualizadas por pessoas sem
experiência em Python - sem quebrar.
Aquela construção de ferramentas personalizadas são dicas fáceis em
outra força - a construção e a manutenção de software personalizado é
fácil, ponto final. É por isso que, enquanto o enorme framework Django pode ser o mais famoso framework web Python, há também uma série de micro e pequenos frameworks de sucesso.
Ao trabalhar em uma poderosa linguagem de programação com uma grande
variedade de bibliotecas padrão e de terceiros, muitas vezes você não
precisa aceitar os trade-offs que são necessários ao usar qualquer
estrutura framework off-the-shelf. Isso significa que você pode
construir exatamente o software que seus clientes querem, em vez de
dizer-lhes que "esta é a forma como é feito, desculpe". Para mim, essa é
uma diferença enorme. Eu me sinto envergonhado quando tenho que dizer a
um cliente que não, desculpe, isso parece uma simples exigência, mas o
sistema que usamos torna isso impossível ou caro de implementar de forma
proibitiva. Sempre que isso acontece, você falhou.
Escrever software que se encaixa ao modelo do cliente em vez de a um
framework é importante, e eu sinto que muitos desenvolvedores hoje
perderam de vista esse fato simples. Vários programadores agora passam
mais tempo sendo configuradores de frameworks e inventores de desculpas
para suas falhas, em vez de programarem de verdade.
Finalmente, se você for um(a) chefe ou gerente geral, usar o Python
tem um benefício final - programadores de Python são menos frustrados*, o
que os torna mais felizes, e ainda mais produtivos!
(* pode não ser verdade quando estiverem instalando extensões de código-fonte distribuídas C no Windows)
⁂
Texto original de Fredrik Håård, disponível em http://blaag.haard.se/Why-Python-is-important-for-you/#disqus_thread
|
Atualmente, utilizar o lxml para escrever XML em Python está cada
vez mais comum. Não existem grandes dificuldades para entender o lxml,
mas se o seu uso não for bem planejado, você pode terminar tendo uma
replicação de código enorme.
Foi em uma conversa com um amigo que escrevi um wrapper para remover a
replicação de linhas que existiam na geração do XML dele. O código está
abaixo para ser utilizado como bem entender.
Nota: Existem alguns problemas ainda e o principal é
que estou utilizando um DICT para fazer a passagem dos parâmetros para a
geração do XML. O Dict não mantem a ordem de entrada dos dados, então a
saída do seu XML ficará um pouco bagunçada.
''' **************************************************************************************** Building a XML Tree as Easy as ABC in Python
This implementation requires lxml
Created by Igor Hercowitz 2012-07-04 ****************************************************************************************** '''
from lxml import etree
class XmlBuilder: def __init__(self, root_name='root'): self.root = etree.Element(root_name) self.pretty_print = True
def set_prettyprint(self, choice): if isinstance(choice, bool): self.pretty_print = choice else: raise TypeError('This value should be a boolean')
def xmlbuilder(self, parent=None, unique=True, use_attributes=False, **kwargs): use_attributes = False
if parent is None: parent = self.root
if isinstance(parent, etree._Element): node_parent = parent else: node_parent = etree.Element(parent)
if 'attributes' in kwargs: for k, v in kwargs['attributes'].items(): kwargs[k] = v use_attributes = True del kwargs['attributes']
for k, v in kwargs.items(): node = None if unique and node_parent.find(k) is not None: node = node_parent.find(k)
if isinstance(v, dict): if node is None: node = etree.SubElement(node_parent, k)
self.xmlbuilder(node, unique=False, use_attributes=use_attributes, **v)
else:
if use_attributes: node = node_parent.set(k, v) else: node = etree.SubElement(node_parent, k) node.text = v use_attributes = False return node_parent
def __str__(self): return etree.tostring(self.root, pretty_print=self.pretty_print)
if __name__ == "__main__": x = XmlBuilder('receitas') x.xmlbuilder(receita={ 'titulo': 'Bolo de Chocolate', 'ingredientes': {'attributes': { 'manteiga': '1 xicara', 'acucar': '2 xicaras', 'ovo': '4', 'farinha': '2 xicaras e 1/2 de farinha de trigo com fermento', 'leite': '1 copo'}}, 'preparo': 'Misture os ingredientes, bote numa forma untada com manteiga e trigo.'\ 'Depois coloque no forno. Por ultimo, faca a cobertura.' }) print x
O código está disponível aqui: https://gist.github.com/3425176
Abraços a todos! *** Artigo de Igor Hercowitz
|
Arakoon, nosso
armazenamento de chave-valor desenvolvido internamente, é um dos nossos
projetos mais emblemáticos. Uma vez que um servidor não é de muita
utilidade caso não permita que os clientes se comuniquem com ele, nós
desenvolvemos também algumas bibliotecas de clientes, incluindo um
cliente OCaml, C, PHP e Python.
Foi desenvolvido um cliente alternativo próximo ao cliente Python mantido dentro do repositório principal do Arakoon, (fonte). Um dos objetivos desse cliente alternativo era apoiar o Twisted, um framework de rede assíncrona.
Neste artigo, vou apresentar o método utilizado para conseguir esse
resultado. Ele mantém uma clara separação entre o protocolo (os bytes
passando sobre o fio) e o transporte (o próprio fio). Os transportes
síncronos e assíncronos pode ser implementados podem ser implementados, e
novos comandos de request/response podem ser adicionados facilmente, em
um único lugar no código fonte.
Ao longo deste artigo, vamos escrever um cliente para esse servidor,
que implementa um protocolo semelhante ao protocolo Arakoon:
import socket
import struct
import threading
HOST = 'localhost'
PORT = 8080
COMMAND_STRUCT = struct.Struct('<I')
SUCCESS_CODE = struct.pack('<I', 0)
ERROR_CODE = struct.pack('<I', 1)
ERROR_MESSAGE = 'Invalid request'
ERROR_MESSAGE_DATA = struct.pack('<I%ds' % len(ERROR_MESSAGE),
len(ERROR_MESSAGE), ERROR_MESSAGE)
LEN_STRUCT = struct.Struct('<I')
def handle(conn):
while True:
command_data = ''
while len(command_data) < COMMAND_STRUCT.size:
data = conn.recv(COMMAND_STRUCT.size - len(command_data))
if not data:
return
command_data += data
command, = COMMAND_STRUCT.unpack(command_data)
if command == 1:
len_data = ''
while len(len_data) < LEN_STRUCT.size:
data = conn.recv(LEN_STRUCT.size - len(len_data))
len_data += data
len_, = LEN_STRUCT.unpack(len_data)
data = ''
while len(data) < len_:
data += conn.recv(len_ - len(data))
conn.send(SUCCESS_CODE)
conn.send(struct.pack('<L%ds' % len(data), len(data), data[::-1]))
else:
conn.send(ERROR_CODE)
conn.send(ERROR_MESSAGE_DATA)
def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((HOST, PORT))
sock.listen(1)
while True:
conn, addr = sock.accept()
print 'Connect: %r' % (addr, )
threading.Thread(target=lambda: handle(conn)).start()
if __name__ == '__main__':
main()
Cada mensagem enviada por um cliente começa com um número inteiro de
32 bits, o identificador de comando. Atualmente, apenas um comando,
‘reverse’ com ID 1, está implementado. Isso leva uma única string como
argumento. As strings são codificadas em duas partes: primeiro, um
número inteiro de 32 bits que contém o tamanho da string, seguido pelos
dados reais da string como caracteres.
Quando o servidor recebe um comando, ele envia um inteiro de 32 bits denotando sucesso (0×00) ou fracasso (0×01). O comando ‘reverse’
simplesmente retorna uma string de entrada, invertida, usando a
codificação da mesma string como descrito anteriormente. Uma vez que
esse ciclo estiver completo, um novo comando pode ser enviado por um
cliente.
Agora, no lado do cliente. Vamos precisar de algumas importações:
import socket
import struct
import logging
import functools
import collections
from twisted.python import log
from twisted.internet import defer, protocol, reactor
from twisted.protocols import basic, stateful
import utils
Os módulos ‘utils’ contêm alguns helpers, e estão contidos no repositório Pyrakoon.
Nós precisamos de uma maneira de comunicação entre a camada de
protocolo e a camada de transporte. Só o lado do protocolo sabe quantos
dados são esperados, e apenas o lado de transporte pode fornecer esses
bytes, mesmo que não possa ter a quantidade necessária de dados
disponíveis imediatamente, e deseje obter a execução (no caso de rede
assíncrona). Para tornar o desenvolvimento mais fácil dentro dessas
limitações, as co-rotinas são utilizadas em todo o sistema para
encapsular o estado intermediário sempre que possível, mantendo a API
simples.
Veja como a API funciona: uma ação única de protocolo (por exemplo, a
leitura da resposta de um pedido) é feita por uma co-rotina, o que
rende um ou mais objetos “Request”, que encapsulam o número de bytes que
devem ser fornecidos à co-rotina para o protocolo ser capaz de
construir um valor, ou um objeto “Result” que encapsula o valor final.
Sempre que a camada superior receber um objeto “Request”, ela deve ler o
número solicitado de bytes do transporte, depois envia os dados para a
co-rotina, o que vai render outro “Request”, ou, finalmente, um
‘Response’.
As definições são muito simples:
class Request(object):
def __init__(self, count):
self.count = count
class Result(object):
def __init__(self, value):
self.value = value
Em seguida, o protocolo utiliza alguns tipos diferentes de valores:
inteiros de 32 bits (sem assinatura) e strings. Este último utiliza o
primeiro em sua codificação interna.
Cada tipo tem três métodos: ‘check’, ‘serialize’ e ‘receive’.
‘check’ realiza a validação de entrada para valores de determinado
tipo (verificação de tipo, verificação de limites,…). ‘serialize’ é um
gerador que produz os dados codificados para um determinado valor.
“receive” é uma co-rotina que produz objetos ‘Request’ e ‘Result’ para
receber um valor do tipo.
Para os tipos básicos (por exemplo, números inteiros), os valores
embalados utilizando o módulo ‘struct’ módulo podem ser usados,
portanto, as implementações de base fornecem a funcionalidade necessária
para isso. Aqui está o tipo básico, e as implementações para ambos inteiros não-assinados de 32 bits e strings:
class Type(object):
PACKER = None
def check(self, value):
raise NotImplementedError
def serialize(self, value):
if not self.PACKER:
raise NotImplementedError
yield self.PACKER.pack(value)
def receive(self):
if not self.PACKER:
raise NotImplementedError
data = yield Request(self.PACKER.size)
result, = self.PACKER.unpack(data)
yield Result(result)
class UnsignedInt32(Type):
PACKER = struct.Struct('<I')
MAX_INT = (2 ** 32) - 1
def check(self, value):
if not isinstance(value, (int, long)):
raise TypeError
if value < 0:
raise ValueError('Unsigned integer expected')
if value > self.MAX_INT:
raise ValueError('Integer overflow')
UNSIGNED_INT32 = UnsignedInt32()
class String(Type):
def check(self, value):
if not isinstance(value, str):
raise TypeError
def serialize(self, value):
length = len(value)
for bytes_ in UNSIGNED_INT32.serialize(length):
yield bytes_
yield struct.pack('<%ds' % length, value)
def receive(self):
length_receiver = UNSIGNED_INT32.receive()
request = length_receiver.next()
while isinstance(request, Request):
value = yield request
request = length_receiver.send(value)
if not isinstance(request, Result):
raise TypeError
length = request.value
if length == 0:
result = ''
else:
data = yield Request(length)
result, = struct.unpack('<%ds' % length, data)
yield Result(result)
STRING = String()
Agora que os tipos básicos estão definidos, podemos descrever as mensagens de solicitação/resposta transferidas entre o cliente e o servidor.
Cada mensagem possui uma tag (seu identificador), zero ou mais
argumentos, e um tipo de retorno. As mensagens podem ser serializadas e
recebidas de forma semelhante aos métodos correspondentes em ‘Type’. A
maioria, se não todos as ligações necessárias, pode ser escondida dentro
da classe ‘Message’, então as classes específicas de comando podem ser
muito curtas e simples. Isto torna mais fácil adicionar novos comandos
de protocolo para o cliente também!
Aqui está a definição de ‘Message’, bem como a implementação do nosso
comando ‘Reverse’. Observe como a definição deste último é simples.
class Message(object):
TAG = None
ARGS = None
RETURN_TYPE = None
def serialize(self):
for bytes_ in UNSIGNED_INT32.serialize(self.TAG):
yield bytes_
for arg in self.ARGS:
name, type_ = arg
for bytes_ in type_.serialize(getattr(self, name)):
yield bytes_
def receive(self):
code_receiver = UNSIGNED_INT32.receive()
request = code_receiver.next()
while isinstance(request, Request):
value = yield request
request = code_receiver.send(value)
if not isinstance(request, Result):
yield TypeError
code = request.value
if code == 0x00:
result_receiver = self.RETURN_TYPE.receive()
else:
result_receiver = STRING.receive()
request = result_receiver.next()
while isinstance(request, Request):
value = yield request
request = result_receiver.send(value)
if not isinstance(request, Result):
raise TypeError
result = request.value
if code == 0x00:
yield Result(result)
else:
raise Exception('Error %d: %s' % (code, result))
class Reverse(Message):
TAG = 0x01
ARGS = ('text', STRING),
RETURN_TYPE = STRING
def __init__(self, text):
super(Reverse, self).__init__()
self.text = text
O próximo passo, é escrever a classe base para todas as
implementações de clientes reais. Algumas construções de método dinâmico
são usadas on the go, com base nas seguintes duas funções utilitárias:
def validate_types(specs, args):
for spec, arg in zip(specs, args):
name, type_ = spec[:2]
try:
type_.check(arg)
except TypeError:
raise TypeError('Invalid type of argument "%s"' % name)
except ValueError:
raise ValueError('Invalid value of argument "%s"' % name)
def call(message_type):
def wrapper(fun):
argspec = ['self']
for arg in message_type.ARGS:
argspec.append(arg[0])
@utils.update_argspec(*argspec)
@functools.wraps(fun)
def wrapped(**kwargs):
self = kwargs['self']
if not self.connected:
raise RuntimeError('Not connected')
args = tuple(kwargs[arg[0]] for arg in message_type.ARGS)
validate_types(message_type.ARGS, args)
message = message_type(*args)
return self._process(message)
return wrapped
return wrapper
A classe base ‘Cliente’ se torna extremamente simples. Sempre que um
novo comando é adicionado ao protocolo, acaba ficando óbvio adicioná-lo a
essa classe (como é feito para a chamada ‘reverse’).
class Client(object):
connected = False
@call(Reverse)
def reverse(self):
assert False
def _process(self, message):
raise NotImplementedError
Isso é tudo que existe. O que resta são as implementações do transporte específico do cliente.
Começar com um cliente de socket síncrono é o mais fácil. Tudo o que
precisamos é de um método ‘connect’ para configurar um socket e
implementar o ‘Client._process’ para lidar com a interação entre o
protocolo e o transporte. A implementação é bem simples e direta:
class SyncClient(Client):
def __init__(self):
self._socket = None
def connect(self, addr, port):
self._socket = socket.create_connection((addr, port))
@property
def connected(self):
return self._socket is not None
def _process(self, message):
try:
for part in message.serialize():
self._socket.sendall(part)
receiver = message.receive()
request = receiver.next()
while isinstance(request, Request):
data = ''
while len(data) < request.count:
d = self._socket.recv(request.count - len(data))
if not d:
raise Exception
data += d
request = receiver.send(data)
if not isinstance(request, Result):
raise TypeError
utils.kill_coroutine(receiver, logging.exception)
return request.value
except Exception:
try:
self._socket.close()
finally:
self._socket = None
raise
O protocolo Twisted é um pouco mais complexo e não será abordado em
detalhes neste artigo. Se você alguma vez escreveu um protocolo Twisted
sozinho, deve ser fácil seguir em frente. A implementação piggy-backs em
‘twisted.protocol.stateful.StatefulProtocol’, o que simplifica muito.
class TwistedProtocol(Client, stateful.StatefulProtocol, basic._PauseableMixin):
_INITIAL_REQUEST_SIZE = UNSIGNED_INT32.PACKER.size
def __init__(self):
Client.__init__(self)
self._handlers = collections.deque()
self._currentHandler = None
self._connected = False
self._deferredLock = defer.DeferredLock()
def _process(self, message):
deferred = defer.Deferred()
self._handlers.append((message.receive(), deferred))
def process(_):
try:
for data in message.serialize():
self.transport.write(data)
finally:
self._deferredLock.release()
self._deferredLock.acquire().addCallback(process)
return deferred
def getInitialState(self):
self._currentHandler = None
return self._responseCodeReceived, self._INITIAL_REQUEST_SIZE
def _responseCodeReceived(self, data):
self._currentHandler = None
try:
self._currentHandler = handler = self._handlers.pop()
except IndexError:
log.msg('Request data received but no handler registered')
self.transport.loseConnection()
return None
request = handler[0].next()
if isinstance(request, Result):
return self._handleResult(request)
elif isinstance(request, Request):
if request.count != self._INITIAL_REQUEST_SIZE:
handler[1].errback(ValueError('Unexpected request count'))
self.transport.loseConnection()
return None
return self._handleRequest(data)
else:
log.err(TypeError,
'Received unknown type from message parsing coroutine')
handler[1].errback(TypeError)
self.transport.loseConnection()
return None
def _handleRequest(self, data):
if not self._currentHandler:
log.msg('Request data received but no handler registered')
self.transport.loseConnection()
return None
receiver, deferred = self._currentHandler
try:
request = receiver.send(data)
except Exception, exc: #pylint: disable-msg=W0703
log.err(exc, 'Exception raised by message receive loop')
deferred.errback(exc)
return self.getInitialState()
if isinstance(request, Result):
return self._handleResult(request)
elif isinstance(request, Request):
return self._handleRequest, request.count
else:
log.err(TypeError,
'Received unknown type from message parsing coroutine')
deferred.errback(TypeError)
self.transport.loseConnection()
return None
def _handleResult(self, result):
receiver, deferred = self._currentHandler
self._currentHandler = None
# To be on the safe side...
utils.kill_coroutine(receiver, lambda msg: log.err(None, msg))
deferred.callback(result.value)
return self.getInitialState()
def connectionLost(self, reason=protocol.connectionDone):
self._connected = False
self._cancelHandlers(reason)
return stateful.StatefulProtocol.connectionLost(self, reason)
def _cancelHandlers(self, reason):
while self._handlers:
receiver, deferred = self._handlers.popleft()
utils.kill_coroutine(receiver, lambda msg: log.err(None, msg))
deferred.errback(reason)
É isso aí! Finalmente, podemos testar os nossos clientes contra o servidor:
HOST = 'localhost'
PORT = 8080
def test_sync():
client = SyncClient()
client.connect(HOST, PORT)
r = client.reverse('sync')
print 'sync =>', r
print r, '=>', client.reverse(r)
def test_twisted():
def create_client(host, port):
client = protocol.ClientCreator(reactor, TwistedProtocol)
return client.connectTCP(host, port)
@defer.inlineCallbacks
def run(proto):
result = yield proto.reverse('twisted')
print 'twisted =>', result
result2 = yield proto.reverse(result)
print result2, '=>', result
proto.transport.loseConnection()
deferred = create_client(HOST, PORT)
deferred.addCallback(run)
deferred.addBoth(lambda _: reactor.stop())
reactor.run()
if __name__ == '__main__':
test_sync()
test_twisted()
Se, por exemplo, um método ‘add’ fosse adicionado ao servidor, que
por sua vez retornasse a soma de dois dados de inteiros de 32 bits
não-assinados, poderíamos definir um novo comando tipo este:
class Add(Message):
TAG = 0x02
ARGS = ('a', UNSIGNED_INT32), ('b', UNSIGNED_INT32),
RETURN_TYPE = UNSIGNED_INT32
def __init__(self, a, b):
super(Add, self).__init__()
self.a = a
self.b = b
Em seguida, adicione-o à classe “Client” assim:
@call(Add)
def add(self):
assert False
Uma vez feito isso, o método ‘add(self, a, b)’ estará disponível em todos os clientes e irá funcionar como o esperado!
Este é apenas um exemplo básico. O protocolo Arakoon contém tipos mais complexos, incluindo tipos ‘option’ e listas’.
Consulte o código-fonte Pyrakoon para ver como isso é feito. Apenas
uma definição de tipo deve ser adicionada, vários comandos podem
usá-los, já que é fácil.
Utilizando a abordagem descrita neste artigo, torna-se fácil fornecer
implementações de clientes que usam vários backends diferentes
(bloqueio, sem bloqueio, sockets ou qualquer outra coisa como
transporte,…), e simplificar a adição de novos comandos/chamadas para
todos os clientes de uma só vez (mantendo-os em sincronia). Isso
simplifica demais a manutenção do cliente.
***
Texto original de Nicolas Trangez, disponível em http://blog.incubaid.com/2011/12/13/hybrid-sync-async-python-requestresponse-protocol-client-implementations/
|
Alguns dias atrás uma colega me perguntou qual era o sentido das
propriedades em Python. Afinal, escrever propriedades exige tanto quanto
escrever um getter, ou um setter e elas realmente não adicionam
qualquer funcionalidade – com exceção de não ter que escrever ‘()’ no
acesso.
Superficialmente, esse argumento se atém a comparar uma classe simples implementada com getters e setters, e com propriedades.
Implementadas com getters e setters
>>> class GetSet(object):
... x = 0
... def set_x(self, x):
... self.x = x
... def get_x(self):
... return self.x
...
>>> getset = GetSet()
>>> getset.set_x(3)
>>> getset.get_x()
3
Implementadas com as propriedades
>>> class Props(object):
... _x = 0
... @property
... def x(self):
... return self._x
... @x.setter
... def x(self, x):
... self._x = x
...
>>> props = Props()
>>> props.x = 5
>>> props.x
5
A questão
Na verdade, nós fomos de 196 a 208 chars neste caso de uso simples –
então, por que usamos propriedades em tudo? A resposta é que, neste caso
de uso, nós não usaríamos. Na verdade, poderíamos escrever assim:
>>> class MyClass(object):
... x = 0
...
>>> my = MyClass()
>>> my.x = 4
>>> my.x
4
Eu posso ouvir você gritando: “Mas, não há encapsulamento!”. O que
vamos fazer se precisarmos controlar o acesso à x, torná-lo somente
leitura ou fazer outra coisa com ele? Não vamos ter que refazer tudo
para os getters e setters que evitamos?
Não, nós temos apenas que mudar para a versão apropriada, adicionar o
que desejamos, e não se alterar a interface. A melhor coisa das
propriedades não é que elas substituem getters e setters, é que você não
precisa escrever o seu código à prova do futuro. Você pode começar
escrevendo a implementação mais simples que se possa imaginar, e se mais
tarde você precisar alterar a implementação, você ainda pode fazer isso
sem alterar a interface. Bom, hein?
***
Artigo original disponível em: http://blaag.haard.se/What-s-the-point-of-properties-in-Python/#disqus_thread
|
Durante a EuroPython 2012, depois do meu treinamento e das minhas
palestras, eu realmente precisava codar, então comecei a hackear em um
aplicativo “prático” da AST – tornando (algumas) constantes mais rápidas
do que as variáveis locais, em vez de mais lento por tê-las alinhado no
import.
Para fazer isso, eu uso um importador personalizado em meta_path
para interceptar a importação, e encontrar as atribuições que se
assemelham a constantes – isto é, qualquer variável de nível de módulo
em ALL_CAPS que seja uma simples string ou um número.
Eu armazeno os nomes e os valores, e depois simplesmente substituo
qualquer tentativa de carregar o nome com o valor armazenado. (Sim – o
que pode dar errado de várias formas terríveis!)
Para aqueles que não conhecem AST - Abstract Syntax Tree – é uma
representação em árvore da estrutura sintática de seu programa. O Python
permite que você olhe e modifique a AST antes de compilá-la, o que na
prática permite a você reescrever a própria estrutura de um programa
antes que ele seja executado. Para uma boa introdução para a AST, eu
recomendo What would you do with an AST, de Matthew Desmarais..
Para fazer isso, primeiro eu preciso interceptar a importação – o
importador em si não é muito interessante, mas o que ele faz é tentar
encontrar a fonte para qualquer módulo importado (se, por exemplo, um
arquivo .pyc for encontrado, ele simplesmente retira o ‘c’ e tenta
carregar o arquivo.).
Com a fonte encontrada e lida, o importador só chama o transformador, compila o resultado, e coloca-o no ssys.modules:
module = types.ModuleType(name)
inlined = transform(src)
code = compile(inlined, filename, 'exec')
sys.modules[name] = module
exec(code, module.__dict__)
O método transform analisa a fonte, cria o NodeTransformer que irá modificar a AST, e transfere a AST analisada para ele.
def transform(src):
"""Transforms the given source and return the AST"""
tree = ast.parse(src)
cm = ConstantMaker()
newtree = cm.visit(tree)
return newtree
Nosso NodeTransformer é igualmente simples e sobrecarrega o visit_Module (para encontrar as constantes) e o visit_Name (para substituir o uso dos nomes com o valor). O visit_Module
começa com a construção de uma lista de todas as atribuições no corpo
do módulo, e depois filtra as que cumprem os critérios de constantes:
eles devem ser números ou strings, e devem ser nomeados em ALL_CAPS.
Quaisquer atribuições são armazenadas em um mapa name->value, que
podem então ser utilizadas por visit_Name.
def visit_Module(self, node):
"""Find eglible variables to be inlined and store
the Name->value mapping in self._constants for later use"""
assigns = [x for x in node.body if
type(x) == _ast.Assign]
for assign in assigns:
if type(assign.value) in (_ast.Num, _ast.Str):
for name in assign.targets:
if RE_CONSTANT.match(name.id):
self._constants[name.id] = assign.value
return self.generic_visit(node)
A análise das atribuições deve ser feita antes da chamada para generic_visit, ou nós não vamos ter o mapeamento até depois de o resto do módulo já ter sido visitado. O mapeamento torna o trabalho de visit_Name extremamente simples:
def visit_Name(self, node):
"""If node.id is in self._constants, replace the
loading of the node with the actual value"""
return self._constants.get(node.id, node)
E isso é tudo o que precisamos fazer! Um benchmark simples
(simplista?) mostra que ela funciona conforme esperado para casos
simples – dada a seguinte fonte que mistura acesso constante com algum
“trabalho” de outros:
ONE = 1
TWO = "two"
THREE = 3
FOUR = 4.0
def costly(iterations):
tempstr = ""
obj = MyClass()
for i in range(iterations):
tempstr += ONE * TWO * THREE + str(i)
obj.change()
tempstr += str(obj.value)
eturn tempstr
class MyClass(object):
def __init__(self):
self.value = random.random()*THREE
def change(self):
self.value += random.random()*FOUR
…uma versão transformada executa de 15% a 20% mais rápido do que a
versão não transformada. Claro, meu primeiro benchmark, que fez apenas o
carregamento de constantes, foi zilhões de vezes mais rápido, mas
também não muito interessante.
Esta é, naturalmente, uma implementação muito limitada – uma
implementação “boa” teria que evitar escrever para as constantes (neste
momento, a escrita será silenciosamente ignorada pelo código no módulo
atual), a escrita in module para uma constante deve ser detectada, a
transformação deveria fazer um fallback para retornar a árvore
não-transformada, se falhar e talvez, apenas talvez, não será apenas uma
ideia muito boa mesmo.
Foi, no entanto, muito divertido escrever! O código está disponível no repositório do site – o timer que usei para os benchmarks foi escrito por @BaltoRouberol.
A próxima experiência será o alinhamento de funções, eu acho, ou talvez avaliação preguiçosa de parâmetros de função.
***
Texto original disponível em http://blaag.haard.se/Using-the-AST-to-hack-constants-into-Python/#disqus_thread
Fredrik Håård
|