Google+ Seguidores

viernes, 22 de junio de 2018

Implementación de una Pila (Stack) en Python

Introducción:


Hola amigos de Internet, mi nombre es Luis y les doy nuevamente la bienvenida a Mi Diario Python.

En el día de hoy nos dedicaremos a conocer el concepto de Pila (Stack) y realizaremos una pequeña implementación de una Pila en el lenguaje de programación Python.

¿Que es una Pila?


Una pila es una estructura de datos que permite almacenar y recuperar datos. Las pilas son muy utilizadas en el ámbito de la informática debido a su simplicidad y capacidad de dar respuesta a numerosos procesos.

Para el manejo de los datos cuenta con dos operaciones básicas: aplicar (push), que coloca un objeto en la pila, y su operación inversa desapilar (pop), que retira el ultimo elemento apilado.

Una metáfora que se utiliza con frecuencia es la idea de una pila de platos dispuesta en una cafetería en un contenedor con un muelle que mantiene la pila a nivel. En esa serie, solo el primer plato es visible y accesible para el usuario, todos las demás permanecen ocultos. Como se añaden nuevos platos, cada nuevo plato se convierte en la parte superior de la pila, permaneciendo escondidos debajo los demás. A medida que el plato superior se extrae de la pila, el inmediatamente inferior pasa a ocupar la parte superior de la pila. Dos principios importantes son ilustrados por esta metáfora: únicamente se accede al plato que se encuentra en la parte superior (el último en depositarse), y el resto de platos de la pila permanecen ocultos. Para extraer un plato distinto al superior habrá que extraer antes los que se encuentran sobre él.
Supongamos que se está procesando una función y en su interior llama a otra función. La función se abandona para procesar la función de la llamada, pero antes se almacena en una pila la dirección que apunta a la función. Ahora supongamos que esa nueva función llama a su vez a otra función. Igualmente, se almacena su dirección, se abandona y se atiende la petición. Así en tantos casos como existan peticiones. La ventaja de la pila es que no requiere definir ninguna estructura de control ni conocer las veces que el programa estará saltando entre funciones para después retomarlas, con la única limitación de la capacidad de almacenamiento de la pila. Conforme se van cerrando las funciones, se van rescatando las funciones precedentes mediante sus direcciones almacenadas en la pila y se va concluyendo su proceso, esto hasta llegar a la primera.
El caso típico es el de una función recursiva (que se llama a si misma), esto es posible implementarlo con sencillez mediante una pila. La función se llama a si misma tantas veces como sea necesario hasta que el resultado de la función cumpla la condición de retorno; entonces, todas las funciones abiertas van completando su proceso en cascada. No se necesita saber cuandas veces se anidará y, por tanto, tampoco cuando se cumplirá la condición, con la única limitación de la capacidad de la pila. De sobrepasarse ese límite, normalmente porque se entra en un bucle sin final, se produce el «error de desbordamiento de la pila».

Una pila hecha en Python:

 A continuación les mostrare un ejemplo realizada en Python. Se trata de una clase llamada Stack que implementa todas las funcionalidades de una pila:

# Implementación de una pila en pyhton

class Stack: # Creamos la clase Stack
    def __init__(self):
        self.items = []
    
    def is_empty(self): # Metodo para verificar si la pila esta vacia
        return self.items == []
    
    def push(self, item): # Metodo para insertar elementos a la pila
        self.items.insert(0, item)
    
    def pop(self): # Metodo para eliminar el ultimo elemento apilado
        return self.items.pop(0)
    
    def print_stack(self): # Metodo para mostrar los elementos de la pila
        print(self.items)
    
    
pila = Stack() # Creamos una instancia de la pila
 
# ingresamos algunos elementos a la pila
pila.push('a')
pila.push('b')
pila.push('c')
 
pila.print_stack() # Mostramos los elementos de la pila
 
pila.pop() # Utilizamos el metodo pop
pila.print_stack() # Mostramos nuevamente los elementos de la pila

['c', 'b', 'a']
['b', 'a']



Esta implementación también implica conocimientos sobre la Programación Orientada a Objetos. Puedes Aprender más acerca de este tema en nuestros tutoriales de Aprender Python.

¿Alguna duda? No dudes en dejar tu comentario.

