Generics/template în python? (Programare, Python, Șabloane, Programare Generică)

chei a intrebat.

Cum gestionează python scenariile de tip generic/template? Să zicem că vreau să creez un fișier extern „BinaryTree.py” și să îl fac să se ocupe de arbori binari, dar pentru orice tip de date.

Așadar, aș putea să-i transmit tipul unui obiect personalizat și să am un arbore binar al acelui obiect. Cum se face acest lucru în python?

Comentarii

    16

  • python are șabloane de rață –  > Por David Heffernan.
10 răspunsuri
André Caron

Python folosește tastatura de rață, , deci nu are nevoie de o sintaxă specială pentru a gestiona mai multe tipuri.

Dacă veniți din mediul C++, vă amintiți că, atâta timp cât operațiile utilizate în funcția/clasa șablon sunt definite pe un anumit tip T (la nivel de sintaxă), puteți utiliza acel tip T în șablon.

Deci, în principiu, funcționează în același mod:

  1. definiți un contract pentru tipul de elemente pe care doriți să le introduceți în arborele binar.
  2. documentați acest contract (de exemplu, în documentația clasei)
  3. implementați arborele binar folosind doar operațiile specificate în contract
  4. bucurați-vă de

Veți observa totuși că, dacă nu scrieți o verificare explicită a tipului (ceea ce este de obicei descurajat), nu veți putea impune ca un arbore binar să conțină numai elemente de tipul ales.

Comentarii

  • André, aș dori să înțeleg de ce verificarea explicită a tipului este în mod normal descurajată în Python. Sunt confuz pentru că s-ar părea că, în cazul unui limbaj tipizat dinamic, am putea avea o mulțime de probleme dacă nu putem garanta tipurile posibile care vor intra în funcție. Dar, din nou, sunt foarte nou în Python. 🙂 –  > Por ScottEdwards2000.
  • @ScottEdwards2000 Puteți avea o verificare implicită a tipului cu ajutorul indicilor de tip din PEP 484 și a unui verificator de tip –  > Por noɥʇʎԀʎzɐɹƆ.
  • Din perspectiva puristului Python, Python este un limbaj dinamic și duck-typing este paradigmă; adică, siguranța de tip este considerată „nepythonică”. Acesta este un lucru pe care mi-a fost greu să îl consider acceptabil – pentru o vreme – deoarece sunt foarte atașat de C#. Pe de o parte, consider că siguranța de tip este o necesitate. Pe măsură ce am echilibrat balanța între lumea .Net și paradigma Pythonic, am acceptat că type-safety este de fapt un suzetă și că, dacă am nevoie, tot ce trebuie să fac este să if isintance(o, t): sau if not isinstance(o, t): … destul de simplu. –  > Por IAbstract.
  • Mulțumesc comentatorilor, răspunsuri excelente. Mi-am dat seama după ce le-am citit că de fapt vreau doar verificarea tipului pentru a-mi prinde propriile erori. Așa că voi folosi doar verificarea implicită a tipului. –  > Por ScottEdwards2000.
  • Cred că mulți pythoniști nu înțeleg ce înseamnă acest lucru – genericele sunt o modalitate de a oferi libertate și siguranță în același timp. Chiar și lăsând la o parte genericele & doar folosind parametrii tipizați, scriitorul funcției știe că își poate modifica codul pentru a folosi orice metodă pe care o oferă clasa; cu tipizarea rață dacă începi să folosești o metodă pe care nu o foloseai înainte, ai schimbat brusc definiția raței, iar lucrurile se vor strica probabil. –  > Por Ken Williams.
momo

Celelalte răspunsuri sunt în totalitate în regulă:

  • Nu este nevoie de o sintaxă specială pentru a susține genericul în Python.
  • Python folosește tipărirea rață, așa cum a subliniat André.

Cu toate acestea, dacă doriți în continuare un tipizat variantă tipizată, există o soluție încorporată începând cu Python 3.5.


Clase generice:

from typing import TypeVar, Generic, List

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

Funcții generice:

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.

Verificarea statică a tipului:

