tag:blogger.com,1999:blog-83123612338566688272024-03-12T21:40:25.714-04:00JungleCodersTecnologia vista por um amazônida maníaco por tecnologia.Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.comBlogger66125tag:blogger.com,1999:blog-8312361233856668827.post-70768675700103678022017-01-21T15:17:00.001-04:002019-02-17T12:34:47.514-04:00Migrando o servidor de chat para Python 3.6<a href="https://blog.nilo.pro.br/posts/2017-01-21-migrando-o-servidor-de-chat-para-python-3-6/">Leia no novo blog</a><br />
<br />
Na época do lançamento do Python 3.4, eu estava tão contente com a integração do Asyncio que escrevi um servidor de chat <a href="http://junglecoders.blogspot.be/2014/08/servidor-de-chat-com-websockets-e.html" target="_blank">aqui</a>. O tempo passou e novas versões do Python foram lançadas. Resolvi então migrar o servidor para Python 3.6.<br />
<br />
Uma das grandes mudanças que ocorreram no Python 3.5, foi o suporte a <b><i>async</i></b> e <i><b>await</b></i> para substituir <i><b>@asyncio.corroutine</b></i> e <i><b>yield from</b></i> respectivamente. Esta pequena mudança por si só já facilita em muito a leitura do código, que ficou mais leve. Mas uma das principais mudanças do Python 3.6 são as <i>f-strings</i> que facilitam a formação de mensagens.<br />
<br />
Primeiro, vamos preparar o ambiente. É preciso instalar o Python 3.6. Se você utiliza Windows, basta baixar o pacote no site da <a href="https://www.python.org/ftp/python/3.6.0/python-3.6.0.exe">Python.org</a>.<br />
<br />
<h3>
Ubuntu 16.10</h3>
Se você utiliza Ubuntu 16.10, ainda precisa baixar os fontes e compilar... mas seguindo a recomendação de amigos do Telegram, resolvi experimentar com o <a href="https://github.com/yyuu/pyenv-installer">pyenv</a>!<br />
<br />
<script src="https://gist.github.com/lskbr/b679b811c836b1c12375ec0321408418.js"></script> Para instalar no Ubuntu, baixe o <a href="https://gist.github.com/lskbr/b679b811c836b1c12375ec0321408418/raw/b3cb729ae5861d76d802a52b747a35a0aac886fe/install_python360.sh">install_python360.sh</a> e rode com:<br />
<pre>bash install_python360.sh</pre>
<br />
<br />
Como alguns pacotes precisam ser instalados no Ubuntu, ele vai usar sudo. Esteja pronto para digitar a senha. No meu caso, como uso docker (docker run -rm -t -i ubuntu:16.10 /bin/bash), rodei o script como root. Se você instalar no seu usuário, ele vai chamar o <b><i>sudo</i></b> quando necessário. Eu gravei um pequeno vídeo do que aconteceu na minha instalação:<br />
<br />
<script async="" id="asciicast-840ez0cfuebtccohhlx5cbb68" src="https://asciinema.org/a/840ez0cfuebtccohhlx5cbb68.js" type="text/javascript"></script>
<br />
<h3>
Windows</h3>
Depois de instalar o <a href="https://www.python.org/ftp/python/3.6.0/python-3.6.0.exe">Python 3.6.0</a>, instale o websockets com pip3 install websockets<br />
<br />
<h3>
Outros sistemas</h3>
Instale o Python 3.6.0 e o módulo <a href="https://github.com/aaugustin/websockets">websockets</a>.<br />
<br />
<h3>
O novo servidor</h3>
<br />
Mudando <b><i>@asyncio.coroutine</i></b> para <i><b>async def</b></i>, o código já fica mais claro. Em uma segunda passagem, eu substitui os <b><i>yield from</i></b> por <b><i>await</i></b>. Como estamos usando Python 3.6, não custa adaptar as strings para <i>f-strings</i>. E para terminar a migração, configurei o log para que o código não fique cheio de prints! Ficou assim:<br />
<br />
<script src="https://gist.github.com/lskbr/69c895d7152e30019fa3f2bc01680414.js"></script> Antes de executar, temos que preparar um certificado SSL (no Linux).<br />
<br />
<pre>openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
</pre>
<br />
<h3>
O cliente</h3>
<br />
Hoje não tem como escapar do Javascript. Fiz poucas alterações no código, a maior delas foi simplesmente cosmética e agora o texto rola para baixo automaticamente quando novas mensagens chegam.<br />
<br />
<script src="https://gist.github.com/lskbr/2d3040d1401c149745156fe1d2f2e01d.js"></script>
<br />
<h3>
Rodando</h3>
<br />
Imaginando que você esteja no mesmo diretório dos arquivos deste post, vamos criar um servidor web simples com python, claro:<br />
<pre>python -m SimpleHTTPServer 8080</pre>
<br />
<br />
Deixe rodando e abra um outro terminal. Vamos executar nosso servidor:<br />
<pre>python server.py</pre>
<br />
<br />
E finalmente, abra o browser usando localhost ou seu ip:<br />
<a href="http://localhost:8080/cliente.html">http://localhost:8080/cliente.html</a><br />
<br />
Observação: como utilizamos um certificado auto-assinado, precisamos dar permissão ao browser de abrir a página. Como o websocket apenas usa SSL, abra uma outra janela no browser, mas na porta 8765:<br />
<a href="https://localhost:8765/">https://localhost:8765</a><br />
<br />
Siga o procedimento de seu browser para abriar a página. Normalmente você deve clicar em um botão dizendo que quer continuar acessando a página. Se tudo der certo, você receberá a mensagem: <span style="white-space: pre-wrap;">Invalid request. Feche a janela e recarrege o cliente em:</span><br />
<a href="http://localhost:8080/cliente.html">http://localhost:8080/cliente.html</a><br />
<br />
Ele agora deve ter conectado normalmente. Abra outra janela no mesmo endereço.<br />
Digite:<br />
<pre>/nome X</pre>
<br />
e depois envie uma mensagem. Ela deve aparecer na outra janela. Você deve digitar /nome Nome antes de enviar mensagens. Teste com vários clientes, modifique e divirta-se.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-qdTysYJN9JI/WIOzVw4S8iI/AAAAAAAABaU/GzMvlcJq4jAeb1oIdBHijDp6l9BmicAfgCLcB/s1600/browsers.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="165" src="https://3.bp.blogspot.com/-qdTysYJN9JI/WIOzVw4S8iI/AAAAAAAABaU/GzMvlcJq4jAeb1oIdBHijDp6l9BmicAfgCLcB/s320/browsers.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/-ipZfWaoEIGs/WIOzV7QYYbI/AAAAAAAABaY/9RyoEGHeOjIghaRm5o53HliedCH660MlQCLcB/s1600/server.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="154" src="https://4.bp.blogspot.com/-ipZfWaoEIGs/WIOzV7QYYbI/AAAAAAAABaY/9RyoEGHeOjIghaRm5o53HliedCH660MlQCLcB/s320/server.png" width="320" /></a></div>
<br />
<br />
<br />
<br />
<br />Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com0tag:blogger.com,1999:blog-8312361233856668827.post-50856258961903528312016-12-27T09:41:00.001-04:002019-02-17T12:35:20.703-04:00Consultas via Telegram<a href="https://blog.nilo.pro.br/posts/2016-12-27-consultas-via-telegram/">Leia no novo blog</a><br />
<br />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gist-embed/2.4/gist-embed.min.js" type="text/javascript"></script>
Seria legal se profissionais de informática dessem consultas como médicos ou advogados, mas algo nos impede de cobrar por tudo e esse desejo ou intenção de compartilhar ideias nos consome.<br />
<br />
Eu participo de vários grupos de Telegram, principalmente sobre Python, um deles é o <a href="https://telegram.me/PyCoding" target="_blank">PyCoding</a> e o outro é o <a href="https://telegram.me/pythonbr" target="_blank">pybr</a>. Normalmente eu leio os grupos quando estou usando meu celular, então nem sempre é possível ajudar com as dúvidas, mas vou tentar separar um pouco de tempo para explorar algumas ideias aqui e lá.<br />
<br />
Hoje está tão fácil aprender qualquer coisa que tenho notado uma ansiedade cada vez maior de quem começa a programar de aprender tudo. Em um só mês, algumas pessoas querem aprender Python, SciPi, TensorFlow, Android e o que mais der. Um mês é pouco tempo. Pode-se aprender a programar em períodos relativamente pequenos, mas leva tempo para se acostumar com as novas ideias, linguagens e bibliotecas. O Peter Norvig comentou sobre essa ansiedade no <a href="http://norvig.com/21-days.html" target="_blank">Learn Programming in Ten Years</a>.<br />
<br />
<h3>
Cálculo de médias</h3>
<br />
Vamos ao interesse do post, a tal consulta.<br />
<br />
<code data-gist-id="8659ee0d645c2bd8e40c9b107702e030"></code>
O colega Wesley enviou dois programas, vou começar pelo mais simples. Primeiro vamos desconsiderar os palavrões, nosso colega é jovem.<br />
<br />
Uma coisa que gostei muito foi a primeira linha de mensagens. Poucos se preocupam em dizer o que faz o programa, isso é legal! Eu faria apenas uma pequena modificação para que a linha não fosse tão grande.<br />
<br />
Como eu cresci nos anos 80, sem letras minúsculas e acentos, é questão de honra corrigir as mensagens.<br />
<br />
Nas linhas 7 a 11, os valores das variávies m1, n1, n2, n3 e n4 são solicitados. Como a função input retorna strings, veja que no resto do programa a função float foi utilizada para converter estes valores. Neste caso, o valor convertido deveria ser armazenado diretamente na variável.<br />
<br />
Desta forma, simplificamos a linha 12 de forma a facilmente perceber um erro de prioridade de operações. Quando fazemos o cálculo de n1 + n2 + n3 + n4 / 4, sem utilizar parênteses, as operações são realizados por ordem de prioridade, como na matemática. Assim, n4/4 é somado a n1, n2 e n3. Para calcular a média, precisamos de parênteses: (n1 + n2 + n3 + n4) / 4. Agora, a soma das notas é calculada e depois dividida por quatro, como queríamos.<br />
<br />
Entre as linhas 15 e 19 acredito que tenha sido apenas um teste. Vou remover para não atrapalhar o entendimento do programa final.<br />
<br />
As linhas de 24 a 35 imprimem vários pontos, vou apenas simplificar.<br />
<br />
Para terminar, pequenas modificações para usar as f strings do Python 3.6.<br />
<br />
<code data-gist-id="538e7383667956259b51aa71ba9a4bec"></code>
<br />
<h3>
Programa com tkinter</h3>
O outro programa é uma interface gráfica, usando tkinter.<br />
<br />
<code data-gist-id="829eab5892d2145735e3d18af9b6ae57"></code>
Como os fontes foram postados no Telegram, muito se perde. De cara há um problema com o import da linha 2. Eu parabenizo o Wesley pela coragem de usar o tkinter. É umas das partes do Python que menos gosto, mas que funciona.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-9hZ2seGK6LI/WGJap5AH_PI/AAAAAAAABZM/f2KXL5e1RVEbUu_Upua27Y-auQhxwQ9WgCLcB/s1600/w1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="230" src="https://3.bp.blogspot.com/-9hZ2seGK6LI/WGJap5AH_PI/AAAAAAAABZM/f2KXL5e1RVEbUu_Upua27Y-auQhxwQ9WgCLcB/s320/w1.png" width="320" /></a></div>
<br />
Deve-se evitar os import * no Python, isso polui o namespace e causa problemas chatos de resolver. No caso do tkinter, é um caso a se pensar, mas nunca misturar o * com os imports de classes e funções individuais.<br />
<br />
Uma coisa que salta aos olhos, não, não falo do fundo caladryl, mas da repetição da cor em várias partes do código. Vamos criar uma constante para cor de fundo, melhor, vamos retirar as cores e deixar as cores padrão.<br />
<br />
Um outro problema é a validação de valores, acrescentei uma função float_ou_zero que retorna zero caso o valor digitado não possa ser convertido para float.<br />
<br />
Usar tkinter sem classes é um tanto confuso, eu particularmente não gosto de ter funções com variáveis globais e de ter definições de funções e variáveis misturadas, mas isso é assunto para outro post.<br />
<br />
<br />
<code data-gist-id="1a23d85d37e3817684b646eaa2ec9f44"></code>
Vejamos como ficou!
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/-y7HGc3tukjo/WGJniMmsXJI/AAAAAAAABZc/5pfO0LicbH4DSftQSvZeAYYTGRp2hxdAwCLcB/s1600/w2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="198" src="https://4.bp.blogspot.com/-y7HGc3tukjo/WGJniMmsXJI/AAAAAAAABZc/5pfO0LicbH4DSftQSvZeAYYTGRp2hxdAwCLcB/s320/w2.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<br />
<h3>
Convertendo ints
</h3>
<div>
Outro post interessante foi o de como converter vários ints de uma só vez. O problema inicial era calcular um valor do tipo hh:mm:ss em total de segundos.</div>
<br />
<code data-gist-id="3be6ed741e0bd84aa27a09f86fe2a82d"></code>
<br />
O que me chamou atenção foi uma das soluções:
<br />
<code data-gist-id="7c9373ba66cebe8a59227bf9a0440b79"></code>
<br />
Correta, porém, achei que o foco da solução não era mais o problema inicial, mas fazer em menos linhas. De repente, passa o medo de "Perlizar" o Python.<br />
O problema em si, exige validação dos dados. Este é um detalhe importante que é fácil de ser esquecido. Então, ao invés de fazer com menos linhas, vamos adicionar o mínimo de validação.
<br />
<code data-gist-id="7768a8e1eff95983523b1577c4ffbcbe"></code>
Esta solução utiliza o módulo datetime do Python e o tipo time para validar as horas entre 0 e 23, minutos entre 0 e 60 e o mesmo para segundos. Se o usuário entrar um valor errado, terá que redigitar após receber uma mensagem de erro. Embora eu tenha usado a expansão de listas duas vezes em uma só linha (Perlização?), acho que o código ficou relativamente bom.<br />
<br />
São detalhes, mas que fazem a diferença em programas maiores. Nem sempre escrever em menos linhas é o mais correto ou deveria ser o foco principal da solução de um problema.<br />
<br />
Ainda sobra margem para uma outra solução, onde criamos uma função para converter horas, minutos e segundos para total em segundos.<br />
<br />
<code data-gist-id="91d86b510cdaa68a17e6f325ff45a40d"></code>
Além da validação (ainda que mínima), ganhamos a flexibilidade de digitar valores como 10, 10:20 ou 10:20:30. O programa que fizemos pode ser importado por outros programas e suas funções reutilizadas, sem perder a funcionalidade inicial se usado como programa principal.Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com1tag:blogger.com,1999:blog-8312361233856668827.post-19574089360703594082016-05-28T08:05:00.002-04:002019-02-17T12:35:54.279-04:00Uso de UUIDs como chave primária com Django e Postgres<a href="https://blog.nilo.pro.br/posts/2016-05-28-uso-de-uuids-como-chave-primaria-com-django-e-postgres/">Leia no novo blog</a><br />
<br />
Por default, o Django cria chaves primárias do tipo inteiro (32 bits) quando usado com o banco PostgreSQL. Estes campos são incrementados automaticamente e funcionam perfeitamente bem em ambiente local. Um problema que aparece quando você cria uma API é o fato de seus ids sequenciais e numéricos exporem detalhes de sua base de dados.<br />
<br />
Imagine que seu cliente tem o id 1, ele pode imaginar (e com razão) que é seu primeiro cliente. O mesmo pode ser utilizado por concorrentes para saber quantos novos clientes você obteve em determinado período, bastando para isso criar uma nova conta. Isto pode gerar uma vontade incontrolável em algumas pessoas de explorar os valores de suas chaves. Com chaves inteiras, esta tarefa é fácil, basta incrementar o valor da chave de 1 e tentar novamente.<br />
<br />
Uma alternativa às chaves inteiras é a utilização de UUID, geradas aleatoriamente, porém maiores (128 bits). Por serem 4 vezes maiores, já podemos imaginar que o espaço em disco ocupado pelas chaves e índices vai também aumentar. Mas e quanto as operações de inserção, busca e atualização de tabelas? Quanto custa substituir as chaves inteiras por UUIDs.<br />
<br />
Exemplo de UUID:<br />
<div style="text-align: center;">
<b><span style="font-family: "courier new" , "courier" , monospace;">cab5ade3-2dc3-4344-b5a6-80df59f91458</span></b></div>
<br />
Eu resolvi fazer um teste, comparando chaves inteiras, uuids e uma solução mista, onde a uuid é utilizada fora da aplicação, mas mantendo uma chave primária inteira.<br />
<br />
Os modelos são bem simples (chaves inteiras, modelo de referência):<br />
<br />
<pre class="brush:python">class A(models.Model):
bigtext = models.TextField()
name = models.CharField(max_length=100)
counter = models.IntegerField(default=0)
class B(models.Model):
parent = models.ForeignKey(A, related_name="bs")
bigtext = models.TextField()
counter = models.IntegerField(default=0)
class C(models.Model):
parent = models.ForeignKey(B, related_name="cs")
grandparent = models.ForeignKey(A, related_name="cs")
bigtext = models.TextField()
counter = models.IntegerField(default=0)
</pre>
<br />
A, B, C e foram configuradas de forma a estabeler um relacionamento entre elas.<br />
<br />
Vamos modificar os modelos para utilizarmos UUIDs:<br />
<br />
<pre class="brush:python">class A(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
bigtext = models.TextField()
name = models.CharField(max_length=100)
counter = models.IntegerField(default=0)
class B(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
parent = models.ForeignKey(A, related_name="bs")
bigtext = models.TextField()
counter = models.IntegerField(default=0)
class C(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
parent = models.ForeignKey(B, related_name="cs")
grandparent = models.ForeignKey(A, related_name="cs")
bigtext = models.TextField()
counter = models.IntegerField(default=0)
</pre>
<br />
<br />
E uma solução híbrida alternativa, com duas chaves, uma uuid e outra inteira:<br />
<br />
<pre class="brush:python">class A(models.Model):
surrogate_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
bigtext = models.TextField()
name = models.CharField(max_length=100)
counter = models.IntegerField(default=0)
class B(models.Model):
surrogate_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
parent = models.ForeignKey(A, related_name="bs")
bigtext = models.TextField()
counter = models.IntegerField(default=0)
class C(models.Model):
surrogate_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
parent = models.ForeignKey(B, related_name="cs")
grandparent = models.ForeignKey(A, related_name="cs")
bigtext = models.TextField()
counter = models.IntegerField(default=0)
</pre>
<br />
<br />
O programa completo pode ser baixado aqui: <a href="https://github.com/lskbr/keyperftest">https://github.com/lskbr/keyperftest</a><br />
<br />
Ambiente de testes:<br />
Processador: Intel Core i7 4790K 4 GHz<br />
Disco: Samsung SSD 850 EVO<br />
Ubuntu 16.04 rodando em VM VirtualBox (4 GB, 4 CPUs)<br />
PostgreSQL 9.5.2 rodando via docker<br />
Django 1.9.6<br />
Python 3.5.1<br />
Testes realizados em: 28/05/2016<br />
<br />
Rodando o programa com 1000 registros, obtemos os seguintes resultados (todos os tempos em segundos):<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-L3d7FBzOZDk/V0mDumehR1I/AAAAAAAABVY/PbfDg_jtfyEMYbjR5w8AK7CsrngYQPDHQCKgB/s1600/image001.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="210" src="https://2.bp.blogspot.com/-L3d7FBzOZDk/V0mDumehR1I/AAAAAAAABVY/PbfDg_jtfyEMYbjR5w8AK7CsrngYQPDHQCKgB/s320/image001.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
Neste primeiro resultado, percebemos o tempo de inserção com chave inteira e uuid não é muito diferente. A solução mista (chave inteira + uuid) leva mais tempo.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/-vLTAoK6fct4/V0mDulP2bYI/AAAAAAAABVc/bHc4XokTlm8TK7XaRV7ZG7JcPUoexQM9QCKgB/s1600/image002.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="210" src="https://4.bp.blogspot.com/-vLTAoK6fct4/V0mDulP2bYI/AAAAAAAABVc/bHc4XokTlm8TK7XaRV7ZG7JcPUoexQM9QCKgB/s320/image002.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
Para simular o tempo de acesso aleatório a tabelas com chave inteira ou uuid, vemos que como esperado, as chaves inteiras possuem a melhor performance. A solução híbrida se apresenta como uma alternativa interessante, uma vez que o relacionamento entre as tabelas continua sendo feito por chaves inteiras (a chave surrogate é utilizada apenas para encontrar o registro em C, as ligações entre A e B são feitas com chaves inteiras).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-PJHt4_HtoBQ/V0mDupFdQwI/AAAAAAAABVU/m9VozzYOMRYB2sobDTBFPY0TI8gioAj1gCKgB/s1600/image003.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="210" src="https://2.bp.blogspot.com/-PJHt4_HtoBQ/V0mDupFdQwI/AAAAAAAABVU/m9VozzYOMRYB2sobDTBFPY0TI8gioAj1gCKgB/s320/image003.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
E nesta imagem o resultado das operações de atualização da tabelas. No caso, atualizamos a partir de C, as tabelas A e B. Mais uma vez as chaves inteiras tiveram um melhor desempenho e a combinação de chave inteira com uuid se apresenta como uma meio termo.<br />
<br />
Repetindo os testes, mas desta vez para 10.000 registros, as diferenças se tornam mais claras.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
No caso da inserção:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/-t-PFfq1UlIE/V0mDu6Sf4YI/AAAAAAAABVg/szL0ULQuasoigQX8aMK51CkbhxtDddKJwCKgB/s1600/image004.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="210" src="https://4.bp.blogspot.com/-t-PFfq1UlIE/V0mDu6Sf4YI/AAAAAAAABVg/szL0ULQuasoigQX8aMK51CkbhxtDddKJwCKgB/s320/image004.png" width="320" /></a></div>
<br />
Acesso:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-c2HvliP5Ec4/V0mDvB-eTOI/AAAAAAAABVo/BfX-xj3eQug7qSJdtSejzXHUq24RHgQ1wCKgB/s1600/image006.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="210" src="https://3.bp.blogspot.com/-c2HvliP5Ec4/V0mDvB-eTOI/AAAAAAAABVo/BfX-xj3eQug7qSJdtSejzXHUq24RHgQ1wCKgB/s320/image006.png" width="320" /></a></div>
<br />
Atualização<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-o0ZVc7FMsfM/V0mDuzDi_zI/AAAAAAAABVk/APtWtJXlHpgs3pnZ0T55BpVhZZc8zJi6gCKgB/s1600/image005.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="210" src="https://2.bp.blogspot.com/-o0ZVc7FMsfM/V0mDuzDi_zI/AAAAAAAABVk/APtWtJXlHpgs3pnZ0T55BpVhZZc8zJi6gCKgB/s320/image005.png" width="320" /></a></div>
<br />
Embora haja diferença entre os tempos destas operações, os resultados não demonstram uma lentidão excessiva ao aumentarmos o tamanho da chave 4 vezes, de 32 para 128 bits.<br />
<br />
Considerando os tempos das operações com chaves inteiras como 100%, temos os seguintes resultados médios:<br />
<br />
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse; margin-left: auto; margin-right: auto; width: 400px;">
<colgroup><col span="4" style="width: 60pt;" width="80"></col>
</colgroup><tbody>
<tr height="20" style="height: 15.0pt;">
<td height="20" style="height: 15.0pt; width: 60pt;" width="80"></td>
<td style="width: 60pt;" width="80">Inserção</td>
<td class="xl65" style="width: 60pt;" width="80">Atualização</td>
<td class="xl65" style="width: 60pt;" width="80">Acesso</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" style="height: 15.0pt;">integer</td>
<td align="right" class="xl65">100.00%</td>
<td align="right" class="xl65">100.00%</td>
<td align="right" class="xl65">100.00%</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" style="height: 15.0pt;">uuid</td>
<td align="right" class="xl65">101.64%</td>
<td align="right" class="xl65">108.77%</td>
<td align="right" class="xl65">108.97%</td>
</tr>
<tr height="20" style="height: 15.0pt;">
<td height="20" style="height: 15.0pt;">interger + uuid</td>
<td align="right" class="xl65">106.15%</td>
<td align="right" class="xl65">106.63%</td>
<td align="right" class="xl65">111.85%</td>
</tr>
</tbody></table>
<br />
<br />
Utilizar as UUIDs em URLs também aumenta o tamanho das strings, mas acredito ser um preço a se pagar pela comodidade.<br />
<br />
Além de esconder a sequência de chaves e não possibilitar a dedução do número de registros de seu banco de dados, UUID possuem as seguintes vantagens:<br />
a) Podem ser geradas em várias máquinas, permitindo que seu banco rode em vários servidores sincronizados.<br />
b) Evitam ataques por dedução das chaves<br />
<br />
Desvantagens de UUIDs:<br />
a) Ocupam mais espaço em disco e em memória (128 bits)<br />
b) São um pouco mais lentas para gerar<br />
c) Aumentam o tamanho das URLs<br />
d) São difíceis de memorizar (o que pode dificultar as tarefas de depuração)<br />
<br />
Eu decidi utilizar UUID em projetos futuros, uma vez que a segurança das chaves é mais importante para mim que o espaço ocupado por estas e que não há uma degradação importante na velocidade de acesso ao banco. O uso de UUIDs também facilitar a utilização do banco em clusters e até mesmo a geração de chaves offline.<br />
<br />
<br />Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com2tag:blogger.com,1999:blog-8312361233856668827.post-82346624659245703802016-05-22T07:02:00.001-04:002019-02-17T12:38:32.625-04:00Convertendo um jogo escrito em Basic para Python - Parte III<a href="https://blog.nilo.pro.br/posts/2016-05-22-convertendo-um-jogo-escrito-em-basic-para-python-parte-iii/">Leia no novo blog</a><br />
<br />
Nesta terceira parte, temos os seguintes objetivos:<br />
<br />
<br />
<ul>
<li>Limpar as classes</li>
<li>Vários aviões</li>
<li>Múltiplos tiros.</li>
<li>Generalizar os objetos do jogo em uma super classe</li>
<li>Exibir um placar</li>
<li>Atribuir teclas para atirar e jogar de novo ou sair</li>
</ul>
<br />
<br />
Na versão da Parte II, as classes tem muito código repetido.<br />
Analisando cada uma delas, podemos chegar a conclusão de um comportamento comum quanto<br />
a forma de desenhar e atualizar os objetos. Um método para retirar o objeto do jogo também é utilizado.<br />
<br />
Todo objeto do jogo precisa ter acesso ao jogo em sim.<br />
Embora quase não utilizemos cores, eu adicionei a cor como parâmetro, uma vez que pode ser usada para representar múltiplos jogadores ou diferentes tipos de objetos.<br />
<br />
Uma das coisas que não ficaram claras na versão anterior é o controle de colisão. Na realidade, o controle de colisão que utilizamos é bem básico e difícil de generalizar. Para permitir o controle da colisão entre objetos, cada objeto terá uma posição x e y na tela, com altura e largura.<br />
<br />
Desta forma, dois objetos colidem se o retângulo formado pelos pontos:<br />
<br />
(x, y), (x + largura, y), (x + largura, y + altura), (x, y + altura)<br />
<br />
De dois objetos tiverem pontos em comum.<br />
<br />
Como diferentes tipos de objetos colidem entre si, a propriedade colisão guarda o tipo de objeto.<br />
Desta forma, podemos separar os tiros do avião em si.<br />
<br />
Uma variável de DEBUG também foi adicionada, para mostrar o retângulo de colisão caso precisemos fazer ajustes.<br />
<br />
<pre class="brush:python">class ObjetoDoJogo:
def __init__(self, jogo, cor):
self.jogo = jogo
self.cor = cor
self.ativo = True
self.colisao = FUNDO
self.largura = 0
self.altura = 0
self.x = 0
self.y = 0
self.debug = DEBUG
</pre>
<br />
Se debug estiver ativo, vamos desenhar uma caixa em volta do objeto.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-BywiNdd74p0/V0GQUG9noRI/AAAAAAAABUc/kwmfQkNEedcbYWoB6c_ZOFtzz4Mpc7DYwCLcB/s1600/debug%2Bativo.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="228" src="https://2.bp.blogspot.com/-BywiNdd74p0/V0GQUG9noRI/AAAAAAAABUc/kwmfQkNEedcbYWoB6c_ZOFtzz4Mpc7DYwCLcB/s320/debug%2Bativo.png" width="320" /></a></div>
<br />
<br />
Desta forma, fica mais fácil detectar erros nos limites e no tamanho do objeto.<br />
<br />
<pre class="brush:python"> def desenha(self):
if self.debug:
set_color(2)
pyglet.graphics.draw(4, pyglet.gl.GL_LINE_LOOP,
('v2f', (self.x, self.y,
self.x + self.largura, self.y,
self.x + self.largura, self.y + self.altura,
self.x, self.y + self.altura)))
</pre>
<br />
A atualização das propriedades, embora presente em todos os objetos, não tem um comportamento padrão.<br />
Vamos utilizar apenas pass para marcar o lugar.<br />
<br />
<pre class="brush:python"> def atualiza(self):
"""Calcula nova posição, atualiza valores a cada frame"""
pass
</pre>
<br />
Aqui um método para dar uma oportunidade ao objeto de tratar a colisão com outro objeto.<br />
Este método será chamado pelo jogo, sempre que dois objetos colidirem.<br />
<br />
<pre class="brush:python"> def colidiu(self, outro):
"""O que fazer em caso de colisao? Recebe o outro objeto"""
pass
</pre>
<br />
E este aqui é o teste de colisão em si, bem simples. Ele só funciona com larguras e alturas positivas.
<br />
<pre class="brush:python"> def colide(self, outro):
if self.x < outro.x + outro.largura and self.x + self.largura > outro.x and \
self.y < outro.y + outro.altura and self.altura + self.y > outro.y:
self.colidiu(outro)
outro.colidiu(self)
</pre>
<br />
Quando a colisão é detectada, o método colidiu dos dois objetos é chamado.<br />
<br />
E finalmente o método para desativar e retirar o objeto do jogo:<br />
<br />
<pre class="brush:python"> def desativa(self):
self.ativo = False
self.jogo.desativa(self)
</pre>
<br />
Vejamos como ficou a classe do Avião:<br />
<br />
<pre class="brush:python">class Aviao(ObjetoDoJogo):
def __init__(self, jogo, cor=3):
super(Aviao, self).__init__(jogo, cor)
self.velocidade = (random.randint(0, 6) + 4) * RATIO
self.y = 159 - (random.randint(0, 135) + 11)
self.x = self.velocidade
self.altura = 9
self.largura = 15
self.colisao = AVIAO
self.escapou = False
def desenha(self):
super().desenha()
y = self.y + self.altura
x = self.x
set_color(self.cor)
pyglet.graphics.draw(6, pyglet.gl.GL_LINE_LOOP,
('v2f', (x, y,
x, y - 8,
x + 3, y - 2,
x + 12, y - 2,
x + 14, y,
x, y)))
def atualiza(self):
self.x += self.velocidade
if self.x > 265:
self.escapou = True
self.desativa()
def colidiu(self, outro):
if outro.colisao == TIRO:
self.jogo.objetos.append(Explosao(self.jogo, self.x, self.y))
self.desativa()
</pre>
<br />
Nesta versão, o nome das propriedades já foi ajustado. Não utilizo mais altura_do_aviao que se tornou apenas a coordenada y. Na classe Avião, adicionei a propriedade escapou para marcar os aviões que saem da tela sem terem sido destruídos, dos que são atingidos por mísseis. Mais tarde, esta informação será utilizada para contar pontos.<br />
<br />
Quando o avião colide com um Tiro, um novo tipo de objeto, Explosao é adicionado ao jogo. Desta forma, teremos um efeito simples quando o avião for atingido.<br />
<br />
Uma das vantagens de se trabalhar com objetos é a facilidade de criá-los. No caso, a explosão é apenas outro objeto adicionado ao jogo.<br />
<br />
<pre class="brush:python">class Explosao(ObjetoDoJogo):
def __init__(self, jogo, x, y):
super(Explosao, self).__init__(jogo, cor=0)
self.estagio = 8
self.x = x
self.y = y
def desenha(self):
dx = 4 * self.estagio #random.randint(1, 4)
dy = random.randint(1, 4) * self.estagio
dx2 = random.randint(1, 4) * self.estagio
dy2 = random.randint(1, 4) * self.estagio
y = self.y
set_color(7)
pyglet.graphics.draw(3, pyglet.gl.GL_TRIANGLES,
('v2f', (self.x, y,
self.x - dx, y + dy,
self.x + dx2, y + dy2)))
set_color(8)
pyglet.graphics.draw(3, pyglet.gl.GL_TRIANGLES,
('v2f', (self.x, y,
self.x -dx/self.estagio , y + dx,
self.x + dx2 * self.estagio, y + dy2)))
def atualiza(self):
self.estagio -= 0.4
if(self.estagio < 0):
self.desativa()
</pre>
<br />
A ideia da explosão é que triângulos amarelos e vermelhos, de tamanhos aleatórios apareçam na tela.<br />
Como a explosão não se move, a propriedade estagio foi adicionada para controlar seu tempo de vida.<br />
No caso, a cada frame, subtrairemos 0.4 de estagio e quando este for negativo, o objeto será retirado do jogo.<br />
<br />
A classe Tiro foi também melhorada:<br />
<br />
<pre class="brush:python">class Tiro(ObjetoDoJogo):
def __init__(self, jogo, posicao):
super(Tiro, self).__init__(jogo, cor=3)
self.y = 170
self.x = (posicao + 1) * 70
self.velocidade = 5 * RATIO
self.colisao = 2
self.altura = 5
self.largura = 2
self.errou = False
def desenha(self):
super().desenha()
set_color(self.cor)
y = self.y + self.altura
pyglet.graphics.draw(2, pyglet.gl.GL_LINES,
('v2f', (self.x, y,
self.x, y - 4)))
v = 4
a = random.randint(3, 4)
set_color(7)
pyglet.graphics.draw(3, pyglet.gl.GL_TRIANGLES,
('v2f', (self.x, y,
self.x - 1, y + v,
self.x + 1, y + v)))
set_color(8)
pyglet.graphics.draw(3, pyglet.gl.GL_TRIANGLES,
('v2f', (self.x, y,
self.x - 0.5, y + a,
self.x + 0.5, y + a)))
def atualiza(self):
self.y -= self.velocidade
if self.y < 0:
self.errou = True
self.desativa()
def colidiu(self, outro):
if outro.colisao == AVIAO:
self.desativa()
</pre>
<br />
O Tiro agora tem um foguete, o efeito é interessante, embora simples.<br />
Quando o tiro sai da tela sem atingir um avião, este é considerado um erro e o jogador é penalizado.<br />
<br />
As bases são também desenhadas como objetos do jogo. Nesta versão eu mudei o desenho.<br />
<br />
<pre class="brush:python">class Base(ObjetoDoJogo):
def __init__(self, jogo, posicao):
super(Base, self).__init__(jogo, cor=3)
self.y = 175
self.x = (posicao + 1) * 70
def desenha(self):
super().desenha()
set_color(self.cor)
pyglet.graphics.draw(5, pyglet.gl.GL_LINE_STRIP,
('v2f', (self.x - 5, self.y + 5,
self.x - 3, self.y + 2,
self.x, self.y,
self.x + 3, self.y + 2,
self.x + 5, self.y + 5,
)))<span style="font-family: "times new roman";"><span style="white-space: normal;">
</span></span></pre>
<br />
A classe do jogo em si foi simplificada:<br />
<br />
<pre class="brush:python">class Game:
def __init__(self, parent):
self.cor = CORES
self.window = parent
self.tiros_disparados = 0 # tiros já disparados
self.estado = "jogando"
self.pressionado = False
self.label = None
self.objetos = [Aviao(self)]
self.objetos.extend([Base(self, base) for base in range(0, 3)])
self.pontos = 0
self.placar = pyglet.text.Label("Pontos: 0", font_name="Times New Roman", font_size=16,
x=80, y=-185 * 4, anchor_x='center', anchor_y='center')
</pre>
<br />
Agora temos um contador de pontos e um placar. Um objeto da classe Avião e as três bases são adicionadas ao jogo inicial.<br />
<br />
<pre class="brush:python">def desativa(self, objeto):
self.objetos.remove(objeto)
if objeto.colisao == AVIAO:
if objeto.escapou:
self.pontos -= 3
else:
self.pontos += 5
self.objetos.append(Aviao(self))
elif objeto.colisao == TIRO:
if objeto.errou:
self.pontos -= 1
</pre>
<br />
A desativação de um objeto é a retirada deste da lista de objetos do jogo.<br />
Quando é um avião, verifica se o avião escapou da tela sem ter sido atingido.<br />
Se escapou, o jogador perde 3 pontos. Se foi destruído, o jogador ganha 5 pontos.<br />
<br />
A cada tiro disparado que sai da tela sem atingir um avião, o jogador perde 1 ponto.<br />
<br />
Agora que temos o placar a mostrar, precisamos modificar o método mostra_mensagem.<br />
Este deve exibir o placar sempre:<br />
<br />
<pre class="brush:python"> def mostra_mensagem(self):
glPushMatrix()
glScalef(0.25, -0.25, 0.25)
if self.label:
self.label.draw()
self.placar.text = "Pontos: {}".format(self.pontos)
self.placar.draw()
glPopMatrix()
</pre>
<br />
<br />
O glPushMatrix e glPopMatrix foram utilizados para corrigir o desenho do texto.<br />
Como estamos utilizando uma escala 4 com inversão das coordenadas do eixo Y, o glScalef<br />
foi utilizado com 0.25 (que na multiplicação é o mesmo que 1/4 ou que dividir por 4) e<br />
-0.25 no eixo Y, que divide por 4 e inverte o sinal.<br />
O glPushMatrix é utilizado para salvar a matriz atual na pilha, desta forma o glScalef<br />
será utilizado apenas até o glPopMatrix que restaura os parâmetros anteriores.<br />
<br />
O método atualiza foi simplificado:<br />
<br />
<pre class="brush:python"> def atualiza(self):
self.jogue()
if self.estado != "jogando":
if self.pressionado:
pyglet.app.event_loop.exit()
self.mostra_mensagem()
</pre>
<br />
O método jogue sempre é chamado. Desta forma podemos ter o desenho do jogo, mesmo quando exibimos uma mensagem de fim por exemplo. Nesta versão, não há mudança de estado, pois eu modifiquei o jogo para gerar outro avião sempre que um for destruído ou sumir, não tendo fim.<br />
<br />
E o método jogue também simplificado:<br />
<br />
<pre class="brush:python"> def jogue(self):
for objeto in self.objetos:
objeto.desenha()
self.processa_tiros()
if self.estado == "jogando":
for objeto in self.objetos:
objeto.atualiza()
if self.pressionado:
self.objetos.append(Tiro(self, self.tiros_disparados % 3))
self.tiros_disparados += 1
self.pressionado = False
</pre>
<br />
Ele apenas desenha todos os objetos da lista e chama processa_tiros que testa colisões.<br />
Se ainda estivermos jogando, chama a atualização de todos os objetos da lista e se algo foi pressionado, gera um tiro. Para termos vários tiros, a base que o dispara é calculada pelo módulo do número do tiro e o número de bases.<br />
<br />
<pre class="brush:python">def processa_tiros(self):
tiros = [objeto for objeto in self.objetos if objeto.colisao == TIRO]
avioes = [objeto for objeto in self.objetos if objeto.colisao == AVIAO]
for aviao in avioes:
for tiro in tiros:
aviao.colide(tiro)
</pre>
<br />
Aqui uma simplificação, onde abuso um pouco do computador :-D<br />
Utilizando duas "list comprehensions", eu filtro a lista de objetos, procurando aviões e tiros.<br />
Desta forma, a verificação de colisão se torna mais simples, pois basta verificar se o avião colidiu com um dos tiros.<br />
<br />
O jogo principal pouco mudou, salvo o método on_key_press:<br />
<br />
<pre class="brush:python">def on_key_press(self, symbol, modifiers):
if symbol == key.S:
print("Placar: {}".format(self.game.pontos))
pyglet.app.event_loop.exit()
elif symbol == key.R:
self.game = Game(self)
else:
self.game.pressionado = True
</pre>
<br />
No caso, o jogo termina quando pressionamos a tecla S e reinicia se pressionarmos a tecla R.<br />
<br />
O jogo completo ficou assim:<br />
<br />
<script src="https://gist.github.com/lskbr/82ea8ef573f36b3b8b6d13de23643ca2.js"></script>
<br />
Video:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/6aTbgxzzg5A/0.jpg" frameborder="0" height="266" src="https://www.youtube.com/embed/6aTbgxzzg5A?feature=player_embedded" width="320"></iframe></div>
<br />
<br />
Para o próximo post:<br />
<br />
<ol>
<li>Sons</li>
<li>Imagens</li>
</ol>
<br />
<br />
A pyglet não é atualizada há bastante tempo. Eu devo procurar outra biblioteca gráfica para outros posts, mas vou terminar as melhorias deste jogo ainda com pyglet e abusando da OpenGL.<br />
<br />
<br />Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com0tag:blogger.com,1999:blog-8312361233856668827.post-35717144678383245342016-05-21T13:28:00.000-04:002019-02-17T12:38:56.479-04:00Convertendo um jogo escrito em Basic para Python - Parte II<a href="https://blog.nilo.pro.br/posts/2016-05-21-convertendo-um-jogo-escrito-em-basic-para-python-parte-ii/">Leia no novo blog</a><br />
<br />
Neste segundo post, vamos melhorar nosso jogo.<br />
<br />
Embora a nova versão rode em Python, ainda está muito anos 80.<br />
A primeira coisa a corrigir é a animação. Como estamos usando um sistema de coordenadas de 280 por 192 pontos para simular as coordenadas do Apple, multiplicamos cada coordenada por 4 na hora de desenhar. Para deixar parecido com o Apple, eu reduzi o número de frames a 8 frames por segundo. Por isso a animação dá tantos saltos! Para fazer com que rode a 60 frames, temos que multiplicar as velocidades pela razão entre o número de frames antigos e o novo: 8/60. A nova versão define algumas constantes para isso:<br />
<br />
<pre class="brush:python">FRAMES_ORIGINAL = 8
FRAMES_NOVO = 60
RATIO = FRAMES_ORIGINAL / FRAMES_NOVO
</pre>
<br />
<br />
Olhando o código, fica fácil perceber que o tiro, o avião e as bases são objetos. Vamos extrair o código do jogo e dividir as operações em atualiza e desenha. No caso, atualiza calcula a nova posição do objeto e desenha realiza o desenho em si.<br />
<br />
<pre class="brush:python">class Aviao:
def __init__(self, jogo, cor=3):
self.cor = cor
self.velocidade_aviao = (random.randint(0, 6) + 4) * RATIO
self.altura_do_aviao = 159 - (random.randint(0, 135) + 11)
self.posicao_do_aviao = self.velocidade_aviao
self.ativo = True
self.jogo = jogo
def desenha(self):
y = self.altura_do_aviao
x = self.posicao_do_aviao
set_color(self.cor)
pyglet.graphics.draw(6, pyglet.gl.GL_LINE_LOOP,
('v2f', (x, y,
x, y - 8,
x + 3, y - 2,
x + 12, y - 2,
x + 14, y,
x, y)))
def desativa(self):
self.ativo = False
self.jogo.desativa(self)
def atualiza(self):
self.posicao_do_aviao += self.velocidade_aviao
if self.posicao_do_aviao > 265:
self.desativa()
</pre>
<br />
<br />
<br />
O código completo pode ser visto aqui:
<br />
<script src="https://gist.github.com/lskbr/c6ada79c1436c5ed648abcb82823378a.js"></script>
Tente entender como a forma de desenhar em etapas, separando o desenho da atualização, permite que exibamos a mensagem com a imagem do jogo no fundo, ao invés da tela preta..<br />
<br />
Os próximos passos são:<br />
<br />
<ol>
<li>Limpar as classes</li>
<li>Vários aviões</li>
<li>Múltiplos tiros.</li>
<li>Generalizar os objetos do jogo em uma super classe.</li>
<li>Exibir um placar</li>
<li>Atribuir teclas para atirar e jogar de novo ou sair.</li>
</ol>
<br />
<br />
Até o próximo!<br />
<br />
<br />Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com0tag:blogger.com,1999:blog-8312361233856668827.post-35704413520069088582016-05-14T13:13:00.001-04:002019-02-17T12:39:16.887-04:00Convertendo um jogo escrito em Basic para Python - Parte I<a href="https://blog.nilo.pro.br/posts/2016-05-14-convertendo-um-jogo-escrito-em-basic-para-python-parte-i/">Leia no novo blog</a><br />
<br />
A nostalgia dos computadores da década de 80 é algo que nunca parei de ter. Quando criança, tive a sorte de utilzar vários computadores de 8-bits, como ZX-81, ZX-Spectrum, Apple II e MSX, ou melhor, seus clones nacionais (TK-85, TK-90X, TK2000), uma vez que o Brasil vivia a época da Reserva do Mercado de Informática.<br />
Numa época que não havia Internet, nós passávamos o tempo a digitar programas. Uma série de livros sobre programação de jogos foi editada pela editora Lutécia no Brasil, mas os originais americanos foram liberados pela <a href="http://www.usborne.com/catalogue/feature-page/computer-and-coding-books.aspx">Usborne</a>. Neste artigo, eu vou traduzir o jogo principal do Computer Battlegames, chamado de Missile, de Apple II Basic para Python com Pyglet. A listagem original está na página 34 do livro em inglês (ver pdf acima).<br />
<br />
<pre class="brush:vb">10 HOME
20 HGR
30 HCOLOR=3
40 DIM Y(3),F(3)
50 N=1 : MS=5
60 PS=INT(RND(1)*6+4)
70 P=INT(RND(1)*135+11)
80 GOSUB 400
90 FOR I=PS TO 265 STEP PS
100 X=I-PS : Y=159-P : C=0 : GOSUB 300
110 X=I : C=3: GOSUB 300
120 F$="" : IF PEEK(-16384)>127 THEN GET F$
130 IF F$="" OR N>3 THEN 160
140 F(N)=1
150 N=N+1
160 FOR J=1 TO 3
170 C=0 : GOSUB 350
180 IF F(J)=0 OR Y(J)>145 THEN 230
190 Y(J)=Y(J)+MS
200 C=3 : GOSUB 350
210 X=J*70-I : Y=P-Y(J)
220 IF X>-1 AND X<15 AND Y>-9 AND Y<5 THEN 270
230 NEXT
240 NEXT
250 VTAB 22 : PRINT "MISSED"
260 END
270 VTAB 22 : PRINT "HIT!!!"
280 END
300 HCOLOR=C
310 HPLOT X,Y TO X,Y-8
320 HPLOT TO X+3,Y-2 : HPLOT TO X+12, Y-2
330 HPLOT TO X+14,Y : HPLOT TO X,Y
340 RETURN
350 HCOLOR=C
360 HPLOT 70*J,158-Y(J) TO 70*J,154-Y(J)
340 RETURN
350 HCOLOR=C
360 HPLOT 70*J, 158-Y(J) TO 70*J,154-Y(J)
370 RETURN
400 FOR J=1 TO 3
410 HPLOT 70*J-5,159 TO 70*J+5,159
420 NEXT
430 RETURN
</pre>
Vejamos o jogo rodando em um emulador:
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/rXFUHKVOEFw/0.jpg" frameborder="0" height="266" src="https://www.youtube.com/embed/rXFUHKVOEFw?feature=player_embedded" width="320"></iframe></div>
<br />
Versão comentada:
<br />
<pre class="brush:vb"># Limpa a tela
10 HOME
# Entra no modo de alta resolução 280x192
20 HGR
# Seleciona a cor 3 (purpura/magenta)
30 HCOLOR=3
# Cria dois vetores com 3 elementos cada
40 DIM Y(3),F(3)
# Inicializa N igual a 1 e MS igual a 5
50 N=1 : MS=5
# Gera um número aleatório entre 0 e 6 + 4
60 PS=INT(RND(1)*6+4)
# Gera um número aleatório entre 0 e 135 + 11
70 P=INT(RND(1)*135+11)
# Desvia para subrotina
80 GOSUB 400
# Loop: repete I de PS até 265, incrementando de PS
90 FOR I=PS TO 265 STEP PS
# Calcula os valores de X, Y e C. Desvia para subrotina em 300
100 X=I-PS : Y=159-P : C=0 : GOSUB 300
# Define X e C. Desvia para subrotina 300
110 X=I : C=3: GOSUB 300
# Verifica se uma tecla foi pressionada. Se foi, guarda em F
120 F$="" : IF PEEK(-16384)>127 THEN GET F$
# Se algo foi pressionado ou se N>3 desvia para 160
130 IF F$="" OR N>3 THEN 160
# F[N] = 1
140 F(N)=1
# N+=1
150 N=N+1
# Repete J de 1 até 3
160 FOR J=1 TO 3
# Zera C e desvia para subrotina da linha 350
170 C=0 : GOSUB 350
# Se F[J]==0 ou Y[J]>145 desvia para 230
180 IF F(J)=0 OR Y(J)>145 THEN 230
# Y[J]+=MS
190 Y(J)=Y(J)+MS
# C=3. Desvia para subrotina da linha 350
200 C=3 : GOSUB 350
# Calcula X e Y
210 X=J*70-I : Y=P-Y(J)
# Se X>-1 e X<15 e Y>-9 e Y<5 desvia para 270
220 IF X>-1 AND X<15 AND Y>-9 AND Y<5 THEN 270
# Fim do loop, volta para o for da linha 160
230 NEXT
# Fim do loop, volta para for da linha 90
240 NEXT
# Posiciona o cursor na linha 22 e imprime MISSED
250 VTAB 22 : PRINT "MISSED"
# Termina o programa
260 END
# Posiciona o cursor na linha 22 e imprime HIT!!!
270 VTAB 22 : PRINT "HIT!!!"
# Termina o programa
280 END
# Troca a cor de desenho para C
300 HCOLOR=C
# Traça uma linha de X,Y até X,Y-8
310 HPLOT X,Y TO X,Y-8
# Continua a linha
320 HPLOT TO X+3,Y-2 : HPLOT TO X+12, Y-2
330 HPLOT TO X+14,Y : HPLOT TO X,Y
# Retorno da subrotina
340 RETURN
# Troca a cor de desenho para C
350 HCOLOR=C
# Desenha linha
360 HPLOT 70*J,158-Y(J) TO 70*J,154-Y(J)
# Volta da subrotina
340 RETURN
# Troca a cor para C
350 HCOLOR=C
# Desenha linha
360 HPLOT 70*J, 158-Y(J) TO 70*J,154-Y(J)
# Volta da subrotina
370 RETURN
# Repete J de 1 à 3
400 FOR J=1 TO 3
# Desenha linha
410 HPLOT 70*J-5,159 TO 70*J+5,159
# Fim do loop, volta para linha 400
420 NEXT
# Retorna da subrotina
430 RETURN
</pre>
Bom, como precisaremos desenhar, vamos instalar a Pyglet:
<br />
<pre class="brush:bash">pip3 install pyglet
</pre>
<br />
A primeira coisa a fazer é criar a janela da Pyglet e separar a janela do jogo em si.
No caso, a janela é responsável por receber os eventos do teclado.
A configuração do OpenGL também precisam ser feitas aqui.
Como a alta resolução do Apple II é muito pequena 280x192 pontos, eu estou usando uma escala 4.
Desta forma, as coordenadas permanecerão as mesmas, mas os gráficos serão 4 vezes maiores.
Se ficar muito grande em seu monitor, você pode ajustar a escala.
<br />
Eu tentei manter os comentários originais para ficar mais fácil de relacionar o código novo com o antigo.
<br />
Outra mudança são as coordenadas Y. No Apple II, Y = 0 é a primeira linha e em OpenGL é a última.
<br />
Vejamos como ficou o código da janela:
<br />
<pre class="brush:python">class Missile(pyglet.window.Window):
# 20 HGR
def __init__(self):
self.scale = 4 # Escala os gráficos 4x
self.frames = 8 # Frames por segundo.
# Define quantas vezes vamos atualizar a tela por segundo
super(Missile, self).__init__(280 * self.scale,
192 * self.scale,
caption="Missiles")
self.set_mouse_visible(False)
self.game = Game(self)
self.schedule = pyglet.clock.schedule_interval(
func=self.update, interval=1.0 / self.frames)
def update(self, interval):
pass
def on_draw(self):
window.clear()
self.game.atualiza()
def on_resize(self, width, height):
# Inicializa a view
glViewport(0, 0, width, height)
glMatrixMode(gl.GL_PROJECTION)
glLoadIdentity()
# Inverte as coordenadas do eixo Y
glOrtho(0, width, height, 0, -1, 1)
# Aplica a escala
glScalef(self.scale, self.scale, 1.0)
glMatrixMode(gl.GL_MODELVIEW)
def on_key_press(self, symbol, modifiers):
# Verifica se uma tecla foi pressionada. Se foi, guarda em F
# 120 F$="" : IF PEEK(-16384)>127 THEN GET F$
self.game.pressionado = True
def crialabel(self, mensagem, x=None, y=None,
fonte='Times New Roman', tamanho=36):
"""Prepara uma mensagem de texto para ser exibida"""
x = x or self.width // 2
y = y or self.height // 2
return pyglet.text.Label(
mensagem, font_name=fonte, font_size=tamanho,
x=x, y=y, anchor_x='center', anchor_y='center')
</pre>
<br />
Além disso, as cores do Apple II precisam ser definidas:
<br />
<pre class="brush:python">def rgb_to_f(r, g, b):
return(r / 255.0, g / 255.0, b / 255.0)
# Cores do modo de alta resolução HGR
# Fonte: https://github.com/AppleWin/AppleWin/issues/254
CORES = [(0.0, 0.0, 0.0), # Preto 0
rgb_to_f(20, 245, 60), # Verde 1
rgb_to_f(255, 68, 253), # Magenta 2
rgb_to_f(255, 255, 255), # Branco 3
rgb_to_f(255, 106, 60), # Laranja 5
rgb_to_f(20, 207, 253), # Azul médio 6
rgb_to_f(255, 255, 255), # Branco 7
]
</pre>
<br />
e finalmente a classe Game com o jogo em si. Observar que os índices em Python começam em 0 e em Basic começam com 1.
Primeiro passo da conversão:
<br />
<pre class="brush:python">class Game():
def __init__(self, parent):
self.cor = CORES
self.window = parent
# Cria dois vetores com 3 elementos cada
# 40 DIM Y(3),F(3)
self.Y = [0] * 3
self.F = [0] * 3
# Inicializa N igual a 1 e MS igual a 5
# 50 N=1 : MS=5
self.N = 0
self.MS = 5
# Gera um número aleatório entre 0 e 6 + 4
# 60 PS=INT(RND(1)*6+4)
self.PS = random.randint(0, 6) + 4
# Gera um número aleatório entre 0 e 135 + 11
# 70 P=INT(RND(1)*135+11)
self.P = random.randint(0, 135) + 11
self.estado = "jogando"
self.I = self.PS
self.pressionado = False
self.label = None
def atualiza(self):
if self.estado == "jogando":
self.jogue()
else:
if self.label:
glMatrixMode(gl.GL_PROJECTION)
glLoadIdentity()
glOrtho(0, self.window.width, 0, self.window.height, -1, 1)
glMatrixMode(gl.GL_MODELVIEW)
self.label.draw()
if self.pressionado:
pyglet.app.event_loop.exit()
def jogue(self):
# Desvia para subrotina
# 80 GOSUB 400
self.sub_400()
# Loop: repete I de PS até 265, incrementando de PS
# 90 FOR I=PS TO 265 STEP PS
if self.I > 265:
self.sub_250()
return
# Calcula os valores de X, Y e C. Desvia para subrotina em 300
# 100 X=I-PS : Y=159-P : C=0 : GOSUB 300
self.X = self.I - self.PS
self.y = 159 - self.P
self.C = 0
self.sub_300()
# Define X e C. Desvia para subrotina 300
# 110 X=I : C=3: GOSUB 300
self.X = self.I
self.C = 3
self.sub_300()
# Se algo foi pressionado ou se N>3 desvia para 160
# 130 IF F$="" OR N>3 THEN 160
if self.pressionado and self.N < 3:
# F[N] = 1
# 140 F(N)=1
self.F[self.N] = 1
# N+=1
# 150 N=N+1
self.N += 1
self.pressionado = False
# Repete J de 1 até 3
# 160 FOR J=1 TO 3
for self.J in range(0, 3):
# Zera C e desvia para subrotina da linha 350
# 170 C=0 : GOSUB 350
self.C = 0
self.sub_350()
# Se F[J]==0 ou Y[J]>145 desvia para 230
# 180 IF F(J)=0 OR Y(J)>145 THEN 230
if self.F[self.J] == 0 or self.Y[self.J] > 145:
continue
# Y[J]+=MS
# 190 Y(J)=Y(J)+MS
self.Y[self.J] += self.MS
# C=3. Desvia para subrotina da linha 350
# 200 C=3 : GOSUB 350
self.C = 3
self.sub_350()
# Calcula X e Y
# 210 X=J*70-I : Y=P-Y(J)
self.X = (self.J + 1) * 70 - self.I
self.y = self.P - self.Y[self.J]
# Se X>-1 e X<15 e Y>-9 e Y<5 desvia para 270
# 220 IF X>-1 AND X<15 AND Y>-9 AND Y<5 THEN 270
if self.X > -1 and self.X < 15 and self.y > -9 and self.y < 5:
self.sub_270()
# Fim do loop, volta para o for da linha 160
# 230 NEXT
# Fim do loop, volta para for da linha 90
# 240 NEXT
# 90 FOR I=PS TO 265 STEP PS
self.I += self.PS
def sub_250(self):
# Posiciona o cursor na linha 22 e imprime MISSED
# 250 VTAB 22 : PRINT "MISSED"
print("MISSED")
self.imprima("MISSED")
# Termina o programa
# 260 END
self.muda_estado("fimdejogo")
def sub_270(self):
# Posiciona o cursor na linha 22 e imprime HIT!!!
# 270 VTAB 22 : PRINT "HIT!!!"
print("HIT")
self.imprima("HIT!!!")
# Termina o programa
self.muda_estado("fimdejogo")
def sub_300(self):
# Troca a cor de desenho para C
# 300 HCOLOR=C
self.set_color(self.C)
# Traça uma linha de X,Y até X,Y-8
# 310 HPLOT X,Y TO X,Y-8
# Continua a linha
# 320 HPLOT TO X+3,Y-2 : HPLOT TO X+12, Y-2
# 330 HPLOT TO X+14,Y : HPLOT TO X,Y
pyglet.graphics.draw(6, pyglet.gl.GL_LINE_LOOP,
('v2i', (self.X, self.y,
self.X, self.y - 8,
self.X + 3, self.y - 2,
self.X + 12, self.y - 2,
self.X + 14, self.y,
self.X, self.y)))
# Retorno da subrotina
# 340 RETURN
def sub_350(self):
# Troca a cor para C
# 350 HCOLOR=C
self.set_color(self.C)
# Desenha linha
# 360 HPLOT 70*J, 158-Y(J) TO 70*J,154-Y(J)
J = self.J + 1
pyglet.graphics.draw(2, pyglet.gl.GL_LINES,
('v2i', (70 * J, 158 - self.Y[self.J],
70 * J, 154 - self.Y[self.J])))
# Volta da subrotina
# 370 RETURN
def sub_400(self):
self.set_color(3)
# 400 FOR J=1 TO 3
for J in range(1, 4):
# Desenha linha
# 410 HPLOT 70*J-5,159 TO 70*J+5,159
pyglet.graphics.draw(2, pyglet.gl.GL_LINES,
('v2i', (70 * J - 5, 159,
70 * J + 5, 159)))
# Fim do loop, volta para linha 400
# 420 NEXT
# Retorna da subrotina
# 430 RETURN
def set_color(self, color):
glColor3f(*self.cor[color])
def muda_estado(self, estado):
print("Mudança de Estado: {} --> {}".format(self.estado, estado))
self.estado = estado
def imprima(self, mensagem):
self.label = self.window.crialabel(mensagem)</pre>
<br />
Como em OpenGL limpamos a tela a cada frame, a rotina que apaga os objetos pode ser apagada.
No caso, a chamada a sub_300() com C=0.
<br />
O nome dos métodos ainda não foram mudados, vamos renomear:
<br />
sub_250 para pedeu
<br />
sub_270 para acertou
<br />
sub_300 para desenha_aviao
<br />
sub_350 para desenha_tiro
<br />
sub_400 para desenha_bases
<br />
Apagando os comentários com o código em Basic, temos:
<br />
<pre class="brush:python">import pyglet
from pyglet.gl import *
import random
def rgb_to_f(r, g, b):
return(r / 255.0, g / 255.0, b / 255.0)
# Cores do modo de alta resolução HGR
# Fonte: https://github.com/AppleWin/AppleWin/issues/254
CORES = [(0.0, 0.0, 0.0), # Preto 0
rgb_to_f(20, 245, 60), # Verde 1
rgb_to_f(255, 68, 253), # Magenta 2
rgb_to_f(255, 255, 255), # Branco 3
rgb_to_f(255, 106, 60), # Laranja 5
rgb_to_f(20, 207, 253), # Azul médio 6
rgb_to_f(255, 255, 255), # Branco 7
]
class Game():
def __init__(self, parent):
self.cor = CORES
self.window = parent
# Cria dois vetores com 3 elementos cada
self.Y = [0] * 3
self.F = [0] * 3
# Inicializa N igual a 1 e MS igual a 5
self.N = 0
self.MS = 5
# Gera um número aleatório entre 0 e 6 + 4
self.PS = random.randint(0, 6) + 4
# Gera um número aleatório entre 0 e 135 + 11
self.P = random.randint(0, 135) + 11
self.estado = "jogando"
self.I = self.PS
self.pressionado = False
self.label = None
def atualiza(self):
if self.estado == "jogando":
self.jogue()
else:
if self.label:
glMatrixMode(gl.GL_PROJECTION)
glLoadIdentity()
glOrtho(0, self.window.width, 0, self.window.height, -1, 1)
glMatrixMode(gl.GL_MODELVIEW)
self.label.draw()
if self.pressionado:
pyglet.app.event_loop.exit()
def jogue(self):
self.desenha_bases()
if self.I > 265:
self.perdeu()
return
self.X = self.I - self.PS
self.y = 159 - self.P
self.C = 0
self.desenha_aviao()
self.X = self.I
self.C = 3
self.desenha_aviao()
if self.pressionado and self.N < 3:
self.F[self.N] = 1
self.N += 1
self.pressionado = False
for self.J in range(0, 3):
if self.F[self.J] == 0 or self.Y[self.J] > 145:
continue
self.Y[self.J] += self.MS
self.C = 3
self.desenha_tiro()
self.X = (self.J + 1) * 70 - self.I
self.y = self.P - self.Y[self.J]
if self.X > -1 and self.X < 15 and self.y > -9 and self.y < 5:
self.acertou()
self.I += self.PS
def perdeu(self):
self.imprima("MISSED")
self.muda_estado("fimdejogo")
def acertou(self):
self.imprima("HIT!!!")
self.muda_estado("fimdejogo")
def desenha_aviao(self):
self.set_color(self.C)
pyglet.graphics.draw(6, pyglet.gl.GL_LINE_LOOP,
('v2i', (self.X, self.y,
self.X, self.y - 8,
self.X + 3, self.y - 2,
self.X + 12, self.y - 2,
self.X + 14, self.y,
self.X, self.y)))
def desenha_tiro(self):
self.set_color(self.C)
J = self.J + 1
pyglet.graphics.draw(2, pyglet.gl.GL_LINES,
('v2i', (70 * J, 158 - self.Y[self.J],
70 * J, 154 - self.Y[self.J])))
def desenha_bases(self):
self.set_color(3)
for J in range(1, 4):
pyglet.graphics.draw(2, pyglet.gl.GL_LINES,
('v2i', (70 * J - 5, 159,
70 * J + 5, 159)))
def set_color(self, color):
glColor3f(*self.cor[color])
def muda_estado(self, estado):
print("Mudança de Estado: {} --> {}".format(self.estado, estado))
self.estado = estado
def imprima(self, mensagem):
self.label = self.window.crialabel(mensagem)
class Missile(pyglet.window.Window):
def __init__(self):
self.scale = 4 # Escala os gráficos 4x
self.frames = 8 # Frames por segundo.
# Define quantas vezes vamos atualizar a tela por segundo
super(Missile, self).__init__(280 * self.scale,
192 * self.scale,
caption="Missiles")
self.set_mouse_visible(False)
self.game = Game(self)
self.schedule = pyglet.clock.schedule_interval(
func=self.update, interval=1.0 / self.frames)
def update(self, interval):
pass
def on_draw(self):
window.clear()
self.game.atualiza()
def on_resize(self, width, height):
# Inicializa a view
glViewport(0, 0, width, height)
glMatrixMode(gl.GL_PROJECTION)
glLoadIdentity()
# Inverte as coordenadas do eixo Y
glOrtho(0, width, height, 0, -1, 1)
# Aplica a escala
glScalef(self.scale, self.scale, 1.0)
glMatrixMode(gl.GL_MODELVIEW)
def on_key_press(self, symbol, modifiers):
self.game.pressionado = True
def crialabel(self, mensagem, x=None, y=None,
fonte='Times New Roman', tamanho=36):
"""Prepara uma mensagem de texto para ser exibida"""
x = x or self.width // 2
y = y or self.height // 2
return pyglet.text.Label(
mensagem, font_name=fonte, font_size=tamanho,
x=x, y=y, anchor_x='center', anchor_y='center')
window = Missile()
pyglet.app.run()
</pre>
<br />
Bem melhor, mas ainda guarda várias características do programa em Basic.
O nome das variáveis é uma catástrofe. Renomeando as variáveis e melhorando os comentários, o programa completo fica assim:
<br />
<pre class="brush:python">import pyglet
from pyglet.gl import *
import random
def rgb_to_f(r, g, b):
return(r / 255.0, g / 255.0, b / 255.0)
# Cores do modo de alta resolução HGR
# Fonte: https://github.com/AppleWin/AppleWin/issues/254
CORES = [(0.0, 0.0, 0.0), # Preto 0
rgb_to_f(20, 245, 60), # Verde 1
rgb_to_f(255, 68, 253), # Magenta 2
rgb_to_f(255, 255, 255), # Branco 3
rgb_to_f(255, 106, 60), # Laranja 5
rgb_to_f(20, 207, 253), # Azul médio 6
rgb_to_f(255, 255, 255), # Branco 7
]
class Game():
def __init__(self, parent):
self.cor = CORES
self.window = parent
# Cria dois vetores com 3 elementos cada
self.Y = [0] * 3 # Altura do tiro
self.F = [0] * 3 # Estado do tiro
self.tiros_disparados = 0 # tiros já disparados
self.velocidade_do_tiro = 5
# Gera um número aleatório entre 0 e 6 + 4
self.velocidade_aviao = random.randint(0, 6) + 4
# Gera um número aleatório entre 0 e 135 + 11
self.altura_do_aviao = random.randint(0, 135) + 11
self.estado = "jogando"
self.posicao_do_aviao = self.velocidade_aviao
self.pressionado = False
self.label = None
def atualiza(self):
if self.estado == "jogando":
self.jogue()
else:
if self.label:
glMatrixMode(gl.GL_PROJECTION)
glLoadIdentity()
glOrtho(0, self.window.width, 0, self.window.height, -1, 1)
glMatrixMode(gl.GL_MODELVIEW)
self.label.draw()
if self.pressionado:
pyglet.app.event_loop.exit()
def jogue(self):
self.desenha_bases()
if self.posicao_do_aviao > 265:
self.perdeu()
return
self.y = 159 - self.altura_do_aviao
self.X = self.posicao_do_aviao
self.C = 3
self.desenha_aviao()
if self.pressionado and self.tiros_disparados < 3:
self.F[self.tiros_disparados] = 1
self.tiros_disparados += 1
self.pressionado = False
self.processa_tiros()
self.posicao_do_aviao += self.velocidade_aviao
def processa_tiros(self):
for self.J in range(0, 3):
if self.F[self.J] == 0 or self.Y[self.J] > 145:
continue
self.Y[self.J] += self.velocidade_do_tiro
self.C = 3
self.desenha_tiro()
self.X = (self.J + 1) * 70 - self.posicao_do_aviao
self.y = self.altura_do_aviao - self.Y[self.J]
if self.X > -1 and self.X < 15 and self.y > -9 and self.y < 5:
self.acertou()
def perdeu(self):
self.imprima("MISSED")
self.muda_estado("fimdejogo")
def acertou(self):
self.imprima("HIT!!!")
self.muda_estado("fimdejogo")
def desenha_aviao(self):
self.set_color(self.C)
pyglet.graphics.draw(6, pyglet.gl.GL_LINE_LOOP,
('v2i', (self.X, self.y,
self.X, self.y - 8,
self.X + 3, self.y - 2,
self.X + 12, self.y - 2,
self.X + 14, self.y,
self.X, self.y)))
def desenha_tiro(self):
self.set_color(self.C)
J = self.J + 1
pyglet.graphics.draw(2, pyglet.gl.GL_LINES,
('v2i', (70 * J, 158 - self.Y[self.J],
70 * J, 154 - self.Y[self.J])))
def desenha_bases(self):
self.set_color(3)
for J in range(1, 4):
pyglet.graphics.draw(2, pyglet.gl.GL_LINES,
('v2i', (70 * J - 5, 159,
70 * J + 5, 159)))
def set_color(self, color):
glColor3f(*self.cor[color])
def muda_estado(self, estado):
print("Mudança de Estado: {} --> {}".format(self.estado, estado))
self.estado = estado
def imprima(self, mensagem):
self.label = self.window.crialabel(mensagem)
class Missile(pyglet.window.Window):
def __init__(self):
self.scale = 4 # Escala os gráficos 4x
self.frames = 8 # Frames por segundo.
# Define quantas vezes vamos atualizar a tela por segundo
super(Missile, self).__init__(280 * self.scale,
192 * self.scale,
caption="Missiles")
self.set_mouse_visible(False)
self.game = Game(self)
self.schedule = pyglet.clock.schedule_interval(
func=self.update, interval=1.0 / self.frames)
def update(self, interval):
pass
def on_draw(self):
window.clear()
self.game.atualiza()
def on_resize(self, width, height):
# Inicializa a view
glViewport(0, 0, width, height)
glMatrixMode(gl.GL_PROJECTION)
glLoadIdentity()
# Inverte as coordenadas do eixo Y
glOrtho(0, width, height, 0, -1, 1)
# Aplica a escala
glScalef(self.scale, self.scale, 1.0)
glMatrixMode(gl.GL_MODELVIEW)
def on_key_press(self, symbol, modifiers):
self.game.pressionado = True
def crialabel(self, mensagem, x=None, y=None,
fonte='Times New Roman', tamanho=36):
"""Prepara uma mensagem de texto para ser exibida"""
x = x or self.width // 2
y = y or self.height // 2
return pyglet.text.Label(
mensagem, font_name=fonte, font_size=tamanho,
x=x, y=y, anchor_x='center', anchor_y='center')
window = Missile()
pyglet.app.run()
</pre>
<br />
Em um próximo artigo, vou continuar a refatorar o código. Tiro e Avião são claramente classes. Animação deixa a desejar. Mas é o divertido da programação, você sempre pode melhorar.Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com0tag:blogger.com,1999:blog-8312361233856668827.post-66869920086283362042015-09-30T04:01:00.002-04:002015-09-30T04:01:57.658-04:00Ajude a manter a Fundação Nokia abertaA Fundação Nokia é uma instituição de ensino médio, localizada em Manaus, Amazonas. Atuando há 30 anos na região, a instituição teve vários nomes como CEPI, FEPEMM, FMM e finalmente Fundação Nokia a partir de 2001. Com a venda da Nokia, a Fundação Nokia passou a ser mantida pela Microsoft.<br />
<br />
Este ano, pela primeira vez em 30 anos, o processo seletivo da instituição foi suspenso.<br />
Se esta situação continuar, a Fundação Nokia vai desaparecer em 3 anos.<br />
<br />
Como ex-alunos desta instituição, pedimos que você entre em contato com nossos representantes, enviando um e-mail explicando suas razões para ajudar a manter a Fundação aberta.<br />
<br />
Governo do Amazonas<br />
<a href="http://www.amazonas.am.gov.br/fale-conosco/">http://www.amazonas.am.gov.br/fale-conosco/</a><br />
<br />
Vereadores de Manaus:<br />
<a href="http://www.cmm.am.gov.br/vereador/">http://www.cmm.am.gov.br/vereador/</a><br />
<br />
Deputados Estaduais do Amazonas:<br />
<a href="http://www.ale.am.gov.br/deputados/">http://www.ale.am.gov.br/deputados/</a><br />
<br />
Deputados Federais da bancada do Amazonas:<br />
<a href="http://www.camara.gov.br/internet/deputado/Dep_Lista_foto.asp?Legislatura=55&Partido=QQ&SX=QQ&Todos=None&UF=AM&condic=QQ&forma=lista&nome=&ordem=nome&origem=None">http://www.camara.gov.br/internet/deputado/Dep_Lista_foto.asp?Legislatura=55&Partido=QQ&SX=QQ&Todos=None&UF=AM&condic=QQ&forma=lista&nome=&ordem=nome&origem=None</a><br />
<br />
Senadores do Amazonas:<br />
<a href="http://www25.senado.leg.br/web/senadores/por-uf/-/uf/AM">http://www25.senado.leg.br/web/senadores/por-uf/-/uf/AM</a><br />
<br />
Vamos tentar também via Twitter, eu já enviei alguns pelo <b><a href="https://twitter.com/lskbr">@lskbr</a></b> (retuites ajudam!), mas você também pode enviar diretamente ao CEO da Microsoft:<br />
<br />
Satya Nadella - CEO Microsoft<br />
<b><a href="https://twitter.com/satyanadella?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor">@satyanadella</a></b><br />
<br />
Para quem não conhece a Fundação, esta página tem várias novidades: <a href="http://www.fundacaonokia.org/noticias">http://www.fundacaonokia.org/noticias</a><br />
<br />
Não podemos ficar calados e esperar que esta escola feche. Ajude a manter a Fundação Nokia aberta, solicitando a Microsoft mais tempo para encontrar uma outra instituição mantenedora e exigindo a presença de nossos políticos.Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com1tag:blogger.com,1999:blog-8312361233856668827.post-88078683217827078872014-08-30T14:45:00.002-04:002019-02-17T12:39:44.081-04:00Servidor de Chat com websockets e asyncio<a href="https://blog.nilo.pro.br/posts/2014-08-30-servidor-de-chat-com-websockets-e-asyncio/">Leia no novo blog</a><br />
<br />
Já é hora de escrever sobre um projeto mais completo. Aproveito para mostrar o módulo <a href="https://github.com/aaugustin/websockets/tree/master/websockets" target="_blank">websockets</a> para Python 3.4 que funciona muito bem com asyncio. Para não ter problemas de interface, eu resolvi escrever o cliente do chat em JavaScript. O cliente de exemplo foi <a href="http://www.websocket.org/echo.html" target="_blank">baixado daqui</a>. Como quase sempre, os exemplos são muito simples e nos deixam com água na boca sobre o que poderíamos realmente fazer. Quem já tentou escrever um chat em JavaScript sabe que WebSockets são uma mão na roda.<br />
<br />
Este artigo faz parte de uma série que escrevo sobre o asyncio do Python 3.4. Você pode ler os outros artigos clicando aqui: <a href="http://junglecoders.blogspot.be/2014/06/python-asyncio-metodos-assincronos-em.html" target="_blank">Python e asyncio</a>, <a href="http://junglecoders.blogspot.be/2014/08/asyncio-e-corotinas.html" target="_blank">Asyncio e corotinas</a> e o <a href="http://junglecoders.blogspot.be/2014/08/asyncio-lendo-o-teclado.html" target="_blank">Lendo o teclado</a>.<br />
<br />
A ideia de usarmos WebSockets visa demonstrar a facilidade do módulo websockets que deve ser instalado no Python 3.4:<br />
<br />
<pre class="brush:shell">pip install websockets
</pre>
<br />
Uma vez instalado o módulo, podemos criar um servidor, como o mostrado na documentação do <a href="http://aaugustin.github.io/websockets/#module-websockets.http" target="_blank">módulo</a>:<br />
<br />
<pre class="brush:python">import asyncio
import websockets
@asyncio.coroutine
def hello(websocket, path):
name = yield from websocket.recv()
print("< {}".format(name))
greeting = "Hello {}!".format(name)
yield from websocket.send(greeting)
print("> {}".format(greeting))
start_server = websockets.serve(hello, 'localhost', 8765)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
</pre>
<br />
Execute o servidor com:<br />
<pre class="brush:shell">py -3 server.py
</pre>
<br />
Para testar este programa, precisaremos de uma página com nosso cliente de chat. Eu preparei uma página com todo o código html e javascript necessário. Baixe a página <a href="http://static.nilo.pro.br/artigos/cliente.html">cliente.html aqui</a>. Salve o arquivo e abra-o com seu browser preferido: Chrome, Firefox ou o IE (>10).<br />
<br />
Como o servidor é muito simples, tudo que podemos fazer é enviar uma mensagem e recebê-la de volta, já que é um servidor do tipo Echo (eco). Veja também que o servidor de WebSockets é inicializado como nossos outros servidores com o módulo asyncio, mas que este não obedece uma interface com métodos definidos para conexão, recebimento de dados etc. O objeto retornado pelo módulo websockets é um objeto com o cliente já conectado. Vejamos primeiramente como criar o servidor:<br />
<br />
<pre class="brush:python">start_server = websockets.serve(hello, 'localhost', 8765)</pre>
<br />
A linha acima cria nosso servidor, chamando a função <b>hello</b> sempre que um novo cliente for executado. O nome <b>localhost</b> se refere a nosso computador e 8765 é a porta que utilizaremos para receber as conexões. Vejamos a função <b>hello</b>. Quando uma nova conexão for recebida, a função <b>hello</b> será chamado com dois parâmetros: o primeiro é o cliente já conectado e pronto para ser utilizado; e o segundo é o <b>path</b> ou o caminho usado (veremos isso depois em outro artigo). Na realidade, a corotina <b>hello</b> é responsável pelo tempo de vida e gestão da conexão do cliente. Quando a corotina <b>hello</b> termina, o cliente é desconectado. Veja também que usamos o <b>yield from</b> para enviar e receber dados. O uso do <b>yield from</b> permite que escrevamos nosso código como se sua execução fosse sequencial, como já discutimos nos outros artigos.<br />
<br />
Um detalhe importante a notar é que a interface do módulo websockets já entrega os dados no formato de mensagem (como definido pelo protocolo). Diferentemente de um socket TCP/IP comum que trabalha com streams, entregando bytes. Quando o método <b>recv</b> retorna, uma mensagem inteira foi recebida, pouca importa quantos <b>read</b> foram feitos para completar esta tarefa. Esta característica vai facilitar muito a implementação do servidor de chat, uma vez que não precisaremos inventar um delimitador de mensagem, nem separar as mensagens manualmente em nosso código.<br />
<br />
Em relação a nosso servidor de chat, o servidor de exemplo é bem limitado. A maior limitação é não permitir a comunicação entre clientes. A ideia do servidor de chat é enviar mensagens a todos os clientes conectados. Desta forma, o servidor deve ser informado sobre e registrar todas as conexões e desconexões do sistema. Vamos manter a lista dos clientes conectados com uma lista. Nossos clientes serão controlados por uma classe <b>Cliente</b> que veremos mais tarde. Observe a implementação parcial da classe <b>Servidor</b>:<br />
<br />
<pre class="brush:python">class Servidor:
def __init__(self):
self.conectados = []
@property
def nconectados(self):
return len(self.conectados)
@asyncio.coroutine
def conecta(self, websocket, path):
cliente = Cliente(self, websocket, path)
if cliente not in self.conectados:
self.conectados.append(cliente)
print("Novo cliente conectado. Total: {0}".format(self.nconectados))
yield from cliente.gerencia()
def desconecta(self, cliente):
if cliente in self.conectados:
self.conectados.remove(cliente)
print("Cliente {1} desconectado. Total: {0}".format(self.nconectados, cliente.nome))
</pre>
<br />
Veja que apenas <b>conecta</b> é uma corotina. Na realidade, todo o trabalho é feito na classe <b>Cliente</b>, que disponibiliza o método <b>gerencia</b> como uma corotina. O importante agora é entender a manutenção da lista de conexões ativas.<br />
<br />
Vejamos a classe <b>Cliente</b> (parcial):<br />
<br />
<pre class="brush:python">class Cliente:
def __init__(self, servidor, websocket, path):
self.cliente = websocket
self.servidor = servidor
self.nome = None
@property
def conectado(self):
return self.cliente.open
@asyncio.coroutine
def gerencia(self):
try:
yield from self.envia("Bem vindo ao servidor de chat escrito em Python 3.4 com asyncio e WebSockets. Identifique-se com /nome SeuNome")
while True:
mensagem = yield from self.recebe()
if mensagem:
print("{0} < {1}".format(self.nome, mensagem))
yield from self.processa_comandos(mensagem)
else:
break
except Exception:
print("Erro")
raise
finally:
self.servidor.desconecta(self)
</pre>
<br />
Como cada cliente tem seu próprio websocket e precisa se comunicar com o servidor, guardaremos estas referências como atributos. Já preparamos também a gestão de nomes, embora tenhamos inicializado o nome do <b>Cliente</b> com <b>None</b>. O método <b>gerencia</b>, que é uma corotina, envia uma mensagem de boas vindas ao cliente e como no exemplo anterior, utiliza <b>yield from</b> para realizar o envio no loop de eventos. Uma vez que a mensagem inicial é enviada, entramos em um loop infinito que espera uma mensagem do cliente. Quando a conexão é fechada ou acontece um erro, o valor de mensagem é igual a <b>None</b>, por isso, testamos o valor de mensagem para sair do loop infinito criado pelo <b>while True</b>. Da mesma forma que no primeiro servidor de exemplo, nosso cliente é desconectado quando a corotina <b>gerencia</b> termina. Aproveitamos o fim da corotina para informar ao servidor que este cliente está se desconectado.<br />
<br />
Ao recebermos uma mensagem, iniciamos o processamento da mesma, utilizando o método corotina <b>processa_comandos</b> da classe <b>Cliente</b>:<br />
<pre class="brush:python"> @asyncio.coroutine
def processa_comandos(self, mensagem):
if mensagem.strip().startswith("/"):
comandos=shlex.split(mensagem.strip()[1:])
if len(comandos)==0:
yield from self.envia("Comando inválido")
return
print(comandos)
comando = comandos[0].lower()
if comando == "horas":
yield from self.envia("Hora atual: " + time.strftime("%H:%M:%S"))
elif comando == "data":
yield from self.envia("Data atual: " + time.strftime("%d/%m/%y"))
elif comando == "clientes":
yield from self.envia("{0} clientes conectados".format(self.servidor.nconectados))
elif comando == "nome":
yield from self.altera_nome(comandos)
elif comando == "apenas":
yield from self.apenas_para(comandos)
else:
yield from self.envia("Comando desconhecido")
else:
if self.nome:
yield from self.servidor.envia_a_todos(self, mensagem)
else:
yield from self.envia("Identifique-se para enviar mensagens. Use o comando /nome SeuNome")
</pre>
<br />
Lembrando os bons velhos tempos do IRC, o método <b>processa_comandos</b> reconhece comandos iniciados pela barra <b>/</b>. Desta forma, caso um cliente envie para o servidor <b>/horas</b>, este retornará a hora atual do servidor. Implementamos também os comandos:<br />
<b><br /></b>
<br />
<ul>
<li><b>/data</b> que envia a data atual;</li>
<li><b>/clientes</b> que envia quantos clientes estão conectados ao servidor, </li>
<li><b>/nome</b> e <b>/apenas</b> que veremos mais adiante. </li>
</ul>
<br />
<br />
Utilizamos o módulo <b>shlex</b> para simplificar o processamento dos comandos, uma vez que a função <b>shlex.split</b> permite processar uma linha de texto como uma linha de comandos do bash, reconhecendo valores entre aspas e retirando os espaços em branco entre os parâmetros. Caso o usuário envie uma mensagem que não se inicia por uma barra, esta mensagem será enviada a todos os outros usuários conectados.<br />
<br />
Para melhorar nosso chat, utilizamos o comando <b>/nome</b> para configurar nosso nome. O servidor cuida para que apenas um usuário utilize cada nome, retornando uma mensagem de erro, caso o nome desejado já esteja em uso. Este comando é processado pelo método <b>altera_nome</b> da classe <b>Cliente</b>:<br />
<br />
<pre class="brush:python"> @asyncio.coroutine
def altera_nome(self, comandos):
if len(comandos)>1 and self.servidor.verifica_nome(comandos[1]):
self.nome = comandos[1]
yield from self.envia("Nome alterado com sucesso para {0}".format(self.nome))
else:
yield from self.envia("Nome em uso ou inválido. Escolha um outro.")
</pre>
<br />
O método <b>altera_nome</b> simplesmente verifica se passamos um parâmetro depois do comando <b>/nome</b>, pois comandos é uma lista onde cada elemento é um parâmetro (mas o primeiro é o nome do comando em si). Usando o método <b>verifica_nome</b> do servidor, checamos se o nome é único e enviamos uma mensagem de confirmação ou de erro dependendo do resultado. O método <b>verifica_nome</b> da classe <b>Servidor</b> é apresentado abaixo:<br />
<br />
<pre class="brush:python"> def verifica_nome(self, nome):
for cliente in self.conectados:
if cliente.nome and cliente.nome == nome:
return False
return True
</pre>
<br />
A verificação percorre toda a lista com os clientes conectados e verifica se um nome igual já foi registrado. Caso não encontre o nome na lista dos clientes já conectados, retorna <b>True</b>, permitindo o registro do nome pelo cliente que o solicitou.<br />
<br />
Um outro comando interessante é o <b>/apenas</b> que permite enviarmos uma mensagem apenas para determinado cliente. Vejamos a implementação do método <b>apenas_para</b> no cliente:<br />
<br />
<pre class="brush:python"> @asyncio.coroutine
def apenas_para(self, comandos):
if len(comandos)<3:
yield from self.envia("Comando incorreto. /apenas Destinatário mensagem")
return
destinatario = comandos[1]
mensagem = " ".join(comandos[2:])
enviado = yield from self.servidor.envia_a_destinatario(self, mensagem, destinatario)
if not enviado:
yield from self.envia("Destinatário {0} não encontrado. Mensagem não enviada.".format(destinatario))
</pre>
<br />
E do método que realiza o envio na classe <b>Servidor</b>:
<br />
<br />
<pre class="brush:python"> @asyncio.coroutine
def envia_a_destinatario(self, origem, mensagem, destinatario):
for cliente in self.conectados:
if cliente.nome == destinatario and origem != cliente and cliente.conectado:
print("Enviando de <{0}> para <{1}>: {2}".format(origem.nome, cliente.nome, mensagem))
yield from cliente.envia("PRIVADO de {0} >> {1}".format(origem.nome, mensagem))
return True
return False
</pre>
<br />
Um outro método importante é o que envia uma mensagem a todos os clientes conectados:<br />
<br />
<pre class="brush:python"> @asyncio.coroutine
def envia_a_todos(self, origem, mensagem):
print("Enviando a todos")
for cliente in self.conectados:
if origem != cliente and cliente.conectado:
print("Enviando de <{0}> para <{1}>: {2}".format(origem.nome, cliente.nome, mensagem))
yield from cliente.envia("{0} >> {1}".format(origem.nome, mensagem))
</pre>
<br />
Veja que enviamos a mensagem a todos da lista, mas que tomamos o cuidado para não enviar a mensagem ao mesmo cliente que a enviou, pois esta seria impressa uma segunda vez e nosso cliente Javascript já fez este trabalho por nós.
A listagem completa abaixo:
<br />
<br />
<pre class="brush:python">import asyncio
import websockets
import time
import shlex
class Servidor:
def __init__(self):
self.conectados = []
@property
def nconectados(self):
return len(self.conectados)
@asyncio.coroutine
def conecta(self, websocket, path):
cliente = Cliente(self, websocket, path)
if cliente not in self.conectados:
self.conectados.append(cliente)
print("Novo cliente conectado. Total: {0}".format(self.nconectados))
yield from cliente.gerencia()
def desconecta(self, cliente):
if cliente in self.conectados:
self.conectados.remove(cliente)
print("Cliente {1} desconectado. Total: {0}".format(self.nconectados, cliente.nome))
@asyncio.coroutine
def envia_a_todos(self, origem, mensagem):
print("Enviando a todos")
for cliente in self.conectados:
if origem != cliente and cliente.conectado:
print("Enviando de <{0}> para <{1}>: {2}".format(origem.nome, cliente.nome, mensagem))
yield from cliente.envia("{0} >> {1}".format(origem.nome, mensagem))
@asyncio.coroutine
def envia_a_destinatario(self, origem, mensagem, destinatario):
for cliente in self.conectados:
if cliente.nome == destinatario and origem != cliente and cliente.conectado:
print("Enviando de <{0}> para <{1}>: {2}".format(origem.nome, cliente.nome, mensagem))
yield from cliente.envia("PRIVADO de {0} >> {1}".format(origem.nome, mensagem))
return True
return False
def verifica_nome(self, nome):
for cliente in self.conectados:
if cliente.nome and cliente.nome == nome:
return False
return True
class Cliente:
def __init__(self, servidor, websocket, path):
self.cliente = websocket
self.servidor = servidor
self.nome = None
@property
def conectado(self):
return self.cliente.open
@asyncio.coroutine
def gerencia(self):
try:
yield from self.envia("Bem vindo ao servidor de chat escrito em Python 3.4 com asyncio e WebSockets. Identifique-se com /nome SeuNome")
while True:
mensagem = yield from self.recebe()
if mensagem:
print("{0} < {1}".format(self.nome, mensagem))
yield from self.processa_comandos(mensagem)
else:
break
except Exception:
print("Erro")
raise
finally:
self.servidor.desconecta(self)
@asyncio.coroutine
def envia(self, mensagem):
yield from self.cliente.send(mensagem)
@asyncio.coroutine
def recebe(self):
mensagem = yield from self.cliente.recv()
return mensagem
@asyncio.coroutine
def processa_comandos(self, mensagem):
if mensagem.strip().startswith("/"):
comandos=shlex.split(mensagem.strip()[1:])
if len(comandos)==0:
yield from self.envia("Comando inválido")
return
print(comandos)
comando = comandos[0].lower()
if comando == "horas":
yield from self.envia("Hora atual: " + time.strftime("%H:%M:%S"))
elif comando == "data":
yield from self.envia("Data atual: " + time.strftime("%d/%m/%y"))
elif comando == "clientes":
yield from self.envia("{0} clientes conectados".format(self.servidor.nconectados))
elif comando == "nome":
yield from self.altera_nome(comandos)
elif comando == "apenas":
yield from self.apenas_para(comandos)
else:
yield from self.envia("Comando desconhecido")
else:
if self.nome:
yield from self.servidor.envia_a_todos(self, mensagem)
else:
yield from self.envia("Identifique-se para enviar mensagens. Use o comando /nome SeuNome")
@asyncio.coroutine
def altera_nome(self, comandos):
if len(comandos)>1 and self.servidor.verifica_nome(comandos[1]):
self.nome = comandos[1]
yield from self.envia("Nome alterado com sucesso para {0}".format(self.nome))
else:
yield from self.envia("Nome em uso ou inválido. Escolha um outro.")
@asyncio.coroutine
def apenas_para(self, comandos):
if len(comandos)<3:
yield from self.envia("Comando incorreto. /apenas Destinatário mensagem")
return
destinatario = comandos[1]
mensagem = " ".join(comandos[2:])
enviado = yield from self.servidor.envia_a_destinatario(self, mensagem, destinatario)
if not enviado:
yield from self.envia("Destinatário {0} não encontrado. Mensagem não enviada.".format(destinatario))
servidor=Servidor()
loop=asyncio.get_event_loop()
start_server = websockets.serve(servidor.conecta, 'localhost', 8765)
try:
loop.run_until_complete(start_server)
loop.run_forever()
finally:
start_server.close()
</pre>
<br />
Você pode baixar o arquivo completo clicando <a href="http://static.nilo.pro.br/artigos/server2.py">aqui</a>.<br />
<br />
Para testar, abra o arquivo <a href="http://static.nilo.pro.br/artigos/cliente.html">cliente.html</a> que você já deve ter salvo em seu computador. Se você já ativou o servidor com<br />
<br />
<pre class="brush:shell">py -3 servidor2.py
</pre>
<br />
nosso cliente já deve estar conectado. Caso contrário, recarregue a página no navegador para forçar a reconexão. Quando conectado, o cliente exibe uma barra azul no topo da página. Para simular vários clientes, abra várias vezes o arquivo cliente.html. Vejamos uma sessão simples com 3 clientes:<br />
<br />
No primeiro cliente:<br />
<pre class="brush:shell">/nome Cliente1
/horas
</pre>
<br />
No segundo cliente:<br />
<pre class="brush:shell">/nome Cliente2
/data
</pre>
<br />
No terceiro cliente:<br />
<pre class="brush:shell">/nome Cliente3
/apenas Cliente1 Olá 1
/apenas Cliente2 Olá 2
Olá todos!
</pre>
<br />
Não esqueça de copiar linha por linha nas respectivas janelas. Digite ENTER para enviar a mensagem, uma de cada vez. Divirta-se!<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/-7AXfBX6aNt8/VAIbvEngJ_I/AAAAAAAAAw8/-THFqwvuqvw/s1600/clienteweb.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="232" src="https://2.bp.blogspot.com/-7AXfBX6aNt8/VAIbvEngJ_I/AAAAAAAAAw8/-THFqwvuqvw/s1600/clienteweb.png" width="320" /></a></div>
<br />Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com2tag:blogger.com,1999:blog-8312361233856668827.post-73779493711068949122014-08-23T12:15:00.001-04:002019-02-17T12:40:05.331-04:00Asyncio - Lendo o teclado<a href="https://blog.nilo.pro.br/posts/2014-08-23-asyncio-lendo-o-teclado/">Leia no novo blog</a><br />
<br />
Continuando a série sobre o módulo <b>asyncio</b> do Python 3.4, vamos ver como criar um jogo simples, em modo texto. O objetivo do jogo é simplesmente mostrar um labirinto e deixar o jogador se mover utilizando o teclado numérico (4 - esquerda, 6 - direita, 8 - cima, 2 - baixo e S para sair). Para exercitar nossos músculos da época do DOS com programação assíncrona, vamos exibir o relógio na última linha. O resultado final deve se parecer com a imagem abaixo:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://3.bp.blogspot.com/-UNULglFnVEE/U_i2AyqGLNI/AAAAAAAAAv8/jbaSKYudnGY/s1600/labirinto.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="216" src="https://3.bp.blogspot.com/-UNULglFnVEE/U_i2AyqGLNI/AAAAAAAAAv8/jbaSKYudnGY/s1600/labirinto.png" width="400" /></a></div>
<br />
Você precisa instalar o <a href="https://github.com/lskbr/colorconsole" target="_blank">colorconsole</a> e o Python 3.4.1 para executar este programa.<br />
<br />
<pre class="brush:python">from random import shuffle, randrange
from colorconsole import terminal
from concurrent.futures import ThreadPoolExecutor
import datetime
import asyncio
# Colorconsole: https://github.com/lskbr/colorconsole
# Make_make: http://rosettacode.org/wiki/Maze_generation#Python
def make_maze(w = 16, h = 8):
vis = [[0] * w + [1] for _ in range(h)] + [[1] * (w + 1)]
ver = [["| "] * w + ['|'] for _ in range(h)] + [[]]
hor = [["+--"] * w + ['+'] for _ in range(h + 1)]
def walk(x, y):
vis[y][x] = 1
d = [(x - 1, y), (x, y + 1), (x + 1, y), (x, y - 1)]
shuffle(d)
for (xx, yy) in d:
if vis[yy][xx]: continue
if xx == x: hor[max(y, yy)][x] = "+ "
if yy == y: ver[y][max(x, xx)] = " "
walk(xx, yy)
walk(randrange(w), randrange(h))
maze = []
for (a, b) in zip(hor, ver):
maze.append("".join(a))
maze.append("".join(b))
return maze
class Jogo:
LARGURA = 24
ALTURA = 11
def __init__(self):
self.tela = terminal.get_terminal(conEmu=False)
self.tela.enable_unbuffered_input_mode()
self.labirinto_cores = (terminal.colors["RED"],terminal.colors["BLACK"])
self.jogador_carac = (terminal.colors["WHITE"],terminal.colors["BLUE"],'*')
self.labirinto = make_maze(Jogo.LARGURA,Jogo.ALTURA)
self.loop = asyncio.get_event_loop()
self.tpool = ThreadPoolExecutor(max_workers=2)
while True:
self.x = randrange(Jogo.LARGURA*3)
self.y = randrange(Jogo.ALTURA*2)
if self.pode_mover(self.x, self.y):
break
self.jogando = True
def fim_do_jogo(self):
self.jogando = False
self.loop.stop()
@asyncio.coroutine
def le_teclado(self):
while self.jogando:
key = yield from self.loop.run_in_executor(self.tpool, self.tela.getch)
if(key!=None):
nx, ny = self.x, self.y
if key == b'4':
if nx > 1:
nx-=1
elif key == b'6':
if nx < Jogo.LARGURA*3-1:
nx+=1
elif key == b'8':
if ny > 0:
ny -=1
elif key == b'2':
if ny < Jogo.ALTURA*2:
ny +=1
elif key == b"S":
self.fim_do_jogo()
break
if self.pode_mover(nx,ny) and (nx, ny) != (self.x,self.y):
self.x, self.y = nx, ny
self.desenha()
def pode_mover(self, x,y):
return self.labirinto[y][x]==' '
def desenha(self):
self.tela.set_color(*self.labirinto_cores)
#self.tela.clear()
self.tela.gotoXY(0,0)
self.tela.set_title("Labirinto")
for linha in self.labirinto:
print(linha)
self.tela.gotoXY(self.x, self.y)
self.tela.cprint(*self.jogador_carac)
@asyncio.coroutine
def relogio(self):
while self.jogando:
self.tela.print_at(10,23,datetime.datetime.now().strftime("%d/%m/%y %H:%M:%S"))
yield from asyncio.sleep(1)
def execute(self):
self.tela.clear()
self.desenha()
try:
asyncio.async(self.le_teclado())
asyncio.async(self.relogio())
self.loop.run_forever()
except KeyboardInterrupt:
print("exit")
finally:
self.tpool.shutdown()
self.loop.close()
self.tela.restore_buffered_mode()
jogo = Jogo()
jogo.execute()
</pre>
Para adicionar um pouco de variação ao jogo, eu baixei uma função que gera labirintos <a href="http://rosettacode.org/wiki/Maze_generation#Python" target="_blank">deste site</a>. O programa foi modificado para retornar uma lista de strings, que é utilizada para detectar as paredes do labirinto.<br />
<br />
O loop de eventos é parecido com o dos artigos anteriores, mas desta vez um ThreadPool está sendo criado. Um thread extra é necessário, pois vamos bloquear a rotina até que uma tecla seja pressionada. Esta construção funciona com várias rotinas bloqueantes que não podem ser utilizadas com asyncio, pois interromperiam a execução de todas as outras corotinas.<br />
<br />
<pre class="brush:python"> @asyncio.coroutine
def le_teclado(self):
while self.jogando:
key = yield from self.loop.run_in_executor(self.tpool, self.tela.getch)
if(key!=None):
nx, ny = self.x, self.y
if key == b'4':
if nx > 1:
nx-=1
elif key == b'6':
if nx < Jogo.LARGURA*3-1:
nx+=1
elif key == b'8':
if ny > 0:
ny -=1
elif key == b'2':
if ny < Jogo.ALTURA*2:
ny +=1
elif key == b"S":
self.fim_do_jogo()
break
if self.pode_mover(nx,ny) and (nx, ny) != (self.x,self.y):
self.x, self.y = nx, ny
self.desenha()
</pre>
<br />
Criamos <b>le_teclado</b> como uma corotina, mas como o módulo <b>colorconsole</b> não foi criado para trabalhar com corotinas, precisamos chamar <b>self.tela.getch</b> usando um <b><a href="https://docs.python.org/3/library/concurrent.futures.html#executor-objects" target="_blank">Executor</a></b>, no caso, nosso <b><a href="https://docs.python.org/3/library/concurrent.futures.html#threadpoolexecutor" target="_blank">ThreadPool</a></b>. Desta forma, nossa chamada bloqueante será executada em um <i>thread</i> do ThreadPool e nosso loop de eventos vai continuar a executar normalmente. Quando pressionarmos uma tecla, a função <b>self.tela.getch</b> vai retornar e a partir daí, trabalharemos com o resultado no <b>yield from</b>. O resto do método verifica se a tecla é de movimento ou de saída do jogo. No final, verificamos se o jogador se mexeu ou se a nova posição seria a de uma parede. Caso a posição seja alterada, redesenhamos a tela, para que a nova posição do jogador seja visível.<br />
<br />
<pre class="brush:python"> @asyncio.coroutine
def relogio(self):
while self.jogando:
self.tela.print_at(10,23,datetime.datetime.now().strftime("%d/%m/%y %H:%M:%S"))
yield from asyncio.sleep(1)
</pre>
<br />
A corotina <b>relogio</b> exibe a data e hora atuais a cada 1 segundo na tela. Esta execução ocorre mesmo se nada pressionarmos no teclado, confirmado a correta execução de nosso loop de eventos. Este programa pode ser executado para conter inimigos e movê-los em outra corotina, similar a do relógio.<br />
<br />
Testado no Windows 8.1 com Python 3.4.1, colorconsole 0.7.1.Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com0tag:blogger.com,1999:blog-8312361233856668827.post-32145208472515267832014-08-10T12:43:00.001-04:002019-02-17T12:40:32.819-04:00Asyncio e corotinas<a href="https://blog.nilo.pro.br/posts/2014-08-10-asyncio-e-corotinas/">Leia no novo blog</a><br />
<br />
Continuando a série sobre o módulo <a href="https://docs.python.org/3/library/asyncio.html" target="_blank">asyncio</a> do Python 3.4, vou apresentar as corotinas e como elas simplificam a escrita de nossos programas com o <b>loop</b> de eventos. Com a saída do Python 3.4, eu atualizei o livro de <a href="http://python.nilo.pro.br/index.html" target="_blank">Introdução à Programação com Python</a>. Alguns assuntos fogem ao escopo do livro que é destinado a iniciantes. Eu estou continuando uma série de posts curtos sobre alguns tópicos que acho interessantes e quem sabe até podem virar base para um novo livro. <a href="http://junglecoders.blogspot.be/2014/06/python-asyncio-metodos-assincronos-em.html" target="_blank">Clique aqui</a> para ler o primeiro artigo.<br />
<br />
No artigo anterior, apresentamos uma chamada ao <b>loop</b> de eventos bem simples:
<br />
<pre class="brush:python">import asyncio
def print_and_repeat(loop):
print('Hello World')
loop.call_later(2, print_and_repeat, loop)
loop = asyncio.get_event_loop()
loop.call_soon(print_and_repeat, loop)
loop.run_forever()
</pre>
<br />
Vejamos como reescrever este exemplo simples usando corotinas:
<br />
<pre class="brush:python">import asyncio
@asyncio.coroutine
def print_and_repeat(loop):
while True:
print('Hello World')
yield from asyncio.sleep(2)
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(print_and_repeat(loop))
finally:
loop.close()
</pre>
<br />
Este exemplo foi extraído da <a href="https://docs.python.org/3/library/asyncio-task.html#example-hello-world-coroutine" target="_blank">documentação do Python</a>, vamos ver o que mudou. A principal mudança no início do programa é o uso do decorador <b>@asyncio.coroutine</b>. Este decorador transforma nossa função em uma corotina e permite a utilização do <b>yield from</b>, como definido na <a href="http://legacy.python.org/dev/peps/pep-0380/" target="_blank">PEP380</a>. Veja que o cabeçalho da função não foi alterado, mas que substituímos a chamada de <b>loop.call_later</b> pela combinação de um <b>while True</b> com um <b>yield from</b> no final. Uma corotina pode ser suspensa e esperar o processamento de uma outra corotina. No caso, <b>asyncio.sleep(2)</b> é uma corotina do módulo <b>asyncio</b> que suspende a execução da função pelo número de segundos passados como parâmetro. Na realidade, esta chamada retorna uma corotina que é marcada como completa após os 2 segundos. Isto pode ser realizado pois no <b>yield from</b>, criamos a nova corotina e indicamos ao <b>loop</b> que não continue a executar <b>print_and_repeat</b> até que a nova corotina esteja concluída. A partir deste ponto, a execução volta ao <b>loop</b> de eventos que monitora a conclusão da nova corotina criada, suspendendo a execução da anterior. Uma vez que o a corotina do <b>sleep</b> é concluída, após 2 segundos, o <b>loop</b> reativa a chamada suspensa de <b>print_and_repeat</b> e a execução continua, voltando para o <b>while</b>. Parece complicado, mas veja como ficou fácil de escrevermos a função. Fica bem mais claro nossa intenção de realizar uma repetição do <b>print('Hello World')</b> a cada 2 segundos.<br />
<br />
Modificamos também a chamada de execução da corotina, pois agora utilizamos <b>loop.run_until_complete</b> para iniciar nossa corotina principal. Aproveitamos para colocar tudo entre um <b>try...finally</b> para terminar a execução do <b>loop</b> corretamente (mesmo em caso de exceção). Perceba que no exemplo anterior, com <b>call_soon</b>, passamos a função e seus parâmetros, mas não executamos a função em si. No caso de <b>run_until_complete</b>, estamos passando o retorno da chamada de <b>print_and_repeat</b> que é uma corotina, uma vez que a marcamos com o decorador <b>@asyncio.coroutine</b>.<br />
<br />
No post anterior comparamos a velocidade de execução entre as várias formas de se executar código em paralelo com Python. Agora veremos como usar o <b>asyncio</b> para criar uma aplicação prática, como um cliente e um servidor TCP/IP, mas indo além dos exemplos da documentação do Python. É preciso lembrar que o módulo <b>asyncio</b> ainda é muito novo e que tanto a documentação quanto a implementação de algumas funcionalidades ainda estão sendo alteradas.<br />
<br />
Vamos começar pelo servidor. Um servidor TCP/IP é um exemplo clássico de programa chato a escrever. Normalmente, você pode escolher utilizar <b>threads</b> ou se aventurar com <b>select</b> e chamadas não bloqueantes para gerenciar várias conexões. Este problema se agrava em aplicações mais complexas, onde algum processamento precisa ser realizado antes de se gerar a resposta, por exemplo, a um comando do usuário. Usando o módulo <b>asyncio</b>, esta tarefa fica bem mais fácil. Primeiro, porque o tratamento de dados é gerenciado por uma classe, responsável pelo protocolo. Esta classe traz métodos que são chamados em situações comuns ao programarmos um servidor TCP/IP, como chegada de uma nova conexão, desconexão, chegada de dados para leitura entre outras. Além disso, o <b>asyncio</b> também traz classes especializadas em quebrar os dados em linhas, o que facilita a implementação de protocolos com comandos em formato texto, terminados por enter (CR).<br />
<br />
O servidor é controlado por uma classe chamada <b>EchoServer</b>, pois o desenvolvi a partir do servidor de Echo dado como exemplo na documentação, mas com alguns detalhes que observei no código do módulo <b>asyncio</b>. O protocolo implementado é bem simples, a cada linha, a data e hora atuais são enviadas. Se o cliente enviar sair a conexão é terminada. Vamos ver o programa completo e discutir parte por parte.<br />
<br />
<pre class="brush:python">import asyncio
import time
from common import *
class EchoServer(asyncio.streams.FlowControlMixin):
ativas = 0
def connection_made(self, transporte):
peername = transporte.get_extra_info('peername')
print('Conexão de {}'.format(peername))
EchoServer.ativas+=1
print("Conexões ativas: {}".format(EchoServer.ativas))
self.transporte = transporte
self.leitor = asyncio.StreamReader()
self.leitor.set_transport(self.transporte)
self.escritor = asyncio.StreamWriter(transport=self.transporte, protocol=self, reader=self.leitor, loop=asyncio.get_event_loop())
asyncio.async(self.gerencia())
@asyncio.coroutine
def gerencia(self):
while True:
dados = yield from self.leitor.readline()
self.escritor.write(strToByte(time.strftime("%c")+"\r\n"))
yield from self.escritor.drain()
comando = byteToStr(dados).strip().lower()
if(comando == "sair"):
self.transporte.close()
return
def connection_lost(self, exp):
EchoServer.ativas-=1
print("Conexões ativas: {}".format(EchoServer.ativas))
super().connection_lost(exp)
def data_received(self, dados):
print('data received: {0}'.format(byteToHex(dados)))
print(' received: {0}'.format(strPrintable(dados)))
print(' string: {0}'.format(byteToStr(dados)))
self.leitor.feed_data(dados)
loop = asyncio.get_event_loop()
coro = loop.create_server(EchoServer, '127.0.0.1', 8888)
servidor = loop.run_until_complete(coro)
print('Escutando {}'.format(servidor.sockets[0].getsockname()))
try:
loop.run_forever()
except KeyboardInterrupt:
print("exit")
finally:
servidor.close()
loop.close()
</pre>
<br />
A classe <b>EchoServer</b> herda de uma classe fornecida em <b>asyncio.streams</b>, chamada <b>FlowControlMixin</b>. A classe <b>FlowControlMixin</b> é por sua vez derivada de <b>Protocols</b>, também fornecida pelo módulo <b>asyncio</b>. A ideia desta classe é implementar um <i>protocol factory</i>, ou seja, um construtor de instâncias responsáveis pela implementação da gestão de cada nova conexão. Um protocolo normal, precisa herdar apenas de <b>Protocols</b>, mas para utilizar alguns métodos para leitura buferizada de linhas, especialmente o <b>write.drain</b> que veremos logo após, a implementação contida em <b>FlowControlMixin</b> é interessante.<br />
<br />
Aproveitamos a nova classe para contar o número de conexões ativas. Cada nova conexão recebida por nosso servidor TCP/IP chama o construtor de nossa classe e o método <b>connection_made</b>, passando o transporte (entenda como o <i>socket</i> já conectado) como parâmetro.<br />
<br />
Utilizando o parâmetro <b>transporte</b>, chamamos o método <b>get_extra_info('peername')</b> para obter o endereço do cliente que acabou de se conectar ao servidor. Logo em seguida, incrementamos o número de conexões. Veja que como o código que roda no <b>loop</b> de eventos não é <i>multi-threaded</i>, não precisamos de <i>locks</i> ou de outros mecanismos de controle, já que apenas uma função roda a cada vez. O resto do método <b>connection_made</b> prepara as instâncias do leitor e do escritor, objetos das classes <b>StreamReader</b> e <b>StreamWriter</b> respectivamente. Estes objetos vão fornecer corotinas úteis para ler e escrever os dados de forma não bloqueante. Veja que passamos <b>transporte</b> tanto para o escritor quanto para o leitor e que uma série de parâmetros são necessários para a inicialização do escritor.<br />
<br />
No fim de <b>connection_made</b>, usamos a função <b>asyncio.async</b> para iniciar o processamento da corotina <b>self.gerencia</b>, dentro do <b>loop</b> de eventos. Veja que o método <b>gerencia</b> foi marcado com o decorador <b>@asyncio.coroutine</b>.<br />
<br />
O método gerencia contém uma estrutura de repetição <b>while</b> que espera uma linha do cliente. Veja que utilizamos <b>yield from</b> para suspender a execução de <b>gerencia</b> enquanto <b>self.leitor.readline()</b> não terminar. Neste ponto, a execução volta para o <b>loop</b> de eventos e retorna apenas quando <b>self.leitor.readline()</b> contém uma linha enviada pelo cliente ou caso uma exceção tenha ocorrido. O uso do <b>yield from</b> é fundamental, pois caso o <b>readline()</b> esperasse o cliente enviar a linha para continuar a execução, todo o <b>loop</b> de eventos seria bloqueado. Como o uso do <b>yield from</b>, a execução volta para o <b>loop</b> que é livre para executar outros métodos e outras corotinas. O objetivo é não fazer o computador esperar por dados ou resultados que demoram muito tempo (ou um tempo desconhecido, possivelmente longo, para retornar). Outra característica de <b>yield from</b> é que o resultado do <b>self.leitor.readline()</b> é retornado e no caso, armazenado na variável dados.<br />
<br />
A execução segue normalmente e nosso servidor envia a hora e a data atual, veja que uma linha foi acrescentada ao final da string. Este fim de linha é importante, pois como nosso protocolo é em formato texto e orientado a linhas, esperamos o enter (CR) para processar o comando ou a resposta.<br />
Depois de escrever a resposta, usamos <b>self.escritor.drain()</b> que é uma outra corotina. Esta nova corotina não completa até o que o <i>buffer</i> de escrita seja enviado. Desta forma, podemos garantir que os dados foram enviados (ainda que não possamos ter certeza se estes foram recebidos pelo cliente) antes de continuarmos. Como usamos <b>yield from</b> com esta corotina, a execução é suspensa até que o <b>drain</b> seja completado.<br />
<br />
Como usamos Python 3.4, os dados são do tipo <b>byte</b> e não string. A função <b>byteToStr</b> converte de bytes para string, usando a codificação UTF-8. Esta função será apresentada no programa <b>common.py</b>, compartilhando rotinas úteis tanto para nosso cliente quanto para nosso servidor. Para facilitar o processamento de comandos, retiramos os espaços em branco do início e fim do comando, inclusive enter (CR) e LF, e convertemos o resultado para minúsculas com <b>lower</b>. Se o comando for igual a "sair", chamamos o método <b>close</b> de <b>self.transporte</b> para encerarmos a conexão. Veja que ao fecharmos a conexão, finalmente retornamos como em uma função normal, utilizando <b>return</b> e terminando assim a execução de nossa corotina <b>gerencia</b>.<br />
<br />
O método <b>connection_lost</b> é chamado quando a desconexão do cliente é detectada. O número de conexões ativas é decrementado e o método <b>connection_lost</b> da superclasse é chamado. O parâmetro <b>exp</b> contém <b>None</b> caso seja uma desconexão normal ou a exceção em caso de erro. Neste exemplo não estamos tratando os possíveis erros para nos concentrarmos no <b>asyncio</b>.<br />
<br />
Já o método <b>data_received</b> é chamado sempre que dados forem recebidos pelo transporte. Os dados recebidos são passados como parâmetro (<b>dados</b>). Aqui, incluí algumas funções de <i>debug</i> que exibem os dados em formato hexadecimal, string e UTF-8. Estas funções são necessárias para verificarmos se os dados estão chegando no formato esperado. Você pode executar um teste com um programa de telnet clássico, como o Putty no Windows, mas não esqueça de desativar a opção de negociação do protocolo, para evitar que comandos que não interpretamos sejam enviados, ou simplesmente, teste com o cliente que é apresentado logo abaixo.<br />
<br />
Um detalhe muito importante de <b>data_received</b> é a chamada do método <b>self.leitor.feed_data</b>, que envia os dados para o <b>leitor</b>, responsável por quebrar os dados em linhas.<br />
<br />
Em nosso programa principal, obtemos o <b>loop</b> de eventos com <b>get_event_loop()</b> e criamos uma corotina que inicializa nosso servidor com <b>loop.create_server</b>. Em <b>create_server</b>, informamos o endereço que nosso servidor irá escutar (ip e porta). Veja que a classe <b>EchoServer</b> foi passada como <i>protocol factory</i>.<br />
<br />
Ao chamarmos <b>loop.run_until_complete(coro)</b>, o <b>loop</b> de eventos roda até que a corotina criada pelo <b>create_server</b> termine, retornando um objeto servidor, utilizado para parar o servidor e para ter acesso a todas as conexões, mas isso fica para outro post.<br />
<br />
Chamamos <b>loop.run_forever()</b> para ativar nosso servidor. Para desativá-lo, digite CTRL+C.<br />
<br />
Neste ponto, o endereço 127.0.0.1, porta 8888 estará recebendo conexões. Quando uma conexão for recebida, uma nova instância de <b>EchoServer</b> será criada. Ao se estabelecer a conexão o método <b>connection_made</b> será chamado e ativará uma corotina <b>gerencia</b> para gerenciar a recepção e o envio de linhas de comandos. O método <b>data_received</b> é chamado sempre que novos dados forem recebidos (seja uma linha completa ou não). O método <b>connection_lost</b> é chamado quando o cliente se desconectar.<br />
<br />
Vejamos o código fonte de <b>common.py</b>:<br />
<br />
<pre class="brush:python">import string
def byteToHex(data, sep=" "):
return sep.join("{0:02X}".format(x) for x in data)
def strPrintable(data, sep=" "):
return sep.join("{0:2s}".format(chr(s) if chr(s) in string.printable and s>30 else ".") for s in data)
def strToByte(s, encoding ="utf-8"):
return s.encode(encoding)
def byteToStr(data, encoding ="utf-8"):
return data.decode(encoding, errors="replace")
</pre>
<br />
E o código fonte de nosso cliente.py:<br />
<br />
<pre class="brush:python">import asyncio
import time
from common import *
class EchoClient(asyncio.streams.FlowControlMixin):
def connection_made(self, transporte):
peername = transporte.get_extra_info('peername')
print('Conectado à {}'.format(peername))
self.transporte = transporte
self.leitor = asyncio.StreamReader()
self.leitor.set_transport(self.transporte)
self.escritor = asyncio.StreamWriter(transport=self.transporte, protocol=self, reader=self.leitor, loop=asyncio.get_event_loop())
asyncio.async(self.gerencia())
self.feito = asyncio.Future()
@asyncio.coroutine
def gerencia(self):
for x in range(10):
self.escritor.write(strToByte("Alô\r\n"))
yield from self.escritor.drain()
dados = yield from self.leitor.readline()
self.escritor.write(strToByte("sair\r\n"))
yield from self.escritor.drain()
self.transporte.close()
self.feito.set_result(True)
def connection_lost(self, exp):
print("Conexão perdida")
super().connection_lost(exp)
def data_received(self, dados):
print('dados recebidos: {0}'.format(byteToHex(dados)))
print(' recebidos: {0}'.format(strPrintable(dados)))
print(' string: {0}'.format(byteToStr(dados)))
self.leitor.feed_data(dados)
loop = asyncio.get_event_loop()
coro = loop.create_connection(EchoClient, '127.0.0.1', 8888)
transporte, protocolo = loop.run_until_complete(coro)
try:
loop.run_until_complete(protocolo.feito)
except KeyboardInterrupt:
pass
finally:
loop.close()
</pre>
<br />
Execute o servidor e depois o cliente, cada em uma janela ou terminal diferente. Veja que o cliente termina sua execução após enviar 10 vezes o comando Alô e sair. Execute várias vezes o cliente e veja que o servidor continua ativo. Experimente aumentar o número de comandos de 10 para 100 no cliente e reexecute. Tente executar a partir de uma terceira janela outro cliente simultaneamente. Observe que conseguimos implementar um cliente e um servidor TCP/IP em um pouco mais de 100 linhas de código em Python. Um servidor capaz de atender vários clientes sem utilizar múltiplos threads.
Você pode comentar ou remover os <b>prints</b> que não precisar, eles servem apenas para <i>debugar</i>.<br />
<br />
O código do cliente é muito parecido com o código do servidor. A principal mudança é o método <b>gerencia</b> e o atributo <b>self.feito</b>, criado com <b>asyncio.Future()</b>. Vejamos a criação da instância de nosso cliente. A função <b>create_conection</b> é na realidade uma corotina que recebe o protocolo (no caso <b>EchoClient</b>), o ip e a porta do servidor. Ao chamarmos <b>loop.run_until_complete(coro)</b>, uma tupla com o <b>transporte</b> e o <b>protocolo</b> é retornada. Este retorno é importante, pois precisamos ter acesso a instância de <b>EchoClient</b> criada para gerenciar nossa conexão, no caso protocolo. Com a instância de <b>EchoClient</b> retornada em <b>protocolo</b>, podemos rodar o <b>loop</b> até que feito seja marcada como finalizada: <b>loop.run_until_complete(protocolo.feito)</b>. Esta etapa é importante, pois devemos executar o <b>loop</b> até que o cliente tenha tempo para terminar seu trabalho. Veja que no final de <b>gerencia</b>, marcamos <b>self.feito</b> como concluída: <b>self.feito.set_result(True)</b>.<br />
<br />
Se você quiser testar o servidor com vários clientes simultaneamente, adicione a seguinte função ao código de <b>cliente.py</b> e modifique o programa principal para:<br />
<br />
<pre class="brush:python">@asyncio.coroutine
def roda_varias(vezes):
pendente = []
for t in range(vezes):
pendente.append(asyncio.async(loop.create_connection(EchoClient, '127.0.0.1', 8888)))
for y in pendente:
transporte, protocolo = yield from y
yield from asyncio.wait_for(protocolo.feito, None)
loop = asyncio.get_event_loop()
coro = loop.create_connection(EchoClient, '127.0.0.1', 8888)
client = loop.run_until_complete(coro)
try:
loop.run_until_complete(roda_varias(100))
except KeyboardInterrupt:
pass
finally:
loop.close()
</pre>
<br />
Você também pode utilizar <b>start_server</b> e <b>open_connection</b> para receber diretamente uma tupla com <b>StreamReader</b> e <b>StreamWriter</b>, mas estes exemplos você pode encontrar na documentação do Python.<br />
<br />
No próximo artigo, uma nova classe comum será usada para gerenciar os protocolos e outra forma de instanciação será passada para realizar uma comunicação entre vários clientes.<br />
<br />
<br />
<br />Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com0tag:blogger.com,1999:blog-8312361233856668827.post-6901968428008135192014-06-28T16:19:00.000-04:002019-02-17T12:40:58.492-04:00Python asyncio - Métodos assíncronos em Python<a href="https://blog.nilo.pro.br/posts/2014-06-28-python-asyncio-metodos-assincronos-em-python/">Leia no novo blog</a><br />
<br />
Com a saída do Python 3.4, eu atualizei o livro de <a href="http://python.nilo.pro.br/index.html" target="_blank">Introdução à Programação com Python</a>. Alguns assuntos fogem ao escopo do livro que é destinado a iniciantes. Eu vou começar a escrever uma série de posts curtos sobre alguns tópicos que acho interessantes e quem sabe até podem virar base para um novo livro.<br />
<br />
Uma das novidades do Python 3.4 é o módulo <a href="https://docs.python.org/3/library/asyncio.html" target="_blank">asyncio</a> que traz várias rotinas para chamada de métodos assíncronos em Python. A programação assíncrona é um pouco diferente do que normalmente estamos habituados a escrever em Python, mas é uma excelente alternativa a utilização de threads e uma boa escolha para resolver problemas com muitas entras ou saídas (<i>I/O</i>).<br />
<br />
<pre class="brush:python">import asyncio
def print_and_repeat(loop):
print('Hello World')
loop.call_later(2, print_and_repeat, loop)
loop = asyncio.get_event_loop()
loop.call_soon(print_and_repeat, loop)
loop.run_forever()
</pre>
<br />
O mecanismo usado no exemplo é bem simples. A variável <b>loop</b> contém o loop de eventos, uma vez que chamamos <b>asyncio.get_event_loop()</b> e esta retorna o <b>loop</b> de eventos atual. Na linha seguinte, chamamos o método <b>call_soon</b> para agendar a chamada de um método. <b>call_soon</b> adiciona a chamada da função <b>print_and_repeat</b>, definida anteriormente. O segundo parâmetro de <b>call_soon</b> é na realidade o parâmetro para <b>print_and_repeat</b>. Desta forma, <b>loop.call_soon(print_and_repeat, loop)</b> adiciona ao loop de eventos uma chamada a função <b>print_and_repeat</b>, passando <b>loop</b> como primeiro parâmetro. Confuso? Vamos ver o que acontece ao executarmos:<br />
<br />
<pre>Z:\artigos>c:\python34\python asyncio1.py
Hello World
Hello World
Hello World
Hello World
</pre>
<br />
O programa executa e fica na linha do <b>loop.run_forever()</b>. O método <b>run_forever()</b> processa os eventos e é necessário para o bom funcionamento do nosso programa. Experimente remover esta linha e veja que nada é impresso na tela, com o programa finalizando logo em seguida. Isto acontece porque sem <b>run_forever</b>, os eventos não são processados e o Python executa o script até o fim, finalizando sem nunca ter chamado <b>print_and_repeat</b>, ou seja, sem processar a lista de eventos.
<br />
Voltando ao nosso exemplo, você entendeu como <i>Hello Word</i> foi impresso várias vezes? Como se estivesse dentro de um for ou while? Isto acontece na última linha de <b>print_and_repeat</b>, onde com o método <b>call_later</b> agendamos uma próxima chamada a <b>print_and_repeat</b> depois de 2 segundos. Logo, o primeiro parâmetro de <b>call_later</b> é o tempo a esperar antes de chamar a função, o segundo a função em si, seguido dos parâmetros a passar a esta função, como fizemos na chamada de <b>call_soon </b>anteriormente.<br />
<br />
O código equivalente, sem utilizar eventos seria algo como:<br />
<br />
<pre class="brush:python">import time
while True:
print("Hello World")
time.sleep(2)
</pre>
<br />
Se é tão simples, por que complicar? Eventos facilitam a execução de código alternadamente, algo difícil de realizar sem utilizarmos threads. Basicamente, solicitamos ao loop de eventos para executar nossas funções de acordo com sua disponibilidade, como se passássemos em nosso programa uma lista de tarefas a executar. O loop de eventos atualiza constantemente a lista de tarefas e isto permite a execução de tarefas em ordem diferente da que estas foram incluídas.
<br />
Vejamos um outro exemplo, com <b>call_later</b>:<br />
<br />
<pre class="brush:python">import asyncio
import time
import random
def faz_algo(loop):
espera = random.random()
print("Fazendo algo... espera = %f" % espera)
loop.call_later(espera, faz_algo, loop)
def print_and_repeat(loop):
global último
agora = time.time()
print('Alô - Tempo decorrido: %f' % (agora - último))
último = agora
loop.call_later(2, print_and_repeat, loop)
último = time.time()
loop = asyncio.get_event_loop()
loop.call_soon(print_and_repeat, loop)
loop.call_soon(faz_algo, loop)
loop.run_forever()
</pre>
<br />
Que ao executar nos exibe:
<br />
<br />
<pre>Z:\artigos&gt;c:\python34\python asyncio2.py
Alô - Tempo decorrido: 0.007002
Fazendo algo... espera = 0.360861
Fazendo algo... espera = 0.411369
Fazendo algo... espera = 0.788518
Fazendo algo... espera = 0.253137
Fazendo algo... espera = 0.193489
Alô - Tempo decorrido: 2.003724
Fazendo algo... espera = 0.142127
Fazendo algo... espera = 0.523892
Fazendo algo... espera = 0.250166
Fazendo algo... espera = 0.467522
Fazendo algo... espera = 0.304402
Fazendo algo... espera = 0.831368
Alô - Tempo decorrido: 1.988947
Fazendo algo... espera = 0.647338
Fazendo algo... espera = 0.378327
Fazendo algo... espera = 0.013393
Fazendo algo... espera = 0.284973
Fazendo algo... espera = 0.368350
Alô - Tempo decorrido: 2.002417
Fazendo algo... espera = 0.257536
</pre>
<br />
Os valores de espera podem variar de uma execução a outra, o importante é observar que chamamos a função <b>faz_algo</b> e <b>print_and_repeat</b> são chamadas alternadamente, ou melhor, quase que ao mesmo tempo, como se estivéssemos utilizando threads. O que o novo programa faz é executar <b>print_and_repeat</b> como no primeiro exemplo, mas também a função <b>faz_algo</b>. A função <b>faz_algo</b> gera um tempo de espera aleatório, usando <b>random.random()</b> para agendar a próxima execução. Como <b>print_and_repeat</b> executa apenas a cada 2 segundos, o loop de eventos fica livre para executar outras tarefas, o que podemos ver na saída de nosso programa.<br />
O programa equivalente, sem o loop de eventos ficaria parecido com:<br />
<br />
<pre class="brush:python">import time
import random
def faz_algo():
espera = random.random()
print("Fazendo algo... espera = %f" % espera)
time.sleep(espera)
def print_and_repeat():
global último
agora = time.time()
print('Alô - Tempo decorrido: %f' % (agora - último))
último = agora
último = 0
while True:
agora = time.time()
if agora - último >= 2:
print_and_repeat()
else:
faz_algo()
</pre>
<br />
Que produz uma saída semelhante:
<br />
<br />
<pre>Z:\artigos>c:\python34\python asyncio3.py
Alô - Tempo decorrido: 1403984084.967565
Fazendo algo... espera = 0.233725
Fazendo algo... espera = 0.309948
Fazendo algo... espera = 0.551685
Fazendo algo... espera = 0.835331
Fazendo algo... espera = 0.008247
Fazendo algo... espera = 0.140163
Alô - Tempo decorrido: 2.081037
Fazendo algo... espera = 0.597524
Fazendo algo... espera = 0.004582
Fazendo algo... espera = 0.054279
Fazendo algo... espera = 0.037356
Fazendo algo... espera = 0.951933
Fazendo algo... espera = 0.003549
Fazendo algo... espera = 0.856917
Alô - Tempo decorrido: 2.506460
Fazendo algo... espera = 0.435528
Fazendo algo... espera = 0.599356
Fazendo algo... espera = 0.798355
Fazendo algo... espera = 0.594801
</pre>
<br />
O importante é notar que quanto mais tarefas temos a realizar, mais complicado ficaria escrever o programa equivalente, sem o loop de eventos. Um conceito a também observar é que apenas uma das funções roda de cada vez. Este detalhe permite construir nossos programas como fazemos normalmente, sem nos preocuparmos com threads. Outro detalhe é o módulo <b>asyncio</b> traz várias outras classes que ajudam a trabalhar de forma assíncrona com arquivos e sockets, por exemplo.
A programação com loop de eventos não resolve todos os tipos de problema. Vejamos uma função chamada <b>calcula_algo</b> que utiliza o processador para realizar um cálculo relativamente demorado.
Veja o programa abaixo:
<br />
<br />
<pre class="brush:python">import asyncio
import time
import random
def calcula_algo(loop, id):
limite = random.randint(30000,50000)
print("Calculando %d" % id)
z=1
for x in range(1,limite):
z*=x
print("Fim do Cálculo %d" % id)
loop.call_soon(calcula_algo, loop, id+2)
def faz_algo(loop):
espera = random.random()
print("Fazendo algo... espera = %f" % espera)
loop.call_later(espera, faz_algo, loop)
def print_and_repeat(loop):
global último
agora = time.time()
print('Alô - Tempo decorrido: %f' % (agora - último))
último = agora
loop.call_later(2, print_and_repeat, loop)
último = time.time()
loop = asyncio.get_event_loop()
loop.call_soon(print_and_repeat, loop)
loop.call_soon(faz_algo, loop)
loop.call_soon(calcula_algo, loop, 1)
loop.call_soon(calcula_algo, loop, 2)
loop.run_forever()
</pre>
<br />
Que produz como saída:
<br />
<br />
<pre>Z:\artigos>c:\python34\python asyncio4.py
Alô - Tempo decorrido: 0.007003
Fazendo algo... espera = 0.761420
Calculando 1
Fim do Cálculo 1
Calculando 2
Fim do Cálculo 2
Calculando 3
Fim do Cálculo 3
Calculando 4
Fim do Cálculo 4
Fazendo algo... espera = 0.395006
Alô - Tempo decorrido: 6.790526
Calculando 5
Fim do Cálculo 5
Calculando 6
Fim do Cálculo 6
Calculando 7
Fim do Cálculo 7
Calculando 8
Fim do Cálculo 8
Fazendo algo... espera = 0.540093
Alô - Tempo decorrido: 5.859909
Calculando 9
Fim do Cálculo 9
Calculando 10
Fim do Cálculo 10
Calculando 11
Fim do Cálculo 11
Calculando 12
Fim do Cálculo 12
Fazendo algo... espera = 0.174978
Alô - Tempo decorrido: 6.830562
Calculando 13
Fim do Cálculo 13
Calculando 14
</pre>
<br />
Veja que o atraso para chamar as outras funções é agora muito mais importante, ultrapassando os 6 segundos entre as chamadas de <b>print_and_repeat</b> e tendo um atraso considerável também no processamento de <b>faz_algo</b>. Bem, este comportamento é esperado, uma vez que a função <b>calcula_algo</b> é o que se chama de <i>CPU bound</i>, ou seja, é uma função que precisa mais da atenção do processador do computador que uma operação de criação de arquivo (<i>I/O bound</i>).
<br />
<br />
Para utilizar corretamente seu computador, você deve começar a separar seus problemas em <i>CPU bound</i> e <i>I/O bound</i>. No caso de problemas <i>CPU bound</i>, threads oferecem a melhor performance, pois temos vários processadores no mesmo computador. Já para problemas <i>I/O bound</i>, ou seja, que precisam acessar o disco ou a rede (ou entrada de dados vinda do teclado), o loop de eventos assíncrono é mais rápido e fácil de programar. Em problemas mistos, onde temos código <i>CPU bound</i> e código<i> I/O bound</i> a executar, uma solução mista precisa ser aplicada.<br />
<br />
Por exemplo, threads são relativamente caros para serem criados e são difíceis de controlar e programar. Nos próximos posts, abordarei outros detalhes do módulo asyncio. Antes de continuarmos com métodos assíncronos, vamos comparar o tempo de execução entre as soluções assíncronas, múltiplos threads e com múltiplos processos. Vamos avaliar versões modificadas da função <b>calcula_algo</b> para cada um dessas formas de paralelização. O problema testado será o tempo de total de execução de 20 chamadas a <b>calcula_algo</b>.<br />
<br />
Antes de começarmos, vamos remover a parte aleatória da função e transformar o valor de limite em uma constante. Desta forma as comparações serão mais justas e não dependerão do número obtido por <b>randint()</b>.<br />
<br />
<pre class="brush:python">import asyncio
import time
def calcula_algo(loop, id):
limite = 40000
print("Calculando %d" % id)
z=1
for x in range(1,limite):
z*=x
print("Fim do Cálculo %d" % id)
if id < 20:
loop.call_soon(calcula_algo, loop, id+1)
else:
loop.stop()
inicio = time.time()
loop = asyncio.get_event_loop()
loop.call_soon(calcula_algo, loop, 1)
loop.run_forever()
fim = time.time()
print("Tempo total: %f s" % (fim-inicio))
</pre>
<br />
Execute o programa e veja que o cálculo foi feito de forma sequencial, ou seja, uma chamada após a outra. Em meu computador o teste executou em aproximadamente 30.37 s. O tempo em seu computador pode e vai variar, pois depende do seu processador e do que sua máquina está fazendo durante os testes.
<br />
<br />
<pre class="brush:python">import time
import threading
def calcula_algo(id):
limite = 40000
print("Calculando %d" % id)
z=1
for x in range(1,limite):
z*=x
print("Fim do Cálculo %d" % id)
inicio = time.time()
ativos = []
for x in range(20):
t = threading.Thread(target=calcula_algo, args=(x,))
t.start()
ativos.append(t)
for t in ativos:
t.join()
fim = time.time()
print("Tempo total: %f s" % (fim-inicio))
</pre>
<br />
Compare a saída do programa com múltiplos threads com a saída do programa assíncrono. Veja que as chamadas foram iniciadas quase que ao mesmo tempo e que terminaram em uma ordem aleatória. Esta falta de previsibilidade de threads é uma das razões de evitarmos seu uso, especialmente em Python. Devido a uma característica do interpretador Python, que utiliza um lock global, o famoso GIL, programas com múltiplos thread em Python não são eficientes, pois apenas um thread executa de cada vez, o que nos faz voltar ao problema do programa assíncrono adicionado ao tempo de criação e execução dos threads. Em meus testes, este programa teve um desempenho um pouco pior que o programa assíncrono, terminando em 30.67 s. Se você executar novamente este programa, verá que a utilização de CPU não chega nem perto de 100%. Em meu sistema Core i7 que possui 8 cores (4 reais + 4 hyperthreaded), a utilização não passou de 20%.
<br />
Vejamos uma solução mais Pythonica, utilizando múltiplos processos e o excelente módulo multiprocessing.
<br />
<br />
<pre class="brush:python">import sys
import time
from multiprocessing import Pool
def calcula_algo(id):
limite = 40000
print("Calculando %d" % id)
z=1
for x in range(1,limite):
z*=x
print("Fim do Cálculo %d" % id)
if __name__ == '__main__':
nproc = int(sys.argv[1])
print("Executando com %d processos.")
inicio = time.time()
processos = Pool(nproc)
processos.map(calcula_algo,list(range(20)))
fim = time.time()
print("Tempo total: %f s" % (fim-inicio))
</pre>
<br />
Execute o programa várias vezes, passando a cada execução, um dos parâmetros 1, 2, 4, 8 e 20. O parâmetro indica quantos processos teremos em nosso pool. Um pool de processos é um conjunto de processos inicializados pelo módulo multiprocessing. Estes processos ficam disponíveis para o nosso programa e são processos do sistema operacional e não simples threads. Toda a comunicação entre processos é gerenciada pelo módulo multiprocessing. Este módulo é muito interessante, pois realizar este tipo de tarefa em outras linguagens é bem mais complicado. Uma das vantagens do multiprocessing é que cada processo roda seu próprio interpretador Python e assim são capazes de rodar simultaneamente, sem os problemas do GIL que falamos anteriormente. Veja no Gerenciador de Tarefas de seu sistema operacional que múltiplos processos python (ou python.exe no Windows) rodam ao mesmo tempo durante a execução de nosso programa e que agora você deve ter obtido 100% de utilização durante alguns momentos. Veja o resultado da execução de todos os testes em meu computador no gráfico abaixo (passe o mouse sobre as colunas para ver seu valor):<br />
<br />
<script src="https://www.google.com/jsapi" type="text/javascript"></script>
<script type="text/javascript">
// Load the Visualization API and the piechart package.
google.load('visualization', '1.0', {'packages':['corechart']});
// Set a callback to run when the Google Visualization API is loaded.
google.setOnLoadCallback(drawChart);
// Callback that creates and populates a data table,
// instantiates the pie chart, passes in the data and
// draws it.
function drawChart() {
// Create the data table.
var data = new google.visualization.DataTable();
data.addColumn('string', 'Método');
data.addColumn('number', 'Tempo');
data.addRows([
['asyncio', 30.37],
['Threads (20)', 30.67],
['Multiprocessing 1', 30.47],
['Multiprocessing 2', 16.95],
['Multiprocessing 4', 9.96],
['Multiprocessing 8', 7.61],
['Multiprocessing 20', 7.57]
]);
// Set chart options
var options = {'title':'Tempo(s) de execução de 20 chamadas a calcula_algo',
'width':500, 'legend':{position:'none'},
vAxis: {
title: "Tempo (s)",
viewWindowMode:'explicit',
gridlines: {count: 20},
viewWindow:{
max:35,
min:0
}
},
'height':500};
// Instantiate and draw our chart, passing in some options.
var chart = new google.visualization.ColumnChart(document.getElementById('chart_div'));
chart.draw(data, options);
}
</script>
<br />
<div id="chart_div">
</div>
<br />
A solução com o multiprocessing melhora ao adicionarmos processos, mas esta melhoria se estabiliza em volta do número de processadores de sua máquina e começa a piorar um pouco depois disso.
<br />
<br />
O mau desempenho do módulo asyncio é apenas um exemplo de má utilização :-D. Os métodos assíncronos devem ser utilizados com funções que não bloqueiam e que terminam rapidamente. Usar métodos assíncronos com funções <i>CPU bound</i> não pode trazer bons resultados. No entanto, devido aos problemas com o GIL, métodos assíncronos podem simplificar o trabalho de programação e manter a performance de múltiplos threads. Pois a execução sequencial das funções evita a necessidade de sincronizar seus dados. Mesmo com os problemas de GIL, programas em Python que usam threads devem se preparar para execução simultânea de funções, pois a execução salta de um thread a outro durante a execução das funções. Veremos uma outra comparação entre threads e métodos assíncronos em outro posto, usando arquivos.<br />
<br />
Isto é só uma pequena amostra do que podemos fazer em Python. Em outro post, abordaremos exemplos mais práticos. O importante é saber a diferença entre a execução assíncrona, com threads e com múltiplos processos. Veremos também como usar como usar um pool de threads em Python e assim combinar as vantagens de threads e métodos assíncronos.
Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com0tag:blogger.com,1999:blog-8312361233856668827.post-40748265065519726772013-05-09T14:02:00.000-04:002013-05-09T14:02:03.726-04:00Todos os exercícios do livro já estão no site!Acabei de subir todos os exercícios resolvidos do meu livro: "Introdução à Programação com Python".<br />
<br />
Site do livro: <a href="http://www.nilo.pro.br/iprog/">http://www.nilo.pro.br/iprog/</a><br />
<br />
Download dos exercícios: <a href="http://www.nilo.pro.br/iprog/exercicios_resolvidos.zip">http://www.nilo.pro.br/iprog/exercicios_resolvidos.zip</a><br />
<br />
Exercícios on line: <a href="http://www.nilo.pro.br/html/exercicios/">http://www.nilo.pro.br/html/exercicios/</a><br />
<br />
<br />Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com3tag:blogger.com,1999:blog-8312361233856668827.post-15356265947718093952013-01-26T05:48:00.001-04:002019-02-17T12:41:23.018-04:00Múltiplos monitores com o Remote Desktop<a href="https://blog.nilo.pro.br/posts/2013-01-26-multiplos-monitores-com-o-remote-desktop/">Leia no novo blog</a><br />
<br />
Depois de usar dois ou mais monitores para programar é difícil querer usar apenas um. O Remote Desktop da Microsoft suporta vários monitores, mas normalmente só funciona bem quando a máquina remota e a local possuem 2 monitores.<br />
<br />
No trabalho, eu uso um computador com 2 monitores Full HD e um notebook com a resolução de tablet :-D O que torna impossível usar o notebook para programar. Eu normalmente abro uma sessão do Remote Desktop pela manhã e só a fecho na hora de ir embora... muitas vezes não uso nem o teclado do notebook durante o dia, ele se transforma em um disco e CPU remotos !<br />
<br />
O problema é que a máquina local possui 2 monitores e a máquina remota apenas 1. O Remote Desktop usa a resolução máxima de apenas um dos monitores da máquina local, o que deixa um monitor sobrando. Embora o notebook tenha uma tela de baixa resolução, o processador e a memória são muito bons, logo ele é minha máquina de desenvolvimento padrão (Core i7, 8GB). Nada como abrir o Visual Studio 2012 pela manhã e vários outros programas ao lado dele, cada um na sua própria tela.<br />
<br />
Para usar o Remote Desktop com os dois monitores locais, é preciso adicionar um parâmetro na linha de comando: /span<br />
<br />
Para não esquecer, é melhor mesmo criar um atalho, já com este parâmetro.<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">mstsc.exe /span</span><br />
<br />
O resultado é um monitor remoto com a resolução total dos dois monitores locais, no meu caso: 2 x 1980 = 3960 pontos ! Funciona também se os dois monitores locais tiverem resolução diferente, mas a quantidade de linhas será a menor entre os dois. No meu caso, acabei com uma tela remota de 3960 x 1080 pontos.<br />
<br />
Seria tudo perfeito se algumas operações do Windows não incomodassem. O monitor super largo, estendendo-se por dois monitores físicos, continua sendo um e apenas um grande monitor para a máquina remota. Logo, se maximizarmos uma janela, esta ocupará os dois monitores locais! Isso pode ser resolvido com as áreas de redimensionamento do Windows 7. Para "maximizar" uma janela em apenas um dos monitores, basta arrastar a janela até o canto esquerdo ou direito (mouse todo à esquerda ou à direita, mas na metade da altura da tela). A tela muda de cor, algo como um azul escuro transparente no meu tema. Quando esta troca de cor aparecer, basta soltar o botão e a janela que você estava arrastando muda de tamanho para ocupar a área azul. O resultado é uma janela que ocupa toda a área de um dos monitores.<br />
<br />
Um resultado colateral é que a barra de tarefas do Windows passa a ocupar os dois monitores, desta forma, eu tenho o botão de start no monitor da esquerda e o relógio no da direita!<br />
<br />
Fica a fica para os fanáticos com 2 monitores!Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com1tag:blogger.com,1999:blog-8312361233856668827.post-52607561392557880622012-08-03T20:00:00.001-04:002012-08-03T20:20:21.987-04:00Python Computer e Raspberry Pi<h2>
Preparação</h2>
Segunda (30/07/12), recebi meu primeiro <a href="http://www.raspberrypi.org/">Raspberry Pi</a>, comprado na <a href="http://www.element14.com/community/groups/raspberry-pi">Element 14</a>. Depois de um mês de espera, recebo na caixa do correio, mas um tanto tarde na Bélgica… 21h. Na verdade, chegou perto do meio dia, mas como não estava em casa, o carteiro deixou como carta normal mesmo.<br />
<a href="http://lh3.ggpht.com/-_8iEU9z8f5g/UBxlu_tn-4I/AAAAAAAAAGQ/kvQCOnUrvAY/s1600-h/P1060883%25255B12%25255D.jpg"><img alt="P1060883" border="0" height="351" src="http://lh3.ggpht.com/-EU9Lgs3u2uY/UBxlvm8s5pI/AAAAAAAAAGU/DRQSHkz5gHE/P1060883_thumb%25255B12%25255D.jpg?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="P1060883" width="522" /></a><br />
A caixa é bem pequena, como um cartão de crédito. Na realidade, eu encontrei o computador no meio de outras cartas <img alt="Open-mouthed smile" class="wlEmoticon wlEmoticon-openmouthedsmile" src="http://lh5.ggpht.com/-jn3nXUWIr6Q/UBxlwrm20YI/AAAAAAAAAGc/RECubX_p3qw/wlEmoticon-openmouthedsmile%25255B2%25255D.png?imgmax=800" style="border-bottom-style: none; border-left-style: none; border-right-style: none; border-top-style: none;" />!<br />
O problema é que não tinha nenhuma loja aberta para comprar um cabo ou outro, caso precisasse. O jeito era improvisar.<br />
Eu sabia que ia precisar de um Hub USB com alimentação, pois a USB do Pi não fornece muita energia. Sabendo que o computador estava para chegar, eu comprei um Hub USB de 4 portas, ainda no sábado, aproveitando a visita ao supermercado:<br />
<a href="http://lh6.ggpht.com/-t9FMoWLDodQ/UBxlxuLPOGI/AAAAAAAAAGk/ROUksuefgMw/s1600-h/P1060882%25255B6%25255D.jpg"><img alt="P1060882" border="0" height="415" src="http://lh6.ggpht.com/-BtjwM_HNDF0/UBxlyY1k91I/AAAAAAAAAGs/adRbou1Ofgw/P1060882_thumb%25255B8%25255D.jpg?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="P1060882" width="507" /></a><br />
E também cartas SD:<br />
<a href="http://lh3.ggpht.com/-gxs3rYATvm0/UBxlzPZ-9FI/AAAAAAAAAG4/OXy0oHMmqfg/s1600-h/P1060878%25255B6%25255D.jpg"><img alt="P1060878" border="0" height="262" src="http://lh3.ggpht.com/-dvsvms4gZxc/UBxl0N_C0TI/AAAAAAAAAG8/h7pmz0iYTBg/P1060878_thumb%25255B6%25255D.jpg?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="P1060878" width="514" /></a><br />
Pois o Pi só dá boot pela carta SD. Comprei uma carta de 4GB e outra de 8GB, pois uma era classe 4 e a outra classe 10. Aparentemente o Pi tem problemas com cartas classe 10, mas a minha funcionou perfeitamente, mas sem ganho de performance notável.<br />
Antes mesmo de conectar o computador, é preciso baixar o Linux e gravá-lo na carta SD. Instruções detalhadas em inglês são encontradas <a href="http://www.raspberrypi.org/downloads">aqui</a>. Se você usa Windows, precisa baixar o <a href="http://www.softpedia.com/get/CD-DVD-Tools/Data-CD-DVD-Burning/Win32-Disk-Imager.shtml">Win32DiskImage</a>. Ele não tem setup, é só descompactar e executar direto. Você precisa ser administrador do computador para gravar a imagem. Eu utilizei o <a href="http://downloads.raspberrypi.org/images/raspbian/2012-07-15-wheezy-raspbian/2012-07-15-wheezy-raspbian.zip">Raspbian</a>, mas outras distribuições também estão disponíveis. A imagem do Raspbian tem uns 450MB e ao descompactá-la você terá o arquivo .img, necessário para o Win32DiskImage.<br />
Gravar a imagem é bem fácil. Introduza o cartão SD no leitor do computador. Normalmente o Windows se oferece para abri-lo. Aproveite a chance para verificar se este está vazio, pois a gravação da imagem vai apagar todo o conteúdo do cartão. Com o Win32DiskImage aberto, selecione o arquivo .img com o Raspbian e ao lado verifique se o drive com a carta SD está correto. Se você tem mais de uma carta SD ou caso apareça mais de uma opção, verifique o que está fazendo, evitando assim perder seus dados. Clique no botão Write e espere a gravação ser finalizada.<br />
Com a carta SD pronta, resta a conexão do Raspberry Pi.<br />
<a href="http://lh3.ggpht.com/-CX-XU_xKeFI/UBxl08B3FyI/AAAAAAAAAHE/bwerGDZG4NU/s1600-h/P1060886%25255B6%25255D.jpg"><img alt="P1060886" border="0" height="309" src="http://lh6.ggpht.com/-VkwofEhMZWw/UBxl2JwZRDI/AAAAAAAAAHQ/Vrpil9UwJCg/P1060886_thumb%25255B6%25255D.jpg?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="P1060886" width="488" /></a><br />
Como já estava tarde, montei uma mesa de trabalho no chão da sala, usando uma caixa de monitor e uma folha A4 para dar noção do tamanhão do Raspberry Pi. Sem medir muito, eu chuto que numa folha A4 caberiam 9 Raspberry Pis!<br />
Esta placa contém um SoC (System on a Chip) fabricado pela Broadcom, no caso do modelo B, um BCM2835 com:<br />
<ul>
<li>Processador ARM1176JZF-S (armv6k) rodando a 700 MHz </li>
<li>256 MB de Ram </li>
<li>2 portas USB 2.0 </li>
<li>1 porta Ethernet </li>
<li>GPU Broadcom VideoCore IV </li>
<li>Leitor de carta SD </li>
<li>Saída de vídeo HDMI </li>
<li>Saída de vídeo composto (conector RCA) </li>
<li>Saída de áudio padrão, 3.5 mm </li>
<li>Conector de entrada e saída genérico com GPIO/UART/I2C e SPI </li>
<li>Dimensões: 85.60 x 53.98 mm </li>
<li>Peso: 45g</li>
</ul>
Um verdadeiro micro sem ventilador e de baixo custo (35€ com frete).<br />
Para conectá-lo à minha TV, emprestei o cabo do vídeo game <img alt="Open-mouthed smile" class="wlEmoticon wlEmoticon-openmouthedsmile" src="http://lh5.ggpht.com/-jn3nXUWIr6Q/UBxlwrm20YI/AAAAAAAAAGc/RECubX_p3qw/wlEmoticon-openmouthedsmile%25255B2%25255D.png?imgmax=800" style="border-bottom-style: none; border-left-style: none; border-right-style: none; border-top-style: none;" />. Mouse e teclado eu peguei os que uso com meu notebook. Duro foi achar o cabo com o conector <a href="http://en.wikipedia.org/wiki/File:MicroB_USB_Plug.jpg">Micro B USB</a>. Eu consegui um carregador de celular:<br />
<a href="http://lh3.ggpht.com/-uQJQwGdpv1k/UBxl3K2idrI/AAAAAAAAAHY/zHrYwFRN8d4/s1600-h/P1060884%25255B6%25255D.jpg"><img alt="P1060884" border="0" height="377" src="http://lh3.ggpht.com/-TfJ0HydvEtw/UBxl4L-yC1I/AAAAAAAAAHc/zWEq5TzDb8g/P1060884_thumb%25255B3%25255D.jpg?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="P1060884" width="501" /></a><br />
Ficou assim:<br />
<a href="http://lh3.ggpht.com/-KVfB-ZpThkI/UBxl4-orgzI/AAAAAAAAAHk/mEp413x6zOo/s1600-h/P1060889%25255B6%25255D.jpg"><img alt="P1060889" border="0" height="429" src="http://lh3.ggpht.com/-8599QlZHzAU/UBxl5-EvjNI/AAAAAAAAAHs/Nrinitk-Xmo/P1060889_thumb%25255B6%25255D.jpg?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="P1060889" width="457" /></a><br />
É preciso ter cuidado ao conectar e desconectar o Raspberry Pi. Como a placa não tem gabinete, todas as precauções contra eletricidade estática devem ser tomadas. Não se pode esquecer também de não forçar os conectores e principalmente que o Pi não tem On/Off: ligou a força ele já está ON e dando o boot!<br />
<a href="http://lh3.ggpht.com/-8BRKYf_rzBY/UBxl6kqswlI/AAAAAAAAAH0/Bup22FW-cEA/s1600-h/P1060887%25255B6%25255D.jpg"><img alt="P1060887" border="0" height="344" src="http://lh3.ggpht.com/-47Vpyc-Tqyc/UBxl7oJwRxI/AAAAAAAAAH8/O0DcjzC5lZo/P1060887_thumb%25255B6%25255D.jpg?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="P1060887" width="520" /></a><br />
Até aqui tudo certo. Mas ao tentar usar o teclado, este não respondia e o Pi chegou a travar. Lembrei então que ele precisa de pelo menos 700mA para funcionar. Checando o carregador de celular que encontrei, vi que ele só fornecia 300 mA. Ele foi então substituído por um cabo USB normal ligado ao carregador de um iPhone. Depois, eu eliminei o carregador e liguei tudo no Hub alimentado. Com a fonte certa, tudo passou a funcionar corretamente.<br />
Ao dar o boot, ou executando sudo raspi-config, você tem a tela do utilitário de configuração simplificada abaixo:<br />
<a href="http://lh5.ggpht.com/-nWMWFikEbGk/UBxl8Yv3jdI/AAAAAAAAAIE/7-3UGE3AFRc/s1600-h/raspconfig%25255B3%25255D.png"><img alt="raspconfig" border="0" height="282" src="http://lh6.ggpht.com/-z1KfP_LyVV4/UBxl9SWQoUI/AAAAAAAAAIM/nxj8NXlCajw/raspconfig_thumb%25255B1%25255D.png?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="raspconfig" width="521" /></a><br />
Como meu cartão era de 4 GB, a primeira coisa que fiz foi selecionar expand_rootfs. Esta opção aumenta o disco criado pela restauração da imagem para utilizar todo o cartão. Depois, eu configurei o teclado (configure_keyboard), troquei a senha do usuário pi (change_pass) e o fuso horário (change_timezone). Para sair é só selecionar Finish e dar boot.<br />
O Pi não tem relógio permanente, ele usa um servidor de tempo para ajustar o relógio durante o boot. Configurar o fuso horário é importante por isso.<br />
Esqueci de falar do acesso à Internet. Eu apenas liguei um cabo Ethernet que estava sobrando e o Pi pega um IP usando DHCP. O acesso fica muito fácil e no próximo boot ele já sincroniza o horário, pega um IP e você fica pronto para usar a Internet ou sua rede local. Eu não tenho um cartão WiFi USB, mas se for o caso, na wiki do Raspbery Pi tem a lista dos <a href="http://elinux.org/RPi_VerifiedPeripherals#USB_WiFi_Adapters">WiFi compatíveis</a>.<br />
Meu teste era plugar um leitor de DVD externo (USB) e assistir um vídeo antes de dormir. Instalei o VLC, usando o apt-get, mas não consegui ver vídeo algum. Tentei também com arquivos AVI, sem sucesso. Acho que nestes casos o melhor é tentar a OpenElec com o XBMC.<br />
Entrei no X, digitando startx. Baixei o <a href="http://sourceforge.net/projects/invasores/">Invasores</a> e funcionou de primeira, com som na TV!<br />
<a href="http://lh3.ggpht.com/-ZqCAscPLgMs/UBxl-OemNiI/AAAAAAAAAIY/IUexuoW-rfw/s1600-h/P1060893%25255B6%25255D.jpg"><img alt="P1060893" border="0" height="279" src="http://lh3.ggpht.com/-9n1Nj9BMEM4/UBxl_oFw0DI/AAAAAAAAAIc/8I7ezoHFXNU/P1060893_thumb%25255B7%25255D.jpg?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="P1060893" width="490" /></a><br />
Fiquei surpreso ao constatar que o PyGame já vinha instalado. Mesmo um jogo simples como o Invasores teve algumas paradas. Tentei então os jogos pré-instalados e observei o mesmo problema. O X é pesado demais para o Pi.<br />
O Browser é muito simples e não suporta HTML5, muito menos flash. Além disso… quando digo muito lento, não estou exagerando. Lembra os primeiros anos de Windows… mas roda. Instalei o Chromium, versão open source do Google Chrome, mas também é muito lento e pesado. Não podemos esquecer que um browser moderno consome muita memória e que rodar JavaScript exige um processador bom. Nem memória, nem processador sobram no Raspberry Pi para este tipo de coisa. Os drivers ainda estão sendo desenvolvidos e quase tudo roda sem otimização/aceleração da GPU. Novas versões devem melhorar esta situação.<br />
<h2>
Python Computer</h2>
Meu objetivo ao comprar o Raspberry Pi era de configurar um Linux para funcionar como estação de ensino de Python. Como o Pi de Raspberry Pi vem de Python… apareceu a oportunidade. Eu chamei meu projetinho de Python Computer. A ideia era configurar o Linux como um computador de 8 bits que ligava e caia no interpretador Basic. No caso, eu queria um Linux que ligasse e entrasse no interpretador Python. Eu já havia feito um teste usando SilverLight para executar o Python Computer em um browser. Porém, esta solução dependeria de acesso a Internet, versão do browser, plug ins, etc. Ter tudo num pequeno computador me parece mais interessante.<br />
Para isso, o X não é realmente necessário. No Linux, a PyGame pode rodar no framebuffer. Esta configuração me pareceu mais realista que um ambiente gráfico completo no Raspberry Pi.<br />
A primeira coisa que fiz foi mudar o tamanho da fonte de texto, pois a padrão é muito pequena para se ler na TV.<br />
Isso pode ser feito editando o arquivo /etc/default/console-setup como root:<br />
<br />
sudo vim /etc/default/console-setup<br />
<br />
Na linha que contém FONTFACE escreva:<br />
<br />
FONTFACE=”TERMINUS”<br />
<br />
e na com FONTSIZE:<br />
<br />
FONTSIZE=”16x32”<br />
<br />
Salve o arquivo.<br />
<br />
Para configurar o vim, crie o arquivo ~python/.vimrc com as seguintes linhas:<br />
<br />
<span lang="FR-BE"> </span><br />
<span lang="FR-BE">syntax on<br />set ts=4<br />set expandtab<br />set shiftwidth=4<br />set softtabstop=4<br />set smartindent<br />set number<br />set cindent<br />set nowrap<br />set go=+b</span><br />
<span lang="FR-BE"> autocmd BufRead *.py set makeprg=python\ -c\ \"import\ py_compile,sys;\ sys.stderr=sys.stdout;\ py_compile.compile(r'%')\"<br />
autocmd BufRead *.py set efm=%C\ %.%#,%A\ \ File\ \"%f\"\\,\ line\ %l%.%#,%Z%[%^\ ]%\\@=%m<br />
autocmd BufRead *.py nmap <F5> :!python %<CR></span> <br />
<span lang="FR-BE"><br /></span><br />
Agora crie o usuário python:<br />
<br />
sudo useradd –m –s /usr/bin/ipython python<br />
<br />
E edite o inittab:<br />
<br />
sudo vim /etc/inittab<br />
<br />
Modifique a linha do terminal 6, no meu inittab a linha 59. De:<br />
<br />
6:23:respawn:/sbin/getty 38400 tty6<br />
<br />
para:<br />
<br />
6:23:respawn:/sbin/getty 38400 tty6 –a python<br />
<br />
Isto fará que com que o ipython seja aberto no terminal 6 durante o boot, sem precisar de login. Faça o boot e digite ALT+F6<br />
<a href="http://lh4.ggpht.com/-o5apZ27FQxA/UBxmAf2SZOI/AAAAAAAAAIk/A4VXXeS1YpI/s1600-h/ipythonboot%25255B6%25255D.png"><img alt="ipythonboot" border="0" height="357" src="http://lh5.ggpht.com/-nMWFwGvQVos/UBxmBueW53I/AAAAAAAAAIw/IVHMfDHB2FA/ipythonboot_thumb%25255B8%25255D.png?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="ipythonboot" width="528" /></a><br />
Se quiser apagar estas mensagens do login, edite o arquivo /etc/motd.<br />
Para editar um arquivo sem sair do ipython, digite: edit nomedoarquivo.py<br />
<a href="http://lh4.ggpht.com/-VE0oroyBeGY/UBxmC2Q8o2I/AAAAAAAAAI0/umh1dfln8nw/s1600-h/vim_inv%25255B4%25255D.png"><img alt="vim_inv" border="0" height="286" src="http://lh6.ggpht.com/-6WUzWEcQ0Pc/UBxmDoffR1I/AAAAAAAAAI8/IHFe07V_i0w/vim_inv_thumb%25255B2%25255D.png?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="vim_inv" width="527" /></a><br />
Se o arquivo não existir ele será criado. Ao voltar sair do editor, ele volta para o ipython e executa o arquivo. <br />
O vim não é o editor mais boa pinta do pedaço, mas em modo texto não conheço outro melhor. Para quem estiver saudades dos programas da Borland, instale o joe:<br />
<br />
sudo apt-get install joe<br />
<br />
e no /etc/profile, adicione:<br />
<br />
export EDITOR=/usr/bin/joe<br />
<br />
O joe é mais amigável e usa os comandos do Wordstar/Sidekick… e ambientes Turbo da Borland.<br />
Para rodar jogos escritos com a PyGame, modifique a linha que escolhe a quantidade (profundidade) de cores para que 16 bit colors seja escolhido.<br />
No invasores, edite o arquivo video.py, modifique o método modo da classe Video:<br />
<br />
self.tela = pygame.display.set_mode(dimensao, 0, 16)<br />
<br />
Depois, é só rodar o Invasores no framebufer!<br />
<a href="http://lh3.ggpht.com/-uiUWBnBExpQ/UBxmEWPdtuI/AAAAAAAAAJE/io6-Q94jyJU/s1600-h/jogo2%25255B3%25255D.png"><img alt="jogo2" border="0" height="398" src="http://lh5.ggpht.com/-QGObNBOuYxc/UBxmFXx7kvI/AAAAAAAAAJM/iLztn7pBdXI/jogo2_thumb%25255B1%25255D.png?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="jogo2" width="529" /></a><br />
<a href="http://lh5.ggpht.com/-hz14HPyEnVA/UBxmFxwx50I/AAAAAAAAAJY/ZrkYJwUsF7o/s1600-h/jogo%25255B3%25255D.png"><img alt="jogo" border="0" height="397" src="http://lh5.ggpht.com/-60tX4xI3xpA/UBxmHKEgyTI/AAAAAAAAAJg/_dwO3XcxZhE/jogo_thumb%25255B1%25255D.png?imgmax=800" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="jogo" width="528" /></a><br />
Mesmo alterando a partição da RAM entre a CPU e a GPU para o máximo, ou seja, 128 MB/128 MB, não consegui rodar em resoluções maiores que 1024x768. Lembrando que no X o Invasores roda normalmente.<br />
Se você gosta de usar o modo texto, não deixe de dar uma olhada no screen. Como a tela é larga, eu a dividi em duas partes. Fica muito legal usar metade para rodar algo e a outra para ler a documentação ou editar os fontes.<br />
Uma dica para ligar e desligar o Pi é utilizar uma régua com interruptor:<br />
<a href="http://lh6.ggpht.com/-qPrRIWQZaA0/UBxmIQrM3dI/AAAAAAAAAJo/iAhK4dCqhc8/s1600-h/P1070022%25255B6%25255D.jpg"><img alt="P1070022" border="0" height="262" src="http://lh4.ggpht.com/-BrVYSv_yOaQ/UBxmJH_Py8I/AAAAAAAAAJs/xw4shyIrBOY/P1070022_thumb%25255B6%25255D.jpg?imgmax=800" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="P1070022" width="504" /></a><br />
Quanto a Internet, como o Pi fica ao lado do meu notebook, configurei o compartilhamento de Internet do Windows 7 e funcionou direitinho. Desta forma, eu uso o WiFi do notebook para dar acesso ao Pi. Outra vantagem é que posso usar o WinSCP para copiar arquivos entre o notebook e o Pi e mesmo fazer sessões com SSH. Fica a foto da mesa de guerra:<br />
<a href="http://lh5.ggpht.com/-Li4xm7K2J_0/UBxmJ2lNkDI/AAAAAAAAAJ0/h4zI8BHne-4/s1600-h/P1070032%25255B5%25255D.jpg"><img alt="P1070032" border="0" height="384" src="http://lh6.ggpht.com/-Zi6LcRKPOdM/UBxmKr1zFhI/AAAAAAAAAJ8/HeHk7cqiBrU/P1070032_thumb%25255B2%25255D.jpg?imgmax=800" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="P1070032" width="512" /></a><br />
<h2>
Custo</h2>
<h2>
<table border="1" cellpadding="2" cellspacing="0" width="401"> <tbody>
<tr> <td valign="top" width="201">Item</td> <td align="right" width="198"><div align="right">
Preço</div>
</td></tr>
<tr> <td valign="top" width="201">Raspberry Pi</td> <td align="right" width="198"><div align="right">
35€</div>
</td></tr>
<tr> <td valign="top" width="201">Hub USB 4 portas</td> <td align="right" width="198"><div align="right">
12€</div>
</td></tr>
<tr> <td valign="top" width="201">Carta SD 4 GB</td> <td align="right" width="198"><div align="right">
5€</div>
</td></tr>
<tr> <td valign="top" width="201">Teclado</td> <td align="right" width="198"><div align="right">
10€</div>
</td></tr>
<tr> <td valign="top" width="201">Mouse</td> <td align="right" width="198"><div align="right">
12€</div>
</td></tr>
<tr> <td valign="top" width="201">Cabo HDMI</td> <td align="right" width="198"><div align="right">
6€</div>
</td></tr>
<tr> <td valign="top" width="201">Monitor</td> <td align="right" width="198"><div align="right">
150€</div>
</td></tr>
<tr> <td valign="top" width="201"><div align="right">
Total</div>
</td> <td align="right" width="198"><div align="right">
230€</div>
</td></tr>
</tbody></table>
</h2>
Como eu já tinha o monitor, teclado e mouse, não saiu tão caro. Nesta lista ainda falta adicionar 12€ para uma caixinha e mais 6€ para uma fonte com cabo Micro B USB que ainda não chegaram. O hub é opcional se você ligar apenas o teclado e o mouse.<br />
<br />
<h2>
Conclusão</h2>
O Pi é muito legal, mas é um brinquedo para desenvolvedores que gostem muito de Linux. Com o tempo, os drivers ficarão mais maduros e a performance deve melhorar. Eu acredito que logo teremos um browser com suporte a HTML5 e um XBMC mais redondo. Por enquanto, usar Python no console já chamou a atenção das crianças aqui de casa, como um modo especial de programação e criação de jogos. O fato de ver um computador aberto com as luzes piscando também chamou a atenção. Outra vantagem do Pi é que não roda Facebook, Twitter ou YouTube. Para o ensino, gostaria de medir a diferença entre usar um ambiente como o Pi em relação a interfaces ricas como o Windows.Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com2tag:blogger.com,1999:blog-8312361233856668827.post-91977906268143206242012-08-01T09:09:00.000-04:002019-02-17T12:42:23.920-04:00Por que UTF-8 e não ASCII para o Português? (PARTE II)<a href="https://blog.nilo.pro.br/posts/2012-08-01-por-que-utf-8-e-nao-ascii-para-o-portugues-parte-ii/">Leia no novo blog</a><br />
<br />
Continuação do post, originalmente feita na lista Python-Brasil:<br />
<br />
Vou tentar de novo, a thread já falou de 3 coisas diferentes:<br />
1. Codificação a usar em programas Python: por que UTF-8 é altamente recomendável<br />
2. Codificações em geral e problemas causados e resolvidos por ela<br />
3. Um bug do Python no Windows, quando o prompt é configurado para página 65001<br />
<br />
Vou tentar explicar para todo mundo, pois é um tópico recorrente.<br />
<br />
Mas antes de voltar nestes tópicos, temos que voltar a arquivos.<br />
<br />
Tentativa 2:<br />
<br />
Para entender a codificação de caracteres, temos que entender do que se trata.<br />
Quem programa conhece o código ASCII, que mapeia cada caractere do alfabeto latino, códigos de controle e alguns símbolos em 7 bits.<br />
É 7 bits, por isso vai de 0 a 127. Isso funcionava bem na década de 60... quando se enxugava bit para salvar tempo de transmissão de dados e armazenamento, muito antes dos torrents e afins :-D. A internacionalização disto ainda não estava em foco, aliás, ASCII significa Código Padrão Americano para Intercâmbio de Informação. Americano, diga-se estado-unidense.<br />
<br />
Nossos computadores hoje usam 8 bits por byte, mas nem sempre foi assim. A IBM e depois a Microsoft entre outras empresas aproveitaram o bit extra para completar 8 bits e adicionaram mais 128 caracteres, uma vez que cada bit adicionado dobra a capacidade de representação, potência de 2, etc. Esses caracteres foram usados para representar aqueles caracteres como bordas, símbolos e alguns acentos. Quem usou DOS, lembra bem disso.<br />
<br />
Como 256 símbolos não são suficientes para representar todos os caracteres de todas as línguas, a IBM e outros fabricantes, criaram páginas de código específicas para cada país ou língua. Assim, a página de código 437 (cp437) continha símbolos de desenho e caracteres acentuados como o ç e o ñ, usados em línguas como o francês e o espanhol, que atendem às necessidades da América do Norte e algumas línguas européias. Um exemplo de língua não atendida completamente é o português, pois na página 437 não tem ã nem õ. Esse problema foi resolvido com a página 850, que troca alguns caracteres de desenho e símbolos pouco usados por acentos de várias línguas do ocidente europeu.<br />
Depois de muita história, voltemos a como isso muda nossos bytes.<br />
<br />
No código ASCII, a letra A maiúscula é representada pelo número 65 em decimal ou 0x41 em hexadecimal. O B é a letra seguinte, então deram o número 66 ou 0x42.<br />
<br />
Se você tem um arquivo com apenas duas letras AB uma após a outra, ele vai ocupar (seus dados) dois bytes no disco. O conteúdo binário dos dados do arquivo em disco são a sequência de bytes 0x41 e 0x42 (AB ou 65 e 66). É muito importante entender esta codificação antes de continuar lendo. Se você não entende que um A é guardado como o número 65, esqueça UTF-8... será preciso reler ou pedir ajuda a um amigo. Antigamente, curso de informática começava com sistema binário e tabela ASCII, hoje o primeiro programa já baixa páginas na Internet... mas a teoria de base é deixada para trás.<br />
<br />
Tanto na página 437 quanto na 850, toda a tabela ASCII, ou seja, seus 127 caracteres foram preservados. Assim, nosso arquivo AB é mostrado do mesmo jeito em ambas as páginas. A diferença começa aparecer quando usamos os caracteres diferentes entre elas.<br />
<br />
Agora imagine que adicionamos um Ã, usando a página 850, pois escrevemos num computador configurado para português:<br />
ABÃ<br />
No disco teríamos 3 bytes:<br />
0x41 0x42 0xC3<br />
Na página 850, o à é traduzido para o símbolo 199, ou 0xC3 em hexadecimal.<br />
Agora, imagine que enviamos esse arquivo para um amigo americano, que abre num computador que usa a página 437. O conteúdo no disco continua o mesmo: 0x41 0x42 0xC3, mas o que ele vê na tela é:<br />
AB<a class="mw-redirect" href="http://en.wikipedia.org/wiki/%E2%95%9F" style="background-color: #ffefaf; background-image: none; color: #0b0080; font-family: monospace; font-size: large; line-height: 19px; text-align: center; text-decoration: none !important;" title="╟">╟</a><br />
Para onde foi nosso Ã? Para lugar algum... ela estará lá, se usarmos a mesma página de código, ou seja a 850, que usamos para escrever.<br />
Com apenas 3 bytes, já podemos ver o que pode acontecer... agora imagine com arquivos inteiros !<br />
Como os computadores se espalharam pelo mundo, várias páginas foram criadas para o russo, grego, etc. Imagine então escrever um arquivo com partes em grego, partes em russo e chinês... uma catástrofe.<br />
<br />
O que uma tabela de codificação faz é mapear um valor numérico para um símbolo gráfico ou caractere. Você escolhe a tabela que quer usar, mas para fazer uma tradução entre tabelas precisa saber qual a tabela usada para codificar os dados atuais e para qual tabela você quer traduzir.<br />
Outro problema é que línguas como o chinês precisam de mais de 256 símbolos para um texto normal, uma vez que o alfabeto deles é muito maior que o nosso. Surgem então tabelas de múltiplos bytes, onde mais de um byte era usado para cada caractere. Ainda assim, você precisava saber qual tabela multibyte foi usada... repetindo a confusão. Quem já trabalhou com Windows em C++ usando MBCS sabe a dor que isso causa...<br />
<br />
Uma das soluções para múltiplas linguas é criar um tabelaço que resolveria todos os problemas, foi criado o UNICODE. Desta forma, todas as línguas seriam representadas. O problema é que para conter todos os símbolos, vários bytes teriam que ser utilizados até para caracteres latinos.<br />
Assim, cada letra, numa simplificação seria representada por 2 bytes (simplificação, porque 2 bytes não são suficientes, pois temos mais de 65536 caracteres no Unicode !). Continuando, nosso A em unicode é representado como 0x00 0x41 e o B como 0x00 0x42. É cada letra passa a ser representada por dois bytes e um deles é o temido 0x00 ! O Ã ficou na posição 0x00 0xC3. No disco:<br />
ABÃ<br />
ficaram assim:<br />
0x00 0x41 0x00 0x42 0x00 0xC3<br />
<br />
Agora usamos 6 bytes para 3 caracteres. Ainda nem falamos de byte order ou de BOM... isso fica para outro dia :-D<br />
<br />
Com 6 bytes para 3 letras, logo apareceram problemas de armazenamento de dados, pois os arquivos começaram a dobrar de tamanho e a tomar 2x mais tempo para serem transmitidos... em teoria. Uma forma mais enxuta de representar estes caracteres foi desenvolvida: o UTF-8.<br />
Usando a mesma tabela base do Unicode, mas introduzindo um esquema de troca de páginas, ABÃ em UTF-8 são escritos no disco como:<br />
0x40 0x41 0xC3 0x83<br />
<br />
O Ã foi traduzido como 0xC3 0x83 !<br />
Passamos de 6 para 4 bytes, sem perder a capacidade de escrever em praticamente qualquer língua!<br />
<br />
O que acontece no Python. Um arquivo py de apenas uma linha para imprimir ABÃ pode ser escrito como:<br />
print "ABÃ"<br />
<br />
No disco ele será gravado se usarmos um editor utf-8 para escrevê-lo:<br />
0x70 0x72 0x69 0x6E 0x74 0x20 0x22 0x41 0x42 0xC3 0x83 0x22 0x0D 0x0A<br />
<br />
São esses bytes que o Python.exe vai ler.<br />
Em UTF-8 estes bytes seriam traduzidos para:<br />
<tt>0x70 p <br />0x72 r<br />0x69 i<br />0x6E n<br />0x74 t<br />0x20 -> espaço em branco<br />0x22 "<br />0x41 A<br />0x42 B<br />0xC3 --> primeiro byte do Ã<br />0x83 --> segundo byte do Ã<br />0x22 "<br />0x0D --> CR<br />0x0A --> LF</tt><br />
<br />
Mas o interpretador Python não sabe disso !<br />
<br />
C:\Users\nilo>\Python27\python.exe Desktop\test.py<br />
File "Desktop\test.py", line 1<br />
SyntaxError: Non-ASCII character '\xc3' in file Desktop\test.py on line 1, but no encoding declared; see <a class="moz-txt-link-freetext" href="http://www.python.org/peps/pep-0263.html">http://www.python.org/peps/pep-0263.html</a> for details<br />
<br />
Ele diz: Non-ASCII e depois \xc3 que é outra forma de dizer 0xC3<br />
Por que? No Python 2, o arquivo foi lido como ASCII, tendo apenas símbolos de 0 a 127. 0xC3 é 199, ou seja, fora da tabela ASCII, daí o erro.<br />
Neste caso, para resolver temos que colocar o # coding: utf-8<br />
O programa fica assim:<br />
<br />
<tt># coding: utf-8<br />print "ABÃ"<br />
</tt><br />
Que em hexa é:<br />
<tt>0x23 0x20 0x63 0x6F 0x64 0x69 0x6E 0x67 0x3A 0x20 0x75 0x74 0x66 0x2D 0x38 0x0D 0x0A # coding: utf-8<br />0x70 0x72 0x69 0x6E 0x74 0x20 0x22 0x41 0x42 <span style="color: #cc0000;">0xC3 0x83</span> 0x22 0x0D 0x0A print "ABÃ"</tt><br />
<br />
Veja que a segunda linha continua exatamente a mesma coisa.<br />
Mas ao executar-mos temos:<br />
<br />
<tt>C:\Users\nilo>\Python27\python.exe Desktop\test.py<br />ABÃ</tt><br />
<br />
Não deu erro, mas também não imprimiu o que queríamos. Vejamos o que deu errado.<br />
Primeiro a página de código do meu console:<br />
<tt>C:\Users\nilo>chcp<br />Active code page: 850</tt><br />
<br />
Ué... mas a página 850 suporta o Ã. Por que o Python não imprimiu corretamente?<br />
Simplesmente porque o cabeçalho # coding: utf-8 apenas indica em que codificação você escreveu o programa. Isso faz com ele consiga ler seu código, mesmo com acentos, desde que você tenha também usado um editor de textos em UTF-8, como diz o cabeçalho. Se você usar um cabeçalho diferente da real codificação do arquivo, os bytes em disco não vão mudar e os caracteres serão traduzidos usando tabelas incorretas. Isso é muito difícil de perceber apenas olhando, por isso eu recomendo o editor Hex. Com o tempo fica claro de fazer até no PsPad.<br />
<br />
Ainda temos que resolver o problema da saída. No Python 2, as strings não são traduzidas de uma tabela para outra. Esta ambiguidade foi corrigida, no Python 3, com o tipo byte... mas ai já é outra história. Se você quer que o Python traduza de uma tabela para outra, use o prefixo u na string, de forma a indicar que é uma string unicode, codificada no formato utf-8, como dito no cabeçalho do programa.<br />
Como strings comuns não tem tradução de página automaticamente, a sequência 0xc3 0x83 é mostrada na tela pela tabela da cp 850, que utiliza apenas um byte por caractere. Logo, dois bytes, dois caracteres. Um para o 0xc3 e outro para 0x83.<br />
<br />
Vejamos o programa com o u antes das aspas:<br />
# coding: utf-8<br />
print u"ABÃ"<br />
<br />
No disco:<br />
<tt>0x23 </tt><tt>0x</tt><tt>20 </tt><tt>0x</tt><tt>63 </tt><tt>0x</tt><tt>6F </tt><tt>0x</tt><tt>64 </tt><tt>0x</tt><tt>69 </tt><tt>0x</tt><tt>6E </tt><tt>0x</tt><tt>67 </tt><tt>0x</tt><tt>3A </tt><tt>0x</tt><tt>20 </tt><tt>0x</tt><tt>75 </tt><tt>0x</tt><tt>74 </tt><tt>0x</tt><tt>66 </tt><tt>0x</tt><tt>2D </tt><tt>0x</tt><tt>38 </tt><tt>0x</tt><tt>0D </tt><tt>0x</tt><tt>0A # coding: utf-8<br />
</tt><tt>0x</tt><tt>70 </tt><tt>0x</tt><tt>72 </tt><tt>0x</tt><tt>69 </tt><tt>0x</tt><tt>6E </tt><tt>0x</tt><tt>74 </tt><tt>0x</tt><tt>20 </tt><tt>0x</tt><tt><span style="color: #cc0000;"><u>75</u></span> </tt><tt>0x</tt><tt>22 </tt><tt>0x</tt><tt>41 </tt><tt>0x</tt><tt>42 </tt><tt>0x</tt><tt>C3 </tt><tt>0x</tt><tt>83 </tt><tt>0x</tt><tt>22 </tt><tt>0x</tt><tt>0D </tt><tt>0x</tt><tt>0A print u"ABÃ"</tt><br />
<br />
Veja que a única diferença é 0x75 (a letra u), mas o resultado é diferente:<br />
<br />
<tt>C:\Users\nilo>\Python27\python.exe Desktop\test.py<br />ABÃ<br />
</tt><br />
Agora saiu corretamente! Por que? Porque o Python sabe que a string é unicode e que a saída do console no meu Windows usa a cp850. Então ele converte os bytes durante a impressão para que sejam apresentados corretamente.<br />
<br />
Por isso é importante entender a codificação do seu arquivo e a codificação do console, banco de dados, etc. Você precisa ajudar o programa a se comportar bem.<br />
<br />
Vejamos agora o erro do cabeçalho inválido, onde declaramos UTF-8, mas nosso editor grava usando a cp1252 do Windows:<br />
Visualmente o arquivo tem o mesmo conteúdo:<br />
# coding: utf-8<br />
print u"ABÃ"<br />
Mas no disco:<br />
<tt>0x23 0x20 0x63 0x6F 0x64 0x69 0x6E 0x67 0x3A 0x20 0x75 0x74 0x66 0x2D 0x38 0x0D 0x0A # coding: utf-8<br />0x70 0x72 0x69 0x6E 0x74 0x20 0x75 0x22 0x41 0x42 <span style="color: #cc0000;">0xC3</span> 0x22 0x0D 0x0A</tt><tt> print u"ABÃ"</tt><br />
<br />
Resulta em:<br />
<tt>C:\Users\nilo>\Python27\python.exe Desktop\test.py<br /> File "Desktop\test.py", line 2<br /> print u"ABÃ"<br />SyntaxError: (unicode error) 'utf8' codec can't decode byte 0xc3 in position 0:<br />unexpected end of data</tt><br />
<br />
Por que? Bem, se você comparar a segunda linha em hexadecimal com a do exemplo anterior, verá que na cp1252, o à foi traduzido como 0xC3, ou seja, apenas um byte. Mas declaramos no cabeçalho que estaríamos usando UTF-8! O interpretador Python é um programa e confia no que declaramos. Ele lê o arquivo como se fosse UTF-8 e acha o 0xC3 que não é apenas um caractere, mas o marcador de início de troca de página. Depois de ler o 0xC3 ele espera o outro byte desta página, mas acha as aspas (0x22). 0xC3 0x22 é uma sequência inválida em UTF-8 e o interpretador explode com uma exceção de codificação.<br />
<br />
Voltando ao início do tópico:<br />
1. Codificação a usar em programas Python: por que UTF-8 é altamente recomendável<br />
Por que você pode enviar seus programas para outros computadores (linux, mac, windows) e usar acentos, evitando problemas futuros. Mas só funciona se seu cabeçalho expressar a codificação real usado no arquivo. Caso contrário não funciona.<br />
<br />
2. Codificações em geral e problemas causados e resolvidos por ela<br />
Acho que o início da mensagem responde essa.<br />
<br />
3. Um bug do Python no Windows, quando o prompt é configurado para página 65001<br />
Além das páginas da IBM, a Microsoft tem também as suas. Entre elas a cp1252 e a cp 65001 para o UTF8. Se você configurar e se somente se você configurar seu console para usar a página 650001, utf-8, o resultado é o seguinte:<br />
<br />
<tt>C:\Users\nilo>chcp 65001<br />Active code page: 65001<br />
<br />C:\Users\nilo>\Python27\python.exe Desktop\test.py<br />Traceback (most recent call last):<br /> File "Desktop\test.py", line 2, in <module><br /> print u"ABÃ"<br />LookupError: unknown encoding: cp65001<br />
<br />C:\Users\nilo>\Python32\python.exe Desktop\test.py<br />Fatal Python error: Py_Initialize: can't initialize sys standard streams<br />LookupError: unknown encoding: cp65001<br />
<br />This application has requested the Runtime to terminate it in an unusual way.<br />Please contact the application's support team for more information.<br />
</module></tt><br />
É só neste caso, bem específico e desnecessário para o português que temos um bug aberto ainda no Python 3.3.<br />
Não é um bug do Windows, pois funciona em Java, C# e C. É apenas a forma que o interpretador trata a cp65001 diferente de utf8.Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com0tag:blogger.com,1999:blog-8312361233856668827.post-26768541590308385112012-07-29T09:05:00.000-04:002019-02-17T12:42:43.620-04:00Por que UTF-8 e não ASCII para o Português? (PARTE I)<a href="https://blog.nilo.pro.br/posts/2012-07-29-por-que-utf-8-e-nao-ascii-para-o-portugues-parte-i/">Leia no novo blog</a><br />
<br />
Um outro post que fiz na Python-Brasil:<br />
<br />
Os colegas já falaram sobre o por quê do UTF-8.<br />
<br />
Eu gostaria apenas de lembrar que o assunto é mais complicado do que
parece, por exemplo no Python 2.7:<br />
<tt># -*- coding: utf-8 -*-<br />
print "Acentos: áéíóúãõç"<br />
print u"Acentos2: áéíóúãõç"</tt><br />
<br />
Execute o programa acima no Windows, pode ser pelo IDLE ou pelo
console:<br />
<br />
<b><tt>C:\Users\nilo\Desktop>\Python27\python.exe test.py<br />
Acentos: ├í├®├¡├│├║├º├ú├Á<br />
Acentos2: áéíóúçãõ</tt></b><br />
<br />
Você deve ter obtido bons resultados apenas na linha do Acentos2. Se
a string não é marcada com unicode, vai ser simplesmente impressa
como uma sequência de bytes, sem tradução. Se tiver o u na frente,
como em acentos2, o Python saca que precisa traduzir de unicode para
cp850, no caso do console aqui de casa. Já no Linux, as duas linhas
produzem resultados corretos!<br />
<br />
O encoding: utf-8 informa apenas a codificação do código fonte. Ou
seja, é apenas uma dica de como os caracteres deveriam estar
codificados. Para que funcione corretamente, seu editor de texto tem
que estar configurado para UTF-8 também. Se misturar, é desastre na
certa. Eu recomendo o PSPad no Windows para editar com UTF-8. Para
verificar o encoding de um arquivo que você não conhece, ou para ter
certeza de qual codificação seu editor realmente utilizou, use um
visualizador binário como o HxD [3]. No hex edit do PS Pad, atenção
que ele mostra os caracteres em Unicode, mesmo se a codificação for
UTF-8. Isso para lembrar que UTF-8 é uma representação ou forma de
codificação de caracteres Unicode. O Notepad++ pode também ser usado
para editar e codificar arquivos em UTF-8.<br />
No Mac e no Linux, tente o hexdump -C arquivo<br />
Quando o arquivo esta codificado corretamente em utf-8, você deve
ter mais de um byte para os caracteres acentuados.<br />
<br />
Por exemplo, o programa acima, criado no vim do Ubuntu:<br />
<tt>nilo@linuxvm:~$ hexdump -C test.py <br />
00000000 23 20 2d 2a 2d 20 63 6f 64 69 6e 67 3a 20 20 75 |# -*-
coding: u|<br />
00000010 74 66 2d 38 20 2d 2a 2d 0a 70 72 69 6e 74 20 22 |tf-8
-*-.print "|<br />
00000020 41 63 65 6e 74 6f 73 3a 20 <span style="color: #cc0000;">c3 a1
c3 a9 c3 ad c3</span> |Acentos: .......|<br />
00000030 <span style="color: #cc0000;">b3 c3 ba c3 a3 c3 b5 c3 a7</span>
22 0a 70 72 69 6e 74 |.........".print|<br />
00000040 20 22 41 63 65 6e 74 6f 73 32 3a 20 <span style="color: #cc0000;">c3 a1 c3 a9</span> | "Acentos2: ....|<br />
00000050 <span style="color: #cc0000;">c3 ad c3 b3 c3 ba c3 a3 c3 b5 c3
a7</span> 22 0a 0a |............"..|<br />
0000005f</tt><br />
<br />
Um site bacana é esse aqui: <a href="http://www.utf8-chartable.de/">http://www.utf8-chartable.de/</a><br />
<br />
Uma vez resolvido o problema de codificação dos fontes, restam
ainda: <br />
* A codificação do console<br />
* A codificação dos arquivos de dados<br />
* Codificação do banco de dados<br />
<br />
Tanto o Mac quanto Linux usam UTF-8 por padrão. O Windows usa a cp
1252 (GUI), compatível com iso8859_1. Cuidado também se você troca
arquivos entre máquinas Windows, Linux e Mac. E nunca misture duas
codificações no mesmo arquivo, pois isto gera erros difíceis de
detectar e resolver.<br />
É fácil misturar quando se faz append em um arquivo, vindo de outra
máquina ou mesmo gerado em um outro programa.<br />
O Windows em chinês, russo e outras línguas não utilizam a cp1252!
Por isso UTF-8 é uma boa pedida, pois consegue codificar caracteres
Unicode com um ou vários bytes, dependendo da necessidade.<br />
<br />
O Python 3 resolve muito destes problemas, mas a documentação
diz[1]:<br />
<br />
<br />
<blockquote>
<i><span style="font-family: sans-serif; font-size: 16px; font-style: normal; line-height: 20px; text-align: justify;">Files
opened as text files (still the default mode for </span><a class="reference external" href="http://docs.python.org/release/3.0.1/library/functions.html#open" style="color: #355f7c; font-family: sans-serif; font-size: 16px; font-style: normal; line-height: 20px; text-align: justify; text-decoration: none;" title="open"><tt class="xref docutils literal" style="background-color: transparent; font-size: 0.95em; font-weight: bold; padding-bottom: 0px; padding-left: 1px; padding-right: 1px; padding-top: 0px;"><span class="pre">open()</span></tt></a><span style="font-family: sans-serif; font-size: 16px; font-style: normal; line-height: 20px; text-align: justify;">) always
use an encoding to map between strings (in memory) and bytes
(on disk). Binary files (opened with a </span><tt class="docutils literal" style="font-size: 0.95em; font-style: normal; line-height: 20px; padding: 0px 1px; text-align: justify;"><span class="pre">b</span></tt><span style="font-family: sans-serif; font-size: 16px; font-style: normal; line-height: 20px; text-align: justify;"> in the mode argument)
always use bytes in memory. This means that if a file is
opened using an incorrect mode or encoding, I/O will likely
fail loudly, instead of silently producing incorrect data. It
also means that even Unix users will have to specify the
correct mode (text or binary) when opening a file. There is a
platform-dependent default encoding, which on Unixy platforms
can be set with the </span><tt class="docutils literal" style="font-size: 0.95em; font-style: normal; line-height: 20px; padding: 0px 1px; text-align: justify;"><span class="pre">LANG</span></tt><span style="font-family: sans-serif; font-size: 16px; font-style: normal; line-height: 20px; text-align: justify;"> environment variable
(and sometimes also with some other platform-specific
locale-related environment variables). In many cases, but not
all, <u>the system default is UTF-8; you should never count
on this default</u>. Any application reading or writing more
than pure ASCII text should probably have a way to override
the encoding. There is no longer any need for using the
encoding-aware streams in the </span><a class="reference
external" href="http://docs.python.org/release/3.0.1/library/codecs.html#module-codecs" style="color: #355f7c; font-family: sans-serif; font-size: 16px; font-style: normal; line-height: 20px; text-align: justify; text-decoration: none;" title="Encode and decode data and streams."><tt class="xref docutils literal" style="background-color: transparent; font-size: 0.95em; font-weight: bold; padding-bottom: 0px; padding-left: 1px; padding-right: 1px; padding-top: 0px;"><span class="pre">codecs</span></tt></a><span style="font-family: sans-serif; font-size: 16px; font-style: normal; line-height: 20px; text-align: justify;"> module.</span></i></blockquote>
<br />
A parte que sublinhei diz: "... o padrão do sistema é UTF-8; você
não deve contar nunca com este padrão..."<br />
Resumindo, é um assunto que merece ser estudado, pois causa
problemas "mágicos" que sempre aparecem.<br />
<br />
Um texto que explica tudo com detalhes pode ser encontrado em [2].<br />
<br />
[]<br />
<br />
Nilo Menezes<br />
[1] <a href="http://docs.python.org/release/3.0.1/whatsnew/3.0.html">http://docs.python.org/release/3.0.1/whatsnew/3.0.html</a><br />
[2] <a href="http://wiki.python.org.br/TudoSobrePythoneUnicode">http://wiki.python.org.br/TudoSobrePythoneUnicode</a><br />
[3] <a href="http://mh-nexus.de/en/hxd/">http://mh-nexus.de/en/hxd/</a>Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com1tag:blogger.com,1999:blog-8312361233856668827.post-8451605735862327222012-07-25T08:35:00.000-04:002019-02-17T12:43:06.272-04:00Por que aprender várias linguagens de programação?<a href="https://blog.nilo.pro.br/posts/2012-07-25-por-que-aprender-varias-linguagens-de-programacao/">Leia no novo blog</a><br />
<br />
Outro post da lista Python-Brasil, onde se discutia qual a melhor linguagem para se aprender a programar:<br />
<br />
<br />
Eu concordo que Python é uma ótima linguagem como primeira linguagem
de programação.<br />
Mas nem tudo é Hello World e muitas vezes o professor ou o
coordenador do curso ensaiam de apresentar Java ou C++... para facilitar cursos futuros. Exemplo: apresentam um Java troncho
em ICC para depois afinar num curso de OO. Outros por não conhecerem Python ou descartarem Python por ser
script.<br />
<br />
Algumas faculdades são também assombradas por fatalistas que pregam
o ensino de linguagens do mercado. Quando fiz faculdade, em aprendi Pascal, C, Java, Modula, Prolog,
Assembly do MIPS e outros bichos. Nenhuma destas linguagens foi
ensinada diretamente, mas no contexto das disciplinas de ICC,
estruturas de dados, sistemas operacionais, etc. Já na época tinha
fantasma dizendo que deveríamos aprender Word e outros praguejando
Prolog. O esquema era que deveríamos aprender as linguagens
sozinhos, eles só nos davam um bom motivo :-D<br />
<br />
Eu fiz faculdade depois de já estar trabalhando, depois de um curso
técnico em informática... 18 anos depois eu tenho uma visão pessoal. Acredito que o melhor mesmo é aprender e ter contato com o maior
número possível de linguagens na faculdade. De preferência,
linguagens com paradigmas diferentes.<br />
<br />
Python é muito boa, mas se for a única, estaremos repetindo o mesmo
erro.<br />
<br />
Eu acredito que só programar numa linguagem é como falar apenas uma
língua.<br />
<br />
Eu defendo Python como primeira linguagem por ser uma das mais
fáceis de aprender.<br />
Além disso, a taxa de retorno do Python é excelente. Você consegue
premiar o aluno, pois este fica contente em saber que consegue fazer
algo útil sozinho. Com C, muitos desistem, pois o esforço inicial é
grande e a impressão é que o trabalho não rende.<br />
<br />
<brincadeira>ICC com Java ou C++ é terrorismo
:-D</brincadeira><br />
<br />Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com0tag:blogger.com,1999:blog-8312361233856668827.post-32784563964725775102012-07-24T08:40:00.000-04:002012-07-24T08:40:00.402-04:00XML-ManDe <span class="blsp-spelling-error" id="SPELLING_ERROR_0">todos</span> os <span class="blsp-spelling-error" id="SPELLING_ERROR_1">super-heróis</span> que <span class="blsp-spelling-error" id="SPELLING_ERROR_2">vivem</span> na <span class="blsp-spelling-error" id="SPELLING_ERROR_3">ilha</span> de Java, o mais <span class="blsp-spelling-error" id="SPELLING_ERROR_4">perigoso</span> é o <span class="blsp-spelling-error" id="SPELLING_ERROR_5">XML-Man</span>.<br />
<div>
<br /></div>
<div>
<span class="blsp-spelling-error" id="SPELLING_ERROR_6">XML-Man</span> <span class="blsp-spelling-error" id="SPELLING_ERROR_7">resolve</span> <span class="blsp-spelling-error" id="SPELLING_ERROR_8">tudo</span> <span class="blsp-spelling-error" id="SPELLING_ERROR_9">com</span> a <span class="blsp-spelling-error" id="SPELLING_ERROR_10">herdeira</span> do <span class="blsp-spelling-error" id="SPELLING_ERROR_11">SGML</span>, prima do HTML. <span class="blsp-spelling-error" id="SPELLING_ERROR_12">Tudo</span>, <span class="blsp-spelling-error" id="SPELLING_ERROR_13">tudo</span>. Se fosse <span class="blsp-spelling-error" id="SPELLING_ERROR_14">possível</span>, <span class="blsp-spelling-error" id="SPELLING_ERROR_15">escreveria</span> <span class="blsp-spelling-error" id="SPELLING_ERROR_16">programas</span> Java <span class="blsp-spelling-error" id="SPELLING_ERROR_17">em</span> <span class="blsp-spelling-error" id="SPELLING_ERROR_18">XML</span> <span class="blsp-spelling-error" id="SPELLING_ERROR_19">também</span>. O <span class="blsp-spelling-error" id="SPELLING_ERROR_20">poder</span> de <span class="blsp-spelling-error" id="SPELLING_ERROR_21">XML-Man</span> é de <span class="blsp-spelling-error" id="SPELLING_ERROR_22">criar</span> <span class="blsp-spelling-error" id="SPELLING_ERROR_23">ferramentas</span> de <span class="blsp-spelling-error" id="SPELLING_ERROR_24">configuração</span> e <span class="blsp-spelling-error" id="SPELLING_ERROR_25">gerenciamento</span> que <span class="blsp-spelling-error" id="SPELLING_ERROR_26">você</span> <span class="blsp-spelling-error" id="SPELLING_ERROR_27">não</span> <span class="blsp-spelling-error" id="SPELLING_ERROR_28">precisaria</span> se <span class="blsp-spelling-error" id="SPELLING_ERROR_29">não</span> <span class="blsp-spelling-error" id="SPELLING_ERROR_30">tivesse</span> que <span class="blsp-spelling-error" id="SPELLING_ERROR_31">escrever</span> tudo em <span class="blsp-spelling-error" id="SPELLING_ERROR_32">XML</span>.</div>
<div>
<br /></div>
<div>
<br /></div>Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com0tag:blogger.com,1999:blog-8312361233856668827.post-77971306525454393602012-07-21T08:30:00.000-04:002019-02-17T12:43:45.825-04:00Certificados e diplomas em informática<a href="https://blog.nilo.pro.br/posts/2012-07-21-certificados-e-diplomas-em-informatica/">Leia no novo blog</a><br />
<br />
Esta foi a resposta que dei num post na lista Python-Brasil sobre a importância de certificados.<br />
<br />
Há alguns anos eu contratei mais de 100 profissionais de informática... desenvovedores C++, Java, testadores etc. Durante este período, eu aprendi a não confiar em diploma algum.<br />
<br />
Eu contratei gente vinda de faculdade particular muito melhor que de
faculdades federais ou estaduais, embora esta não fosse a regra, mas a
exceção. Se a pessoa que contrata é a mesma que vai trabalhar com você,
por exemplo seu futuro chefe, ele procura alguém que resolva os
problemas dele, diploma e certificado ele deixa para o RH ver :-D. Eu
conheci muita gente boa que nem faculdade tinha, mas são casos raros e
não a regra. Para algumas posições de TI, faculdade é luxo... para
outras é absolutamente necessário. Eu sei que para administração de
redes, certificação é fundamental.<br />
<br />
A questão de diploma/certificados aparece quando você tenta trabalhar numa empresa maior onde:<br />
<br />
a) Alguém do RH ou empresa externa vai fazer a pré-seleção para o
pessoal de TI. Neste caso, a primeira seleção é quase mecânica com um
check list do CV: tem faculdade? É de informática ou engenharia? trabalha há quantos anos... etc. Normalmente o RH não arrisca passar um
CV para entrevista se nem tiver encontrado alguns pré-requisitos.
Algumas empresas tem mesmo cotas de qualidade e escolarização, onde uma
determinada porcentagem tem que ter mestrado, doutorado ou graduação.
Onde eu trabalhei, nosso cliente pedia um inventário de talentos e
diplomas. Neste caso, é importante aparecer a palavra Python, seja na
sua experiência, ou mesmo no nome de um curso que você fez (mais
abaixo).<br />
<br />
b) A pessoa recebe um número excessivo de CVs para a mesma vaga, na
época eu recebi mais de 2200. Neste caso, ela vai fazer um pesquisa como
o pessoal de RH. Normalmente se privilegia experiência nesses casos,
mas sem os diplomas, depende muito. No meu caso, minha secretária era
professora de inglês. Eu passava uns CVs pré-selecionados para ela, ela
ligava, fazia um teste por telefone e filtrava os CVs que eu deveria
chamar para entrevista, sorte minha.<br />
<br />
Eu comecei a trabalhar cedo com informática (início dos anos 90) e até
meus 28 anos eu não tinha diploma universitário. Ná época, o importante
para mim era ganhar dinheiro. Consegui ótimas vagas em empresas
pequenas, apenas com um diploma do curso técnico. Nunca fiz certificação
alguma. Nunca trabalhei em mega-empresas, pois o salário dependia da
formação e nunca era bom para mim (pagavam menos).<br />
Depois eu me formei e fiz mestrado. Valeu muito a pena na hora de
trabalhar fora, pois para ter permissão de trabalho o diploma é
obrigatório (pelo menos na Bélgica). Aqui, nem de certificação ouço
falar, mas um "engenheiro" passa 5 anos na faculade em tempo integral.
Aqui já se sai mestre. Escrevi "engenheiro", pois aqui esta palavra
praticamente substitui o nosso graduado.<br />
<br />
Como você mesmo disse, toda forma de adquirir conhecimento é importante.
Se eu morasse em SP, eu assistira o curso do Luciano, tenho certeza que
o networking e os bizus de Python apareceriam, independente do meu
nível de formação ou experiência. Um curso online pode ser muito bom,
mas depende muito. Eu fiz uns no Cursera, uns excelentes e outros que
não consegui nem chegar ao fim. Tudo depende, só de experimentar já se
aprende muito.<br />
<br />
Quanto a certificado de curso livre, sinceramente, nunca serviram para
nada, na minha experiência. Se for fazer o curso, faça para aprender. O
certificado só serve para o RH fazer o tal inventário de talentos mais
tarde e se para isso servir. Eu tenho maior orgulho de um curso de
programação Basic I que fiz em 86... mas nunca tive oportuinidade de
usá-lo, ficou como recordação mesmo.<br />
<br />
Se eu voltasse a contratar hoje, eu daria preferência para pessoas que
saibam aprender sozinhas. Seja com livros, cursos presenciais ou
on-line, mas alguém que se vire para aprender um assunto. Nessa lista,
vale até que aprendeu Python lendo a documentação on-line que é ótima.
Mas se eu tivesse que escolher entre dois candidatos com experiência
equivalente, mas um formado e outro não, o com diploma teria a vaga.<br />
<br />
Três coisas são importantes num CV: experiência, formação superior e em alguns casos certificações.<br />
<br />
Cursos livres eu não colocaria nem no CV. Salvo se o CV estiver muito
magrinho. Se o curso foi de Python, é mais importante dizer que sabe e
que ja fez X, Y e Z com a linguagem. O curso livre pode aparecer na
entrevista. O networking do curso pode ajudar mais que o curso em si,
mas lembre-se que isso pode ser uma faca de dois gumes, pois estas
pessoas lembrarão de como você se comportou no curso, nos intervalos,
etc.<br />
<br />
Resumindo, eu faria um curso online, independente do certificado, se
quisesse acelerar o aprendizado de uma linguagem/assunto. Mas não
ficaria só por ai.Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com0tag:blogger.com,1999:blog-8312361233856668827.post-73169441557433077592012-07-17T14:11:00.000-04:002012-07-17T14:11:13.334-04:00De volta à AlemanhaComecei meu estágio em Braunschweig... 10 anos depois de Munique, me deparo com problemas bem diferentes. Munique é uma cidade grande... Braunschweig tem 250 mil habitantes. A estrutura é muito boa, mas diferentemente de Munique, só com inglês você não faz tudo :-(<br />
10 anos mais velho, aprender alemão deixa de ser tão divertido. A falta de novidades faz tudo virar rotina bem rápido. Pelo menos está chovendo menos que em Mons :-D<br />
<br />
<br />
<br />Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com1tag:blogger.com,1999:blog-8312361233856668827.post-42046318407282712632012-04-28T06:25:00.001-04:002012-04-28T06:25:15.673-04:00Gênesis InformáticoComo estou quase sendo obrigado a programar em Java de novo, fica a piada:<br />
<br />
No início não havia computadores, apenas máquinas.<br />
<br />
As máquinas existiam, mas a lógica era limitada.<br />
<br />
O homem domou a corrente elétrica e a transformou em sinais digitais capazes de transportar infomação.<br />
<br />
Tudo era nulo, apenas bits 0.<br />
<br />
Criou-se então os bits 1.<br />
<br />
Surge a lógica.<br />
<br />
Mas os bits 0 e 1 eram sós e o byte foi criado para agrupá-los.<br />
<br />
Bits e bytes sem nome vagavam pelas máquinas, sem saber o que fazer. As máquinas ainda eram criadas para uma finalidade bem específica e a lógica codificada no material. Surgiam os primeiros sistemas.<br />
<br />
O programador foi então criado e a ele foi dada a responsabilidade de organizar bits e bytes. Surgia o Caos.<br />
<br />
Mas o programador era só e criou linguagens e compiladores para acompanhá-lo. O sistema enviou mensagens dizendo que aquilo era perigoso e que agora milhões de bits e bytes teriam que ser utilizados para fazer as coisas mais simples. A mensagem foi ignorada e surgia assim o bug.<br />
<br />
O programador continuou proliferando código escrito em múltiplas linguagens. Quando inventou o C, o sistema enviou uma nova mensagem dizendo que já era suficiente e que o programador deveria se contentar com bits, bytes e ponteiros, todos felizes na RAM.<br />
<br />
O C era bom e foi usado para criar o UNIX.<br />
<br />
O diabo, invejoso, criou o Windows para atentar o programador por toda sua existência.<br />
<br />
Novos programas foram criados a partir desses princípios básicos entre o universo de software e hardware. Um dia, o programador traiu o sistema e criou jAdao e jEva, escrenvendo-os num dialeto de C++ banido aos habitantes da ilha de Java. O sistema então castigou jAdao e jEva por terem acumulado tantos bits e bytes a ponto de usar toda a memória do sistema e os baniu para Java dizendo:<br />
<br />
Usarás o dialeto no teu dia-a-dia.<br />
Esquecerás o que é um bit.
<br />
Usarás um IDE lento.<br />
Verás tudo através deste código que não conhecerá limites de tamanho, performance ou coerência.<br />
Jamais tocarás num ponteiro.<br />
Teus descendentes não usarão propriedades e todos os programadores terão que escrever getters e setters pelo resto de seus dias.<br />
Teu código só entenderá XML e produzirá logs indecifráveis.<br />
Serás atormentado por cobras e outras pedras preciosas que não sofrerão desses males, serão menores e mais ágeis, porém ainda mais lentas.<br />
<div>
Tuas classes proliferarão e dominarão o mundo.
<br />
Este dialeto será portátil e te acompanhará por todas as máquinas que usares, sempre pendindo para fazer update.<br />
<br />
<br />
<br />
<br />
<br /></div>Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com2tag:blogger.com,1999:blog-8312361233856668827.post-33563961866343348332011-06-04T16:51:00.000-04:002019-02-17T12:44:29.622-04:00A Era do Javascript<a href="https://blog.nilo.pro.br/posts/2011-06-04-a-era-do-javascript/">Leia no novo blog</a><br />
<br />
Meus primeiros contatos com Javascript datam de uma época distante, onde o Netscape era o mais popular browser da Internet! Eu usava um servidor Sun e estudava um produto chamado Netscape One, que vinha com uma tecnologia de script do lado servidor chamada de LiveScript. Ela já era também disponível no browser, mas não prestava para muita coisa na época. As tecnologias quentes eram o CGI e o SSI. Eu me interessava também pela novidade do momento, a tal de PHP 3 ! Se minha memória não falha, estou falando do período de 95 a 98.<br />
<div>
<br />
<div>
Tecnologias como Java Applets nunca "pegaram", sempre foram consideradas lentas demais, pesadas demais. Já com o Javascript, além de lento, não se podia fazer muita coisa. A maior parte dos trabalhos se limitava a validação de campos e tarefas menores com strings e datas. Jogos em Javascript, só de forca e da velha. Nessa época eu escrevia CGI's em C++ e lembro da magia que foi aprender a programar em Perl.</div>
<div>
<br /></div>
<div>
<div>
Javascript nunca foi uma linguagem "seria" até alguns anos atrás, quando ficou impossível de ser ignorada. No lançamento do iPhone, muitos ficaram chocados com idéia inicial da Apple de aplicações web com Javascript... depois é que eles liberaram aplicações nativas. Isso já em 2007... mas a maioria dos celulares tinha browsers de Internet sofríveis, muitos sem Javascript. Hoje vemos compiladores de e para Javascript, compactadores, obfuscadores e uma infinidade de livros. Javascript está instalada na maioria dos computadores do mundo, incluindo telefones celulares e mesmo sistemas embarcados. Você pode até mesmo usar Javascript para instrumentar seu programa escrito em Java ou C++! É impossível ignorar a força do Javascript. A prova final para mim foi há algumas semanas quando vi uma pequena máquina virtual, suficientemente completa para rodar o <a href="http://bellard.org/jslinux/">kernel do Linux em Javascript</a>, fora jogos como Doom e emuladores diversos, completamente escritos ou portados para Javascript!</div>
</div>
<div>
<br /></div>
<div>
A impressão que tenho é o mesmo que aconteceu na década de 80, onde para se ter velocidade programava-se em Assembly, mas era difícil e cada micro tinha o seu próprio Assembly incompatível. Isso era nítido na programação de jogos. Até nas revistas, os programas eram escritos parte em Basic, parte em Assembly. Com o passar do tempo, ficou prático escrever em Pascal e C, compiladas e muito mais fáceis que Assembly. Com o Javascript acontece a mesma coisa, quando começo a ver a quantidade de compiladores que agora geram códigos em Javascript, tenho a mesma impressão. Como escrever grandes programas em Javascript ainda é difícil, diversas bibliotecas e ferramentas podem ser utilizados para reduzir este esforço. Uma dessas ferramentas é a <a href="http://jashkenas.github.com/coffee-script/">CoffeScript</a>, que dá uma sintaxe limpa ao Javascript, inspirada em Python. É como se essas linguagens estivessem fazendo o trabalho que C e Pascal fizeram antes e o Javascript sendo o novo Assembly universal, afinal é suportado pela maioria dos micros e é multiplataforma. Não falo aqui que o Javascript substitui o Assembly, nada disso. Apenas um posicionamento das linguagens como ferramenta num cenário de aplicações global.</div>
<div>
<br /></div>
<div>
Como tudo agora é na ou para a Web, Javascript é incontornável. A popularização do suporte a HTML 5 nos browsers só vem a ajudar. Nos últimos anos, motores Javascript de alta performance se popularizaram no Google Chrome, FireFox, Safari e até mesmo no Internet Explorer! Canvas, WebGL, bancos de dados locais, web sockets, web workers, tudo disponível em praticamente qualquer browser, via Javascript! </div>
<div>
<br /></div>
</div>
Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com0tag:blogger.com,1999:blog-8312361233856668827.post-70883848556658485102011-03-12T18:35:00.000-04:002011-03-12T18:35:43.939-04:00Khan no TED<iframe allowfullscreen="" frameborder="0" height="390" src="http://www.youtube.com/embed/nTFEUsudhfs" title="YouTube video player" width="640"></iframe><br />
Eu ainda me impressiono com a Khan Academy. Fantástica ferramenta para quem precisa relembrar e aprender conceitos de matemática, física, etc. Agora ele ataca problemas de nivelamento entre estudantes com ritmos de aprendizagem diferente. Quando teremos este tipo de iniciativa em português? Vídeos e não mini novelas! O potencial das video aulas, com playback controlado pelos alunos é interessante, mesmo em áreas sem Internet ou infra-estrutura para video aulas transmitidas ao vivo.Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com0tag:blogger.com,1999:blog-8312361233856668827.post-17248325829482159162011-02-08T06:12:00.001-04:002019-02-17T12:53:20.271-04:00Programe ou seja programado<a href="https://blog.nilo.pro.br/posts/2011-02-08-programe-ou-seja-programado/">Leia no novo blog</a>
<br>
Eu vi este vídeo ano passado, mas como não tinha legendas em português... não o coloquei aqui.<br />
Porém, mesmo sem legendas, estas ideias não poderiam ficar sem um comentário breve.<br />
Assista ao vídeo: <br />
<br />
<iframe allowfullscreen="" frameborder="0" height="295" src="http://www.youtube.com/embed/imV3pPIUy1k?fs=1" width="480"></iframe><br />
<br />
Douglas Rushkoff apresenta a importância do conhecimento de programação no mundo moderno.<br />
Não que ele defenda um mundo de programadores ou que aborde uma linguagem de programação X ou Y.<br />
O que é fantástico no vídeo é a relação entre a invenção da escrita, programação e as mídias modernas.<br />
Do início do compartilhamento do conhecimento através da escrita, de livros e depois com a invenção da prensa moderna, ele compara o acesso do cidadão comum a cada uma destas formas de transmissão do saber. Como esse conhecimento era controlado e distribuído entre as pessoas comuns e como sempre a sociedade comum estava um passo atrás das últimas tecnologias de produção e distribuição deste mesmo conhecimento.<br />
<br />
A questão é realmente saber se hoje estamos ficando para trás, ao não incluirmos a programação no mundo de nossos filhos. Hoje, tão importante quanto ler e escrever, programar é necessário. De uma simples planilha a sistemas complexos, diferentes níveis de programação são utilizados. Assim como a invenção da escrita não levou a uma sociedade de escritores, como apresentado no vídeo, a programação não criará uma sociedade de programadores. O ponto é realmente ter ideia do que pode ser programado, de ter noções básicas de como as coisas funcionam e não de simplesmente utilizar os meios como nos são fornecidos: Twitter, Facebook, Google e mesmo a web.<br />
<br />
O vídeo postado pela Wilmara no Facebook me chamou a atenção para o problema da educação:<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="390" src="http://www.youtube.com/embed/-uqDyBR29as" title="YouTube video player" width="480"></iframe><br />
<br />
Isso me fez pensar no por quê meus filhos ainda perguntam que língua é falada em tal ou tal país... tendo a Wikipédia e o Google disponíveis. Eles sabem acessar o Facebook, jogos e principalmente o YouTube. Mas a capacidade de síntese não é dada ou ensinada na escola. Continuamos a ensinar a decorar e não a reunir novas informações e criticar o conhecimento. Lembro do tempo que passei para achar o nome científico de 10 animais quando estava no primário... hoje isso seria feito em segundos!<br />
<br />
O foco continua sendo: ler, memorizar, repetir. Eu fui confrontado com esta realidade no meu primeiro emprego, como professor de lógica de programação. Os alunos estavam na faixa dos 15 anos, e eu tinha acabado de completar 18. Quando começamos a trabalhar com problemas simples de matemática, como porcentagens e proporções a casa caiu. Isso era muito estranho, pois eu era professor numa escola técnica, digamos elitista do ponto de vista que você tinha que ser aprovado num concurso para estudar lá. Não eram alunos ruins ou com problemas de aprendizado, mas alunos acima da média! O que alguns tinham dificuldade era em como utilizar o conhecimento passado através de exercícios de cálculo para uma nova realidade. Repito, não era problema em calcular, mas de saber que técnica já conhecida utilizar para resolver um problema similar, mas em outro contexto. Era simplesmente desenvolver suas equações, escrever enunciados de novos problemas, aplicar o que já sabiam, o contrário da forma como tinham sido preparados a vida toda!<br />
<br />
Traçando um paralelo: será que ao invés de apenas deixar nossas crianças assistirem vídeos no YouTube, deveríamos ensiná-las a criar seus próprios vídeos? A escrever corretamente, ou pelo menos tentar escrever corretamente, usando wikis e mesmo criando suas próprias home pages? Aqui falamos de programação em sentido mais amplo, como dominar as ferramentas que utilizamos. Afinal, eu não sou artista, mas recebi noções básicas de arte na escola... ainda sei combinar cores, embora não saiba desenhar ou pintar. Precisamos ensinar nossas crianças a dominar as novas mídias. Não para criar uma sociedade de programadores, mas para evitar que eles simplesmente consumam conhecimento sem a menor noção de como este é produzido, armazenado, disponibilizado e principalmente as formas que pode ser controlado.<br />
<br />
Vídeo bônus:<br />
<iframe frameborder="0" height="225" src="http://player.vimeo.com/video/18681508" width="400"></iframe><br />
<a href="http://vimeo.com/18681508">Only at OR Books: Program or Be Programmed by Douglas Rushkoff</a> from <a href="http://vimeo.com/orbooks">OR Books</a> on <a href="http://vimeo.com/">Vimeo</a>.Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com0tag:blogger.com,1999:blog-8312361233856668827.post-26049270993420892322010-12-02T14:22:00.000-04:002019-02-17T12:53:57.015-04:00Livro de Introdução Programação com Python já está venda<a href="https://blog.nilo.pro.br/posts/2010-12-02-livro-de-introducao-programacao-com-python-ja-esta-venda/">Leia no novo blog</a><br />
<br />
Nada como uma noite fria e cheia de neve para colocar o blog em dia!<br />
Meu livro de programação foi publicado pela Editora Novatec e já está disponível para venda.<br />
Eu criei uma página sobre o livro, com links para livrarias e emails de contato:<br />
<br />
<a href="http://www.nilo.pro.br/iprog/">http://www.nilo.pro.br/iprog/</a><br />
<br />
O livro foi escrito de forma a apresentar novos conceitos gradualmente, com exemplos e exercícios. Quando comecei a dar aulas, eu ensinava Basic e Pascal. Com o Basic, tinha o problema dos números de linha. Com o Pascal os ponto-e-vírgula... era muita coisa para escrever antes de começar a programar. Nestes casos, o uso de um pseudo-código claro e simples, livre de símbolos em excesso era interessante. Eu evitei usar pseudo-código ou fluxogramas, pois acredito que ao se aprender a programar, o melhor é ir direto ao ponto. Com a linguagem Python, isso tudo fica muito mais simples. Escrever em pseudo-código pode até ser mais complicado que escrever em Python!<br />
O leitor/aluno precisa ver algo acontecer para continuar estudando. O importante é saber ler o programa e entender o que vai acontecer quando o programa for executado. Eu realmente acredito que o aluno deve saber se o programa está correto antes de executá-lo. A execução é apenas uma confirmação. Uma seção sobre rastreamento foi incluída no livro, para preservar esta importante etapa no aprendizado de programação.<br />
<br />
A clareza e simplicidade da linguagem são realmente muito importantes para facilitar o aprendizado. Python torna a programação acessível par estudantes de outras áreas e não apenas de computação e engenharia.<br />
Outra característica do Python é que ela permite usar quase tudo que temos no computador. Uma vez aprendendo a programar em Python, pode-se continuar estudando outras linguagens de programação ou partir diretamente para aplicações web, jogos, banco de dados, etc. O caminho é mais suave e sem interrupções. Não serve apenas de linguagem introdutória, mas do dia a dia.<br />
<br />
Escrever um livro era algo que eu sempre quis fazer. Dá muito trabalho, tem que ler, reler, re-escrever e corrigir inúmeras vezes. O trabalho de revisão é realmente incrível, principalmente com a nova ortografia e com os desafios de escrever um livro técnico para leigos. Ter uma editora ajuda muito nesta tarefa, pois sozinhos nós começamos a não perceber mais os erros. Digo nós porque minha esposa ajudou na revisão.<br />
<br />
<br />
<span class="Apple-style-span" style="font-family: inherit;">Título: Introdução à Programação com Python</span><br />
<span class="Apple-style-span" style="font-family: inherit;">Autor: Nilo Ney Coutinho Menezes</span><br />
<span class="Apple-style-span" style="font-family: inherit;">ISBN: 978-85-7522-250-8<br />
Páginas: 224</span><br />
<span class="Apple-style-span" style="font-family: inherit;">Editora: <a href="http://novatec.com.br/livros/intropython/">Novatec</a><br />
Ano: 2010</span><br />
<br />
Resenha do Livro:<br />
<br />
<span class="Apple-style-span" style="line-height: 20px;"></span><br />
<div style="margin-bottom: 1.5em; margin-left: 0em; margin-right: 0em; margin-top: 0em; padding-bottom: 0em; padding-left: 0em; padding-right: 0em; padding-top: 0em;">
<span class="Apple-style-span" style="font-family: inherit;">Este livro é orientado ao iniciante em programação. Os conceitos básicos de programação, como expressões, variáveis, repetições, decisões, listas, funções e arquivos, são apresentados um a um com exemplos e exercícios. A obra visa explorar a programação de computadores como ferramenta do dia a dia. Ela pode ser lida durante um curso de introdução à programação de computadores e usada como guia de estudo para autodidatas. Para aproveitamento pleno do conteúdo, conhecimentos básicos de informática, como digitar textos, abrir e salvar arquivos, são suficientes. Todo software utilizado no livro pode ser baixado gratuitamente, sendo executado em Windows, Linux e Mac OS X.</span></div>
<div style="margin-bottom: 1.5em; margin-left: 0em; margin-right: 0em; margin-top: 0em; padding-bottom: 0em; padding-left: 0em; padding-right: 0em; padding-top: 0em;">
<span class="Apple-style-span" style="font-family: inherit;">Embora a linguagem Python (versão 3.x) seja muito poderosa e repleta de recursos modernos de programação, este livro não pretende ensinar a linguagem em si, mas ensinar a programar. Alguns recursos da linguagem não foram utilizados para privilegiar os exercícios de lógica de programação e oferecer uma preparação mais ampla ao leitor para outras linguagens. Essa escolha não impediu a apresentação de recursos poderosos da linguagem, embora o livro não seja fundamentalmente uma obra de referência.</span></div>
<br />
<a href="http://novatec.com.br/livros/intropython/sumario9788575222508.pdf">Sumário (PDF)</a>Anonymoushttp://www.blogger.com/profile/11395140461172290588noreply@blogger.com0