Mi nombre es Luis, y fue un placer compartir mis conocimientos con todos ustedes :D.

Seguir Leyendo

martes, 19 de junio de 2018

Metaclases en Python

Metaclases en Python

Metaclases de Python y metaprogramación











El término metaprogramación se refiere a la posibilidad de que un programa tenga conocimiento o se manipule a sí mismo. Python admite una forma de metaprogramación para clases llamadas metaclases .
Las metaclases son un concepto OOP esotérico , acechando detrás de prácticamente todos los códigos Python. Los estás usando ya sea que lo sepas o no. En su mayor parte, no necesita ser consciente de ello. La mayoría de los programadores de Python rara vez, o nunca, tienen que pensar en metaclases.
Sin embargo, cuando surge la necesidad, Python proporciona una capacidad que no todos los lenguajes orientados a objetos admiten: puede entenderse y definir metaclases personalizadas. El uso de metaclases personalizadas es un tanto controvertido, como lo sugiere la siguiente cita de Tim Peters, el gurú de Python autor del Zen de Python :
"Las metaclases son una magia más profunda de la que el 99% de los usuarios deberían preocuparse. Si se pregunta si los necesita, no (las personas que realmente los necesitan saben con certeza que los necesitan y no necesitan una explicación sobre por qué) ".
Tim Peters

Hay Pythonistas (como se conoce a los aficionados a Python) que creen que nunca debes usar metaclases personalizadas. Eso podría ir un poco lejos, pero probablemente sea cierto que las metaclases personalizadas en su mayoría no son necesarias. Si no es bastante obvio que un problema lo requiera, entonces probablemente será más limpio y más legible si se resuelve de una manera más simple.
Aún así, la comprensión de las metaclases de Python vale la pena, ya que conduce a una mejor comprensión de las partes internas de las clases de Python en general. Nunca se sabe: un día puede encontrarse en una de esas situaciones en las que simplemente sabe que una metaclase personalizada es lo que quiere.

Clases de estilo antiguo vs. estilo nuevo

En el reino de Python, una clase puede ser una de dos variedades . No se ha decidido ninguna terminología oficial, por lo que se les llama informalmente clases de estilo antiguo y nuevo.

Clases de estilo antiguo

Con las clases de estilo antiguo, la clase y el tipo no son exactamente lo mismo. Una instancia de una clase de estilo antiguo siempre se implementa a partir de un único tipo incorporado llamado instanceSi objes una instancia de una clase antigua, obj.__class__designa la clase, pero type(obj)siempre lo es instance
>>> clase  Foo : 
...     pase 
... 
>>> x  =  Foo () 
>>> x . __class__ 
<clase __main__.Foo a las 0x000000000535CC48> 
>>> tipo ( x ) 
<tipo 'instancia'>

Clases de nuevo estilo

Las clases de estilo nuevo unifican los conceptos de clase y tipo. Si objes una instancia de una clase de estilo nuevo, type(obj)es lo mismo que obj.__class__:
>>> clase  Foo : 
...     pasar 
>>> obj  =  Foo () 
>>> obj . __class__ 
<clase '__main __. Foo'> 
>>> tipo ( obj ) 
<clase '__main __. Foo'> 
>>> obj . __class__  es  type ( obj ) 
True
>>> n  =  5 
>>> d  =  {  'x'  :  1 ,  'y'  :  2  }

>>> clase  Foo : 
...     pase 
... 
>>> x  =  Foo ()

>>> para  obj  en  ( n ,  d ,  x ): 
...     print ( tipo ( obj )  es  obj . __class__ ) 
... 
Verdadero 
Verdadero 
Verdadero

Tipo y clase

En Python 3, todas las clases son de estilo nuevo. Por lo tanto, en Python 3 es razonable referirse al tipo de un objeto y su clase indistintamente.
Recuerde que, en Python, todo es un objeto. Las clases también son objetos. Como resultado, una clase debe tener un tipo. ¿Cuál es el tipo de una clase?
Considera lo siguiente:
>>> clase  Foo : 
...     pase 
... 
>>> x  =  Foo ()

>>> escriba ( x ) 
<clase '__main __. Foo'>

