Einführung
In diesem Lab lernen Sie, wie Sie die Interna von Python-Funktionen untersuchen können. Funktionen in Python sind Objekte mit ihren eigenen Attributen und Methoden, und das Verständnis dieser kann tiefere Einblicke in das Funktionieren von Funktionen bieten. Mit diesem Wissen können Sie leistungsfähigeres und anpassungsfähigeres Code schreiben.
Sie werden lernen, Funktionsattribute und -eigenschaften zu untersuchen, das inspect-Modul zur Untersuchung von Funktionssignaturen zu verwenden und diese Untersuchungstechniken anzuwenden, um die Implementierung einer Klasse zu verbessern. Die Datei, die Sie ändern werden, ist structure.py.
Untersuchung von Funktionsattributen
In Python werden Funktionen als First-Class-Objekte (erstklassige Objekte) betrachtet. Was bedeutet das? Nun, es ist ähnlich wie in der realen Welt, wo es verschiedene Arten von Objekten gibt, wie z.B. ein Buch oder einen Stift. In Python sind auch Funktionen Objekte, und wie andere Objekte verfügen sie auch über ihre eigenen Attribute. Diese Attribute können uns viele nützliche Informationen über die Funktion geben, wie z.B. ihren Namen, wo sie definiert ist und wie sie implementiert ist.
Lassen Sie uns unsere Untersuchung beginnen, indem wir eine interaktive Python-Shell öffnen. Diese Shell ist wie ein Spielplatz, auf dem wir sofort Python-Code schreiben und ausführen können. Dazu navigieren wir zunächst in das Projektverzeichnis und starten dann den Python-Interpreter. Hier sind die Befehle, die Sie in Ihrem Terminal ausführen müssen:
cd ~/project
python3
Jetzt, da wir in der interaktiven Python-Shell sind, definieren wir eine einfache Funktion. Diese Funktion nimmt zwei Zahlen und addiert sie. So können wir sie definieren:
def add(x, y):
'Adds two things'
return x + y
In diesem Code haben wir eine Funktion namens add erstellt. Sie nimmt zwei Parameter, x und y, und gibt ihre Summe zurück. Die Zeichenkette 'Adds two things' wird Docstring genannt, der verwendet wird, um zu dokumentieren, was die Funktion tut.
Verwendung von dir() zur Untersuchung von Funktionsattributen
In Python ist die dir()-Funktion ein nützliches Werkzeug. Sie kann verwendet werden, um eine Liste aller Attribute und Methoden eines Objekts zu erhalten. Lassen Sie uns sie verwenden, um zu sehen, welche Attribute unsere add-Funktion hat. Führen Sie den folgenden Code in der interaktiven Python-Shell aus:
dir(add)
Wenn Sie diesen Code ausführen, sehen Sie eine lange Liste von Attributen. Hier ist ein Beispiel für die Ausgabe:
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
Diese Liste zeigt alle Attribute und Methoden, die mit der add-Funktion verbunden sind.
Zugriff auf grundlegende Funktionsinformationen
Jetzt schauen wir uns einige der grundlegenden Funktionsattribute genauer an. Diese Attribute können uns wichtige Informationen über die Funktion geben. Führen Sie den folgenden Code in der interaktiven Python-Shell aus:
print(add.__name__)
print(add.__module__)
print(add.__doc__)
Wenn Sie diesen Code ausführen, sehen Sie die folgende Ausgabe:
add
__main__
Adds two things
Lassen Sie uns verstehen, was jedes dieser Attribute bedeutet:
__name__: Dieses Attribut gibt uns den Namen der Funktion. In unserem Fall heißt die Funktionadd.__module__: Es sagt uns, in welchem Modul die Funktion definiert ist. Wenn wir Code in der interaktiven Shell ausführen, ist das Modul normalerweise__main__.__doc__: Dies ist die Dokumentationszeichenkette (Docstring) der Funktion. Sie bietet eine kurze Beschreibung dessen, was die Funktion tut.
Untersuchung des Funktionscodes
Das __code__-Attribut einer Funktion ist sehr interessant. Es enthält Informationen darüber, wie die Funktion implementiert ist, einschließlich ihres Bytecodes und anderer Details. Lassen Sie uns sehen, was wir daraus lernen können. Führen Sie den folgenden Code in der interaktiven Python-Shell aus:
print(add.__code__.co_varnames)
print(add.__code__.co_argcount)
Die Ausgabe wird sein:
('x', 'y')
2
Hier ist, was diese Attribute uns sagen:
co_varnames: Es ist ein Tupel, das die Namen aller lokalen Variablen enthält, die von der Funktion verwendet werden. In unsereradd-Funktion sind die lokalen Variablenxundy.co_argcount: Dieses Attribut sagt uns, wie viele Argumente die Funktion erwartet. Unsereadd-Funktion erwartet zwei Argumente, also ist der Wert 2.
Wenn Sie neugierig sind und mehr Attribute des __code__-Objekts untersuchen möchten, können Sie die dir()-Funktion erneut verwenden. Führen Sie den folgenden Code aus:
dir(add.__code__)
Dies wird alle Attribute des Code-Objekts anzeigen, die tiefergehende Details darüber enthalten, wie die Funktion implementiert ist.
Verwendung des inspect-Moduls
In Python enthält die Standardbibliothek ein sehr nützliches inspect-Modul. Dieses Modul ist wie ein Detektivwerkzeug, das uns hilft, Informationen über lebende Objekte in Python zu sammeln. Lebende Objekte können Dinge wie Module, Klassen und Funktionen sein. Anstatt manuell durch die Attribute eines Objekts zu wühlen, um Informationen zu finden, bietet das inspect-Modul organisiertere und hochwertigere Methoden, um die Eigenschaften von Funktionen zu verstehen.
Lassen Sie uns weiterhin die gleiche interaktive Python-Shell verwenden, um zu untersuchen, wie dieses Modul funktioniert.
Funktionssignaturen
Die inspect.signature()-Funktion ist ein nützliches Werkzeug. Wenn Sie eine Funktion an sie übergeben, gibt sie ein Signature-Objekt zurück. Dieses Objekt enthält wichtige Details über die Parameter der Funktion.
Hier ist ein Beispiel. Nehmen wir an, wir haben eine Funktion namens add. Wir können die inspect.signature()-Funktion verwenden, um ihre Signatur zu erhalten:
import inspect
sig = inspect.signature(add)
print(sig)
Wenn Sie diesen Code ausführen, wird die Ausgabe sein:
(x, y)
Diese Ausgabe zeigt uns die Signatur der Funktion, die uns sagt, welche Parameter die Funktion akzeptieren kann.
Untersuchung von Parameterdetails
Wir können einen Schritt weiter gehen und tiefere Informationen über jeden Parameter der Funktion erhalten.
print(sig.parameters)
Die Ausgabe dieses Codes wird sein:
OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y">)])
Die Parameter der Funktion werden in einem geordneten Wörterbuch gespeichert. Manchmal interessieren uns möglicherweise nur die Namen der Parameter. Wir können dieses geordnete Wörterbuch in ein Tupel umwandeln, um nur die Parameternamen zu extrahieren.
param_names = tuple(sig.parameters)
print(param_names)
Die Ausgabe wird sein:
('x', 'y')
Untersuchung einzelner Parameter
Wir können uns auch genauer jeden einzelnen Parameter ansehen. Der folgende Code durchläuft jeden Parameter in der Funktion und gibt einige wichtige Details darüber aus.
for name, param in sig.parameters.items():
print(f"Parameter: {name}")
print(f" Kind: {param.kind}")
print(f" Default: {param.default if param.default is not param.empty else 'No default'}")
Dieser Code zeigt uns Details zu jedem Parameter. Er sagt uns die Art des Parameters (ob es sich um einen Positions- oder einen Schlüsselwortparameter handelt usw.) und seinen Standardwert, falls er einen hat.
Das inspect-Modul hat viele andere nützliche Funktionen für die Funktionsintrospektion. Hier sind einige Beispiele:
inspect.getdoc(obj): Diese Funktion ruft die Dokumentationszeichenkette für ein Objekt ab. Dokumentationszeichenketten sind wie Notizen, die Programmierer schreiben, um zu erklären, was ein Objekt tut.inspect.getfile(obj): Sie hilft uns herauszufinden, in welcher Datei ein Objekt definiert ist. Dies kann sehr nützlich sein, wenn wir den Quellcode eines Objekts lokalisieren möchten.inspect.getsource(obj): Diese Funktion holt den Quellcode eines Objekts. Sie ermöglicht es uns, genau zu sehen, wie das Objekt implementiert ist.
Anwendung der Funktionsuntersuchung in Klassen
Jetzt werden wir das, was wir über die Funktionsuntersuchung gelernt haben, nutzen, um die Implementierung einer Klasse zu verbessern. Die Funktionsuntersuchung ermöglicht es uns, in Funktionen hineinzuschauen und ihre Struktur zu verstehen, wie z.B. die Parameter, die sie akzeptieren. In diesem Fall werden wir es nutzen, um unseren Klassen-Code effizienter und fehlerunanfälliger zu gestalten. Wir werden eine Structure-Klasse modifizieren, damit sie automatisch die Feldnamen aus der Signatur der __init__-Methode erkennen kann.
Verständnis der Structure-Klasse
Die Datei structure.py enthält eine Structure-Klasse. Diese Klasse fungiert als Basisklasse, was bedeutet, dass andere Klassen von ihr erben können, um strukturierte Datenobjekte zu erstellen. Derzeit müssen wir, um die Attribute der Objekte zu definieren, die von Klassen erstellt werden, die von Structure erben, eine Klassenvariable _fields festlegen.
Öffnen wir die Datei im Editor. Wir verwenden den folgenden Befehl, um in das Projektverzeichnis zu navigieren:
cd ~/project
Nachdem Sie diesen Befehl ausgeführt haben, können Sie die vorhandene Structure-Klasse in der Datei structure.py im WebIDE finden und anzeigen.
Erstellung einer Stock-Klasse
Erstellen wir eine Stock-Klasse, die von der Structure-Klasse erbt. Vererbung bedeutet, dass die Stock-Klasse alle Funktionen der Structure-Klasse erhält und auch ihre eigenen hinzufügen kann. Fügen wir den folgenden Code ans Ende der Datei structure.py hinzu:
class Stock(Structure):
_fields = ('name', 'shares', 'price')
def __init__(self, name, shares, price):
self._init()
Es gibt jedoch ein Problem mit diesem Ansatz. Wir müssen sowohl das _fields-Tupel als auch die __init__-Methode mit denselben Parameternamen definieren. Dies ist redundant, da wir im Wesentlichen dieselben Informationen zweimal schreiben. Wenn wir vergessen, eine der Definitionen zu aktualisieren, wenn wir die andere ändern, kann dies zu Fehlern führen.
Hinzufügen einer set_fields-Klassenmethode
Um dieses Problem zu beheben, fügen wir der Structure-Klasse eine set_fields-Klassenmethode hinzu. Diese Methode wird automatisch die Feldnamen aus der Signatur der __init__-Methode erkennen. Hier ist der Code, den wir zur Structure-Klasse hinzufügen müssen:
@classmethod
def set_fields(cls):
## Get the signature of the __init__ method
import inspect
sig = inspect.signature(cls.__init__)
## Get parameter names, skipping 'self'
params = list(sig.parameters.keys())[1:]
## Set _fields attribute on the class
cls._fields = tuple(params)
Diese Methode verwendet das inspect-Modul, das ein leistungsstarkes Werkzeug in Python ist, um Informationen über Objekte wie Funktionen und Klassen zu erhalten. Zuerst erhält sie die Signatur der __init__-Methode. Dann extrahiert sie die Parameternamen, wobei sie den self-Parameter überspringt, da self ein spezieller Parameter in Python-Klassen ist, der auf die Instanz selbst verweist. Schließlich legt sie die Klassenvariable _fields mit diesen Parameternamen fest.
Modifikation der Stock-Klasse
Jetzt, da wir die set_fields-Methode haben, können wir unsere Stock-Klasse vereinfachen. Ersetzen Sie den vorherigen Code der Stock-Klasse durch den folgenden:
class Stock(Structure):
def __init__(self, name, shares, price):
self._init()
## Call set_fields to automatically set _fields from __init__
Stock.set_fields()
Auf diese Weise müssen wir das _fields-Tupel nicht manuell definieren. Die set_fields-Methode kümmert sich darum.
Testen der modifizierten Klasse
Um sicherzustellen, dass unsere modifizierte Klasse korrekt funktioniert, erstellen wir ein einfaches Testskript. Erstellen Sie eine neue Datei namens test_structure.py und fügen Sie den folgenden Code hinzu:
from structure import Stock
def test_stock():
## Create a Stock object
s = Stock(name='GOOG', shares=100, price=490.1)
## Test string representation
print(f"Stock representation: {s}")
## Test attribute access
print(f"Name: {s.name}")
print(f"Shares: {s.shares}")
print(f"Price: {s.price}")
## Test attribute modification
s.shares = 50
print(f"Updated shares: {s.shares}")
## Test attribute error
try:
s.share = 50 ## Misspelled attribute
print("Error: Did not raise AttributeError")
except AttributeError as e:
print(f"Correctly raised: {e}")
if __name__ == "__main__":
test_stock()
Dieses Testskript erstellt ein Stock-Objekt, testet seine String-Darstellung, greift auf seine Attribute zu, modifiziert ein Attribut und versucht, auf ein falsch geschriebenes Attribut zuzugreifen, um zu überprüfen, ob es den richtigen Fehler auslöst.
Um das Testskript auszuführen, verwenden Sie den folgenden Befehl:
python3 test_structure.py
Sie sollten eine Ausgabe ähnlich der folgenden sehen:
Stock representation: Stock('GOOG',100,490.1)
Name: GOOG
Shares: 100
Price: 490.1
Updated shares: 50
Correctly raised: No attribute share
Wie es funktioniert
- Die
set_fields-Methode verwendetinspect.signature(), um die Parameternamen aus der__init__-Methode zu erhalten. Diese Funktion gibt uns detaillierte Informationen über die Parameter der__init__-Methode. - Sie legt dann automatisch die Klassenvariable
_fieldsbasierend auf diesen Parameternamen fest. So müssen wir die gleichen Parameternamen nicht an zwei verschiedenen Stellen schreiben. - Dies beseitigt die Notwendigkeit, sowohl
_fieldsals auch__init__mit übereinstimmenden Parameternamen manuell zu definieren. Es macht unseren Code wartbarer, denn wenn wir die Parameter in der__init__-Methode ändern, werden die_fieldsautomatisch aktualisiert.
Dieser Ansatz nutzt die Funktionsuntersuchung, um unseren Code wartbarer und fehlerunanfälliger zu machen. Es ist eine praktische Anwendung der Introspektionsfähigkeiten von Python, die es uns ermöglichen, Objekte zur Laufzeit zu untersuchen und zu modifizieren.
Zusammenfassung
In diesem Lab haben Sie gelernt, wie Sie die internen Strukturen von Python-Funktionen untersuchen können. Sie haben die Attribute von Funktionen direkt mithilfe von Methoden wie dir() untersucht und auf spezielle Attribute wie __name__, __doc__ und __code__ zugegriffen. Sie haben auch das inspect-Modul verwendet, um strukturierte Informationen über Funktionssignaturen und Parameter zu erhalten.
Die Funktionsuntersuchung ist eine leistungsstarke Python-Funktion, die es Ihnen ermöglicht, dynamischeren, flexibleren und wartbareren Code zu schreiben. Die Fähigkeit, Funktionsattribute zur Laufzeit zu untersuchen und zu manipulieren, eröffnet Möglichkeiten für Metaprogrammierung, die Erstellung von selbst-dokumentierendem Code und das Aufbauen fortschrittlicher Frameworks.