Coole neue Funktionen in Python 3.7

Coole neue Funktionen in Python 3.7

Python 3.7 ist offiziell veröffentlicht! Diese neue Python-Version befindet sich seit September 2016 in der Entwicklung, und jetzt können wir alle die Ergebnisse der harten Arbeit der Kernentwickler genießen.

Was bringt die neue Python-Version? Während die documentation einen guten Überblick über die neuen Funktionen bietet, wird dieser Artikel einen tiefen Einblick in einige der größten Neuigkeiten geben. Diese schließen ein:

  • Einfacherer Zugriff auf Debugger über einen neuen integrierten + breakpoint () +

  • Einfache Klassenerstellung mit Datenklassen

  • Benutzerdefinierter Zugriff auf Modulattribute

  • Verbesserte Unterstützung für Typhinweise

  • Timing-Funktionen mit höherer Präzision

Noch wichtiger ist, dass Python 3.7 schnell ist.

In den letzten Abschnitten dieses Artikels erfahren Sie mehr über diese Geschwindigkeit sowie einige der anderen coolen Funktionen von Python 3.7. Sie erhalten auch einige Tipps zum Upgrade auf die neue Version.

Der + breakpoint () + Built-In

Während wir uns vielleicht bemühen, perfekten Code zu schreiben, ist die einfache Wahrheit, dass wir dies niemals tun. Das Debuggen ist ein wichtiger Bestandteil der Programmierung. Python 3.7 führt die neue integrierte Funktion + breakpoint () + ein. Dies fügt Python keine neuen Funktionen hinzu, macht jedoch die Verwendung von Debuggern flexibler und intuitiver.

Angenommen, Sie haben den folgenden Buggy-Code in der Datei + bugs.py +:

def divide(e, f):
    return f/e

a, b = 0, 1
print(divide(a, b))

Das Ausführen des Codes verursacht einen "+ ZeroDivisionError " innerhalb der Funktion " dividieren () ". Angenommen, Sie möchten Ihren Code unterbrechen und in einen https://realpython.com/python-debugging-pdb/[debugger] ganz oben in ` divid () +` wechseln. Sie können dies tun, indem Sie in Ihrem Code einen sogenannten „Haltepunkt“ festlegen:

def divide(e, f):
    # Insert breakpoint here
    return f/e

Ein Haltepunkt ist ein Signal in Ihrem Code, dass die Ausführung vorübergehend gestoppt werden soll, damit Sie sich im aktuellen Status des Programms umsehen können. Wie platzieren Sie den Haltepunkt? In Python 3.6 und niedriger verwenden Sie diese etwas kryptische Zeile:

def divide(e, f):
    import pdb; pdb.set_trace()
    return f/e

Hier ist https://docs.python.org/library/pdb.html [+ pdb +] der Python-Debugger aus der Standardbibliothek. In Python 3.7 können Sie stattdessen den neuen Funktionsaufruf + breakpoint () + als Verknüpfung verwenden:

def divide(e, f):
    breakpoint()
    return f/e

Im Hintergrund importiert + breakpoint () + zuerst + pdb + und ruft dann + pdb.set_trace () + für Sie auf. Die offensichtlichen Vorteile sind, dass + breakpoint () + leichter zu merken ist und dass Sie nur 12 statt 27 Zeichen eingeben müssen. Der eigentliche Vorteil der Verwendung von "+ breakpoint () +" ist jedoch die Anpassbarkeit.

Führen Sie Ihr Skript "+ bugs.py " mit " breakpoint () +" aus:

$ python3.7 bugs.py
>/home/gahjelle/bugs.py(3)divide()
-> return f/e
(Pdb)

Das Skript wird unterbrochen, wenn es + breakpoint () + erreicht und Sie in eine PDB-Debugging-Sitzung versetzt. Sie können + c + eingeben und [.keys] # drücken. # Geben Sie # ein, um das Skript fortzusetzen. Weitere Informationen zu PDB und Debugging finden Sie unter https://realpython.com/python-debugging-pdb/[Nathan Jennings 'PDB-Handbuch.

Angenommen, Sie glauben, Sie haben den Fehler behoben. Sie möchten das Skript erneut ausführen, ohne jedoch im Debugger anzuhalten. Sie können natürlich die Zeile + breakpoint () + auskommentieren, aber eine andere Option ist die Verwendung der Umgebungsvariablen + PYTHONBREAKPOINT +. Diese Variable steuert das Verhalten von "+ breakpoint () ". Wenn Sie " PYTHONBREAKPOINT = 0 " setzen, wird jeder Aufruf von " breakpoint () +" ignoriert:

$ PYTHONBREAKPOINT=0 python3.7 bugs.py
ZeroDivisionError: division by zero

Hoppla, es scheint, als hätten Sie den Fehler doch nicht behoben …​

Eine andere Option ist die Verwendung von "+ PYTHONBREAKPOINT +", um einen anderen Debugger als PDB anzugeben. Um beispielsweise PuDB (einen visuellen Debugger in der Konsole) zu verwenden, haben Sie folgende Möglichkeiten:

$ PYTHONBREAKPOINT=pudb.set_trace python3.7 bugs.py

Damit dies funktioniert, muss + pudb + installiert sein (+ pip install pudb +). Python kümmert sich jedoch darum, + pudb + für Sie zu importieren. Auf diese Weise können Sie auch Ihren Standard-Debugger festlegen. Setzen Sie einfach die Umgebungsvariable + PYTHONBREAKPOINT + auf Ihren bevorzugten Debugger. Anweisungen zum Festlegen einer Umgebungsvariablen auf Ihrem System finden Sie unter this guide.

Die neue Funktion + breakpoint () + funktioniert nicht nur mit Debuggern. Eine bequeme Option könnte darin bestehen, einfach eine interaktive Shell in Ihrem Code zu starten. Um beispielsweise eine IPython-Sitzung zu starten, können Sie Folgendes verwenden:

