Home

Awesome

<p align="center"><img src="/images/logo.png#gh-light-mode-only" alt=""><img src="/images/logo-dark.png#gh-dark-mode-only" alt=""></p> <h1 align="center">What the f*ck Python! 😱</h1> <p align="center">Entdecke und verstehe Python durch überraschende Code-Schnipsel.</p>

Übersetzungen: English | Chinesisch 中文 | Vietnamesisch Tiếng Việt | Spanisch Español | Koreanisch 한국어 | Russisch Русский | Übersetzung hinzufügen

Andere Modi: Interaktive Website | Interaktives Notebook | CLI

Python, bekannt als gut designte High-Level und Interpreter-basierte Programmiersprache, stellt viele Features zur Verfügung, um dem Programmierer das Leben zu erleichtern. Allerdings kann es vorkommen, dass ein Python-Schnipsel ein unerwartetes Verhalten zeigt.

Hier ist ein schönes Projekt, das versucht die Dinge aufzuzeigen, die bei einigen Code-Schnipseln unter der Haube passieren und darüber hinaus einige weniger bekannte Features von Python zu erklären.

Während manche Beispiele nicht unbedingt beeindruckend erscheinen, zeigen sie dennoch interessante Details von Python, die dir womöglich noch nicht aufgefallen sind. Ich finde, dass es eine schöne Möglichkeit ist, die Interna einer Programmiersprache zu lernen und ich glaube das findest du auch !

Wenn du ein erfahrener Python-Programmierer bist, kannst du dies als Herausforderung ansehen, um möglichst viel beim ersten Anlauf richtig zu machen. Du hast vielleicht manches schon erlebt, sodass ich möglicherweise alte Erinnerungen wecken kann! :sweat_smile:

PS: Wenn du bereits mehrfach hier warst, kannst du dich hier über neue Modifikationen informieren (die Beispiele, die mit einem Stern markiert sind, sind Teil des letzten Releases).

Also, los gehts...

Inhaltsverzeichnis

<!-- Generated using "markdown-toc -i README.md --maxdepth 3"--> <!-- toc --> <!-- tocstop -->

Sruktur der Beispiele

Alle Beispiele sind nach folgendem Muster aufgebaut:

▶ Ein schicker Titel

# Vorbereitung des Codes.
# Vorbereitung für etwas Magisches...

Ausgabe (Python version(en)):

>>> triggering_statement
Irgendeine unerwartete Ausgabe

(Optional): Eine Zeile, die die unerwartete Ausgabe beschreibt.

💡 Erklärung:

# Aufsetzen des Codes
# Mehr Beispiele für ein besseres Verständnis (wenn erforderlich)

Ausgabe (Python version(en)):

>>> trigger # Ein Beispiel, das es einfach macht, die Magie zu verstehen
# Eine begründete Ausgabe

Note: Alle Beispiele sind mit Pythons 3.5.2 interaktiven Interpreter getestet, und sie sollten für alle Python Versionen funktionieren. Ausnahmen werden vor dem Ausgabe kenntlich gemacht.

Benutzung

Ein guter Weg, um die Beispiele bestmöglich zu nutzen, ist es, sie von anfang an durchzugehen und bei jedem Beispiel folgendes zu tun:

PS: Du kannst dir auch WTFPython im Terminal ansehen, indem du das pypi package nutzt:

$ pip install wtfpython -U
$ wtfpython

👀 Beispiele

Kapitel: Fordere dein Gehirn heraus!

▶ Das Wichtigste zuerst! *

<!-- Example ID: d3d73936-3cf1-4632-b5ab-817981338863 --> <!-- read-only -->

Aus irgendwelchen Gründen ist der "Walrus" Operator (:=) in Python 3.8 ziemlich beliebt. Lass uns starten,

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") # Das funktioniert merkwürdigerweise
'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 # Typisches Auspacken
>>> a, b
(6, 9)
>>> (a, b = 16, 19) # Oops
  File "<stdin>", line 1
    (a, b = 16, 19)
          ^
SyntaxError: invalid syntax

>>> (a, b := 16, 19) # Dies gibt ein eigenartiges 3-Tupel aus
(6, 16, 19)

>>> a # Ist a immernoch unverändert ?
6

>>> b
16

💡 Erklärung

Schneller Rückblick zum Walrus Operator

Der Walrus Operator (:=) wurde in Python 3.8 eingeführt. Er kann in Situationen sinvoll sein, in denen du ein Wert einer Variablen in einem Ausdruck zuweisen möchtest.

def irgendeine_funktion():
        # Irgendeine Berechnung, die teuer ist (=> sehr viel Zeit und Ressourcen in Aspruch nimmt)
        # time.sleep(1000)
        return 5

# Anstatt:
if irgendeine_funktion():
        print(irgendeine_funktion()) # Schlechter Stil, da die Funktion zweimal aufgerufen wird

# Oder:
a = irgendeine_funktion()
if a:
    print(a)

# Nun kannst du folgendes schreiben:
if a := irgendeine_funktion():
        print(a)

