Hallo, Bewohner! Python ist eine dynamische Programmiersprache, die in einer Vielzahl von Themenbereichen verwendet wird. Während es einfach ist, Code in Python zu schreiben, ist es viel schwieriger, den Code lesbar, wiederverwendbar und einfach zu warten. Dritte Ausgabe von Python. Best Practices and Tools “bieten Ihnen die Tools, mit denen Sie Probleme bei der Softwareentwicklung und -wartung effektiv lösen können. Die Autoren sprechen zunächst über die neuen Funktionen in Python 3.7 und die erweiterten Aspekte der Python-Syntax. Sie beraten weiterhin zur Umsetzung populärer Paradigmen, einschließlich objektorientierter, funktionaler und ereignisgesteuerter Programmierung. Die Autoren sprechen auch über die besten Benennungsmethoden und darüber, wie Sie die Bereitstellung von Programmen auf Remoteservern automatisieren können. Du wirst es lernen,Erstellen nützlicher Python-Erweiterungen in C, C ++, Cython und CFFI.
Für wen ist dieses Buch?
Python, . , Python. , , , Python.
, . , Python. , , . Python 3.7 , Python 2.7 .
- -, , : .
, . , Python. , , . Python 3.7 , Python 2.7 .
- -, , : .
Zugriffsmuster für erweiterte Attribute
Beim Erlernen von Python sind viele C ++ - und Java-Programmierer überrascht, dass das private Schlüsselwort fehlt. Das nächstgelegene Konzept ist das Mangeln von Namen. Jedes Mal, wenn einem Attribut __ vorangestellt wird, wird es vom Interpreter dynamisch umbenannt:
class MyClass:
__secret_value = 1
Wenn Sie über den ursprünglichen Namen auf das Attribut __secret_value zugreifen, wird eine AttributeError-Ausnahme ausgelöst:
>>> instance_of = MyClass()
>>> instance_of.__secret_value
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyClass' object has no attribute '__secret_value'
>>> dir(MyClass)
['_MyClass__secret_value', '__class__', '__delattr__', '__dict__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
>>> instance_of._MyClass__secret_value
1
Dies geschieht speziell, um Namenskonflikte durch Vererbung zu vermeiden, da das Attribut durch den Klassennamen als Präfix umbenannt wird. Dies ist kein genaues Analogon zu privat, da auf das Attribut über einen verketteten Namen zugegriffen werden kann. Diese Eigenschaft kann verwendet werden, um den Zugriff auf einige Attribute zu schützen. In der Praxis wird __ jedoch nie verwendet. Wenn das Attribut nicht öffentlich ist, wird üblicherweise das Präfix _ verwendet. Es ruft nicht den Namensdekorationsalgorithmus auf, sondern dokumentiert das Attribut als privates Element der Klasse und ist der vorherrschende Stil.
Python verfügt über andere Mechanismen, um den öffentlichen vom privaten Teil einer Klasse zu trennen. Deskriptoren und Eigenschaften bieten eine Möglichkeit, diese Trennung aufzuräumen.
Deskriptoren
Mit dem Deskriptor können Sie die Aktion anpassen, die ausgeführt wird, wenn Sie auf ein Attribut für ein Objekt verweisen.
Deskriptoren sind das Herzstück des komplexen Attributzugriffs in Python. Sie werden verwendet, um Eigenschaften, Methoden, Klassenmethoden, statische Methoden und Supertypen zu implementieren. Dies sind die Klassen, die definieren, wie auf die Attribute einer anderen Klasse zugegriffen wird. Mit anderen Worten, eine Klasse kann die Kontrolle über ein Attribut an eine andere Klasse delegieren.
Deskriptorklassen basieren auf drei speziellen Methoden, die das Deskriptorprotokoll bilden:
__set __ (self, obj, value) - Wird aufgerufen, wenn ein Attribut festgelegt wird. In den folgenden Beispielen werden wir es als "Setter" bezeichnen;
__get __ (self, obj, owner = None) - wird aufgerufen, wenn das Attribut gelesen wird (im Folgenden der Getter);
__delete __ (self, object) - Wird aufgerufen, wenn del von einem Attribut aufgerufen wird.
Ein Deskriptor, der __get__ und __set__ implementiert, wird als Datendeskriptor bezeichnet. Wenn nur __get__ implementiert wird, wird es als No-Data-Deskriptor bezeichnet.
Die Methoden dieses Protokolls werden bei jeder Suche nach einem Attribut von der Methode __getattribute __ () aufgerufen (nicht zu verwechseln mit __getattr __ (), die einen anderen Zweck hat). Immer wenn eine solche Suche mit einem Punkt oder einem direkten Funktionsaufruf durchgeführt wird, wird implizit die Methode __getattribute __ () aufgerufen, die das Attribut in der folgenden Reihenfolge sucht.
- Überprüft, ob ein Attribut ein Datendeskriptor für ein Objekt der Instanzklasse ist.
- Wenn nicht, wird geprüft, ob das Attribut im __dict__ des Instanzobjekts gefunden wird.
- Überprüft schließlich, ob das Attribut ein Handle ohne Daten für das Instanzklassenobjekt ist.
Mit anderen Worten, Datendeskriptoren haben Vorrang vor __dict__, was wiederum Vorrang vor Nicht-Datendeskriptoren hat.
Zur Verdeutlichung hier ein Beispiel aus der offiziellen Python-Dokumentation, das zeigt, wie Deskriptoren in echtem Code funktionieren:
class RevealAccess(object):
""" ,
"""
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
print('Retrieving', self.name)
return self.val
def __set__(self, obj, val):
print('Updating', self.name)
self.val = val
class MyClass(object):
x = RevealAccess(10, 'var "x"')
y = 5
Hier ist ein Beispiel für die interaktive Verwendung:
>>> m = MyClass()
>>> m.x
Retrieving var "x"
10
>>> m.x = 20
Updating var "x"
>>> m.x
Retrieving var "x"
20
>>> m.y
5
Das Beispiel zeigt deutlich, dass, wenn die Klasse einen Datendeskriptor für dieses Attribut hat, __get __ () aufgerufen wird, um jedes Mal einen Wert zurückzugeben, wenn ein Instanzattribut abgerufen wird, und __set __ () aufgerufen wird, wenn diesem Attribut ein Wert zugewiesen wird. Die Verwendung der Methode __del__ wird im vorherigen Beispiel nicht gezeigt, sollte jedoch offensichtlich sein: Sie wird immer dann aufgerufen, wenn ein Instanzattribut mit der Anweisung del instance.attribute oder delattr (instance, 'attribute') entfernt wird.
Der Unterschied zwischen Daten- und Nicht-Daten-Deskriptoren ist aus den am Anfang dieses Unterabschnitts genannten Gründen wichtig. Python verwendet das Deskriptorprotokoll, um Klassenfunktionen über Methoden an Instanzen zu binden. Sie gelten auch für die Dekoratoren classmethod und staticmethod. Dies liegt daran, dass funktionale Objekte im Wesentlichen auch datenlose Deskriptoren sind:
>>> def function(): pass
>>> hasattr(function, '__get__')
True
>>> hasattr(function, '__set__')
False
Gleiches gilt für Funktionen, die mit Lambda-Ausdrücken erstellt wurden:
>>> hasattr(lambda: None, '__get__')
True
>>> hasattr(lambda: None, '__set__')
False
Solange __dict__ nicht Vorrang vor datenlosen Deskriptoren hat, können wir bestimmte Methoden bereits instanziierter Instanzen zur Laufzeit nicht dynamisch überschreiben. Glücklicherweise ist dies dank der Funktionsweise von Deskriptoren in Python möglich. Daher können Entwickler auswählen, welche Instanzen was funktionieren, ohne Unterklassen zu verwenden.
Beispiel aus dem wirklichen Leben: träge Bewertung von Attributen. Ein Beispiel für die Verwendung von Deskriptoren besteht darin, die Initialisierung eines Klassenattributs zu verzögern, wenn von einer Instanz aus darauf zugegriffen wird. Dies kann nützlich sein, wenn die Initialisierung solcher Attribute vom globalen Anwendungskontext abhängt. Ein anderer Fall ist, wenn eine solche Initialisierung zu teuer ist und nicht bekannt ist, ob das Attribut nach dem Importieren der Klasse überhaupt verwendet wird. Ein solcher Deskriptor kann wie folgt implementiert werden:
class InitOnAccess:
def __init__(self, klass, *args, **kwargs):
self.klass = klass
self.args = args
self.kwargs = kwargs
self._initialized = None
def __get__(self, instance, owner):
if self._initialized is None:
print('initialized!')
self._initialized = self.klass(*self.args, **self.kwargs)
else:
print('cached!')
return self._initialized
Unten finden Sie ein Anwendungsbeispiel:
>>> class MyClass:
... lazily_initialized = InitOnAccess(list, "argument")
...
>>> m = MyClass()
>>> m.lazily_initialized
initialized!
['a', 'r', 'g', 'u', 'm', 'e', 'n', 't']
>>> m.lazily_initialized
cached!
['a', 'r', 'g', 'u', 'm', 'e', 'n', 't']
Die offizielle PyPI OpenGL Python-Bibliothek namens PyOpenGL verwendet eine solche Technik, um ein Lazy_Property-Objekt zu implementieren, das sowohl ein Dekorator als auch ein Datendeskriptor ist:
class lazy_property(object):
def __init__(self, function):
self.fget = function
def __get__(self, obj, cls):
value = self.fget(obj)
setattr(obj, self.fget.__name__, value)
return value
Diese Implementierung ähnelt der Verwendung des Eigenschaftsdekorators (wir werden später darauf eingehen), aber die im Dekorator eingeschlossene Funktion wird nur einmal ausgeführt, und dann wird das Klassenattribut durch den von dieser Funktionseigenschaft zurückgegebenen Wert ersetzt. Diese Methode ist häufig nützlich, wenn Sie gleichzeitig zwei Anforderungen erfüllen müssen:
- Eine Objektinstanz muss als Klassenattribut gespeichert werden, das von ihren Instanzen gemeinsam genutzt wird (um Ressourcen zu sparen).
- Dieses Objekt kann zum Zeitpunkt des Imports nicht initialisiert werden, da der Erstellungsprozess von einem globalen Status der Anwendung / des Kontexts abhängt.
Bei Anwendungen, die mit OpenGL geschrieben wurden, tritt diese Situation häufig auf. Das Erstellen von Shadern in OpenGL ist beispielsweise teuer, da Code kompiliert werden muss, der in der OpenGL Shading Language (GLSL) geschrieben ist. Es ist sinnvoll, sie nur einmal zu erstellen und gleichzeitig ihre Beschreibung in unmittelbarer Nähe zu den Klassen zu halten, die sie benötigen. Auf der anderen Seite können Shader-Kompilierungen nicht durchgeführt werden, ohne den OpenGL-Kontext zu initialisieren. Daher ist es schwierig, sie zum Zeitpunkt des Imports im globalen Modul-Namespace zu definieren und zusammenzusetzen.
Das folgende Beispiel zeigt eine mögliche Verwendung einer modifizierten Version des PyOpenGL-Dekorators lazy_property (hier lazy_class_attribute) in einer abstrakten OpenGL-Anwendung. Änderungen am ursprünglichen lazy_property-Dekorator sind erforderlich, damit das Attribut für verschiedene Instanzen der Klasse freigegeben werden kann:
import OpenGL.GL as gl
from OpenGL.GL import shaders
class lazy_class_attribute(object):
def __init__(self, function):
self.fget = function
def __get__(self, obj, cls):
value = self.fget(obj or cls)
# : -
#
setattr(cls, self.fget.__name__, value)
return value
class ObjectUsingShaderProgram(object):
# -
VERTEX_CODE = """
#version 330 core
layout(location = 0) in vec4 vertexPosition;
void main(){
gl_Position = vertexPosition;
}
"""
# ,
FRAGMENT_CODE = """
#version 330 core
out lowp vec4 out_color;
void main(){
out_color = vec4(1, 1, 1, 1);
}
"""
@lazy_class_attribute
def shader_program(self):
print("compiling!")
return shaders.compileProgram(
shaders.compileShader(
self.VERTEX_CODE, gl.GL_VERTEX_SHADER
),
shaders.compileShader(
self.FRAGMENT_CODE, gl.GL_FRAGMENT_SHADER
)
)
Wie alle erweiterten Python-Syntaxfunktionen sollte auch diese mit Sorgfalt verwendet und im Code gut dokumentiert werden. Für unerfahrene Entwickler kann das geänderte Verhalten einer Klasse eine Überraschung sein, da die Deskriptoren das Verhalten der Klasse beeinflussen. Daher ist es sehr wichtig sicherzustellen, dass alle Mitglieder Ihres Teams mit Deskriptoren vertraut sind und dieses Konzept verstehen, wenn es eine wichtige Rolle in der Projektcodebasis spielt.
Eigenschaften
Eigenschaften bieten einen integrierten Deskriptortyp, der weiß, wie ein Attribut einer Reihe von Methoden zugeordnet wird. Die Eigenschaft akzeptiert vier optionale Argumente: fget, fset, fdel und doc. Letzteres kann bereitgestellt werden, um die dem Attribut zugeordnete Dokumentzeichenfolge so zu definieren, als wäre es eine Methode. Unten finden Sie ein Beispiel für eine Rechteckklasse, die entweder durch direkten Zugriff auf die Attribute mit zwei Eckpunkten oder mithilfe der Eigenschaften width und height bearbeitet werden kann:
class Rectangle:
def __init__(self, x1, y1, x2, y2):
self.x1, self.y1 = x1, y1
self.x2, self.y2 = x2, y2
def _width_get(self):
return self.x2 - self.x1
def _width_set(self, value):
self.x2 = self.x1 + value
def _height_get(self):
return self.y2 - self.y1
def _height_set(self, value):
self.y2 = self.y1 + value
width = property(
_width_get, _width_set,
doc="rectangle width measured from left"
)
height = property(
_height_get, _height_set,
doc="rectangle height measured from top"
)
def __repr__(self):
return "{}({}, {}, {}, {})".format(
self.__class__.__name__,
self.x1, self.y1, self.x2, self.y2
)
Das folgende Codefragment enthält ein Beispiel für solche Eigenschaften, die in einer interaktiven Sitzung definiert wurden:
>>> rectangle.width, rectangle.height
(15, 24)
>>> rectangle.width = 100
>>> rectangle
Rectangle(10, 10, 110, 34)
>>> rectangle.height = 100
>>> rectangle
Rectangle(10, 10, 110, 110)
>>> help(Rectangle)
Help on class Rectangle in module chapter3:
class Rectangle(builtins.object)
| Methods defined here:
|
| __init__(self, x1, y1, x2, y2)
| Initialize self. See help(type(self)) for accurate signature.
|
| __repr__(self)
| Return repr(self).
|
| --------------------------------------------------------
| Data descriptors defined here:
| (...)
|
| height
| rectangle height measured from top
|
| width
| rectangle width measured from left
Diese Eigenschaften erleichtern das Schreiben von Deskriptoren, sollten jedoch bei der Verwendung der Klassenvererbung mit Vorsicht behandelt werden. Das Attribut wird dynamisch mit den Methoden der aktuellen Klasse erstellt und wendet keine Methoden an, die in abgeleiteten Klassen überschrieben werden.
Der Code im folgenden Beispiel kann die Implementierung der fget-Methode aus der width-Eigenschaft der übergeordneten Klasse (Rectangle) nicht überschreiben:
>>> class MetricRectangle(Rectangle):
... def _width_get(self):
... return "{} meters".format(self.x2 - self.x1)
...
>>> Rectangle(0, 0, 100, 100).width
100
Um dieses Problem zu lösen, sollte die gesamte Eigenschaft in der abgeleiteten Klasse überschrieben werden:
>>> class MetricRectangle(Rectangle):
... def _width_get(self):
... return "{} meters".format(self.x2 - self.x1)
... width = property(_width_get, Rectangle.width.fset)
...
>>> MetricRectangle(0, 0, 100, 100).width
'100 meters'
Leider weist der Code einige Probleme mit der Wartbarkeit auf. Verwirrung kann entstehen, wenn ein Entwickler beschließt, die übergeordnete Klasse zu ändern, aber vergisst, den Eigenschaftsaufruf zu aktualisieren. Aus diesem Grund wird nicht empfohlen, nur Teile des Verhaltens von Eigenschaften zu überschreiben. Anstatt sich auf die Implementierung der übergeordneten Klasse zu verlassen, empfiehlt es sich, alle Eigenschaftsmethoden in abgeleiteten Klassen neu zu schreiben, wenn Sie ihre Funktionsweise ändern möchten. Es gibt normalerweise keine anderen Optionen, da das Ändern der Eigenschaften des Setter-Verhaltens eine Änderung des Getter-Verhaltens zur Folge hat.
Der beste Weg, um Eigenschaften zu erstellen, besteht darin, die Eigenschaft als Dekorateur zu verwenden. Dadurch wird die Anzahl der Methodensignaturen innerhalb der Klasse verringert und der Code lesbarer und wartbarer:
class Rectangle:
def __init__(self, x1, y1, x2, y2):
self.x1, self.y1 = x1, y1
self.x2, self.y2 = x2, y2
@property
def width(self):
""" """
return self.x2 - self.x1
@width.setter
def width(self, value):
self.x2 = self.x1 + value
@property
def height(self):
""" """
return self.y2 - self.y1
@height.setter
def height(self, value):
self.y2 = self.y1 + value
Slots
Eine interessante Funktion, die Entwickler selten verwenden, sind Slots. Mit ihnen können Sie mithilfe des Attributs __slots__ eine statische Liste von Attributen für eine Klasse festlegen und das Erstellen eines __dict__ -Wörterbuchs in jeder Instanz der Klasse überspringen. Sie wurden erstellt, um Speicherplatz für Klassen mit wenigen Attributen zu sparen, da __dict__ nicht in jeder Instanz erstellt wird.
Sie können auch beim Erstellen von Klassen helfen, deren Signaturen eingefroren werden müssen. Wenn Sie beispielsweise die dynamischen Eigenschaften einer Sprache für eine bestimmte Klasse einschränken müssen, können Slots hilfreich sein:
>>> class Frozen:
... __slots__ = ['ice', 'cream']
...
>>> '__dict__' in dir(Frozen)
False
>>> 'ice' in dir(Frozen)
True
>>> frozen = Frozen()
>>> frozen.ice = True
>>> frozen.cream = None
>>> frozen.icy = True
Traceback (most recent call last): File "<input>", line 1, in <module>
AttributeError: 'Frozen' object has no attribute 'icy'
Diese Funktion muss mit Vorsicht verwendet werden. Wenn der Satz verfügbarer Attribute auf Slots beschränkt ist, ist es viel schwieriger, einem Objekt dynamisch etwas hinzuzufügen. Einige bekannte Tricks, wie das Patchen von Affen, funktionieren nicht mit Instanzen von Klassen, die bestimmte Slots haben. Glücklicherweise können abgeleiteten Klassen neue Attribute hinzugefügt werden, wenn sie keine eigenen definierten Slots haben:
>>> class Unfrozen(Frozen):
... pass
...
>>> unfrozen = Unfrozen()
>>> unfrozen.icy = False
>>> unfrozen.icy
False
Über die Autoren
Michal Jaworski ist ein Python-Programmierer mit zehnjähriger Erfahrung. Er hatte verschiedene Positionen in verschiedenen Unternehmen inne: von einem regulären Full-Stack-Entwickler über einen Softwarearchitekten bis hin zu einem Vice President of Development in einem dynamischen Startup-Unternehmen. Michal ist derzeit Senior Backend Engineer bei Showpad. Verfügt über umfangreiche Erfahrung in der Entwicklung von verteilten Hochleistungsdiensten. Darüber hinaus leistet er einen aktiven Beitrag zu vielen Open-Source-Python-Projekten.
Tarek Ziade ist ein Python-Entwickler. Lebt auf dem Land in der Nähe von Dijon, Frankreich. Arbeitet bei Mozilla im Serviceteam. Tarek gründete die französische Python-Benutzergruppe (Afpy genannt) und hat mehrere Bücher über Python auf Französisch und Englisch geschrieben. In seiner Freizeit vom Hacken und Feiern beschäftigt er sich mit seinen Lieblingshobbys: Joggen oder Trompete spielen.
Sie können seinen persönlichen Blog (Fetchez le Python) besuchen und ihm auf Twitter (tarek_ziade) folgen.
Über den wissenschaftlichen Herausgeber
Cody Jackson ist Ph.D., Gründer von Socius Consulting, einem in San Antonio ansässigen Beratungsunternehmen für IT und Unternehmensführung, und Mitbegründer von Top Men Technologies. Derzeit arbeitet er für CACI International als Lead Engineer für ICS / SCADA Modeling. In der IT-Branche seit 1994, seit seiner Zeit bei der Marine als Nuklearchemiker und Funkingenieur. Vor CACI arbeitete er an der Universität des ECPI als Assistenzprofessor für Computerinformationssysteme. Ich habe die Python-Programmierung selbst gelernt und die Bücher Programmieren mit Python und geheimen Rezepten des Python-Ninja geschrieben.
Weitere Details zum Buch finden Sie auf der Website des Verlags
" Inhaltsverzeichnis
" Auszug
für Einwohner 25% Rabatt auf den Gutschein - Python
Nach Zahlungseingang für die Papierversion des Buches wird ein E-Book an die E-Mail gesendet.