Trebuie să folosiți un static type checker cum ar fi mypy pentru a vă analiza codul sursă.

Instalați mypy:

python3 -m pip install mypy

Analizați-vă codul sursă, de exemplu un anumit fișier:

mypy foo.py

sau un director:

mypy some_directory

mypy va detecta și va imprima erorile de tip. O ieșire concretă pentru exemplul Stack furnizat mai sus:

foo.py:23: error: Argument 1 to "push" of "Stack" has incompatible type "str"; expected "int"

Referințe: documentația mypy despre generics și rularea mypy

Comentarii

  • Cu siguranță cel mai bun răspuns aici –  > Por Denis Itskovich.
  • Notă importantă de la docs.python.org/3.5/library/typing.html este „Metaclasa utilizată de Generic este o subclasă a abc.ABCMeta.” –  > Por Jonathan Komar.
  • @JonathanKomar de ce este important acest lucru în acest context? –  > Por Sush.
  • @Sush Pentru că, dacă știi asta, atunci toate cunoștințele tale existente despre abc.ABC sunt aplicabile clasei Stack de aici. –  > Por Jonathan Komar.
  • Am rulat codul stack de mai sus și nu am primit nicio eroare la stack.push(„x”) din anumite motive. De ce se întâmplă asta? –  > Por Quoc Anh Tran.
8bitjoey

De fapt, acum puteți folosi genericele în Python 3.5+.Vedeți PEP-484 și documentația modulului de tipărire.

Conform practicii mele, nu este foarte simplu și clar, în special pentru cei care sunt familiarizați cu Java Generics, dar totuși utilizabil.

Comentarii

    15

  • Asta arată ca o înșelăciune ieftină a generics tbh. E ca și cum cineva a luat generics, le-a pus într-un blender, l-a lăsat să funcționeze și a uitat de el până când motorul blenderului s-a ars, iar apoi l-a scos 2 zile mai târziu și a spus:: „Hei, am luat medicamente generice”. –  > Por Toată lumea.
  • Acestea sunt „type hints”, nu au nimic de-a face cu genericele. –  > Por wool.in.silver.
  • Același lucru în typescript, dar acolo funcționează ca în Java (sintactic). Genericele în aceste limbaje sunt doar indicii de tip –  > Por Davide.
Ariyo Live

După ce am venit cu câteva idei bune despre crearea de tipuri generice în python, am început să caut pe alții care au avut aceeași idee, dar nu am găsit niciunul. Așa că, iată-l aici. Am încercat acest lucru și funcționează bine. Ne permite să parametrizăm tipurile noastre în python.

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 

Acum puteți deriva tipuri din acest tip generic.

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

Această soluție este simplistă și are limitele sale. De fiecare dată când creați un tip generic, se va crea un nou tip. Astfel, mai multe clase care moștenesc List( str ) ca părinte ar fi moștenit din două clase separate. Pentru a depăși această problemă, trebuie să creați un dict pentru a stoca diferitele forme ale clasei interioare și să returnați clasa interioară creată anterior, în loc să creați una nouă. Acest lucru ar împiedica crearea de tipuri duplicate cu aceiași parametri. Dacă sunteți interesat, o soluție mai elegantă poate fi realizată cu decoratori și/sau metaclase.

Comentarii

  • Puteți detalia modul în care dict poate fi utilizat în exemplul de mai sus? Aveți un fragment pentru asta, fie în git, fie în altceva? Vă mulțumesc… –  > Por gnomeria.
  • Nu am un exemplu și ar putea consuma puțin timp în acest moment. Cu toate acestea, principiile nu sunt atât de dificile. Dict-ul acționează ca un cache. Când noua clasă este creată, trebuie să se uite la parametrii de tip pentru a crea un identificator pentru acel tip și configurația parametrilor. Apoi îl poate folosi ca o cheie într-un dict pentru a căuta clasa existentă anterior. În acest fel, se va folosi acea clasă la nesfârșit. –  > Por Ariyo Live.
  • Mulțumesc pentru inspirație – consultați răspunsul meu pentru o extindere a acestei tehnici cu metaclase –  > Por Eric.