Ausgabe (> 3.8):

5
5
5

Das hat uns eine Zeile Code erspart. Zudem spart es einen zusätzlichen Aufruf der Funktion irgendeine_funktion.


▶ Strings können manchmal schwierig sein

<!-- Example ID: 30f1d3fc-e267-4b30-84ef-4d9e7091ac1a --->

1.

>>> a = "irgendein_string"
>>> id(a)
140420665652016
>>> id("irgendein" + "_" + "string") # Beachte, dass beide ids dieselben sind.
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 # Alle Versionen außer 3.7.x
True

>>> a = "wtf!"; b = "wtf!"
>>> a is b # Das wird True oder False ausgeben, je nach dem wo du es aufrufst (Python Shell / iPython / in einem Skript)
False
# Dieses mal in einer Datei: some_file.py
a = "wtf!"
b = "wtf!"
print(a is b)

# Gibt True aus, wenn das Modul aufgerufen wird!

4.

Ausgabe (< Python3.7 )

>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False

Ergibt Sinn, Oder?

💡 Erklärung:


▶ Vorsicht bei verketteten Operationen

<!-- Example ID: 07974979-9c86-4720-80bd-467aa19470d9 --->
>>> (False == False) in [False] # ergibt Sinn
False
>>> False == (False in [False]) # ergibt Sinn
False
>>> False == False in [False] # Was nun?
True

>>> True is False == False
False
>>> False is False is False
True

>>> 1 > 0 < 1
True
>>> (1 > 0) < 1
False
>>> 1 > (0 < 1)
False

💡 Erklärung:

Zitat von https://docs.python.org/3/reference/expressions.html#comparisons

Formally, if a, b, c, ..., y, z are expressions and op1, op2, ..., opN are comparison operators, then a op1 b op2 c ... y opN z is equivalent to a op1 b and b op2 c and ... y opN z, except that each expression is evaluated at most once.

Übersetzt:

Formal ausgedrückt: wenn a, b, c, ..., y, z Ausdrücke und op1, op2, ..., opN Vergleichsoperatoren sind, dann sind a op1 b op2 c ... y opN z äquivalent zu a op1 b and b op2 c and ... y opN z, mit der Ausnahme, dass jeder Ausdruck höchstens einmal ausgewertet wird.

Während dieses Verhalten in den Beispielen vielleicht unsinnig erscheint, kann es super verwendet werden, z.B. a == b == c und 0 <= x <= 100.


▶ Wie man den is Operator nicht nutzt

<!-- Example ID: 230fa2ac-ab36-4ad1-b675-5f5a1c1a6217 --->

Das folgende Beispiel ist im Internet überall bekannt.

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 (Python 3.7.x spezifisch)

>>> a, b = 257, 257
>>> a is b
False

💡 Erklärung:

Der Unterschied zwischen is und ==

256 ist ein existierendes Objekt, aber 257 nicht

Wenn du Python startest, werden die Nummern von -5 bis 256 bereitgestellt. Diese Nummern werden sehr oft benutzt, also ergibt es Sinn, sie schnell bereit zu haben.

Zitat von https://docs.python.org/3/c-api/long.html

The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you just get back a reference to the existing object. So it should be possible to change the value of 1. I suspect the behavior of Python, in this case, is undefined. :-)

Übersetzung:

Die momentane Implementation stellt ein Array aus Integer-Objekten für alle Integer zwischen -5 und 256 bereit. Wenn du einen int in diesem Bereich erstellst, bekommst du nur eine Referenz auf das existierende Objekt zurück. Also sollte es möglich sein, den Wert von 1 zu ändern. Ich vermute das Verhalten von Python ist in diesem Fall undefiniert. :-)

>>> 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 nicht schlau genug während des Ausführens von y = 257 zu erkennen, dass wir bereits ein Integer mit dem Wert 257 erstellt haben und daher wird ein neues Objekt im Speicher angelegt.

Ähnliche Optimierungen treffen auf andere immutable Objekte zu, z.B. leere Tuples. Da Listen mutable sind, wird [] is [] zu False ausgewertet und () is () wird zu True ausgewertet. Das erklärt unser zweiter Schnipsel. Lass uns mit dem dritten Beispiell weiter machen:

Sowohl a und b beziehen sich auf dasselbe Objekt wenn sie in derselben Zeile mit demselben Wert initialisiert werden.

Ausgabe

>>> a, b = 257, 257
>>> id(a)
140640774013296
>>> id(b)
140640774013296
>>> a = 257
>>> b = 257
>>> id(a)
140640774013392
>>> id(b)
140640774013488

▶ Hash brownies

<!-- Example ID: eb17db53-49fd-4b61-85d6-345c5ca213ff --->

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" hat die Existenz von "Ruby" ausgelöscht ?
"Python"
>>> some_dict[5] 
"Python"

>>> complex_five = 5 + 0j
>>> type(complex_five)
complex
>>> some_dict[complex_five]
"Python"

Warum also ist Python überall zu finden ?

💡 Erklärung


▶ Tief im Inneren sind wir alle gleich