>>> tipo ( Foo ) 
<clase 'tipo'>
El tipo de xes clase Foo, como era de esperar. Pero el tipo de Foo, la clase en sí, es typeEn general, el tipo de cualquier clase de estilo nuevo es type.
El tipo de clases incorporadas con las que está familiarizado también es type:
>>> para  t  en  int ,  float ,  dict ,  list ,  tuple : 
...     print ( type ( t )) 
... 
<class 'type'> 
<class 'type'> 
<class 'type'> 
<class ' tipo '> 
<clase' tipo '>
Para el caso, el tipo de typees typetambién (sí, realmente):
>>> tipo ( tipo ) 
<clase 'tipo'>
typees una metaclase, de la cual las clases son instancias. Así como un objeto ordinario es una instancia de una clase, cualquier clase de estilo nuevo en Python, y por lo tanto cualquier clase en Python 3, es una instancia de la typemetaclase.
En el caso anterior:
  • xes una instancia de clase Foo.
  • Fooes una instancia de la typemetaclase.
  • typetambién es una instancia de la typemetaclase, por lo que es una instancia de sí mismo.
cadena de clase

Definiendo una clase dinámicamente

La type()función incorporada, cuando se pasa un argumento, devuelve el tipo de un objeto. Para las clases de estilo nuevo, generalmente es lo mismo que el atributo del objeto__class__ :
>>> tipo ( 3 ) 
<clase 'int'>

>>> escriba ([ 'foo' ,  'bar' ,  'baz' ]) 
<class 'list'>

>>> t  =  ( 1 ,  2 ,  3 ,  4 ,  5 ) 
>>> tipo ( t ) 
<clase 'tuple'>

>>> clase  Foo : 
...     pasar 
... 
>>> tipo ( Foo ()) 
<clase '__main __. Foo'>
También puede llamar type()con tres argumentos type(<name>, <bases>, <dct>):
  • <name>especifica el nombre de la clase. Esto se convierte en el __name__atributo de la clase.
  • <bases>especifica una tupla de las clases base de las que hereda la clase. Esto se convierte en el __bases__atributo de la clase.
  • <dct>especifica un diccionario de espacio de nombres que contiene definiciones para el cuerpo de la clase. Esto se convierte en el __dict__atributo de la clase.
Llamar type()de esta manera crea una nueva instancia de la typemetaclase. En otras palabras, crea dinámicamente una nueva clase.
En cada uno de los ejemplos siguientes, el fragmento superior define dinámicamente una clase con type(), mientras que el fragmento debajo define la clase de la manera habitual, con la classinstrucción. En cada caso, los dos fragmentos son funcionalmente equivalentes.

Ejemplo 1

En este primer ejemplo, los argumentos <bases><dct>pasados ​​a type()son vacíos. No se especifica ninguna herencia de ninguna clase principal, y nada se coloca inicialmente en el diccionario de espacio de nombres. Esta es la definición de clase más simple posible:
>>> Foo  =  tipo ( 'Foo' ,  (),  {})

>>> x  =  Foo () 
>>> x 
<__ main__.Objeto de objeto en 0x04CFAD50>
>>> clase  Foo : 
...     pase 
... 
>>> x  =  Foo () 
>>> x 
<__ main__.Oculino objeto en 0x0370AD50>

Ejemplo 2

Aquí, <bases>hay una tupla con un solo elemento Foo, que especifica la clase padre de la que Barhereda. Un atributo attr,, se coloca inicialmente en el diccionario de espacio de nombres:
>>> Bar  =  tipo ( 'Bar' ,  ( Foo ,),  dict ( attr = 100 ))

>>> x  =  Bar () 
>>> x . attr 
100 
>>> x . __class__ 
<class '__main __. Bar'> 
>>> x . __class__ . __bases__ 
(<clase '__main __. Foo'>,)
>>> clase  Bar ( Foo ): 
...     attr  =  100 
...

>>> x  =  Bar () 
>>> x . attr 
100 
>>> x . __class__ 
<class '__main __. Bar'> 
>>> x . __class__ . __bases__ 
(<clase '__main __. Foo'>,)

Ejemplo 3

Esta vez, <bases>está nuevamente vacío. Dos objetos se colocan en el diccionario de espacio de nombres a través del <dct>argumento. El primero es un atributo nombrado attry el segundo una función llamada attr_val, que se convierte en un método de la clase definida:
>>> Foo  =  tipo ( 
...     'Foo' , 
...     (), 
...     { 
...         'attr' :  100 , 
...         'attr_val' :  lambda  x  :  x . Attr 
...     } 
... )

