Erkunden und verstehen Sie Python anhand überraschender Schnipsel.
Übersetzungen: Chinesisch 中文 | Vietnamesisch Tiếng Việt | Spanisch Spanisch | Koreanisch 한국어 | Russisch Русский | Deutsch Deutsch | Übersetzung hinzufügen
Andere Modi: Interaktive Website | Interaktives Notizbuch
Python, eine wunderschön gestaltete High-Level- und Interpreter-basierte Programmiersprache, bietet uns viele Funktionen für den Komfort des Programmierers. Aber manchmal scheinen die Ergebnisse eines Python-Snippets auf den ersten Blick nicht offensichtlich zu sein.
Hier ist ein unterhaltsames Projekt, das versucht zu erklären, was genau unter der Haube einiger kontraintuitiver Snippets und weniger bekannter Funktionen in Python passiert.
Während einige der Beispiele, die Sie unten sehen, möglicherweise keine WTFs im wahrsten Sinne des Wortes sind, enthüllen sie einige der interessanten Teile von Python, die Ihnen möglicherweise nicht bekannt sind. Ich finde es eine schöne Möglichkeit, die Interna einer Programmiersprache zu erlernen, und ich glaube, dass Sie es auch interessant finden werden!
Wenn Sie ein erfahrener Python-Programmierer sind, können Sie es als Herausforderung ansehen, die meisten davon gleich beim ersten Versuch richtig zu machen. Vielleicht haben Sie einige davon schon einmal erlebt, und vielleicht kann ich schöne alte Erinnerungen an Sie wieder aufleben lassen! ?
PS: Wenn Sie ein wiederkehrender Leser sind, können Sie sich hier über die neuen Änderungen informieren (die mit einem Sternchen markierten Beispiele sind diejenigen, die in der letzten Hauptrevision hinzugefügt wurden).
Also, los geht's...
is
Bediener nichtis not ...
ist nicht is (not ...)
del
Bedienunggoto
, aber warum?+=
ist schnellerdict
*dict
*Alle Beispiele sind wie folgt aufgebaut:
▶ Ein ausgefallener Titel
# Set up the code. # Preparation for the magic...Ausgabe (Python-Version(en)):
> >> triggering_statement Some unexpected output(Optional): Eine Zeile, die die unerwartete Ausgabe beschreibt.
Erläuterung:
- Kurze Erklärung, was passiert und warum es passiert.
# Set up code # More examples for further clarification (if necessary)Ausgabe (Python-Version(en)):
> >> trigger # some example that makes it easy to unveil the magic # some justified output
Hinweis: Alle Beispiele wurden mit dem interaktiven Interpreter Python 3.5.2 getestet und sollten für alle Python-Versionen funktionieren, sofern dies nicht ausdrücklich vor der Ausgabe angegeben wird.
Eine gute Möglichkeit, das Beste aus diesen Beispielen herauszuholen, besteht meiner Meinung nach darin, sie der Reihe nach zu lesen, und zwar für jedes Beispiel:
Aus irgendeinem Grund ist der „Walross“-Operator ( :=
) von Python 3.8 sehr beliebt geworden. Schauen wir es uns an,
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
Schnelle Auffrischung des Walross-Operators
Der Walross-Operator ( :=
) wurde in Python 3.8 eingeführt und kann in Situationen nützlich sein, in denen Sie Variablen innerhalb eines Ausdrucks Werte zuweisen möchten.
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 )
Ausgabe (> 3,8):
5
5
5
Dadurch wurde eine Codezeile eingespart und implizit verhindert, dass some_func
zweimal aufgerufen wurde.
Der „Zuweisungsausdruck“ ohne Klammern (Verwendung des Walross-Operators) ist auf der obersten Ebene eingeschränkt, daher der SyntaxError
in der a := "wtf_walrus"
-Anweisung des ersten Snippets. Das Klammern hat wie erwartet funktioniert und a
zugewiesen.
Wie üblich ist das Klammern eines Ausdrucks, der =
-Operator enthält, nicht zulässig. Daher der Syntaxfehler in (a, b = 6, 9)
.
Die Syntax des Walrus-Operators hat die Form NAME:= expr
, wobei NAME
ein gültiger Bezeichner und expr
ein gültiger Ausdruck ist. Daher wird iterierbares Packen und Entpacken nicht unterstützt, was bedeutet,
(a := 6, 9)
ist äquivalent zu ((a := 6), 9)
und letztendlich (a, 9)
(wobei der Wert von a
6 ist)
> >> ( a := 6 , 9 ) == (( a := 6 ), 9 )
True
> >> x = ( a := 696 , 9 )
> >> x
( 696 , 9 )
> >> x [ 0 ] is a # Both reference same memory location
True
Ebenso ist (a, b := 16, 19)
äquivalent zu (a, (b := 16), 19)
was nichts anderes als ein 3-Tupel ist.
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.
Ausgabe (< Python3.7 )
> >> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
> >> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False
Macht doch Sinn, oder?
'wtf'
wird interniert, aber ''.join(['w', 't', 'f'])
wird nicht interniert)'wtf!'
wurde nicht interniert wegen !
. Die CPython-Implementierung dieser Regel finden Sie hier a
und b
auf "wtf!"
gesetzt sind. In derselben Zeile erstellt der Python-Interpreter ein neues Objekt und verweist dann gleichzeitig auf die zweite Variable. Wenn Sie es in separaten Zeilen tun, „weiß“ es nicht, dass es bereits "wtf!"
gibt. als Objekt (weil "wtf!"
gemäß den oben genannten Fakten nicht implizit interniert ist). Es handelt sich um eine Optimierung zur Kompilierungszeit. Diese Optimierung gilt nicht für 3.7.x-Versionen von CPython (weitere Informationen finden Sie in diesem Problem).a, b = "wtf!", "wtf!"
ist eine einzelne Anweisung, wohingegen a = "wtf!"; b = "wtf!"
sind zwei Anweisungen in einer einzigen Zeile. Dies erklärt, warum die Identitäten in a = "wtf!"; b = "wtf!"
und erklären Sie auch, warum sie gleich sind, wenn sie in some_file.py
aufgerufen werden'a'*20
beim Kompilieren durch 'aaaaaaaaaaaaaaaaaaaa'
ersetzt wird, um zur Laufzeit einige Taktzyklen zu sparen. Eine konstante Faltung erfolgt nur für Zeichenfolgen mit einer Länge von weniger als 21. (Warum? Stellen Sie sich die Größe der .pyc
Datei vor, die als Ergebnis des Ausdrucks 'a'*10**10
generiert wird). Hier ist die Implementierungsquelle dafür. > >> ( 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
Gemäß https://docs.python.org/3/reference/expressions.html#comparisons
Wenn a, b, c, ..., y, z Ausdrücke sind und op1, op2, ..., opN Vergleichsoperatoren sind, dann ist a op1 b op2 c ... y opN z äquivalent zu a op1 b und b op2 c und ... y opN z, außer dass jeder Ausdruck höchstens einmal ausgewertet wird.
Während Ihnen ein solches Verhalten in den obigen Beispielen albern erscheinen mag, ist es bei Dingen wie a == b == c
und 0 <= x <= 100
fantastisch.
False is False is False
ist äquivalent zu (False is False) and (False is False)
True is False == False
ist äquivalent zu (True is False) and (False == False)
und da der erste Teil der Anweisung ( True is False
) zu False
ausgewertet wird, ergibt der Gesamtausdruck False
.1 > 0 < 1
entspricht (1 > 0) and (0 < 1)
was als True
ausgewertet wird.(1 > 0) < 1
entspricht True < 1
und > >> int ( True )
1
> >> True + 1 #not relevant for this example, but just for fun
2
1 < 1
als False
ausgewertetis
Bediener nichtDas Folgende ist ein sehr berühmtes Beispiel, das im gesamten Internet zu finden ist.
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. Ausgabe
> >> a , b = 257 , 257
> >> a is b
True
Ausgabe (speziell Python 3.7.x)
> >> a , b = 257 , 257
> >> a is b
False
Der Unterschied zwischen is
und ==
is
Operator prüft, ob beide Operanden auf dasselbe Objekt verweisen (dh er prüft, ob die Identität der Operanden übereinstimmt oder nicht).==
Operator vergleicht die Werte beider Operanden und prüft, ob sie gleich sind.is
bedeutet also Referenzgleichheit und ==
Wertgleichheit. Ein Beispiel zur Aufklärung, > >> class A : pass
> >> A () is A () # These are two empty objects at two different memory locations.
False
256
ist ein vorhandenes Objekt, 257
jedoch nicht
Beim Starten von Python werden die Zahlen von -5
bis 256
zugewiesen. Diese Zahlen werden häufig verwendet, daher ist es sinnvoll, sie einfach bereitzuhalten.
Zitat aus https://docs.python.org/3/c-api/long.html
Die aktuelle Implementierung behält ein Array ganzzahliger Objekte für alle Ganzzahlen zwischen -5 und 256 bei. Wenn Sie in diesem Bereich ein int erstellen, erhalten Sie lediglich einen Verweis auf das vorhandene Objekt zurück. Es sollte also möglich sein, den Wert 1 zu ändern. Ich vermute, dass das Verhalten von Python in diesem Fall undefiniert ist. :-)
> >> 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
Hier ist der Interpreter bei der Ausführung y = 257
nicht schlau genug, um zu erkennen, dass wir bereits eine Ganzzahl mit dem Wert 257,
und erstellt daher ein weiteres Objekt im Speicher.
Eine ähnliche Optimierung gilt auch für andere unveränderliche Objekte wie leere Tupel. Da Listen veränderbar sind, gibt [] is []
False
und () is ()
True
zurück. Dies erklärt unseren zweiten Snippet. Kommen wir zum dritten,
Sowohl a
als auch b
verweisen auf dasselbe Objekt, wenn sie mit demselben Wert in derselben Zeile initialisiert werden.
Ausgabe
> >> a , b = 257 , 257
> >> id ( a )
140640774013296
> >> id ( b )
140640774013296
> >> a = 257
> >> b = 257
> >> id ( a )
140640774013392
> >> id ( b )
140640774013488
Wenn a und b in derselben Zeile auf 257
gesetzt werden, erstellt der Python-Interpreter ein neues Objekt und verweist dann gleichzeitig auf die zweite Variable. Wenn Sie es in separaten Zeilen tun, „weiß“ es nicht, dass es bereits 257
als Objekt gibt.
Es handelt sich um eine Compiler-Optimierung und gilt speziell für die interaktive Umgebung. Wenn Sie zwei Zeilen in einen Live-Interpreter eingeben, werden diese separat kompiliert und daher separat optimiert. Wenn Sie dieses Beispiel in einer .py
Datei ausprobieren würden, würden Sie nicht dasselbe Verhalten feststellen, da die Datei vollständig auf einmal kompiliert wird. Diese Optimierung ist nicht auf Ganzzahlen beschränkt, sie funktioniert auch für andere unveränderliche Datentypen wie Strings (sehen Sie sich das Beispiel „Strings sind knifflig“) und Floats an.
> >> a , b = 257.0 , 257.0
> >> a is b
True
Warum hat das bei Python 3.7 nicht funktioniert? Der abstrakte Grund liegt darin, dass solche Compiler-Optimierungen spezifisch für die Implementierung sind (dh sie können sich je nach Version, Betriebssystem usw. ändern). Ich finde immer noch heraus, welche genauen Implementierungsänderungen das Problem verursachen. Sie können dieses Problem auf Aktualisierungen überprüfen.
1.
some_dict = {}
some_dict [ 5.5 ] = "JavaScript"
some_dict [ 5.0 ] = "Ruby"
some_dict [ 5 ] = "Python"
Ausgabe:
> >> 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"
Warum ist Python also überall zu finden?
Die Eindeutigkeit von Schlüsseln in einem Python-Wörterbuch beruht auf Äquivalenz , nicht auf Identität. Auch wenn 5
, 5.0
und 5 + 0j
unterschiedliche Objekte unterschiedlichen Typs sind, können sie nicht beide im selben dict
(oder set
) enthalten sein, da sie gleich sind. Sobald Sie einen von ihnen einfügen, wird der Versuch, nach einem eindeutigen, aber äquivalenten Schlüssel zu suchen, mit dem ursprünglich zugeordneten Wert erfolgreich sein (anstatt mit einem KeyError
fehlzuschlagen):
> >> 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
Dies gilt auch beim Festlegen eines Elements. Wenn Sie also some_dict[5] = "Python"
ausführen, findet Python das vorhandene Element mit dem entsprechenden Schlüssel 5.0 -> "Ruby"
, überschreibt seinen Wert an Ort und Stelle und lässt den ursprünglichen Schlüssel in Ruhe.
> >> some_dict
{ 5.0 : 'Ruby' }
> >> some_dict [ 5 ] = "Python"
> >> some_dict
{ 5.0 : 'Python' }
Wie können wir also den Schlüssel auf 5
(anstelle von 5.0
) aktualisieren? Wir können dieses Update nicht direkt durchführen, aber wir können zuerst den Schlüssel löschen ( del some_dict[5.0]
) und ihn dann festlegen ( some_dict[5]
), um die Ganzzahl 5
als Schlüssel statt als Gleitkomma zu erhalten 5.0
, obwohl dies in seltenen Fällen erforderlich sein sollte.
Wie hat Python 5
in einem Wörterbuch gefunden, das 5.0
enthält? Python erledigt dies in konstanter Zeit, ohne jedes Element mithilfe von Hash-Funktionen durchsuchen zu müssen. Wenn Python in einem Diktat nach einem Schlüssel foo
sucht, berechnet es zunächst hash(foo)
(der in konstanter Zeit ausgeführt wird). Da es in Python erforderlich ist, dass Objekte, die gleich sind, auch denselben Hash-Wert haben (Dokumente hier), haben 5
, 5.0
und 5 + 0j
denselben Hash-Wert.
> >> 5 == 5.0 == 5 + 0j
True
> >> hash ( 5 ) == hash ( 5.0 ) == hash ( 5 + 0j )
True
Hinweis: Das Gegenteil ist nicht unbedingt der Fall: Objekte mit gleichen Hashwerten können selbst ungleich sein. (Dies führt zu einer sogenannten Hash-Kollision und beeinträchtigt die Leistung in konstanter Zeit, die Hashing normalerweise bietet.)
class WTF :
pass
Ausgabe:
> >> 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
Als id
aufgerufen wurde, erstellte Python ein WTF
Klassenobjekt und übergab es an die id
-Funktion. Die id
-Funktion übernimmt ihre id
(ihren Speicherort) und wirft das Objekt weg. Das Objekt wird zerstört.
Wenn wir dies zweimal hintereinander tun, weist Python auch diesem zweiten Objekt denselben Speicherort zu. Da (in CPython) id
den Speicherort als Objekt-ID verwendet, ist die ID der beiden Objekte dieselbe.
Die ID des Objekts ist also nur für die Lebensdauer des Objekts eindeutig. Nachdem das Objekt zerstört wurde oder bevor es erstellt wurde, kann etwas anderes dieselbe ID haben.
Aber warum hat der is
Operator False
ergeben? Mal sehen, mit diesem Ausschnitt.
class WTF ( object ):
def __init__ ( self ): print ( "I" )
def __del__ ( self ): print ( "D" )
Ausgabe:
> >> WTF () is WTF ()
I
I
D
D
False
> >> id ( WTF ()) == id ( WTF ())
I
D
I
D
True
Wie Sie vielleicht sehen, ist es die Reihenfolge, in der die Objekte zerstört werden, die hier den entscheidenden Unterschied macht.
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
Ausgabe
> >> 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
Was ist hier los?
Der Grund, warum die intransitive Gleichheit zwischen dictionary
, ordered_dict
und another_ordered_dict
nicht galt, liegt in der Art und Weise, wie die Methode __eq__
in der Klasse OrderedDict
implementiert ist. Aus den Dokumenten
Gleichheitstests zwischen OrderedDict-Objekten sind reihenfolgesensitiv und werden als
list(od1.items())==list(od2.items())
implementiert. Gleichheitstests zwischenOrderedDict
-Objekten und anderen Mapping-Objekten sind wie normale Wörterbücher unabhängig von der Reihenfolge.
Der Grund für diese Verhaltensgleichheit liegt darin, dass OrderedDict
Objekte überall dort direkt ersetzt werden können, wo ein reguläres Wörterbuch verwendet wird.
Okay, warum hat sich die Änderung der Reihenfolge auf die Länge des generierten set
-Objekts ausgewirkt? Die Antwort ist nur das Fehlen intransitiver Gleichheit. Da Mengen „ungeordnete“ Sammlungen eindeutiger Elemente sind, sollte die Reihenfolge, in der Elemente eingefügt werden, keine Rolle spielen. Aber in diesem Fall ist es wichtig. Lassen Sie es uns ein wenig aufschlüsseln,
> >> 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
Die Inkonsistenz liegt also daran, dass another_ordered_dict in another_set
False
ist, da ordered_dict
bereits in another_set
vorhanden war und ordered_dict == another_ordered_dict
wie zuvor beobachtet „ False
ist.
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 )
Ausgabe:
> >> 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
oder continue
-Anweisung in der try
Suite einer „try…finally“-Anweisung ausgeführt wird, wird beim Ausgang auch die finally
Klausel ausgeführt.return
-Anweisung bestimmt. Da die finally
Klausel immer ausgeführt wird, ist eine in der finally
Klausel ausgeführte return
Anweisung immer die letzte, die ausgeführt wird.return
oder break
-Anweisung ausführt. some_string = "wtf"
some_dict = {}
for i , some_dict [ i ] in enumerate ( some_string ):
i = 10
Ausgabe:
> >> some_dict # An indexed dict appears.
{ 0 : 'w' , 1 : 't' , 2 : 'f' }
Eine for
-Anweisung ist in der Python-Grammatik wie folgt definiert:
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
Wobei exprlist
das Zuweisungsziel ist. Dies bedeutet, dass das Äquivalent von {exprlist} = {next_value}
für jedes Element im Iterable ausgeführt wird. Ein interessantes Beispiel, das dies verdeutlicht:
for i in range ( 4 ):
print ( i )
i = 10
Ausgabe:
0
1
2
3
Haben Sie erwartet, dass die Schleife nur einmal ausgeführt wird?
Erläuterung:
i = 10
wirkt sich aufgrund der Funktionsweise von for-Schleifen in Python niemals auf die Iterationen der Schleife aus. Vor Beginn jeder Iteration wird das nächste vom Iterator bereitgestellte Element (in diesem Fall range(4)
“) entpackt und den Ziellistenvariablen (in diesem Fall i
“) zugewiesen. Die Funktion enumerate(some_string)
liefert in jeder Iteration einen neuen Wert i
(einen Zähler, der nach oben geht) und ein Zeichen aus dem some_string
. Anschließend wird der (soeben zugewiesene) i
Schlüssel des Wörterbuchs some_dict
auf dieses Zeichen gesetzt. Das Abrollen der Schleife kann wie folgt vereinfacht werden:
> >> 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 ]
Ausgabe:
> >> 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 ]
Ausgabe:
> >> 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 ]
Ausgabe:
> >> print ( list ( gen ))
[ 401 , 501 , 601 , 402 , 502 , 602 , 403 , 503 , 603 ]
In einem Generatorausdruck wird die in
Klausel zur Deklarationszeit ausgewertet, die Bedingungsklausel jedoch zur Laufzeit.
Vor der Laufzeit wird array
also der Liste [2, 8, 22]
neu zugewiesen, und da von 1
, 8
und 15
nur die Anzahl von 8
größer als 0
ist, liefert der Generator nur 8
.
Die Unterschiede in der Ausgabe von g1
und g2
im zweiten Teil sind auf die Art und Weise zurückzuführen, wie den Variablen array_1
und array_2
neue Werte zugewiesen werden.
Im ersten Fall ist array_1
an das neue Objekt [1,2,3,4,5]
gebunden und da die in
Klausel zum Zeitpunkt der Deklaration ausgewertet wird, verweist sie immer noch auf das alte Objekt [1,2,3,4]
(die nicht zerstört wird).
Im zweiten Fall aktualisiert die Slice-Zuweisung zu array_2
dasselbe alte Objekt [1,2,3,4]
auf [1,2,3,4,5]
. Daher verweisen sowohl g2
als auch array_2
immer noch auf dasselbe Objekt (das jetzt auf [1,2,3,4,5]
aktualisiert wurde).
Okay, sollte der Wert von list(gen)
im dritten Snippet nach der bisher besprochenen Logik nicht [11, 21, 31, 12, 22, 32, 13, 23, 33]
sein? (weil array_3
und array_4
sich genauso verhalten werden wie array_1
). Der Grund, warum (nur) array_4
Werte aktualisiert wurden, wird in PEP-289 erläutert
Nur der äußerste for-Ausdruck wird sofort ausgewertet, die anderen Ausdrücke werden zurückgestellt, bis der Generator ausgeführt wird.
is not ...
ist nicht is (not ...)
> >> 'something' is not None
True
> >> 'something' is ( not None )
False
is not
ist ein einzelner binärer Operator und verhält sich anders als die Verwendung is
und not
getrennt.is not
als False
ausgewertet, wenn die Variablen auf beiden Seiten des Operators auf dasselbe Objekt verweisen, andernfalls als True
.(not None)
zu True
ausgewertet, da der Wert None
in einem booleschen Kontext False
ist, sodass der Ausdruck zu 'something' is True
wird. # Let's initialize a row
row = [ "" ] * 3 #row i['', '', '']
# Let's make a board
board = [ row ] * 3
Ausgabe:
> >> board
[[ '' , '' , '' ], [ '' , '' , '' ], [ '' , '' , '' ]]
> >> board [ 0 ]
[ '' , '' , '' ]
> >> board [ 0 ][ 0 ]
''
> >> board [ 0 ][ 0 ] = "X"
> >> board
[[ 'X' , '' , '' ], [ 'X' , '' , '' ], [ 'X' , '' , '' ]]
Wir haben nicht drei "X"
zugewiesen, oder?
Wenn wir row
initialisieren, erklärt diese Visualisierung, was im Speicher passiert
Und wenn das board
durch Multiplizieren von row
initialisiert wird, geschieht Folgendes im Speicher (jedes der Elemente board[0]
, board[1]
und board[2]
ist ein Verweis auf dieselbe Liste, auf row
verweist).
Wir können dieses Szenario hier vermeiden, indem wir keine row
zum Generieren board
verwenden. (In dieser Ausgabe gefragt).
> >> 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 ]
Ausgabe (Python-Version):
> >> results
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
> >> funcs_results
[ 6 , 6 , 6 , 6 , 6 , 6 , 6 ]
Die Werte von x
waren in jeder Iteration vor dem Anhängen von some_func
an funcs
unterschiedlich, aber alle Funktionen geben 6 zurück, wenn sie nach Abschluss der Schleife ausgewertet werden.
> >> 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
im umgebenden Kontext, anstatt den Wert von x
zum Zeitpunkt der Funktionserstellung zu verwenden. Daher verwenden alle Funktionen für die Berechnung den zuletzt der Variablen zugewiesenen Wert. Wir können sehen, dass es das x
aus dem umgebenden Kontext (also keine lokale Variable) verwendet mit: > >> import inspect
> >> inspect . getclosurevars ( funcs [ 0 ])
ClosureVars ( nonlocals = {}, globals = { 'x' : 6 }, builtins = {}, unbound = set ())
Da x
ein globaler Wert ist, können wir den Wert ändern, den die funcs
suchen und zurückgeben, indem wir x
aktualisieren:
> >> x = 42
> >> [ func () for func in funcs ]
[ 42 , 42 , 42 , 42 , 42 , 42 , 42 ]
x
zu diesem Zeitpunkt speichert. funcs = []
for x in range ( 7 ):
def some_func ( x = x ):
return x
funcs . append ( some_func )
Ausgabe:
> >> funcs_results = [ func () for func in funcs ]
> >> funcs_results
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
Das x
wird nicht mehr im globalen Bereich verwendet:
> >> inspect . getclosurevars ( funcs [ 0 ])
ClosureVars ( nonlocals = {}, globals = {}, builtins = {}, unbound = set ())
1.
> >> isinstance ( 3 , int )
True
> >> isinstance ( type , object )
True
> >> isinstance ( object , type )
True
Welches ist also die „ultimative“ Basisklasse? Hinter der Verwirrung steckt übrigens noch mehr,
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
ist eine Metaklasse in Python.object
, was sowohl Klassen als auch deren Objekte (Instanzen) umfasst.type
ist die Metaklasse der Klasse object
, und jede Klasse (einschließlich type
) hat direkt oder indirekt von object
geerbt.object
und type
. Die Verwirrung in den obigen Snippets entsteht, weil wir über diese Beziehungen ( issubclass
und isinstance
) im Hinblick auf Python-Klassen nachdenken. Die Beziehung zwischen object
und type
kann in reinem Python nicht reproduziert werden. Genauer gesagt können die folgenden Beziehungen nicht in reinem Python reproduziert werden:object
und type
(sowohl beide als Instanzen voneinander als auch von sich selbst) bestehen in Python aufgrund von „Betrug“ auf der Implementierungsebene.Ausgabe:
> >> from collections . abc import Hashable
> >> issubclass ( list , object )
True
> >> issubclass ( object , Hashable )
True
> >> issubclass ( list , Hashable )
False
Von den Unterklassenbeziehungen wurde erwartet, dass sie transitiv sind, oder? (Das heißt, wenn A
eine Unterklasse von B
ist und B
eine Unterklasse von C
ist, sollte A
eine Unterklasse von C
sein.)
__subclasscheck__
in einer Metaklasse definieren.issubclass(cls, Hashable)
aufgerufen wird, sucht es einfach nach einer Nicht-Falsey-Methode „ __hash__
“ in cls
oder nach etwas, von dem es erbt.object
hashbar ist, list
jedoch nicht hashbar ist, wird die Transitivitätsbeziehung unterbrochen. class SomeClass :
def method ( self ):
pass
@ classmethod
def classm ( cls ):
pass
@ staticmethod
def staticm ():
pass
Ausgabe:
> >> 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
Wenn wir zweimal auf classm
zugreifen, erhalten wir ein gleiches Objekt, aber nicht dasselbe ? Mal sehen, was mit Instanzen von SomeClass
passiert:
o1 = SomeClass ()
o2 = SomeClass ()
Ausgabe:
> >> 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
Wenn Sie zweimal auf classm
oder method
zugreifen, werden gleiche, aber nicht gleiche Objekte für dieselbe Instanz von SomeClass
erstellt.
self
als erstes Argument, obwohl wir es nicht explizit übergeben). > >> o1 . method
< bound method SomeClass . method of < __main__ . SomeClass object at ... >>
o1.method is o1.method
niemals wahr. Durch den Zugriff auf Funktionen als Klassenattribute (im Gegensatz zu Instanzen) werden jedoch keine Methoden erstellt. also SomeClass.method is SomeClass.method
ist wahr. > >> SomeClass . method
< function SomeClass . method at ... >
classmethod
wandelt Funktionen in Klassenmethoden um. Klassenmethoden sind Deskriptoren, die beim Zugriff ein Methodenobjekt erstellen, das die Klasse (den Typ) des Objekts bindet, und nicht das Objekt selbst. > >> o1 . classm
< bound method SomeClass . classm of < class '__main__.SomeClass' >>
classmethod
auch dann eine Methode, wenn sie als Klassenattribute aufgerufen werden (in diesem Fall binden sie die Klasse und nicht an deren Typ). SomeClass.classm is SomeClass.classm
ist falsch. > >> SomeClass . classm
< bound method SomeClass . classm of < class '__main__.SomeClass' >>
o1.method == o1.method
wahr, obwohl es sich nicht um dasselbe Objekt im Speicher handelt.staticmethod
wandelt Funktionen in einen „No-Op“-Deskriptor um, der die Funktion unverändert zurückgibt. Es werden nie Methodenobjekte erstellt, daher ist ein Vergleich mit is
wahr. > >> o1 . staticm
< function SomeClass . staticm at ... >
> >> SomeClass . staticm
< function SomeClass . staticm at ... >
self
zu beeinträchtigen. CPython 3.7 löste dieses Problem durch die Einführung neuer Opcodes, die sich mit dem Aufrufen von Methoden befassen, ohne die temporären Methodenobjekte zu erstellen. Dies wird nur verwendet, wenn die Funktion, auf die zugegriffen wird, tatsächlich aufgerufen wird, sodass die Snippets hier nicht betroffen sind und weiterhin Methoden generieren :) > >> all ([ True , True , True ])
True
> >> all ([ True , True , False ])
False
> >> all ([])
True
> >> all ([[]])
False
> >> all ([[[]]])
True
Warum ist diese Wahr-Falsch-Änderung?
Die Implementierung all
Funktionen ist äquivalent zu
def all ( iterable ):
for element in iterable :
if not element :
return False
return True
all([])
gibt True
zurück, da die Iterable leer ist.
all([[]])
gibt False
zurück, da das übergebene Array ein Element hat, []
, und in Python ist eine leere Liste falsch.
all([[[]]])
und höhere rekursive Varianten sind immer True
. Dies liegt daran, dass das einzelne Element des übergebenen Arrays ( [[...]]
) nicht mehr leer ist und Listen mit Werten wahr sind.
Ausgabe (< 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
Ausgabe:
> >> print ( " " " )
"
>> > print ( r""" )
"
>> > print ( r" " )
File "<stdin>" , line 1
print ( r" " )
^
SyntaxError : EOL while scanning string literal
>> > r''' == " \ '"
True
> >> "wt " f"
'wt"f'
r
angegeben) werden die Backslashes unverändert weitergegeben, zusammen mit dem Verhalten, das folgende Zeichen zu maskieren. > >> r'wt"f' == 'wt \ "f'
True
> >> print ( repr ( r'wt"f' ))
'wt \ "f'
> >> print ( " n " )
> >> print ( r"\n" )
' \ n'
print(r"")
) entging der Backslash dem abschließenden Anführungszeichen, sodass der Parser kein abschließendes Anführungszeichen mehr hatte (daher der SyntaxError
). Aus diesem Grund funktionieren Backslashes am Ende einer Rohzeichenfolge nicht. x = True
y = False
Ausgabe:
> >> not x == y
True
> >> x == not y
File "<input>" , line 1
x == not y
^
SyntaxError : invalid syntax
==
hat in Python eine höhere Priorität als der Operator not
.not x == y
gleichbedeutend mit not (x == y)
was gleichbedeutend damit ist, dass not (True == False)
schließlich zu True
ausgewertet wird.x == not y
löst einen SyntaxError
aus, da man davon ausgehen kann, dass es äquivalent zu (x == not) y
und nicht x == (not y)
ist, was Sie auf den ersten Blick vielleicht erwartet hätten.not
-Token Teil des not in
Operators ist (da beide ==
und not in
Operatoren die gleiche Priorität haben), aber nachdem er kein in
Token nach dem not
-Token finden konnte, löst er einen SyntaxError
aus.Ausgabe:
> >> 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
'''
und """
sind ebenfalls String-Trennzeichen in Python, die einen SyntaxError verursachen, da der Python-Interpreter beim Scannen des aktuell gefundenen String-Literals in dreifachen Anführungszeichen ein abschließendes dreifaches Anführungszeichen als Trennzeichen erwartet hat.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
Ausgabe:
> >> 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!" )
Ausgabe (< 3.x):
> >> tell_truth ()
I have lost faith in truth !
bool
ist eine Unterklasse von int
in Python
> >> issubclass ( bool , int )
True
> >> issubclass ( int , bool )
False
Und somit sind True
und False
Instanzen von int
> >> isinstance ( True , int )
True
> >> isinstance ( False , int )
True
Der ganzzahlige Wert von True
ist 1
und der von False
ist 0
.
> >> int ( True )
1
> >> int ( False )
0
Die Gründe dafür finden Sie in dieser StackOverflow-Antwort.
Anfangs hatte Python keinen bool
-Typ (die Leute verwendeten 0 für „falsch“ und einen Wert ungleich Null wie 1 für „wahr“). True
, False
und ein bool
-Typ wurden in 2.x-Versionen hinzugefügt, aber aus Gründen der Abwärtskompatibilität konnten True
und False
nicht zu Konstanten gemacht werden. Es handelte sich lediglich um integrierte Variablen, die neu zugewiesen werden konnten
Python 3 war abwärtsinkompatibel, das Problem wurde endlich behoben und daher funktioniert das letzte Snippet nicht mit Python 3.x!
1.
class A :
x = 1
class B ( A ):
pass
class C ( A ):
pass
Ausgabe:
> >> 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 ]
Ausgabe:
> >> 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
+=
-Operator ändert das veränderbare Objekt direkt, ohne ein neues Objekt zu erstellen. Das Ändern des Attributs einer Instanz wirkt sich also auch auf die anderen Instanzen und das Klassenattribut aus. some_iterable = ( 'a' , 'b' )
def some_func ( val ):
return "something"
Ausgabe (<= 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
in Generatoren und Verständnissen.yield
innerhalb des Listenverständnisses mehr und löst einen SyntaxError
aus.1.
def some_func ( x ):
if x == 3 :
return [ "wtf" ]
else :
yield from range ( x )
Ausgabe (> 3.3):
> >> list ( some_func ( 3 ))
[]
Wo ist das "wtf"
geblieben? Liegt es an einem besonderen yield from
? Lassen Sie uns das bestätigen,
2.
def some_func ( x ):
if x == 3 :
return [ "wtf" ]
else :
for i in range ( x ):
yield i
Ausgabe:
> >> list ( some_func ( 3 ))
[]
Das gleiche Ergebnis, das hat auch nicht funktioniert.
return
-Anweisungen mit Werten innerhalb von Generatoren zu verwenden (siehe PEP380). Die offiziellen Dokumente besagen, dass„...
return expr
in einem Generator führt dazu, dassStopIteration(expr)
beim Verlassen des Generators ausgelöst wird.“
Im Fall von some_func(3)
wird StopIteration
aufgrund der return
-Anweisung am Anfang ausgelöst. Die StopIteration
Ausnahme wird automatisch im list(...)
-Wrapper und in der for
Schleife abgefangen. Daher führen die beiden obigen Snippets zu einer leeren Liste.
Um ["wtf"]
vom Generator some_func
zu erhalten, müssen wir die StopIteration
Ausnahme abfangen.
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' )
Ausgabe:
> >> 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'
und 'nan'
sind spezielle Zeichenfolgen (ohne Berücksichtigung der Groß-/Kleinschreibung), die bei expliziter Typumwandlung in float
-Typ zur Darstellung der mathematischen „Unendlichkeit“ bzw. „keine Zahl“ verwendet werden.
Da gemäß den IEEE-Standards NaN != NaN
die Einhaltung dieser Regel die Reflexivitätsannahme eines Sammlungselements in Python bricht, d. h. wenn x
Teil einer Sammlung wie list
ist, basieren Implementierungen wie Vergleiche auf der Annahme, dass x == x
. Aufgrund dieser Annahme wird beim Vergleich zweier Elemente zuerst die Identität verglichen (da dies schneller ist), und die Werte werden nur dann verglichen, wenn die Identitäten nicht übereinstimmen. Der folgende Ausschnitt soll die Sache klarer machen:
> >> x = float ( 'nan' )
> >> x == x , [ x ] == [ x ]
( False , True )
> >> y = float ( 'nan' )
> >> y == y , [ y ] == [ y ]
( False , True )
> >> x == y , [ x ] == [ y ]
( False , False )
Da die Identitäten von x
und y
unterschiedlich sind, werden die Werte berücksichtigt, die ebenfalls unterschiedlich sind; Daher gibt der Vergleich dieses Mal False
zurück.
Interessante Lektüre: Reflexivität und andere Säulen der Zivilisation
Dies mag trivial erscheinen, wenn Sie wissen, wie Referenzen in Python funktionieren.
some_tuple = ( "A" , "tuple" , "with" , "values" )
another_tuple = ([ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 ])
Ausgabe:
> >> 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 ])
Aber ich dachte, Tupel seien unveränderlich ...
Zitat aus https://docs.python.org/3/reference/datamodel.html
Unveränderliche Sequenzen Ein Objekt eines unveränderlichen Sequenztyps kann sich nach seiner Erstellung nicht mehr ändern. (Wenn das Objekt Verweise auf andere Objekte enthält, können diese anderen Objekte veränderbar sein und geändert werden; die Sammlung von Objekten, auf die ein unveränderliches Objekt direkt verweist, kann sich jedoch nicht ändern.)
+=
-Operator ändert die Liste direkt. Die Artikelzuordnung funktioniert nicht, aber wenn die Ausnahme auftritt, wurde der Artikel bereits an Ort und Stelle geändert.
Es gibt auch eine Erklärung in den offiziellen Python-FAQ.
e = 7
try :
raise Exception ()
except Exception as e :
pass
Ausgabe (Python 2.x):
> >> print ( e )
# prints nothing
Ausgabe (Python 3.x):
> >> print ( e )
NameError : name 'e' is not defined
Quelle: https://docs.python.org/3/reference/compound_stmts.html#exclusive
Wenn eine Ausnahme mithilfe von as
target zugewiesen wurde, wird sie am Ende der except
-Klausel gelöscht. Das ist so, als ob
except E as N :
foo
wurde übersetzt in
except E as N :
try :
foo
finally :
del N
Dies bedeutet, dass der Ausnahme ein anderer Name zugewiesen werden muss, um nach der Ausnahmeklausel auf sie verweisen zu können. Ausnahmen werden gelöscht, da sie mit dem angehängten Traceback einen Referenzzyklus mit dem Stack-Frame bilden und alle lokalen Elemente in diesem Frame am Leben halten, bis die nächste Garbage Collection erfolgt.
Die Klauseln haben in Python keinen Gültigkeitsbereich. Alles im Beispiel liegt im selben Gültigkeitsbereich vor und die Variable e
wurde aufgrund der Ausführung der except
-Klausel entfernt. Gleiches gilt für Funktionen, die ihre getrennten inneren Szene haben. Das folgende Beispiel zeigt Folgendes:
def f ( x ):
del ( x )
print ( x )
x = 5
y = [ 5 , 4 , 3 ]
Ausgabe:
> >> f ( x )
UnboundLocalError : local variable 'x' referenced before assignment
>> > f ( y )
UnboundLocalError : local variable 'x' referenced before assignment
>> > x
5
> >> y
[ 5 , 4 , 3 ]
In Python 2.x wird der variable Name e
der Exception()
Instanz zugewiesen. Wenn Sie also versuchen, zu drucken, druckt er nichts.
Ausgabe (Python 2.x):
> >> e
Exception ()
> >> print e
# Nothing is printed!
class SomeClass ( str ):
pass
some_dict = { 's' : 42 }
Ausgabe:
> >> 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
"s"
str
__hash__
s
SomeClass
SomeClass("s") == "s"
bewertet True
, weil SomeClass
auch __eq__
-Methode aus str
-Klasse erbt.
Da beide Objekte denselben Wert hasht und gleich sind, werden sie im Wörterbuch durch denselben Schlüssel dargestellt.
Für das gewünschte Verhalten können wir die __eq__
-Methode in SomeClass
neu definieren
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 }
Ausgabe:
> >> 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
Ausgabe:
> >> a
{ 5 : ({...}, 5 )}
(target_list "=")+ (expression_list | yield_expression)
Eine Zuweisungsanweisung bewertet die Expressionsliste (denken Sie daran, dass dies ein einzelner Ausdruck oder eine von Kommas getrennte Liste sein kann, wobei letztere ein Tupel ergeben) und weist jedem der Ziellisten das einzelne resultierende Objekt zu, von links nach rechts.
Das +
in (target_list "=")+
bedeutet, dass es eine oder mehrere Ziellisten geben kann. In diesem Fall sind Ziellisten a, b
und a[b]
(beachten Sie, dass die Expressionsliste genau eine ist, die in unserem Fall {}, 5
) ist.
Nachdem die Expressionsliste bewertet wurde, wird ihr Wert von links nach rechts auf die Ziellisten ausgepackt. In unserem Fall wird zuerst das {}, 5
Tupel auf a, b
ausgepackt und wir haben jetzt a = {}
und b = 5
.
a
wird nun {}
zugeordnet, was ein veränderliches Objekt ist.
Die zweite Zielliste lautet a[b]
(Sie können erwarten, dass dies einen Fehler aufwirft, da sowohl a
als auch b
in den Aussagen vorher nicht definiert wurden. Aber denken Sie daran, wir haben gerade a
{}
und b
bis 5
zugewiesen).
Jetzt setzen wir den Schlüssel 5
im Wörterbuch zum Tupel ({}, 5)
Erstellen einer kreisförmigen Referenz (die {...}
in der Ausgabe bezieht sich auf dasselbe Objekt, das a
bereits bezieht). Ein weiteres einfacheres Beispiel für die kreisförmige Referenz könnte sein
> >> 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
Ähnlich ist in unserem Beispiel der Fall ( a[b][0]
ist dasselbe Objekt wie a
)
Um es zusammenzufassen, können Sie das Beispiel nach unten brechen
a , b = {}, 5
a [ b ] = a , b
Und die kreisförmige Referenz kann durch die Tatsache gerechtfertigt werden, dass a[b][0]
das gleiche Objekt wie a
ist
> >> a [ b ][ 0 ] is a
True
> >> # Python 3.10.6
>> > int ( "2" * 5432 )
> >> # Python 3.10.8
>> > int ( "2" * 5432 )
Ausgabe:
> >> # 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 .
Dieser Anruf bei int()
funktioniert in Python 3.10.6 einwandfrei und erhöht einen ValueError in Python 3.10.8. Beachten Sie, dass Python immer noch mit großen Ganzzahlen arbeiten kann. Der Fehler wird nur bei der Konvertierung zwischen Ganzzahlen und Zeichenfolgen erhöht.
Glücklicherweise können Sie die Grenze für die zulässige Anzahl von Ziffern erhöhen, wenn Sie erwarten, dass eine Operation sie überschreitet. Dazu können Sie eines der folgenden verwenden:
Überprüfen Sie die Dokumentation für weitere Informationen zum Ändern des Standardlimits, wenn Sie erwarten, dass Ihr Code diesen Wert überschreitet.
x = { 0 : None }
for i in x :
del x [ i ]
x [ i + 1 ] = None
print ( i )
Ausgabe (Python 2.7- Python 3.5):
0
1
2
3
4
5
6
7
Ja, es läuft genau acht Mal und hört auf.
RuntimeError: dictionary keys changed during iteration
wenn Sie versuchen, dies zu tun.del
Betrieb class SomeClass :
def __del__ ( self ):
print ( "Deleted!" )
Ausgabe: 1.
> >> x = SomeClass ()
> >> y = x
> >> del x # this should print "Deleted!"
> >> del y
Deleted !
Puh, endlich gelöscht. Möglicherweise haben Sie vermutet, was __del__
vor unserem ersten Versuch, x
zu löschen, gerettet zu haben. Fügen wir dem Beispiel weitere Wendungen hinzu.
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 }
Okay, jetzt wird es gelöscht?
del x
ruft x.__del__()
nicht direkt an.del x
auftritt, löscht Python den Namen x
aus dem aktuellen Bereich und verringert sich um 1 der Referenzzahl des verwiesenen Objekts x
__del__()
wird nur aufgerufen, wenn die Referenzzahl des Objekts Null erreicht.__del__()
nicht aufgerufen, da die vorherige Anweisung ( >>> y
) im interaktiven Interpreter einen None
Verweis auf _
Objekt erstellt hat Ausdruck auf der Repla), wodurch verhindert wird, dass die Referenzzahl Null erreicht, wenn del y
auftritt.globals
(oder wirklich, was etwas ausführt, das ein Nicht None
-Ergebnis hat), führte dazu, dass _
auf das neue Ergebnis verweist und die vorhandene Referenz fallen ließ. Jetzt erreichte die Referenzzahl 0 und wir können "gelöscht!" Sehen. gedruckt werden (endlich!).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 ()
Ausgabe:
> >> 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
Wenn Sie eine Zuordnung zu einer Variablen im Bereich treffen, wird sie zu diesem Umfang lokal. So wird a
zum Umfang eines another_func
lokal, aber es wurde bisher nicht im selben Bereich initialisiert, was einen Fehler verursacht.
Um die äußere Bereichsvariable a
in another_func
zu ändern, müssen wir das global
Schlüsselwort verwenden.
def another_func ()
global a
a += 1
return a
Ausgabe:
> >> another_func ()
2
In another_closure_func
wird a
zum Geltungsbereich eines another_inner_func
, aber es wurde zuvor nicht in demselben Bereich initialisiert, weshalb es einen Fehler macht.
Verwenden Sie das nonlocal
Schlüsselwort, um die äußere Bereichsvariable a
in another_inner_func
zu ändern. Die nichtlokale Anweisung wird verwendet, um auf Variablen zu verweisen, die im nächsten äußeren (ohne globalen) Bereich definiert sind.
def another_func ():
a = 1
def another_inner_func ():
nonlocal a
a += 1
return a
return another_inner_func ()
Ausgabe:
> >> another_func ()
2
Die Keywords global
und nonlocal
sagen den Python -Interpreter, keine neuen Variablen zu deklarieren und sie in den entsprechenden äußeren Teilen nach oben zu schauen.
Lesen Sie diesen kurzen, aber fantastischen Leitfaden, um mehr darüber zu erfahren, wie Namespaces und Scope -Auflösung in Python funktioniert.
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 )
Ausgabe:
> >> list_1
[ 1 , 2 , 3 , 4 ]
> >> list_2
[ 2 , 4 ]
> >> list_3
[]
> >> list_4
[ 2 , 4 ]
Können Sie sich erraten, warum die Ausgabe [2, 4]
ist?
Es ist nie eine gute Idee, das Objekt zu ändern, das Sie iterieren. Die richtige Möglichkeit, dies zu tun, besteht stattdessen darin, über eine Kopie des Objekts zu iterieren und genau das list_3[:]
.
> >> some_list = [ 1 , 2 , 3 , 4 ]
> >> id ( some_list )
139798789457608
> >> id ( some_list [:]) # Notice that python creates new object for sliced list.
139798779601192
Unterschied zwischen del
, remove
und pop
:
del var_name
entfernt nur die Bindung des var_name
aus dem lokalen oder globalen Namespace (deshalb ist die list_1
nicht betroffen).remove
Sie den ersten Übereinstimmungswert und nicht einen bestimmten Index, erhöht ValueError
, wenn der Wert nicht gefunden wird.pop
entfernt das Element in einem bestimmten Index und gibt es zurück, erhöht IndexError
, wenn ein ungültiger Index angegeben ist. Warum ist der Ausgang [2, 4]
?
1
aus list_2
oder list_4
entfernen, sind der Inhalt der Listen jetzt [2, 3, 4]
. Die verbleibenden Elemente werden nach unten verschoben, dh 2
befindet sich am Index 0, und 3
befindet sich in Index 1. Da die nächste Iteration Index 1 betrachtet wird (was die 3
ist) wird die 2
vollständig übersprungen. Ähnliches wird mit jedem alternativen Element in der List -Sequenz passieren. > >> 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 )]
Wohin ging Element 3
aus der 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
hinzu, indem Sie die next
Funktion auf sie aufrufen, und stoppt, wenn eines der iterablen erschöpft ist.result
verworfen. Das ist mit 3
im Nummern der numbers_iter
passiert.zip
zu tun, wäre, wäre, dass > >> 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' )
Ausgabe:
6 : for x inside loop
6 : x in global
Aber x
wurde nie außerhalb des Rahmens von für Schleife definiert ...
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' )
Ausgabe:
6 : for x inside loop
6 : x in global
3.
Ausgabe (Python 2.x):
> >> x = 1
> >> print ([ x for x in range ( 5 )])
[ 0 , 1 , 2 , 3 , 4 ]
> >> print ( x )
4
Ausgabe (Python 3.x):
> >> x = 1
> >> print ([ x for x in range ( 5 )])
[ 0 , 1 , 2 , 3 , 4 ]
> >> print ( x )
1
In Python verwenden For-Schleifen den Umfang, in dem sie existieren, und lassen ihre definierten Loop-Variablen hinter sich. Dies gilt auch, wenn wir die For-Loop-Variable im globalen Namespace zuvor ausdrücklich definiert haben. In diesem Fall wird es die vorhandene Variable wiederherstellen.
Die Unterschiede in der Ausgabe von Python 2.x und Python 3.x -Dolmetschern für das Beispiel für das Listenverständnis können erläutert werden, indem die in Python 3.0 Changelog dokumentierten Änderung dokumentiert wird:
"Listen Sie die Verständnisse auf, die das syntaktische Formular nicht mehr unterstützen
[... for var in item1, item2, ...]
. Verwenden Sie[... for var in (item1, item2, ...)]
Stattdessen. Beachten Sie auch diese Liste Verständnissen haben unterschiedliche Semantik: Sie sind näher am syntaktischen Zucker für einen Generatorausdruck in einemlist()
-Konstruktor, und insbesondere sind die Schleifensteuerungsvariablen nicht mehr in den Umgebungsbereich eingetragen. "
def some_func ( default_arg = []):
default_arg . append ( "some_string" )
return default_arg
Ausgabe:
> >> some_func ()
[ 'some_string' ]
> >> some_func ()
[ 'some_string' , 'some_string' ]
> >> some_func ([])
[ 'some_string' ]
> >> some_func ()
[ 'some_string' , 'some_string' , 'some_string' ]
Die standardmäßigen Veränderungsargumente der Funktionen in Python sind nicht jedes Mal initialisiert, wenn Sie die Funktion aufrufen. Stattdessen wird der kürzlich zugewiesene Wert ihnen als Standardwert verwendet. Wenn wir als Argument ausdrücklich []
an some_func
übergeben haben, wurde der Standardwert der Variablen default_arg
nicht verwendet, sodass die Funktion wie erwartet zurückgegeben wurde.
def some_func ( default_arg = []):
default_arg . append ( "some_string" )
return default_arg
Ausgabe:
> >> 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' ],)
Eine übliche Praxis, um Fehler aufgrund veränderlicher Argumente zu vermeiden, besteht darin, None
als Standardwert zuzuweisen und später zu prüfen, ob ein Wert an die Funktion übergeben wird, die diesem Argument entspricht. Beispiel:
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!" )
Ausgabe (Python 2.x):
Caught !
ValueError : list . remove ( x ): x not in list
Ausgabe (Python 3.x):
File "<input>" , line 3
except IndexError , ValueError :
^
SyntaxError : invalid syntax
Um der Ausnahmeklausel mehrere Ausnahmen hinzuzufügen, müssen Sie sie als erstklassiges Tupel als erstes Argument übergeben. Das zweite Argument ist ein optionaler Name, der bei der Lieferung die Ausnahmeinstanz bindet, die angehoben wurde. Beispiel,
some_list = [ 1 , 2 , 3 ]
try :
# This should raise a ``ValueError``
some_list . remove ( 4 )
except ( IndexError , ValueError ), e :
print ( "Caught again!" )
print ( e )
Ausgabe (Python 2.x):
Caught again!
list.remove(x): x not in list
Ausgabe (Python 3.x):
File "<input>" , line 4
except ( IndexError , ValueError ), e :
^
IndentationError : unindent does not match any outer indentation level
Die Trennung der Ausnahme von der Variablen mit einem Komma ist veraltet und funktioniert in Python 3 nicht; Der richtige Weg ist zu as
. Beispiel,
some_list = [ 1 , 2 , 3 ]
try :
some_list . remove ( 4 )
except ( IndexError , ValueError ) as e :
print ( "Caught again!" )
print ( e )
Ausgabe:
Caught again!
list.remove(x): x not in list
1.
a = [ 1 , 2 , 3 , 4 ]
b = a
a = a + [ 5 , 6 , 7 , 8 ]
Ausgabe:
> >> 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 ]
Ausgabe:
> >> a
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
> >> b
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
a += b
verhalten sich nicht immer genauso wie a = a + b
. Klassen können die op=
-Operatoren unterschiedlich implementieren, und Listen tun dies.
Der Ausdruck a = a + [5,6,7,8]
generiert eine neue Liste und setzt a
Verweis auf diese neue Liste, wobei b
unverändert bleibt.
Der Ausdruck a += [5,6,7,8]
wird tatsächlich einer "Erweiterung" -Funktion zugeordnet, die auf der Liste arbeitet, so dass a
und b
immer noch auf die gleiche Liste verweisen, die an Ort und Stelle geändert wurde.
1.
x = 5
class SomeClass :
x = 17
y = ( x for i in range ( 10 ))
Ausgabe:
> >> list ( SomeClass . y )[ 0 ]
5
2.
x = 5
class SomeClass :
x = 17
y = [ x for i in range ( 10 )]
Ausgabe (Python 2.x):
> >> SomeClass . y [ 0 ]
17
Ausgabe (Python 3.x):
> >> SomeClass . y [ 0 ]
5
Lassen Sie uns eine naive Funktion implementieren, um das mittlere Element einer Liste zu erhalten:
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
Es scheint, als hätte Python 2,5 zu 2 abgerundet.
round()
die Rundung von Banker, bei der 0,5 Fraktionen auf die nächste gleichmäßige Zahl gerundet sind: > >> 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])
nur 1 zurückgegeben hat, da der Index round(0.5) - 1 = 0 - 1 = -1
war und das letzte Element in der Liste zurückgab.Ich habe bis heute noch nicht einmal einen Pythonisten mit einer einzigen Erfahrung getroffen, die nicht auf eine oder mehrere der folgenden Szenarien gestoßen ist.
1.
x , y = ( 0 , 1 ) if True else None , None
Ausgabe:
> >> 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 )
Ausgabe:
one
two
o
n
e
tuple ()
3.
ten_words_list = [
"some",
"very",
"big",
"list",
"that"
"consists",
"of",
"exactly",
"ten",
"words"
]
Ausgabe
> >> len ( ten_words_list )
9
4.. Nicht stark genug behaupten
a = "python"
b = "javascript"
Ausgabe:
# 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 })
Ausgabe:
> >> 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
Ausgabe:
> >> some_recursive_func ([ 5 , 0 ])
[ 0 , 0 ]
> >> similar_recursive_func ( 5 )
4
Für 1 ist die korrekte Aussage für das erwartete Verhalten x, y = (0, 1) if True else (None, None)
.
Für 2 ist die korrekte Aussage für das erwartete Verhalten t = ('one',)
oder t = 'one',
(fehlendes Komma). Andernfalls betrachtet der Dolmetscher t
als ein str
und iteriert über It It Charakter durch Charakter.
()
ist ein spezielles Token und bezeichnet leere tuple
.
Wie Sie vielleicht bereits herausgefunden haben, gibt es in der Liste ein vermisstes Komma nach dem 5. Element ( "that"
). Also durch implizite String buchstäbliche Verkettung,
> >> ten_words_list
[ 'some' , 'very' , 'big' , 'list' , 'thatconsists' , 'of' , 'exactly' , 'ten' , 'words' ]
Kein AssertionError
wurde im 4. Snippet angehoben, da wir anstatt den individuellen Ausdruck a == b
zu behaupten, gründen wir das gesamte Tupel. Das folgende Ausschnitt wird die Dinge klären,
> >> 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
Was den fünften Snippet betrifft, so, dass die meisten Methoden, die die Elemente von Sequenz-/Zuordnungsobjekten wie list.append
, dict.update
, list.sort
usw. ändern, ändern und die Objekte einstellen und None
zurückgeben. Die Begründung dahinter besteht darin, die Leistung zu verbessern, indem vermieden wird, dass eine Kopie des Objekts vorgenommen wird, wenn der Vorgang angelegt werden kann (von hier aus genannt).
Last sollte man ziemlich offensichtlich sein, veränderbares Objekt (wie list
) kann in der Funktion verändert werden, und die Neuzuweisung eines unveränderlichen ( a -= 1
) ist keine Änderung des Wertes.
Wenn Sie sich dieser Nitpicks bewusst sind, können Sie auf lange Sicht stundenlange Debugging -Anstrengungen ersparen.
> >> 'a' . split ()
[ 'a' ]
# is same as
> >> 'a' . split ( ' ' )
[ 'a' ]
# but
> >> len ( '' . split ())
0
# isn't the same as
> >> len ( '' . split ( ' ' ))
1
' '
ist, aber laut den DokumentenWenn SEP nicht angegeben ist oder
None
ist, wird ein anderer Spaltalgorithmus angewendet: Läufe von aufeinanderfolgenden Whitespace werden als einzelner Trennscheider angesehen, und das Ergebnis enthält keine leeren Zeichenfolgen zu Beginn oder Ende, wenn die Saite führende oder nachgezogene Weißespace hat. Infolgedessen spaltet die Aufteilung einer leeren Zeichenfolge oder einer Zeichenfolge, die aus nur Whitespace mit einem None -Trennzeichen besteht[]
. Wenn SEP angegeben wird, werden aufeinanderfolgende Grenzwerte nicht zusammengefasst und gilt als leere Saiten (z. B.'1,,2'.split(',')
zurück['1', '', '2']
). Das Aufteilen einer leeren Zeichenfolge mit einem angegebenen Trennzeichen gibt['']
zurück.
> >> ' a ' . split ( ' ' )
[ '' , 'a' , '' ]
> >> ' a ' . split ()
[ 'a' ]
> >> '' . split ( ' ' )
[ '' ]
# File: module.py
def some_weird_name_func_ ():
print ( "works!" )
def _another_weird_name_func ():
print ( "works!" )
Ausgabe
> >> 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
Es ist oft ratsam, keine Wildcard -Importe zu verwenden. Der erste offensichtliche Grund dafür ist, dass in Wildcard -Importen die Namen mit einem führenden Unterstrich nicht importiert werden. Dies kann während der Laufzeit zu Fehlern führen.
Hätten wir from ... import a, b, c
-Syntax verwendet, wäre der obige NameError
nicht aufgetreten.
> >> from module import some_weird_name_func_ , _another_weird_name_func
> >> _another_weird_name_func ()
works !
Wenn Sie wirklich Wildcard -Importe verwenden möchten, müssen Sie die Liste __all__
in Ihrem Modul definieren, die eine Liste öffentlicher Objekte enthalten, die verfügbar sind, wenn wir Wildcard -Importe durchführen.
__all__ = [ '_another_weird_name_func' ]
def some_weird_name_func_ ():
print ( "works!" )
def _another_weird_name_func ():
print ( "works!" )
Ausgabe
> >> _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
Die sorted
Methode gibt immer eine Liste zurück, und das Vergleich von Listen und Tupeln gibt in Python immer False
zurück.
> >> [] == tuple ()
False
> >> x = 7 , 8 , 9
> >> type ( x ), type ( sorted ( x ))
( tuple , list )
Im Gegensatz zu sorted
gibt die reversed
Methode einen Iterator zurück. Warum? Da die Sortierung erfordert, dass der Iterator entweder an Ort und Stelle geändert wird oder einen zusätzlichen Container (eine Liste) verwendet, während die Umkehrung einfach durch Iterieren vom letzten Index zum ersten funktionieren kann.
Während des Vergleichs sorted(y) == sorted(y)
, wird der erste Anruf an sorted()
den Iterator y
konsumiert, und der nächste Anruf gibt nur eine leere Liste zurück.
> >> 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 )
Ausgabe (<3,5):
( 'Time at noon is' , datetime . time ( 12 , 0 ))
Die Mitternachtszeit ist nicht gedruckt.
Vor Python 3.5 wurde der Boolesche Wert für datetime.time
-Objekt als False
angesehen, wenn es in UTC Mitternacht dargestellt wurde. Es ist fehleranfällig, wenn die if obj:
Syntax verwendet wird, um zu überprüfen, ob der obj
null oder ein Äquivalent von "leer" ist.
Dieser Abschnitt enthält einige weniger bekannte und interessante Dinge über Python, von denen die meisten Anfänger wie ich nicht wissen (na ja, nicht mehr).
Nun, hier gehst du
import antigravity
Ausgabe: SSHH ... Es ist ein supergeheimer.
antigravity
-Modul ist eines der wenigen Ostereier, die von Python -Entwicklern freigesetzt werden.import antigravity
eröffnet einen Webbrowser, der auf den klassischen XKCD -Comic über Python zeigt.goto
, aber warum? 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!" )
Ausgabe (Python 2.3):
I am trapped , please rescue !
I am trapped , please rescue !
Freedom !
goto
in Python wurde am 1. April 2004 als Aprilscherz angekündigt.goto
in Python nicht präsent ist.Wenn Sie einer der Personen gehören, die nicht gerne Whitespace in Python verwenden, um Bereiche zu bezeichnen, können Sie den C-Stil {} durch Importieren verwenden.
from __future__ import braces
Ausgabe:
File "some_file.py" , line 1
from __future__ import braces
SyntaxError : not a chance
Zahnspange? Auf keinen Fall! Wenn Sie denken, dass das enttäuschend ist, verwenden Sie Java. Okay, eine andere überraschende Sache, können Sie feststellen, wo der SyntaxError
in __future__
-Modulcode aufgewachsen ist?
__future__
-Modul wird normalerweise verwendet, um Funktionen aus zukünftigen Versionen von Python bereitzustellen. Die "Zukunft" in diesem spezifischen Kontext ist jedoch ironisch.future.c
-Datei tatsächlich vorhanden.future.c
ausgesetzt, bevor er als normale Importanweisung behandelt wird.Ausgabe (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
Los geht's.
Dies ist relevant für PEP-401, die am 1. April 2009 veröffentlicht wurden (jetzt wissen Sie, was es bedeutet).
Zitat aus dem PEP-401
Der Flufl wurde erkannt, dass der Operator für!
Es gab mehr Dinge, die Onkel Barry am Pep teilen musste; Sie können sie hier lesen.
Es funktioniert in einer interaktiven Umgebung gut, aber es wird eine SyntaxError
erhöhen, wenn Sie über die Python -Datei ausgeführt werden (siehe dieses Problem). Sie können die Anweisung jedoch in eine eval
einwickeln oder compile
, um sie zum Laufen zu bringen.
from __future__ import barry_as_FLUFL
print ( eval ( '"Ruby" <> "Python"' ))
import this
Warten Sie, was ist das ? this
ist Liebe ❤️
Ausgabe:
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!
Es ist der Zen von 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
Modul in Python ist ein Osterei für den Zen von Python (Pep 20).love is not True or False; love is love
, ironisch, aber es ist selbsterklärend (wenn nicht, siehe die Beispiele, die mit is
und is not
). Die else
Klausel für Schleifen. Ein typisches Beispiel könnte sein:
def does_exists_num ( l , to_find ):
for num in l :
if num == to_find :
print ( "Exists!" )
break
else :
print ( "Does not exist" )
Ausgabe:
> >> some_list = [ 1 , 2 , 3 , 4 , 5 ]
> >> does_exists_num ( some_list , 4 )
Exists !
>> > does_exists_num ( some_list , - 1 )
Does not exist
Die else
Klausel in Ausnahmebehandlung. Ein Beispiel,
try :
pass
except :
print ( "Exception occurred!!!" )
else :
print ( "Try block executed successfully..." )
Ausgabe:
Try block executed successfully ...
else
Klausel nach einer Schleife wird nur ausgeführt, wenn nach all den Iterationen keine explizite break
vorliegt. Sie können es sich als "Nobreak" -Klausel vorstellen.else
wird die Klausel nach einem Try -Block auch als "Fertigstellungsklausel" bezeichnet, wenn die else
Klausel in einer try
erreicht ist, dass der Try -Block tatsächlich erfolgreich abgeschlossen wurde. def some_func ():
Ellipsis
Ausgabe
> >> 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
ein weltweit verfügbares integriertes Objekt, das ...
> >> ...
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
ist also ein Array von Arrays von Arrays. Nehmen wir an, wir möchten das zweite Element (Index 1
) aller innersten Arrays drucken. Wir können mit Ellipsis alle vorhergehenden Dimensionen umgehen > >> 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]
oder Tuple[str, ...]
))Die Schreibweise ist beabsichtigt. Bitte geben Sie dafür keinen Patch ein.
Ausgabe (Python 3.x):
> >> infinity = float ( 'infinity' )
> >> hash ( infinity )
314159
> >> hash ( float ( '-inf' ))
- 314159
float('-inf')
in Python 3 "-10⁵ x π", während "-10⁵ xe" in Python 2.1.
class Yo ( object ):
def __init__ ( self ):
self . __honey = True
self . bro = True
Ausgabe:
> >> 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
Ausgabe:
> >> 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__'
Warum hat Yo()._Yo__honey
gearbeitet?
3.
_A__variable = "Some value"
class A ( object ):
def some_func ( self ):
return __variable # not initialized anywhere yet
Ausgabe:
> >> 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'
__
(doppelter Unterstrich, auch bekannt als "Dunder") und endet nicht mit mehr als einem nachfolgenden Unterstrich durch Hinzufügen _NameOfTheClass
vor.__honey
-Attribut zuzugreifen, mussten wir _Yo
an die Front anhängen, was Konflikte mit dem in einer anderen Klasse definierten Attribut desselben Namens verhindern würde.__variable
in der Aussage return __variable
wurde auf _A__variable
verstümmelt, was auch der Name der Variablen ist, die wir im äußeren Bereich deklariert haben.Ausgabe:
> >> value = 11
> >> valuе = 32
> >> value
11
Wut?
Hinweis: Der einfachste Weg, dies zu reproduzieren, besteht darin, die Anweisungen einfach aus dem obigen Snippet zu kopieren und in Ihre Datei/Shell einzufügen.
Einige nicht-westliche Zeichen sehen mit Buchstaben im englischen Alphabet identisch aus, gelten jedoch durch den Dolmetscher.
> >> 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
Die integrierte ord()
-Funktion gibt den Unicode-Codepunkt eines Zeichens zurück, und verschiedene Codepositionen der kyrillischen 'E' und des lateinischen 'E' rechtfertigen das Verhalten des obigen Beispiels.
# `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 ()
Ausgabe:
> >> energy_send ( 123.456 )
> >> energy_receive ()
123.456
Wo ist der Nobelpreis?
energy_send
erstellt wurde, nicht zurückgegeben wird, so dass der Speicherplatz frei zu einer Umverteilung ist.numpy.empty()
gibt den nächsten freien Speicherschlitz zurück, ohne ihn neu zu initialisieren. Dieser Speicherplatz ist einfach derselbe, der nur befreit wurde (normalerweise, aber nicht immer). 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
Ausgabe (Python 2.x):
> >> square ( 10 )
10
Sollte das nicht 100 sein?
Hinweis: Wenn Sie dies nicht reproduzieren können, versuchen Sie, die Datei mixed_tabs_and_spaces.py über die Shell auszuführen.
Mischen Sie keine Registerkarten und Räume! Das Charakter, das gerade vorrückte, ist eine "Registerkarte", und der Code wird durch mehrere "4 Leerzeichen" an anderer Stelle im Beispiel eingerichtet.
So behandelt Python Tabs:
Erstens werden die Registerkarten (von links nach rechts) von eins bis acht Leerzeichen ersetzt, so dass die Gesamtzahl der Zeichen bis hin zu dem Ersatz ein Vielfaches von acht <...> ist
Die "Tab" an der letzten square
wird also durch acht Leerzeichen ersetzt und gelangt in die Schleife.
Python 3 ist so freundlich, einen Fehler für solche Fälle automatisch zu werfen.
Ausgabe (Python 3.x):
TabError : inconsistent use of tabs and spaces in indentation
+=
ist schneller # 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
+=
ist schneller als +
für die Verkettung von mehr als zwei Zeichenfolgen, da die erste Zeichenfolge (Beispiel, s1
für s1 += s2 + s3
) bei der Berechnung der vollständigen Zeichenfolge nicht zerstört wird. 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
Ausgabe:
# 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 )
Lassen Sie uns die Anzahl der Iterationen um den Faktor 10 erhöhen.
> >> 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 )
Sie können mehr über Timeit oder %Timeit für diese Links lesen. Sie werden verwendet, um die Ausführungszeit von Codestücken zu messen.
Verwenden Sie nicht +
zum Erzeugen langer Saiten - in Python ist str
unveränderlich, sodass die linken und rechten Saiten für jedes Paar von Verketten in die neue Schnur kopiert werden müssen. Wenn Sie vier Zeichenfolgen mit Länge 10 verkettet, kopieren Sie (10+10)+((10+10) +10)+(((10+10) +10) +10) = 90 Zeichen anstelle von nur 40 Charaktere. Die Dinge werden mit zunehmender Anzahl und Größe der Zeichenfolge quadratisch schlechter (mit den Ausführungszeiten der Funktion add_bytes_with_plus
gerechtfertigt)
Daher wird empfohlen, .format.
oder %
syntax (sie sind jedoch für sehr kurze Zeichenfolgen etwas langsamer als +
).
Oder besser, wenn Sie bereits Inhalte in Form eines iterbaren Objekts erhältlich sind, verwenden Sie ''.join(iterable_object)
das viel schneller ist.
Im Gegensatz zu add_bytes_with_plus
aufgrund der im vorherigen Beispiel diskutierten +=
Optimierungen zeigte add_string_with_plus
keine quadratische Erhöhung der Ausführungszeit. Wäre die Aussage s = s + "x" + "y" + "z"
anstelle von s += "xyz"
gewesen, wäre die Erhöhung quadratisch gewesen.
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 )
So viele Möglichkeiten, eine riesige Saite zu formatieren und zu erstellen, sind etwas im Gegensatz zum Zen von Python, wonach, wonach,
Es sollte eine-und vorzugsweise nur einen-aufstrebende Art geben, dies zu tun.
dict
-Lookups * some_dict = { str ( i ): 1 for i in range ( 1_000_000 )}
another_dict = { str ( i ): 1 for i in range ( 1_000_000 )}
Ausgabe:
> >> % 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 )
Warum werden die gleichen Lookups langsamer?
str
, int
, jedes Objekt ...) und eine spezialisierte für den allgemeinen Fall von Wörterbüchern aus str
-nur -Tasten übernimmt.lookdict_unicode
in CPython-Quelle) weiß, dass alle vorhandenen Tasten (einschließlich der Look-Up-Taste) Zeichenfolgen sind und den schnelleren und einfacheren String-Vergleich verwendet, um Tasten zu vergleichen, anstatt die __eq__
Methode aufzurufen.dict
zugegriffen wird, wird mit einem Nicht- str
-Schlüssel geändert, sodass zukünftige Lookups die generische Funktion verwenden.dict
-Instanz nicht reversibel, und der Schlüssel muss im Wörterbuch nicht einmal existieren. Deshalb hat der Versuch, eine gescheiterte Suche zu finden, den gleichen Effekt.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__ )
Ausgabe: (Python 3.8, andere Python 3 -Versionen können ein wenig variieren)
> >> 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
Versuchen wir es erneut ... in einem neuen Dolmetscher:
> >> 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
Was lässt diese Wörterbücher aufgebläht werden? Und warum werden auch neu erstellte Objekte aufgebläht?
__init__
der allerersten erstellten Instanz hinzugefügt werden können, ohne eine zu verursachen "Unshare"). Wenn bei der Größe einer Größe mehrere Instanzen vorhanden sind, ist die Taste-Sharing für alle zukünftigen Instanzen derselben Klasse deaktiviert: CPython kann nicht erkennen, ob Ihre Instanzen dieselben Attribute mehr verwenden, und beschließt, sich zu retten, um ihre Teile zu teilen, um ihre Teile zu teilen Schlüssel.__init__
initialisieren! join()
ist eine String -Operation anstelle des Listenvorgangs. (Art von kontraintuitiv bei der ersten Verwendung)
Erläuterung: Wenn join()
eine Methode in einer Zeichenfolge ist, kann sie auf einer beliebigen iterablen (Liste, Tupel, Iteratoren) funktionieren. Wenn es sich um eine Methode auf einer Liste handelte, müsste sie von jedem Typ separat implementiert werden. Außerdem ist es nicht sehr sinnvoll, eine stringspezifische Methode auf eine generische list
-API zu setzen.
Nur wenige seltsam aussehende, aber semantisch korrekte Aussagen:
[] = ()
ist eine semantisch korrekte Aussage (Auspacken eines leeren tuple
in eine leere list
)'a'[0][0][0][0][0]
ist ebenfalls semantisch korrekt, da Python keinen Charakter -Datentyp hat, wie andere Sprachen, die von C verzweigt werden Single-Charakter-Zeichenfolge.3 --0-- 5 == 8
und --5 == 5
sind beide semantisch korrekt und bewertet zu True
. Da a
eine Zahl ist, sind ++a
und --a
beide gültige Python -Aussagen, verhalten sich jedoch nicht im Vergleich zu ähnlichen Aussagen in Sprachen wie C, C ++ oder Java.
> >> a = 5
> >> a
5
> >> + + a
5
> >> - - a
5
Erläuterung:
++
Operator. Es sind tatsächlich zwei +
Operatoren.++a
Parse als +(+a)
was zu a
. In ähnlicher Weise kann die Ausgabe der --a
gerechtfertigt werden.Sie müssen sich des Walrus -Betreibers in Python bewusst sein. Aber haben Sie jemals von dem Space-Invader-Operator gehört?
> >> a = 42
> >> a -= - 1
> >> a
43
Es wird als alternativer Inkrementierungsoperator zusammen mit einem anderen verwendet
> >> a += + 1
> >> a
> >> 44
Erläuterung: Dieser Streich stammt aus Raymond Hettingers Tweet. Der Space Invader -Operator ist eigentlich nur ein falsch formatiertes a -= (-1)
. Das entspricht a = a - (- 1)
. Ähnlich für den Fall a += (+ 1)
.
Python hat einen undokumentierten Konverse -Implikationsoperator.
> >> False ** False == True
True
> >> False ** True == False
True
> >> True ** False == True
True
> >> True ** True == True
True
Erläuterung: Wenn Sie False
und True
durch 0 und 1 ersetzen und die Mathematik durchführen, entspricht die Wahrheitstabelle einem umgekehrten Implikationsoperator. (Quelle)
Da wir über Betreiber sprechen, gibt es auch @
Operator für die Matrix -Multiplikation (keine Sorge, diesmal ist es echt).
> >> import numpy as np
> >> np . array ([ 2 , 2 , 2 ]) @ np . array ([ 7 , 8 , 8 ])
46
Erläuterung: Der @
Operator wurde in Python 3.5 hinzugefügt, um die wissenschaftliche Gemeinschaft im Auge zu behalten. Jedes Objekt kann __matmul__
magische Methode überlasten, um das Verhalten für diesen Operator zu definieren.
Ab Python 3.8 können Sie eine typische F-String-Syntax wie f'{some_var=}
für schnelles Debugging verwenden. Beispiel,
> >> some_string = "wtfpython"
> >> f' { some_string = } '
"some_string='wtfpython'"
Python verwendet 2 Bytes für den lokalen variablen Speicher in Funktionen. Theoretisch bedeutet dies, dass in einer Funktion nur 65536 Variablen definiert werden können. Python verfügt jedoch über eine praktische Lösung, die verwendet werden kann, um mehr als 2^16 Variablennamen zu speichern. Der folgende Code zeigt, was im Stapel passiert, wenn mehr als 65536 lokale Variablen definiert sind (Warnung: Dieser Code druckt um 2^18 Textzeilen, also seien Sie vorbereitet!):
import dis
exec ( """
def f():
""" + """
""" . join ([ "X" + str ( x ) + "=" + str ( x ) for x in range ( 65539 )]))
f ()
print ( dis . dis ( f ))
Mehrere Python -Threads werden Ihren Python -Code nicht gleichzeitig ausgeführt (ja, Sie haben es richtig gehört!). Es mag intuitiv erscheinen, mehrere Threads hervorzubringen und sie gleichzeitig Ihren Python -Code auszuführen. Aufgrund der globalen Interpreter -Sperre in Python, die Sie tun, ist alles, was Sie tun, Ihre Threads in derselben Kernumdrehung durch Drehung ausführen. Python-Threads eignen sich gut für IO-gebundene Aufgaben, aber um die tatsächliche Parallelisierung in Python für CPU-gebundene Aufgaben zu erreichen, möchten Sie vielleicht das Python-Multiprocessing-Modul verwenden.
Manchmal druckt die print
möglicherweise nicht sofort Werte. Zum Beispiel,
# File some_file.py
import time
print ( "wtfpython" , end = "_" )
time . sleep ( 3 )
Dadurch wird das wtfpython
nach 3 Sekunden aufgrund des end
gedruckt, da der Ausgangspuffer entweder nach Begegnung mit n
oder wenn das Programm ausgeführt wird. Wir können den Puffer zum Spülen erzwingen, indem wir flush=True
Argument vorgeben.
Listen Sie das Schneiden mit außerhalb der Grenzensindizes auf
> >> some_list = [ 1 , 2 , 3 , 4 , 5 ]
> >> some_list [ 111 :]
[]
Das Schneiden eines Iterablens erzeugt nicht immer ein neues Objekt. Zum Beispiel,
> >> 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('١٢٣٤٥٦٧٨٩')
gibt 123456789
in Python zurück. In Python enthalten Dezimalzeichen Ziffernzeichen und alle Zeichen, die zur Bildung von Dezimal-Radix-Nummern verwendet werden können, z. U+0660, arabisch-indische Ziffer Null. Hier ist eine interessante Geschichte zu diesem Verhalten von Python.
Sie können numerische Literale mit Unterstrichen (zur besseren Lesbarkeit) von Python 3 trennen.
> >> six_million = 6_000_000
> >> six_million
6000000
> >> hex_address = 0xF00D_CAFE
> >> hex_address
4027435774
'abc'.count('') == 4
. Hier ist eine ungefähre Implementierung der count
-Methode, die die Dinge klarer machen würde
def count ( s , sub ):
result = 0
for i in range ( len ( s ) + 1 - len ( sub )):
result += ( s [ i : i + len ( sub )] == sub )
return result
Das Verhalten ist auf die Übereinstimmung von leerem Substring ( ''
) mit Länge von 0 in der ursprünglichen Zeichenfolge zurückzuführen.
Ein paar Möglichkeiten, wie Sie zu WTFPython beitragen können,
Weitere Informationen finden Sie unter Beitrags.md. Fühlen Sie sich frei, ein neues Problem zu erstellen, um Dinge zu diskutieren.
PS: Bitte erreichen Sie nicht mit Backlinking -Anfragen. Es werden keine Links hinzugefügt, es sei denn, sie sind für das Projekt von großer Bedeutung.
Die Idee und das Design für diese Kollektion wurden ursprünglich von Denys Dovhans fantastischem Projekt WTFJS inspiriert. Die überwältigende Unterstützung durch Pythonistas gab ihr die Form, in der es sich derzeit befindet.
© 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.
Das ist alles Leute! For upcoming content like this, you can add your email here.