W jaki sposób Python obsługuje scenariusze typu ogólnego / szablonu? Powiedzmy, że chcę utworzyć zewnętrzny plik „BinaryTree.py” i zlecić mu obsługę drzew binarnych, ale dla dowolnego typu danych.
Mogłem więc przekazać mu typ obiektu niestandardowego i mieć drzewo binarne tego obiektu. Jak to się robi w Pythonie?
python
templates
generic-programming
Klucze
źródło
źródło
Odpowiedzi:
Python używa pisania kaczego , więc nie potrzebuje specjalnej składni do obsługi wielu typów.
Jeśli jesteś w tle C ++, będziesz pamiętać, że dopóki operacje używane w funkcji / klasie szablonu są zdefiniowane na jakimś typie
T
(na poziomie składni), możesz użyć tego typuT
w szablonie.Zasadniczo działa to w ten sam sposób:
Zauważysz jednak, że jeśli nie napiszesz jawnego sprawdzania typu (co jest zwykle odradzane), nie będziesz w stanie wymusić, że drzewo binarne zawiera tylko elementy wybranego typu.
źródło
if isintance(o, t):
if not isinstance(o, t):
Pozostałe odpowiedzi są całkowicie w porządku:
Jeśli jednak nadal potrzebujesz wariantu wpisanego na maszynie , istnieje wbudowane rozwiązanie od wersji Python 3.5.
Klasy ogólne :
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
Funkcje ogólne:
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.
Odniesienie: mypy dokumentacja o rodzajach generycznych .
źródło
Właściwie teraz możesz używać typów ogólnych w Pythonie 3.5+. Zobacz dokumentację PEP-484 i moduł do pisania .
Zgodnie z moją praktyką nie jest to zbyt płynne i jasne, zwłaszcza dla tych, którzy znają język generyczny Java, ale nadal można go używać.
źródło
Po kilku dobrych przemyśleniach na temat tworzenia typów ogólnych w Pythonie, zacząłem szukać innych, którzy mieli ten sam pomysł, ale nie mogłem znaleźć żadnego. A więc oto jest. Wypróbowałem to i działa dobrze. Pozwala nam sparametryzować nasze typy w Pythonie.
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
Możesz teraz wyprowadzać typy z tego typu ogólnego.
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
To rozwiązanie jest uproszczone i ma swoje ograniczenia. Za każdym razem, gdy tworzysz typ ogólny, tworzy on nowy typ. Tak więc wiele klas dziedziczących
List( str )
jako rodzic będzie dziedziczyć z dwóch oddzielnych klas. Aby to przezwyciężyć, musisz utworzyć dyktando, aby przechowywać różne formy klasy wewnętrznej i zwrócić poprzednio utworzoną klasę wewnętrzną, zamiast tworzyć nową. Zapobiegałoby to tworzeniu zduplikowanych typów o tych samych parametrach. Jeśli jesteś zainteresowany, możesz stworzyć bardziej eleganckie rozwiązanie z dekoratorami i / lub metaklasami.źródło
Ponieważ Python jest typowany dynamicznie, typy obiektów w wielu przypadkach nie mają znaczenia. Lepszym pomysłem jest zaakceptowanie wszystkiego.
Aby pokazać, co mam na myśli, ta klasa drzewa zaakceptuje wszystko dla swoich dwóch gałęzi:
class BinaryTree: def __init__(self, left, right): self.left, self.right = left, right
Można go użyć w ten sposób:
branch1 = BinaryTree(1,2) myitem = MyClass() branch2 = BinaryTree(myitem, None) tree = BinaryTree(branch1, branch2)
źródło
foo
na każdym obiekcie, to umieszczanie ciągów znaków w kontenerze jest złym pomysłem. Nie lepiej jest cokolwiek zaakceptować . Jednak wygodnie jest nie wymagać, aby wszystkie obiekty w kontenerze pochodziły z klasyHasAFooMethod
.Ponieważ python jest wpisywany dynamicznie, jest to bardzo łatwe. W rzeczywistości musiałbyś wykonać dodatkową pracę, aby Twoja klasa BinaryTree nie działała z żadnym typem danych.
Na przykład, jeśli chcesz, aby wartości kluczowe, które są używane do umieszczenia obiektu w drzewie, dostępne w obiekcie za pomocą metody takiej jak
key()
po prostu wywołaniekey()
obiektów. Na przykład:class BinaryTree(object): def insert(self, object_to_insert): key = object_to_insert.key()
Zauważ, że nigdy nie musisz definiować rodzaju klasy object_to_insert. Dopóki ma
key()
metodę, będzie działać.Wyjątkiem jest sytuacja, gdy chcesz, aby działał z podstawowymi typami danych, takimi jak ciągi znaków lub liczby całkowite. Będziesz musiał opakować je w klasę, aby zmusić je do pracy z twoim ogólnym BinaryTree. Jeśli to brzmi zbyt ciężko i chcesz uzyskać dodatkową efektywność przechowywania po prostu strun, przepraszam, nie w tym Python jest dobry.
źródło
Integer
opakowywać (jak w Javie z boxing / unboxing).Oto wariant tej odpowiedzi, który wykorzystuje metaklasy, aby uniknąć niechlujnej składni i użyć składni w
typing
stylu -stylList[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] ))
Dzięki tej nowej metaklasie możemy przepisać przykład w odpowiedzi, do której odsyłam, jako:
@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
Takie podejście ma kilka fajnych zalet
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
źródło
Zobacz, jak robią to wbudowane kontenery.
dict
ilist
tak dalej zawierają heterogeniczne elementy dowolnego typu. Jeśli zdefiniujesz, powiedzmy,insert(val)
funkcję dla swojego drzewa, w pewnym momencie zrobi ona coś podobnego,node.value = val
a Python zajmie się resztą.źródło
Na szczęście podjęto pewne wysiłki w zakresie programowania ogólnego w Pythonie. Jest biblioteka: ogólna
Oto jego dokumentacja: http://generic.readthedocs.org/en/latest/
Nie rozwijał się przez lata, ale możesz mieć ogólne pojęcie, jak używać i tworzyć własną bibliotekę.
Twoje zdrowie
źródło
Jeśli używasz Pythona 2 lub chcesz przepisać kod java. To nie jest prawdziwe rozwiązanie tego problemu. Oto, co pracuję w nocy: https://github.com/FlorianSteenbuck/python-generics Nadal nie mam kompilatora, więc obecnie używasz go w ten sposób:
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
wsparcie?
wsparcieźródło