2011-07-17 23 views
38

¿Cómo maneja Python los escenarios genéricos/tipo de plantilla? Digamos que quiero crear un archivo externo "BinaryTree.py" y hacer que maneje árboles binarios, pero para cualquier tipo de datos.¿Genéricos/plantillas en python?

Así que podría pasarle el tipo de un objeto personalizado y tener un árbol binario de ese objeto. ¿Cómo se hace esto en Python?

+9

python tiene plantillas de pato –

Respuesta

44

Python usa duck typing, por lo que no necesita una sintaxis especial para manejar varios tipos.

Si eres de un fondo C++, recordarás que, siempre que las operaciones utilizadas en la función/clase de la plantilla se definan en algún tipo T (en el nivel de sintaxis), puedes usar ese tipo T en la plantilla.

Así que, básicamente, funciona de la misma manera:

  1. definen un contrato para el tipo de elementos que desea insertar en el árbol binario.
  2. documento de este contrato (es decir, en la documentación de la clase)
  3. implementar el árbol binario utilizando sólo las operaciones especificadas en el contrato
  4. disfrutar

Se habrá dado cuenta sin embargo, que a menos que se escribe la comprobación de tipos explícita (que generalmente no se recomienda), no podrá exigir que un árbol binario contenga solo elementos del tipo elegido.

+0

André, me gustaría entender por qué normalmente se desaconseja la verificación explícita de tipos en Python. Estoy confundido porque parece ser un lenguaje de tipado dinámico, podríamos tener muchos problemas si no podemos garantizar los posibles tipos que entrarán en la función. Pero, nuevamente, soy nuevo en Python. :-) – ScottEdwards2000

+1

@ ScottEdwards2000 Puede tener una comprobación de tipo implícita con sugerencias de tipo en PEP 484 y un verificador de tipo –

+0

@ ScottEdwards2000 no debe verificar los tipos, porque python es de tipo pato. Si alguien quiere usar tu funcionalidad con una clase diferente, eso depende de ellos, siempre y cuando transmitan cosas que se comporten como tus patos, no deberías preocuparte si es realmente un pato o un pato falso. – McKay

0

Mire cómo lo hacen los contenedores incorporados. dict y list y así sucesivamente contienen elementos heterogéneos de cualquier tipo que desee. Si define, por ejemplo, una función insert(val) para su árbol, en algún momento hará algo como node.value = val y Python se encargará del resto.

2

Dado que Python se tipea 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 por sus dos ramas:

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

y podría usarse como esto:

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

Los tipos de objetos * do * matter. Si está recorriendo los elementos del contenedor y llamando a un método 'foo' en cada objeto, entonces poner cadenas en el contenedor es una mala idea. No es una * mejor * idea aceptar * cualquier cosa *. Sin embargo, es * conveniente * no requerir que todos los objetos en el contenedor deriven de la clase 'HasAFooMethod'. –

+1

En realidad, el tipo * does * matter: tiene que ordenarse. –

+0

Oh, bien. Yo no entendí entonces. – Andrea

3

Desde pitón se escribe de forma dinámica, esta es super facil De hecho, tendrías que hacer un trabajo extra para que tu clase BinaryTree no funcione con ningún tipo de datos.

Por ejemplo, si desea los valores clave que se utilizan para colocar el objeto en el árbol disponible dentro del objeto de un método como key(), simplemente llame al key() en los objetos. Por ejemplo:

class BinaryTree(object): 

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

Tenga en cuenta que nunca necesita definir qué clase de clase es object_to_insert. Siempre que tenga un método key(), funcionará.

La excepción es si desea que funcione con tipos de datos básicos como cadenas o enteros. Tendrás que envolverlos en una clase para que funcionen con tu BinaryTree genérico. Si eso suena demasiado pesado y quieres la eficiencia extra de solo almacenar cadenas, lo siento, Python no es bueno en eso.

+3

Por el contrario: todos los tipos de datos son objetos en Python. No necesitan envolverse (como en Java con el boxeo/desempaquetado 'Entero'). – thirtythreeforty

1

Afortunadamente ha habido algunos esfuerzos para la programación genérica en python. Hay una biblioteca: generic

Aquí está la documentación para ello: http://generic.readthedocs.org/en/latest/

No ha progresar durante años, pero se puede tener una idea aproximada de cómo utilizar & hacer su propia biblioteca.

Saludos

12

En realidad, ahora puede usar los genéricos en Python 3.5 +. Ver PEP-484 y typing library documentation.

De acuerdo con mi práctica, no es muy claro y sin fisuras, especialmente para aquellos que están familiarizados con los genéricos de Java, pero todavía se puede utilizar.

2

Después de pensar en 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 simple, y tiene sus limitaciones. Cada vez que creas un tipo genérico, creará un nuevo tipo. Por lo tanto, las clases múltiples que heredan List(str) como padre heredarían de dos clases separadas. Para superar esto, debe crear un dict 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.

+0

¿Puede explicar cómo se puede usar el dict en el ejemplo anterior? ¿Tienes un fragmento para eso en git o algo así? Gracias .. – samiunn

+0

No tengo un ejemplo, y podría ser un poco lento en este momento. Sin embargo, los principios no son tan difíciles. El dict actúa como caché. Cuando la nueva clase es crear, necesita mirar los parámetros de tipo para crear un identificador para ese tipo y configuración de parámetros. Luego puede usarlo como una clave en un dict para buscar la clase previamente existente. De esta manera, utilizará esa clase una y otra vez. –