$ PYTHONBREAKPOINT=IPython.embed python3.7 bugs.py
IPython 6.3.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: print(e/f)
0.0

Sie können auch Ihre eigene Funktion erstellen und + breakpoint () + das aufrufen lassen. Der folgende Code druckt alle Variablen im lokalen Bereich. Fügen Sie es einer Datei mit dem Namen + bp_utils.py + hinzu:

from pprint import pprint
import sys

def print_locals():
    caller = sys._getframe(1)  # Caller is 1 frame up.
    pprint(caller.f_locals)

Um diese Funktion zu verwenden, setzen Sie + PYTHONBREAKPOINT + wie zuvor mit der Notation + <Modul>. <Funktion> +:

$ PYTHONBREAKPOINT=bp_utils.print_locals python3.7 bugs.py
{'e': 0, 'f': 1}
ZeroDivisionError: division by zero

Normalerweise wird + breakpoint () + verwendet, um Funktionen und Methoden aufzurufen, die keine Argumente benötigen. Es ist jedoch auch möglich, Argumente zu übergeben. Ändern Sie die Zeile + breakpoint () + in + bugs.py + in:

breakpoint(e, f, end="<-END\n")
*Hinweis:* Der Standard-PDB-Debugger löst in dieser Zeile einen "+ TypeError +" aus, da "+ pdb.set_trace () +" keine Positionsargumente akzeptiert.

Führen Sie diesen Code mit + breakpoint () + aus, der sich als + print () + Funktion tarnt, um ein einfaches Beispiel für die Argumente zu sehen, die durchlaufen werden:

$ PYTHONBREAKPOINT=print python3.7 bugs.py
0 1<-END
ZeroDivisionError: division by zero

Siehe PEP 553 sowie die Dokumentation zu https://docs.python.org/3.7/library/functions.html#breakpoint [` + breakpoint () + ] und https://docs.python.org/3.7/library/sys.html#sys.breakpointhook [ + sys.breakpointhook () + `] für weitere Informationen.

Datenklassen

Das neue Modul + dataclasses + erleichtert das Schreiben eigener Klassen, da spezielle Methoden wie + . init () +, + . repr () + `und + . eq () + werden automatisch hinzugefügt. Mit dem Dekorator `+ @ dataclass + können Sie Folgendes schreiben:

from dataclasses import dataclass, field

@dataclass(order=True)
class Country:
    name: str
    population: int
    area: float = field(repr=False, compare=False)
    coastline: float = 0

    def beach_per_person(self):
        """Meters of coastline per person"""
        return (self.coastline * 1000)/self.population

Diese neun Codezeilen stehen für eine ganze Reihe von Code und Best Practices. Überlegen Sie, was erforderlich ist, um "+ Country " als reguläre Klasse zu implementieren: die " . init () " -Methode, eine " repr ", sechs verschiedene Vergleichsmethoden sowie die " .beach_per_person () " Methode. Sie können das Feld unten erweitern, um eine Implementierung von " Land +" anzuzeigen, die in etwa der Datenklasse entspricht:

Nach der Erstellung ist eine Datenklasse eine normale Klasse. Sie können beispielsweise auf normale Weise von einer Datenklasse erben. Der Hauptzweck von Datenklassen besteht darin, das Schreiben robuster Klassen, insbesondere kleiner Klassen, die hauptsächlich Daten speichern, schnell und einfach zu machen.

Sie können die Datenklasse + Country + wie jede andere Klasse verwenden:

>>>

>>> norway = Country("Norway", 5320045, 323802, 58133)
>>> norway
Country(name='Norway', population=5320045, coastline=58133)

>>> norway.area
323802

>>> usa = Country("United States", 326625791, 9833517, 19924)
>>> nepal = Country("Nepal", 29384297, 147181)
>>> nepal
Country(name='Nepal', population=29384297, coastline=0)

>>> usa.beach_per_person()
0.06099946957342386

>>> norway.beach_per_person()
10.927163210085629

Beachten Sie, dass alle Felder "+ .name ", " .population ", " .area " und " .coastline " beim Initialisieren der Klasse verwendet werden (obwohl " .coastline " optional ist, wie in der Beispiel für Binnennepal). Die Klasse " Country " hat eine vernünftige https://dbader.org/blog/python-repr-vs-str [` repr +`], während das Definieren von Methoden genauso funktioniert wie für reguläre Klassen.

Standardmäßig können Datenklassen auf Gleichheit verglichen werden. Da wir im Dekorator + @ dataclass + + order = True + angegeben haben, kann die Klasse + Country + auch sortiert werden:

>>>

>>> norway == norway
True

>>> nepal == usa
False

>>> sorted((norway, usa, nepal))
[Country(name='Nepal', population=29384297, coastline=0),
 Country(name='Norway', population=5320045, coastline=58133),
 Country(name='United States', population=326625791, coastline=19924)]

Die Sortierung erfolgt nach den Feldwerten, zuerst + .name +, dann + .population + und so weiter. Wenn Sie jedoch + field () + verwenden, können Sie customize, welche Felder im Vergleich verwendet werden. Im Beispiel wurde das Feld "+ .area " aus " repr +" und den Vergleichen weggelassen.

*Hinweis:* Die Länderdaten stammen aus dem https://www.cia.gov/library/publications/the-world-factbook/[CIA World Factbook]. Die Bevölkerungszahlen werden für Juli 2017 geschätzt.

Bevor Sie alle Ihren nächsten Strandurlaub in Norwegen buchen, sagt das Factbook Folgendes über das Norwegian Climate: „Gemäßigt entlang der Küste, modifiziert durch North Atlantic Current; kälteres Inneres mit erhöhtem Niederschlag und kälteren Sommern; das ganze Jahr über regnerisch an der Westküste. “

