() translation by (you can also view the original English article)
Conflitos de nome acontecem o tempo todo na vida real. Por exemplo, todas as escolas que frequentei tinham pelo menos dois alunos da minha turma que compartilhavam o mesmo primeiro nome. Se alguém entrasse na sala e perguntasse a respeito do aluno X, teríamos que entusiasticamente perguntar: "com qual você está falando? Há dois alunos com o nome X". Depois disso, a pessoa nos daria o sobrenome, e nós então apontaríamos a pessoa certa.
Toda essa confusão e o processo de determinar a exata pessoa que estamos falando, necessitando de outras informações além do nome, poderiam ser evitadas se todos tivessem um nome exclusivo. Este não é um problema em uma sala com 30 alunos. No entanto, torna-se cada vez mais difícil arranjar um nome único, significativo e fácil de lembrar para todas as crianças em uma escola, cidade, estado, país ou no mundo inteiro. Outra questão em proporcionar a cada criança um nome exclusivo é que o processo de determinar se a outra pessoa também nomeou seu filho Macey, Maci ou Macie pode ser muito cansativo.
Um conflito muito semelhante também pode ocorrer em programação. Quando você estiver escrevendo um programa de apenas 30 linhas com nenhuma dependência externa, é muito fácil dar nomes originais e significativos para todas as suas variáveis. O problema surge quando há milhares de linhas em um programa e alguns módulos externos também. Neste tutorial, você aprenderá sobre namespaces, sua importância e a definição de escopo em Python.
O que são os Namespaces?
Um namespace é basicamente um sistema para certificar-se que todos os nomes em um programa são únicos e podem ser usados sem qualquer conflito. Você já deve saber que tudo em Python — como sequências de caracteres, listas, funções, etc. — é um objeto. Outro fato interessante é que o Python implementa namespaces como dicionários. Há um mapeamento de nome-para-objeto, com os nomes como chaves e os objetos como valores. Vários namespaces podem usar o mesmo nome e mapeá-los para um objeto diferente. Aqui estão alguns exemplos de namespaces:
- Namespace local: Este namespace inclui nomes locais dentro de uma função. Este namespace é criado quando uma função é chamada, e só dura até que a função retorne.
- Namespace global: Este namespace inclui nomes de vários módulos importados que você está usando em um projeto. Ele é criado quando o módulo está incluído no projeto, e dura até o script terminar.
- Namespace interno: Este namespace inclui funções internas e nomes de exceções internas.
Na série Módulos matemáticos com Python que escrevi no Envato Tuts+, falei sobre funções matemáticas úteis disponíveis em módulos diferentes. Por exemplo, os módulos "math" e "cmath" têm muitas funções que são comuns a ambos, como log10()
, acos()
, cos()
, exp()
, etc. Se você estiver usando dois desses módulos no mesmo programa, a única maneira de utilizar estas funções de forma inequívoca é prefixá-las com o nome do módulo, como math.log10()
e cmath.log10()
.
O que é o escopo?
Os namespaces ajudam-nos a identificar todos os nomes dentro de um programa. No entanto, isto não significa que podemos usar um nome de variável em qualquer lugar que quisermos. Um nome também tem um escopo que define as partes do programa onde você pode usar esse nome sem usar qualquer prefixo. Assim como namespaces, também há vários escopos em um programa. Aqui está uma lista de alguns escopos que podem existir durante a execução de um programa.
- Um escopo local, que é o escopo interno que contém uma lista de nomes locais disponíveis na função atual.
- Um escopo delimitado a todas as funções. A busca de um nome começa com o escopo mais perto e fechado e continua até o mais aberto.
- Um escopo de nível modular que contém todos os nomes globais do módulo atual.
- O escopo mais externo que contém uma lista de todos os nomes internos.
Nas próximas seções deste tutorial, usaremos extensivamente a função interna dir() do Python para retornar uma lista de nomes no escopo local. Isto irá ajudá-lo a entender o conceito de namespaces e escopo mais claramente.
Resolução de escopo
Como eu mencionei na seção anterior, a busca de um determinado nome começa a partir da função mais fechada e então move-se para a mais aberta até que o programa possa mapear esse nome para um objeto. Quando o nome não é encontrado em qualquer um dos namespaces, o programa gera uma exceção NameError.
Antes de começar, tente digitar dir()
na IDLE ou qualquer outra IDE Python.
1 |
dir() |
2 |
# ['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']
|
Todos esses nomes listados por dir()
estão disponíveis em cada programa Python. Para abreviar, vou começar a referir-me a eles como '__builtins__'...'__spec__'
no resto dos exemplos.
Vamos ver a saída da função dir()
depois de definir uma variável e uma função.
1 |
a_num = 10 |
2 |
dir() |
3 |
# ['__builtins__' .... '__spec__', 'a_num']
|
4 |
|
5 |
def some_func(): |
6 |
b_num = 11 |
7 |
print(dir()) |
8 |
|
9 |
some_func() |
10 |
# ['b_num']
|
11 |
|
12 |
dir() |
13 |
# ['__builtins__' ... '__spec__', 'a_num', 'some_func']
|
14 |
A função dir()
apenas gera uma lista de nomes dentro do escopo atual. É por isso que, dentro do escopo de some_func()
, há apenas um nome chamado b_num
. Chamar dir()
depois de definir some_func()
adiciona-o à lista de nomes disponíveis no namespace global.
Agora, vejamos a lista de nomes dentro de algumas funções aninhadas. O código neste bloco é continuação do bloco anterior.
1 |
def outer_func(): |
2 |
c_num = 12 |
3 |
def inner_func(): |
4 |
d_num = 13 |
5 |
print(dir(), ' - names in inner_func') |
6 |
e_num = 14 |
7 |
inner_func() |
8 |
print(dir(), ' - names in outer_func') |
9 |
|
10 |
outer_func() |
11 |
# ['d_num'] - names in inner_func
|
12 |
# ['c_num', 'e_num', 'inner_func'] - names in outer_func
|
O código acima define duas variáveis e uma função dentro do escopo de outer_func()
. Dentro inner_func()
, a função dir()
apenas imprime o nome d_num
. Isso parece justo já que d_num
e a única variável que foi definida.
A menos que explicitamente especificado usando global
, reatribuir um nome global dentro de um namespace local cria uma nova variável local com o mesmo nome. Isto fica evidente no código a seguir.
1 |
a_num = 10 |
2 |
b_num = 11 |
3 |
|
4 |
def outer_func(): |
5 |
global a_num |
6 |
a_num = 15 |
7 |
b_num = 16 |
8 |
def inner_func(): |
9 |
global a_num |
10 |
a_num = 20 |
11 |
b_num = 21 |
12 |
print('a_num inside inner_func :', a_num) |
13 |
print('b_num inside inner_func :', b_num) |
14 |
inner_func() |
15 |
print('a_num inside outer_func :', a_num) |
16 |
print('b_num inside outer_func :', b_num) |
17 |
|
18 |
outer_func() |
19 |
print('a_num outside all functions :', a_num) |
20 |
print('b_num outside all functions :', b_num) |
21 |
|
22 |
# a_num inside inner_func : 20
|
23 |
# b_num inside inner_func : 21
|
24 |
|
25 |
# a_num inside outer_func : 20
|
26 |
# b_num inside outer_func : 16
|
27 |
|
28 |
# a_num outside all functions : 20
|
29 |
# b_num outside all functions : 11
|
Dentro de outer_func()
e inner_func()
, a_num
foi declarado para ser uma variável global. Estamos apenas a criar um valor diferente para a mesma variável global. Esta é a razão que o valor de a_num
em todos os locais é 20. Por outro lado, cada função cria sua própria variável b_num
com um escopo local, e a função print()
imprime o valor dessa variável no escopo local.
Importando módulos corretamente
É muito comum importar módulos externos em seus projetos para acelerar o desenvolvimento. Há três maneiras de importar módulos. Nesta seção, você aprenderá sobre todos esses métodos, discutindo seus prós e contras em detalhe.
-
from module import *
: Este método de importar um módulo importa todos os nomes do módulo determinado diretamente em seu namespace atual. Você pode ser tentado a usar este método porque ele permite que você use uma função diretamente sem adicionar o nome do módulo como um prefixo. No entanto, é muito sujeito a erros, e você também perde a capacidade de dizer qual módulo foi realmente importado na função. Aqui está um exemplo do uso deste método:
1 |
dir() |
2 |
# ['__builtins__' ... '__spec__']
|
3 |
|
4 |
from math import * |
5 |
dir() |
6 |
# ['__builtins__' ... '__spec__', 'acos', 'acosh', 'asin', 'asinh',
|
7 |
# 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees',
|
8 |
# 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod',
|
9 |
# 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite',
|
10 |
# 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2',
|
11 |
# 'modf', 'nan', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan',
|
12 |
# 'tanh', 'trunc']
|
13 |
|
14 |
log10(125) |
15 |
# 2.0969100130080562
|
16 |
|
17 |
from cmath import * |
18 |
dir() |
19 |
# ['__builtins__' ... '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan',
|
20 |
# 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf',
|
21 |
# 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum',
|
22 |
# 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan',
|
23 |
# 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'phase',
|
24 |
# 'pi', 'polar', 'pow', 'radians', 'rect', 'sin', 'sinh', 'sqrt', 'tan', 'tanh',
|
25 |
# 'trunc']
|
26 |
|
27 |
log10(125) |
28 |
# (2.0969100130080562+0j)
|
Se você estiver familiarizado com os módulos math e cmath, você já sabe que existem alguns nomes comuns que são definidos em ambos os módulos, mas eles aplicam-se a números reais e complexos, respectivamente.
Como importamos o módulo cmath após o módulo math, ele sobreescreveu as definições da funções comuns do módulo math. É por isso que o primeiro log10(125)
retorna um número real e o segundo log10(125)
retorna um número complexo. É impossível usar a função log10()
do módulo math agora. Mesmo que você tente digitar math.log10(125)
, você receberá uma exceçãoNameError porque math
realmente não existe no namespace.
A linha abaixo é a forma que você não deve usar para importar funções de diferentes módulos, mesmo para poupar um pouco de digitação.
-
from module import nameA, nameB
: se você sabe que você só vai usar um ou dois nomes de um módulo, você pode importá-los diretamente usando esse método. Desta forma, você pode escrever o código de maneira mais concisa, mantendo o mínimo de poluição do namespace. No entanto, tenha em mente que você ainda não pode usar qualquer outro nome do módulo escrevendomodule.nameZ
. Qualquer função que tem o mesmo nome em seu programa também irá substituir a definição desta função importada do módulo. Isto fará com que a função importada fique inutilizável. Aqui está um exemplo do uso deste método:
1 |
dir() |
2 |
# ['__builtins__' ... '__spec__']
|
3 |
|
4 |
from math import log2, log10 |
5 |
dir() |
6 |
# ['__builtins__' ... '__spec__', 'log10', 'log2']
|
7 |
|
8 |
log10(125) |
9 |
# 2.0969100130080562
|
-
import module
: esta é a maneira mais segura e recomendada de importar um módulo. A única desvantagem é que você terá que prefixar o nome do módulo para todos os nomes que você vai usar no programa. No entanto, você será capaz de evitar a poluição do namespace e também definir funções cujos nomes coincidem com o nome das funções do módulo.
1 |
dir() |
2 |
# ['__builtins__' ... '__spec__']
|
3 |
|
4 |
import math |
5 |
dir() |
6 |
# ['__builtins__' ... '__spec__', 'math']
|
7 |
|
8 |
math.log10(125) |
9 |
# 2.0969100130080562
|
Considerações finais
Espero que este tutorial tenha te ajudado a entender os namespaces e sua importância. Você agora deve ser capaz de determinar o escopo de nomes diferentes em um programa e evitar possíveis armadilhas.
Além disso, não hesite em ver o que temos disponível para venda e para estudo na loja e não hesite em fazer perguntas e fornecer seu feedback valioso usando a seção abaixo.
A seção final do artigo discutiu diferentes maneiras de importar módulos em Python e os prós e contras de cada uma delas. Se você tiver quaisquer perguntas relacionadas a este tópico, por favor escreva nos comentários.