Hallo Habr. Vor kurzem war ich verrückt nach Design - Zugriffsmodifikatoren und Schnittstellen, dann habe ich es in die Programmiersprache Python portiert. Ich frage unter kat - ich teile die Ergebnisse und wie es funktioniert. Für Interessierte gibt es am Ende des Artikels einen Link zum Projekt auf Github.
Zugriffsmodifikatoren
Zugriffsmodifikatoren beschränken den Zugriff auf Objekte - auf Methoden ihrer Klasse oder auf untergeordnete Klassen - auf Methoden ihrer übergeordneten Klasse. Durch die Verwendung von Zugriffsmodifikatoren können Daten in der Klasse ausgeblendet werden, sodass niemand außerhalb die Arbeit dieser Klasse stören kann.
Private Methoden sind nur innerhalb der Klasse verfügbar,
geschützt (innerhalb) - innerhalb der Klasse und in untergeordneten Klassen.
Wie private und geschützte Methoden in Python implementiert werden
Spoiler - auf der Ebene der Übereinstimmung, dass Erwachsene sie einfach nicht außerhalb des Klassenzimmers anrufen. Vor privaten Methoden müssen Sie einen doppelten Unterstrich schreiben, bevor Sie einen geschützten. Und Sie können trotz ihres "eingeschränkten" Zugriffs weiterhin auf die Methoden zugreifen.
class Car: def _start_engine(self): return "Engine's sound." def run(self): return self._start_engine() if __name__ == '__main__': car = Car() assert "Engine's sound." == car.run() assert "Engine's sound." == car._start_engine()
Folgende Nachteile können festgestellt werden:
- Wenn die Methode _start_engine einige Klassenvariablen aktualisiert oder den Status beibehalten und nicht nur eine „dumme Berechnung“ zurückgegeben hat, könnten Sie etwas für die zukünftige Arbeit mit der Klasse beschädigt haben. Sie erlauben sich nicht, etwas im Motor Ihres Autos zu reparieren, weil Sie dann nirgendwo hingehen, oder?
- Der Fließpunkt des vorherigen - um sicherzustellen, dass Sie "sicher" (das Aufrufen der Methode schadet der Klasse selbst nicht) eine geschützte Methode verwenden können - müssen Sie den Code untersuchen und Zeit verbringen.
- Die Autoren der Bibliotheken hoffen, dass niemand die geschützten und privaten Methoden der Klassen verwendet, die Sie in Ihren Projekten verwenden. Daher können sie die Implementierung in jeder Version ändern (was sich aufgrund der Abwärtskompatibilität nicht auf öffentliche Methoden auswirkt, aber Sie werden darunter leiden).
- Der Autor der Klasse, Ihr Kollege, erwartet, dass Sie die technische Verschuldung des Projekts nicht durch eine geschützte oder private Methode außerhalb der von ihm erstellten Klasse erhöhen. Schließlich muss derjenige, der es umgestaltet oder modifiziert (private Klassenmethode), sicherstellen (z. B. durch Tests), dass seine Änderungen Ihren Code nicht beschädigen. Und wenn sie es brechen, muss er Zeit damit verbringen, dieses Problem zu lösen (mit einer Krücke, weil er es gestern braucht).
- Vielleicht stellen Sie sicher, dass andere Programmierer keine geschützten oder privaten Methoden für die Codeüberprüfung verwenden und „dafür schlagen“, also verbringen Sie Zeit.
So implementieren Sie geschützte Methoden mithilfe einer Bibliothek
from accessify import protected class Car: @protected def start_engine(self): return "Engine's sound." def run(self): return self.start_engine() if __name__ == '__main__': car = Car() assert "Engine's sound." == car.run() car.start_engine()
Der Versuch, die Methode
start_engine außerhalb der Klasse
aufzurufen , führt zu folgendem Fehler (die Methode ist gemäß der Zugriffsrichtlinie nicht verfügbar):
Traceback (most recent call last): File "examples/access/private.py", line 24, in <module> car.start_engine() File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/main.py", line 92, in private_wrapper class_name=instance_class.__name__, method_name=method.__name__, accessify.errors.InaccessibleDueToItsProtectionLevelException: Car.start_engine() is inaccessible due to its protection level
Verwenden der Bibliothek:
- Sie müssen keine hässlichen (subjektiven) Unterstriche oder doppelten Unterstriche verwenden.
- Sie erhalten eine schöne (subjektive) Methode zum Implementieren von Zugriffsmodifikatoren im Code - private und geschützte Dekorateure.
- Übertragen Sie die Verantwortung von der Person auf den Dolmetscher.
Wie es funktioniert:
- Der private oder geschützte Dekorator - der "höchste" Dekorator - wird vor der Klassenmethode ausgelöst , die als privater oder geschützter Zugriffsmodifikator deklariert wurde.
- Mithilfe der integrierten Inspect- Bibliothek ruft der Dekorateur das aktuelle Objekt aus dem Aufrufstapel ab - inspect.currentframe () . Dieses Objekt verfügt über die folgenden Attribute, die für uns nützlich sind: den Namespace (lokale) und den Link zum vorherigen Objekt aus dem Aufrufstapel (das Objekt, das die Methode mit dem Zugriffsmodifikator aufruft).
(Sehr vereinfachte Darstellung)
- inspect.currentframe (). f_back - Verwenden Sie dieses Attribut, um zu überprüfen, ob sich das vorherige Objekt aus dem Aufrufstapel im Klassenkörper befindet oder nicht. Schauen Sie sich dazu den Namespace - f_locals an . Wenn der Namespace ein self- Attribut enthält, wird die Methode innerhalb der Klasse aufgerufen, wenn nicht, außerhalb der Klasse. Wenn Sie eine Methode mit einem privaten oder geschützten Zugriffsmodifikator außerhalb der Klasse aufrufen, tritt ein Zugriffsrichtlinienfehler auf.
Schnittstellen
Schnittstellen sind ein Vertrag über die Interaktion mit einer Klasse, die sie implementiert. Die Schnittstelle enthält die Methodensignaturen (den Namen der Funktionen, die Eingabeargumente) und die Klasse, die die Schnittstelle implementiert, implementiert nach den Signaturen die Logik. Zusammenfassend können Sie sicher sein, dass beide Objekte dieser Klassen dieselben Methoden haben, wenn zwei Klassen dieselbe Schnittstelle implementieren.
Beispiel
Wir haben eine
Benutzerklasse , die das
Speicherobjekt verwendet , um einen neuen Benutzer zu erstellen.
class User: def __init__(self, storage): self.storage = storage def create(self, name): return storage.create_with_name(name=name)
Sie können den Benutzer mit
DatabaseStorage.create_with_name in der Datenbank speichern.
class DatabaseStorage: def create_with_name(self, name): ...
Sie können den Benutzer mit
FileStorage.create_with_name in Dateien
speichern .
class FileStorage: def create_with_name(self, name): ...
Aufgrund der Tatsache, dass die Signaturen der Methoden
create_with_name (Name, Argumente) für die Klassen gleich sind, muss sich die
Benutzerklasse keine Gedanken darüber machen, für welches Objekt sie ersetzt wurde, wenn beide dieselben Methoden haben. Dies kann erreicht werden, wenn die Klassen
FileStorage und
DatabaseStorage dieselbe Schnittstelle implementieren (
dh sie sind an den Vertrag gebunden, um eine Methode mit Logik darin zu definieren).
if __name__ == '__main__': if settings.config.storage = FileStorage: storage = FileStorage() if settings.config.storage = DatabaseStorage: storage = DatabaseStorage() user = User(storage=storage) user.create_with_name(name=...)
So arbeiten Sie mit Schnittstellen mithilfe der Bibliothek
Wenn eine Klasse eine Schnittstelle implementiert, muss die Klasse
alle Methoden der Schnittstelle enthalten . Im folgenden Beispiel enthält die HumanInterface-Schnittstelle die eat-Methode, und die Human-Klasse implementiert sie, implementiert jedoch nicht die eat-Methode.
from accessify import implements class HumanInterface: @staticmethod def eat(food, *args, allergy=None, **kwargs): pass if __name__ == '__main__': @implements(HumanInterface) class Human: pass
Das Skript wird mit folgendem Fehler beendet:
Traceback (most recent call last): File "examples/interfaces/single.py", line 18, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 66, in decorator interface_method_arguments=interface_method.arguments_as_string, accessify.errors.InterfaceMemberHasNotBeenImplementedException: class Human does not implement interface member HumanInterface.eat(food, args, allergy, kwargs)
Wenn eine Klasse eine Schnittstelle implementiert, muss die Klasse alle Methoden der Schnittstelle enthalten,
einschließlich aller eingehenden Argumente . Im folgenden Beispiel enthält die HumanInterface-Schnittstelle die eat-Methode, die 4 Argumente für die Eingabe verwendet, und die Human-Klasse implementiert sie, implementiert jedoch die eat-Methode mit nur 1 Argument.
from accessify import implements class HumanInterface: @staticmethod def eat(food, *args, allergy=None, **kwargs): pass if __name__ == '__main__': @implements(HumanInterface) class Human: @staticmethod def eat(food): pass
Das Skript wird mit folgendem Fehler beendet:
Traceback (most recent call last): File "examples/interfaces/single_arguments.py", line 16, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 87, in decorator interface_method_arguments=interface_method.arguments_as_string, accessify.errors.InterfaceMemberHasNotBeenImplementedWithMismatchedArgumentsException: class Human implements interface member HumanInterface.eat(food, args, allergy, kwargs) with mismatched arguments
Wenn eine Klasse eine Schnittstelle implementiert, muss die Klasse alle Methoden der Schnittstelle enthalten, einschließlich eingehender Argumente und
Zugriffsmodifikatoren . Im folgenden Beispiel enthält die HumanInterface-Schnittstelle die private eat-Methode, und die Human-Klasse implementiert sie, implementiert jedoch nicht den privaten Zugriffsmodifikator für die eat-Methode.
from accessify import implements, private class HumanInterface: @private @staticmethod def eat(food, *args, allergy=None, **kwargs): pass if __name__ == '__main__': @implements(HumanInterface) class Human: @staticmethod def eat(food, *args, allergy=None, **kwargs): pass
Das Skript wird mit folgendem Fehler beendet:
Traceback (most recent call last): File "examples/interfaces/single_access.py", line 18, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 77, in decorator interface_method_name=interface_method.name, accessify.errors.ImplementedInterfaceMemberHasIncorrectAccessModifierException: Human.eat(food, args, allergy, kwargs) mismatches HumanInterface.eat() member access modifier.
Eine Klasse kann mehrere (unbegrenzt viele) Schnittstellen implementieren. Wenn eine Klasse mehrere Schnittstellen implementiert, muss die Klasse
alle Methoden aller Schnittstellen enthalten, einschließlich eingehender Argumente und Zugriffsmodifikatoren . Im folgenden Beispiel implementiert die Human-Klasse die eat-Methode der HumanBasicsInterface-Schnittstelle, implementiert jedoch nicht die Love-Methode der HumanSoulInterface-Schnittstelle.
from accessify import implements class HumanSoulInterface: def love(self, who, *args, **kwargs): pass class HumanBasicsInterface: @staticmethod def eat(food, *args, allergy=None, **kwargs): pass if __name__ == '__main__': @implements(HumanSoulInterface, HumanBasicsInterface) class Human: def love(self, who, *args, **kwargs): pass
Das Skript wird mit folgendem Fehler beendet:
Traceback (most recent call last): File "examples/interfaces/multiple.py", line 19, in <module> @implements(HumanSoulInterface, HumanBasicsInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 66, in decorator interface_method_arguments=interface_method.arguments_as_string, accessify.errors.InterfaceMemberHasNotBeenImplementedException: class Human does not implement interface member HumanBasicsInterface.eat(food, args, allergy, kwargs)
Killer-Feature - Eine Schnittstellenmethode kann "angeben", welche Fehler eine Methode einer Klasse, die sie implementiert, "werfen" soll. Im folgenden Beispiel wird "deklariert", dass die "love" -Methode der "HumanInterface" -Schnittstelle eine "HumanDoesNotExistError" -Ausnahme und auslösen soll
"HumanAlreadyInLoveError", aber die "Liebes" -Methode der "Human" -Klasse "wirft" keinen von ihnen.
from accessify import implements, throws class HumanDoesNotExistError(Exception): pass class HumanAlreadyInLoveError(Exception): pass class HumanInterface: @throws(HumanDoesNotExistError, HumanAlreadyInLoveError) def love(self, who, *args, **kwargs): pass if __name__ == '__main__': @implements(HumanInterface) class Human: def love(self, who, *args, **kwargs): if who is None: raise HumanDoesNotExistError('Human whom need to love does not exist')
Das Skript wird mit folgendem Fehler beendet:
Traceback (most recent call last): File "examples/interfaces/throws.py", line 21, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 103, in decorator class_method_arguments=class_member.arguments_as_string, accessify.errors.DeclaredInterfaceExceptionHasNotBeenImplementedException: Declared exception HumanAlreadyInLoveError by HumanInterface.love() member has not been implemented by Human.love(self, who, args, kwargs)
Zusammenfassend lässt sich sagen, dass Sie die Bibliothek verwenden:
- Sie können eine oder mehrere Schnittstellen implementieren.
- Schnittstellen werden mit Zugriffsmodifikatoren kombiniert.
- Sie erhalten eine Trennung von Schnittstellen und abstrakten Klassen ( abc-Modul in Python ). Jetzt müssen Sie keine abstrakten Klassen mehr als Schnittstellen verwenden, wenn Sie dies getan haben (ich habe es getan).
- Im Vergleich zu abstrakten Klassen. Wenn Sie nicht alle Methodenargumente über die Schnittstelle definiert haben, wird bei Verwendung einer abstrakten Klasse eine Fehlermeldung angezeigt - Nr.
- Im Vergleich zu abstrakten Klassen. Bei Verwendung von Schnittstellen wird beim Erstellen der Klasse eine Fehlermeldung angezeigt (wenn Sie die Klasse geschrieben und die * .py- Datei aufgerufen haben). In abstrakten Klassen wird bereits beim Aufrufen einer Methode eines Klassenobjekts ein Fehler angezeigt.
Wie es funktioniert:
- Mit der integrierten Integrationsbibliothek im Implementierungsdekorator werden alle Methoden der Klasse und ihrer Schnittstellen - inspect.getmembers () - abgerufen . Ein eindeutiger Index einer Methode ist eine Kombination aus Name und Typ (statische Methode, Eigenschaft usw.).
- Und mit inspect.signature () die Argumente zur Methode.
- Wir durchlaufen alle Methoden der Schnittstelle und prüfen, ob es in der Klasse, die die Schnittstelle implementiert, eine solche Methode (anhand eines eindeutigen Index) gibt, ob die eingehenden Argumente identisch sind, ob die Zugriffsmodifikatoren identisch sind und ob die Methode die deklarierten Fehler in der Schnittstellenmethode implementiert.
Vielen Dank für Ihre Aufmerksamkeit auf den Artikel.
Link zum Projekt auf Github .