¿Cómo maneja Python los escenarios de tipo genérico / de plantilla? Digamos que quiero crear un archivo externo "BinaryTree.py" y que maneje árboles binarios, pero para cualquier tipo de datos.
Entonces podría pasarle el tipo de objeto personalizado y tener un árbol binario de ese objeto. ¿Cómo se hace esto en Python?
python
templates
generic-programming
llaves
fuente
fuente
Respuestas:
Python usa el tipo pato , por lo que no necesita una sintaxis especial para manejar varios tipos.
Si tiene experiencia en C ++, recordará que, siempre que las operaciones utilizadas en la función / clase de la plantilla estén definidas en algún tipo
T
(a nivel de sintaxis), puede utilizar ese tipoT
en la plantilla.Entonces, básicamente, funciona de la misma manera:
Sin embargo, observará que, a menos que escriba una verificación de tipo explícita (que generalmente no se recomienda), no podrá exigir que un árbol binario contenga solo elementos del tipo elegido.
fuente
if isintance(o, t):
oif not isinstance(o, t):
... bastante simple.Las otras respuestas están totalmente bien:
Sin embargo, si aún desea una variante escrita , existe una solución incorporada desde Python 3.5.
Clases genéricas :
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
Funciones genéricas:
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.
Referencia: documentación mypy sobre genéricos .
fuente
De hecho, ahora puede usar genéricos en Python 3.5+. Consulte PEP-484 y la documentación de la biblioteca de mecanografía .
Según mi práctica, no es muy transparente y claro, especialmente para aquellos que están familiarizados con Java Generics, pero aún se puede usar.
fuente
Después de pensar en algunos buenos pensamientos sobre cómo hacer tipos genéricos en Python, comencé a buscar otros que tuvieran la misma idea, pero no pude encontrar ninguno. Asi que aqui esta. Probé esto y funciona bien. Nos permite parametrizar nuestros tipos en 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
Ahora puede derivar tipos de este tipo genérico.
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
Esta solución es simplista y tiene sus limitaciones. Cada vez que crea un tipo genérico, creará un nuevo tipo. Por lo tanto, varias clases que heredan
List( str )
como padre heredarían de dos clases separadas. Para superar esto, debe crear un dictado para almacenar las diversas formas de la clase interna y devolver la clase interna creada anteriormente, en lugar de crear una nueva. Esto evitaría que se creen tipos duplicados con los mismos parámetros. Si está interesado, se puede hacer una solución más elegante con decoradores y / o metaclases.fuente
Debido a que Python se escribe dinámicamente, los tipos de objetos no importan en muchos casos. Es una mejor idea aceptar cualquier cosa.
Para demostrar lo que quiero decir, esta clase de árbol aceptará cualquier cosa para sus dos ramas:
class BinaryTree: def __init__(self, left, right): self.left, self.right = left, right
Y podría usarse así:
branch1 = BinaryTree(1,2) myitem = MyClass() branch2 = BinaryTree(myitem, None) tree = BinaryTree(branch1, branch2)
fuente
foo
en cada objeto, entonces poner cadenas en el contenedor es una mala idea. No es mejor idea aceptar nada . Sin embargo, es conveniente no requerir que todos los objetos del contenedor deriven de classHasAFooMethod
.Dado que Python se escribe dinámicamente, esto es muy fácil. De hecho, tendría que hacer un trabajo adicional para que su clase BinaryTree no funcione con ningún tipo de datos.
Por ejemplo, si desea que los valores clave que se utilizan para colocar el objeto en el árbol estén disponibles dentro del objeto desde un método como el
key()
que acaba de llamarkey()
a los objetos. Por ejemplo:class BinaryTree(object): def insert(self, object_to_insert): key = object_to_insert.key()
Tenga en cuenta que nunca es necesario definir qué tipo de clase es object_to_insert. Siempre que tenga un
key()
método, funcionará.La excepción es si desea que funcione con tipos de datos básicos como cadenas o números enteros. Tendrá que envolverlos en una clase para que funcionen con su BinaryTree genérico. Si eso suena demasiado pesado y desea la eficiencia adicional de almacenar cadenas, lo siento, Python no es bueno para eso.
fuente
Integer
boxing / unboxing).Aquí hay una variante de esta respuesta que usa metaclases para evitar la sintaxis desordenada y usa la sintaxis de
typing
estiloList[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] ))
Con esta nueva metaclase, podemos reescribir el ejemplo en la respuesta a la que me vinculo como:
@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
Este enfoque tiene algunos beneficios interesantes
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
fuente
Mira cómo lo hacen los contenedores incorporados.
dict
ylist
así sucesivamente contienen elementos heterogéneos del tipo que desee. Si define, digamos, unainsert(val)
función para su árbol, en algún momento hará algo comonode.value = val
y Python se encargará del resto.fuente
Afortunadamente, ha habido algunos esfuerzos para la programación genérica en Python. Hay una biblioteca: genérica
Aquí está la documentación para ello: http://generic.readthedocs.org/en/latest/
No ha progresado durante años, pero puede tener una idea aproximada de cómo usar y hacer su propia biblioteca.
Salud
fuente
Si usa Python 2 o desea reescribir el código java. No hay una solución real para esto. Esto es lo que consigo trabajando en una noche: https://github.com/FlorianSteenbuck/python-generics Todavía no obtengo un compilador, así que actualmente lo usas así:
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
soporte?
soportefuente