Skip to content

Commit

Permalink
closes #21, correções de @magnvmopvs
Browse files Browse the repository at this point in the history
  • Loading branch information
dunossauro committed Apr 4, 2017
1 parent eb7bcdb commit b22919b
Showing 1 changed file with 43 additions and 44 deletions.
87 changes: 43 additions & 44 deletions roteiros/8_closures_1_escopo.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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:

Expand Down Expand Up @@ -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)
Expand All @@ -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):
"""
Expand All @@ -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
Expand All @@ -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():
Expand Down Expand Up @@ -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__()`
Expand All @@ -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)
Expand All @@ -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)

Expand All @@ -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)
Expand All @@ -195,21 +194,21 @@ 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
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
"""
Expand All @@ -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
"""
Expand All @@ -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
Expand All @@ -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()`:

Expand All @@ -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`:

Expand All @@ -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')
Expand All @@ -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

Expand All @@ -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
"""
Expand All @@ -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
"""
Expand All @@ -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.

0 comments on commit b22919b

Please sign in to comment.