Andrea

Deoarece Python este tipizat dinamic, tipurile de obiecte nu contează în multe cazuri. Este o idee mai bună să acceptați orice.

Pentru a demonstra ce vreau să spun, această clasă copac va accepta orice pentru cele două ramuri ale sale:

class BinaryTree:
    def __init__(self, left, right):
        self.left, self.right = left, right

Și ar putea fi folosită astfel:

branch1 = BinaryTree(1,2)
myitem = MyClass()
branch2 = BinaryTree(myitem, None)
tree = BinaryTree(branch1, branch2)

Comentarii

  • Tipurile de obiecte do contează. Dacă treceți în buclă peste elementele din container și apelați o metodă foo pe fiecare obiect, atunci introducerea de șiruri de caractere în container este o idee proastă. Nu este un mai bună să acceptați orice. Cu toate acestea, este convenabil să nu se ceară ca toate obiectele din container să derive din clasa HasAFooMethod. –  > Por André Caron.
  • De fapt, tipul face contează: trebuie să fie comandat. –  > Por Fred Foo.
  • Oh, bine. Atunci am înțeles greșit. –  > Por Andrea.
Leopd

Din moment ce python este tipizat dinamic, acest lucru este foarte ușor. De fapt, ar trebui să faceți o muncă suplimentară pentru ca clasa BinaryTree să nu lucreze cu orice tip de date.

De exemplu, dacă doriți ca valorile cheie care sunt folosite pentru a plasa obiectul în arbore să fie disponibile în cadrul obiectului dintr-o metodă precum key() trebuie doar să apelați key() asupra obiectelor. De exemplu:

class BinaryTree(object):

    def insert(self, object_to_insert):
        key = object_to_insert.key()

Rețineți că nu trebuie să definiți niciodată ce fel de clasă este obiect_to_insert. Atâta timp cât are o clasă key() va funcționa.

Excepția este dacă doriți să funcționeze cu tipuri de date de bază, cum ar fi șiruri de caractere sau numere întregi. Va trebui să le înfășurați într-o clasă pentru a le face să funcționeze cu BinaryTree-ul dvs. generic. Dacă asta sună prea greu și doriți eficiența suplimentară de a stoca de fapt doar șiruri de caractere, îmi pare rău, Python nu se pricepe la asta.

Comentarii

  • Dimpotrivă: toate tipurile de date sunt obiecte în Python. Ele nu trebuie să fie înfășurate (ca în Java cu Integer boxing/unboxing). –  > Por George Hilliard.
Eric

Iată o variantă a acestui răspuns care folosește metaclase pentru a evita sintaxa dezordonată și folosește typing-style List[int] sintaxă:

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]
        ))

Cu această nouă metaclasă, putem rescrie exemplul din răspunsul la care am făcut legătura ca:

@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

Această abordare are câteva avantaje interesante

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

John Zwinck

Uitați-vă la modul în care o fac containerele încorporate. dict și list și așa mai departe conțin elemente eterogene de orice tip doriți. Dacă definiți, să zicem, un insert(val) pentru arborele dvs., aceasta va face la un moment dat ceva de genul node.value = val iar Python se va ocupa de restul.

igauravsehrawat

Din fericire, au existat unele eforturi pentru programarea generică în python . Există o bibliotecă : generic

Aici este documentația pentru aceasta: http://generic.readthedocs.org/en/latest/

Nu a progresat de-a lungul anilor , dar puteți avea o idee aproximativă despre cum să folosiți & faceți-vă propria bibliotecă.

Salutări

Florian Steenbuck

Dacă folosiți Python 2 sau doriți să rescrieți codul java. Their nu este o soluție reală pentru acest lucru. Iată ce am reușit să lucrez într-o noapte: https://github.com/FlorianSteenbuck/python-generics Tot nu primesc nici un compilator, așa că în prezent îl folosiți așa:

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

  • Compilator
  • Obțineți clase și tipuri generice care să funcționeze (pentru lucruri precum <? extends List<Number>>)
  • Adăugați super suport
  • Adăugați ? suport
  • Curățarea codului