<!-- Example ID: 8f99a35f-1736-43e2-920d-3b78ec35da9b --->
class WTF:
  pass

Ausgabe:

>>> WTF() == WTF() # zwei verschiedene Instanzen können nicht gleich sein
False
>>> WTF() is WTF() # Idetitäten sind ebenfalls unterschiedlich
False
>>> hash(WTF()) == hash(WTF()) # Hash-Werte _sollten_ ebenfalls verschieden sein
True
>>> id(WTF()) == id(WTF())
True

💡 Erklärung:


▶ Unordnung in der Ordnung *

<!-- Example ID: 91bff1f8-541d-455a-9de4-6cd8ff00ea66 --->
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):
    """
    Ein Dictionary auch __hash__ magic implementiert.
    """
    __hash__ = lambda self: 0

class OrderedDictWithHash(OrderedDict):
    """
    Ein OrderedDict was auch __hash__ magic implementiert.
    """
    __hash__ = lambda self: 0

Ausgabe

>>> dictionary == ordered_dict # Wenn a == b
True
>>> dictionary == another_ordered_dict # und b == c
True
>>> ordered_dict == another_ordered_dict # warum ist dann c != a ??
False

# Wir wissen alle, dass ein Set nur aus einzigartigen Elementen besteht,
# Lass uns ein Set aus Dictionaries bauen und sehen, was passiert...

>>> len({dictionary, ordered_dict, another_ordered_dict})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'

# Ergibt Sinn, denn ein Dictionary implementiert nicht __hash__, lass uns unsere
# Wrapper-Klasse benutzen.
>>> 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}) # verändere die Reihenfolge
2

Was geht hier vor ?

💡 Erklärung:


▶ Versuche es weiter... *

<!-- Example ID: b4349443-e89f-4d25-a109-82616be9d41a --->
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(): # Ein gotcha!
    try:
        for i in range(3):
            try:
                1 / i
            except ZeroDivisionError:
                # Lass es uns hier hin packen und es außerhalb des Loops behandeln
                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

💡 Erklärung:


▶ Wofür?

<!-- Example ID: 64a9dccf-5083-4bc9-98aa-8aeecde4f210 --->
some_string = "wtf"
some_dict = {}
for i, some_dict[i] in enumerate(some_string):
    i = 10

Ausgabe:

>>> some_dict # Ein indiziertes dictionary erscheint.
{0: 'w', 1: 't', 2: 'f'}

💡 Erklärung:


▶ Diskrepanz in der Auswertungszeit

<!-- Example ID: 6aa11a4b-4cf1-467a-b43a-810731517e98 --->

1.

array = [1, 8, 15]
# Ein typischer Generator-Ausdruck
gen = (x for x in array if array.count(x) > 0)
array = [2, 8, 22]

Ausgabe:

>>> print(list(gen)) # Wo sind die anderen Variablen hin?
[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]

💡 Erklärung


is not ... ist nicht is (not ...)

<!-- Example ID: b26fb1ed-0c7d-4b9c-8c6d-94a58a055c0d --->
>>> 'something' is not None
True
>>> 'something' is (not None)
False

💡 Erklärung


▶ Ein tic-tac-toe wo X im ersten Versuch gewinnt!

<!-- Example ID: 69329249-bdcb-424f-bd09-cca2e6705a7a --->
# Lass uns eine Zeile initialisieren
row = [""] * 3 #row i['', '', '']
# Lass uns ein board bauen
board = [row] * 3

Ausgabe:

>>> board
[['', '', ''], ['', '', ''], ['', '', '']]
>>> board[0]
['', '', '']
>>> board[0][0]
''
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['X', '', ''], ['X', '', '']]

Wir haben nicht dreimal "X" zugewiesen, oder?

💡 Erklärung:

Wenn wir die Variable row initialisieren, dann erklärt diese Visualisierung, was im Speicher passiert

image

Und wenn das board durch Multiplizieren der row initialisiert wird, dann passiert das hier innerhalb des Speichers (jedes der Elemente board[0], board[1] und board[2] ist eine Referenz aud dieselbe Liste, aud die row verweist)

image

Wir können dieses Szenario umfahren, indem wir nicht die row Variable zum generieren von board benutzen. (gefragt hier).

>>> board = [['']*3 for _ in range(3)]
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['', '', ''], ['', '', '']]

▶ Schrödingers Variable *

<!-- Example ID: 4dc42f77-94cb-4eb5-a120-8203d3ed7604 --->
funcs = []
results = []
for x in range(7):
    def some_func():
        return x
    funcs.append(some_func)
    results.append(some_func())  # Beachte hier den Funktionsaufruf 

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]

The values of x were different in every iteration prior to appending some_func to funcs, but all the functions return 6 when they're evaluated after the loop completes.

>>> 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]

💡 Erklärung:

>>> 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, den funcs nachschlägt und zurüchgibt, verändern, indem wir x updaten:

>>> x = 42
>>> [func() for func in funcs]
[42, 42, 42, 42, 42, 42, 42]
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]

x wird nicht länger im globalen Scope verwendet:

