Ich bin neu in Python und benötige einige Ratschläge zur Implementierung des folgenden Szenarios.
Ich habe zwei Klassen für die Verwaltung von Domains bei zwei verschiedenen Registraren. Beide haben die gleiche Schnittstelle, z
class RegistrarA(Object):
def __init__(self, domain):
self.domain = domain
def lookup(self):
...
def register(self, info):
...
und
class RegistrarB(object):
def __init__(self, domain):
self.domain = domain
def lookup(self):
...
def register(self, info):
...
Ich möchte eine Domain-Klasse erstellen, die bei gegebenem Domain-Namen die richtige Registrar-Klasse basierend auf der Erweiterung lädt, z
com = Domain('test.com') #load RegistrarA
com.lookup()
biz = Domain('test.biz') #load RegistrarB
biz.lookup()
Ich weiß, dass dies mit einer Factory-Funktion erreicht werden kann (siehe unten), aber ist dies der beste Weg, dies zu tun, oder gibt es einen besseren Weg, OOP-Funktionen zu verwenden?
def factory(domain):
if ...:
return RegistrarA(domain)
else:
return RegistrarB(domain)
@staticmethod
könnte in diesem Zusammenhang etwas besser gewesen sein, denke ichis_registrar_for()
sich gegenseitig aus und werden dies auch in Zukunft bleiben . Die Reihenfolge der von zurückgegebenen Werte__subclasses__()
ist beliebig. Und diese Reihenfolge ist im Allgemeinen wichtig. Wenn sich etwas im Code ändert (möglicherweise so geringfügig wie die Reihenfolge der Klassendefinitionen), kann dies zu einem anderen Ergebnis führen. Die Kosten für solche Fehler, IMO, sind enorm und überwiegen bei weitem die Vorteile dieses Ansatzes. Ich würde stattdessen den Ansatz des OP verwenden, bei dem eine einzelne Funktion die gesamte Logik der Unterklassenauswahl enthält.__subclasses__
nur unmittelbare Unterklassen zurückgegeben werden. Eine mehrstufige Vererbung würde daher eine kleine Optimierung erfordern, um korrekt verarbeitet zu werden.__subclasses__
nur für lebende Objekte funktioniert. Wenn eine Klasse noch nicht importiert wurde, wird sie nicht in den Ergebnissen angezeigt (da sie nicht vorhanden ist).Angenommen, Sie benötigen separate Klassen für verschiedene Registrare (obwohl dies in Ihrem Beispiel nicht offensichtlich ist), sieht Ihre Lösung in Ordnung aus, obwohl RegistrarA und RegistrarB wahrscheinlich die Funktionalität gemeinsam nutzen und von einer abstrakten Basisklasse abgeleitet werden könnten .
Alternativ zu Ihrer
factory
Funktion können Sie ein Diktat angeben, das Ihren Registrar-Klassen zugeordnet ist:Registrar = {'test.com': RegistrarA, 'test.biz': RegistrarB}
Dann:
registrar = Registrar['test.com'](domain)
Ein Problem: Sie führen hier nicht wirklich eine Klassenfabrik durch, da Sie eher Instanzen als Klassen zurückgeben.
quelle
In Python können Sie die eigentliche Klasse direkt ändern:
class Domain(object): def __init__(self, domain): self.domain = domain if ...: self.__class__ = RegistrarA else: self.__class__ = RegistrarB
Und dann wird das Folgende funktionieren.
com = Domain('test.com') #load RegistrarA com.lookup()
Ich benutze diesen Ansatz erfolgreich.
quelle
Sie können eine 'Wrapper'-Klasse erstellen und ihre
__new__()
Methode überladen , um Instanzen der spezialisierten Unterklassen zurückzugeben, z.class Registrar(object): def __new__(self, domain): if ...: return RegistrarA(domain) elif ...: return RegistrarB(domain) else: raise Exception()
Um sich mit sich nicht gegenseitig ausschließenden Bedingungen zu befassen, die in anderen Antworten angesprochen wurden, müssen Sie sich zunächst die Frage stellen, ob die Wrapper-Klasse, die die Rolle eines Dispatchers spielt, die Bedingungen regeln soll oder es wird es an die Fachklassen delegieren. Ich kann einen gemeinsamen Mechanismus vorschlagen, bei dem die spezialisierten Klassen ihre eigenen Bedingungen definieren, der Wrapper jedoch die Validierung wie folgt durchführt (vorausgesetzt, jede spezialisierte Klasse stellt eine Klassenmethode bereit, die überprüft, ob es sich um einen Registrar für eine bestimmte Domäne handelt, is_registrar_for (. ..) wie in anderen Antworten vorgeschlagen):
class Registrar(object): registrars = [RegistrarA, RegistrarB] def __new__(self, domain): matched_registrars = [r for r in self.registrars if r.is_registrar_for(domain)] if len(matched_registrars) > 1: raise Exception('More than one registrar matched!') elif len(matched_registrars) < 1: raise Exception('No registrar was matched!') else: return matched_registrars[0](domain)
quelle
__new__
etwas anderes als eine Instanz von zurückzugebencls
, und da die RückgabeNone
ausdrücklich verboten ist, würde dies zu einer Schlussfolgerung, dass eine Instanz einer anderen Klasse zurückgegeben werden darf.Ich habe die ganze Zeit dieses Problem. Wenn Sie die Klassen in Ihre Anwendung (und ihre Module) eingebettet haben, können Sie eine Funktion verwenden. Wenn Sie Plugins jedoch dynamisch laden, benötigen Sie etwas Dynamischeres - die automatische Registrierung der Klassen bei einer Factory über Metaklassen.
Hier ist ein Muster, von dem ich sicher bin, dass ich es ursprünglich aus StackOverflow entfernt habe, aber ich habe noch nicht den Pfad zum ursprünglichen Beitrag
_registry = {} class PluginType(type): def __init__(cls, name, bases, attrs): _registry[name] = cls return super(PluginType, cls).__init__(name, bases, attrs) class Plugin(object): __metaclass__ = PluginType # python <3.0 only def __init__(self, *args): pass def load_class(plugin_name, plugin_dir): plugin_file = plugin_name + ".py" for root, dirs, files in os.walk(plugin_dir) : if plugin_file in (s for s in files if s.endswith('.py')) : fp, pathname, description = imp.find_module(plugin_name, [root]) try: mod = imp.load_module(plugin_name, fp, pathname, description) finally: if fp: fp.close() return def get_class(plugin_name) : t = None if plugin_name in _registry: t = _registry[plugin_name] return t def get_instance(plugin_name, *args): return get_class(plugin_name)(*args)
quelle
wie wäre es mit so etwas
class Domain(object): registrars = [] @classmethod def add_registrar( cls, reg ): registrars.append( reg ) def __init__( self, domain ): self.domain = domain for reg in self.__class__.registrars: if reg.is_registrar_for( domain ): self.registrar = reg def lookup( self ): return self.registrar.lookup() Domain.add_registrar( RegistrarA ) Domain.add_registrar( RegistrarB ) com = Domain('test.com') com.lookup()
quelle
Hier ist ein Metaklasse sammelt implizit Registars Klassen in einem ENTITIES dict
class DomainMeta(type): ENTITIES = {} def __new__(cls, name, bases, attrs): cls = type.__new__(cls, name, bases, attrs) try: entity = attrs['domain'] cls.ENTITIES[entity] = cls except KeyError: pass return cls class Domain(metaclass=DomainMeta): @classmethod def factory(cls, domain): return DomainMeta.ENTITIES[domain]() class RegistrarA(Domain): domain = 'test.com' def lookup(self): return 'Custom command for .com TLD' class RegistrarB(Domain): domain = 'test.biz' def lookup(self): return 'Custom command for .biz TLD' com = Domain.factory('test.com') type(com) # <class '__main__.RegistrarA'> com.lookup() # 'Custom command for .com TLD' com = Domain.factory('test.biz') type(com) # <class '__main__.RegistrarB'> com.lookup() # 'Custom command for .biz TLD'
quelle