From 43ad17e66197c32fbf78003a73cb4339a150738e Mon Sep 17 00:00:00 2001 From: Julio Biason Date: Mon, 19 Nov 2018 15:54:36 -0200 Subject: [PATCH] Talking a bit about python and shuffle --- content/fugindo-para-as-colinas-com-python.md | 501 ++++++++++++++++++ 1 file changed, 501 insertions(+) create mode 100644 content/fugindo-para-as-colinas-com-python.md diff --git a/content/fugindo-para-as-colinas-com-python.md b/content/fugindo-para-as-colinas-com-python.md new file mode 100644 index 0000000..f018fcd --- /dev/null +++ b/content/fugindo-para-as-colinas-com-python.md @@ -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. + + + +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 "", line 1, in +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 "", line 1, in +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 "", line 1, in +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.