>>> inspect.getclosurevars(funcs[0])
ClosureVars(nonlocals={}, globals={}, builtins={}, unbound=set())

▶ Das Henne-Ei-Problem *

<!-- Example ID: 60730dc2-0d79-4416-8568-2a63323b3ce8 --->

1.

>>> isinstance(3, int)
True
>>> isinstance(type, object)
True
>>> isinstance(object, type)
True

Was ist also die ultimative Basisklasse? Die Verwirrung wird noch größer:

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

💡 Erklärung


▶ Beziehungen in Unterklassen

<!-- Example ID: 9f6d8cf0-e1b5-42d0-84a0-4cfab25a0bc0 --->

Ausgabe:

>>> from collections import Hashable
>>> from collections.abc import Hashable
>>> issubclass(list, object)
True
>>> issubclass(object, Hashable)
True
>>> issubclass(list, Hashable)
False

Die Unterklassenbeziehungen sollten transitiv sein, nicht wahr? (d.h., wenn A eine Unterklasse von B ist, und B eine Unterklasse von C, dann sollte A eine Unterklasse von C sein)

💡 Erklärung:


▶ Methodengleichheit und -identität

<!-- Example ID: 94802911-48fe-4242-defa-728ae893fa32 --->
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, bekommen wir ein gleiches Objekt, aber nicht dasselbe oder? Lass uns 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

Der zweifache Zugriff auf classm oder method, erzeugt gleiche, aber nicht gleiche Objekte für dieselbe Instanz von SomeClass.

💡 Erklärung

>>> o1.method
<bound method SomeClass.method of <__main__.SomeClass object at ...>>
>>> SomeClass.method
<function SomeClass.method at ...>
>>> o1.classm
<bound method SomeClass.classm of <class '__main__.SomeClass'>>
>>> SomeClass.classm
<bound method SomeClass.classm of <class '__main__.SomeClass'>>
>>> o1.staticm
<function SomeClass.staticm at ...>
>>> SomeClass.staticm
<function SomeClass.staticm at ...>

▶ All-true-ation *

<!-- Example ID: dfe6d845-e452-48fe-a2da-0ed3869a8042 -->
>>> all([True, True, True])
True
>>> all([True, True, False])
False

>>> all([])
True
>>> all([[]])
False
>>> all([[[]]])
True

Warum ist diese Änderung True/False ?

💡 Erklärung:


▶ Das überraschende Komma

<!-- Example ID: 31a819c8-ed73-4dcc-84eb-91bedbb51e58 --->

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

💡 Erklärung:


▶ Strings und die Backslashes

<!-- Example ID: 6ae622c3-6d99-4041-9b33-507bd1a4407b --->

Ausgabe:

>>> print("\"")
"

>>> print(r"\"")
\"