Datenklassen machen einige der gleichen Dinge wie https://dbader.org/blog/writing-clean-python-with-namedtuples [+ namedtuple +]. Ihre größte Inspiration beziehen sie jedoch aus dem + attrs + Projekt. Weitere Beispiele und weitere Informationen sowie https://www.python.org/dev/peps/pep-0557 finden Sie in unserer full Anleitung zu Datenklassen/[PEP 557] für die offizielle Beschreibung.

Anpassung der Modulattribute

Attribute sind überall in Python! Während Klassenattribute wahrscheinlich die bekanntesten sind, können Attribute tatsächlich auf praktisch alles angewendet werden - einschließlich Funktionen und Modulen. Einige der grundlegenden Funktionen von Python sind als Attribute implementiert: die meisten Introspektionsfunktionen, Dokumentzeichenfolgen und Namensräume. Funktionen innerhalb eines Moduls werden als Modulattribute zur Verfügung gestellt.

Attribute werden am häufigsten mit der Punktnotation abgerufen: + thing.attribute +. Sie können jedoch auch Attribute abrufen, die zur Laufzeit mit + getattr () + benannt werden:

import random

random_attr = random.choice(("gammavariate", "lognormvariate", "normalvariate"))
random_func = getattr(random, random_attr)

print(f"A {random_attr} random value: {random_func(1, 1)}")

Wenn Sie diesen Code ausführen, wird Folgendes erzeugt:

A gammavariate random value: 2.8017715125270618

