Pitão é uma linguagem de programação interpretada, orientada a objetos e de alto nível com semântica dinâmica. Suas estruturas de dados embutidas de alto nível, combinadas com tipagem dinâmica e vinculação dinâmica, tornam-no muito atraente para Desenvolvimento de Aplicação Rápida , bem como para uso como linguagem de script ou cola para conectar componentes ou serviços existentes. Python oferece suporte a módulos e pacotes, incentivando assim a modularidade do programa e a reutilização de código.
A sintaxe simples e fácil de aprender do Python pode enganar Desenvolvedores Python - especialmente aqueles que são mais novos na língua - perdendo algumas de suas sutilezas e subestimando o poder do linguagem Python diversa .
Com isso em mente, este artigo apresenta uma lista dos '10 principais' de erros um tanto sutis e difíceis de detectar que podem afetar ainda mais desenvolvedores avançados de Python na traseira.
tutorial de aprendizado de máquina para iniciantes
(Observação: este artigo se destina a um público mais avançado do que Erros comuns de programadores Python , que é mais voltado para aqueles que são novos no idioma.)
Python permite que você especifique que um argumento de função é opcional fornecendo um valor padrão para isso. Embora seja um ótimo recurso da linguagem, pode causar alguma confusão quando o valor padrão é mutável . Por exemplo, considere esta definição de função Python:
>>> def foo(bar=[]): # bar is optional and defaults to [] if not specified ... bar.append('baz') # but this line could be problematic, as we'll see... ... return bar
Um erro comum é pensar que o argumento opcional será definido para a expressão padrão especificada cada vez a função é chamada sem fornecer um valor para o argumento opcional. No código acima, por exemplo, pode-se esperar que a chamada foo()
repetidamente (ou seja, sem especificar um bar
argumento) sempre retornaria 'baz'
, uma vez que a suposição seria que cada vez foo()
é chamado (sem um argumento bar
especificado) bar
está definido como []
(ou seja, uma nova lista vazia).
Mas vamos ver o que realmente acontece quando você faz isso:
>>> foo() ['baz'] >>> foo() ['baz', 'baz'] >>> foo() ['baz', 'baz', 'baz']
Hã? Por que ele continuou acrescentando o valor padrão de 'baz'
para um existir listar cada vez foo()
foi chamado, em vez de criar um Novo lista cada vez?
A resposta de programação Python mais avançada é que o valor padrão para um argumento de função é avaliado apenas uma vez, no momento em que a função é definida. Assim, o bar
argumento é inicializado com seu padrão (ou seja, uma lista vazia) somente quando foo()
é definido primeiro, mas depois chama foo()
(ou seja, sem um argumento bar
especificado) continuará a usar a mesma lista para a qual bar
foi inicializado originalmente.
Para sua informação, uma solução alternativa comum para isso é a seguinte:
>>> def foo(bar=None): ... if bar is None: # or if not bar: ... bar = [] ... bar.append('baz') ... return bar ... >>> foo() ['baz'] >>> foo() ['baz'] >>> foo() ['baz']
Considere o seguinte exemplo:
>>> class A(object): ... x = 1 ... >>> class B(A): ... pass ... >>> class C(A): ... pass ... >>> print A.x, B.x, C.x 1 1 1
Faz sentido.
>>> B.x = 2 >>> print A.x, B.x, C.x 1 2 1
Sim, novamente como esperado.
>>> A.x = 3 >>> print A.x, B.x, C.x 3 2 3
O que $% #! & ?? Nós apenas mudamos A.x
. Por que C.x
mudar também?
Em Python, as variáveis de classe são tratadas internamente como dicionários e seguem o que costuma ser chamado de Pedido de resolução de método (MRO) . Portanto, no código acima, uma vez que o atributo x
não for encontrado na classe C
, ele será pesquisado em suas classes base (apenas A
no exemplo acima, embora Python suporte várias heranças). Em outras palavras, C
não tem seu próprio x
propriedade, independente de A
. Portanto, referências a C.x
são, na verdade, referências a A.x
. Isso causa um problema no Python, a menos que seja tratado corretamente. Aprender mais sobre atributos de classe em Python .
Suponha que você tenha o seguinte código:
>>> try: ... l = ['a', 'b'] ... int(l[2]) ... except ValueError, IndexError: # To catch both exceptions, right? ... pass ... Traceback (most recent call last): File '', line 3, in IndexError: list index out of range
O problema aqui é que except
declaração faz não pegue uma lista de exceções especificadas dessa maneira. Em vez disso, no Python 2.x, a sintaxe except Exception, e
é usado para ligar a exceção ao opcional segundo parâmetro especificado (neste caso e
), a fim de torná-lo disponível para inspeção posterior. Como resultado, no código acima, o IndexError
exceção é não sendo pego pelo except
declaração; em vez disso, a exceção acaba sendo vinculada a um parâmetro denominado IndexError
.
o que é adobe xd cc
A maneira correta de capturar várias exceções em um except
declaração é especificar o primeiro parâmetro como um tupla contendo todas as exceções a serem capturadas. Além disso, para portabilidade máxima, use o as
palavra-chave, uma vez que essa sintaxe é compatível com Python 2 e Python 3:
>>> try: ... l = ['a', 'b'] ... int(l[2]) ... except (ValueError, IndexError) as e: ... pass ... >>>
A resolução de escopo Python é baseada no que é conhecido como LEGB regra, que é uma abreviação de eu ocal, É fechando, G lobal, B uilt-in. Parece bastante direto, certo? Bem, na verdade, existem algumas sutilezas na maneira como isso funciona em Python, o que nos leva ao problema comum de programação Python mais avançado a seguir. Considere o seguinte:
>>> x = 10 >>> def foo(): ... x += 1 ... print x ... >>> foo() Traceback (most recent call last): File '', line 1, in File '', line 2, in foo UnboundLocalError: local variable 'x' referenced before assignment
Qual é o problema?
O erro acima ocorre porque, quando você faz um tarefa a uma variável em um escopo, essa variável é automaticamente considerada pelo Python como local para esse escopo e sombreia qualquer variável com nome semelhante em qualquer escopo externo.
Muitos ficam, portanto, surpresos ao receber um UnboundLocalError
no código que funcionava anteriormente, quando ele é modificado pela adição de uma instrução de atribuição em algum lugar do corpo de uma função. (Você pode ler mais sobre isso Aqui .)
É particularmente comum que isso atrapalhe os desenvolvedores ao usar listas . Considere o seguinte exemplo:
>>> lst = [1, 2, 3] >>> def foo1(): ... lst.append(5) # This works ok... ... >>> foo1() >>> lst [1, 2, 3, 5] >>> lst = [1, 2, 3] >>> def foo2(): ... lst += [5] # ... but this bombs! ... >>> foo2() Traceback (most recent call last): File '', line 1, in File '', line 2, in foo UnboundLocalError: local variable 'lst' referenced before assignment
Hã? Por que foo2
bombar enquanto foo1
correu bem?
A resposta é a mesma do problema do exemplo anterior, mas é reconhecidamente mais sutil. foo1
não está fazendo um tarefa para lst
, enquanto foo2
é. Lembrando que lst += [5]
é apenas uma abreviação para lst = lst + [5]
, vemos que estamos tentando atribuir um valor para lst
(portanto, presume-se que o Python esteja no escopo local). No entanto, o valor que estamos procurando atribuir a lst
é baseado em lst
em si (novamente, agora presumivelmente no âmbito local), que ainda não foi definido. Estrondo.
O problema com o código a seguir deve ser bastante óbvio:
>>> odd = lambda x : bool(x % 2) >>> numbers = [n for n in range(10)] >>> for i in range(len(numbers)): ... if odd(numbers[i]): ... del numbers[i] # BAD: Deleting item from a list while iterating over it ... Traceback (most recent call last): File '', line 2, in IndexError: list index out of range
Excluir um item de uma lista ou array durante a iteração é um problema Python bem conhecido por qualquer desenvolvedor de software experiente. Mas, embora o exemplo acima possa ser bastante óbvio, mesmo os desenvolvedores avançados podem ser acidentalmente mordidos por isso em um código que é muito mais complexo.
Felizmente, o Python incorpora vários paradigmas de programação elegantes que, quando usados corretamente, podem resultar em um código significativamente simplificado e otimizado. Um benefício colateral disso é que o código mais simples tem menos probabilidade de ser mordido pelo bug de exclusão acidental de um item da lista durante a iteração. Um desses paradigmas é o de listar compreensões . Além disso, as compreensões de lista são particularmente úteis para evitar esse problema específico, conforme mostrado por esta implementação alternativa do código acima, que funciona perfeitamente:
>>> odd = lambda x : bool(x % 2) >>> numbers = [n for n in range(10)] >>> numbers[:] = [n for n in numbers if not odd(n)] # ahh, the beauty of it all >>> numbers [0, 2, 4, 6, 8]
Considerando o seguinte exemplo:
>>> def create_multipliers(): ... return [lambda x : i * x for i in range(5)] >>> for multiplier in create_multipliers(): ... print multiplier(2) ...
Você pode esperar a seguinte saída:
0 2 4 6 8
Mas você realmente obtém:
8 8 8 8 8
Surpresa!
movimento nos princípios do design
Isso acontece devido ao Python ligação tardia comportamento que diz que os valores das variáveis usadas nos fechamentos são consultados no momento em que a função interna é chamada. Portanto, no código acima, sempre que qualquer uma das funções retornadas é chamada, o valor de i
é procurado no escopo circundante no momento em que é chamado (e até lá, o loop foi concluído, então i
já recebeu seu valor final de 4).
A solução para esse problema comum do Python é meio hack:
>>> def create_multipliers(): ... return [lambda x, i=i : i * x for i in range(5)] ... >>> for multiplier in create_multipliers(): ... print multiplier(2) ... 0 2 4 6 8
Voilà! Estamos aproveitando os argumentos padrão aqui para gerar funções anônimas a fim de alcançar o comportamento desejado. Alguns diriam que é elegante. Alguns diriam que é sutil. Alguns odeiam. Mas se você é um desenvolvedor Python, é importante entender em qualquer caso.
Digamos que você tenha dois arquivos, a.py
e b.py
, cada um importando o outro, como segue:
Em a.py
:
import b def f(): return b.x print f()
E em b.py
:
import a x = 1 def g(): print a.f()
Primeiro, vamos tentar importar a.py
:
>>> import a 1
Funcionou muito bem. Talvez isso o surpreenda. Afinal, temos uma importação circular aqui que presumivelmente deve ser um problema, não é?
A resposta é que o simples presença de uma importação circular não é, por si só, um problema em Python. Se um módulo já foi importado, o Python é inteligente o suficiente para não tentar importá-lo novamente. No entanto, dependendo do ponto em que cada módulo está tentando acessar funções ou variáveis definidas no outro, você pode de fato ter problemas.
Portanto, voltando ao nosso exemplo, quando importamos a.py
, não houve problemas para importar b.py
, pois b.py
não requer nada de a.py
a ser definida no momento em que é importado . A única referência em b.py
para a
é a chamada para a.f()
. Mas essa chamada está em g()
e nada em a.py
ou b.py
invoca g()
. Então a vida é boa.
Mas o que acontece se tentarmos importar b.py
(sem ter importado anteriormente a.py
, isto é):
>>> import b Traceback (most recent call last): File '', line 1, in File 'b.py', line 1, in import a File 'a.py', line 6, in print f() File 'a.py', line 4, in f return b.x AttributeError: 'module' object has no attribute 'x'
Opa. Isso não é bom! O problema aqui é que, no processo de importação b.py
, ele tenta importar a.py
, que por sua vez chama f()
, que tenta acessar b.x
. Mas b.x
ainda não foi definido. Portanto, o AttributeError
exceção.
Pelo menos uma solução para isso é bastante trivial. Basta modificar b.py
para importar a.py
dentro g()
:
x = 1 def g(): import a # This will be evaluated only when g() is called print a.f()
Não, quando importamos, está tudo bem:
>>> import b >>> b.g() 1 # Printed a first time since module 'a' calls 'print f()' at the end 1 # Printed a second time, this one is our call to 'g'
Uma das belezas do Python é a riqueza de módulos de biblioteca que vem com 'fora da caixa'. Mas, como resultado, se você não o estiver evitando conscientemente, não é tão difícil encontrar um conflito de nomes entre o nome de um de seus módulos e um módulo com o mesmo nome na biblioteca padrão que vem com o Python (por exemplo , você pode ter um módulo denominado email.py
em seu código, o que estaria em conflito com o módulo de biblioteca padrão de mesmo nome).
Isso pode levar a problemas complicados, como importar outra biblioteca que, por sua vez, tenta importar a versão da biblioteca padrão do Python de um módulo, mas, uma vez que você tem um módulo com o mesmo nome, o outro pacote importa por engano sua versão em vez de dentro a biblioteca padrão do Python. É aqui que ocorrem os erros graves do Python.
Portanto, deve-se tomar cuidado para evitar o uso dos mesmos nomes dos módulos da biblioteca padrão do Python. É muito mais fácil para você alterar o nome de um módulo dentro do seu pacote do que arquivar um Proposta de aprimoramento Python (PEP) para solicitar uma mudança de nome no upstream e tentar aprová-la.
Considere o seguinte arquivo foo.py
:
import sys def bar(i): if i == 1: raise KeyError(1) if i == 2: raise ValueError(2) def bad(): e = None try: bar(int(sys.argv[1])) except KeyError as e: print('key error') except ValueError as e: print('value error') print(e) bad()
No Python 2, isso funciona bem:
$ python foo.py 1 key error 1 $ python foo.py 2 value error 2
Mas agora vamos dar uma olhada no Python 3:
qual é o trabalho de um CFO
$ python3 foo.py 1 key error Traceback (most recent call last): File 'foo.py', line 19, in bad() File 'foo.py', line 17, in bad print(e) UnboundLocalError: local variable 'e' referenced before assignment
O que acabou de acontecer aqui? O “problema” é que, no Python 3, o objeto de exceção não está acessível além do escopo do except
quadra. (A razão para isso é que, caso contrário, ele manteria um ciclo de referência com o quadro de pilha na memória até que o coletor de lixo execute e limpe as referências da memória. Mais detalhes técnicos sobre isso estão disponíveis Aqui )
Uma maneira de evitar esse problema é manter uma referência ao objeto de exceção lado de fora o escopo do except
bloquear para que permaneça acessível. Esta é uma versão do exemplo anterior que usa essa técnica, gerando um código que é compatível com Python 2 e Python 3:
import sys def bar(i): if i == 1: raise KeyError(1) if i == 2: raise ValueError(2) def good(): exception = None try: bar(int(sys.argv[1])) except KeyError as e: exception = e print('key error') except ValueError as e: exception = e print('value error') print(exception) good()
Executando isso no Py3k:
$ python3 foo.py 1 key error 1 $ python3 foo.py 2 value error 2
Yippee!
(Aliás, nosso Guia de contratação de Python discute uma série de outras diferenças importantes a serem observadas ao migrar o código do Python 2 para o Python 3.)
__del__
métodoDigamos que você tenha isso em um arquivo chamado mod.py
:
import foo class Bar(object): ... def __del__(self): foo.cleanup(self.myhandle)
E você então tentou fazer isso de another_mod.py
:
import mod mybar = mod.Bar()
Você ficaria com um AttributeError
feio | exceção.
Por quê? Porque, conforme relatado Aqui , quando o interpretador é desligado, as variáveis globais do módulo são definidas como None
. Como resultado, no exemplo acima, no ponto que __del__
é invocado, o nome foo
já foi definido como None
.
Uma solução para este problema de programação Python um pouco mais avançado seria usar atexit.register()
em vez de. Dessa forma, quando a execução do programa terminar (ou seja, ao sair normalmente), seus manipuladores registrados serão iniciados antes o intérprete é desligado.
Com esse entendimento, uma correção para o acima mod.py
o código pode ser mais ou menos assim:
diferença entre llc c corp e s corp
import foo import atexit def cleanup(handle): foo.cleanup(handle) class Bar(object): def __init__(self): ... atexit.register(cleanup, self.myhandle)
Essa implementação fornece uma maneira limpa e confiável de chamar qualquer funcionalidade de limpeza necessária após o encerramento normal do programa. Obviamente, depende de foo.cleanup
para decidir o que fazer com o objeto vinculado ao nome self.myhandle
, mas você entendeu.
Python é uma linguagem poderosa e flexível com muitos mecanismos e paradigmas que podem melhorar muito a produtividade. No entanto, como acontece com qualquer ferramenta de software ou linguagem, ter uma compreensão limitada ou apreciação de seus recursos pode às vezes ser mais um impedimento do que um benefício, deixando alguém no estado proverbial de “saber o suficiente para ser perigoso”.
Familiarizar-se com as principais nuances do Python, como (mas não se limitando a) os problemas de programação moderadamente avançados levantados neste artigo, ajudará a otimizar o uso da linguagem, evitando alguns de seus erros mais comuns.
Você também pode querer verificar nosso Guia do Insider para Entrevistas em Python para sugestões sobre perguntas de entrevista que podem ajudar a identificar especialistas em Python.
Esperamos que você tenha achado as dicas neste artigo úteis e agradecemos seus comentários.