>>> x  =  Foo () 
>>> x . attr 
100 
>>> x . attr_val () 
100
>>> clase  Foo : 
...     attr  =  100 
...     def  attr_val ( self ): 
...         regresa a  ti mismo . attr 
...

>>> x  =  Foo () 
>>> x . attr 
100 
>>> x . attr_val () 
100

Ejemplo 4

Solo se pueden definir funciones muy simples lambdaen Python . En el siguiente ejemplo, una función un poco más compleja se define externamente y luego se asigna attr_valen el diccionario de espacio de nombres a través del nombre f:
>>> def  f ( obj ): 
...     print ( 'attr =' ,  obj . attr ) 
... 
>>> Foo  =  tipo ( 
...     'Foo' , 
...     (), 
...     { 
...         'attr' :  100 , 
...         'attr_val' :  f 
...     } 
... )

>>> x  =  Foo () 
>>> x . attr 
100 
>>> x . attr_val () 
attr = 100
>>> def  f ( obj ): 
...     print ( 'attr =' ,  obj . attr ) 
... 
>>> clase  Foo : 
...     attr  =  100 
...     attr_val  =  f 
...

>>> x  =  Foo () 
>>> x . attr 
100 
>>> x . attr_val () 
attr = 100

Metaclases personalizadas

Considera de nuevo este ejemplo gastado:
>>> clase  Foo : 
...     pase 
... 
>>> f  =  Foo ()
La expresión Foo()crea una nueva instancia de clase FooCuando el intérprete se encuentra Foo(), ocurre lo siguiente:
  • El __call__()método de Foola clase padre se llama. Dado que Fooes una clase de estilo nuevo estándar, la clase padre es la typemetaclase, por lo que type's __call__()se invoca el método.
  • Ese __call__()método a su vez invoca lo siguiente:
    • __new__()
    • __init__()
Si Foono define __new__()__init__(), los métodos predeterminados se heredan de Foosu ascendencia. Pero si Foodefine estos métodos, anulan los de la ascendencia, lo que permite un comportamiento personalizado al crear instancias Foo.
A continuación, new()se define y asigna un método personalizado llamado como el __new__()método para Foo:
>>> def  new ( cls ): 
...     x  =  objeto . __new__ ( cls ) 
...     x . attr  =  100 
...     devuelve  x 
... 
>>> Foo . __new__  =  new

>>> f  =  Foo () 
>>> f . attr 
100

>>> g  =  Foo () 
>>> g . attr 
100
Esto modifica el comportamiento de instanciación de la clase Foo: cada vez que Foose crea una instancia de , de forma predeterminada se inicializa con un atributo llamado attr, que tiene un valor de 100(Este tipo de código aparecería más habitualmente en el __init__()método y no en el típico __new__(). Este ejemplo está ideado para fines de demostración).
Ahora, como ya se ha reiterado, las clases también son objetos. Supongamos que desea personalizar de forma similar el comportamiento de creación de instancias al crear una clase como FooSi fuera a seguir el patrón anterior, definiría nuevamente un método personalizado y lo asignaría como el __new__()método para la clase de la cual Fooes una instancia. Fooes una instancia de la typemetaclase, por lo que el código se ve más o menos así:
# Alerta de spoiler: ¡Esto no funciona! 
>>> def  new ( cls ): 
...     x  =  tipo . __new__ ( cls ) 
...     x . attr  =  100 
...     devuelve  x 
... 
>>> tipo . __new__  =  nueva 
Rastreo (llamada más reciente pasado): 
  File "<pyshell # 77>" , línea 1 , en <módulo> 
    Tipo . __new__  =  new 
