Explorando e entendendo Python por meio de trechos surpreendentes.
Traduções: Chinês 中文 | Vietnamita Tiếng Việt | Espanhol Espanhol | Coreano 한국어 | Russo Russo Русский | Alemão Alemão | Adicionar tradução
Outras modalidades: Site Interativo | Caderno Interativo
Python, sendo uma linguagem de programação de alto nível lindamente projetada e baseada em interpretador, nos fornece muitos recursos para o conforto do programador. Mas às vezes, os resultados de um snippet Python podem não parecer óbvios à primeira vista.
Aqui está um projeto divertido que tenta explicar o que exatamente está acontecendo nos bastidores de alguns trechos contra-intuitivos e recursos menos conhecidos em Python.
Embora alguns dos exemplos que você vê abaixo possam não ser WTFs no sentido mais verdadeiro, eles revelarão algumas das partes interessantes do Python que você talvez não conheça. Acho que é uma ótima maneira de aprender o interior de uma linguagem de programação e acredito que você também achará interessante!
Se você é um programador Python experiente, pode considerar um desafio acertar a maioria deles na primeira tentativa. Você pode já ter experimentado alguns deles antes, e talvez eu seja capaz de reviver suas doces e antigas lembranças! ?
PS: Se você é um leitor antigo, pode aprender sobre as novas modificações aqui (os exemplos marcados com asterisco são aqueles adicionados na última revisão principal).
Então, vamos lá...
is
operadoris not ...
não is (not ...)
del
operaçãogoto
, mas por quê?+=
é mais rápidodict
*dict
de instância inchados *Todos os exemplos estão estruturados conforme abaixo:
▶ Algum título sofisticado
# Set up the code. # Preparation for the magic...Saída (versões do Python):
> >> triggering_statement Some unexpected output(Opcional): Uma linha descrevendo a saída inesperada.
Explicação:
- Breve explicação do que está acontecendo e por que está acontecendo.
# Set up code # More examples for further clarification (if necessary)Saída (versões do Python):
> >> trigger # some example that makes it easy to unveil the magic # some justified output
Nota: Todos os exemplos são testados no interpretador interativo Python 3.5.2 e devem funcionar para todas as versões do Python, a menos que especificado explicitamente antes da saída.
Uma boa maneira de aproveitar ao máximo esses exemplos, na minha opinião, é lê-los em ordem sequencial e para cada exemplo:
Por alguma razão, o operador "Walrus" do Python 3.8 ( :=
) tornou-se bastante popular. Vamos dar uma olhada,
1.
# Python version 3.8+
> >> a = "wtf_walrus"
> >> a
'wtf_walrus'
> >> a := "wtf_walrus"
File "<stdin>" , line 1
a := "wtf_walrus"
^
SyntaxError : invalid syntax
>> > ( a := "wtf_walrus" ) # This works though
'wtf_walrus'
> >> a
'wtf_walrus'
2.
# Python version 3.8+
> >> a = 6 , 9
> >> a
( 6 , 9 )
> >> ( a := 6 , 9 )
( 6 , 9 )
> >> a
6
> >> a , b = 6 , 9 # Typical unpacking
> >> a , b
( 6 , 9 )
>> > ( a , b = 16 , 19 ) # Oops
File "<stdin>" , line 1
( a , b = 16 , 19 )
^
SyntaxError : invalid syntax
>> > ( a , b := 16 , 19 ) # This prints out a weird 3-tuple
( 6 , 16 , 19 )
>> > a # a is still unchanged?
6
>> > b
16
Atualização rápida do operador de morsa
O operador Walrus ( :=
) foi introduzido no Python 3.8 e pode ser útil em situações em que você deseja atribuir valores a variáveis dentro de uma expressão.
def some_func ():
# Assume some expensive computation here
# time.sleep(1000)
return 5
# So instead of,
if some_func ():
print ( some_func ()) # Which is bad practice since computation is happening twice
# or
a = some_func ()
if a :
print ( a )
# Now you can concisely write
if a := some_func ():
print ( a )
Saída (> 3,8):
5
5
5
Isso salvou uma linha de código e impediu implicitamente a invocação some_func
duas vezes.
A "expressão de atribuição" sem parênteses (uso do operador walrus) é restrita no nível superior, daí o SyntaxError
na instrução a := "wtf_walrus"
do primeiro trecho. Colocar parênteses funcionou conforme o esperado e atribuiu a
.
Como de costume, não é permitido colocar parênteses em uma expressão contendo o operador =
. Daí o erro de sintaxe em (a, b = 6, 9)
.
A sintaxe do operador Walrus tem o formato NAME:= expr
, onde NAME
é um identificador válido e expr
é uma expressão válida. Conseqüentemente, empacotamento e desempacotamento iteráveis não são suportados, o que significa,
(a := 6, 9)
é equivalente a ((a := 6), 9)
e, finalmente (a, 9)
(onde o valor de a
é 6')
> >> ( a := 6 , 9 ) == (( a := 6 ), 9 )
True
> >> x = ( a := 696 , 9 )
> >> x
( 696 , 9 )
> >> x [ 0 ] is a # Both reference same memory location
True
Da mesma forma, (a, b := 16, 19)
é equivalente a (a, (b := 16), 19)
que nada mais é do que uma tupla de 3.
1.
> >> a = "some_string"
> >> id ( a )
140420665652016
> >> id ( "some" + "_" + "string" ) # Notice that both the ids are same.
140420665652016
2.
> >> a = "wtf"
> >> b = "wtf"
> >> a is b
True
> >> a = "wtf!"
> >> b = "wtf!"
> >> a is b
False
3.
> >> a , b = "wtf!" , "wtf!"
> >> a is b # All versions except 3.7.x
True
> >> a = "wtf!" ; b = "wtf!"
> >> a is b # This will print True or False depending on where you're invoking it (python shell / ipython / as a script)
False
# This time in file some_file.py
a = "wtf!"
b = "wtf!"
print ( a is b )
# prints True when the module is invoked!
4.
Saída (<Python3.7)
> >> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
> >> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False
Faz sentido, certo?
'wtf'
será internado, mas ''.join(['w', 't', 'f'])
não será internado)'wtf!'
não foi internado devido a !
. A implementação CPython desta regra pode ser encontrada aqui a
e b
estão definidos como "wtf!"
na mesma linha, o interpretador Python cria um novo objeto e, em seguida, faz referência à segunda variável ao mesmo tempo. Se você fizer isso em linhas separadas, ele não "sabe" que já existe "wtf!"
como um objeto (porque "wtf!"
não está implicitamente internado de acordo com os fatos mencionados acima). É uma otimização em tempo de compilação. Esta otimização não se aplica às versões 3.7.x do CPython (verifique este problema para mais discussões).a, b = "wtf!", "wtf!"
é uma instrução única, enquanto a = "wtf!"; b = "wtf!"
são duas declarações em uma única linha. Isso explica porque as identidades são diferentes em a = "wtf!"; b = "wtf!"
e também explique por que eles são iguais quando invocados em some_file.py
'a'*20
é substituída por 'aaaaaaaaaaaaaaaaaaaa'
durante a compilação para economizar alguns ciclos de clock durante o tempo de execução. A dobra constante ocorre apenas para strings com comprimento inferior a 21. (Por quê? Imagine o tamanho do arquivo .pyc
gerado como resultado da expressão 'a'*10**10
). Aqui está a fonte de implementação para o mesmo. > >> ( False == False ) in [ False ] # makes sense
False
> >> False == ( False in [ False ]) # makes sense
False
> >> False == False in [ False ] # now what?
True
> >> True is False == False
False
> >> False is False is False
True
> >> 1 > 0 < 1
True
> >> ( 1 > 0 ) < 1
False
> >> 1 > ( 0 < 1 )
False
Conforme https://docs.python.org/3/reference/expressions.html#comparisons
Formalmente, se a, b, c, ..., y, z são expressões e op1, op2, ..., opN são operadores de comparação, então a op1 b op2 c ... y opN z é equivalente a a op1 b e b op2 c e ... y opN z, exceto que cada expressão é avaliada no máximo uma vez.
Embora tal comportamento possa parecer bobo para você nos exemplos acima, é fantástico com coisas como a == b == c
e 0 <= x <= 100
.
False is False is False
é equivalente a (False is False) and (False is False)
True is False == False
é equivalente a (True is False) and (False == False)
e como a primeira parte da instrução ( True is False
) é avaliada como False
, a expressão geral é avaliada como False
.1 > 0 < 1
é equivalente a (1 > 0) and (0 < 1)
que é avaliado como True
.(1 > 0) < 1
é equivalente a True < 1
e > >> int ( True )
1
> >> True + 1 #not relevant for this example, but just for fun
2
1 < 1
é avaliado como False
is
operadorA seguir está um exemplo muito famoso presente em toda a internet.
1.
> >> a = 256
> >> b = 256
> >> a is b
True
> >> a = 257
> >> b = 257
> >> a is b
False
2.
> >> a = []
> >> b = []
> >> a is b
False
> >> a = tuple ()
> >> b = tuple ()
> >> a is b
True
3. Saída
> >> a , b = 257 , 257
> >> a is b
True
Saída (especificamente Python 3.7.x)
> >> a , b = 257 , 257
> >> a is b
False
A diferença entre is
e ==
is
verifica se ambos os operandos se referem ao mesmo objeto (ou seja, verifica se a identidade dos operandos corresponde ou não).==
compara os valores de ambos os operandos e verifica se eles são iguais.is
para igualdade de referência e ==
é para igualdade de valores. Um exemplo para esclarecer as coisas, > >> class A : pass
> >> A () is A () # These are two empty objects at two different memory locations.
False
256
é um objeto existente, mas 257
não é
Ao iniciar o python, os números de -5
a 256
serão alocados. Esses números são muito usados, então faz sentido tê-los prontos.
Citando https://docs.python.org/3/c-api/long.html
A implementação atual mantém um array de objetos inteiros para todos os inteiros entre -5 e 256, quando você cria um int nesse intervalo você apenas recebe de volta uma referência ao objeto existente. Portanto, deveria ser possível alterar o valor de 1. Suspeito que o comportamento do Python, neste caso, seja indefinido. :-)
> >> id ( 256 )
10922528
> >> a = 256
> >> b = 256
> >> id ( a )
10922528
> >> id ( b )
10922528
> >> id ( 257 )
140084850247312
> >> x = 257
> >> y = 257
> >> id ( x )
140084850247440
> >> id ( y )
140084850247344
Aqui o intérprete não é inteligente o suficiente ao executar y = 257
para reconhecer que já criamos um número inteiro de valor 257,
e então ele cria outro objeto na memória.
Otimização semelhante também se aplica a outros objetos imutáveis , como tuplas vazias. Como as listas são mutáveis, é por isso que [] is []
retornará False
e () is ()
retornará True
. Isso explica nosso segundo trecho. Vamos passar para o terceiro,
Tanto a
quanto b
referem-se ao mesmo objeto quando inicializado com o mesmo valor na mesma linha.
Saída
> >> a , b = 257 , 257
> >> id ( a )
140640774013296
> >> id ( b )
140640774013296
> >> a = 257
> >> b = 257
> >> id ( a )
140640774013392
> >> id ( b )
140640774013488
Quando a e b são definidos como 257
na mesma linha, o interpretador Python cria um novo objeto e, em seguida, faz referência à segunda variável ao mesmo tempo. Se você fizer isso em linhas separadas, ele não "sabe" que já existe 257
como objeto.
É uma otimização do compilador e se aplica especificamente ao ambiente interativo. Quando você insere duas linhas em um interpretador ao vivo, elas são compiladas separadamente e, portanto, otimizadas separadamente. Se você tentasse este exemplo em um arquivo .py
, não veria o mesmo comportamento, porque o arquivo é compilado de uma só vez. Esta otimização não se limita a números inteiros, ela funciona para outros tipos de dados imutáveis, como strings (verifique o "exemplo de strings são complicados") e flutuantes também,
> >> a , b = 257.0 , 257.0
> >> a is b
True
Por que isso não funcionou para Python 3.7? A razão abstrata é porque tais otimizações do compilador são específicas da implementação (ou seja, podem mudar com a versão, sistema operacional, etc.). Ainda estou descobrindo qual mudança exata de implementação causa o problema. Você pode verificar este problema para atualizações.
1.
some_dict = {}
some_dict [ 5.5 ] = "JavaScript"
some_dict [ 5.0 ] = "Ruby"
some_dict [ 5 ] = "Python"
Saída:
> >> some_dict [ 5.5 ]
"JavaScript"
> >> some_dict [ 5.0 ] # "Python" destroyed the existence of "Ruby"?
"Python"
> >> some_dict [ 5 ]
"Python"
> >> complex_five = 5 + 0j
> >> type ( complex_five )
complex
> >> some_dict [ complex_five ]
"Python"
Então, por que o Python está em todo lugar?
A exclusividade das chaves em um dicionário Python é por equivalência , não por identidade. Portanto, embora 5
, 5.0
e 5 + 0j
sejam objetos distintos de tipos diferentes, por serem iguais, não podem estar ambos no mesmo dict
(ou set
). Assim que você inserir qualquer um deles, a tentativa de procurar qualquer chave distinta, mas equivalente, terá sucesso com o valor mapeado original (em vez de falhar com KeyError
):
> >> 5 == 5.0 == 5 + 0j
True
> >> 5 is not 5.0 is not 5 + 0j
True
> >> some_dict = {}
> >> some_dict [ 5.0 ] = "Ruby"
> >> 5.0 in some_dict
True
> >> ( 5 in some_dict ) and ( 5 + 0j in some_dict )
True
Isto também se aplica ao definir um item. Então, quando você faz some_dict[5] = "Python"
, Python encontra o item existente com chave equivalente 5.0 -> "Ruby"
, substitui seu valor no lugar e deixa a chave original em paz.
> >> some_dict
{ 5.0 : 'Ruby' }
> >> some_dict [ 5 ] = "Python"
> >> some_dict
{ 5.0 : 'Python' }
Então, como podemos atualizar a chave para 5
(em vez de 5.0
)? Na verdade, não podemos fazer essa atualização no local, mas o que podemos fazer é primeiro excluir a chave ( del some_dict[5.0]
) e, em seguida, configurá-la ( some_dict[5]
) para obter o número inteiro 5
como a chave em vez de flutuante 5.0
, embora isso deva ser necessário em casos raros.
Como o Python encontrou 5
em um dicionário contendo 5.0
? Python faz isso em tempo constante, sem precisar examinar cada item usando funções hash. Quando Python procura uma chave foo
em um dict, ele primeiro calcula hash(foo)
(que é executado em tempo constante). Como em Python é necessário que objetos que comparem iguais também tenham o mesmo valor de hash (documentos aqui), 5
, 5.0
e 5 + 0j
tenham o mesmo valor de hash.
> >> 5 == 5.0 == 5 + 0j
True
> >> hash ( 5 ) == hash ( 5.0 ) == hash ( 5 + 0j )
True
Nota: O inverso não é necessariamente verdadeiro: objetos com valores de hash iguais podem ser desiguais. (Isso causa o que é conhecido como colisão de hash e degrada o desempenho de tempo constante que o hash geralmente fornece.)
class WTF :
pass
Saída:
> >> WTF () == WTF () # two different instances can't be equal
False
> >> WTF () is WTF () # identities are also different
False
> >> hash ( WTF ()) == hash ( WTF ()) # hashes _should_ be different as well
True
> >> id ( WTF ()) == id ( WTF ())
True
Quando id
foi chamado, Python criou um objeto de classe WTF
e o passou para a função id
. A função id
pega seu id
(seu local de memória) e joga fora o objeto. O objeto é destruído.
Quando fazemos isso duas vezes seguidas, o Python também aloca o mesmo local de memória para esse segundo objeto. Como (no CPython) id
usa o local da memória como id do objeto, o id dos dois objetos é o mesmo.
Portanto, o id do objeto é único apenas durante o tempo de vida do objeto. Depois que o objeto for destruído ou antes de ser criado, outra coisa poderá ter o mesmo ID.
Mas por que o operador is
foi avaliado como False
? Vamos ver com este trecho.
class WTF ( object ):
def __init__ ( self ): print ( "I" )
def __del__ ( self ): print ( "D" )
Saída:
> >> WTF () is WTF ()
I
I
D
D
False
> >> id ( WTF ()) == id ( WTF ())
I
D
I
D
True
Como você pode observar, a ordem em que os objetos são destruídos é o que fez toda a diferença aqui.
from collections import OrderedDict
dictionary = dict ()
dictionary [ 1 ] = 'a' ; dictionary [ 2 ] = 'b' ;
ordered_dict = OrderedDict ()
ordered_dict [ 1 ] = 'a' ; ordered_dict [ 2 ] = 'b' ;
another_ordered_dict = OrderedDict ()
another_ordered_dict [ 2 ] = 'b' ; another_ordered_dict [ 1 ] = 'a' ;
class DictWithHash ( dict ):
"""
A dict that also implements __hash__ magic.
"""
__hash__ = lambda self : 0
class OrderedDictWithHash ( OrderedDict ):
"""
An OrderedDict that also implements __hash__ magic.
"""
__hash__ = lambda self : 0
Saída
> >> dictionary == ordered_dict # If a == b
True
> >> dictionary == another_ordered_dict # and b == c
True
> >> ordered_dict == another_ordered_dict # then why isn't c == a ??
False
# We all know that a set consists of only unique elements,
# let's try making a set of these dictionaries and see what happens...
> >> len ({ dictionary , ordered_dict , another_ordered_dict })
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
TypeError : unhashable type : 'dict'
# Makes sense since dict don't have __hash__ implemented, let's use
# our wrapper classes.
> >> dictionary = DictWithHash ()
> >> dictionary [ 1 ] = 'a' ; dictionary [ 2 ] = 'b' ;
> >> ordered_dict = OrderedDictWithHash ()
> >> ordered_dict [ 1 ] = 'a' ; ordered_dict [ 2 ] = 'b' ;
> >> another_ordered_dict = OrderedDictWithHash ()
> >> another_ordered_dict [ 2 ] = 'b' ; another_ordered_dict [ 1 ] = 'a' ;
> >> len ({ dictionary , ordered_dict , another_ordered_dict })
1
> >> len ({ ordered_dict , another_ordered_dict , dictionary }) # changing the order
2
O que está acontecendo aqui?
A razão pela qual a igualdade intransitiva não se manteve entre dictionary
, ordered_dict
e another_ordered_dict
é devido à maneira como o método __eq__
é implementado na classe OrderedDict
. Dos documentos
Os testes de igualdade entre objetos OrderedDict são sensíveis à ordem e são implementados como
list(od1.items())==list(od2.items())
. Os testes de igualdade entre objetosOrderedDict
e outros objetos de mapeamento não diferenciam a ordem, como dicionários regulares.
A razão para essa igualdade de comportamento é que ela permite que objetos OrderedDict
sejam substituídos diretamente em qualquer lugar em que um dicionário regular seja usado.
Ok, então por que alterar a ordem afetou o comprimento do objeto set
gerado? A resposta é apenas a falta de igualdade intransitiva. Como os conjuntos são coleções "não ordenadas" de elementos únicos, a ordem na qual os elementos são inseridos não deve importar. Mas neste caso, isso importa. Vamos decompô-lo um pouco,
> >> some_set = set ()
> >> some_set . add ( dictionary ) # these are the mapping objects from the snippets above
> >> ordered_dict in some_set
True
> >> some_set . add ( ordered_dict )
> >> len ( some_set )
1
> >> another_ordered_dict in some_set
True
> >> some_set . add ( another_ordered_dict )
> >> len ( some_set )
1
> >> another_set = set ()
> >> another_set . add ( ordered_dict )
> >> another_ordered_dict in another_set
False
> >> another_set . add ( another_ordered_dict )
> >> len ( another_set )
2
> >> dictionary in another_set
True
> >> another_set . add ( another_ordered_dict )
> >> len ( another_set )
2
Portanto, a inconsistência se deve ao fato de another_ordered_dict in another_set
ser False
porque ordered_dict
já estava presente em another_set
e como observado antes, ordered_dict == another_ordered_dict
é False
.
def some_func ():
try :
return 'from_try'
finally :
return 'from_finally'
def another_func ():
for _ in range ( 3 ):
try :
continue
finally :
print ( "Finally!" )
def one_more_func (): # A gotcha!
try :
for i in range ( 3 ):
try :
1 / i
except ZeroDivisionError :
# Let's throw it here and handle it outside for loop
raise ZeroDivisionError ( "A trivial divide by zero error" )
finally :
print ( "Iteration" , i )
break
except ZeroDivisionError as e :
print ( "Zero division error occurred" , e )
Saída:
> >> some_func ()
'from_finally'
> >> another_func ()
Finally !
Finally !
Finally !
>> > 1 / 0
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
ZeroDivisionError : division by zero
> >> one_more_func ()
Iteration 0
return
, break
ou continue
é executada no conjunto try
de uma instrução "try…finally", a cláusula finally
também é executada na saída.return
executada. Como a cláusula finally
sempre é executada, uma instrução return
executada na cláusula finally
será sempre a última executada.return
ou break
, a exceção salva temporariamente será descartada. some_string = "wtf"
some_dict = {}
for i , some_dict [ i ] in enumerate ( some_string ):
i = 10
Saída:
> >> some_dict # An indexed dict appears.
{ 0 : 'w' , 1 : 't' , 2 : 'f' }
Uma instrução for
é definida na gramática Python como:
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
Onde exprlist
é o destino da atribuição. Isso significa que o equivalente a {exprlist} = {next_value}
é executado para cada item do iterável. Um exemplo interessante que ilustra isso:
for i in range ( 4 ):
print ( i )
i = 10
Saída:
0
1
2
3
Você esperava que o loop fosse executado apenas uma vez?
Explicação:
i = 10
nunca afeta as iterações do loop devido à maneira como os loops for funcionam em Python. Antes do início de cada iteração, o próximo item fornecido pelo iterador ( range(4)
neste caso) é descompactado e atribuído às variáveis da lista de destino ( i
neste caso). A função enumerate(some_string)
produz um novo valor i
(um contador subindo) e um caractere de some_string
em cada iteração. Em seguida, ele define a chave i
(recém atribuída) do dicionário some_dict
para esse caractere. O desenrolar do loop pode ser simplificado como:
> >> i , some_dict [ i ] = ( 0 , 'w' )
> >> i , some_dict [ i ] = ( 1 , 't' )
> >> i , some_dict [ i ] = ( 2 , 'f' )
> >> some_dict
1.
array = [ 1 , 8 , 15 ]
# A typical generator expression
gen = ( x for x in array if array . count ( x ) > 0 )
array = [ 2 , 8 , 22 ]
Saída:
> >> print ( list ( gen )) # Where did the other values go?
[ 8 ]
2.
array_1 = [ 1 , 2 , 3 , 4 ]
gen_1 = ( x for x in array_1 )
array_1 = [ 1 , 2 , 3 , 4 , 5 ]
array_2 = [ 1 , 2 , 3 , 4 ]
gen_2 = ( x for x in array_2 )
array_2 [:] = [ 1 , 2 , 3 , 4 , 5 ]
Saída:
> >> print ( list ( gen_1 ))
[ 1 , 2 , 3 , 4 ]
> >> print ( list ( gen_2 ))
[ 1 , 2 , 3 , 4 , 5 ]
3.
array_3 = [ 1 , 2 , 3 ]
array_4 = [ 10 , 20 , 30 ]
gen = ( i + j for i in array_3 for j in array_4 )
array_3 = [ 4 , 5 , 6 ]
array_4 = [ 400 , 500 , 600 ]
Saída:
> >> print ( list ( gen ))
[ 401 , 501 , 601 , 402 , 502 , 602 , 403 , 503 , 603 ]
Em uma expressão geradora, a cláusula in
é avaliada em tempo de declaração, mas a cláusula condicional é avaliada em tempo de execução.
Portanto, antes do tempo de execução, array
é reatribuído à lista [2, 8, 22]
e, como de 1
, 8
e 15
, apenas a contagem de 8
é maior que 0
, o gerador produz apenas 8
.
As diferenças na saída de g1
e g2
na segunda parte se devem à forma como as variáveis array_1
e array_2
são reatribuídas com valores.
No primeiro caso, array_1
está vinculado ao novo objeto [1,2,3,4,5]
e como a cláusula in
é avaliada no momento da declaração ela ainda se refere ao objeto antigo [1,2,3,4]
(que não é destruído).
No segundo caso, a atribuição de fatia para array_2
atualiza o mesmo objeto antigo [1,2,3,4]
para [1,2,3,4,5]
. Portanto, g2
e array_2
ainda têm referência ao mesmo objeto (que agora foi atualizado para [1,2,3,4,5]
).
Ok, seguindo a lógica discutida até agora, o valor de list(gen)
no terceiro trecho não deveria ser [11, 21, 31, 12, 22, 32, 13, 23, 33]
? (porque array_3
e array_4
se comportarão exatamente como array_1
). A razão pela qual (apenas) os valores array_4
foram atualizados é explicada em PEP-289
Apenas a expressão for mais externa é avaliada imediatamente, as outras expressões são adiadas até que o gerador seja executado.
is not ...
não is (not ...)
> >> 'something' is not None
True
> >> 'something' is ( not None )
False
is not
é um único operador binário e tem comportamento diferente de usar is
e not
separado.is not
é avaliado como False
se as variáveis em ambos os lados do operador apontarem para o mesmo objeto e True
caso contrário.(not None)
é avaliado como True
, pois o valor None
é False
em um contexto booleano, então a expressão se torna 'something' is True
. # Let's initialize a row
row = [ "" ] * 3 #row i['', '', '']
# Let's make a board
board = [ row ] * 3
Saída:
> >> board
[[ '' , '' , '' ], [ '' , '' , '' ], [ '' , '' , '' ]]
> >> board [ 0 ]
[ '' , '' , '' ]
> >> board [ 0 ][ 0 ]
''
> >> board [ 0 ][ 0 ] = "X"
> >> board
[[ 'X' , '' , '' ], [ 'X' , '' , '' ], [ 'X' , '' , '' ]]
Não atribuímos três "X"
, não é?
Quando inicializamos a variável row
, esta visualização explica o que acontece na memória
E quando a board
é inicializada multiplicando a row
, é isso que acontece dentro da memória (cada um dos elementos board[0]
, board[1]
e board[2]
é uma referência à mesma lista referida por row
)
Podemos evitar esse cenário aqui não usando a variável row
para gerar board
. (Perguntado nesta edição).
> >> board = [[ '' ] * 3 for _ in range ( 3 )]
> >> board [ 0 ][ 0 ] = "X"
> >> board
[[ 'X' , '' , '' ], [ '' , '' , '' ], [ '' , '' , '' ]]
funcs = []
results = []
for x in range ( 7 ):
def some_func ():
return x
funcs . append ( some_func )
results . append ( some_func ()) # note the function call here
funcs_results = [ func () for func in funcs ]
Saída (versão Python):
> >> results
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
> >> funcs_results
[ 6 , 6 , 6 , 6 , 6 , 6 , 6 ]
Os valores de x
eram diferentes em cada iteração antes de anexar some_func
a funcs
, mas todas as funções retornam 6 quando são avaliadas após a conclusão do loop.
> >> powers_of_x = [ lambda x : x ** i for i in range ( 10 )]
> >> [ f ( 2 ) for f in powers_of_x ]
[ 512 , 512 , 512 , 512 , 512 , 512 , 512 , 512 , 512 , 512 ]
x
no contexto circundante, em vez de usar o valor de x
no momento em que a função é criada. Portanto, todas as funções usam o último valor atribuído à variável para cálculo. Podemos ver que ele está usando x
do contexto circundante (ou seja, não uma variável local) com: > >> import inspect
> >> inspect . getclosurevars ( funcs [ 0 ])
ClosureVars ( nonlocals = {}, globals = { 'x' : 6 }, builtins = {}, unbound = set ())
Como x
é um valor global, podemos alterar o valor que as funcs
irão procurar e retornar atualizando x
:
> >> x = 42
> >> [ func () for func in funcs ]
[ 42 , 42 , 42 , 42 , 42 , 42 , 42 ]
x
naquele momento. funcs = []
for x in range ( 7 ):
def some_func ( x = x ):
return x
funcs . append ( some_func )
Saída:
> >> funcs_results = [ func () for func in funcs ]
> >> funcs_results
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
Não está mais usando x
no escopo global:
> >> inspect . getclosurevars ( funcs [ 0 ])
ClosureVars ( nonlocals = {}, globals = {}, builtins = {}, unbound = set ())
1.
> >> isinstance ( 3 , int )
True
> >> isinstance ( type , object )
True
> >> isinstance ( object , type )
True
Então, qual é a classe base "definitiva"? A propósito, há mais confusão
2.
> >> class A : pass
> >> isinstance ( A , A )
False
> >> isinstance ( type , type )
True
> >> isinstance ( object , object )
True
3.
> >> issubclass ( int , object )
True
> >> issubclass ( type , object )
True
> >> issubclass ( object , type )
False
type
é uma metaclasse em Python.object
em Python, que inclui classes e também seus objetos (instâncias).type
é a metaclasse da classe object
, e cada classe (incluindo type
) foi herdada direta ou indiretamente de object
.object
e type
. A confusão nos trechos acima surge porque estamos pensando nesses relacionamentos ( issubclass
e isinstance
) em termos de classes Python. A relação entre object
e type
não pode ser reproduzida em python puro. Para ser mais preciso, os seguintes relacionamentos não podem ser reproduzidos em Python puro,object
e type
(ambos sendo instâncias um do outro e também deles mesmos) existem em Python por causa de "trapaça" no nível de implementação.Saída:
> >> from collections . abc import Hashable
> >> issubclass ( list , object )
True
> >> issubclass ( object , Hashable )
True
> >> issubclass ( list , Hashable )
False
Esperava-se que os relacionamentos de subclasse fossem transitivos, certo? (ou seja, se A
é uma subclasse de B
e B
é uma subclasse de C
, A
deve ser uma subclasse de C
)
__subclasscheck__
arbitrário em uma metaclasse.issubclass(cls, Hashable)
é chamado, ele simplesmente procura pelo método " __hash__
" não-Falsey em cls
ou qualquer coisa de que ele herde.object
é hashável, mas list
não é hashável, ele quebra a relação de transitividade. class SomeClass :
def method ( self ):
pass
@ classmethod
def classm ( cls ):
pass
@ staticmethod
def staticm ():
pass
Saída:
> >> print ( SomeClass . method is SomeClass . method )
True
> >> print ( SomeClass . classm is SomeClass . classm )
False
> >> print ( SomeClass . classm == SomeClass . classm )
True
> >> print ( SomeClass . staticm is SomeClass . staticm )
True
Acessando classm
duas vezes, obtemos um objeto igual, mas não o mesmo ? Vamos ver o que acontece com instâncias de SomeClass
:
o1 = SomeClass ()
o2 = SomeClass ()
Saída:
> >> print ( o1 . method == o2 . method )
False
> >> print ( o1 . method == o1 . method )
True
> >> print ( o1 . method is o1 . method )
False
> >> print ( o1 . classm is o1 . classm )
False
> >> print ( o1 . classm == o1 . classm == o2 . classm == SomeClass . classm )
True
> >> print ( o1 . staticm is o1 . staticm is o2 . staticm is SomeClass . staticm )
True
Acessar classm
ou method
duas vezes cria objetos iguais, mas não iguais, para a mesma instância de SomeClass
.
self
como primeiro argumento, apesar de não passá-lo explicitamente). > >> o1 . method
< bound method SomeClass . method of < __main__ . SomeClass object at ... >>
o1.method is o1.method
nunca é verdadeiro. Entretanto, acessar funções como atributos de classe (em oposição a instância) não cria métodos; então SomeClass.method is SomeClass.method
é verdadeiro. > >> SomeClass . method
< function SomeClass . method at ... >
classmethod
transforma funções em métodos de classe. Métodos de classe são descritores que, quando acessados, criam um objeto de método que vincula a classe (tipo) do objeto, em vez do próprio objeto. > >> o1 . classm
< bound method SomeClass . classm of < class '__main__.SomeClass' >>
classmethod
s criarão um método também quando acessados como atributos de classe (nesse caso, eles vinculam a classe, não ao tipo dela). Então SomeClass.classm is SomeClass.classm
é falso. > >> SomeClass . classm
< bound method SomeClass . classm of < class '__main__.SomeClass' >>
o1.method == o1.method
é verdadeiro, embora não seja o mesmo objeto na memória.staticmethod
transforma funções em um descritor "no-op", que retorna a função como está. Nenhum objeto de método é criado, então a comparação com is
é verdadeira. > >> o1 . staticm
< function SomeClass . staticm at ... >
> >> SomeClass . staticm
< function SomeClass . staticm at ... >
self
afetado. O CPython 3.7 resolveu o problema introduzindo novos opcodes que lidam com a chamada de métodos sem criar objetos de método temporários. Isso é usado apenas quando a função acessada é realmente chamada, portanto os trechos aqui não são afetados e ainda geram métodos :) > >> all ([ True , True , True ])
True
> >> all ([ True , True , False ])
False
> >> all ([])
True
> >> all ([[]])
False
> >> all ([[[]]])
True
Por que essa alteração Verdadeiro-Falso?
A implementação de all
as funções é equivalente a
def all ( iterable ):
for element in iterable :
if not element :
return False
return True
all([])
retorna True
pois o iterável está vazio.
all([[]])
retorna False
porque o array passado tem um elemento, []
, e em python, uma lista vazia é falsa.
all([[[]]])
e variantes recursivas superiores são sempre True
. Isso ocorre porque o único elemento da matriz passada ( [[...]]
) não está mais vazio e as listas com valores são verdadeiras.
Saída (<3,6):
> >> def f ( x , y ,):
... print ( x , y )
...
> >> def g ( x = 4 , y = 5 ,):
... print ( x , y )
...
> >> def h ( x , ** kwargs ,):
File "<stdin>" , line 1
def h ( x , ** kwargs ,):
^
SyntaxError : invalid syntax
>> > def h ( * args ,):
File "<stdin>" , line 1
def h ( * args ,):
^
SyntaxError : invalid syntax
Saída:
> >> print ( " " " )
"
>> > print ( r""" )
"
>> > print ( r" " )
File "<stdin>" , line 1
print ( r" " )
^
SyntaxError : EOL while scanning string literal
>> > r''' == " \ '"
True
> >> "wt " f"
'wt"f'
r
), as barras invertidas passam como estão, juntamente com o comportamento de escapar do caractere seguinte. > >> r'wt"f' == 'wt \ "f'
True
> >> print ( repr ( r'wt"f' ))
'wt \ "f'
> >> print ( " n " )
> >> print ( r"\n" )
' \ n'
print(r"")
), a barra invertida escapou da aspa final, deixando o analisador sem uma aspa final (daí o SyntaxError
). É por isso que as barras invertidas não funcionam no final de uma string bruta. x = True
y = False
Saída:
> >> not x == y
True
> >> x == not y
File "<input>" , line 1
x == not y
^
SyntaxError : invalid syntax
==
tem precedência mais alta do que o operador not
em Python.not x == y
é equivalente a not (x == y)
que é equivalente a not (True == False)
finalmente avaliado como True
.x == not y
gera um SyntaxError
porque pode ser considerado equivalente a (x == not) y
e not x == (not y)
o que você poderia esperar à primeira vista.not
fizesse parte do operador not in
(porque ambos os operadores ==
e not in
têm a mesma precedência), mas depois de não conseguir encontrar um token in
após o token not
, ele gera um SyntaxError
.Saída:
> >> print ( 'wtfpython' '' )
wtfpython
> >> print ( "wtfpython" "" )
wtfpython
> >> # The following statements raise `SyntaxError`
>> > # print('''wtfpython')
>> > # print("""wtfpython")
File "<input>" , line 3
print ("""wtfpython")
^
SyntaxError: EOF while scanning triple-quoted string literal
>>> print("wtf" "python")
wtfpython
>>> print("wtf" "") # or "wtf"""
wtf
'''
e """
também são delimitadores de string em Python, o que causa um SyntaxError porque o interpretador Python estava esperando uma aspa tripla de terminação como delimitador ao verificar o literal de string tripla atualmente encontrado.1.
# A simple example to count the number of booleans and
# integers in an iterable of mixed data types.
mixed_list = [ False , 1.0 , "some_string" , 3 , True , [], False ]
integers_found_so_far = 0
booleans_found_so_far = 0
for item in mixed_list :
if isinstance ( item , int ):
integers_found_so_far += 1
elif isinstance ( item , bool ):
booleans_found_so_far += 1
Saída:
> >> integers_found_so_far
4
> >> booleans_found_so_far
0
2.
> >> some_bool = True
> >> "wtf" * some_bool
'wtf'
> >> some_bool = False
> >> "wtf" * some_bool
''
3.
def tell_truth ():
True = False
if True == False :
print ( "I have lost faith in truth!" )
Saída (<3.x):
> >> tell_truth ()
I have lost faith in truth !
bool
é uma subclasse de int
em Python
> >> issubclass ( bool , int )
True
> >> issubclass ( int , bool )
False
E assim, True
e False
são instâncias de int
> >> isinstance ( True , int )
True
> >> isinstance ( False , int )
True
O valor inteiro de True
é 1
e o de False
é 0
.
> >> int ( True )
1
> >> int ( False )
0
Veja esta resposta do StackOverflow para saber a lógica por trás disso.
Inicialmente, Python não tinha tipo bool
(as pessoas usavam 0 para falso e valores diferentes de zero como 1 para verdadeiro). True
, False
e um tipo bool
foram adicionados nas versões 2.x, mas, para compatibilidade com versões anteriores, True
e False
não puderam se tornar constantes. Elas eram apenas variáveis integradas e era possível reatribuí-las
O Python 3 era incompatível com versões anteriores, o problema foi finalmente corrigido e, portanto, o último trecho não funcionará com o Python 3.x!
1.
class A :
x = 1
class B ( A ):
pass
class C ( A ):
pass
Saída:
> >> A . x , B . x , C . x
( 1 , 1 , 1 )
>> > B . x = 2
>> > A . x , B . x , C . x
( 1 , 2 , 1 )
>> > A . x = 3
>> > A . x , B . x , C . x # C.x changed, but B.x didn't
( 3 , 2 , 3 )
>> > a = A ()
>> > a . x , A . x
( 3 , 3 )
>> > a . x + = 1
>> > a . x , A . x
( 4 , 3 )
2.
class SomeClass :
some_var = 15
some_list = [ 5 ]
another_list = [ 5 ]
def __init__ ( self , x ):
self . some_var = x + 1
self . some_list = self . some_list + [ x ]
self . another_list += [ x ]
Saída:
> >> some_obj = SomeClass ( 420 )
> >> some_obj . some_list
[ 5 , 420 ]
> >> some_obj . another_list
[ 5 , 420 ]
> >> another_obj = SomeClass ( 111 )
> >> another_obj . some_list
[ 5 , 111 ]
> >> another_obj . another_list
[ 5 , 420 , 111 ]
> >> another_obj . another_list is SomeClass . another_list
True
> >> another_obj . another_list is some_obj . another_list
True
+=
modifica o objeto mutável no local sem criar um novo objeto. Portanto, alterar o atributo de uma instância afeta as outras instâncias e também o atributo de classe. some_iterable = ( 'a' , 'b' )
def some_func ( val ):
return "something"
Saída (<= 3.7.x):
> >> [ x for x in some_iterable ]
[ 'a' , 'b' ]
> >> [( yield x ) for x in some_iterable ]
< generator object < listcomp > at 0x7f70b0a4ad58 >
> >> list ([( yield x ) for x in some_iterable ])
[ 'a' , 'b' ]
> >> list (( yield x ) for x in some_iterable )
[ 'a' , None , 'b' , None ]
> >> list ( some_func (( yield x )) for x in some_iterable )
[ 'a' , 'something' , 'b' , 'something' ]
yield
do CPython em geradores e compreensões.yield
dentro da compreensão da lista e lançará um SyntaxError
.1.
def some_func ( x ):
if x == 3 :
return [ "wtf" ]
else :
yield from range ( x )
Saída (> 3,3):
> >> list ( some_func ( 3 ))
[]
Para onde foi o "wtf"
? É devido a algum efeito especial do yield from
? Vamos validar isso,
2.
def some_func ( x ):
if x == 3 :
return [ "wtf" ]
else :
for i in range ( x ):
yield i
Saída:
> >> list ( some_func ( 3 ))
[]
O mesmo resultado, isso também não funcionou.
return
com valores dentro de geradores (Ver PEP380). Os documentos oficiais dizem que,"...
return expr
em um gerador faz com queStopIteration(expr)
seja gerado na saída do gerador."
No caso de some_func(3)
, StopIteration
é gerado no início por causa da instrução return
. A exceção StopIteration
é automaticamente capturada dentro do wrapper list(...)
e do loop for
. Portanto, os dois trechos acima resultam em uma lista vazia.
Para obter ["wtf"]
do gerador some_func
precisamos capturar a exceção StopIteration
,
try :
next ( some_func ( 3 ))
except StopIteration as e :
some_string = e . value
> >> some_string
[ "wtf" ]
1.
a = float ( 'inf' )
b = float ( 'nan' )
c = float ( '-iNf' ) # These strings are case-insensitive
d = float ( 'nan' )
Saída:
> >> a
inf
> >> b
nan
> >> c
- inf
> >> float ( 'some_other_string' )
ValueError : could not convert string to float : some_other_string
> >> a == - c # inf==inf
True
> >> None == None # None == None
True
> >> b == d # but nan!=nan
False
> >> 50 / a
0.0
> >> a / a
nan
> >> 23 + b
nan
2.
> >> x = float ( 'nan' )
> >> y = x / x
> >> y is y # identity holds
True
> >> y == y # equality fails of y
False
> >> [ y ] == [ y ] # but the equality succeeds for the list containing y
True
'inf'
e 'nan'
são strings especiais (sem distinção entre maiúsculas e minúsculas), que, quando explicitamente convertidas para o tipo float
, são usadas para representar "infinito" matemático e "não um número", respectivamente.
Visto que, de acordo com os padrões IEEE NaN != NaN
, obedecer a esta regra quebra a suposição de reflexividade de um elemento de coleção em Python, ou seja, se x
faz parte de uma coleção como list
, as implementações como comparação são baseadas na suposição de que x == x
. Devido a essa suposição, a identidade é comparada primeiro (já que é mais rápida) ao comparar dois elementos, e os valores são comparados somente quando as identidades são incompatíveis. O trecho a seguir tornará as coisas mais claras,
> >> x = float ( 'nan' )
> >> x == x , [ x ] == [ x ]
( False , True )
> >> y = float ( 'nan' )
> >> y == y , [ y ] == [ y ]
( False , True )
> >> x == y , [ x ] == [ y ]
( False , False )
Como as identidades de x
e y
são diferentes, são considerados os valores, que também são diferentes; portanto, a comparação retorna False
desta vez.
Leitura interessante: Reflexividade e outros pilares da civilização
Isso pode parecer trivial se você souber como as referências funcionam em Python.
some_tuple = ( "A" , "tuple" , "with" , "values" )
another_tuple = ([ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 ])
Saída:
> >> some_tuple [ 2 ] = "change this"
TypeError : 'tuple' object does not support item assignment
> >> another_tuple [ 2 ]. append ( 1000 ) #This throws no error
> >> another_tuple
([ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 , 1000 ])
> >> another_tuple [ 2 ] += [ 99 , 999 ]
TypeError : 'tuple' object does not support item assignment
> >> another_tuple
([ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 , 1000 , 99 , 999 ])
Mas eu pensei que as tuplas eram imutáveis ...
Citando https://docs.python.org/3/reference/datamodel.html
Sequências imutáveis Um objeto de um tipo de sequência imutável não pode ser alterado depois de criado. (Se o objeto contiver referências a outros objetos, esses outros objetos poderão ser mutáveis e modificados; no entanto, a coleção de objetos referenciados diretamente por um objeto imutável não pode ser alterada.)
O operador +=
altera a lista no local. A atribuição do item não funciona, mas quando ocorre a exceção, o item já foi alterado.
Há também uma explicação no FAQ oficial do Python.
e = 7
try :
raise Exception ()
except Exception as e :
pass
Saída (Python 2.x):
> >> print ( e )
# prints nothing
Saída (Python 3.x):
> >> print ( e )
NameError : name 'e' is not defined
Fonte: https://docs.python.org/3/reference/compound_stmts.html#except
Quando uma exceção foi atribuída usando as
destino, ela é limpa no final da cláusula except
. Isto é como se
except E as N :
foo
foi traduzido para
except E as N :
try :
foo
finally :
del N
Isso significa que a exceção deve ser atribuída a um nome diferente para poder fazer referência a ela após a cláusula except. As exceções são limpas porque, com o traceback anexado a elas, elas formam um ciclo de referência com o quadro de pilha, mantendo todos os locais nesse quadro vivos até que ocorra a próxima coleta de lixo.
As cláusulas não têm escopo em Python. Tudo no exemplo está presente no mesmo escopo, e a variável e
foi removida devido à execução da cláusula except
. O mesmo não acontece com funções que possuem escopos internos separados. O exemplo abaixo ilustra o seguinte:
def f ( x ):
del ( x )
print ( x )
x = 5
y = [ 5 , 4 , 3 ]
Saída:
> >> f ( x )
UnboundLocalError : local variable 'x' referenced before assignment
>> > f ( y )
UnboundLocalError : local variable 'x' referenced before assignment
>> > x
5
> >> y
[ 5 , 4 , 3 ]
No Python 2.x, o nome da variável e
é atribuído à instância Exception()
; portanto, quando você tenta imprimir, ele não imprime nada.
Saída (python 2.x):
> >> e
Exception ()
> >> print e
# Nothing is printed!
class SomeClass ( str ):
pass
some_dict = { 's' : 42 }
Saída:
> >> type ( list ( some_dict . keys ())[ 0 ])
str
> >> s = SomeClass ( 's' )
> >> some_dict [ s ] = 40
> >> some_dict # expected: Two different keys-value pairs
{ 's' : 40 }
> >> type ( list ( some_dict . keys ())[ 0 ])
str
Tanto o objeto s
quanto a string "s"
hash para o mesmo valor porque SomeClass
herda o método __hash__
da classe str
.
SomeClass("s") == "s"
Avalia como True
porque SomeClass
também herda o método __eq__
da classe str
.
Como ambos os objetos hash com o mesmo valor e são iguais, eles são representados pela mesma chave no dicionário.
Para o comportamento desejado, podemos redefinir o método __eq__
em SomeClass
class SomeClass ( str ):
def __eq__ ( self , other ):
return (
type ( self ) is SomeClass
and type ( other ) is SomeClass
and super (). __eq__ ( other )
)
# When we define a custom __eq__, Python stops automatically inheriting the
# __hash__ method, so we need to define it as well
__hash__ = str . __hash__
some_dict = { 's' : 42 }
Saída:
> >> s = SomeClass ( 's' )
> >> some_dict [ s ] = 40
> >> some_dict
{ 's' : 40 , 's' : 42 }
> >> keys = list ( some_dict . keys ())
> >> type ( keys [ 0 ]), type ( keys [ 1 ])
( __main__ . SomeClass , str )
a , b = a [ b ] = {}, 5
Saída:
> >> a
{ 5 : ({...}, 5 )}
(target_list "=")+ (expression_list | yield_expression)
Uma instrução de atribuição avalia a lista de expressão (lembre-se de que essa pode ser uma única expressão ou uma lista separada por vírgula, este último produzindo uma tupla) e atribui o único objeto resultante a cada uma das listas de destino, da esquerda para a direita.
O +
in (target_list "=")+
significa que pode haver uma ou mais listas de destino. Nesse caso, as listas de destino são a, b
e a[b]
(note que a lista de expressão é exatamente uma, que no nosso caso é {}, 5
).
Depois que a lista de expressão é avaliada, seu valor é descompactado nas listas de destino da esquerda para a direita . Então, no nosso caso, primeiro a {}, 5
tupla é descompactada para a, b
e agora temos a = {}
e b = 5
.
a
agora é atribuído a {}
, que é um objeto mutável.
A segunda lista de destino é a[b]
(você pode esperar que isso faça um erro porque a
e b
não foram definidos nas declarações antes. Mas lembre -se, acabamos de atribuir a
a {}
e b
a 5
).
Agora, estamos definindo a chave 5
no dicionário para a tupla ({}, 5)
criando uma referência circular (o {...}
na saída refere -se ao mesmo objeto que a
já está referenciando). Outro exemplo mais simples de referência circular pode ser
> >> some_list = some_list [ 0 ] = [ 0 ]
> >> some_list
[[...]]
> >> some_list [ 0 ]
[[...]]
> >> some_list is some_list [ 0 ]
True
> >> some_list [ 0 ][ 0 ][ 0 ][ 0 ][ 0 ][ 0 ] == some_list
True
Semelhante é o caso do nosso exemplo ( a[b][0]
é o mesmo objeto que a
)
Então, para resumir, você pode dividir o exemplo para
a , b = {}, 5
a [ b ] = a , b
E a referência circular pode ser justificada pelo fato de que a[b][0]
é o mesmo objeto que a
> >> a [ b ][ 0 ] is a
True
> >> # Python 3.10.6
>> > int ( "2" * 5432 )
> >> # Python 3.10.8
>> > int ( "2" * 5432 )
Saída:
> >> # Python 3.10.6
222222222222222222222222222222222222222222222222222222222222222. ..
>> > # Python 3.10.8
Traceback ( most recent call last ):
...
ValueError : Exceeds the limit ( 4300 ) for integer string conversion :
value has 5432 digits ; use sys . set_int_max_str_digits ()
to increase the limit .
Esta chamada para int()
funciona bem no Python 3.10.6 e levanta um ValueError no Python 3.10.8. Observe que o Python ainda pode funcionar com grandes números inteiros. O erro é aumentado apenas ao converter entre números inteiros e strings.
Felizmente, você pode aumentar o limite para o número permitido de dígitos quando espera que uma operação o exceda. Para fazer isso, você pode usar um dos seguintes:
Verifique a documentação para obter mais detalhes sobre a alteração do limite padrão se você espera que seu código exceda esse valor.
x = { 0 : None }
for i in x :
del x [ i ]
x [ i + 1 ] = None
print ( i )
Saída (Python 2.7- Python 3.5):
0
1
2
3
4
5
6
7
Sim, ele funciona exatamente oito vezes e para.
RuntimeError: dictionary keys changed during iteration
se tentar fazer isso.del
class SomeClass :
def __del__ ( self ):
print ( "Deleted!" )
Saída: 1.
> >> x = SomeClass ()
> >> y = x
> >> del x # this should print "Deleted!"
> >> del y
Deleted !
Ufa, finalmente excluída. Você deve ter adivinhado o que salvou __del__
de ser chamado em nossa primeira tentativa de excluir x
. Vamos adicionar mais reviravoltas ao exemplo.
2.
> >> x = SomeClass ()
> >> y = x
> >> del x
> >> y # check if y exists
< __main__ . SomeClass instance at 0x7f98a1a67fc8 >
> >> del y # Like previously, this should print "Deleted!"
> >> globals () # oh, it didn't. Let's check all our global variables and confirm
Deleted !
{ '__builtins__' : < module '__builtin__' ( built - in ) > , 'SomeClass' : < class __main__ . SomeClass at 0x7f98a1a5f668 > , '__package__' : None , '__name__' : '__main__' , '__doc__' : None }
Ok, agora está excluído?
del x
não liga diretamente x.__del__()
.del x
é encontrado, o Python exclui o nome x
do escopo atual e diminui em 1 a contagem de referência do objeto x
referenciado. __del__()
é chamado apenas quando a contagem de referência do objeto atinge zero.__del__()
não foi chamado porque a declaração anterior ( >>> y
) no intérprete interativo criou outra referência ao mesmo objeto (especificamente, a variável _
que faz referência ao valor do resultado do último None
Expressão no Repl), impedindo assim que a contagem de referência atingisse zero quando del y
foi encontrado.globals
(ou realmente, executar qualquer coisa que tenha um resultado não None
) fez com que _
faça referência ao novo resultado, retirando a referência existente. Agora a contagem de referência chegou a 0 e podemos ver "Excluído!" sendo impresso (finalmente!).1.
a = 1
def some_func ():
return a
def another_func ():
a += 1
return a
2.
def some_closure_func ():
a = 1
def some_inner_func ():
return a
return some_inner_func ()
def another_closure_func ():
a = 1
def another_inner_func ():
a += 1
return a
return another_inner_func ()
Saída:
> >> some_func ()
1
> >> another_func ()
UnboundLocalError : local variable 'a' referenced before assignment
>> > some_closure_func ()
1
> >> another_closure_func ()
UnboundLocalError : local variable 'a' referenced before assignment
Quando você faz uma tarefa a uma variável no escopo, ela se torna local para esse escopo. Portanto, a
se torna local para o escopo de another_func
, mas não foi inicializado anteriormente no mesmo escopo, o que lança um erro.
Para modificar a variável de escopo externo a
em another_func
, precisamos usar a palavra -chave global
.
def another_func ()
global a
a += 1
return a
Saída:
> >> another_func ()
2
Em another_closure_func
, a
se torna local para o escopo de another_inner_func
, mas não foi inicializado anteriormente no mesmo escopo, e é por isso que lança um erro.
Para modificar a variável de escopo externo a
em another_inner_func
, use a palavra -chave nonlocal
. A instrução não -local é usada para se referir a variáveis definidas no escopo externo mais próximo (excluindo o global).
def another_func ():
a = 1
def another_inner_func ():
nonlocal a
a += 1
return a
return another_inner_func ()
Saída:
> >> another_func ()
2
As palavras -chave global
e nonlocal
dizem ao intérprete Python para não declarar novas variáveis e procurar -as nos escopos externos correspondentes.
Leia este curto, mas um guia incrível para saber mais sobre como os espaços para nome e a resolução do escopo funcionam no Python.
list_1 = [ 1 , 2 , 3 , 4 ]
list_2 = [ 1 , 2 , 3 , 4 ]
list_3 = [ 1 , 2 , 3 , 4 ]
list_4 = [ 1 , 2 , 3 , 4 ]
for idx , item in enumerate ( list_1 ):
del item
for idx , item in enumerate ( list_2 ):
list_2 . remove ( item )
for idx , item in enumerate ( list_3 [:]):
list_3 . remove ( item )
for idx , item in enumerate ( list_4 ):
list_4 . pop ( idx )
Saída:
> >> list_1
[ 1 , 2 , 3 , 4 ]
> >> list_2
[ 2 , 4 ]
> >> list_3
[]
> >> list_4
[ 2 , 4 ]
Você consegue adivinhar por que a saída é [2, 4]
?
Nunca é uma boa ideia mudar o objeto que você está iterar. A maneira correta de fazer isso é iterar sobre uma cópia do objeto, e list_3[:]
faz exatamente isso.
> >> some_list = [ 1 , 2 , 3 , 4 ]
> >> id ( some_list )
139798789457608
> >> id ( some_list [:]) # Notice that python creates new object for sliced list.
139798779601192
Diferença entre del
, remove
e pop
:
del var_name
apenas remove a ligação do var_name
do espaço para nome local ou global (é por isso que o list_1
não é afetado).remove
remove o primeiro valor correspondente, não um índice específico, aumenta ValueError
se o valor não for encontrado.pop
remove o elemento em um índice específico e o retorna, eleva IndexError
se um índice inválido for especificado. Por que a saída é [2, 4]
?
1
da list_2
ou list_4
, o conteúdo das listas agora é [2, 3, 4]
. Os elementos restantes são deslocados para baixo, ou seja, 2
está no índice 0 e 3
está no índice 1. Como a próxima iteração analisará o índice 1 (que é o 3
), os 2
são totalmente ignorados. Uma coisa semelhante acontecerá com cada elemento alternativo na sequência da lista. > >> numbers = list ( range ( 7 ))
> >> numbers
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
> >> first_three , remaining = numbers [: 3 ], numbers [ 3 :]
> >> first_three , remaining
([ 0 , 1 , 2 ], [ 3 , 4 , 5 , 6 ])
>> > numbers_iter = iter ( numbers )
>> > list ( zip ( numbers_iter , first_three ))
[( 0 , 0 ), ( 1 , 1 ), ( 2 , 2 )]
# so far so good, let's zip the remaining
>> > list ( zip ( numbers_iter , remaining ))
[( 4 , 3 ), ( 5 , 4 ), ( 6 , 5 )]
Para onde o elemento 3
foi da lista numbers
?
def zip ( * iterables ):
sentinel = object ()
iterators = [ iter ( it ) for it in iterables ]
while iterators :
result = []
for it in iterators :
elem = next ( it , sentinel )
if elem is sentinel : return
result . append ( elem )
yield tuple ( result )
result
chamando a next
função neles e para sempre que qualquer um dos iteráveis estiver esgotado.result
são descartados. Foi o que aconteceu com 3
no numbers_iter
.zip
seria, > >> numbers = list ( range ( 7 ))
> >> numbers_iter = iter ( numbers )
> >> list ( zip ( first_three , numbers_iter ))
[( 0 , 0 ), ( 1 , 1 ), ( 2 , 2 )]
> >> list ( zip ( remaining , numbers_iter ))
[( 3 , 3 ), ( 4 , 4 ), ( 5 , 5 ), ( 6 , 6 )]
1.
for x in range ( 7 ):
if x == 6 :
print ( x , ': for x inside loop' )
print ( x , ': x in global' )
Saída:
6 : for x inside loop
6 : x in global
Mas x
nunca foi definido fora do escopo do loop ...
2.
# This time let's initialize x first
x = - 1
for x in range ( 7 ):
if x == 6 :
print ( x , ': for x inside loop' )
print ( x , ': x in global' )
Saída:
6 : for x inside loop
6 : x in global
3.
Saída (python 2.x):
> >> x = 1
> >> print ([ x for x in range ( 5 )])
[ 0 , 1 , 2 , 3 , 4 ]
> >> print ( x )
4
Saída (python 3.x):
> >> x = 1
> >> print ([ x for x in range ( 5 )])
[ 0 , 1 , 2 , 3 , 4 ]
> >> print ( x )
1
Em Python, os loops usam o escopo em que existem e deixam para trás o loop definido. Isso também se aplica se definirmos explicitamente a variável de loop para o espaço para nome global antes. Nesse caso, ele rebatará a variável existente.
As diferenças na saída do Python 2.x e Python 3.x Intesters for List Compreension Exemplo podem ser explicadas seguindo a mudança documentada no que há de novo no Python 3.0 Changelog:
"As compreensões da lista não suportam mais a forma sintática
[... for var in item1, item2, ...]
. Use[... for var in (item1, item2, ...)]
. As compreensões têm semântica diferente: elas estão mais próximas do açúcar sintático para uma expressão de gerador dentro de um construtorlist()
e, em particular, as variáveis de controle de loop não são mais vazadas para o escopo circundante ".
def some_func ( default_arg = []):
default_arg . append ( "some_string" )
return default_arg
Saída:
> >> some_func ()
[ 'some_string' ]
> >> some_func ()
[ 'some_string' , 'some_string' ]
> >> some_func ([])
[ 'some_string' ]
> >> some_func ()
[ 'some_string' , 'some_string' , 'some_string' ]
Os argumentos mutáveis padrão das funções no Python não são realmente inicializados toda vez que você chama a função. Em vez disso, o valor recentemente atribuído a eles é usado como o valor padrão. Quando passamos explicitamente []
para some_func
como o argumento, o valor padrão da variável default_arg
não foi usado, portanto a função retornou conforme o esperado.
def some_func ( default_arg = []):
default_arg . append ( "some_string" )
return default_arg
Saída:
> >> some_func . __defaults__ #This will show the default argument values for the function
([],)
> >> some_func ()
> >> some_func . __defaults__
([ 'some_string' ],)
> >> some_func ()
> >> some_func . __defaults__
([ 'some_string' , 'some_string' ],)
> >> some_func ([])
> >> some_func . __defaults__
([ 'some_string' , 'some_string' ],)
Uma prática comum para evitar erros devido a argumentos mutáveis é atribuir None
como o valor padrão e verifique posteriormente se algum valor é passado para a função correspondente a esse argumento. Exemplo:
def some_func ( default_arg = None ):
if default_arg is None :
default_arg = []
default_arg . append ( "some_string" )
return default_arg
some_list = [ 1 , 2 , 3 ]
try :
# This should raise an ``IndexError``
print ( some_list [ 4 ])
except IndexError , ValueError :
print ( "Caught!" )
try :
# This should raise a ``ValueError``
some_list . remove ( 4 )
except IndexError , ValueError :
print ( "Caught again!" )
Saída (python 2.x):
Caught !
ValueError : list . remove ( x ): x not in list
Saída (python 3.x):
File "<input>" , line 3
except IndexError , ValueError :
^
SyntaxError : invalid syntax
Para adicionar várias exceções à cláusula, exceto, você precisa passá -las como uma tupla entre parênteses como o primeiro argumento. O segundo argumento é um nome opcional, que, quando fornecido, vinculará a instância de exceção que foi levantada. Exemplo,
some_list = [ 1 , 2 , 3 ]
try :
# This should raise a ``ValueError``
some_list . remove ( 4 )
except ( IndexError , ValueError ), e :
print ( "Caught again!" )
print ( e )
Saída (python 2.x):
Caught again!
list.remove(x): x not in list
Saída (python 3.x):
File "<input>" , line 4
except ( IndexError , ValueError ), e :
^
IndentationError : unindent does not match any outer indentation level
A separação da exceção da variável com uma vírgula é preterida e não funciona no Python 3; A maneira correta é usar as
. Exemplo,
some_list = [ 1 , 2 , 3 ]
try :
some_list . remove ( 4 )
except ( IndexError , ValueError ) as e :
print ( "Caught again!" )
print ( e )
Saída:
Caught again!
list.remove(x): x not in list
1.
a = [ 1 , 2 , 3 , 4 ]
b = a
a = a + [ 5 , 6 , 7 , 8 ]
Saída:
> >> a
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
> >> b
[ 1 , 2 , 3 , 4 ]
2.
a = [ 1 , 2 , 3 , 4 ]
b = a
a += [ 5 , 6 , 7 , 8 ]
Saída:
> >> a
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
> >> b
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
a += b
nem sempre se comportará da mesma maneira que a = a + b
. As classes podem implementar o op=
Operadores de maneira diferente e as listas fazem isso.
A expressão a = a + [5,6,7,8]
gera uma nova lista e define a
referência de uma nova lista, deixando b
inalterado.
A expressão a += [5,6,7,8]
é na verdade mapeada para uma função "estender" que opera na lista de modo que a
e b
ainda apontem para a mesma lista que foi modificada no local.
1.
x = 5
class SomeClass :
x = 17
y = ( x for i in range ( 10 ))
Saída:
> >> list ( SomeClass . y )[ 0 ]
5
2.
x = 5
class SomeClass :
x = 17
y = [ x for i in range ( 10 )]
Saída (python 2.x):
> >> SomeClass . y [ 0 ]
17
Saída (python 3.x):
> >> SomeClass . y [ 0 ]
5
Vamos implementar uma função ingênua para obter o elemento intermediário de uma lista:
def get_middle ( some_list ):
mid_index = round ( len ( some_list ) / 2 )
return some_list [ mid_index - 1 ]
Python 3.x:
> >> get_middle ([ 1 ]) # looks good
1
> >> get_middle ([ 1 , 2 , 3 ]) # looks good
2
> >> get_middle ([ 1 , 2 , 3 , 4 , 5 ]) # huh?
2
> >> len ([ 1 , 2 , 3 , 4 , 5 ]) / 2 # good
2.5
> >> round ( len ([ 1 , 2 , 3 , 4 , 5 ]) / 2 ) # why?
2
Parece que Python arredondou 2,5 a 2.
round()
usa o arredondamento do banqueiro, onde as frações 0,5 são arredondadas para o número par mais próximo: > >> round ( 0.5 )
0
> >> round ( 1.5 )
2
> >> round ( 2.5 )
2
> >> import numpy # numpy does the same
> >> numpy . round ( 0.5 )
0.0
> >> numpy . round ( 1.5 )
2.0
> >> numpy . round ( 2.5 )
2.0
get_middle([1])
retornou apenas 1 porque o índice foi round(0.5) - 1 = 0 - 1 = -1
, retornando o último elemento na lista.Eu não conheci nem uma única experiência em pythonist até a data que não encontrou um ou mais dos seguintes cenários,
1.
x , y = ( 0 , 1 ) if True else None , None
Saída:
> >> x , y # expected (0, 1)
(( 0 , 1 ), None )
2.
t = ( 'one' , 'two' )
for i in t :
print ( i )
t = ( 'one' )
for i in t :
print ( i )
t = ()
print ( t )
Saída:
one
two
o
n
e
tuple ()
3.
ten_words_list = [
"some",
"very",
"big",
"list",
"that"
"consists",
"of",
"exactly",
"ten",
"words"
]
Saída
> >> len ( ten_words_list )
9
4. Não afirmando fortemente o suficiente
a = "python"
b = "javascript"
Saída:
# An assert statement with an assertion failure message.
> >> assert ( a == b , "Both languages are different" )
# No AssertionError is raised
5.
some_list = [ 1 , 2 , 3 ]
some_dict = {
"key_1" : 1 ,
"key_2" : 2 ,
"key_3" : 3
}
some_list = some_list . append ( 4 )
some_dict = some_dict . update ({ "key_4" : 4 })
Saída:
> >> print ( some_list )
None
> >> print ( some_dict )
None
6.
def some_recursive_func ( a ):
if a [ 0 ] == 0 :
return
a [ 0 ] -= 1
some_recursive_func ( a )
return a
def similar_recursive_func ( a ):
if a == 0 :
return a
a -= 1
similar_recursive_func ( a )
return a
Saída:
> >> some_recursive_func ([ 5 , 0 ])
[ 0 , 0 ]
> >> similar_recursive_func ( 5 )
4
Para 1, a declaração correta para o comportamento esperado é x, y = (0, 1) if True else (None, None)
.
Para 2, a afirmação correta para o comportamento esperado é t = ('one',)
ou t = 'one',
(falta de vírgula), caso contrário, o intérprete considera t
ser um str
e itera sobre o caráter dele por caráter.
()
é um token especial e denota tuple
vazia.
Em 3, como você já descobriu, há uma vírgula faltando após o 5º elemento ( "that"
) na lista. Então, por concatenação literal de cordas implícitas,
> >> ten_words_list
[ 'some' , 'very' , 'big' , 'list' , 'thatconsists' , 'of' , 'exactly' , 'ten' , 'words' ]
Nenhum AssertionError
foi criado no 4º trecho porque, em vez de afirmar a expressão individual a == b
, estamos afirmando a tupla inteira. O snippet seguinte vai esclarecer as coisas,
> >> a = "python"
> >> b = "javascript"
> >> assert a == b
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AssertionError
>> > assert ( a == b , "Values are not equal" )
< stdin > : 1 : SyntaxWarning : assertion is always true , perhaps remove parentheses ?
>> > assert a == b , "Values are not equal"
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AssertionError : Values are not equal
Quanto ao quinto snippet, a maioria dos métodos que modificam os itens de objetos de sequência/mapeamento como list.append
, dict.update
, list.sort
, etc. Modifique os objetos no local e retorne None
. A lógica por trás disso é melhorar o desempenho, evitando fazer uma cópia do objeto se a operação puder ser feita no local (referido a partir daqui).
O último deve ser bastante óbvio, o objeto mutável (como list
) pode ser alterado na função, e a reatribuição de um imutável ( a -= 1
) não é uma alteração do valor.
Estar ciente desses nitpicks pode economizar horas de esforço de depuração a longo prazo.
> >> 'a' . split ()
[ 'a' ]
# is same as
> >> 'a' . split ( ' ' )
[ 'a' ]
# but
> >> len ( '' . split ())
0
# isn't the same as
> >> len ( '' . split ( ' ' ))
1
' '
, mas de acordo com os documentosSe o SEP não for especificado ou
None
for, um algoritmo de divisão diferente será aplicado: as execuções do espaço em branco consecutivas são consideradas um único separador, e o resultado não conterá seqüências vazias no início ou no final se a string tiver um espaço em branco de liderança ou à direita. Consequentemente, dividir uma corda vazia ou uma string que consiste em apenas um espaço em branco com um separador nenhum retorna[]
. Se o SEP for dado, os delimitadores consecutivos não são agrupados e são considerados delimitar strings vazios (por exemplo,'1,,2'.split(',')
retorna['1', '', '2']
). Dividir uma corda vazia com um separador especificado retorna['']
.
> >> ' a ' . split ( ' ' )
[ '' , 'a' , '' ]
> >> ' a ' . split ()
[ 'a' ]
> >> '' . split ( ' ' )
[ '' ]
# File: module.py
def some_weird_name_func_ ():
print ( "works!" )
def _another_weird_name_func ():
print ( "works!" )
Saída
> >> from module import *
> >> some_weird_name_func_ ()
"works!"
> >> _another_weird_name_func ()
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name '_another_weird_name_func' is not defined
Muitas vezes, é aconselhável não usar importações de curinga. A primeira razão óbvia para isso é que, nas importações de curinga, os nomes com um sublinhado líder não são importados. Isso pode levar a erros durante o tempo de execução.
Se tivéssemos usado from ... import a, b, c
Sintaxe, o NameError
acima não teria ocorrido.
> >> from module import some_weird_name_func_ , _another_weird_name_func
> >> _another_weird_name_func ()
works !
Se você realmente deseja usar as importações de curinga, precisará definir a lista __all__
no seu módulo que conterá uma lista de objetos públicos que estarão disponíveis quando fizermos importações de curinga.
__all__ = [ '_another_weird_name_func' ]
def some_weird_name_func_ ():
print ( "works!" )
def _another_weird_name_func ():
print ( "works!" )
Saída
> >> _another_weird_name_func ()
"works!"
> >> some_weird_name_func_ ()
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name 'some_weird_name_func_' is not defined
> >> x = 7 , 8 , 9
> >> sorted ( x ) == x
False
> >> sorted ( x ) == sorted ( x )
True
> >> y = reversed ( x )
> >> sorted ( y ) == sorted ( y )
False
O método sorted
sempre retorna uma lista e comparar listas e tuplas sempre retorna False
no Python.
> >> [] == tuple ()
False
> >> x = 7 , 8 , 9
> >> type ( x ), type ( sorted ( x ))
( tuple , list )
Ao contrário de sorted
, o método reversed
retorna um iterador. Por que? Como a classificação exige que o iterador seja modificado no local ou use um contêiner extra (uma lista), enquanto a reversão pode simplesmente funcionar, iterando do último índice para o primeiro.
Portanto, durante a comparação sorted(y) == sorted(y)
, a primeira chamada para sorted()
consumirá o iterador y
, e a próxima chamada retornará apenas uma lista vazia.
> >> x = 7 , 8 , 9
> >> y = reversed ( x )
> >> sorted ( y ), sorted ( y )
([ 7 , 8 , 9 ], [])
from datetime import datetime
midnight = datetime ( 2018 , 1 , 1 , 0 , 0 )
midnight_time = midnight . time ()
noon = datetime ( 2018 , 1 , 1 , 12 , 0 )
noon_time = noon . time ()
if midnight_time :
print ( "Time at midnight is" , midnight_time )
if noon_time :
print ( "Time at noon is" , noon_time )
Saída (<3,5):
( 'Time at noon is' , datetime . time ( 12 , 0 ))
A meia -noite não está impressa.
Antes do Python 3.5, o valor booleano do objeto datetime.time
foi considerado False
se representasse meia -noite no UTC. É propenso a erros ao usar o if obj:
Sintaxe para verificar se o obj
é nulo ou algum equivalente a "vazio".
Esta seção contém algumas coisas menos conhecidas e interessantes sobre o Python que a maioria dos iniciantes como eu não tem conhecimento (bem, não mais).
Bem, aqui está
import antigravity
Saída: sshh ... é um super-secreto.
antigravity
é um dos poucos ovos de Páscoa lançados pelos desenvolvedores de Python.import antigravity
abre um navegador da Web apontando para o clássico quadrinho XKCD sobre Python.goto
, mas por quê? from goto import goto , label
for i in range ( 9 ):
for j in range ( 9 ):
for k in range ( 9 ):
print ( "I am trapped, please rescue!" )
if k == 2 :
goto . breakout # breaking out from a deeply nested loop
label . breakout
print ( "Freedom!" )
Saída (Python 2.3):
I am trapped , please rescue !
I am trapped , please rescue !
Freedom !
goto
em Python foi anunciada como uma piada de April Fool em 1º de abril de 2004.goto
não está presente no Python.Se você é uma das pessoas que não gosta de usar o espaço em branco em python para denotar escopos, você pode usar o estilo C {} importando,
from __future__ import braces
Saída:
File "some_file.py" , line 1
from __future__ import braces
SyntaxError : not a chance
Aparelho ortodôntico? Sem chance! Se você acha que é decepcionante, use Java. Ok, outra coisa surpreendente, você pode encontrar onde está o SyntaxError
criado no código do módulo __future__
?
__future__
é normalmente usado para fornecer recursos de versões futuras do Python. O "futuro" nesse contexto específico é irônico.future.c
.future.c
antes de tratá -lo como uma declaração de importação normal.Saída (Python 3.x)
> >> from __future__ import barry_as_FLUFL
> >> "Ruby" != "Python" # there's no doubt about it
File "some_file.py" , line 1
"Ruby" != "Python"
^
SyntaxError : invalid syntax
>> > "Ruby" <> "Python"
True
Lá vamos nós.
Isso é relevante para o PEP-401 lançado em 1º de abril de 2009 (agora você sabe, o que isso significa).
Citando do PEP-401
Reconheceu que o operador de desigualdade!
Havia mais coisas que o tio Barry teve que compartilhar no PEP; Você pode lê -los aqui.
Funciona bem em um ambiente interativo, mas elevará um SyntaxError
quando você executar via arquivo python (consulte este problema). No entanto, você pode envolver a declaração dentro de uma eval
ou compile
para funcionar,
from __future__ import barry_as_FLUFL
print ( eval ( '"Ruby" <> "Python"' ))
import this
Espere, o que é isso ? this
é amor ❤️
Saída:
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
É o zen do Python!
> >> love = this
> >> this is love
True
> >> love is True
False
> >> love is False
False
> >> love is not True or False
True
> >> love is not True or False ; love is love # Love is complicated
True
this
módulo em Python é um ovo de Páscoa para o Zen do Python (PEP 20).love is not True or False; love is love
, irônico, mas é auto-explicativo (se não, veja os exemplos relacionados a is
e is not
operadores). A cláusula else
para loops. Um exemplo típico pode ser:
def does_exists_num ( l , to_find ):
for num in l :
if num == to_find :
print ( "Exists!" )
break
else :
print ( "Does not exist" )
Saída:
> >> some_list = [ 1 , 2 , 3 , 4 , 5 ]
> >> does_exists_num ( some_list , 4 )
Exists !
>> > does_exists_num ( some_list , - 1 )
Does not exist
A cláusula else
no manuseio de exceções. Um exemplo,
try :
pass
except :
print ( "Exception occurred!!!" )
else :
print ( "Try block executed successfully..." )
Saída:
Try block executed successfully ...
else
após um loop é executada apenas quando não há break
explícita depois de todas as iterações. Você pode pensar nisso como uma cláusula "Nobrek".else
depois de um bloco de tentativa também é chamada de "cláusula de conclusão" como atingindo a cláusula else
em uma declaração try
significa que o bloco de tentativa realmente foi concluído com êxito. def some_func ():
Ellipsis
Saída
> >> some_func ()
# No output, No Error
> >> SomeRandomString
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name 'SomeRandomString' is not defined
> >> Ellipsis
Ellipsis
Ellipsis
é um objeto interno disponível globalmente que é equivalente a ...
> >> ...
Ellipsis
pass
) > >> import numpy as np
> >> three_dimensional_array = np . arange ( 8 ). reshape ( 2 , 2 , 2 )
array ([
[
[ 0 , 1 ],
[ 2 , 3 ]
],
[
[ 4 , 5 ],
[ 6 , 7 ]
]
])
three_dimensional_array
é uma variedade de matrizes. Digamos que queremos imprimir o segundo elemento (índice 1
) de todas as matrizes mais internas, podemos usar elipsis para ignorar todas as dimensões anteriores > >> three_dimensional_array [:,:, 1 ]
array ([[ 1 , 3 ],
[ 5 , 7 ]])
> >> three_dimensional_array [..., 1 ] # using Ellipsis.
array ([[ 1 , 3 ],
[ 5 , 7 ]])
n_dimensional_array[firs_dim_slice, ..., last_dim_slice]
)(Callable[..., int]
ou Tuple[str, ...]
)))A ortografia é destinada. Por favor, não envie um patch para isso.
Saída (python 3.x):
> >> infinity = float ( 'infinity' )
> >> hash ( infinity )
314159
> >> hash ( float ( '-inf' ))
- 314159
float('-inf')
interessante1.
class Yo ( object ):
def __init__ ( self ):
self . __honey = True
self . bro = True
Saída:
> >> Yo (). bro
True
> >> Yo (). __honey
AttributeError : 'Yo' object has no attribute '__honey'
> >> Yo (). _Yo__honey
True
2.
class Yo ( object ):
def __init__ ( self ):
# Let's try something symmetrical this time
self . __honey__ = True
self . bro = True
Saída:
> >> Yo (). bro
True
> >> Yo (). _Yo__honey__
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AttributeError : 'Yo' object has no attribute '_Yo__honey__'
Por que Yo()._Yo__honey
funcionou?
3.
_A__variable = "Some value"
class A ( object ):
def some_func ( self ):
return __variable # not initialized anywhere yet
Saída:
> >> A (). __variable
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AttributeError : 'A' object has no attribute '__variable'
> >> A (). some_func ()
'Some value'
__
(sublinhado duplo, também conhecido como "Dunder") e não terminando com mais de um sublinhado à direita, adicionando _NameOfTheClass
na frente.__honey
no primeiro snippet, tivemos que anexar _Yo
à frente, o que impediria conflitos com o mesmo atributo definido em qualquer outra classe.__variable
na declaração return __variable
foi mutilado a _A__variable
, que também é o nome da variável que declaramos no escopo externo.Saída:
> >> value = 11
> >> valuе = 32
> >> value
11
Wut?
Nota: A maneira mais fácil de reproduzir isso é simplesmente copiar as instruções do snippet acima e colá -las no seu arquivo/shell.
Alguns caracteres não ocidentais parecem idênticos às letras no alfabeto inglês, mas são consideradas distintas pelo intérprete.
> >> ord ( 'е' ) # cyrillic 'e' (Ye)
1077
> >> ord ( 'e' ) # latin 'e', as used in English and typed using standard keyboard
101
> >> 'е' == 'e'
False
> >> value = 42 # latin e
> >> valuе = 23 # cyrillic 'e', Python 2.x interpreter would raise a `SyntaxError` here
> >> value
42
A função ord()
interna retorna o ponto de código Unicode de um caractere e diferentes posições de código de Cirílico 'E' e Latin 'e' justificam o comportamento do exemplo acima.
# `pip install numpy` first.
import numpy as np
def energy_send ( x ):
# Initializing a numpy array
np . array ([ float ( x )])
def energy_receive ():
# Return an empty numpy array
return np . empty ((), dtype = np . float ). tolist ()
Saída:
> >> energy_send ( 123.456 )
> >> energy_receive ()
123.456
Onde está o Prêmio Nobel?
energy_send
não é retornada, de modo que o espaço de memória seja livre para realocar.numpy.empty()
retorna o próximo slot de memória livre sem reinicializá -lo. Esse ponto de memória é o mesmo que foi libertado (geralmente, mas nem sempre). def square ( x ):
"""
A simple function to calculate the square of a number by addition.
"""
sum_so_far = 0
for counter in range ( x ):
sum_so_far = sum_so_far + x
return sum_so_far
Saída (python 2.x):
> >> square ( 10 )
10
Não deveria ser 100?
Nota: Se você não conseguir reproduzir isso, tente executar o arquivo mixed_tabs_and_spaces.py através do shell.
Não misture guias e espaços! O caractere apenas precedente de retorno é uma "guia" e o código é recuado por múltiplos "4 espaços" em outras partes do exemplo.
É assim que o Python lida com as guias:
Primeiro, as guias são substituídas (da esquerda para a direita) por um a oito espaços, de modo que o número total de caracteres até e incluindo a substituição seja um múltiplo de oito <...>
Portanto, a "guia" na última linha de função square
é substituída por oito espaços e entra no loop.
O Python 3 é gentil o suficiente para causar um erro para esses casos automaticamente.
Saída (python 3.x):
TabError : inconsistent use of tabs and spaces in indentation
+=
é mais rápido # using "+", three strings:
> >> timeit . timeit ( "s1 = s1 + s2 + s3" , setup = "s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000" , number = 100 )
0.25748300552368164
# using "+=", three strings:
> >> timeit . timeit ( "s1 += s2 + s3" , setup = "s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000" , number = 100 )
0.012188911437988281
+=
é mais rápido que +
para concatenar mais de duas strings porque a primeira sequência (exemplo, s1
para s1 += s2 + s3
) não é destruída durante o cálculo da sequência completa. def add_string_with_plus ( iters ):
s = ""
for i in range ( iters ):
s += "xyz"
assert len ( s ) == 3 * iters
def add_bytes_with_plus ( iters ):
s = b""
for i in range ( iters ):
s += b"xyz"
assert len ( s ) == 3 * iters
def add_string_with_format ( iters ):
fs = "{}" * iters
s = fs . format ( * ([ "xyz" ] * iters ))
assert len ( s ) == 3 * iters
def add_string_with_join ( iters ):
l = []
for i in range ( iters ):
l . append ( "xyz" )
s = "" . join ( l )
assert len ( s ) == 3 * iters
def convert_list_to_string ( l , iters ):
s = "" . join ( l )
assert len ( s ) == 3 * iters
Saída:
# Executed in ipython shell using %timeit for better readability of results.
# You can also use the timeit module in normal python shell/scriptm=, example usage below
# timeit.timeit('add_string_with_plus(10000)', number=1000, globals=globals())
> >> NUM_ITERS = 1000
> >> % timeit - n1000 add_string_with_plus ( NUM_ITERS )
124 µs ± 4.73 µs per loop ( mean ± std . dev . of 7 runs , 100 loops each )
> >> % timeit - n1000 add_bytes_with_plus ( NUM_ITERS )
211 µs ± 10.5 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_format ( NUM_ITERS )
61 µs ± 2.18 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_join ( NUM_ITERS )
117 µs ± 3.21 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> l = [ "xyz" ] * NUM_ITERS
> >> % timeit - n1000 convert_list_to_string ( l , NUM_ITERS )
10.1 µs ± 1.06 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
Vamos aumentar o número de iterações em um fator de 10.
> >> NUM_ITERS = 10000
> >> % timeit - n1000 add_string_with_plus ( NUM_ITERS ) # Linear increase in execution time
1.26 ms ± 76.8 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_bytes_with_plus ( NUM_ITERS ) # Quadratic increase
6.82 ms ± 134 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_format ( NUM_ITERS ) # Linear increase
645 µs ± 24.5 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_join ( NUM_ITERS ) # Linear increase
1.17 ms ± 7.25 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> l = [ "xyz" ] * NUM_ITERS
> >> % timeit - n1000 convert_list_to_string ( l , NUM_ITERS ) # Linear increase
86.3 µs ± 2 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
Você pode ler mais sobre o tempo ou %de tempo nesses links. Eles são usados para medir o tempo de execução das peças de código.
Não use +
para gerar cordas longas - no Python, str
é imutável; portanto, as cordas esquerda e direita devem ser copiadas para a nova corda para cada par de concatenações. Se você concatenar quatro cordas de comprimento 10, estará copiando (10+10)+((10+10) +10)+(((10+10) +10) +10) = 90 caracteres em vez de apenas 40 personagens. As coisas pioram quadraticamente à medida que o número e o tamanho da string aumenta (justificado com os tempos de execução da função add_bytes_with_plus
)
Portanto, é aconselhado usar .format.
ou %
sintaxe (no entanto, eles são um pouco mais lentos que +
para seqüências muito curtas).
Ou melhor, se você já tiver conteúdo disponível na forma de um objeto iterável, use ''.join(iterable_object)
que é muito mais rápido.
Ao contrário de add_bytes_with_plus
devido às otimizações +=
discutidas no exemplo anterior, add_string_with_plus
não mostrou um aumento quadrático no tempo de execução. Se a afirmação tivesse sido s = s + "x" + "y" + "z"
em vez de s += "xyz"
, o aumento teria sido quadrático.
def add_string_with_plus ( iters ):
s = ""
for i in range ( iters ):
s = s + "x" + "y" + "z"
assert len ( s ) == 3 * iters
> >> % timeit - n100 add_string_with_plus ( 1000 )
388 µs ± 22.4 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n100 add_string_with_plus ( 10000 ) # Quadratic increase in execution time
9 ms ± 298 µs per loop ( mean ± std . dev . of 7 runs , 100 loops each )
Tantas maneiras de formatar e criar uma corda gigante são um pouco em contraste com o zen do python, segundo o qual,
Deveria haver um-e de preferência apenas uma-maneira óbvia de fazê-lo.
dict
desaceleração * some_dict = { str ( i ): 1 for i in range ( 1_000_000 )}
another_dict = { str ( i ): 1 for i in range ( 1_000_000 )}
Saída:
> >> % timeit some_dict [ '5' ]
28.6 ns ± 0.115 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> some_dict [ 1 ] = 1
> >> % timeit some_dict [ '5' ]
37.2 ns ± 0.265 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> % timeit another_dict [ '5' ]
28.5 ns ± 0.142 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> another_dict [ 1 ] # Trying to access a key that doesn't exist
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
KeyError : 1
> >> % timeit another_dict [ '5' ]
38.5 ns ± 0.0913 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
Por que as mesmas pesquisas estão se tornando mais lentas?
str
, int
, qualquer objeto ...), e uma especializada para o caso comum de dicionários compostos por str
apenas.lookdict_unicode
na fonte do cpython) sabe que todas as teclas existentes (incluindo a chave de procuramento) são strings e usa a comparação mais rápida e simples de string para comparar as teclas, em vez de chamar o método __eq__
.dict
é acessada com uma chave não str
, é modificada para que as pesquisas futuras usem a função genérica.dict
específica, e a chave nem precisa existir no dicionário. É por isso que tentar uma pesquisa falhada tem o mesmo efeito.dict
de instância de inchaço s * import sys
class SomeClass :
def __init__ ( self ):
self . some_attr1 = 1
self . some_attr2 = 2
self . some_attr3 = 3
self . some_attr4 = 4
def dict_size ( o ):
return sys . getsizeof ( o . __dict__ )
Saída: (Python 3.8, outras versões do Python 3 podem variar um pouco)
> >> o1 = SomeClass ()
> >> o2 = SomeClass ()
> >> dict_size ( o1 )
104
> >> dict_size ( o2 )
104
> >> del o1 . some_attr1
> >> o3 = SomeClass ()
> >> dict_size ( o3 )
232
> >> dict_size ( o1 )
232
Vamos tentar novamente ... em um novo intérprete:
> >> o1 = SomeClass ()
> >> o2 = SomeClass ()
> >> dict_size ( o1 )
104 # as expected
> >> o1 . some_attr5 = 5
> >> o1 . some_attr6 = 6
> >> dict_size ( o1 )
360
> >> dict_size ( o2 )
272
> >> o3 = SomeClass ()
> >> dict_size ( o3 )
232
O que faz esses dicionários ficarem inchados? E por que os objetos recém -criados também são inchados?
__init__
da primeira instância criada, sem causar um "não se bem"). Se houver várias instâncias quando ocorrer um redimensionamento, o compartilhamento de chaves é desativado para todas as instâncias futuras da mesma classe: Cpython não pode dizer se suas instâncias estão mais usando o mesmo conjunto de atributos, e decide dar resgate ao tentar compartilhar seus chaves.__init__
! join()
é uma operação de string em vez de operação de lista. (meio que contra-intuitivo no primeiro uso)
Explicação: Se join()
for um método em uma string, ele poderá operar em qualquer iterable (lista, tupla, iteradores). Se fosse um método em uma lista, teria que ser implementado separadamente por todos os tipos. Além disso, não faz muito sentido colocar um método específico de string em uma API de objeto list
genérica.
Poucas declarações estranhas, mas semanticamente corretas:
[] = ()
é uma declaração semanticamente correta (desempacotar uma tuple
vazia em uma list
vazia)'a'[0][0][0][0][0]
também é semanticamente correto, porque o python não possui um tipo de dados de caracteres, como outros idiomas ramificados de C. Portanto, selecionar um único caractere de uma string retorna um string de caractere único.3 --0-- 5 == 8
e --5 == 5
são declarações semanticamente corretas e avaliam como True
. Dado que a
é um número, ++a
e --a
ambos são declarações válidas do Python, mas não se comportam da mesma maneira que comparados com declarações semelhantes em idiomas como C, C ++ ou Java.
> >> a = 5
> >> a
5
> >> + + a
5
> >> - - a
5
Explicação:
++
na gramática Python. Na verdade, são +
operadores.++a
Parses como +(+a)
que se traduz em a
. Da mesma forma, a saída da declaração --a
pode ser justificada.Você deve estar ciente do operador Walrus em Python. Mas você já ouviu falar sobre o operador de conversor espacial ?
> >> a = 42
> >> a -= - 1
> >> a
43
É usado como um operador de incremento alternativo, juntamente com outro
> >> a += + 1
> >> a
> >> 44
Explicação: Esta brincadeira vem do tweet de Raymond Hettinger. O operador do Invader Space é na verdade apenas um a -= (-1)
malformado. Que é equivalente a a = a - (- 1)
. Semelhante para o caso a += (+ 1)
.
O Python possui um operador de implicação Converse sem documentos.
> >> False ** False == True
True
> >> False ** True == False
True
> >> True ** False == True
True
> >> True ** True == True
True
Explicação: Se você substituir False
e True
por 0 e 1 e fizer a matemática, a tabela de verdade será equivalente a um operador de implicação inversa. (Fonte)
Como estamos falando de operadores, também existe o operador @
Multiplicação Matrix (não se preocupe, desta vez é real).
> >> import numpy as np
> >> np . array ([ 2 , 2 , 2 ]) @ np . array ([ 7 , 8 , 8 ])
46
Explicação: O operador @
foi adicionado no Python 3.5, mantendo em mente a comunidade científica. Qualquer objeto pode sobrecarregar o método mágico __matmul__
para definir o comportamento para este operador.
A partir do Python 3.8, você pode usar uma sintaxe de cordas F típica como f'{some_var=}
para depuração rápida. Exemplo,
> >> some_string = "wtfpython"
> >> f' { some_string = } '
"some_string='wtfpython'"
O Python usa 2 bytes para armazenamento variável local nas funções. Em teoria, isso significa que apenas 65536 variáveis podem ser definidas em uma função. No entanto, o Python possui uma solução útil construída que pode ser usada para armazenar mais de 2^16 nomes variáveis. O código a seguir demonstra o que acontece na pilha quando mais de 65536 variáveis locais são definidas (aviso: este código imprime em torno de 2^18 linhas de texto, então esteja preparado!):
import dis
exec ( """
def f():
""" + """
""" . join ([ "X" + str ( x ) + "=" + str ( x ) for x in range ( 65539 )]))
f ()
print ( dis . dis ( f ))
Vários threads Python não executarão seu código Python simultaneamente (sim, você ouviu certo!). Pode parecer intuitivo gerar vários threads e permitir que eles executem seu código Python simultaneamente, mas, devido ao bloqueio global de intérprete em Python, tudo o que você está fazendo é fazer seus threads executar no mesmo curso principal a turno. Os threads Python são bons para tarefas ligadas a IO, mas, para obter paralelização real no Python para tarefas ligadas à CPU, você pode usar o módulo multiprocessamento do Python.
Às vezes, o método print
pode não imprimir valores imediatamente. Por exemplo,
# File some_file.py
import time
print ( "wtfpython" , end = "_" )
time . sleep ( 3 )
Isso imprimirá o wtfpython
após 3 segundos devido ao argumento end
porque o buffer de saída é liberado após encontrar n
ou quando o programa termina a execução. Podemos forçar o buffer a descarregar passando flush=True
.
Lista Flicing With Out of the Bounds Indices não lança erros
> >> some_list = [ 1 , 2 , 3 , 4 , 5 ]
> >> some_list [ 111 :]
[]
Cortar um iterável nem sempre cria um novo objeto. Por exemplo,
> >> some_str = "wtfpython"
> >> some_list = [ 'w' , 't' , 'f' , 'p' , 'y' , 't' , 'h' , 'o' , 'n' ]
> >> some_list is some_list [:] # False expected because a new object is created.
False
> >> some_str is some_str [:] # True because strings are immutable, so making a new object is of not much use.
True
int('١٢٣٤٥٦٧٨٩')
retorna 123456789
no Python 3. Em Python, caracteres decimais incluem caracteres de dígitos e todos os caracteres que podem ser usados para formar números decimais de radix, por exemplo, U+0660, dígito árabe-indic zero. Aqui está uma história interessante relacionada a esse comportamento de Python.
Você pode separar literais numéricos com sublinhados (para melhor legibilidade) do Python 3 em diante.
> >> six_million = 6_000_000
> >> six_million
6000000
> >> hex_address = 0xF00D_CAFE
> >> hex_address
4027435774
'abc'.count('') == 4
. Aqui está uma implementação aproximada do método count
, que tornaria as coisas mais claras
def count ( s , sub ):
result = 0
for i in range ( len ( s ) + 1 - len ( sub )):
result += ( s [ i : i + len ( sub )] == sub )
return result
O comportamento se deve à correspondência da substring vazia ( ''
) com as fatias de comprimento 0 na sequência original.
Algumas maneiras pelas quais você pode contribuir para o wtfpython,
Consulte Contribuindo.md para mais detalhes. Sinta -se à vontade para criar uma nova questão para discutir as coisas.
PS: Por favor, não entre em contato com solicitações de backling, nenhum link será adicionado, a menos que sejam altamente relevantes para o projeto.
A idéia e o design para esta coleção foram inicialmente inspirados pelo incrível projeto WTFJS de Denys Dovhan. O apoio esmagador dos pythonistas deu a forma em que está agora.
© Satwik Kansal
If you like wtfpython, you can use these quick links to share it with your friends,
Twitter | Linkedin | Facebook
I've received a few requests for the pdf (and epub) version of wtfpython. You can add your details here to get them as soon as they are finished.
That's all folks! For upcoming content like this, you can add your email here.