Wie geht Python mit generischen Szenarien / Szenarien vom Typ Vorlage um? Angenommen, ich möchte eine externe Datei "BinaryTree.py" erstellen und Binärbäume verarbeiten lassen, jedoch für jeden Datentyp.
So könnte ich ihm den Typ eines benutzerdefinierten Objekts übergeben und einen Binärbaum dieses Objekts haben. Wie geht das in Python?
python
templates
generic-programming
Schlüssel
quelle
quelle
Antworten:
Python verwendet die Enten-Typisierung , sodass keine spezielle Syntax erforderlich ist, um mehrere Typen zu verarbeiten.
Wenn Sie einen C ++ - Hintergrund haben, werden Sie sich daran erinnern, dass
T
Sie diesen TypT
in der Vorlage verwenden können , solange die in der Vorlagenfunktion / -klasse verwendeten Operationen für einen Typ (auf Syntaxebene) definiert sind .Im Grunde funktioniert es also genauso:
Sie werden jedoch feststellen, dass Sie nicht erzwingen können, dass ein Binärbaum nur Elemente des ausgewählten Typs enthält, es sei denn, Sie schreiben eine explizite Typprüfung (von der normalerweise abgeraten wird).
quelle
if isintance(o, t):
oderif not isinstance(o, t):
... ziemlich einfach tun .Die anderen Antworten sind völlig in Ordnung:
Wenn Sie jedoch weiterhin eine typisierte Variante wünschen , gibt es seit Python 3.5 eine integrierte Lösung.
Generische Klassen :
from typing import TypeVar, Generic T = TypeVar('T') class Stack(Generic[T]): def __init__(self) -> None: # Create an empty list with items of type T self.items: List[T] = [] def push(self, item: T) -> None: self.items.append(item) def pop(self) -> T: return self.items.pop() def empty(self) -> bool: return not self.items
# Construct an empty Stack[int] instance stack = Stack[int]() stack.push(2) stack.pop() stack.push('x') # Type error
Allgemeine Funktionen:
from typing import TypeVar, Sequence T = TypeVar('T') # Declare type variable def first(seq: Sequence[T]) -> T: return seq[0] def last(seq: Sequence[T]) -> T: return seq[-1] n = first([1, 2, 3]) # n has type int.
Referenz: mypy Dokumentation über Generika .
quelle
Tatsächlich können Sie jetzt Generika in Python 3.5+ verwenden. Siehe PEP-484 und Dokumentation zur Typisierungsbibliothek .
Nach meiner Praxis ist es nicht sehr nahtlos und klar, insbesondere für diejenigen, die mit Java Generics vertraut sind, aber dennoch verwendbar.
quelle
Nachdem ich mir einige gute Gedanken über die Herstellung generischer Typen in Python gemacht hatte, suchte ich nach anderen, die die gleiche Idee hatten, aber keine fanden. Hier ist es also. Ich habe das ausprobiert und es funktioniert gut. Es ermöglicht uns, unsere Typen in Python zu parametrisieren.
class List( type ): def __new__(type_ref, member_type): class List(list): def append(self, member): if not isinstance(member, member_type): raise TypeError('Attempted to append a "{0}" to a "{1}" which only takes a "{2}"'.format( type(member).__name__, type(self).__name__, member_type.__name__ )) list.append(self, member) return List
Sie können jetzt Typen von diesem generischen Typ ableiten.
class TestMember: pass class TestList(List(TestMember)): def __init__(self): super().__init__() test_list = TestList() test_list.append(TestMember()) test_list.append('test') # This line will raise an exception
Diese Lösung ist simpel und hat ihre Grenzen. Jedes Mal, wenn Sie einen generischen Typ erstellen, wird ein neuer Typ erstellt. Somit würden mehrere Klassen, die
List( str )
als Eltern erben, von zwei getrennten Klassen erben. Um dies zu überwinden, müssen Sie ein Diktat erstellen, um die verschiedenen Formen der inneren Klasse zu speichern und die zuvor erstellte innere Klasse zurückzugeben, anstatt eine neue zu erstellen. Dies würde verhindern, dass doppelte Typen mit denselben Parametern erstellt werden. Bei Interesse kann mit Dekorateuren und / oder Metaklassen eine elegantere Lösung gefunden werden.quelle
Da Python dynamisch typisiert wird, spielen die Objekttypen in vielen Fällen keine Rolle. Es ist eine bessere Idee, etwas zu akzeptieren.
Um zu demonstrieren, was ich meine, akzeptiert diese Baumklasse alles für ihre beiden Zweige:
class BinaryTree: def __init__(self, left, right): self.left, self.right = left, right
Und es könnte so verwendet werden:
branch1 = BinaryTree(1,2) myitem = MyClass() branch2 = BinaryTree(myitem, None) tree = BinaryTree(branch1, branch2)
quelle
foo
für jedes Objekt eine Methode aufrufen , ist es eine schlechte Idee, Zeichenfolgen in den Container einzufügen. Es ist keine bessere Idee, etwas zu akzeptieren . Es ist jedoch zweckmäßig , nicht zu verlangen, dass alle Objekte im Container von der Klasse stammenHasAFooMethod
.Da Python dynamisch typisiert wird, ist dies sehr einfach. Tatsächlich müssten Sie zusätzliche Arbeit für Ihre BinaryTree-Klasse leisten, um mit keinem Datentyp zu arbeiten.
Wenn Sie beispielsweise möchten, dass die Schlüsselwerte, mit denen das Objekt in dem Baum platziert wird, über eine Methode, wie
key()
Sie sie nurkey()
für die Objekte aufrufen, innerhalb des Objekts verfügbar sind. Zum Beispiel:class BinaryTree(object): def insert(self, object_to_insert): key = object_to_insert.key()
Beachten Sie, dass Sie nie definieren müssen, um welche Art von Klasse object_to_insert es sich handelt. Solange es eine
key()
Methode gibt, wird es funktionieren.Die Ausnahme ist, wenn Sie möchten, dass es mit grundlegenden Datentypen wie Zeichenfolgen oder Ganzzahlen funktioniert. Sie müssen sie in eine Klasse einschließen, damit sie mit Ihrem generischen BinaryTree funktionieren. Wenn das zu schwer klingt und Sie die zusätzliche Effizienz wünschen, nur Strings zu speichern, ist Python leider nicht gut darin.
quelle
Integer
Boxen / Unboxing).Hier ist eine Variante dieser Antwort , die Metaklassen verwendet, um die unordentliche Syntax zu vermeiden, und die Syntax
typing
-style verwendetList[int]
:class template(type): def __new__(metacls, f): cls = type.__new__(metacls, f.__name__, (), { '_f': f, '__qualname__': f.__qualname__, '__module__': f.__module__, '__doc__': f.__doc__ }) cls.__instances = {} return cls def __init__(cls, f): # only needed in 3.5 and below pass def __getitem__(cls, item): if not isinstance(item, tuple): item = (item,) try: return cls.__instances[item] except KeyError: cls.__instances[item] = c = cls._f(*item) item_repr = '[' + ', '.join(repr(i) for i in item) + ']' c.__name__ = cls.__name__ + item_repr c.__qualname__ = cls.__qualname__ + item_repr c.__template__ = cls return c def __subclasscheck__(cls, subclass): for c in subclass.mro(): if getattr(c, '__template__', None) == cls: return True return False def __instancecheck__(cls, instance): return cls.__subclasscheck__(type(instance)) def __repr__(cls): import inspect return '<template {!r}>'.format('{}.{}[{}]'.format( cls.__module__, cls.__qualname__, str(inspect.signature(cls._f))[1:-1] ))
Mit dieser neuen Metaklasse können wir das Beispiel in der Antwort, auf die ich verweise, wie folgt umschreiben:
@template def List(member_type): class List(list): def append(self, member): if not isinstance(member, member_type): raise TypeError('Attempted to append a "{0}" to a "{1}" which only takes a "{2}"'.format( type(member).__name__, type(self).__name__, member_type.__name__ )) list.append(self, member) return List l = List[int]() l.append(1) # ok l.append("one") # error
Dieser Ansatz hat einige nette Vorteile
print(List) # <template '__main__.List[member_type]'> print(List[int]) # <class '__main__.List[<class 'int'>, 10]'> assert List[int] is List[int] assert issubclass(List[int], List) # True
quelle
Schauen Sie sich an, wie die eingebauten Container das machen.
dict
undlist
so weiter enthalten heterogene Elemente jeglicher Art, die Sie mögen. Wenn Sie beispielsweise eineinsert(val)
Funktion für Ihren Baum definieren, wird sie irgendwann so etwas tunnode.value = val
und Python kümmert sich um den Rest.quelle
Glücklicherweise gab es einige Anstrengungen für die generische Programmierung in Python. Es gibt eine Bibliothek: generisch
Hier ist die Dokumentation dazu: http://generic.readthedocs.org/en/latest/
Es hat sich über Jahre nicht weiterentwickelt, aber Sie können eine ungefähre Vorstellung davon haben, wie Sie Ihre eigene Bibliothek verwenden und erstellen.
Prost
quelle
Wenn Sie Python 2 verwenden oder Java-Code neu schreiben möchten. Dafür gibt es keine wirkliche Lösung. Folgendes arbeite ich in einer Nacht: https://github.com/FlorianSteenbuck/python-generics Ich bekomme immer noch keinen Compiler, daher verwenden Sie ihn derzeit so:
class A(GenericObject): def __init__(self, *args, **kwargs): GenericObject.__init__(self, [ ['b',extends,int], ['a',extends,str], [0,extends,bool], ['T',extends,float] ], *args, **kwargs) def _init(self, c, a, b): print "success c="+str(c)+" a="+str(a)+" b="+str(b)
TODOs
<? extends List<Number>>
)super
Unterstützung hinzufügen?
Unterstützung hinzufügenquelle