TypeError :no puede establecer atributos de tipo de extensión / incorporado 'tipo'
Excepto que, como puede ver, no puede reasignar el __new__()método de la typemetaclase. Python no lo permite.
Esto probablemente sea igual de bueno. typees la metaclase de la que se derivan todas las clases de estilo nuevo. Realmente no deberías estar jugando con eso de todos modos. Pero, ¿qué recurso hay allí, si desea personalizar la creación de instancias de una clase?
Una posible solución es una metaclase personalizada. Esencialmente, en lugar de perder el tiempo con la typemetaclase, puedes definir tu propia metaclase, de la cual deriva type, y luego puedes jugar con ella en su lugar.
El primer paso es definir una metaclase que se deriva de type, de la siguiente manera:
>>> clase  Meta ( tipo ): 
...     def  __new__ ( cls ,  nombre ,  bases ,  dct ): 
...         x  =  super () . __new__ ( cls ,  nombre ,  bases ,  dct ) 
...         x . attr  =  100 
...         devolver  x 
...
El encabezado de definición class Meta(type):especifica de dónde se Metaderiva typeDado que typees una metaclase, también Metacrea una metaclase.
Tenga en cuenta que __new__()se ha definido un método personalizado para MetaNo fue posible hacer eso en la typemetaclase directamente. El __new__()método hace lo siguiente:
  • Los delegados viajan super()al __new__()método de la metaclase padre ( type) para crear una nueva clase
  • Asigna el atributo personalizado attra la clase, con un valor de100
  • Devuelve la clase recién creada
Ahora la otra mitad del vudú: defina una nueva clase Fooy especifique que su metaclase es la metaclase personalizada Meta, en lugar de la metaclase estándar typeEsto se hace usando la metaclasspalabra clave en la definición de clase de la siguiente manera:
>>> clase  Foo ( metaclass = Meta ): 
...     pasar 
... 
>>> Foo . attr 
100
Voila! Fooha recogido el attratributo automáticamente de la Metametaclase. Por supuesto, cualquier otra clase que defina de manera similar hará lo mismo:
>>> clase  Bar ( metaclass = Meta ): 
...     pasar 
... 
>>> clase  Qux ( metaclass = Meta ): 
...     pasar 
... 
>>> Bar . attr ,  Qux . attr 
(100, 100)
De la misma manera que una clase funciona como una plantilla para la creación de objetos, una metaclase funciona como una plantilla para la creación de clases. Las metaclases a veces se denominan fábricas de clase .
Compare los siguientes dos ejemplos:
Fábrica de Objetos:
>>> clase  Foo : 
...     def  __init__ ( self ): 
...         self . attr  =  100 
...

>>> x  =  Foo () 
>>> x . attr 
100

>>> y  =  Foo () 
>>> y . attr 
100

>>> z  =  Foo () 
>>> z . attr 
100
Fábrica de clase:
>>> clase  Meta ( tipo ): 
...     def  __init__ ( 
...         cls ,  nombre ,  bases ,  dct 
...     ): 
...         cls . attr  =  100 
... 
>>> clase  X ( metaclase = Meta ): 
...     pasar 
... 
>>> X . attr 
100

>>> clase  Y ( metaclase = Meta ): 
...     pasar 
... 
>>> Y . attr 
100

>>> clase  Z ( metaclase = Meta ): 
...     pasar 
... 
>>> Z . attr 
100

¿Es esto realmente necesario?

Tan simple como el ejemplo de fábrica de clase anterior es, es la esencia de cómo funcionan las metaclases. Permiten la personalización de la creación de instancias de clase.
Aún así, esto es mucho alboroto solo para otorgar el atributo personalizado attren cada clase recién creada. ¿Realmente necesitas una metaclase solo para eso?
En Python, hay al menos otras dos maneras en las que efectivamente se puede lograr lo mismo:
Herencia simple:
>>> clase  Base : 
...     attr  =  100 
...

>>> clase  X ( Base ): 
...     pase 
...

>>> clase  Y ( Base ): 
...     pase 
...

>>> clase  Z ( Base ): 
...     pase 
...

>>> X . attr 
100 
>>> Y . attr 
100 
>>> Z . attr 
100
Decorador de clase:
>>> def  decorator ( cls ): 
...     clase  NewClass ( cls ): 
...         attr  =  100 
...     regresa  NewClass 
... 
>>> @decorator 
... clase  X : 
...     pasa 
... 
>>> @decorator 
... clase  Y : 
...     pasar 
... 
>>> @decorator 
... clase  Z : 
...     pasar 
...

>>> X . attr 
100 
>>> Y . attr 
100 
>>> Z . attr 
100
Recuerda que puedes Aprender Python de manera fácil aquí en Mi Diario Python.
Seguir Leyendo
Powered by Blogger .