>>> print(r"\")
File "<stdin>", line 1
    print(r"\")
              ^
SyntaxError: EOL while scanning string literal

>>> r'\'' == "\\'"
True

💡 Erklärung


▶ not knot!

<!-- Example ID: 7034deb1-7443-417d-94ee-29a800524de8 --->
x = True
y = False

Ausgabe:

>>> not x == y
True
>>> x == not y
  File "<input>", line 1
    x == not y
           ^
SyntaxError: invalid syntax

💡 Erklärung:


▶ Halbe Zeichenketten in dreifachen Anführungszeichen

<!-- Example ID: c55da3e2-1034-43b9-abeb-a7a970a2ad9e --->

Ausgabe:

>>> print('wtfpython''')
wtfpython
>>> print("wtfpython""")
wtfpython
>>> # Die folgende Anweisung wirft einen `SyntaxError`
>>> # print('''wtfpython')
>>> # print("""wtfpython")
  File "<input>", line 3
    print("""wtfpython")
                        ^
SyntaxError: EOF while scanning triple-quoted string literal

💡 Erklärung:


▶ Was ist falsch an booleans?

<!-- Example ID: 0bba5fa7-9e6d-4cd2-8b94-952d061af5dd --->

1.

# Ein einfaches Beispiel, um die Anzahl der Booleans und
# der Integer in einem Iterable mit gemischten Datentypen zu zählen.
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!

💡 Erklärung:


▶ Klassen- und Instanzattribute

<!-- Example ID: 6f332208-33bd-482d-8106-42863b739ed9 --->

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 wurde verändert, aber B.x nicht
(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

💡 Erklärung:


▶ yielding None

<!-- Example ID: 5a40c241-2c30-40d0-8ba9-cf7e097b3b53 --->
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']

💡 Erklärung:


▶ Yielding from... return! *

<!-- Example ID: 5626d8ef-8802-49c2-adbc-7cda5c550816 --->

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" hin? Liegt es an einem besonderen Effekt von yield from? Lass 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; hat also auch nicht funktioniert.

💡 Erklärung:

"... return expr in einem Generator führt zu einem StopIteration(expr), was beim Verlassen des Generators geworfen wird."


▶ Nan-Reflexivität *

<!-- Example ID: 59bee91a-36e0-47a4-8c7d-aa89bf1d3976 --->

1.

a = float('inf')
b = float('nan')
c = float('-iNf')  # Diese Strings sind case-insensitiv
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 # Identität funktioniert
True
>>> y == y # Gleichheit von y schlägt fehl
False
>>> [y] == [y] # aber die Gleichheit für die Liste, die y enthält, gelingt
True

💡 Erklärung:


▶ Verändern des Unveränderlichen!

<!-- Example ID: 15a9e782-1695-43ea-817a-a9208f6bb33d --->

Das sieht vielleicht trivial aus, wenn du weißt 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) #Das wirft keinen Fehler
>>> 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 sind unveränderlich...

💡 Erklärung:


▶ Die verschwindende Variable aus dem äußeren Gültigkeitsbereich

<!-- Example ID: 7f1e71b6-cb3e-44fb-aa47-87ef1b7decc8 --->
e = 7
try:
    raise Exception()
except Exception as e:
    pass

Ausgabe (Python 2.x):

>>> print(e)
# gibt nichts aus

Ausgabe (Python 3.x):

>>> print(e)
NameError: name 'e' is not defined

💡 Erklärung:


▶ Die mysteriöse key type Umwandlung

<!-- Example ID: 00f42dd0-b9ef-408d-9e39-1bc209ce3f36 --->
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 # erwartet: zwei verschiedene key-value-Paare
{'s': 40}
>>> type(list(some_dict.keys())[0])
str

💡 Erklärung:


▶ Lass uns sehen, ob du dies errätst?

<!-- Example ID: 81aa9fbe-bd63-4283-b56d-6fdd14c9105e --->
a, b = a[b] = {}, 5

Ausgabe:

>>> a
{5: ({...}, 5)}

💡 Erklärung:

Eine Zuweisungsanweisung wertet eine Liste von Ausdrücken aus (denk daran, dass dies ein einzelner Ausdruck oder eine durch Komma getrennte Liste sein kann, wobei letzteres ein Tupel ergibt) und weist dem einzelnen resultierenden Objekt jeder der Ziellisten zu, von links nach rechts.


▶ Überschreitet den Grenzwert für die Umwandlung von Integer-Strings

>>> # 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.

💡 Erklärung:

Die Aufforderung int() funktioniert gut in Python 3.10.6 und gibt einen ValueError in Python 3.10.8 aus. Beachte, dass Python auch mit großen ganzen Zahlen arbeiten kann. Der Fehler tritt nur auf, wenn du zwischen Integern und Strings konvertiert wird.

Glücklicherweise kannst du den Grenzwert für die zulässige Anzahl von Ziffern erhöhen, wenn du erwartest, dass ein Vorgang diesen Grenzwert überschreitet. Um das zu tun, kannst du folgendes benutzen:

Check die Dokumentation für mehr Details über das Verändern des Default-Limits, wenn du erwartest, dass dein Code diesen Wert übersteigt.


Kapitel: Slippery Slopes

▶ Modifizieren eines Dictionarys während einer Iteration

<!-- Example ID: b4e5cdfb-c3a8-4112-bd38-e2356d801c41 --->
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 exakt acht mal und stoppt dann.

💡 Erklärung:


▶ Hartnäckige del Operation

<!-- Example ID: 777ed4fd-3a2d-466f-95e7-c4058e61d78e ---> <!-- read-only -->
class SomeClass:
    def __del__(self):
        print("Deleted!")

Ausgabe: 1.

>>> x = SomeClass()
>>> y = x
>>> del x # das sollte "Deleted!" ausgeben
>>> del y
Deleted!

Endlich wird deleted ausgegeben. Vielleicht erahnst du schon schon, warum __del__ nicht schon bei unserem ersten Versuch, x zu löschen, aufgerufen wurde. Ergänzen wir das Beispiel um weitere Aspekte

2.

>>> x = SomeClass()
>>> y = x
>>> del x
>>> y # check, ob y existiert
<__main__.SomeClass instance at 0x7f98a1a67fc8>
>>> del y # Wie vorher sollte das "Deleted!" ausgeben
>>> globals() # oh, das hat es nicht. Lass uns alle globalen Variablen checken und das bestätigen
Deleted!
{'__builtins__': <module '__builtin__' (built-in)>, 'SomeClass': <class __main__.SomeClass at 0x7f98a1a5f668>, '__package__': None, '__name__': '__main__', '__doc__': None}

Okay, jetzt ist es gelöscht :confused:

💡 Erklärung:


▶ Die Variable aus dem äußeren Geltungsbereich

<!-- Example ID: 75c03015-7be9-4289-9e22-4f5fdda056f7 --->

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

💡 Erklärung:


▶ Löschen eines Listenelements während einer Iteration

<!-- Example ID: 4cc52d4e-d42b-4e09-b25f-fbf5699b7d4e --->
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]

Kannst du erklären, warum die Ausgabe [2, 4] ist?

💡 Erklärung:

Unterschied zwischen del, remove, und pop:

Warum ist die Ausgabe [2, 4]?


▶ Lossy Zips von Iteratoren *

<!-- Example ID: c28ed154-e59f-4070-8eb6-8967a4acac6d --->
>>> 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 weit, so gut, lass uns den Rest zippen
>>> list(zip(numbers_iter, remaining))
[(4, 3), (5, 4), (6, 5)]

Wo ist das Element 3 von der Liste numbers hin?

💡 Erklärung:


▶ Schleifenvariablen, die auslaufen!

<!-- Example ID: ccec7bf6-7679-4963-907a-1cd8587be9ea --->

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 Scopes der for-Schleife definiert...

2.

# Las uns dieses Mal x zuerst initialisieren
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

💡 Erklärung:


▶ Vorsicht vor standardmäßig veränderbaren Argumenten!

<!-- Example ID: 7d42dade-e20d-4a7b-9ed7-16fb58505fe9 --->
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']

💡 Erklärung:


▶ Fangen der Exceptions

<!-- Example ID: b5ca5e6a-47b9-4f69-9375-cda0f8c6755d --->
some_list = [1, 2, 3]
try:
    # Das sollte einen ``IndexError`` werfen
    print(some_list[4])
except IndexError, ValueError:
    print("Caught!")

try:
    # Das sollte einen ``ValueError`` werfen
    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

💡 Erklärung


▶ Gleiche Operanden, unterschiedliche Story!

<!-- Example ID: ca052cdf-dd2d-4105-b936-65c28adc18a0 --->

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]

💡 Erklärung:


▶ Namensauflösung ohne Berücksichtigung des Geltungsbereichs der Klasse

<!-- Example ID: 03f73d96-151c-4929-b0a8-f74430788324 --->

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

💡 Erklärung


▶ Runden wie ein Bankier *

Lass uns eine naive Funktion implementieren, um das mittlere Element einer Liste zu bekommen:

def get_middle(some_list):
    mid_index = round(len(some_list) / 2)
    return some_list[mid_index - 1]

Python 3.x:

>>> get_middle([1])  # sieht gut aus
1
>>> get_middle([1,2,3])  # sieht gut aus
2
>>> get_middle([1,2,3,4,5])  # huh?
2
>>> len([1,2,3,4,5]) / 2  # gut
2.5
>>> round(len([1,2,3,4,5]) / 2)  # Warum?
2

Sieht so aus, als ob Python 2.5 zu 2 rundet.

💡 Erklärung:

>>> round(0.5)
0
>>> round(1.5)
2
>>> round(2.5)
2
>>> import numpy  # numpy tut dasselbe
>>> numpy.round(0.5)
0.0
>>> numpy.round(1.5)
2.0
>>> numpy.round(2.5)
2.0

▶ Nadeln im Heuhaufen *

<!-- Example ID: 52a199b1-989a-4b28-8910-dff562cebba9 --->

Ich habe bis heute keinen einzigen erfahrenen Pythonisten getroffen, der nicht auf eines oder mehrere der folgenden Szenarien gestoßen ist:

1.

x, y = (0, 1) if True else None, None

Ausgabe:

>>> x, y  # erwartet: (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. Not asserting strongly enough

a = "python"
b = "javascript"

Ausgabe:

# Eine assert-Anweisung mit einer assertion-Fehlermeldung.
>>> assert(a == b, "Both languages are different")
# Kein AssertionError wurde geworfen

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

💡 Erklärung:


▶ Splitsies *

<!-- Example ID: ec3168ba-a81a-4482-afb0-691f1cc8d65a --->
>>> 'a'.split()
['a']

# ist dasselbe wie
>>> 'a'.split(' ')
['a']

# aber
>>> len(''.split())
0

# ist nicht dasselbe wie
>>> len(''.split(' '))
1

💡 Erklärung:


▶ Wilde Imports *

<!-- Example ID: 83deb561-bd55-4461-bb5e-77dd7f411e1c ---> <!-- read-only -->
# Datei: 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

💡 Erklärung:


▶ Alles sortieren ? *

<!-- Example ID: e5ff1eaf-8823-4738-b4ce-b73f7c9d5511 -->
>>> x = 7, 8, 9
>>> sorted(x) == x
False
>>> sorted(x) == sorted(x)
True

>>> y = reversed(x)
>>> sorted(y) == sorted(y)
False

💡 Erklärung:


▶ Mitternachtszeit gibt es nicht ?

<!-- Example ID: 1bce8294-5619-4d70-8ce3-fe0bade690d1 --->
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))

The midnight time is not printed.

💡 Erklärung:

Vor Python 3.5, wurde der boolesche Wert für das datetime.time-Objekt als False betrachtet, wenn wenn es Mitternacht in UTC dargestellt hätte. Es ist fehleranfällig, wenn die if obj:-Syntax verwendet wird, um zu prüfen, ob obj null oder ein Äquivalent von "leer" ist.



Kapitel: Die verborgenen Schätze!

Dieser Abschnitt enthält ein paar weniger bekannte und interessante Dinge über Python, die den meisten Anfängern wie mir nicht bekannt sind (nun aber schon).

▶ Okay Python, kannst du mich fliegen lassen?

<!-- Example ID: a92f3645-1899-4d50-9721-0031be4aec3f --->

Nun hier wären wir:

import antigravity

Ausgabe: Sshh... It's a super-secret.

💡 Erklärung:


goto, aber wieso?

<!-- Example ID: 2aff961e-7fa5-4986-a18a-9e5894bd89fe --->
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 # Ausbrechen aus einer tief verschachtelten Schleife
label .breakout
print("Freedom!")

Ausgabe (Python 2.3):

I am trapped, please rescue!
I am trapped, please rescue!
Freedom!

💡 Erklärung:


▶ Halte dich fest!

<!-- Example ID: 5c0c75f2-ddd9-4da3-ba49-c4be7ec39acf --->

Wenn du zu den Leuten gehörst, die keine Leerzeichen in Python verwenden wollen, um Bereiche zu kennzeichnen, kannst du den C-Stil {} verwenden, indem du folgendes importierst:

from __future__ import braces

Ausgabe:

  File "some_file.py", line 1
    from __future__ import braces
SyntaxError: not a chance

Klammern? Niemals! Wenn du enttäuscht bist, nutze Java. Okay, eine weitere überraschende Sache, kannst du herausfinden, wo SyntaxError im __future__ Mmodul geworfen wird code?

💡 Erklärung:


▶ Let's meet Friendly Language Uncle For Life

<!-- Example ID: 6427fae6-e959-462d-85da-ce4c94ce41be --->

Ausgabe (Python 3.x)

>>> from __future__ import barry_as_FLUFL
>>> "Ruby" != "Python" # Hier bestehen keine Zweifel
  File "some_file.py", line 1
    "Ruby" != "Python"
              ^
SyntaxError: invalid syntax

>>> "Ruby" <> "Python"
True

Das wars schon.

💡 Erklärung:

-Das ist relevant für PEP-401, was am 1. April 2009 veröffentlicht wurde (nun weißt du, was das heißt).


▶ Selbst Python versteht, dass Liebe kompliziert ist

<!-- Example ID: b93cad9e-d341-45d1-999c-fcdce65bed25 --->
import this

Warte, was ist this? this ist love :heart:

Ausgabe:

Der Zen von Python, von 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!

Das 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  # Liebe ist kompliziert
True

💡 Erklärung:


▶ Ja, es existiert!

<!-- Example ID: 4286db3d-1ea7-47c9-8fb6-a9a04cac6e49 --->

The else clause for loops. Ein typisches Beispiel wäre:

  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 der Behandlung von Exceptions. Ein Beispiel:

try:
    pass
except:
    print("Exception occurred!!!")
else:
    print("Try block executed successfully...")

Ausgabe:

Try block executed successfully...

💡 Erklärung:


▶ Ellipsen *

<!-- Example ID: 969b7100-ab3d-4a7d-ad7d-a6be16181b2b --->
def some_func():
    Ellipsis

Ausgabe

>>> some_func()
# Keine Ausgabe, Kein Fehler

>>> SomeRandomString
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'SomeRandomString' is not defined

>>> Ellipsis
Ellipsis

💡 Erklärung


▶ Einbindung

<!-- Example ID: ff473ea8-a3b1-4876-a6f0-4378aff790c1 --->

Die Schreibweise ist beabsichtigt. Bitte schicke keinen Patch hierfür ab.

Ausgabe (Python 3.x):

>>> infinity = float('infinity')
>>> hash(infinity)
314159
>>> hash(float('-inf'))
-314159

💡 Erklärung:


▶ Lass uns demolieren

<!-- Example ID: 37146d2d-9e67-43a9-8729-3c17934b910c --->

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):
        # Versuchen wir es dieses Mal mit etwas Symmetrischem
        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 funktioniert?

3.

_A__variable = "Some value"

class A(object):
    def some_func(self):
        return __variable # noch nirgends initialisiert

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'

💡 Erklärung:



Kapitel: Der Schein trügt!

▶ Zeilen überspringen?

<!-- Example ID: d50bbde1-fb9d-4735-9633-3444b9d2f417 --->

Ausgabe:

>>> value = 11
>>> valuе = 32
>>> value
11

Was?

Beachte: Um dies zu reproduzieren, kopiere einfach die Anweisungen aus dem obigen Ausschnitt und füge sie in deine Datei/Shell ein.

💡 Erklärung

Einige nicht-westliche Schriftzeichen sehen genauso aus wie die Buchstaben des englischen Alphabets, werden aber von Interpreter als unterschiedlich angesehen.

>>> ord('е') # kyrillisches 'e' (Ye)
1077
>>> ord('e') # lateinisches 'e', wie es in Englisch benutzt wird und auch auf einer Standard-Tastatur vorkommt
101
>>> 'е' == 'e'
False

>>> value = 42 # lateinisches e
>>> valuе = 23 # kyrillisches 'e', der Python 2.x Interpreter würde hier einen `SyntaxError` werfen
>>> value
42

Die built-in ord()-Funktion gibt den Unicode eines Characters zurück code point, und unterschiedliche Codepositionen des kyrillischen "e" und des lateinischen "e" begründen das Verhalten des obigen Beispiels.


▶ Teleportation

<!-- Example ID: edafe923-0c20-4315-b6e1-0c31abfc38f5 --->
# `pip install numpy` vorher.
import numpy as np

def energy_send(x):
    # Initialisiere ein numpy array
    np.array([float(x)])

def energy_receive():
    # Gib ein leeres numpy array zurück
    return np.empty((), dtype=np.float).tolist()

Ausgabe:

>>> energy_send(123.456)
>>> energy_receive()
123.456

Wo ist der Nobelpreis?

💡 Erklärung:


▶ Da ist wohl irgendwas faul...

<!-- Example ID: cb6a37c5-74f7-44ca-b58c-3b902419b362 --->
def square(x):
    """
    Eine einfahce Funktion, um das Quadrat einer Zahl durch Addition zu bestimmen
    """
    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?

Note: Wenn du das Ergebnis nicht reproduzieren kannst, versuch die Datei mixed_tabs_and_spaces.py via shell auszuführen.

💡 Erklärung



Kapitel: Sonstiges

+= ist schneller

<!-- Example ID: bfd19c60-a807-4a26-9598-4912b86ddb36 --->
# Benutzen von "+" mit drei Strings:
>>> timeit.timeit("s1 = s1 + s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
0.25748300552368164
# Benutzen von "+=" mit drei Strings:
>>> timeit.timeit("s1 += s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
0.012188911437988281

💡 Erklärung:


▶ Lass uns einen gigantischen String machen!

<!-- Example ID: c7a07424-63fe-4504-9842-8f3d334f28fc --->
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:

# Ausgeführt in der ipython-Shell unter Verwendung von %timeit für eine bessere Lesbarkeit der Ergebnisse.
# Du kannst das timeit-Modul auch in der normalen Python-Shell/scriptm= verwenden, Beispielverwendung unten
# 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)

Erhöhen wir die Anzahl der Iterationen um den Faktor 10.

>>> NUM_ITERS = 10000
>>> %timeit -n1000 add_string_with_plus(NUM_ITERS) # Linearer Anstieg der Ausführungszeit
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) # Quadratische Anstieg
6.82 ms ± 134 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit -n1000 add_string_with_format(NUM_ITERS) # Linearer Anstieg
645 µs ± 24.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit -n1000 add_string_with_join(NUM_ITERS) # Linearer Anstieg
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) # Linearer Anstieg
86.3 µs ± 2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

💡 Erklärung


▶ Verlangsamen von dict Lookups *

<!-- Example ID: c9c26ce6-df0c-47f7-af0b-966b9386d4c3 --->
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]  # Versuch, auf einen Schlüssel zuzugreifen, der nicht existiert
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)

Wieso werden dieselben lookups immer langsamer?

💡 Erklärung:

▶ Blähende Instanz dicts *

<!-- Example ID: fe706ab4-1615-c0ba-a078-76c98cbe3f48 --->
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, oder Python 3 Versionen können ein bisschen 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 noch einmal... in einem neuen Interpreter:

>>> o1 = SomeClass()
>>> o2 = SomeClass()
>>> dict_size(o1)
104  # wie erwartet
>>> o1.some_attr5 = 5
>>> o1.some_attr6 = 6
>>> dict_size(o1)
360
>>> dict_size(o2)
272
>>> o3 = SomeClass()
>>> dict_size(o3)
232

Was führt dazu, dass diese Dictionaries aufgebläht werden? Und warum werden neu erstellte Objekte ebenfalls aufgebläht?

💡 Erklärung:

▶ Kleinigkeiten *

<!-- Example ID: f885cb82-f1e4-4daa-9ff3-972b14cb1324 --->

Contributing

Ein paar Wege, wie du zu wtfpython beitragen kannst:

Für mehr Details, wirf bitte einen Blick auf CONTRIBUTING.md. Erstelle ruhig ein neues Issue, um zu diskutieren.

PS: Bitte keine Anfragen mit Links erstellen. Es werden keine Links hinzugefügt, es sein denn sie sind für das Projekt relevant.

Anerkennung

Die Idee und das Design für diese Sammlung wurden durch Denys Dovhans tolles Projekt inspiriert wtfjs. Der überragende Support von Pythonisten hat es zu dem gemacht, was es heute ist.

Ein paar nützliche Links!

🎓 License

WTFPL 2.0

© Satwik Kansal

Überrasche auch deine Freunde!

Wenn du wtfpython cool findest, kannst du diese Quick-Links nutzen, um es mit deinen Freunden zu teilen:

Twitter | Linkedin | Facebook

Brauchst du eine pdf version?

Ich habe ein paar Anfragen für eine pdf (und epub) Version von wtfpython erhalten. Du kannst hier deine Daten angeben, um sie schnellstmöglich zu bekommen.

Das ist alles, Freunde! Um neue Updates zu erhalten, kannst du deine email hier hinzufügen.