Conflitos de nomes acontecem o tempo todo na vida real. Por exemplo, todas as escolas que frequentei tinham pelo menos dois alunos na minha turma que compartilhavam o mesmo primeiro nome. Se alguém entrasse na classe e perguntasse pelo aluno X, perguntaríamos com entusiasmo: “De qual você está falando? Há dois alunos chamados X”. Depois disso, o inquiridor nos dava um sobrenome e nós o apresentávamos ao X certo.
Toda essa confusão e o processo de determinar a pessoa exata de quem estamos falando, procurando outras informações além do primeiro nome, poderiam ser evitados se todos tivessem um nome único. Isso não é um problema em uma classe de 30 alunos. No entanto, será cada vez mais difícil encontrar um único, significativo e fácil de lembrar nome para cada criança em uma escola, cidade, cidade, país ou no mundo inteiro. Outro problema em fornecer a cada criança um nome único é que o processo de determinar se outra pessoa também nomeou seu filho Macey, Maci ou Macie pode ser muito cansativo.
Um conflito muito semelhante também pode surgir na programação. Quando você está escrevendo um programa de apenas 30 linhas sem dependências externas, é muito fácil dar nomes únicos e significativos a todas as suas variáveis. O problema surge quando há milhares de linhas em um programa e você também carregou alguns módulos externos. Neste tutorial, você aprenderá sobre namespaces, sua importância e resolução de escopo em Python.
O que são namespaces?
Um namespace é basicamente um sistema para garantir que todos os nomes em um programa sejam únicos e possam ser usados sem nenhum conflito. Você já deve saber que tudo em Python—como strings, listas, funções, etc.—é um objeto. Outro fato interessante é que Python implementa namespaces como dicionários. Existe 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á-lo para um objeto diferente. Aqui estão alguns exemplos de namespaces:
- Namespace local: Este namespace inclui nomes locais dentro de uma função. Esse namespace é criado quando uma função é chamada e dura apenas até que a função retorne.
- Espaço de nomes global: Esse namespace inclui nomes de vários módulos importados que você está usando em um projeto. Ele é criado quando o módulo é incluído no projeto e dura até o final do script.
- Namespace integrado: Esse namespace inclui funções internas e nomes de exceção internos.
- Incluindo namespace: A inclusão de namespaces ocorre quando uma função contém outras funções.
Namespaces integrados
Python tem 152 nomes integrados, incluindo funções, tipos e exceções. Para visualizar esses nomes, abra um shell Python e emita o comando a seguir.
>>> print(dir(__builtins__)) ['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip'] >>> print(len(dir(__builtins__))) 152
Os namespaces integrados estão sempre disponíveis no interpretador Python; por exemplo, se quisermos usar o sum()
função, não precisamos importá-la. Veja o código abaixo, que encontra a soma dos números em uma lista usando o built-in sum()
função.
>>> sum_numbers = sum([3,4,5,5]) >>> sum_numbers 17 >>>
Espaços de nomes globais
Os namespaces globais existem após os namespaces embutidos e geralmente são definidos no nível superior do programa. Estas podem ser quaisquer variáveis definidas ou importações. Por exemplo, suponha que escrevemos um programa que itera por meio de uma lista de números, conforme mostrado abaixo.
numbers =[2,5,16,8,17,13,42,23,21] for num in numbers: if num%2 ==0: print(str(num) + ' is an even number')
No código acima, como sabemos quais são os namespaces globais? Você pode usar globals()
descobrir. globals()
é uma função interna que retorna um dicionário dos nomes globais atuais. Atualize o código conforme mostrado abaixo.
numbers =[2,5,16,8,17,13,42,23,21] for num in numbers: if num%2 ==0: print(str(num) + ' is an even number') print(globals())
Ao executar o programa Python novamente, você deverá obter a saída abaixo.
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f318c40dc10>, '__spec__': None, '__annotations__': {}, '__builtins__':, '__file__': '/home/vaati/Desktop/names_and_scope.py', '__cached__': None, 'numbers': [2, 5, 16, 8, 17, 13, 42, 23, 21], 'num': 21}
A partir dos resultados, podemos confirmar que a variável number é um namespace global. str
e print
são namespaces internos. Se você esquecer de converter num
para uma string na instrução print no código acima, você obterá um TypeError
,
print(num + ' is an even number') TypeError: unsupported operand type(s) for +: 'int' and 'str
TypeError
faz parte dos namespaces internos sobre os quais falamos anteriormente.
Namespace local
Os namespaces locais são definidos dentro de um bloco de código e são acessíveis apenas dentro do bloco, por exemplo, dentro de classes, funções ou loops. Curti global()
python nos fornece o locals()
função, que podemos usar para verificar nomes locais. Considere o exemplo abaixo.
def sum_of_numbers(my_list): total = sum(my_list) print(locals()) sum_of_numbers([3,4,5])
A saída será:
{'my_list': [3, 4, 5], 'total': 12}
A partir dos resultados acima, podemos notar que os nomes locais incluem o total
variável e o argumento da função, i. e my_list
.
Incluindo Namespace
Os namespaces fechados são semelhantes aos namespaces locais, mas as funções aninhadas os criam. Considere o exemplo abaixo.
def main_func(): print('This is the main function') def inner_func(): print('This function is inside the main function')
No exemplo acima, main_func()
é a função envolvente enquanto inner_func()
é a função fechada.
Na série Mathematical Modules in Python no Envato Tuts+, escrevi sobre funções matemáticas úteis disponíveis em diferentes módulos. Por exemplo, os módulos math e cmath possuem muitas funções que são comuns a ambos, como log10()
, acos()
, cos()
, exp()
etc. Se você estiver usando esses dois módulos no mesmo programa, a única maneira de usar essas funções sem ambiguidade é prefixá-las com o nome do módulo, como math.log10()
e cmath.log10()
.
O que é escopo?
Os namespaces nos ajudam a identificar exclusivamente todos os nomes dentro de um programa. No entanto, isso não significa que podemos usar um nome de variável onde quisermos. Um nome também tem um escopo que define as partes do programa em que você pode usar esse nome sem usar nenhum prefixo. Assim como os namespaces, também existem 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 mais interno que contém uma lista de nomes locais disponíveis na função atual.
- Um escopo de todas as funções de fechamento. A busca por um nome começa no escopo mais próximo e se move para fora.
- Um escopo de nível de módulo 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. Este escopo é pesquisado por último para localizar o nome que você fez referência.
Nas próximas seções deste tutorial, usaremos extensivamente a função Python dir() integrada para retornar uma lista de nomes no escopo local atual. Isso ajudará você a entender o conceito de namespaces e escopo com mais clareza.
Resolução do Escopo
Como mencionei na seção anterior, a busca por um determinado nome começa na função mais interna e depois se move cada vez mais alto até que o programa possa mapear esse nome para um objeto. Quando nenhum nome é encontrado em nenhum dos namespaces, o programa gera um NameError
exceção.
Antes de começarmos, tente digitar dir()
em IDLE ou qualquer outro IDE Python.
dir() # ['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']
Todos esses nomes listados por dir()
estão disponíveis em todos os programas Python. Por uma questão de brevidade, começarei a me referir a eles como '__builtins__'...'__spec__'
nos demais exemplos.
Vamos ver a saída do dir()
função depois de definir uma variável e uma função.
a_num = 10 dir() # ['__builtins__' .... '__spec__', 'a_num'] def some_func(): b_num = 11 print(dir()) some_func() # ['b_num'] dir() # ['__builtins__' ... '__spec__', 'a_num', 'some_func']
o dir()
A função apenas gera a lista de nomes dentro do escopo atual. É por isso que dentro do escopo de some_func()
há apenas um nome chamado b_num
. Ligando dir()
depois de definir some_func()
adiciona-o à lista de nomes disponíveis no namespace global.
Agora, vamos ver a lista de nomes dentro de algumas funções aninhadas. O código neste bloco continua do bloco anterior.
def outer_func(): c_num = 12 def inner_func(): d_num = 13 print(dir(), ' - names in inner_func') e_num = 14 inner_func() print(dir(), ' - names in outer_func') outer_func() # ['d_num'] - names in inner_func # ['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()
. Lado de dentro inner_func()
a dir()
função imprime apenas o nome d_num
. Isso parece justo como d_num
é a única variável definida lá.
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. Isso fica evidente no código a seguir.
a_num = 10 b_num = 11 def outer_func(): global a_num a_num = 15 b_num = 16 def inner_func(): global a_num a_num = 20 b_num = 21 print('a_num inside inner_func :', a_num) print('b_num inside inner_func :', b_num) inner_func() print('a_num inside outer_func :', a_num) print('b_num inside outer_func :', b_num) outer_func() print('a_num outside all functions :', a_num) print('b_num outside all functions :', b_num) # a_num inside inner_func : 20 # b_num inside inner_func : 21 # a_num inside outer_func : 20 # b_num inside outer_func : 16 # a_num outside all functions : 20 # b_num outside all functions : 11
Dentro tanto do outer_func()
e inner_func()
, a_num
foi declarado como uma variável global. Estamos apenas definindo um valor diferente para a mesma variável global. Esta é a razão pela qual o valor de a_num
em todos os locais é 20. Por outro lado, cada função cria seu próprio b_num
variável com um escopo local, e o print()
A função imprime o valor desta variável com escopo local.
Importando Módulos Corretamente
É muito comum importar módulos externos em seus projetos para acelerar o desenvolvimento. Existem três maneiras diferentes de importar módulos. Nesta seção, você aprenderá sobre todos esses métodos, discutindo seus prós e contras em detalhes.
Importando todos os nomes de um módulo
from module import *
: Este método de importação de um módulo importa todos os nomes do módulo fornecido diretamente em seu namespace atual. Você pode ficar tentado a usar esse método porque ele permite que você use uma função diretamente sem adicionar o nome do módulo como prefixo. No entanto, é muito propenso a erros e você também perde a capacidade de dizer qual módulo realmente importou essa função. Aqui está um exemplo de uso deste método:
dir() # ['__builtins__' ... '__spec__'] from math import * dir() # ['__builtins__' ... '__spec__', 'acos', 'acosh', 'asin', 'asinh', # 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', # 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', # 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', # 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', # 'modf', 'nan', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', # 'tanh', 'trunc'] log10(125) # 2.0969100130080562 from cmath import * dir() # ['__builtins__' ... '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', # 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', # 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', # 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', # 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'phase', # 'pi', 'polar', 'pow', 'radians', 'rect', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', # 'trunc'] log10(125) # (2.0969100130080562+0j)
Se você estiver familiarizado com o matemática e cmath módulos, você já sabe que existem alguns nomes comuns que são definidos em ambos os módulos, mas se aplicam a números reais e complexos, respectivamente.
Uma vez que importamos o cmath módulo após o matemática módulo, ele sobrescreve as definições de função dessas funções comuns do matemática módulo. É por isso que o primeiro log10(125)
retorna um número real e o segundo log10(125)
retorna um número complexo. Não há como você usar o log10()
função do módulo de matemática agora. Mesmo se você tentar digitar math.log10(125)
você receberá uma exceção NameError porque math
não existe realmente no namespace.
A conclusão é que você não deve usar essa maneira de importar funções de módulos diferentes apenas para economizar alguns toques de tecla.
Importando nomes específicos de um módulo
from module import nameA, nameB
: Se você sabe que usará apenas um ou dois nomes de um módulo, poderá importá-los diretamente usando este método. Dessa forma, você pode escrever o código de forma mais concisa, mantendo a poluição do namespace no mínimo. No entanto, lembre-se de que você ainda não pode usar nenhum outro nome do módulo usando module.nameZ
. Qualquer função que tenha o mesmo nome em seu programa também substituirá a definição dessa função importada do módulo. Isso tornará a função importada inutilizável. Aqui está um exemplo de uso deste método:
dir() # ['__builtins__' ... '__spec__'] from math import log2, log10 dir() # ['__builtins__' ... '__spec__', 'log10', 'log2'] log10(125) # 2.0969100130080562
Importando o módulo para seu próprio namespace
import module
: Esta é a forma 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ê usará no programa. No entanto, você poderá evitar a poluição do namespace e também definir funções cujos nomes correspondam ao nome das funções do módulo.
dir() # ['__builtins__' ... '__spec__'] import math dir() # ['__builtins__' ... '__spec__', 'math'] math.log10(125) # 2.0969100130080562
Pensamentos finais
Espero que este tutorial tenha ajudado você a entender os namespaces e sua importância. Agora você deve ser capaz de determinar o escopo de diferentes nomes em um programa e evitar possíveis armadilhas.
Além disso, não hesite em ver o que temos disponível para venda e estudo no mercado, e não hesite em fazer qualquer pergunta e fornecer seu valioso feedback usando o feed abaixo.
A seção final do artigo discutiu diferentes maneiras de importar módulos em Python e os prós e contras de cada um deles. Se você tiver alguma dúvida relacionada a este tópico, deixe-me saber nos comentários.
Aprenda Python
Aprenda Python com nosso guia tutorial completo sobre Python, seja você apenas começando ou um programador experiente procurando aprender novas habilidades.
Este post foi atualizado com contribuições de Esther Vaati. Esther é desenvolvedora de software e escritora da Envato Tuts+.