Explorer et comprendre Python à travers des extraits surprenants.
Traductions : chinois 中文 | Vietnamien Tiếng Việt | espagnol espagnol | Coréen 한국어 | Russe Russe | Allemand Allemand | Ajouter une traduction
Autres modes : Site Web interactif | Carnet interactif
Python, étant un langage de programmation de haut niveau magnifiquement conçu et basé sur un interpréteur, nous offre de nombreuses fonctionnalités pour le confort du programmeur. Mais parfois, les résultats d’un extrait Python peuvent ne pas sembler évidents à première vue.
Voici un projet amusant tentant d'expliquer ce qui se passe exactement sous le capot pour quelques extraits contre-intuitifs et fonctionnalités moins connues de Python.
Bien que certains des exemples que vous voyez ci-dessous ne soient peut-être pas des WTF au sens propre du terme, ils révéleront certaines des parties intéressantes de Python que vous ignorez peut-être. Je trouve que c'est une bonne façon d'apprendre les rouages d'un langage de programmation, et je pense que vous le trouverez également intéressant !
Si vous êtes un programmeur Python expérimenté, vous pouvez considérer comme un défi de réussir la plupart d'entre eux du premier coup. Vous en avez peut-être déjà vécu quelques-unes auparavant, et je pourrai peut-être raviver de doux vieux souvenirs de vous ! ?
PS : si vous êtes un lecteur fidèle, vous pouvez découvrir les nouvelles modifications ici (les exemples marqués d'un astérisque sont ceux ajoutés dans la dernière révision majeure).
Alors, c'est parti...
is
opérateuris not ...
n'est pas is (not ...)
del
goto
, mais pourquoi ?+=
est plus rapidedict
*dict
s *Tous les exemples sont structurés comme ci-dessous :
▶ Un titre fantaisiste
# Set up the code. # Preparation for the magic...Sortie (version(s) Python) :
> >> triggering_statement Some unexpected output(Facultatif) : une ligne décrivant la sortie inattendue.
Explication:
- Brève explication de ce qui se passe et pourquoi cela se produit.
# Set up code # More examples for further clarification (if necessary)Sortie (version(s) Python) :
> >> trigger # some example that makes it easy to unveil the magic # some justified output
Remarque : tous les exemples sont testés sur l'interpréteur interactif Python 3.5.2 et devraient fonctionner pour toutes les versions de Python, sauf indication contraire explicite avant la sortie.
Une bonne façon de tirer le meilleur parti de ces exemples, à mon avis, est de les lire dans un ordre séquentiel, et pour chaque exemple :
Pour une raison quelconque, l'opérateur "Walrus" ( :=
) de Python 3.8 est devenu très populaire. Vérifions ça,
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
Rappel rapide pour les opérateurs de morses
L'opérateur Walrus ( :=
) a été introduit dans Python 3.8, il peut être utile dans les situations où vous souhaitez attribuer des valeurs à des variables dans une expression.
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 )
Sortie (> 3.8) :
5
5
5
Cela a permis d'économiser une ligne de code et d'empêcher implicitement d'invoquer some_func
deux fois.
"L'expression d'affectation" sans parenthèses (utilisation de l'opérateur morse) est restreinte au niveau supérieur, d'où l' SyntaxError
dans l'instruction a := "wtf_walrus"
du premier extrait. La mise entre parenthèses a fonctionné comme prévu et a attribué a
.
Comme d'habitude, la parenthèse d'une expression contenant l'opérateur =
n'est pas autorisée. D'où l'erreur de syntaxe dans (a, b = 6, 9)
.
La syntaxe de l'opérateur Walrus est de la forme NAME:= expr
, où NAME
est un identifiant valide et expr
est une expression valide. Par conséquent, l'emballage et le déballage itérables ne sont pas pris en charge, ce qui signifie :
(a := 6, 9)
est équivalent à ((a := 6), 9)
et finalement (a, 9)
(où la valeur de a
est 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
De même, (a, b := 16, 19)
est équivalent à (a, (b := 16), 19)
qui n'est rien d'autre qu'un 3-tuple.
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.
Sortie (< Python3.7 )
> >> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
> >> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False
C’est logique, non ?
'wtf'
sera interné mais ''.join(['w', 't', 'f'])
ne sera pas interné)'wtf!'
n'a pas été interné à cause de !
. L'implémentation CPython de cette règle peut être trouvée ici a
et b
sont définis sur "wtf!"
dans la même ligne, l'interpréteur Python crée un nouvel objet, puis référence en même temps la deuxième variable. Si vous le faites sur des lignes séparées, il ne « sait » pas qu'il y a déjà "wtf!"
en tant qu'objet (car "wtf!"
n'est pas implicitement interné selon les faits mentionnés ci-dessus). C'est une optimisation au moment de la compilation. Cette optimisation ne s'applique pas aux versions 3.7.x de CPython (consultez ce problème pour plus de détails).a, b = "wtf!", "wtf!"
est une seule instruction, alors que a = "wtf!"; b = "wtf!"
sont deux déclarations sur une seule ligne. Cela explique pourquoi les identités sont différentes dans a = "wtf!"; b = "wtf!"
, et expliquez également pourquoi ils sont identiques lorsqu'ils sont invoqués dans some_file.py
'a'*20
est remplacée par 'aaaaaaaaaaaaaaaaaaaa'
lors de la compilation pour économiser quelques cycles d'horloge pendant l'exécution. Le pliage constant ne se produit que pour les chaînes d'une longueur inférieure à 21. (Pourquoi ? Imaginez la taille du fichier .pyc
généré à la suite de l'expression 'a'*10**10
). Voici la source d'implémentation pour la même chose. > >> ( 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
Selon https://docs.python.org/3/reference/expressions.html#comparisons
Formellement, si a, b, c, ..., y, z sont des expressions et op1, op2, ..., opN sont des opérateurs de comparaison, alors a op1 b op2 c ... y opN z est équivalent à a op1 b et b op2 c et ... y opN z, sauf que chaque expression est évaluée au plus une fois.
Bien qu'un tel comportement puisse vous sembler idiot dans les exemples ci-dessus, il est fantastique avec des éléments comme a == b == c
et 0 <= x <= 100
.
False is False is False
est équivalent à (False is False) and (False is False)
True is False == False
est équivalent à (True is False) and (False == False)
et puisque la première partie de l'instruction ( True is False
) est évaluée à False
, l'expression globale est évaluée à False
.1 > 0 < 1
est équivalent à (1 > 0) and (0 < 1)
qui est évalué à True
.(1 > 0) < 1
est équivalente à True < 1
et > >> int ( True )
1
> >> True + 1 #not relevant for this example, but just for fun
2
1 < 1
est évalué à False
is
opérateurCe qui suit est un exemple très célèbre présent partout sur 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. Sortie
> >> a , b = 257 , 257
> >> a is b
True
Sortie (Python 3.7.x spécifiquement)
> >> a , b = 257 , 257
> >> a is b
False
La différence entre is
et ==
is
opérateur vérifie si les deux opérandes font référence au même objet (c'est-à-dire qu'il vérifie si l'identité des opérandes correspond ou non).==
compare les valeurs des deux opérandes et vérifie si elles sont identiques.is
s'agit donc d'une égalité de référence et ==
d'une égalité de valeur. Un exemple pour mettre les choses au clair, > >> class A : pass
> >> A () is A () # These are two empty objects at two different memory locations.
False
256
est un objet existant mais 257
ne l'est pas
Lorsque vous démarrez Python, les nombres de -5
à 256
seront attribués. Ces chiffres sont beaucoup utilisés, il est donc logique de les préparer.
Citation de https://docs.python.org/3/c-api/long.html
L'implémentation actuelle conserve un tableau d'objets entiers pour tous les entiers compris entre -5 et 256. Lorsque vous créez un int dans cette plage, vous récupérez simplement une référence à l'objet existant. Il devrait donc être possible de modifier la valeur de 1. Je soupçonne que le comportement de Python, dans ce cas, n'est pas défini. :-)
> >> 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
Ici, l'interpréteur n'est pas assez intelligent lors de l'exécution y = 257
pour reconnaître que nous avons déjà créé un entier de la valeur 257,
et il crée donc un autre objet dans la mémoire.
Une optimisation similaire s'applique également à d'autres objets immuables comme les tuples vides. Puisque les listes sont mutables, c'est pourquoi [] is []
renverra False
et () is ()
renverra True
. Ceci explique notre deuxième extrait. Passons au troisième,
a
et b
font tous deux référence au même objet lorsqu'ils sont initialisés avec la même valeur dans la même ligne.
Sortir
> >> a , b = 257 , 257
> >> id ( a )
140640774013296
> >> id ( b )
140640774013296
> >> a = 257
> >> b = 257
> >> id ( a )
140640774013392
> >> id ( b )
140640774013488
Lorsque a et b sont définis sur 257
sur la même ligne, l'interpréteur Python crée un nouvel objet, puis référence la deuxième variable en même temps. Si vous le faites sur des lignes séparées, il ne « sait » pas qu’il y en a déjà 257
comme objet.
Il s'agit d'une optimisation du compilateur et s'applique spécifiquement à l'environnement interactif. Lorsque vous saisissez deux lignes dans un interpréteur en direct, elles sont compilées séparément, donc optimisées séparément. Si vous essayiez cet exemple dans un fichier .py
, vous ne verriez pas le même comportement, car le fichier est compilé en une seule fois. Cette optimisation ne se limite pas aux entiers, elle fonctionne également pour d'autres types de données immuables comme les chaînes (consultez l'exemple "Les chaînes sont délicates") et les flottants,
> >> a , b = 257.0 , 257.0
> >> a is b
True
Pourquoi cela n'a-t-il pas fonctionné pour Python 3.7 ? La raison abstraite est que ces optimisations du compilateur sont spécifiques à l'implémentation (c'est-à-dire qu'elles peuvent changer avec la version, le système d'exploitation, etc.). Je suis toujours en train de déterminer quel changement d'implémentation exact est à l'origine du problème, vous pouvez consulter ce problème pour les mises à jour.
1.
some_dict = {}
some_dict [ 5.5 ] = "JavaScript"
some_dict [ 5.0 ] = "Ruby"
some_dict [ 5 ] = "Python"
Sortir:
> >> 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"
Alors, pourquoi Python est-il partout ?
L'unicité des clés dans un dictionnaire Python dépend de l'équivalence et non de l'identité. Ainsi, même si 5
, 5.0
et 5 + 0j
sont des objets distincts de types différents, puisqu'ils sont égaux, ils ne peuvent pas tous les deux être dans le même dict
(ou set
). Dès que vous insérez l’une d’entre elles, tenter de rechercher une clé distincte mais équivalente réussira avec la valeur mappée d’origine (plutôt que d’échouer avec une 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
Cela s'applique également lors de la définition d'un élément. Ainsi, lorsque vous effectuez some_dict[5] = "Python"
, Python trouve l'élément existant avec la clé équivalente 5.0 -> "Ruby"
, écrase sa valeur en place et laisse la clé d'origine seule.
> >> some_dict
{ 5.0 : 'Ruby' }
> >> some_dict [ 5 ] = "Python"
> >> some_dict
{ 5.0 : 'Python' }
Alors, comment pouvons-nous mettre à jour la clé à 5
(au lieu de 5.0
) ? Nous ne pouvons pas réellement faire cette mise à jour sur place, mais ce que nous pouvons faire est d'abord de supprimer la clé ( del some_dict[5.0]
), puis de la définir ( some_dict[5]
) pour obtenir l'entier 5
comme clé au lieu de flotter. 5.0
, même si cela devrait être nécessaire dans de rares cas.
Comment Python a-t-il trouvé 5
dans un dictionnaire contenant 5.0
? Python le fait en temps constant sans avoir à parcourir chaque élément à l'aide de fonctions de hachage. Lorsque Python recherche une clé foo
dans un dict, il calcule d'abord hash(foo)
(qui s'exécute en temps constant). Puisqu'en Python, il est nécessaire que les objets comparables aient également la même valeur de hachage (documentation ici), 5
, 5.0
et 5 + 0j
ont la même valeur de hachage.
> >> 5 == 5.0 == 5 + 0j
True
> >> hash ( 5 ) == hash ( 5.0 ) == hash ( 5 + 0j )
True
Remarque : L'inverse n'est pas nécessairement vrai : les objets ayant des valeurs de hachage égales peuvent eux-mêmes être inégaux. (Cela provoque ce que l'on appelle une collision de hachage et dégrade les performances en temps constant fournies habituellement par le hachage.)
class WTF :
pass
Sortir:
> >> 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
Lorsque id
a été appelé, Python a créé un objet de classe WTF
et l'a transmis à la fonction id
. La fonction id
prend son id
(son emplacement mémoire) et jette l'objet. L'objet est détruit.
Lorsque nous faisons cela deux fois de suite, Python alloue également le même emplacement mémoire à ce deuxième objet. Puisque (dans CPython) id
utilise l’emplacement mémoire comme identifiant d’objet, l’identifiant des deux objets est le même.
Ainsi, l'identifiant de l'objet n'est unique que pour la durée de vie de l'objet. Une fois l’objet détruit ou avant sa création, quelque chose d’autre peut avoir le même identifiant.
Mais pourquoi l'opérateur is
a-t-il été évalué à False
? Voyons avec cet extrait.
class WTF ( object ):
def __init__ ( self ): print ( "I" )
def __del__ ( self ): print ( "D" )
Sortir:
> >> WTF () is WTF ()
I
I
D
D
False
> >> id ( WTF ()) == id ( WTF ())
I
D
I
D
True
Comme vous pouvez le constater, c’est l’ordre dans lequel les objets sont détruits qui fait ici toute la différence.
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
Sortir
> >> 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
Que se passe-t-il ici ?
La raison pour laquelle l'égalité intransitive n'a pas été respectée entre dictionary
, ordered_dict
et another_ordered_dict
est due à la façon dont la méthode __eq__
est implémentée dans la classe OrderedDict
. À partir des documents
Les tests d'égalité entre les objets OrderedDict sont sensibles à l'ordre et sont implémentés sous la forme
list(od1.items())==list(od2.items())
. Les tests d'égalité entre les objetsOrderedDict
et d'autres objets Mapping ne sont pas sensibles à l'ordre, comme les dictionnaires classiques.
La raison de cette égalité de comportement est qu'elle permet aux objets OrderedDict
d'être directement substitués partout où un dictionnaire normal est utilisé.
D'accord, alors pourquoi la modification de l'ordre a-t-elle affecté la longueur de l'objet set
généré ? La réponse est uniquement le manque d’égalité intransitive. Puisque les ensembles sont des collections « non ordonnées » d’éléments uniques, l’ordre dans lequel les éléments sont insérés ne devrait pas avoir d’importance. Mais dans ce cas, c’est important. Décomposons-le un peu,
> >> 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
L'incohérence est donc due au fait que another_ordered_dict in another_set
est False
car ordered_dict
était déjà présent dans another_set
et comme observé précédemment, ordered_dict == another_ordered_dict
est 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 )
Sortir:
> >> 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
est exécutée dans la suite try
d'une instruction "try…finally", la finally
est également exécutée à la sortie.return
exécutée. Puisque la clause finally
s'exécute toujours, une instruction return
exécutée dans la clause finally
sera toujours la dernière exécutée.return
ou break
, l'exception temporairement enregistrée est ignorée. some_string = "wtf"
some_dict = {}
for i , some_dict [ i ] in enumerate ( some_string ):
i = 10
Sortir:
> >> some_dict # An indexed dict appears.
{ 0 : 'w' , 1 : 't' , 2 : 'f' }
Une instruction for
est définie dans la grammaire Python comme :
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
Où exprlist
est la cible de l'affectation. Cela signifie que l'équivalent de {exprlist} = {next_value}
est exécuté pour chaque élément de l'itérable. Un exemple intéressant qui illustre ceci :
for i in range ( 4 ):
print ( i )
i = 10
Sortir:
0
1
2
3
Vous attendiez-vous à ce que la boucle ne s'exécute qu'une seule fois ?
Explication:
i = 10
n'affecte jamais les itérations de la boucle en raison du fonctionnement des boucles for en Python. Avant le début de chaque itération, l'élément suivant fourni par l'itérateur ( range(4)
dans ce cas) est décompressé et affecté aux variables de la liste cible ( i
dans ce cas). La fonction enumerate(some_string)
génère une nouvelle valeur i
(un compteur qui monte) et un caractère de some_string
à chaque itération. Il définit ensuite la clé i
(juste attribuée) du dictionnaire some_dict
sur ce caractère. Le déroulement de la boucle peut être simplifié comme suit :
> >> 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 ]
Sortir:
> >> 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 ]
Sortir:
> >> 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 ]
Sortir:
> >> print ( list ( gen ))
[ 401 , 501 , 601 , 402 , 502 , 602 , 403 , 503 , 603 ]
Dans une expression génératrice, la clause in
est évaluée au moment de la déclaration, mais la clause conditionnelle est évaluée au moment de l'exécution.
Ainsi, avant l'exécution, array
est réaffecté à la liste [2, 8, 22]
, et puisque sur 1
, 8
et 15
, seul le nombre de 8
est supérieur à 0
, le générateur ne donne que 8
.
Les différences dans les résultats de g1
et g2
dans la deuxième partie sont dues à la manière dont les variables array_1
et array_2
sont réaffectées.
Dans le premier cas, array_1
est lié au nouvel objet [1,2,3,4,5]
et puisque la clause in
est évaluée au moment de la déclaration, elle fait toujours référence à l'ancien objet [1,2,3,4]
(qui n'est pas détruit).
Dans le second cas, l'affectation de tranche à array_2
met à jour le même ancien objet [1,2,3,4]
en [1,2,3,4,5]
. Par conséquent, g2
et array_2
font toujours référence au même objet (qui a maintenant été mis à jour vers [1,2,3,4,5]
).
D'accord, en suivant la logique discutée jusqu'à présent, la valeur de list(gen)
dans le troisième extrait ne devrait-elle pas être [11, 21, 31, 12, 22, 32, 13, 23, 33]
? (car array_3
et array_4
vont se comporter comme array_1
). La raison pour laquelle (seulement) les valeurs array_4
ont été mises à jour est expliquée dans PEP-289
Seule l'expression for la plus externe est évaluée immédiatement, les autres expressions sont différées jusqu'à l'exécution du générateur.
is not ...
n'est pas is (not ...)
> >> 'something' is not None
True
> >> 'something' is ( not None )
False
is not
est un opérateur binaire unique et a un comportement différent de celui de l'utilisation is
et not
séparé.is not
évalué à False
si les variables de chaque côté de l'opérateur pointent vers le même objet et True
dans le cas contraire.(not None)
est évalué à True
puisque la valeur None
est False
dans un contexte booléen, donc l'expression devient 'something' is True
. # Let's initialize a row
row = [ "" ] * 3 #row i['', '', '']
# Let's make a board
board = [ row ] * 3
Sortir:
> >> board
[[ '' , '' , '' ], [ '' , '' , '' ], [ '' , '' , '' ]]
> >> board [ 0 ]
[ '' , '' , '' ]
> >> board [ 0 ][ 0 ]
''
> >> board [ 0 ][ 0 ] = "X"
> >> board
[[ 'X' , '' , '' ], [ 'X' , '' , '' ], [ 'X' , '' , '' ]]
Nous n'avons pas attribué trois "X"
, n'est-ce pas ?
Lorsque nous initialisons une variable row
, cette visualisation explique ce qui se passe dans la mémoire
Et lorsque le board
est initialisé en multipliant la row
, c'est ce qui se passe à l'intérieur de la mémoire (chacun des éléments board[0]
, board[1]
et board[2]
est une référence à la même liste référencée par row
)
Nous pouvons éviter ce scénario ici en n'utilisant pas de variable row
pour générer board
. (Demandé dans ce numéro).
> >> 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 ]
Sortie (version Python) :
> >> results
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
> >> funcs_results
[ 6 , 6 , 6 , 6 , 6 , 6 , 6 ]
Les valeurs de x
étaient différentes à chaque itération avant l'ajout some_func
à funcs
, mais toutes les fonctions renvoient 6 lorsqu'elles sont évaluées une fois la boucle terminée.
> >> 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
dans le contexte environnant, plutôt que d'utiliser la valeur de x
au moment de la création de la fonction. Ainsi, toutes les fonctions utilisent la dernière valeur attribuée à la variable pour le calcul. Nous pouvons voir qu'il utilise le x
du contexte environnant (c'est-à-dire pas une variable locale) avec : > >> import inspect
> >> inspect . getclosurevars ( funcs [ 0 ])
ClosureVars ( nonlocals = {}, globals = { 'x' : 6 }, builtins = {}, unbound = set ())
Puisque x
est une valeur globale, nous pouvons modifier la valeur que les funcs
rechercheront et renverront en mettant à jour x
:
> >> x = 42
> >> [ func () for func in funcs ]
[ 42 , 42 , 42 , 42 , 42 , 42 , 42 ]
x
à ce moment-là. funcs = []
for x in range ( 7 ):
def some_func ( x = x ):
return x
funcs . append ( some_func )
Sortir:
> >> funcs_results = [ func () for func in funcs ]
> >> funcs_results
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
Il n'utilise plus le x
dans la portée globale :
> >> inspect . getclosurevars ( funcs [ 0 ])
ClosureVars ( nonlocals = {}, globals = {}, builtins = {}, unbound = set ())
1.
> >> isinstance ( 3 , int )
True
> >> isinstance ( type , object )
True
> >> isinstance ( object , type )
True
Alors, quelle est la classe de base « ultime » ? Au fait, il y a plus de confusion
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
est une métaclasse en Python.object
en Python, qui comprend les classes ainsi que leurs objets (instances).type
est la métaclasse de class object
, et chaque classe (y compris type
) a hérité directement ou indirectement de object
.object
et type
. La confusion dans les extraits ci-dessus vient du fait que nous réfléchissons à ces relations ( issubclass
et isinstance
) en termes de classes Python. La relation entre object
et type
ne peut pas être reproduite en python pur. Pour être plus précis, les relations suivantes ne peuvent pas être reproduites en Python pur,object
et type
(les deux étant des instances l'un de l'autre ainsi qu'eux-mêmes) existent en Python en raison de la « triche » au niveau de l'implémentation.Sortir:
> >> from collections . abc import Hashable
> >> issubclass ( list , object )
True
> >> issubclass ( object , Hashable )
True
> >> issubclass ( list , Hashable )
False
Les relations entre les sous-classes devaient être transitives, n'est-ce pas ? (c'est-à-dire que si A
est une sous-classe de B
et B
est une sous-classe de C
, le A
devrait être une sous-classe de C
)
__subclasscheck__
arbitraire dans une métaclasse.issubclass(cls, Hashable)
est appelé, il recherche simplement la méthode non-Falsey " __hash__
" dans cls
ou tout ce dont elle hérite.object
est hachable, mais que list
n'est pas hachable, cela rompt la relation de transitivité. class SomeClass :
def method ( self ):
pass
@ classmethod
def classm ( cls ):
pass
@ staticmethod
def staticm ():
pass
Sortir:
> >> 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
En accédant classm
deux fois, nous obtenons un objet égal, mais pas le même ? Voyons ce qui se passe avec les instances de SomeClass
:
o1 = SomeClass ()
o2 = SomeClass ()
Sortir:
> >> 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
Accéder deux fois classm
ou method
crée des objets égaux mais pas identiques pour la même instance de SomeClass
.
self
comme premier argument, même si nous ne le passons pas explicitement). > >> o1 . method
< bound method SomeClass . method of < __main__ . SomeClass object at ... >>
o1.method is o1.method
n’est jamais véridique. Cependant, l'accès aux fonctions en tant qu'attributs de classe (par opposition à instance) ne crée pas de méthodes ; donc SomeClass.method is SomeClass.method
est véridique. > >> SomeClass . method
< function SomeClass . method at ... >
classmethod
transforme les fonctions en méthodes de classe. Les méthodes de classe sont des descripteurs qui, lorsqu'ils sont accessibles, créent un objet méthode qui lie la classe (type) de l'objet, au lieu de l'objet lui-même. > >> o1 . classm
< bound method SomeClass . classm of < class '__main__.SomeClass' >>
classmethod
créeront également une méthode lorsqu'elles seront accessibles en tant qu'attributs de classe (auquel cas elles lieront la classe, et non à son type). Donc SomeClass.classm is SomeClass.classm
est faux. > >> SomeClass . classm
< bound method SomeClass . classm of < class '__main__.SomeClass' >>
o1.method == o1.method
est véridique, bien que ce ne soit pas le même objet en mémoire.staticmethod
transforme les fonctions en un descripteur "no-op", qui renvoie la fonction telle quelle. Aucun objet méthode n'est jamais créé, donc la comparaison avec is
véridique. > >> o1 . staticm
< function SomeClass . staticm at ... >
> >> SomeClass . staticm
< function SomeClass . staticm at ... >
self
affectées. CPython 3.7 l'a résolu en introduisant de nouveaux opcodes qui traitent des méthodes d'appel sans créer d'objets de méthode temporaires. Ceci n'est utilisé que lorsque la fonction accédée est réellement appelée, donc les extraits ici ne sont pas affectés et génèrent toujours des méthodes :) > >> all ([ True , True , True ])
True
> >> all ([ True , True , False ])
False
> >> all ([])
True
> >> all ([[]])
False
> >> all ([[[]]])
True
Pourquoi cette altération Vrai-Faux ?
La mise en œuvre de all
les fonctions équivaut à
def all ( iterable ):
for element in iterable :
if not element :
return False
return True
all([])
renvoie True
puisque l'itérable est vide.
all([[]])
renvoie False
car le tableau passé a un élément, []
, et en python, une liste vide est fausse.
all([[[]]])
et les variantes récursives supérieures sont toujours True
. En effet, l'élément unique du tableau transmis ( [[...]]
) n'est plus vide et les listes avec des valeurs sont véridiques.
Sortie (< 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
Sortir:
> >> print ( " " " )
"
>> > print ( r""" )
"
>> > print ( r" " )
File "<stdin>" , line 1
print ( r" " )
^
SyntaxError : EOL while scanning string literal
>> > r''' == " \ '"
True
> >> "wt " f"
'wt"f'
r
), les barres obliques inverses se transmettent telles quelles avec le comportement d'échappement du caractère suivant. > >> r'wt"f' == 'wt \ "f'
True
> >> print ( repr ( r'wt"f' ))
'wt \ "f'
> >> print ( " n " )
> >> print ( r"\n" )
' \ n'
print(r"")
), la barre oblique inverse a échappé au guillemet final, laissant l'analyseur sans guillemet final (d'où le SyntaxError
). C'est pourquoi les barres obliques inverses ne fonctionnent pas à la fin d'une chaîne brute. x = True
y = False
Sortir:
> >> not x == y
True
> >> x == not y
File "<input>" , line 1
x == not y
^
SyntaxError : invalid syntax
==
a une priorité plus élevée que l'opérateur not
en Python.not x == y
équivaut à not (x == y)
qui équivaut à not (True == False)
finalement évalué à True
.x == not y
déclenche une SyntaxError
car on peut penser qu'elle est équivalente à (x == not) y
et not x == (not y)
ce à quoi on aurait pu s'attendre à première vue.not
fasse partie de l'opérateur not in
(car les deux opérateurs ==
et not in
ont la même priorité), mais après n'avoir pas pu trouver de jeton in
après le jeton not
, il génère une SyntaxError
.Sortir:
> >> 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
'''
et """
sont également des délimiteurs de chaîne en Python, ce qui provoque une SyntaxError car l'interpréteur Python s'attendait à un guillemet triple de fin comme délimiteur lors de l'analyse du littéral de chaîne entre guillemets triples actuellement rencontré.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
Sortir:
> >> 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!" )
Sortie (< 3.x) :
> >> tell_truth ()
I have lost faith in truth !
bool
est une sous-classe de int
en Python
> >> issubclass ( bool , int )
True
> >> issubclass ( int , bool )
False
Et donc, True
et False
sont des instances de int
> >> isinstance ( True , int )
True
> >> isinstance ( False , int )
True
La valeur entière de True
est 1
et celle de False
est 0
.
> >> int ( True )
1
> >> int ( False )
0
Voir cette réponse StackOverflow pour la justification.
Initialement, Python n'avait pas de type bool
(les gens utilisaient 0 pour faux et une valeur non nulle comme 1 pour vrai). True
, False
et un type bool
ont été ajoutés dans les versions 2.x, mais, pour des raisons de compatibilité descendante, True
et False
n'ont pas pu devenir des constantes. Il s'agissait simplement de variables intégrées et il était possible de les réaffecter
Python 3 était rétrocompatible, le problème a finalement été résolu et le dernier extrait ne fonctionnera donc pas avec Python 3.x !
1.
class A :
x = 1
class B ( A ):
pass
class C ( A ):
pass
Sortir:
> >> 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 ]
Sortir:
> >> 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
+=
modifie l'objet mutable sur place sans créer de nouvel objet. Ainsi, changer l'attribut d'une instance affecte également les autres instances ainsi que l'attribut de classe. some_iterable = ( 'a' , 'b' )
def some_func ( val ):
return "something"
Sortie (<= 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
par CPython dans les générateurs et les compréhensions.yield
dans la compréhension de liste et lancera une SyntaxError
.1.
def some_func ( x ):
if x == 3 :
return [ "wtf" ]
else :
yield from range ( x )
Sortie (> 3.3) :
> >> list ( some_func ( 3 ))
[]
Où est passé le "wtf"
? Est-ce dû à un effet particulier du yield from
? Validons cela,
2.
def some_func ( x ):
if x == 3 :
return [ "wtf" ]
else :
for i in range ( x ):
yield i
Sortir:
> >> list ( some_func ( 3 ))
[]
Le même résultat, cela n'a pas fonctionné non plus.
return
avec des valeurs à l'intérieur des générateurs (voir PEP380). Les documents officiels disent que,"...
return expr
dans un générateur provoqueStopIteration(expr)
à la sortie du générateur."
Dans le cas de some_func(3)
, StopIteration
est déclenché au début en raison de l'instruction return
. L'exception StopIteration
est automatiquement interceptée dans le wrapper list(...)
et la boucle for
. Par conséquent, les deux extraits ci-dessus génèrent une liste vide.
Pour obtenir ["wtf"]
du générateur some_func
nous devons intercepter l'exception 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' )
Sortir:
> >> 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'
et 'nan'
sont des chaînes spéciales (insensibles à la casse) qui, lorsqu'elles sont explicitement transtypées en type float
, sont utilisées pour représenter respectivement "l'infini" mathématique et "pas un nombre".
Puisque selon les normes IEEE NaN != NaN
, le respect de cette règle brise l'hypothèse de réflexivité d'un élément de collection en Python, c'est-à-dire que si x
fait partie d'une collection comme list
, les implémentations comme la comparaison sont basées sur l'hypothèse que x == x
. En raison de cette hypothèse, l'identité est comparée en premier (puisque c'est plus rapide) lors de la comparaison de deux éléments, et les valeurs ne sont comparées que lorsque les identités ne correspondent pas. L'extrait suivant rendra les choses plus claires,
> >> x = float ( 'nan' )
> >> x == x , [ x ] == [ x ]
( False , True )
> >> y = float ( 'nan' )
> >> y == y , [ y ] == [ y ]
( False , True )
> >> x == y , [ x ] == [ y ]
( False , False )
Puisque les identités de x
et y
sont différentes, les valeurs sont considérées, qui sont également différentes ; par conséquent, la comparaison renvoie False
cette fois.
Lecture intéressante : La réflexivité et autres piliers de la civilisation
Cela peut sembler trivial si vous savez comment fonctionnent les références en Python.
some_tuple = ( "A" , "tuple" , "with" , "values" )
another_tuple = ([ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 ])
Sortir:
> >> 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 ])
Mais je pensais que les tuples étaient immuables...
Citation de https://docs.python.org/3/reference/datamodel.html
Séquences immuables Un objet de type séquence immuable ne peut pas changer une fois créé. (Si l'objet contient des références à d'autres objets, ces autres objets peuvent être mutables et modifiés ; cependant, la collection d'objets directement référencés par un objet immuable ne peut pas changer.)
L'opérateur +=
modifie la liste sur place. L'attribution d'éléments ne fonctionne pas, mais lorsque l'exception se produit, l'élément a déjà été modifié sur place.
Il y a aussi une explication dans la FAQ officielle de Python.
e = 7
try :
raise Exception ()
except Exception as e :
pass
Sortie (Python 2.x) :
> >> print ( e )
# prints nothing
Sortie (Python 3.x) :
> >> print ( e )
NameError : name 'e' is not defined
Source : https://docs.python.org/3/reference/compound_stmts.html#sauf
Lorsqu'une exception a été affectée en utilisant as
cible, elle est effacée à la fin de la clause except
. C'est comme si
except E as N :
foo
a été traduit en
except E as N :
try :
foo
finally :
del N
Cela signifie que l'exception doit être affectée à un nom différent pour pouvoir y faire référence après la clause except. Les exceptions sont supprimées car, avec le traçage qui leur est attaché, elles forment un cycle de référence avec le cadre de pile, gardant en vie tous les éléments locaux de ce cadre jusqu'à ce que le prochain garbage collection ait lieu.
Les clauses ne sont pas étendues en Python. Tout dans l'exemple est présent dans la même portée et la variable e
a été supprimée en raison de l'exécution de la clause except
. Il n’en va pas de même avec les fonctions qui ont leurs portées internes distinctes. L'exemple ci-dessous illustre ceci:
def f ( x ):
del ( x )
print ( x )
x = 5
y = [ 5 , 4 , 3 ]
Sortir:
> >> f ( x )
UnboundLocalError : local variable 'x' referenced before assignment
>> > f ( y )
UnboundLocalError : local variable 'x' referenced before assignment
>> > x
5
> >> y
[ 5 , 4 , 3 ]
Dans Python 2.x, le nom de variable e
est affecté à l'instance Exception()
, donc lorsque vous essayez d'imprimer, il n'imprime rien.
Sortie (Python 2.x):
> >> e
Exception ()
> >> print e
# Nothing is printed!
class SomeClass ( str ):
pass
some_dict = { 's' : 42 }
Sortir:
> >> 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
L'objet s
et le hachage de la chaîne "s"
à la même valeur car SomeClass
hérite de la méthode __hash__
de la classe str
.
SomeClass("s") == "s"
évalue à True
car SomeClass
hérite également de la méthode __eq__
de la classe str
.
Étant donné que les deux objets hachent la même valeur et sont égaux, ils sont représentés par la même clé dans le dictionnaire.
Pour le comportement souhaité, nous pouvons redéfinir la méthode __eq__
dans 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 }
Sortir:
> >> 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
Sortir:
> >> a
{ 5 : ({...}, 5 )}
(target_list "=")+ (expression_list | yield_expression)
Une instruction d'attribution évalue la liste d'expression (n'oubliez pas que cela peut être une seule expression ou une liste séparée par des virgules, ce dernier produisant un tuple) et attribue l'objet résultant unique à chacune des listes cibles, de gauche à droite.
Le +
dans (target_list "=")+
signifie qu'il peut y avoir une ou plusieurs listes cibles. Dans ce cas, les listes cibles sont a, b
et a[b]
(notez que la liste d'expression est exactement une, qui dans notre cas est {}, 5
).
Une fois la liste d'expression évaluée, sa valeur est déballée aux listes cibles de gauche à droite . Ainsi, dans notre cas, d'abord le {}, 5
tuple est déballé en a, b
et nous avons maintenant a = {}
et b = 5
.
a
est maintenant attribué à {}
, qui est un objet mutable.
La deuxième liste cible est a[b]
(vous pouvez vous attendre à ce que cela lance une erreur car a
et b
n'ont pas été définis dans les instructions précédemment. Mais rappelez-vous, nous avons juste attribué a
à {}
et b
à 5
).
Maintenant, nous définissons la clé 5
du dictionnaire du Tuple ({}, 5)
Création d'une référence circulaire (le {...}
dans la sortie fait référence au même objet que a
référence déjà). Un autre exemple plus simple de référence circulaire pourrait être
> >> 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
Similaire est le cas dans notre exemple ( a[b][0]
est le même objet que a
)
Donc, pour résumer, vous pouvez décomposer l'exemple
a , b = {}, 5
a [ b ] = a , b
Et la référence circulaire peut être justifiée par le fait qu'un a[b][0]
est le même objet a
> >> a [ b ][ 0 ] is a
True
> >> # Python 3.10.6
>> > int ( "2" * 5432 )
> >> # Python 3.10.8
>> > int ( "2" * 5432 )
Sortir:
> >> # 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 .
Cet appel à int()
fonctionne bien dans Python 3.10.6 et soulève une valeur d'énergie dans Python 3.10.8. Notez que Python peut toujours fonctionner avec de grands entiers. L'erreur n'est augmentée que lors de la conversion entre les entiers et les chaînes.
Heureusement, vous pouvez augmenter la limite du nombre autorisé de chiffres lorsque vous vous attendez à ce qu'une opération le dépasse. Pour ce faire, vous pouvez utiliser l'un des éléments suivants:
Vérifiez la documentation pour plus de détails sur la modification de la limite par défaut si vous vous attendez à ce que votre code dépasse cette valeur.
x = { 0 : None }
for i in x :
del x [ i ]
x [ i + 1 ] = None
print ( i )
Sortie (Python 2.7- Python 3.5):
0
1
2
3
4
5
6
7
Oui, il fonctionne exactement pour huit fois et s'arrête.
RuntimeError: dictionary keys changed during iteration
si vous essayez de le faire.del
tenace class SomeClass :
def __del__ ( self ):
print ( "Deleted!" )
Sortie: 1.
> >> x = SomeClass ()
> >> y = x
> >> del x # this should print "Deleted!"
> >> del y
Deleted !
Ouf, supprimé enfin. Vous avez peut-être deviné ce qui a sauvé __del__
d'être appelé dans notre première tentative de supprimer x
. Ajoutons plus de rebondissements à l'exemple.
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 }
D'accord, maintenant c'est supprimé?
del x
n'appelle pas directement x.__del__()
.del x
est rencontré, Python supprime le nom x
de la portée actuelle et diminue de 1 le nombre de référence de l'objet x
référencé. __del__()
est appelé uniquement lorsque le nombre de références de l'objet atteint zéro.__del__()
n'a pas été appelé parce que l'instruction précédente ( >>> y
) dans l'interpréteur interactif a créé une autre référence au même objet (en particulier, la variable magique _
qui fait référence à la valeur de résultat du dernier non None
Expression sur le REP), empêchant ainsi le nombre de références d'atteindre zéro lorsque del y
a été rencontré.globals
(ou vraiment, exécuter tout ce qui aura un résultat non None
) a fait référence à _
le nouveau résultat, supprimant la référence existante. Maintenant, le nombre de références a atteint 0 et nous pouvons voir "Supprimé!" être imprimé (enfin!).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 ()
Sortir:
> >> 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
Lorsque vous effectuez une affectation à une variable dans la portée, elle devient locale à cette portée. Ainsi, a
devient local à la portée d' another_func
, mais il n'a pas été initialisé précédemment dans la même portée, ce qui lance une erreur.
Pour modifier la variable de portée extérieure a
dans another_func
, nous devons utiliser le mot-clé global
.
def another_func ()
global a
a += 1
return a
Sortir:
> >> another_func ()
2
Dans another_closure_func
, a
devient local à la portée d' another_inner_func
, mais il n'a pas été initialisé précédemment dans la même portée, c'est pourquoi il lance une erreur.
Pour modifier la variable de portée extérieure a
dans another_inner_func
, utilisez le mot-clé nonlocal
. L'énoncé non local est utilisé pour faire référence aux variables définies dans la portée extérieure (à l'exclusion de la globale) la plus proche.
def another_func ():
a = 1
def another_inner_func ():
nonlocal a
a += 1
return a
return another_inner_func ()
Sortir:
> >> another_func ()
2
Les mots clés global
et nonlocal
indiquent à l'interpréteur Python de ne pas déclarer de nouvelles variables et de les rechercher dans les lunettes extérieures correspondantes.
Lisez ce court-circuit mais un guide génial pour en savoir plus sur le fonctionnement des espaces de noms et de la portée de la portée dans 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 )
Sortir:
> >> list_1
[ 1 , 2 , 3 , 4 ]
> >> list_2
[ 2 , 4 ]
> >> list_3
[]
> >> list_4
[ 2 , 4 ]
Pouvez-vous deviner pourquoi la sortie est [2, 4]
?
Ce n'est jamais une bonne idée de changer l'objet sur lequel vous itérez. La bonne façon de le faire est d'itérer à la place une copie de l'objet, et list_3[:]
fait exactement cela.
> >> some_list = [ 1 , 2 , 3 , 4 ]
> >> id ( some_list )
139798789457608
> >> id ( some_list [:]) # Notice that python creates new object for sliced list.
139798779601192
Différence entre del
, remove
et pop
:
del var_name
supprime simplement la liaison du var_name
de l'espace de noms local ou global (c'est pourquoi le list_1
n'est pas affecté).remove
supprime la première valeur de correspondance, pas un index spécifique, augmente ValueError
si la valeur n'est pas trouvée.pop
supprime l'élément à un index spécifique et le renvoie, augmente IndexError
si un index non valide est spécifié. Pourquoi la sortie est [2, 4]
?
1
de list_2
ou list_4
, le contenu des listes est maintenant [2, 3, 4]
. Les éléments restants sont décalés, c'est-à-dire que 2
est à l'index 0, et 3
est à l'indice 1. Étant donné que la prochaine itération va regarder l'index 1 (qui est le 3
), le 2
est complètement ignoré. Une chose similaire se produira avec chaque élément alternatif de la séquence de liste. > >> 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 )]
Où est passé l'élément 3
de la liste 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
en leur appelant la fonction next
et s'arrête chaque fois que l'une des itérables est épuisée.result
sont jetés. C'est ce qui s'est passé avec 3
dans les numbers_iter
.zip
serait, > >> 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' )
Sortir:
6 : for x inside loop
6 : x in global
Mais x
n'a jamais été défini en dehors de la portée de la boucle ...
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' )
Sortir:
6 : for x inside loop
6 : x in global
3.
Sortie (Python 2.x):
> >> x = 1
> >> print ([ x for x in range ( 5 )])
[ 0 , 1 , 2 , 3 , 4 ]
> >> print ( x )
4
Sortie (Python 3.x):
> >> x = 1
> >> print ([ x for x in range ( 5 )])
[ 0 , 1 , 2 , 3 , 4 ]
> >> print ( x )
1
Dans Python, les boucles pour utilisent la portée dans laquelle ils existent et laissent leur variable de boucle définie derrière. Cela s'applique également si nous définissons explicitement la variable de boucle pour l'espace de noms global auparavant. Dans ce cas, il rebindonnera la variable existante.
Les différences dans la sortie de Python 2.x et Python 3.x Les interprètes pour l'exemple de compréhension de la liste peuvent être expliqués en suivant la modification documentée dans ce qui est nouveau dans Python 3.0 Changelog:
"Les compréhensions de la liste ne prennent plus en charge le formulaire syntaxique
[... for var in item1, item2, ...]
. Utilisez[... for var in (item1, item2, ...)]
à la place. De plus, notez cette liste Les compréhensions ont une sémantique différente: elles sont plus proches du sucre syntaxique pour une expression de générateur dans un constructeurlist()
, et en particulier, les variables de contrôle de la boucle ne sont plus divulguées dans la portée environnante. "
def some_func ( default_arg = []):
default_arg . append ( "some_string" )
return default_arg
Sortir:
> >> some_func ()
[ 'some_string' ]
> >> some_func ()
[ 'some_string' , 'some_string' ]
> >> some_func ([])
[ 'some_string' ]
> >> some_func ()
[ 'some_string' , 'some_string' , 'some_string' ]
Les arguments mutables par défaut des fonctions dans Python ne sont pas vraiment initialisés à chaque fois que vous appelez la fonction. Au lieu de cela, la valeur récemment attribuée leur est utilisée comme valeur par défaut. Lorsque nous avons explicitement transmis []
à some_func
comme argument, la valeur par défaut de la variable default_arg
n'a pas été utilisée, donc la fonction est retournée comme prévu.
def some_func ( default_arg = []):
default_arg . append ( "some_string" )
return default_arg
Sortir:
> >> 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' ],)
Une pratique courante pour éviter les bogues en raison d'arguments mutables consiste à en attribuer None
comme valeur par défaut et à vérifier ultérieurement si une valeur est transmise à la fonction correspondant à cet argument. Exemple:
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!" )
Sortie (Python 2.x):
Caught !
ValueError : list . remove ( x ): x not in list
Sortie (Python 3.x):
File "<input>" , line 3
except IndexError , ValueError :
^
SyntaxError : invalid syntax
Pour ajouter plusieurs exceptions à la clause expressive, vous devez les transmettre en tant que tuple parenthésié comme premier argument. Le deuxième argument est un nom facultatif qui, lorsqu'il est fourni liera l'instance d'exception qui a été soulevée. Exemple,
some_list = [ 1 , 2 , 3 ]
try :
# This should raise a ``ValueError``
some_list . remove ( 4 )
except ( IndexError , ValueError ), e :
print ( "Caught again!" )
print ( e )
Sortie (Python 2.x):
Caught again!
list.remove(x): x not in list
Sortie (Python 3.x):
File "<input>" , line 4
except ( IndexError , ValueError ), e :
^
IndentationError : unindent does not match any outer indentation level
La séparation de l'exception de la variable avec une virgule est obsolète et ne fonctionne pas dans Python 3; La bonne façon est d'utiliser as
. Exemple,
some_list = [ 1 , 2 , 3 ]
try :
some_list . remove ( 4 )
except ( IndexError , ValueError ) as e :
print ( "Caught again!" )
print ( e )
Sortir:
Caught again!
list.remove(x): x not in list
1.
a = [ 1 , 2 , 3 , 4 ]
b = a
a = a + [ 5 , 6 , 7 , 8 ]
Sortir:
> >> 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 ]
Sortir:
> >> a
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
> >> b
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
a += b
ne se comporte pas toujours de la même manière que a = a + b
. Les classes peuvent implémenter les opérateurs op=
et les listes le font.
L'expression a = a + [5,6,7,8]
génère une nouvelle liste et définit a
référence à cette nouvelle liste, laissant b
inchangé.
L'expression a += [5,6,7,8]
est en fait mappée à une fonction "étendue" qui fonctionne sur la liste de telle sorte que a
et b
pointent toujours vers la même liste qui a été modifiée en place.
1.
x = 5
class SomeClass :
x = 17
y = ( x for i in range ( 10 ))
Sortir:
> >> list ( SomeClass . y )[ 0 ]
5
2.
x = 5
class SomeClass :
x = 17
y = [ x for i in range ( 10 )]
Sortie (Python 2.x):
> >> SomeClass . y [ 0 ]
17
Sortie (Python 3.x):
> >> SomeClass . y [ 0 ]
5
Implémentons une fonction naïve pour obtenir l'élément central d'une liste:
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
Il semble que Python ait arrondi 2,5 à 2.
round()
utilise l'arrondi Banker où .5 fractions sont arrondies au numéro pair le plus proche: > >> 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])
n'a renvoyé que 1 parce que l'index était round(0.5) - 1 = 0 - 1 = -1
, renvoyant le dernier élément de la liste.Je n'ai même pas rencontré une seule expérience pythoniste jusqu'à ce jour qui n'a pas rencontré un ou plusieurs des scénarios suivants,
1.
x , y = ( 0 , 1 ) if True else None , None
Sortir:
> >> 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 )
Sortir:
one
two
o
n
e
tuple ()
3.
ten_words_list = [
"some",
"very",
"big",
"list",
"that"
"consists",
"of",
"exactly",
"ten",
"words"
]
Sortir
> >> len ( ten_words_list )
9
4. Ne pas affirmer assez fortement
a = "python"
b = "javascript"
Sortir:
# 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 })
Sortir:
> >> 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
Sortir:
> >> some_recursive_func ([ 5 , 0 ])
[ 0 , 0 ]
> >> similar_recursive_func ( 5 )
4
Pour 1, l'instruction correcte pour le comportement attendu est x, y = (0, 1) if True else (None, None)
.
Pour 2, l'instruction correcte pour le comportement attendu est t = ('one',)
ou t = 'one',
(virgule manquante) sinon l'interprète considère t
comme un str
et itérate sur le caractère par caractère.
()
est un jeton spécial et indique tuple
vide.
En 3, comme vous l'avez peut-être déjà compris, il y a une virgule manquante après le 5ème élément ( "that"
) dans la liste. Donc par la concaténation littérale implicite de cordes,
> >> ten_words_list
[ 'some' , 'very' , 'big' , 'list' , 'thatconsists' , 'of' , 'exactly' , 'ten' , 'words' ]
Aucune AssertionError
n'a été élevée dans le 4ème extrait car au lieu d'affirmer l'expression individuelle a == b
, nous affirmons des tuple entiers. L'extrait suivant effacera les choses,
> >> 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
En ce qui concerne le cinquième extrait, la plupart des méthodes qui modifient les éléments d'objets de séquence / mappage comme list.append
, dict.update
, list.sort
, etc. Modifiez les objets en place et renvoyez None
. La justification derrière cela est d'améliorer les performances en évitant de faire une copie de l'objet si l'opération peut être effectuée en place (référée à partir d'ici).
Le dernier devrait être assez évident, l'objet mutable (comme list
) peut être modifié dans la fonction, et la réaffectation d'un immuable ( a -= 1
) n'est pas une altération de la valeur.
Être conscient de ces nitpicks peut vous faire économiser des heures d'effort de débogage à long terme.
> >> 'a' . split ()
[ 'a' ]
# is same as
> >> 'a' . split ( ' ' )
[ 'a' ]
# but
> >> len ( '' . split ())
0
# isn't the same as
> >> len ( '' . split ( ' ' ))
1
' '
, mais selon les documentsSi le SEP n'est pas spécifié ou
None
, un algorithme de fractionnement différent est appliqué: les exécutions d'espace blanc consécutif sont considérées comme un seul séparateur, et le résultat ne contiendra aucune chaîne vide au début ou à la fin si la chaîne a un espace blanc menaçant ou traînant. Par conséquent, la division d'une chaîne vide ou d'une chaîne composée d'espace blanc avec un séparateur non renvoie[]
. Si SEP est donné, les délimiteurs consécutifs ne sont pas regroupés et sont réputés délimiter les chaînes vides (par exemple,'1,,2'.split(',')
renvoie['1', '', '2']
). La division d'une chaîne vide avec un séparateur spécifié renvoie['']
.
> >> ' a ' . split ( ' ' )
[ '' , 'a' , '' ]
> >> ' a ' . split ()
[ 'a' ]
> >> '' . split ( ' ' )
[ '' ]
# File: module.py
def some_weird_name_func_ ():
print ( "works!" )
def _another_weird_name_func ():
print ( "works!" )
Sortir
> >> 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
Il est souvent conseillé de ne pas utiliser les importations génériques. La première raison évidente à cela est, dans les importations de joker, les noms avec un soulignement principal ne sont pas importés. Cela peut entraîner des erreurs pendant l'exécution.
Si nous avions utilisé from ... import a, b, c
Syntaxe, le NameError
ci-dessus n'aurait pas eu lieu.
> >> from module import some_weird_name_func_ , _another_weird_name_func
> >> _another_weird_name_func ()
works !
Si vous souhaitez vraiment utiliser les importations de jogners, vous devrez définir la liste __all__
dans votre module qui contiendra une liste d'objets publics qui seront disponibles lorsque nous effectuerons des importations de joker.
__all__ = [ '_another_weird_name_func' ]
def some_weird_name_func_ ():
print ( "works!" )
def _another_weird_name_func ():
print ( "works!" )
Sortir
> >> _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
La méthode sorted
renvoie toujours une liste, et la comparaison des listes et des tuples renvoie toujours False
dans Python.
> >> [] == tuple ()
False
> >> x = 7 , 8 , 9
> >> type ( x ), type ( sorted ( x ))
( tuple , list )
Contrairement à sorted
, la méthode reversed
renvoie un itérateur. Pourquoi? Parce que le tri nécessite que l'itérateur soit modifié en place ou utilise un conteneur supplémentaire (une liste), tandis que le revers peut simplement fonctionner en itérant du dernier index vers le premier.
Ainsi, pendant la comparaison sorted(y) == sorted(y)
, le premier appel à sorted()
consommera l'itérateur y
, et l'appel suivant renvoie une liste vide.
> >> 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 )
Sortie (<3,5):
( 'Time at noon is' , datetime . time ( 12 , 0 ))
L'heure de minuit n'est pas imprimée.
Avant Python 3.5, la valeur booléenne pour datetime.time
Objet a été considérée comme False
si elle représentait minuit en UTC. Il est sujet aux erreurs lors de l'utilisation de la syntaxe if obj:
pour vérifier si l' obj
est nul ou un équivalent de "vide".
Cette section contient quelques choses moins connues et intéressantes à propos de Python que la plupart des débutants comme moi ignorent (enfin, plus).
Eh bien, c'est parti
import antigravity
Sortie: SSHH ... c'est un super secret.
antigravity
est l'un des rares œufs de Pâques publiés par les développeurs de Python.import antigravity
ouvre un navigateur Web pointant vers la bande dessinée classique XKCD sur Python.goto
, mais pourquoi? 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!" )
Sortie (Python 2.3):
I am trapped , please rescue !
I am trapped , please rescue !
Freedom !
goto
in Python a été annoncée comme une blague de poisson d'avril le 1er avril 2004.goto
n'est pas présent à Python.Si vous êtes l'une des personnes qui n'aiment pas utiliser Whitespace dans Python pour désigner des lunettes, vous pouvez utiliser le style C {} en importation,
from __future__ import braces
Sortir:
File "some_file.py" , line 1
from __future__ import braces
SyntaxError : not a chance
Croisillons? Certainement pas! Si vous pensez que c'est décevant, utilisez Java. D'accord, une autre chose surprenante, pouvez-vous trouver où est la SyntaxError
soulevée dans le code du module __future__
?
__future__
est normalement utilisé pour fournir des fonctionnalités de futures versions de Python. L'exécution de «l'avenir» dans ce contexte spécifique est cependant ironique.future.c
.future.c
avant de le traiter comme une instruction d'importation normale.Sortie (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
Et voilà.
Ceci est pertinent pour PEP-401 publié le 1er avril 2009 (maintenant vous savez, ce que cela signifie).
Citation du PEP-401
A reconnu que l'opérateur d'inégalité! = Inégalité dans Python 3.0 était une horrible erreur induisant des doigts, le Flufl réinstara l'opérateur <> diamant comme l'orthographe unique.
Il y avait plus de choses que l'oncle Barry a dû partager dans le PEP; Vous pouvez les lire ici.
Il fonctionne bien dans un environnement interactif, mais il augmentera une SyntaxError
lorsque vous exécutez via un fichier Python (voir ce problème). Cependant, vous pouvez envelopper la déclaration dans une eval
ou compile
pour le faire fonctionner,
from __future__ import barry_as_FLUFL
print ( eval ( '"Ruby" <> "Python"' ))
import this
Attendez, qu'est-ce que c'est ? this
amour ❤️
Sortir:
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!
C'est le zen de 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
module de Python est un œuf de Pâques pour le zen de Python (PEP 20).love is not True or False; love is love
, ironique mais il est explicite (sinon, veuillez consulter les exemples liés à is
et is not
des opérateurs). La clause else
pour les boucles. Un exemple typique pourrait être:
def does_exists_num ( l , to_find ):
for num in l :
if num == to_find :
print ( "Exists!" )
break
else :
print ( "Does not exist" )
Sortir:
> >> some_list = [ 1 , 2 , 3 , 4 , 5 ]
> >> does_exists_num ( some_list , 4 )
Exists !
>> > does_exists_num ( some_list , - 1 )
Does not exist
La clause else
dans la gestion des exceptions. Un exemple,
try :
pass
except :
print ( "Exception occurred!!!" )
else :
print ( "Try block executed successfully..." )
Sortir:
Try block executed successfully ...
else
après une boucle n'est exécutée que lorsqu'il n'y a pas break
explicite après toutes les itérations. Vous pouvez le considérer comme une clause de "Nobreak".else
après un bloc d'essai est également appelée "clause de complétion" car atteindre la clause else
dans une déclaration try
signifie que le bloc d'essai s'est effectivement terminé avec succès. def some_func ():
Ellipsis
Sortir
> >> 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
est un objet intégré disponible à l'échelle mondiale qui équivaut à ...
> >> ...
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
est donc un tableau de tableaux de tableaux. Disons que nous voulons imprimer le deuxième élément (index 1
) de tous les tableaux les plus intimes, nous pouvons utiliser des ellipsis pour contourner toutes les dimensions précédentes > >> 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, ...]
))L'orthographe est prévue. S'il vous plaît, ne soumettez pas un patch pour cela.
Sortie (Python 3.x):
> >> infinity = float ( 'infinity' )
> >> hash ( infinity )
314159
> >> hash ( float ( '-inf' ))
- 314159
float('-inf')
est "-10⁵ x π" dans Python 3, tandis que "-10⁵ xe" dans Python 2.1.
class Yo ( object ):
def __init__ ( self ):
self . __honey = True
self . bro = True
Sortir:
> >> 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
Sortir:
> >> 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__'
Pourquoi Yo()._Yo__honey
a-t-il travaillé?
3.
_A__variable = "Some value"
class A ( object ):
def some_func ( self ):
return __variable # not initialized anywhere yet
Sortir:
> >> 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'
__
(double soulignement aka "Dunder") et ne se terminant pas avec plus d'un soulignement de fuite en ajoutant _NameOfTheClass
devant.__honey
dans le premier extrait, nous avons dû ajouter _Yo
à l'avant, ce qui empêcherait les conflits avec l'attribut de même nom défini dans toute autre classe.__variable
dans l'instruction return __variable
a été mutilé à _A__variable
, qui se trouve également être le nom de la variable que nous avons déclaré dans la portée extérieure.Sortir:
> >> value = 11
> >> valuе = 32
> >> value
11
Quoi ?
Remarque: La façon la plus simple de reproduire ceci est de copier simplement les instructions de l'extrait ci-dessus et de les coller dans votre fichier / shell.
Certains caractères non occidentaux semblent identiques aux lettres de l'alphabet anglais mais sont considérés comme distincts par l'interprète.
> >> 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
La fonction ord()
intégrée renvoie le point de code Unicode d'un personnage, et différentes positions de code de Cyrillic 'E' et Latin 'E' justifier le comportement de l'exemple ci-dessus.
# `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 ()
Sortir:
> >> energy_send ( 123.456 )
> >> energy_receive ()
123.456
Où est le prix Nobel?
energy_send
n'est pas retourné, de sorte que l'espace mémoire est libre de réaffecter.numpy.empty()
renvoie le prochain emplacement de mémoire libre sans le réinitialiser. Cet spot de mémoire se trouve être le même qui a été libéré (généralement, mais pas toujours). 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
Sortie (Python 2.x):
> >> square ( 10 )
10
Cela ne devrait-il pas être 100?
Remarque: Si vous n'êtes pas en mesure de reproduire cela, essayez d'exécuter le fichier mixtes_tabs_and_spaces.py via le shell.
Ne mélangez pas les onglets et les espaces! Le personnage qui vient d'être précédent est un "onglet", et le code est en retrait par plusieurs "4 espaces" ailleurs dans l'exemple.
C'est ainsi que Python gère les onglets:
Tout d'abord, les onglets sont remplacés (de gauche à droite) par un à huit espaces de telle sorte que le nombre total de caractères jusqu'à et y compris le remplacement est un multiple de huit <...>
Ainsi, le "Tab" à la dernière ligne de fonction square
est remplacé par huit espaces, et il entre dans la boucle.
Python 3 est assez gentil pour lancer automatiquement une erreur pour ces cas.
Sortie (Python 3.x):
TabError : inconsistent use of tabs and spaces in indentation
+=
est plus rapide # 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
+=
est plus rapide que +
pour concaténer plus de deux chaînes car la première chaîne (exemple, s1
pour s1 += s2 + s3
) n'est pas détruite lors du calcul de la chaîne complète. 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
Sortir:
# 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 )
Augmenons le nombre d'itérations d'un facteur 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 )
Vous pouvez en savoir plus sur TimeIt ou% TimeIt sur ces liens. Ils sont utilisés pour mesurer le temps d'exécution des pièces de code.
N'utilisez pas +
pour générer de longues chaînes - en Python, str
est immuable, donc les cordes gauche et droite doivent être copiées dans la nouvelle chaîne pour chaque paire de concaténations. Si vous concaténez quatre chaînes de longueur 10, vous copieriez (10 + 10) + ((10 + 10) +10) + (((10 + 10) +10) +10) = 90 caractères au lieu de seulement 40 personnages. Les choses s'aggravent quadratiquement à mesure que le nombre et la taille de la chaîne augmentent (justifiés avec les temps d'exécution de la fonction add_bytes_with_plus
)
Par conséquent, il est conseillé d'utiliser .format.
ou %
syntaxe (cependant, ils sont légèrement plus lents que +
pour les chaînes très courtes).
Ou mieux, si vous avez déjà du contenu disponible sous la forme d'un objet itérable, utilisez ''.join(iterable_object)
qui est beaucoup plus rapide.
Contrairement à add_bytes_with_plus
en raison des +=
optimisations discutées dans l'exemple précédent, add_string_with_plus
n'a pas montré une augmentation quadratique du temps d'exécution. Si l'instruction avait été s = s + "x" + "y" + "z"
au lieu de s += "xyz"
, l'augmentation aurait été quadratique.
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 )
Tant de façons de formater et de créer une chaîne géante sont quelque peu contrastées avec le zen de Python, selon lequel,
Il devrait y en avoir un ... et de préférence une seule façon de le faire.
dict
* some_dict = { str ( i ): 1 for i in range ( 1_000_000 )}
another_dict = { str ( i ): 1 for i in range ( 1_000_000 )}
Sortir:
> >> % 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 )
Pourquoi les mêmes recherches deviennent-elles plus lentes?
str
, int
, n'importe quel objet ...), et spécialisé pour le cas commun de dictionnaires composés de clés str
.lookdict_unicode
dans la source de CPYTHON) connaît toutes les touches existantes (y compris la clé de recherche), et utilise la comparaison de chaînes plus rapide et plus simple pour comparer les clés, au lieu d'appeler la méthode __eq__
.dict
est accessible avec une touche non str
, elle est modifiée afin que les recherches futures utilisent la fonction générique.dict
particulière, et la clé n'a même pas besoin d'exister dans le dictionnaire. C'est pourquoi tenter une recherche ratée a le même effet.dict
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__ )
Sortie: (Python 3.8, d'autres versions Python 3 peuvent varier un peu)
> >> 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
Essayons à nouveau ... dans un nouvel interprète:
> >> 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
Qu'est-ce qui rend ces dictionnaires gonflés? Et pourquoi les objets nouvellement créés sont-ils également gonflés?
__init__
de la toute première instance créée, sans provoquer un "sans partage"). Si plusieurs instances existent lorsqu'un redimensionnement se produit, le partage des clés est désactivé pour toutes les futures instances de la même classe: Cpython ne peut plus dire si vos instances utilisent le même ensemble d'attributs, et décide de renflouer clés.__init__
! join()
est une opération de chaîne au lieu d'une opération de liste. (une sorte de contre-intuitif à première vue)
Explication: Si join()
est une méthode sur une chaîne, elle peut fonctionner sur n'importe quel itérable (liste, tuple, itérateurs). S'il s'agissait d'une méthode sur une liste, elle devrait être implémentée séparément par tous les types. De plus, il n'a pas beaucoup de sens de mettre une méthode spécifique à une chaîne sur une API d'objet list
générique.
Peu de déclarations étranges mais sémantiquement correctes:
[] = ()
est une déclaration sémantiquement correcte (déballer un tuple
vide dans une list
vide)'a'[0][0][0][0][0]
est également sémantiquement correct, car Python n'a pas de type de données de caractère comme d'autres langues ramifiées à partir de C. donc sélectionner un seul caractère à partir d'une chaîne renvoie un chaîne à un seul caractères.3 --0-- 5 == 8
et --5 == 5
sont tous deux des instructions sémantiquement correctes et évaluent à True
. Étant donné que a
est un nombre, ++a
et --a
sont tous deux des instructions Python valides mais ne se comportent pas de la même manière par rapport à des instructions similaires dans des langues comme C, C ++ ou Java.
> >> a = 5
> >> a
5
> >> + + a
5
> >> - - a
5
Explication:
++
en grammaire Python. Il s'agit en fait de deux opérateurs +
.++a
parses as +(+a)
qui se traduit par a
. De même, la sortie de l'instruction --a
peut être justifiée.Vous devez être conscient de l'opérateur de morse à Python. Mais avez-vous déjà entendu parler de l'opérateur de l'invader spatial ?
> >> a = 42
> >> a -= - 1
> >> a
43
Il est utilisé comme opérateur d'incrément alternatif, avec un autre
> >> a += + 1
> >> a
> >> 44
Explication: Cette farce vient du tweet de Raymond Hettinger. L'opérateur d'espace envahisseur n'est en fait qu'un a -= (-1)
malformatté. Ce qui équivaut à a = a - (- 1)
. Similaire pour le cas a += (+ 1)
.
Python a un opérateur d'inverse inverse sans papiers.
> >> False ** False == True
True
> >> False ** True == False
True
> >> True ** False == True
True
> >> True ** True == True
True
Explication: Si vous remplacez False
et True
par 0 et 1 et faites le calcul, la table de vérité est équivalente à un opérateur d'inverse d'implication. (Source)
Puisque nous parlons d'opérateurs, il y a aussi @
opérateur pour la multiplication matricielle (ne vous inquiétez pas, cette fois, c'est pour de vrai).
> >> import numpy as np
> >> np . array ([ 2 , 2 , 2 ]) @ np . array ([ 7 , 8 , 8 ])
46
Explication: L'opérateur @
a été ajouté dans Python 3.5 en gardant à l'esprit la communauté scientifique. Tout objet peut surcharger la méthode magique __matmul__
pour définir le comportement de cet opérateur.
À partir de Python 3.8, vous pouvez utiliser une syntaxe typique de la chaîne F comme f'{some_var=}
pour un débogage rapide. Exemple,
> >> some_string = "wtfpython"
> >> f' { some_string = } '
"some_string='wtfpython'"
Python utilise 2 octets pour le stockage des variables locales dans les fonctions. En théorie, cela signifie que seulement 65536 variables peuvent être définies dans une fonction. Cependant, Python a une solution pratique intégrée qui peut être utilisée pour stocker plus de 2 ^ 16 noms de variables. Le code suivant montre ce qui se passe dans la pile lorsque plus de 65536 variables locales sont définies (avertissement: ce code imprime environ 2 ^ 18 lignes de texte, alors soyez préparé!):
import dis
exec ( """
def f():
""" + """
""" . join ([ "X" + str ( x ) + "=" + str ( x ) for x in range ( 65539 )]))
f ()
print ( dis . dis ( f ))
Plusieurs threads Python n'en exécuteront pas votre code Python simultanément (oui, vous l'avez bien entendu!). Il peut sembler intuitif de pulvériser plusieurs threads et de les permettre d'exécuter votre code Python simultanément, mais, en raison du verrouillage mondial de l'interprète dans Python, tout ce que vous faites est de faire s'exécuter à son tour sur le même tour de base. Les fils Python sont bons pour les tâches liées à IO, mais pour obtenir une parallélisation réelle dans Python pour les tâches liées au CPU, vous voudrez peut-être utiliser le module de multiprocessement Python.
Parfois, la méthode print
peut ne pas imprimer des valeurs immédiatement. Par exemple,
# File some_file.py
import time
print ( "wtfpython" , end = "_" )
time . sleep ( 3 )
Cela imprimera le wtfpython
après 3 secondes en raison de l'argument end
car le tampon de sortie est rincé soit après la rencontre n
soit lorsque le programme termine l'exécution. Nous pouvons forcer le tampon à rincer en passant flush=True
Argument.
Liste des tranches avec les indices hors limites ne lance aucune erreur
> >> some_list = [ 1 , 2 , 3 , 4 , 5 ]
> >> some_list [ 111 :]
[]
Séliquer un itérable ne crée pas toujours un nouvel objet. Par exemple,
> >> 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('١٢٣٤٥٦٧٨٩')
Renvoie 123456789
dans Python 3. Dans Python, les caractères décimaux incluent des caractères de chiffre et tous les caractères qui peuvent être utilisés pour former des numéros de radix décimaux, par exemple U + 0660, le chiffre arabe-indicatif Zero. Voici une histoire intéressante liée à ce comportement de Python.
Vous pouvez séparer les littéraux numériques avec des soulignements (pour une meilleure lisibilité) à partir de Python 3.
> >> six_million = 6_000_000
> >> six_million
6000000
> >> hex_address = 0xF00D_CAFE
> >> hex_address
4027435774
'abc'.count('') == 4
. Voici une mise en œuvre approximative de la méthode count
, qui rendrait les choses plus claires
def count ( s , sub ):
result = 0
for i in range ( len ( s ) + 1 - len ( sub )):
result += ( s [ i : i + len ( sub )] == sub )
return result
Le comportement est dû à la correspondance de la sous-chaîne vide ( ''
) avec des tranches de longueur 0 dans la chaîne d'origine.
Quelques façons dont vous pouvez contribuer à wtfpython,
Veuillez consulter contribution.md pour plus de détails. N'hésitez pas à créer un nouveau problème pour discuter des choses.
PS: Veuillez ne pas contacter avec les demandes de backlinking, aucun lien ne sera ajouté à moins qu'ils ne soient très pertinents pour le projet.
L'idée et la conception de cette collection ont été initialement inspirées par le projet impressionnant de Denys Dovhan WTFJS. Le soutien écrasant de Pythonistas lui a donné la forme dans laquelle il se trouve actuellement.
© 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.