Bei Klassen sucht der Aufruf von "+ thing.attr " zuerst nach " attr ", das auf " thing " definiert ist. Wird es nicht gefunden, wird die spezielle Methode ` thing . getattr (" attr ") ` aufgerufen. (Dies ist eine Vereinfachung. Weitere Informationen finden Sie unter http://blog.lerner.co.il/python-attributes/[this article].) Mit der Methode ` . getattr () +` können Sie den Zugriff auf Attribute für Objekte anpassen.

Bis Python 3.7 war dieselbe Anpassung für Modulattribute nicht einfach verfügbar. Https://www.python.org/dev/peps/pep-0562/[PEP 562] führt jedoch + getattr () + auf Modulen zusammen mit einer entsprechenden + dir () + Funktion ein. Die Spezialfunktion + dir () + ermöglicht die Anpassung des Ergebnisses des Aufrufs von https://realpython.com/python-modules-packages/#the-dir-function [+ dir () + auf einem Modul].

Das PEP selbst enthält einige Beispiele für die Verwendung dieser Funktionen, einschließlich des Hinzufügens von Verfallswarnungen zu Funktionen und des verzögerten Ladens schwerer Submodule. Im Folgenden wird ein einfaches Plugin-System erstellt, mit dem Funktionen dynamisch zu einem Modul hinzugefügt werden können. Dieses Beispiel nutzt Python-Pakete. Weitere Informationen zu Paketen finden Sie unter this article.

Erstellen Sie ein neues Verzeichnis, + plugins +, und fügen Sie den folgenden Code zu einer Datei hinzu, + plugins/ init . Py +:

from importlib import import_module
from importlib import resources

PLUGINS = dict()

def register_plugin(func):
    """Decorator to register plug-ins"""
    name = func.__name__
    PLUGINS[name] = func
    return func

def __getattr__(name):
    """Return a named plugin"""
    try:
        return PLUGINS[name]
    except KeyError:
        _import_plugins()
        if name in PLUGINS:
            return PLUGINS[name]
        else:
            raise AttributeError(
                f"module {__name__!r} has no attribute {name!r}"
            ) from None

def __dir__():
    """List available plug-ins"""
    _import_plugins()
    return list(PLUGINS.keys())

def _import_plugins():
    """Import all resources to register plug-ins"""
    for name in resources.contents(__name__):
        if name.endswith(".py"):
            import_module(f"{__name__}.{name[:-3]}")

Bevor wir uns ansehen, was dieser Code bewirkt, fügen Sie zwei weitere Dateien im Verzeichnis + plugins + hinzu. Schauen wir uns zuerst + plugins/plugin_1.py + an:

from . import register_plugin

@register_plugin
def hello_1():
    print("Hello from Plugin 1")

Fügen Sie als Nächstes ähnlichen Code in die Datei + plugins/plugin_2.py + ein:

from . import register_plugin

@register_plugin
def hello_2():
    print("Hello from Plugin 2")

@register_plugin
def goodbye():
    print("Plugin 2 says goodbye")

Diese Plugins können jetzt wie folgt verwendet werden:

>>>

>>> import plugins
>>> plugins.hello_1()
Hello from Plugin 1

>>> dir(plugins)
['goodbye', 'hello_1', 'hello_2']

>>> plugins.goodbye()
Plugin 2 says goodbye

Dies mag nicht alles so revolutionär erscheinen (und ist es wahrscheinlich nicht), aber schauen wir uns an, was hier tatsächlich passiert ist. Normalerweise muss, um + plugins.hello_1 () + aufrufen zu können, die Funktion + hello_1 () + in einem + plugins + Modul definiert oder explizit in + init . Py + in einem `+ importiert werden Plugins + `Paket. Hier ist es weder!

Stattdessen wird "+ hello_1 () " in einer beliebigen Datei innerhalb des " plugins " -Pakets definiert, und " hello_1 () " wird Teil des " plugins " -Pakets, indem es sich mit dem " @ register_plugin +" registriert `https://realpython.com/primer-on-python-decorators/[decorator].

Der Unterschied ist subtil. Anstatt dass das Paket vorschreibt, welche Funktionen verfügbar sind, registrieren sich die einzelnen Funktionen als Teil des Pakets. Dies gibt Ihnen eine einfache Struktur, in der Sie Funktionen unabhängig vom Rest des Codes hinzufügen können, ohne eine zentralisierte Liste der verfügbaren Funktionen führen zu müssen.

Lassen Sie uns einen kurzen Überblick darüber geben, was + getattr () + in den + Plugins/ init . Py + Code tut. Wenn Sie nach + plugins.hello_1 () + gefragt haben, sucht Python zuerst nach einer + hello_1 () + Funktion in der Datei + plugins/ init . Py +. Da es keine solche Funktion gibt, ruft Python stattdessen + getattr (" hello_1 ") + auf. Denken Sie an den Quellcode der Funktion + getattr () +:

def __getattr__(name):
    """Return a named plugin"""
    try:
        return PLUGINS[name]        # 1) Try to return plugin
    except KeyError:
        _import_plugins()           # 2) Import all plugins
        if name in PLUGINS:
            return PLUGINS[name]    # 3) Try to return plugin again
        else:
            raise AttributeError(   # 4) Raise error
                f"module {__name__!r} has no attribute {name!r}"
            ) from None

+ getattr () + enthält die folgenden Schritte. Die Nummern in der folgenden Liste entsprechen den nummerierten Kommentaren im Code:

  1. Zunächst versucht die Funktion optimistisch, das genannte Plugin aus dem Wörterbuch + PLUGINS + zurückzugeben. Dies ist erfolgreich, wenn ein Plugin mit dem Namen "+ name +" vorhanden ist und bereits importiert wurde.

  2. Wenn das genannte Plugin nicht im Wörterbuch + PLUGINS + gefunden wird, stellen wir sicher, dass alle Plugins importiert werden.

  3. Geben Sie das genannte Plugin zurück, wenn es nach dem Import verfügbar geworden ist.

  4. Wenn sich das Plugin nach dem Importieren aller Plugins nicht im Wörterbuch "+ PLUGINS " befindet, wird ein " AttributeError " ausgelöst, der besagt, dass " name +" kein Attribut (Plugin) des aktuellen Moduls ist.

Wie ist das Wörterbuch + PLUGINS + gefüllt? Die Funktion + _import_plugins () + importiert alle Python-Dateien in das Paket + plugins +, scheint jedoch nicht + PLUGINS + zu berühren:

def _import_plugins():
    """Import all resources to register plug-ins"""
    for name in resources.contents(__name__):
        if name.endswith(".py"):
            import_module(f"{__name__}.{name[:-3]}")

Vergessen Sie nicht, dass jede Plugin-Funktion vom Dekorator + @ register_plugin + dekoriert wird. Dieser Dekorator wird aufgerufen, wenn die Plugins importiert werden, und ist derjenige, der das Wörterbuch + PLUGINS + tatsächlich auffüllt. Sie können dies sehen, wenn Sie eine der Plugin-Dateien manuell importieren:

>>>

>>> import plugins
>>> plugins.PLUGINS
{}

>>> import plugins.plugin_1
>>> plugins.PLUGINS
{'hello_1': <function hello_1 at 0x7f29d4341598>}

Wenn Sie das Beispiel fortsetzen, beachten Sie, dass beim Aufrufen von + dir () + auf dem Modul auch die verbleibenden Plugins importiert werden:

>>>

>>> dir(plugins)
['goodbye', 'hello_1', 'hello_2']

>>> plugins.PLUGINS
{'hello_1': <function hello_1 at 0x7f29d4341598>,
 'hello_2': <function hello_2 at 0x7f29d4341620>,
 'goodbye': <function goodbye at 0x7f29d43416a8>}

+ dir () + listet normalerweise alle verfügbaren Attribute eines Objekts auf. Normalerweise führt die Verwendung von "+ dir () +" für ein Modul zu ungefähr folgendem Ergebnis:

>>>

>>> import plugins
>>> dir(plugins)
['PLUGINS', '__builtins__', '__cached__', '__doc__',
 '__file__', '__getattr__', '__loader__', '__name__',
 '__package__', '__path__', '__spec__', '_import_plugins',
 'import_module', 'register_plugin', 'resources']

Dies sind zwar nützliche Informationen, wir sind jedoch mehr daran interessiert, die verfügbaren Plugins verfügbar zu machen. In Python 3.7 können Sie das Ergebnis des Aufrufs von "+ dir () " für ein Modul anpassen, indem Sie eine Sonderfunktion " dir () " hinzufügen. Für ` plugins/ init . Py +` stellt diese Funktion zunächst sicher, dass alle Plugins importiert wurden, und listet dann ihre Namen auf:

def __dir__():
    """List available plug-ins"""
    _import_plugins()
    return list(PLUGINS.keys())

Bevor Sie dieses Beispiel verlassen, beachten Sie bitte, dass wir auch eine andere coole neue Funktion von Python 3.7 verwendet haben. Um alle Module in das Verzeichnis + plugins + zu importieren, haben wir das neue Modul https://docs.python.org/3.7/library/importlib.html#module-importlib.resources [+ importlib.resources +] verwendet. Dieses Modul ermöglicht den Zugriff auf Dateien und Ressourcen in Modulen und Paketen, ohne dass + Datei + Hacks (die nicht immer funktionieren) oder + pkg_resources + (was langsam ist) erforderlich sind. Weitere Funktionen von `+ importlib.resources + sind der Link: # other-ziemlich-cool-features [später hervorgehoben].

Schreibverbesserungen

Type Hinweise und Anmerkungen wurden während der gesamten Python 3-Versionsreihe ständig weiterentwickelt. Das Schreibsystem von Python ist jetzt ziemlich stabil. Dennoch bringt Python 3.7 einige Verbesserungen in die Tabelle: bessere Leistung, Kernunterstützung und Weiterleitungsreferenzen.

Python führt zur Laufzeit keine Typprüfung durch (es sei denn, Sie verwenden explizit Pakete wie + erzwingen +). Daher sollte das Hinzufügen von Typhinweisen zu Ihrem Code die Leistung nicht beeinträchtigen.

Leider ist dies nicht ganz richtig, da die meisten Typhinweise das Modul + Typing + benötigen. Das Modul "+ Typing " ist eines der https://www.python.org/dev/peps/pep-0560/#performance[slowest-Module] in der Standardbibliothek. https://www.python.org/dev/peps/pep-0560[PEP 560] bietet einige grundlegende Unterstützung für die Eingabe in Python 3.7, wodurch das Modul ` Typing +` erheblich beschleunigt wird. Die Details hierzu sind im Allgemeinen nicht bekannt. Lehnen Sie sich einfach zurück und genießen Sie die gesteigerte Leistung.

Während das Typensystem von Python einigermaßen ausdrucksstark ist, sind Vorwärtsreferenzen ein Problem, das einige Schmerzen verursacht. Typhinweise - oder allgemeiner Anmerkungen - werden ausgewertet, während das Modul importiert wird. Daher müssen alle Namen bereits definiert sein, bevor sie verwendet werden. Folgendes ist nicht möglich:

class Tree:
    def __init__(self, left: Tree, right: Tree) -> None:
        self.left = left
        self.right = right

Das Ausführen des Codes löst einen "+ NameError " aus, da die Klasse " Tree " in der Definition der " . init () +" -Methode noch nicht (vollständig) definiert ist:

Traceback (most recent call last):
  File "tree.py", line 1, in <module>
    class Tree:
  File "tree.py", line 2, in Tree
    def __init__(self, left: Tree, right: Tree) -> None:
NameError: name 'Tree' is not defined

Um dies zu überwinden, hätten Sie stattdessen "" Tree "" als String-Literal schreiben müssen:

class Tree:
    def __init__(self, left: "Tree", right: "Tree") -> None:
        self.left = left
        self.right = right

Die ursprüngliche Diskussion finden Sie unter PEP 484.

In Zukunft werden solche sogenannten Forward-Referenzen zulässig sein. Http://www.curiousefficiency.org/posts/2014/08/python-4000.html[Python 4.0]. Dies wird dadurch behoben, dass Anmerkungen erst ausgewertet werden, wenn dies ausdrücklich verlangt wird. PEP 563 beschreibt die Details dieses Vorschlags. In Python 3.7 sind Weiterleitungsreferenzen bereits als https://docs.python.org/library/future.html [+ future + import] verfügbar. Sie können jetzt Folgendes schreiben:

from __future__ import annotations

class Tree:
    def __init__(self, left: Tree, right: Tree) -> None:
        self.left = left
        self.right = right

Beachten Sie, dass die verzögerte Auswertung von Anmerkungen nicht nur die etwas umständliche Syntax " Tree " vermeidet, sondern auch Ihren Code beschleunigt, da Typhinweise nicht ausgeführt werden. Weiterleitungsreferenzen werden bereits von + mypy + unterstützt.

Bei weitem die häufigste Verwendung von Anmerkungen sind Typhinweise. Sie haben jedoch zur Laufzeit vollen Zugriff auf die Anmerkungen und können sie nach Belieben verwenden. Wenn Sie Anmerkungen direkt bearbeiten, müssen Sie die möglichen Weiterleitungsreferenzen explizit behandeln.

Lassen Sie uns einige zugegebenermaßen alberne Beispiele erstellen, die zeigen, wann Anmerkungen ausgewertet werden. Zuerst machen wir es im alten Stil, sodass Anmerkungen beim Import ausgewertet werden. Lassen Sie + anno.py + den folgenden Code enthalten:

def greet(name: print("Now!")):
    print(f"Hello {name}")

Beachten Sie, dass die Anmerkung von "+ Name " " print () +" lautet. Dies ist nur, um genau zu sehen, wann die Anmerkung ausgewertet wird. Importieren Sie das neue Modul:

>>>

>>> import anno
Now!

>>> anno.greet.__annotations__
{'name': None}

>>> anno.greet("Alice")
Hello Alice

Wie Sie sehen können, wurde die Anmerkung beim Import ausgewertet. Beachten Sie, dass "+ name " mit " None " versehen wird, da dies der Rückgabewert von " print () +" ist.

Fügen Sie den Import + future + hinzu, um die verzögerte Auswertung von Anmerkungen zu ermöglichen:

from __future__ import annotations

def greet(name: print("Now!")):
    print(f"Hello {name}")

Beim Importieren dieses aktualisierten Codes wird die Anmerkung nicht ausgewertet:

>>>

>>> import anno

>>> anno.greet.__annotations__
{'name': "print('Now!')"}

>>> anno.greet("Marty")
Hello Marty

Beachten Sie, dass + Now! + Niemals gedruckt wird und die Annotation als String-Literal im Wörterbuch + Annotations + beibehalten wird. Um die Annotation auszuwerten, verwenden Sie + typing.get_type_hints () + oder + eval () +:

>>>

>>> import typing
>>> typing.get_type_hints(anno.greet)
Now!
{'name': <class 'NoneType'>}

>>> eval(anno.greet.__annotations__["name"])
Now!

>>> anno.greet.__annotations__
{'name': "print('Now!')"}

Beachten Sie, dass das Wörterbuch + Annotations + niemals aktualisiert wird. Sie müssen die Annotation daher jedes Mal auswerten, wenn Sie sie verwenden.

Timing-Präzision

In Python 3.7 erhält das Modul https://docs.python.org/library/time.html [+ time +] einige neue Funktionen, wie unter https://www.python.org/dev/peps/pep- beschrieben 0564/[PEP 564]. Insbesondere werden die folgenden sechs Funktionen hinzugefügt:

  • + clock_gettime_ns () +: Gibt die Zeit einer angegebenen Uhr zurück

  • + clock_settime_ns () +: Legt die Zeit einer angegebenen Uhr fest

  • + monotonic_ns () +: Gibt die Zeit einer relativen Uhr zurück, die nicht rückwärts gehen kann (z. B. aufgrund der Sommerzeit).

  • + perf_counter_ns () + : Gibt den Wert eines Leistungsindikators zurück - eine Uhr, die speziell zum Messen kurzer Intervalle entwickelt wurde

  • + process_time_ns () +: Gibt die Summe der System- und Benutzer-CPU-Zeit des aktuellen Prozesses zurück (ohne Ruhezeit).

  • + time_ns () + : Gibt die Anzahl der Nanosekunden seit dem 1. Januar 1970 zurück

In gewisser Weise wurden keine neuen Funktionen hinzugefügt. Jede Funktion ähnelt einer bereits vorhandenen Funktion ohne das Suffix + _ns +. Der Unterschied besteht darin, dass die neuen Funktionen eine Anzahl von Nanosekunden als "+ int " anstelle einer Anzahl von Sekunden als " float +" zurückgeben.

Für die meisten Anwendungen wird der Unterschied zwischen diesen neuen Nanosekundenfunktionen und ihrem alten Gegenstück nicht spürbar sein. Die neuen Funktionen sind jedoch leichter zu verstehen, da sie auf "+ int " anstelle von " float +" beruhen. Gleitkommazahlen sind https://docs.python.org/tutorial/floatingpoint.html [von Natur aus ungenau]:

>>>

>>> 0.1 + 0.1 + 0.1
0.30000000000000004

>>> 0.1 + 0.1 + 0.1 == 0.3
False

Dies ist kein Problem mit Python, sondern eine Folge von Computern, die unendliche Dezimalzahlen mit einer endlichen Anzahl von Bits darstellen müssen.

Ein Python + float + folgt dem IEEE 754 Standard und verwendet 53 signifikante Bits. Das Ergebnis ist, dass eine Zeit von mehr als 104 Tagen (2⁵³ oder ungefähr 9 Billiarden Nanosekunden) nicht als Float mit Nanosekundengenauigkeit ausgedrückt werden kann. Im Gegensatz dazu ist ein Python https://stackoverflow.com/a/9860611 [+ int + unbegrenzt], sodass eine ganzzahlige Anzahl von Nanosekunden unabhängig vom Zeitwert immer eine Nanosekundengenauigkeit aufweist.

Beispielsweise gibt "+ time.time () " die Anzahl der Sekunden seit dem 1. Januar 1970 zurück. Diese Zahl ist bereits ziemlich groß, so dass die Genauigkeit dieser Zahl im Mikrosekundenbereich liegt. Diese Funktion zeigt die größte Verbesserung in der Version " _ns ". Die Auflösung von ` time.time_ns () ` ist ungefähr https://www.python.org/dev/peps/pep-0564/#analysis[3 mal besser] als für ` time.time () +` .

Was ist übrigens eine Nanosekunde? Technisch gesehen ist es eine Milliardstel Sekunde oder "+ 1e-9 +" Sekunde, wenn Sie die wissenschaftliche Notation bevorzugen. Dies sind jedoch nur Zahlen, die keine wirkliche Intuition vermitteln. Eine bessere visuelle Hilfe finden Sie unter Grace Hopper’s wunderbare demonstration of the nanosecond.

Abgesehen davon, wenn Sie mit Datumsangaben mit einer Genauigkeit von Nanosekunden arbeiten müssen, wird die Standardbibliothek + datetime + diese nicht schneiden. Es werden explizit nur Mikrosekunden verarbeitet:

>>>

>>> from datetime import datetime, timedelta
>>> datetime(2018, 6, 27) + timedelta(seconds=1e-6)
datetime.datetime(2018, 6, 27, 0, 0, 0, 1)

>>> datetime(2018, 6, 27) + timedelta(seconds=1e-9)
datetime.datetime(2018, 6, 27, 0, 0)

Stattdessen können Sie das Projekt + astropy + verwenden. Das Paket + astropy.time + repräsentiert Datenzeiten unter Verwendung von zwei + float + -Objekten, was eine Präzision von weniger als einer Nanosekunde über Zeiten hinweg garantiert, die das Alter des Universums umfassen . ”

>>>

>>> from astropy.time import Time, TimeDelta
>>> Time("2018-06-27")
<Time object: scale='utc' format='iso' value=2018-06-27 00:00:00.000>

>>> t = Time("2018-06-27") + TimeDelta(1e-9, format="sec")
>>> (t - Time("2018-06-27")).sec
9.976020010071807e-10

Die neueste Version von + astropy + ist in Python 3.5 und höher verfügbar.

Andere ziemlich coole Funktionen

Bisher haben Sie die Schlagzeilen zu den Neuerungen in Python 3.7 gesehen. Es gibt jedoch viele andere Änderungen, die auch ziemlich cool sind. In diesem Abschnitt werden wir einige davon kurz betrachten.

Die Reihenfolge der Wörterbücher ist garantiert

Die CPython-Implementierung von Python 3.6 hat Wörterbücher bestellt. (PyPy hat dies ebenfalls.) Dies bedeutet, dass Elemente in Wörterbüchern in derselben Reihenfolge wiederholt werden, in der sie eingefügt wurden. Das erste Beispiel verwendet Python 3.5 und das zweite Python 3.6:

>>>

>>> {"one": 1, "two": 2, "three": 3}  # Python <= 3.5
{'three': 3, 'one': 1, 'two': 2}

>>> {"one": 1, "two": 2, "three": 3}  # Python >= 3.6
{'one': 1, 'two': 2, 'three': 3}

In Python 3.6 war diese Reihenfolge nur eine schöne Folge dieser Implementierung von + dict +. In Python 3.7 sind Wörterbücher, die ihre Einfügereihenfolge beibehalten, Teil der Sprachenspezifikation. Als solches kann es jetzt in Projekten verwendet werden, die nur Python> = 3.7 (oder CPython> = 3.6) unterstützen.

"` + Async + " und " + await + `" sind Schlüsselwörter

Python 3.5 führte https://www.python.org/dev/peps/pep-0492/[coroutines mit der Syntax + async + und + await + ein. Um Probleme mit der Abwärtskompatibilität zu vermeiden, wurden "+ async " und " await " nicht zur Liste der reservierten Schlüsselwörter hinzugefügt. Mit anderen Worten, es war immer noch möglich, Variablen oder Funktionen mit den Namen " async " und " await +" zu definieren.

In Python 3.7 ist dies nicht mehr möglich:

>>>

>>> async = 1
  File "<stdin>", line 1
    async = 1
          ^
SyntaxError: invalid syntax

>>> def await():
  File "<stdin>", line 1
    def await():
            ^
SyntaxError: invalid syntax

"` + Asyncio + `" Facelifting

Die Standardbibliothek + asyncio + wurde ursprünglich in Python 3.4 eingeführt, um die Parallelität mithilfe von Ereignisschleifen, Coroutinen und Futures auf moderne Weise zu handhaben. Hier ist eine gentle Einführung.

In Python 3.7 erhält das Modul "+ asyncio " ein https://docs.python.org/3.7/whatsnew/3.7.html#asyncio [großes Facelifting], einschließlich vieler neuer Funktionen, Unterstützung für die Kontextvariablen (siehe Link: # Kontextvariablen [unten]) und Leistungsverbesserungen. Besonders hervorzuheben ist " asyncio.run () ", was das Aufrufen von Coroutinen aus synchronem Code vereinfacht. Mit https://docs.python.org/3.7/library/asyncio-task.html#asyncio.run [` asyncio.run () +`] müssen Sie die Ereignisschleife nicht explizit erstellen. Ein asynchrones Hello World-Programm kann jetzt geschrieben werden:

import asyncio

async def hello_world():
    print("Hello World!")

asyncio.run(hello_world())

Kontextvariablen

Kontextvariablen sind Variablen, die je nach Kontext unterschiedliche Werte haben können. Sie ähneln dem Thread-Local Storage, in dem jeder Ausführungsthread einen anderen Wert für eine Variable haben kann. Bei Kontextvariablen kann es jedoch mehrere Kontexte in einem Ausführungsthread geben. Der Hauptanwendungsfall für Kontextvariablen besteht darin, Variablen in gleichzeitigen asynchronen Aufgaben zu verfolgen.

Im folgenden Beispiel werden drei Kontexte mit jeweils einem eigenen Wert für den Wert "+ Name " erstellt. Die Funktion ` greet () ` kann später den Wert von ` name +` in jedem Kontext verwenden:

import contextvars

name = contextvars.ContextVar("name")
contexts = list()

def greet():
    print(f"Hello {name.get()}")

# Construct contexts and set the context variable name
for first_name in ["Steve", "Dina", "Harry"]:
    ctx = contextvars.copy_context()
    ctx.run(name.set, first_name)
    contexts.append(ctx)

# Run greet function inside each context
for ctx in reversed(contexts):
    ctx.run(greet)

Wenn Sie dieses Skript ausführen, werden Steve, Dina und Harry in umgekehrter Reihenfolge begrüßt:

$ python3.7 context_demo.py
Hello Harry
Hello Dina
Hello Steve

Importieren von Datendateien mit "` + importlib.resources + `"

Eine Herausforderung beim Packen eines Python-Projekts besteht darin, zu entscheiden, was mit Projektressourcen wie den vom Projekt benötigten Datendateien geschehen soll. Einige Optionen wurden häufig verwendet:

Jedes von diesen hat seine Mängel. Die erste Option ist nicht portabel. Die Verwendung von + Datei + ist portabler, aber wenn das Python-Projekt installiert ist, landet es möglicherweise in einer Zip-Datei und hat kein Attribut + Datei +. Die dritte Option löst dieses Problem, ist aber leider sehr langsam.

Eine bessere Lösung ist das neue Modul https://docs.python.org/3.7/library/importlib.html#module-importlib.resources [+ importlib.resources +] in der Standardbibliothek. Es verwendet die vorhandene Importfunktion von Python, um auch Datendateien zu importieren. Angenommen, Sie haben eine Ressource in einem Python-Paket wie folgt:

data/
│
├── alice_in_wonderland.txt
└── __init__.py

Beachten Sie, dass "+ data " ein https://realpython.com/python-modules-packages/[Python-Paket sein muss. Das heißt, das Verzeichnis muss eine Datei " init . Py " enthalten (die möglicherweise leer ist). Sie können dann die Datei ` alice_in_wonderland.txt +` wie folgt lesen:

>>>

>>> from importlib import resources
>>> with resources.open_text("data", "alice_in_wonderland.txt") as fid:
...     alice = fid.readlines()
...
>>> print("".join(alice[:7]))
CHAPTER I. Down the Rabbit-Hole

Alice was beginning to get very tired of sitting by her sister on the
bank, and of having nothing to do: once or twice she had peeped into the
book her sister was reading, but it had no pictures or conversations in
it, "and what is the use of a book," thought Alice "without pictures or
conversations?"

Eine ähnliche Funktion https://docs.python.org/3.7/library/importlib.html#importlib.resources.open_binary [+ resources.open_binary () +] steht zum Öffnen von Dateien im Binärmodus zur Verfügung. Im vorherigen Link: # Anpassung von Modulattributen [Beispiel "Plugins als Modulattribute"] haben wir "+ importlib.resources " verwendet, um die verfügbaren Plugins mithilfe von " resources.contents () +" zu ermitteln. Weitere Informationen finden Sie unter Barry Warsaws PyCon 2018-Vortrag.

Es ist möglich, + importlib.resources + in Python 2.7 und Python 3.4+ über einen backport zu verwenden. Ein guide zur Migration von + pkg_resources + nach + importlib.resources + ist verfügbar.

Entwickler-Tricks

Python 3.7 hat mehrere Funktionen hinzugefügt, die sich an Sie als Entwickler richten. Sie haben den Link: # the-breakpoint-integrierte [bereits gesehen die neue + breakpoint () + integrierte]. Darüber hinaus wurden dem Python-Interpreter einige neue https://docs.python.org/3.7/using/cmdline.html#id5 [+ -X + Befehlszeilenoptionen] hinzugefügt.

Mit + -X importtime + können Sie sich leicht ein Bild davon machen, wie viel Zeit die Importe in Ihr Skript benötigen:

$ python3.7 -X importtime my_script.py
import time: self [us] | cumulative | imported package
import time:      2607 |       2607 | _frozen_importlib_external
...
import time:       844 |      28866 |   importlib.resources
import time:       404 |      30434 | plugins

Die Spalte "+ kumulativ " zeigt die kumulative Importzeit (in Mikrosekunden). In diesem Beispiel dauerte das Importieren von " Plugins " ungefähr 0,03 Sekunden. Der größte Teil davon wurde für den Import von " importlib.resources " aufgewendet. Die Spalte ` self +` zeigt die Importzeit ohne verschachtelte Importe.

Sie können jetzt "+ -X dev " verwenden, um den "Entwicklungsmodus" zu aktivieren. Im Entwicklungsmodus werden bestimmte Debug-Funktionen und Laufzeitprüfungen hinzugefügt, die als zu langsam angesehen werden, um standardmäßig aktiviert zu werden. Dazu gehört, dass https://docs.python.org/library/faulthandler.html#module-faulthandler [` faulthandler +`] aktiviert wird, um einen Traceback bei schwerwiegenden Abstürzen anzuzeigen, sowie weitere Warnungen und Debug-Hooks.

Schließlich aktiviert "+ -X utf8 " den https://docs.python.org/3.7/using/cmdline.html#envvar-PYTHONUTF8[UTF-8-Modus]. (Siehe https://www.python.org/dev/peps/pep-0540/[PEP 540].) In diesem Modus wird " UTF-8 +" für die Textcodierung unabhängig vom aktuellen Gebietsschema verwendet.

Optimierungen

Jede neue Version von Python enthält eine Reihe von Optimierungen. In Python 3.7 gibt es einige signifikante Beschleunigungen, darunter:

  • Das Aufrufen vieler Methoden in der Standardbibliothek ist mit weniger Aufwand verbunden.

  • Methodenaufrufe sind im Allgemeinen bis zu 20% schneller.

  • Die Startzeit von Python selbst wird um 10-30% reduziert.

  • Das Importieren von + Typing + ist siebenmal schneller.

Darüber hinaus sind viele weitere spezialisierte Optimierungen enthalten. Eine detaillierte Übersicht finden Sie unter this list.

Also, sollte ich upgraden?

Beginnen wir mit der einfachen Antwort. Wenn Sie eine der neuen Funktionen ausprobieren möchten, die Sie hier gesehen haben, müssen Sie Python 3.7 verwenden können. Die Verwendung von Tools wie https://github.com/pyenv/pyenv [+ pyenv +] oder Anaconda macht es einfach, mehrere Versionen von Python nebeneinander zu installieren . Es gibt keinen Nachteil, Python 3.7 zu installieren und auszuprobieren.

Nun zu den komplizierteren Fragen. Sollten Sie Ihre Produktionsumgebung auf Python 3.7 aktualisieren? Sollten Sie Ihr eigenes Projekt von Python 3.7 abhängig machen, um die neuen Funktionen nutzen zu können?

Mit der offensichtlichen Einschränkung, dass Sie vor dem Upgrade Ihrer Produktionsumgebung immer gründliche Tests durchführen sollten, gibt es in Python 3.7 nur sehr wenige Dinge, die früheren Code beschädigen (+ async + und + warten + werden Schlüsselwörter sind jedoch ein Beispiel). Wenn Sie bereits ein modernes Python verwenden, sollte das Upgrade auf 3.7 reibungslos verlaufen. Wenn Sie ein wenig konservativ sein möchten, sollten Sie auf die Veröffentlichung der ersten Wartungsversion warten - Python 3.7.1 - https://www.python.org/dev/peps/pep-0537/#maintenance-releases [vorläufig voraussichtlich im Juli 2018].

Es ist schwieriger zu argumentieren, dass Sie Ihr Projekt nur 3.7 machen sollten. Viele der neuen Funktionen in Python 3.7 sind entweder als Backports für Python 3.6 (Datenklassen, + importlib.resources +) oder als Annehmlichkeiten (schnellerer Start und Methodenaufrufe, einfacheres Debuggen und + -X + Optionen verfügbar). Letzteres können Sie nutzen, indem Sie Python 3.7 selbst ausführen und gleichzeitig Ihren Code mit Python 3.6 (oder niedriger) kompatibel halten.

Die großen Funktionen, die Ihren Code für Python 3.7 sperren, sind Link: # Anpassung der Modulattribute [+ getattr () + auf Modulen], Link: # Typisierungsverbesserungen [Verweise in Typhinweisen weiterleiten] und die link: # Timing-Präzision [Nanosekunden + Zeit + Funktionen]. Wenn Sie eines davon wirklich brauchen, sollten Sie Ihre Anforderungen erfüllen. Andernfalls ist Ihr Projekt wahrscheinlich für andere nützlicher, wenn es länger auf Python 3.6 ausgeführt werden kann.

Weitere Informationen zum Upgrade finden Sie im Porting to Python 3.7.