From b22919bfbed061834796c07d2728815e5a3e2358 Mon Sep 17 00:00:00 2001 From: z4r4tu5tr4 Date: Mon, 3 Apr 2017 23:29:55 -0300 Subject: [PATCH] =?UTF-8?q?closes=20#21,=20corre=C3=A7=C3=B5es=20de=20@mag?= =?UTF-8?q?nvmopvs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- roteiros/8_closures_1_escopo.md | 87 ++++++++++++++++----------------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/roteiros/8_closures_1_escopo.md b/roteiros/8_closures_1_escopo.md index 817d594..e02d637 100644 --- a/roteiros/8_closures_1_escopo.md +++ b/roteiros/8_closures_1_escopo.md @@ -1,7 +1,6 @@ - # 8. Closures e contexto de variáveis -Já passamos funções como argumento, já retornamos funções e até já mudamos o comportamento das mesmas. Mas tem uma coisa que ainda não fizemos: Definir uma função no corpo de outra função: +Já passamos funções como argumento, já retornamos funções e até já mudamos o comportamento das mesmas. Mas tem uma coisa que ainda não fizemos: definir uma função no corpo de outra função: ```Python @@ -11,13 +10,13 @@ def func_0(): pass ``` -Jaber diz: `Mas isso é uma classe! eu sabia que programação funcional era legal, mas realmente tudo é orientação a objetos.` +Jaber diz: `Mas isso é uma classe! Eu sabia que programação funcional era legal, mas realmente tudo é orientação a objetos.` Tá Jaber, eu entendo seu ponto de vista, mas vou usar uma definição muito boa do livro do Mertz: -"Uma classe são dados com operações anexadas (...) Uma Clojure são operações com dados anexados" +"Uma classe são dados com operações anexadas (...) Uma Closure são operações com dados anexados" -Viu? muda totalmente o modo de ver... Vamos nos explicar de maneira simples em breve, mas vamos entender as closures e fingir que classes não existem, só por alguns minutos. +Viu? Muda totalmente o modo de ver... Vamos nos explicar de maneira simples em breve, mas vamos entender as closures e fingir que classes não existem, só por alguns minutos. Vamos imaginar que temos que guardar um valor dentro de um função. Essa função vai ser uma função que exclusivamente armazena um valor e uma função dentro do seu escopo: @@ -47,9 +46,9 @@ var_func = func_externa(5) var_func(5) # 10 ``` -Como dá pra notar a função externa é atribuída a uma variável e essa variável executa a função interna. Parece complicado, mas na verdade é bem simples. Vamos recapitular algumas coisas. +Como dá pra notar, a função externa é atribuída a uma variável e essa variável executa a função interna. Parece complicado, mas na verdade é bem simples. Vamos recapitular algumas coisas. -Como em python as funções podem ser definidas em qualquer contexto e armazenada em qualquer lugar imagine que a `func_externa()` está sendo atribuída a uma variável. Em um contexto totalmente normal, como fizemos com as funções anônimas até agora. A diferença é um valor, ou uma quantidade `n` de valores, vão ser passadas no momento da atribuição. Esses valores vão ficar armazenados na função de maneira imutável, vamos reaproveitar o código com alguns exemplos: +Como em python as funções podem ser definidas em qualquer contexto e armazenada em qualquer lugar imagine que a `func_externa()` está sendo atribuída a uma variável. Em um contexto totalmente normal, como fizemos com as funções anônimas até agora. A diferença é um valor, ou uma quantidade `n` de valores, vão ser passadas no momento da atribuição. Esses valores vão ficar armazenados na função de maneira imutável. Vamos reaproveitar o código com alguns exemplos: ```Python soma_um = func_externa(1) @@ -66,14 +65,14 @@ soma_quarto(0) # 4 soma_cinco(0) # 5 ``` -Bom, agora imagino que tenha ficado um pouco mais claro. Fixamos valores na `func_externa()` e armazenamos em variáveis (`soma_um()`, `soma_dois()`, `soma_tres()` ...) cada respectiva função mostra valor inicial da função. +Bom, agora imagino que tenha ficado um pouco mais claro. Fixamos valores na `func_externa()` e armazenamos em variáveis (`soma_um()`, `soma_dois()`, `soma_tres()`, ...) cada respectiva função mostra o valor inicial da função. -Quando executamos a função `soma_um(n)` qualquer valor que for usado em `n` vai ser somado a ao valor fixo na função externa `func_externa(1)`, ou seja, `1`. Vale lembrar que a soma é executada porque esse é o comportamento da função interna `func_interna()`. Vamos tentar outra vez e de uma maneira mais simples: +Quando executamos a função `soma_um(n)` qualquer valor que for usado em `n` vai ser somado ao valor fixo na função externa `func_externa(1)`, ou seja, `1`. Vale lembrar que a soma é executada porque esse é o comportamento da função interna `func_interna()`. Vamos tentar outra vez e de uma maneira mais simples: ```Python def diga_oi(saudacao): """ - A funão diga_oi armazenada a sua saudação + A função diga_oi armazenada a sua saudação """ def nome_pessoa(nome): """ @@ -82,7 +81,7 @@ def diga_oi(saudacao): return '{} {}'.format(saudacao, nome) return nome_pessoa -oi_pirata = diga_oi('Ahoy!') # Definição de diga_oi (função externa) fixando Arroy +oi_pirata = diga_oi('Ahoy!') # Definição de diga_oi (função externa) fixando Ahoy oi_pirata('Eduardo') # Ahoy! Eduardo oi_pirata('Jaber') # Ahoy! Jaber @@ -97,7 +96,7 @@ Mas vamos tentar fixar um dicionário de idiomas pra `diga_oi()`? Por exemplo, a dic = {'pirata': 'Ahoy', 'ingles':'Hello', 'portugues': 'Olá'} ``` -Uma vantagem de definir esse dicionário dentro da função é que ele vai ficar isolado das variáveis globais, o que faz ele não gerar efeito colateral e isso é muito positivo: +Uma vantagem de definir esse dicionário dentro da função é que ele vai ficar isolado das variáveis globais, o que faz ele não gerar efeito colateral, e isso é muito positivo: ```Python def diga_oi(): @@ -126,12 +125,12 @@ saudacoes('ingles', 'Python') # 'Hello Python' ## 8.1 Classes vs closures -Você deve ter percebido que até agora as closures tem dois tipos de comportamento diferentes, porém a imutabilidade permanece: +Você deve ter percebido que até agora as closures tem dois tipos de comportamentos diferentes, porém a imutabilidade permanece: 1. Fixando parâmetros para padronização de chamadas 2. O escopo da função externa é acessível para a função interna -Aqui você deve ter sacado o esquema de operações com dados. O dado passado a função externa permanece imutável sempre e inacessível a qualquer contexto externo ao da função, ou seja, o dado foi fixado e não pode ser transformado em nenhum outro valor, a não ser que seja feita outra chamada com outro valor. O que deveria ser dito sobre as classes, e que preferó postergar, é o que toca exatamente nesse ponto. Vamos fazer uma comparação: +Aqui você deve ter sacado o esquema de operações com dados. O dado passado à função externa permanece imutável sempre e inacessível a qualquer contexto externo ao da função, ou seja, o dado foi fixado e não pode ser transformado em nenhum outro valor, a não ser que seja feita outra chamada com outro valor. O que deveria ser dito sobre as classes, e que preferi postergar, é o que toca exatamente nesse ponto. Vamos fazer uma comparação: ### classe `__call__()` @@ -149,7 +148,7 @@ class diga_oi: def __call__(self, nome): """ - __call__ permite que o objeto aplique o operado (), + __call__ permite que o objeto aplique o operador (), seja chamado como função """ return '{} {}'.format(self.saudacao, nome) @@ -175,7 +174,7 @@ class diga_oi: """ def __init__(self, saudacao): """ - Inicializa a instancia do objeto com saudacao sendo imutavel + Inicializa a instância do objeto com saudacao sendo imutável """ object.__setattr__(self, 'saudacao', saudacao) @@ -184,7 +183,7 @@ class diga_oi: def __call__(self, nome): """ - __call__ permite que o objeto aplique o operado (), + __call__ permite que o objeto aplique o operador (), seja chamado como função """ return '{} {}'.format(self.saudacao, nome) @@ -195,7 +194,7 @@ class diga_oi: ## 8.2 Mutação das variáveis de uma closure -Diferente do que eu disse até agora, os valores podem ser alterados no escopo da função externa mas temos uma série de limitações. Caso o objeto passado como parâmetro, ou alocado na função externa, seja mutável (listas, dicionários, conjuntos, ...), o objeto pode receber normalmente as modificações, vamos fazer um teste: +Diferente do que eu disse até agora, os valores podem ser alterados no escopo da função externa, mas temos uma série de limitações. Caso o objeto passado como parâmetro, ou alocado na função externa, seja mutável (listas, dicionários, conjuntos, ...), o objeto pode receber normalmente as modificações, vamos fazer um teste: ```Python @@ -203,13 +202,13 @@ def contador(): """ Função contadora de acessos. - Internamente mantem uma lista que é definida + Internamente mantém uma lista que é definida vazia no momento em que é declarada """ lista = [] def soma(): """ - Adiciona 1 a lista toda vez que a função é chamada. + Adiciona 1 à lista toda vez que a função é chamada. Retorna a somatória dos valores contidos na lista """ @@ -225,20 +224,20 @@ count() # 4 count() # 5 ``` -Isso é uma forma porca de fazer um contador, mas ele funciona. O mais importante disso é que a variável `lista` não está sendo modificada. Os valores estão sendo atribuídos a lista poque ela é um objeto mutável. Mas não seria possível, e vamos tentar isso agora, mudar o conteúdo da variável lista: +Isso é uma forma porca de fazer um contador, mas ele funciona. O mais importante disso é que a variável `lista` não está sendo modificada. Os valores estão sendo atribuídos à lista porque ela é um objeto mutável. Mas não seria possível, e vamos tentar isso agora, mudar o conteúdo da variável lista: ```Python def contador(): """ Função contadora de acessos. - Internamente mantem uma lista que é definida + Internamente mantém uma lista que é definida vazia no momento em que é declarada """ lista = 0 def soma(): """ - Adiciona 1 a lista toda vez que a função é chamada. + Adiciona 1 à lista toda vez que a função é chamada. Retorna a somatória dos valores contidos na lista """ @@ -250,11 +249,11 @@ count = contador() count() # UnboundLocalError: local variable 'lista' referenced before assignment ``` -Vamos tentar mudar o foco um pouco e dar mais importancia a esse erro, ele pode nos mostrar coisas lindas sobre Python e que podemos usar com muito empenho para fazer funcional com mais espenho e graciosidade. +Vamos tentar mudar o foco um pouco e dar mais importância a esse erro, ele pode nos mostrar coisas lindas sobre Python e que podemos usar para fazer funcional com mais empenho e graciosidade. ### UnboundLocalError e escopo de variáveis -`UnboundLocalError` é um erro muito comum em Python, e o que me deixa muito surpreso foi issso te lavado tanto tempo pra acontecer nesses nossos contextos de programação funcional. Vamos falar agora sobre escopo de variáveis, porém vale lembrar que esse não é um tópico de programação funcional e sim de comportamento específico do Python. Se você já sabe sobre tudo isso, você pode pular todos esse tópicos, porém vale a pena, para o melhor entendimento das closures ficar atento a variáveis livres. +`UnboundLocalError` é um erro muito comum em Python, e o que me deixa muito surpreso foi isso ter levado tanto tempo pra acontecer nesses nossos contextos de programação funcional. Vamos falar agora sobre escopo de variáveis, porém vale lembrar que esse não é um tópico de programação funcional, e sim de comportamento específico do Python. Se você já sabe sobre tudo isso, você pode pular todos esse tópicos, porém vale a pena, para o melhor entendimento das closures, ficar atento a variáveis livres. #### Variáveis globais e locais @@ -276,7 +275,7 @@ print(var_0) # 5 print(var_1) # NameError: name 'var_1' is not defined ``` -var_0 está presente em toda a execução, porém var_1 é uma variável do escopo local da função `func()` e fora desse contexto ela simplismente não existe. +var_0 está presente em toda a execução, porém var_1 é uma variável do escopo local da função `func()` e fora desse contexto ela simplesmente não existe. Outro ponto legal disso é que a variável global não pode ser modificada pela função `func()`: @@ -290,7 +289,7 @@ func() print(var_0) # 5 ``` -No momento em que tentamos atribuir a no escopo de `func()` criamos uma nova `var_0` dentro desse contexto, (vamos falar mais sobre isso em um tópico futuro chamado introspecção de funções) o que faz que a variável global permaneça a mesma. +No momento em que tentamos atribuir no escopo de `func()`, criamos uma nova `var_0` dentro desse contexto, (vamos falar mais sobre isso em um tópico futuro chamado introspecção de funções) o que faz que a variável global permaneça a mesma. Se nós quisermos transformar o valor da variável global dentro do escopo da função, podemos usar a palavra reservada `global`: @@ -305,27 +304,27 @@ func() print(var_0) # 7 ``` -Agora, como você pode notar o comportamento é diferente, a palavra `global` disse ao Python que variável usada aqui é exatamente o do escopo global. Quando o Python procura uma variável (vamos falar mais sobre isso em um tópico futuro chamado introspecção de funções [2]) ele segue uma hierarquia: +Agora, como você pode notar, o comportamento é diferente. A palavra `global` disse ao Python que a variável usada aqui é exatamente a do escopo global. Quando o Python procura uma variável (vamos falar mais sobre isso em um tópico futuro chamado introspecção de funções [2]) ele segue uma hierarquia: ``` Existe no meu escopo local? Se sim, use. Caso não -Procure em um escopo mais amplo, se existe, use, Caso não +Procure em um escopo mais amplo, se existe, use. Caso não Procure em um escopo mais amplo .... .... ``` -Ele busca até que o contexto seja o global, a variável não existir ele vai nos retornar `NameError`. Mas isso só vale para leitura. E agora para escrerver? +Ele busca até que o contexto seja o global, se a variável não existir ele vai nos retornar `NameError`. Mas isso só vale para leitura. E agora para escrever? ``` Variável criada no escopo, fica no escopo. ``` -É simples não? isso nos ajuda a previnir muitos erros e não 'assinar' variáveis fora do nosso escopo local e gerar efeitos colaterais bizarros. Exemplo? +É simples não? Isso nos ajuda a prevenir muitos erros e não 'assinar' variáveis fora do nosso escopo local e gerar efeitos colaterais bizarros. Exemplo: ```Python def dir_concat(dir:str) -> str: """ Função que recebe uma string com um diretório - e contatena ele com nosso path atual: + e concatena ele com nosso path atual: Exemplo: >>> dir_concat('documentos') @@ -335,7 +334,7 @@ def dir_concat(dir:str) -> str: return '{}/{}'.format(getcwd(), dir) ``` -Você notou o que existe de errado nessa função? Ela usar a palavra reservada `dir` que é uma função do contexto global do python `dir()`, ou seja, se os contextos locais não fosse criados durante todo o resto da execução desse programa não poderiamos fazer uso da função `dir()` pois ela foi sobreescrita. +Você notou o que existe de errado nessa função? Ela usa a palavra reservada `dir`, que é uma função do contexto global do python `dir()`, ou seja, se os contextos locais não fossem criados durante todo o resto da execução desse programa não poderíamos fazer uso da função `dir()` pois ela foi sobrescrita. #### Variáveis livres @@ -347,13 +346,13 @@ def contador(): """ Função contadora de acessos. - Internamente mantem uma lista que é definida + Internamente mantém uma lista que é definida vazia no momento em que é declarada """ lista = 0 # Variável local de contador def soma(): """ - Adiciona 1 a lista toda vez que a função é chamada. + Adiciona 1 à lista toda vez que a função é chamada. Retorna a somatória dos valores contidos na lista """ @@ -362,33 +361,33 @@ def contador(): return soma ``` -Vamos pensar um pouco, não é possível modificar `lista` pois ela está em um escopo externo ao de `soma()` só que não podemos usar `global` pois lista não é global, lembra da ordem da busca pos nomes? +Vamos pensar um pouco, não é possível modificar `lista` pois ela está em um escopo externo ao de `soma()`, só que não podemos usar `global` pois `lista` não é global. Lembra da ordem da busca por nomes? ``` -faz parte de soma? Não -faz parte de contador, Sim +Faz parte de soma? Não +Faz parte de contador? Sim ``` -Então ele usa, só que responde `UnboundLocalError` pois não podemos mudar seu valor. Em Python 3, e só em python 3, existe a palavra reservada `nonlocal` que dá o poder de outro escopo mutar o valor fora de seu escopo e que não está no escopo global. Ou seja, resolve o nosso problema e isso é lindo. Existe uma PEP inteira detalhando esse aspecto [PEP 3104](https://www.python.org/dev/peps/pep-3104/) pois não quero me alongar nessa explicação, pra simplificar, vou usar o contexto do [wikipedia](https://pt.wikipedia.org/wiki/Vari%C3%A1veis_livres_e_ligadas): +Então ele usa, só que responde `UnboundLocalError` pois não podemos mudar seu valor. Em Python 3, e só em Python 3, existe a palavra reservada `nonlocal`, que dá o poder de outro escopo mutar o valor fora de seu escopo e que não está no escopo global. Ou seja, resolve o nosso problema e isso é lindo. Existe uma PEP inteira detalhando esse aspecto [PEP 3104](https://www.python.org/dev/peps/pep-3104/) pois não quero me alongar nessa explicação, pra simplificar, vou usar o contexto do [wikipedia](https://pt.wikipedia.org/wiki/Vari%C3%A1veis_livres_e_ligadas): ``` -uma variável livre é uma variável referenciada em uma função, que não é nem uma variável local nem um argumento daquela função. +Uma variável livre é uma variável referenciada em uma função, que não é nem uma variável local nem um argumento daquela função. ``` -No caso de nossa closures, é uma variável declara na função externa, que pode ser acessada pela função interna e não pode ser modificada pela mesma. Porém podemos usar `nonlocal`. Vamos tentar? +No caso de nossas closures, é uma variável declarada na função externa, que pode ser acessada pela função interna e não pode ser modificada pela mesma. Porém podemos usar `nonlocal`. Vamos tentar? ```Python def contador(): """ Função contadora de acessos. - Internamente mantem uma lista que é definida + Internamente mantém uma lista que é definida vazia no momento em que é declarada """ var = 0 def soma(): """ - Adiciona 1 a lista toda vez que a função é chamada. + Adiciona 1 à lista toda vez que a função é chamada. Retorna a somatória dos valores contidos na lista """ @@ -398,6 +397,6 @@ def contador(): return soma ``` -Agora é possível gerar esse contador sem o uso da lista, então ele não 'enche nossa memória' com uma lista que pode ter um tamanho nada convencional. Porém isso fere o conceito de imutabilidade, mais ainda é melhor que uma classe por que faz acesso ao recurso `var` é somente a função interna, sem que isso possa ser transformado pelo escopo exterior, como é feito no caso das classes. +Agora é possível gerar esse contador sem o uso da lista, então ele não 'enche nossa memória' com uma lista que pode ter um tamanho nada convencional. Porém isso fere o conceito de imutabilidade, mas ainda é melhor que uma classe porque faz acesso ao recurso `var` é somente a função interna, sem que isso possa ser transformado pelo escopo externo, como é feito no caso das classes. Agora vamos olhar para um lado mais avançado das closures, mas você vai conseguir dar mais vazão e usos derivados das mesmas.