Julio Biason
6 years ago
1 changed files with 501 additions and 0 deletions
@ -0,0 +1,501 @@
|
||||
+++ |
||||
|
||||
title = "Fugindo Para as Colinas Com Python" |
||||
date = 2017-09-18 |
||||
|
||||
category = "code" |
||||
|
||||
[taxonomies] |
||||
tags = ["python", "pt-br"] |
||||
|
||||
+++ |
||||
|
||||
"Hello World" não mostra exatamente o poder de qualquer linguagem. |
||||
Por isso resolvi fazer uma "introdução" ao Python com um problema de |
||||
fujir para as colinas. Ou quase isso. |
||||
|
||||
<!-- more --> |
||||
|
||||
Um dos problemas de qualquer iniciante em qualquer linguagem é pegar o |
||||
"feeling" da linguagem. E, realcionado com isso, é o fato que a maior parte |
||||
dos códigos introdutórios de uma linguagem é o "hello world". Por exemplo: |
||||
|
||||
```python |
||||
print('hello world') |
||||
``` |
||||
|
||||
O que isso diz de Python? No máximo que `print` precisa de parentêses, |
||||
strings podem ser geradas com aspas simples e não precisa de pronto-e-vírgula |
||||
no final. |
||||
|
||||
O que não é muita coisa. |
||||
|
||||
Uma coisa que eu sempre digito quando acontece algum problema é |
||||
|
||||
> Fujam para as colinas! |
||||
|
||||
Só que repetir isso toda hora não me faz um cara muito popular. É por isso que |
||||
eu fico mudando essa frase para coisas do tipo |
||||
|
||||
> Funam para as colijas! |
||||
|
||||
Ou ainda |
||||
|
||||
> Lunam para as jocifas! |
||||
|
||||
Obviamente eu não paro para ficar pensando em todas as possibilidades e fico |
||||
alterando letras randomicamente manualmente. Eu tenho um script para isso. Um |
||||
script em Python. |
||||
|
||||
## O Básico |
||||
|
||||
```python |
||||
print('Fujam para as colinas!') |
||||
``` |
||||
|
||||
Assim já podemos irritar as pessoas repetindo a mesma informação. |
||||
|
||||
O próximo passo é preparar o terreno para a randomicidade de frase. |
||||
|
||||
```python |
||||
print('{}u{}am para as {}o{}i{}as!'.format('f', 'j', 'c', 'l', 'n')) |
||||
``` |
||||
|
||||
Agora já temos algumas coisas pra estudar. |
||||
|
||||
### Help incluso e `format` |
||||
|
||||
Primeiro, `format`. `format` é um método dos objetos do tipo string. Como |
||||
eu sei disso? Porque, um dia, estava eu muito belo e folgado, me perguntando |
||||
"O que as strings em Python podem fazer?", abri o interpretador do Python e |
||||
digitei: |
||||
|
||||
> `help(str)` |
||||
|
||||
E, lá no meio... |
||||
|
||||
``` |
||||
| format(...) |
||||
| S.format(*args, **kwargs) -> string |
||||
| |
||||
| Return a formatted version of S, using substitutions from args and kwargs. |
||||
| The substitutions are identified by braces ('{' and '}'). |
||||
``` |
||||
|
||||
E uma das coisas legais do Python é que ele é capaz de buscar o tipo através |
||||
de um dado; e o que eu quero dizer com isso é que eu não precisaria saber que |
||||
o tipo de uma string é `str`, eu poderia simplesmente fazer `help('fujam |
||||
para as colinas')` e o interpretador mostraria o mesmo help. |
||||
|
||||
Aqui temos mais uma informação importante: `*args` e `**kwargs`. O que são |
||||
esses dois desgraçados? |
||||
|
||||
### Definindo Funções |
||||
|
||||
Em outras linguagens esses são os chamados "variable arguments" ou "argumentos |
||||
variáveis" ou ainda "varargs". Ao invés de definir uma função que tenha um |
||||
número definido de parâmetros, *varargs* permite que a função tenha um número |
||||
indefinido de parâmetros. E eles funcionam da seguinte forma: |
||||
|
||||
Vamos começar definindo uma função: |
||||
|
||||
```python |
||||
def soma(primeiro, segundo): |
||||
total = primeiro + segundo |
||||
return total |
||||
``` |
||||
|
||||
Uma pequena pausa para falar de uma coisa que acabamos de ver de Python, que |
||||
não tínhamos visto ainda: definição de funções e blocos. |
||||
|
||||
Primeiro, funções são definidas com `def`, seguidas do nome da função, um |
||||
parênteses, a lista de argumentos separados por vírgulas, fecha parênteses e |
||||
dois pontos. Em Python, os dois pontos indicam que haverá um início de bloco. |
||||
|
||||
Segundo, ao contrário de outras linguagens, Python não usa colchetes para |
||||
definir os blocos. Isso é feito através da identação (e, obviamente, os dois |
||||
pontos). |
||||
|
||||
Terceiro, Python é uma linguagem de tipagem dinâmica, o que significa que não |
||||
se define o tipo do parâmetro, simplesmente se usa. |
||||
|
||||
{% note() %} |
||||
Em Python 3, é possível definir um "hint" para o tipo, da |
||||
seguinte forma: |
||||
|
||||
```python |
||||
def soma(primeiro: Int, segundo: Int) -> Int: |
||||
return primeiro + segundo |
||||
``` |
||||
|
||||
A única coisa a se cuidar é que isso é só um hint e que se for passado uma |
||||
string, não irá ocorrer qualquer erro. |
||||
{% end %} |
||||
|
||||
### Chamando Funções |
||||
|
||||
Ainda, existem duas formas de passar valores para uma função: |
||||
|
||||
A primeira é só chamar a função passando os argumentos: |
||||
|
||||
```python |
||||
soma(1, 2) |
||||
``` |
||||
|
||||
A segunda é que o Python aceita que sejam nomeados os argumentos: |
||||
|
||||
```python |
||||
soma(primeiro=1, segundo=2) |
||||
``` |
||||
|
||||
O interessante de se nomear os argumentos é que é possível passar os mesmos |
||||
fora da ordem original da função: |
||||
|
||||
```python |
||||
soma(segundo=2, primeiro=1) |
||||
``` |
||||
|
||||
(Essa parte de nomear os argumentos é importante para entender o `**kwargs`.) |
||||
|
||||
### De volta a Varargs |
||||
|
||||
Mas voltando aos *varargs*, o important é notar que a função acima tem dois |
||||
parâmetros. Se eu tentar chamar a função com um número diferente de |
||||
argumentos, o Python vai reclamar: |
||||
|
||||
```python |
||||
>>> soma(1) |
||||
Traceback (most recent call last): |
||||
File "<stdin>", line 1, in <module> |
||||
TypeError: soma() takes exactly 2 arguments (1 given) |
||||
``` |
||||
|
||||
*varargs* remove essa limitação. Se eu mudar a função para: |
||||
|
||||
```python |
||||
def soma(*args): |
||||
print(args) |
||||
``` |
||||
|
||||
O que `*args` faz é pegar todos os argumentos e transformar numa lista. No |
||||
caso, se eu chamar: |
||||
|
||||
```python |
||||
soma(1, 2, 3) |
||||
``` |
||||
|
||||
O resultado seria: |
||||
|
||||
```python |
||||
[1, 2, 3] |
||||
``` |
||||
|
||||
E se eu chamar da forma original, com `soma(1, 2)`, eu tenho: [#fixo]_ |
||||
|
||||
```python |
||||
[1, 2] |
||||
``` |
||||
|
||||
{% note() %} |
||||
Também é possível criar funções com parâmetros fixos e uma parte |
||||
variável, com algo do tipo `def fun(param1, param2, *outros)`; se a |
||||
função for chamada com `fun(1, 2)`, `outros` ficará como uma lista |
||||
vazia (`[]`); se for passado `fun(1, 2, 3, 4)`, `outros` ficará com 3 |
||||
e 4, já que 1 pertence à `param1` e 2 pertence à `param2`. |
||||
{% end %} |
||||
|
||||
O que nós temos aqui é uma lista de elementos. Para fazer o `soma` funcionar |
||||
com uma lista ao invés de argumentos, teríamos que fazer o seguinte: |
||||
|
||||
```python |
||||
def soma(*argumentos): |
||||
total = 0 |
||||
for valor in argumentos: |
||||
total = total + valor |
||||
return total |
||||
``` |
||||
|
||||
De novo, coisas novas: |
||||
|
||||
De novo, blocos são marcados com dois-pontos e uma identação. Assim, o bloco |
||||
do `for` tem uma linha só, porque o `return` está no mesmo nível do |
||||
`for`, ele só vai ser executado depois que o `for` terminar. |
||||
|
||||
E aqui vemos como percorrer elementos de uma lista: `for/in` faz com que |
||||
seja percorrido cada elemento de `argumentos` e o valor será colocado em |
||||
`valor`. |
||||
|
||||
Agora que vimos *varargs* e listas, existe uma coisa mágica do Python que o |
||||
`*`, além de servir para receber um número variável de argumentos e |
||||
transformar numa lista, também serve para fazer o contrário: converter uma |
||||
lista para uma lista de argumentos. |
||||
|
||||
De novo, com o nosso `soma` original: |
||||
|
||||
```python |
||||
def soma(primeiro, segundo): |
||||
return primeiro + segundo |
||||
``` |
||||
|
||||
Eu posso chamar com: |
||||
|
||||
```python |
||||
soma(1, 2) |
||||
``` |
||||
|
||||
Mas eu também posso chamar com: |
||||
|
||||
```python |
||||
argumentos = [1, 2] |
||||
soma(*argumentos) |
||||
``` |
||||
|
||||
### Varargs de kwargs |
||||
|
||||
|
||||
Nós vimos duas coisas relacionadas a chamadas de função: |
||||
|
||||
1. É possível criar funções com número variável de parâmetros, usando `*`. |
||||
2. É possível chamar funcões passando o nome do parâmetro. |
||||
|
||||
O que acontece quando juntamos os dois? |
||||
|
||||
```python |
||||
>>> def fun(*args): |
||||
... print args |
||||
|
||||
>>> fun(args=1) |
||||
Traceback (most recent call last): |
||||
File "<stdin>", line 1, in <module> |
||||
TypeError: fun() got an unexpected keyword argument 'args' |
||||
``` |
||||
|
||||
O problema aqui é que `*` recolhe todos os argumentos sem nome. Para |
||||
recolher os com nomes, é preciso usar `**`. Ele funciona praticamente da |
||||
mesma forma que `*` mas ao invés de uma lista, ele irá conter um dicionário |
||||
-- também conhecido como "array associativo", "objeto", "mapa" e outros nomes, |
||||
dependendo da linguagem. |
||||
|
||||
Por exemplo: |
||||
|
||||
```python |
||||
def fun(**names): |
||||
print names |
||||
|
||||
fun(1) |
||||
Traceback (most recent call last): |
||||
File "<stdin>", line 1, in <module> |
||||
TypeError: fun() takes exactly 0 arguments (1 given) |
||||
``` |
||||
|
||||
O problema aqui é que não foi passado nenhum argumento nomeado. Obviamente o |
||||
Python não sabe o que fazer com um valor qualquer e deu erro. |
||||
|
||||
Agora, se a função for chamada com: |
||||
|
||||
```python |
||||
fun(name='Julio', age=41) |
||||
{'age': 41, 'name': 'Julio'} |
||||
``` |
||||
|
||||
Ou seja, é possível criar uma função que só aceita parâmetro nomeados, mas é |
||||
preciso que os valores sejam buscados do dicionário ao invés de "aparecerem" |
||||
no bloco pelos parâmetros. |
||||
|
||||
## Colocando tudo junto |
||||
|
||||
Por que tudo isso é importante? |
||||
|
||||
Porque, como foi visto no nosso primeiro código com o `format`, o que a |
||||
gente precisa é passar um número variável de elementos |
||||
|
||||
```python |
||||
print('{}u{}am para as {}o{}i{}as!'.format('f', 'j', 'c', 'l', 'n')) |
||||
``` |
||||
|
||||
E nós precisamos alterar a ordem dos argumentos e a única forma que temos de |
||||
fazer isso é usando o *varargs* reverso: |
||||
|
||||
```python |
||||
consoantes = ['f', 'j', 'c', 'l', 'n'] |
||||
print('{}u{}am para as {}o{}i{}as!'.format(*consoantes) |
||||
``` |
||||
|
||||
Nesse momento, os dois códigos vão fazer a mesma coisa. A questão é que agora |
||||
temos uma lista que podemos mexer no conteúdo. |
||||
|
||||
O que precisamos fazer agora: Embaralhar o conteúdo de `consoantes`. O resto |
||||
do código continua o mesmo, já que ele imprime as consoantes nos lugares |
||||
marcados e nós estamos passando a lista para isso. |
||||
|
||||
Para randomizar o conteúdo, nós vamos utilizar uma das bibliotecas disponíveis |
||||
pelo próprio Python: `random`. |
||||
|
||||
Para usar uma biblioteca -- que no Python são chamadas de "módulos" --, é só |
||||
fazer `import` e o nome da biblioteca. No nosso caso |
||||
|
||||
```python |
||||
import random |
||||
``` |
||||
|
||||
Mas o que diabos tem dentro de `random`? Bom, dá pra ver tudo no site |
||||
oficial do Python, onde tem a documentação, ou nós podemos fazer o mesmo |
||||
`help(random)` para ver o help ou ainda usar `dir(random)` para ver o |
||||
conteúdo do módulo. |
||||
|
||||
``` |
||||
>>> import random |
||||
>>> dir(random) |
||||
['BPF', 'LOG4', 'NV_MAGICCONST', 'RECIP_BPF', 'Random', 'SG_MAGICCONST', |
||||
'SystemRandom', 'TWOPI', 'WichmannHill', '_BuiltinMethodType', |
||||
'_MethodType', '__all__', '__builtins__', '__doc__', '__file__', |
||||
'__name__', '__package__', '_acos', '_ceil', '_cos', '_e', '_exp', |
||||
'_hashlib', '_hexlify', '_inst', '_log', '_pi', '_random', '_sin', '_sqrt', |
||||
'_test', '_test_generator', '_urandom', '_warn', 'betavariate', 'choice', |
||||
'division', 'expovariate', 'gammavariate', 'gauss', 'getrandbits', |
||||
'getstate', 'jumpahead', 'lognormvariate', 'normalvariate', |
||||
'paretovariate', 'randint', 'random', 'randrange', 'sample', 'seed', |
||||
'setstate', 'shuffle', 'triangular', 'uniform', 'vonmisesvariate', |
||||
'weibullvariate'] |
||||
``` |
||||
|
||||
No caso, o que nós queremos é o `shuffle` (como eu sei? Porque eu olhei a |
||||
documentação, oras!) |
||||
|
||||
E assim nós temos o código: |
||||
|
||||
```python |
||||
import random |
||||
consoantes = ['f', 'j', 'c', 'l', 'n'] |
||||
random.shuffle(consoantes) |
||||
print('{}u{}am para as {}o{}i{}as!'.format(*consoantes) |
||||
``` |
||||
|
||||
E está feito nosso randomizador de Fugir para as Colinas. |
||||
|
||||
Embora aqui tenhamos alcançado nosso objetivo, existem algumas outras |
||||
coisinhas que são interessantes de se ver. |
||||
|
||||
## In-place |
||||
|
||||
Uma das coisas que `random.shuffle` faz é alterar a ordem do conteúdo, não |
||||
retornando nada no resultado. Por exemplo |
||||
|
||||
```python |
||||
>>> import random |
||||
>>> lista = [1, 2, 3, 4] |
||||
>>> random.shuffle(lista) |
||||
>>> print(lista) |
||||
[2, 4, 1, 3] |
||||
``` |
||||
|
||||
Isso não é um problema caso a lista não seja mais necessária depois do uso (ou |
||||
a ordem original não seja mais necessária). Se fosse necessária, seria preciso |
||||
fazer uma cópia da lista antes de usar o `shuffle`. Existe um módulo chamado |
||||
`copy` para fazer cópias tanto de listas quanto de dicionários. |
||||
Entretamento, para este caso, existe uma forma mais simples. |
||||
|
||||
## Slices |
||||
|
||||
Para acessar um elemento de uma lista, basta usar a posição do element |
||||
(começando em zero, obviamente). |
||||
|
||||
```python |
||||
>>> lista = ['a', 'b', 'c', 'd'] |
||||
>>> print(lista[1]) |
||||
'b' |
||||
``` |
||||
|
||||
Também é possível acessar um grupo de elementos usando `:`, com a posição |
||||
inicial e a posição final (que é exclusiva, ou seja, antes de chegar no |
||||
elemento indicado). |
||||
|
||||
```python |
||||
>>> lista = ['a', 'b', 'c', 'd'] |
||||
>>> print(lista[1:3]) |
||||
['b', 'c'] |
||||
``` |
||||
|
||||
{% note() %} |
||||
Existe ainda um terceiro parâmetro para slides, que é o "step". |
||||
Por exemplo, |
||||
|
||||
```python |
||||
>>> lista = [1, 2, 3, 4] |
||||
>>> print(lista[::2]) |
||||
[1, 3] |
||||
``` |
||||
|
||||
Aqui foi indicado que é pra ir do começo da lista até o final, mas pulando |
||||
de dois em dois. |
||||
|
||||
Embora não muito usado, a parte que realmente importa é que step também |
||||
aceita valores negativos, indicando que é pra seguir na ordem inversa. E o |
||||
uso mais comum é criar uma cópia da lista, mas com os valores invertidos. |
||||
|
||||
```python |
||||
>>> lista = [1, 2, 3, 4] |
||||
>>> print(lista[::-1]) |
||||
[4, 3, 2, 1] |
||||
``` |
||||
{% end %} |
||||
|
||||
Também é possível omitir as posições: Se for omitida a primeira posição, |
||||
significa "desde o começo"; se for omitida a posição final, significa "até o |
||||
fim". |
||||
|
||||
```python |
||||
>>> lista = ['a', 'b', 'c', 'd'] |
||||
>>> print(lista[:3]) |
||||
['a', 'b', 'c'] |
||||
``` |
||||
|
||||
```python |
||||
>>> lista = ['a', 'b', 'c', 'd'] |
||||
>>> print(lista[1:]) |
||||
['b', 'c', 'd'] |
||||
``` |
||||
|
||||
Também é possível usar índices negativos, tanto na posição inicial quanto |
||||
final, indica que é "a partir do fim da lista". |
||||
|
||||
```python |
||||
>>> lista = ['a', 'b', 'c', 'd'] |
||||
>>> print(lista[-2:]) |
||||
['c', 'd'] |
||||
``` |
||||
|
||||
Essas operações de "pegar pedaços de uma lista a partir de uma posição inicial |
||||
e final" são chamados de *slides*. |
||||
|
||||
## Copiando listas por Slices |
||||
|
||||
Mas porque eu comentei de slices? Porque, se você reparar, quando é utilizada |
||||
uma faixa, o Python retorna o resultado como uma lista. Na verdade, não é um |
||||
pedaço da lista original, é uma nova lista. |
||||
|
||||
Considerando que: |
||||
|
||||
1. Sem uma posição inicial, significa que é pra começar do começo da lista. |
||||
2. Sem uma posição final, significa que é ir até o final da lista. |
||||
3. Slices são cópias de uma lista. |
||||
|
||||
O que você acha que acontece se não forem passadas as *duas* posições ao mesmo |
||||
tempo? |
||||
|
||||
Sim, você cria uma cópia da lista. |
||||
|
||||
```python |
||||
>>> import random |
||||
>>> lista = [1, 2, 3, 4] |
||||
>>> copia = lista[:] |
||||
>>> random.shuffle(copia) |
||||
>>> print(copia) |
||||
[2, 4, 1, 3] |
||||
>>> print(lista) |
||||
[1, 2, 3, 4] |
||||
``` |
||||
|
||||
E, com essa cópia, evitamos de termos problemas com a lista passado pelo |
||||
`shuffle`, porque a lista original vai ter sempre os dados na mesma ordem, sem |
||||
nunca ser alterada -- desde que o `shuffle` seja feita na cópia. |
Loading…
Reference in new issue