Categorías
Python

La comprensión de la biblioteca de objetos de Python Mock

 

Tabla de Contenidos

  • estilo antiguo frente a nuevas clases de estilo ClassesOld-Estilo ClassesNew-Style Clases
  • viejo estilo de las clases
  • nuevo estilo
  • tipo y clase
  • definir una clase DynamicallyExample 1Example 2Example 3Example 4
  • Ejemplo 1

  • Ejemplo 2 Ejemplo 3 Ejemplo
  • 4
  • personalizada metaclases
  • ¿Es esto realmente necesario? Clases
  • Conclusión Clases
  • viejo estilo
  • nuevo estilo
  • Ejemplo 1
  • Ejemplo 2
  • Ejemplo 3
  • Ejemplo 4

El término metaprogramming se refiere a la posibilidad de una programa tenga conocimiento o manipular en sí. Python admite una forma de metaprogramming para las clases llamada metaclases .

metaclases son un concepto de programación orientada a objetos esotéricos, al acecho detrás de prácticamente todo el código Python. Está utilizando ellas si usted es consciente de ello o no. En su mayor parte, no es necesario ser consciente de ello. La mayoría de los programadores de Python rara vez, o nunca, tienen que pensar en metaclases.

Cuando surge la necesidad, sin embargo, Python proporciona una capacidad que no todos los lenguajes orientados a objetos apoyan: se puede obtener bajo el capó y definir metaclases personalizados. El uso de metaclases personalizados es algo controvertido, como lo sugiere la siguiente cita de Tim Peters, el gurú de Python que fue autor el Zen de Python:

“metaclases son más profundas mágica del 99% de los usuarios nunca debe preocuparse. Si usted se pregunta si los necesita, no lo hace (las personas que realmente los necesitan saber con certeza que los necesitan, y no necesitan una explicación acerca de por qué) “.

Tim Peters

Hay Pythonistas (como se conoce a los aficionados de Python) que cree que nunca se debe utilizar metaclases personalizados. Eso podría estar pasando un poco lejos, pero es probable que sea cierto que metaclases personalizados en su mayoría no son necesarios. Si no es bastante obvio que un problema llama a ellos, entonces es probable que sea más limpio y más fácil de leer si resuelto de una manera más simple.

Sin embargo, la comprensión de metaclases Python es que vale la pena, ya que conduce a una mejor comprensión de los detalles internos de las clases de Python en general. Nunca se sabe: usted puede encontrar un día en una de esas situaciones en las que acaba de saber que una metaclase costumbre es lo que quiere.

Informarte: no se pierda el seguimiento a este tutorial-Clic aquí para unirse al Boletín pitón real y sabrá cuando la próxima entrega sale.

estilo antiguo frente a las clases de nuevo estilo

En el reino del pitón, una clase puede ser una de dos variedades. Sin terminología oficial se ha decidido, por lo que se conocen informalmente como clases de estilo antiguo y nuevo estilo. Clases

viejo estilo

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 desde un único tipo incorporado llamado instancia. Si obj es una instancia de una clase de estilo antiguo, obj .__ class__ designa la clase, pero el tipo (obj) es siempre ejemplo. El siguiente ejemplo está tomado de Python 2.7: Clases

>>> class Foo:
... pass
...
>>> x = Foo()
>>> x.__class__

>>> type(x)

nuevo estilo

clases de estilo Nueva unifican los conceptos de clase y tipo. Si obj es una instancia de una clase de nuevo estilo, tipo (obj) es el mismo que el obj .__ class__:

>>> class Foo:
... pass
>>> obj = Foo()
>>> obj.__class__

>>> type(obj)

>>> obj.__class__ is type(obj)
True
>>> n = 5
>>> d = { 'x' : 1, 'y' : 2 }

>>> class Foo:
... pass
...
>>> x = Foo()

>>> for obj in (n, d, x):
... print(type(obj) is obj.__class__)
...
True
True
True

tipo y clase

En Python 3, todas las clases son las clases de nuevo estilo. Así, en Python 3 es razonable para referirse a un tipo de objeto y su clase de manera intercambiable.

Nota: En Python 2, las clases son al viejo estilo por defecto. Antes de Python 2.2, las clases de nuevo estilo no fueron soportados en absoluto. A partir de Python 2.2 en adelante, se pueden crear, pero deben ser declaradas explícitamente como nuevo estilo.

Recuerde que, en Python, todo es un objeto. Las clases son objetos también. Como resultado, una clase debe tener un tipo. ¿Cuál es el tipo de una clase?

considerar lo siguiente:

>>> class Foo:
... pass
...
>>> x = Foo()

>>> type(x)

>>> type(Foo)

El tipo de x es la clase Foo, como era de esperar. Pero el tipo de Foo, la propia clase, es el tipo. En general, el tipo de cualquier clase de nuevo estilo es tipo.

El tipo de las clases incorporadas que están familiarizados es también de tipo:

>>> for t in int, float, dict, list, tuple:
... print(type(t))
...





Por lo demás, el tipo de tipo es el tipo así (sí, en serio):

>>> type(type)

tipo es una metaclase, de las cuales las clases son instancias. Así como un objeto ordinario es una instancia de una clase, cualquier clase de nuevo estilo en Python, y por lo tanto cualquier clase en Python 3, es una instancia de la metaclase tipo.

En el caso anterior:

  • x es una instancia de la clase Foo.
  • Foo es una instancia de la metaclase tipo. tipo
  • es también una instancia de la metaclase tipo, por lo que es una instancia de sí mismo.

definir una clase dinámicamente

la incorporada en el tipo de función (), cuando se pasa un argumento, devuelve el tipo de un objeto. Para las clases de nuevo estilo, que es generalmente el mismo que el atributo __class__ del objeto:

>>> type(3)

>>> type(['foo', 'bar', 'baz'])

>>> t = (1, 2, 3, 4, 5)
>>> type(t)

>>> class Foo:
... pass
...
>>> type(Foo())

También puede escribir la llamada () con tres argumentos de tipo (, , ):

  • < name> especifica el nombre de la clase. Esto se convierte en el atributo __name__ de la clase.
  • especifica una tupla de las clases base de que los hereda de la clase. Esto se convierte en el atributo y__bases__ de la clase.
  • especifica un espacio de nombres diccionario que contiene definiciones para el cuerpo de la clase. Esto se convierte en el atributo __dict__ de la clase.

tipo de llamada () de esta forma crea una nueva instancia de la metaclase tipo. En otras palabras, se crea dinámicamente una nueva clase.

En cada uno de los siguientes ejemplos, el fragmento de la parte superior define una clase de forma dinámica con el tipo (), mientras que el siguiente fragmento de código que define la clase de la forma habitual, con la declaración de clase. En cada caso, los dos fragmentos son funcionalmente equivalentes.

Ejemplo 1

En este primer ejemplo, los y argumentos pasados ​​con el tipo () son ambos vacía. Sin herencia de cualquier clase padre se especifica, y nada se coloca inicialmente en el diccionario de espacio de nombres. Esto es posible la definición de clase más simple: Ejemplo

>>> Foo = type('Foo', (), {})

>>> x = Foo()
>>> x
<__main__.Foo object at 0x04CFAD50>
>>> class Foo:
... pass
...
>>> x = Foo()
>>> x
<__main__.Foo object at 0x0370AD50>

2

Aquí, es una tupla con un solo elemento Foo, especificando la clase padre que Bar hereda de. Un atributo, attr, se coloca inicialmente en el diccionario de espacio de nombres: Ejemplo

>>> Bar = type('Bar', (Foo,), dict(attr=100))

>>> x = Bar()
>>> x.attr
100
>>> x.__class__

>>> x.__class__.__bases__
(,)
>>> class Bar(Foo):
... attr = 100
...

>>> x = Bar()
>>> x.attr
100
>>> x.__class__

>>> x.__class__.__bases__
(,)

3

Esta vez, está de nuevo vacía. Dos objetos se colocan en el diccionario de espacio de nombres a través de la argumento. El primero es un attr nombre de atributo y la función de una segunda llamada attr_val, que se convierte en un método de la clase definida: Ejemplo

>>> Foo = type(
... 'Foo',
... (),
... {
... 'attr': 100,
... 'attr_val': lambda x : x.attr
... }
... )

>>> x = Foo()
>>> x.attr
100
>>> x.attr_val()
100
>>> class Foo:
... attr = 100
... def attr_val(self):
... return self.attr
...

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

4

funciones sólo muy simples se pueden definir con lambda en Python. En el siguiente ejemplo, una función ligeramente más complejo está definida externamente entonces asignado a attr_val en el diccionario de espacio de nombres a través del nombre f:

>>> def f(obj):
... print('attr =', obj.attr)
...
>>> Foo = type(
... 'Foo',
... (),
... {
... 'attr': 100,
... 'attr_val': f
... }
... )

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

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

Custom metaclases

Consideremos de nuevo este ejemplo muy gastado:

>>> class Foo:
... pass
...
>>> f = Foo()

La expresión Foo () crea una nueva instancia de la clase Foo. Cuando el intérprete se encuentra con Foo (), ocurre lo siguiente:

  • El __call __ método () de la clase de matriz de Foo se llama. Desde Foo es una clase de estilo nuevo estándar, la clase padre es el tipo metaclase, por lo __call de tipo __ se invoca el método ().
  • Ese método __call __ () invoca a su vez lo siguiente: __ nueva __ __ () init __ ()
  • __new __ ()
  • __init __ ()

El __call __ () método de la clase padre de Foo se llama. Desde Foo es una clase de estilo nuevo estándar, la clase padre es el tipo metaclase, por lo __call de tipo __ se invoca el método ().

Ese método __call __ () invoca a su vez lo siguiente:

  • __new __ ()
  • __init __ ()

Si Foo no define __new __ () y __init __ (), métodos predeterminados se heredan de la ascendencia de Foo. Pero si Foo Cómo define estos métodos, se anulan los de la ascendencia, que permite crear instancias de comportamiento personalizado cuando Foo.

A continuación, un método personalizado llamada nueva () está definido y asignado como el método __new __ () para Foo:

>>> def new(cls):
... x = object.__new__(cls)
... x.attr = 100
... return x
...
>>> Foo.__new__ = new

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

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

esto modifica el comportamiento de instancias de la clase Foo: cada vez que se crea una instancia de Foo, por defecto se inicializa con un atributo llamado attr, que tiene un valor de 100. (código como este sería más suelen aparecer en el __init __ () método, y no típicamente en __new __ (). este ejemplo se ideó para fines de demostración.)

Ahora, como ya se ha reiterado, las clases son objetos también. Suponga que desea personalizar de manera similar el comportamiento de instancias al crear una clase como Foo. Si se va a seguir el patrón anterior, debería volver define un método personalizado y se asigna como el método __new __ () de la clase de los cuales Foo es una instancia. Foo es una instancia de la metaclase tipo, por lo que la apariencia del código de algo como esto:

# Spoiler alert: This doesn't work!
>>> def new(cls):
... x = type.__new__(cls)
... x.attr = 100
... return x
...
>>> type.__new__ = new
Traceback (most recent call last):
File "", line 1, in
type.__new__ = new
TypeError: can't set attributes of built-in/extension type 'type'

Excepto, como se puede ver, no se puede reasignar el método __new __ () de la metaclase tipo. Python no lo permite.

Esta es probablemente igual de bien. tipo es el metaclase del cual se derivan todas las clases de nuevo estilo. En realidad, no debería estar al rededor de todos modos. Pero entonces ¿qué recurso está ahí, si desea personalizar creación de instancias de una clase?

Una posible solución es una metaclase personalizado. En esencia, en lugar de al rededor de la metaclase tipo, puede definir su propio metaclase, que deriva del tipo, y luego se puede ensuciar con ese lugar.

El primer paso es definir una metaclase que se deriva de tipo, como sigue:

>>> class Meta(type):
... def __new__(cls, name, bases, dct):
... x = super().__new__(cls, name, bases, dct)
... x.attr = 100
... return x
...

La clase de cabecera de definición Meta (tipo): especifica que deriva Meta de tipo. Dado que el tipo es una metaclase, que hace una metaclase Meta también.

Tenga en cuenta que una costumbre método __new __ () se ha definido para el Meta. No fue posible hacer eso a la metaclase tipo directamente. El método __new __ () hace lo siguiente:

  • delegados a través de super () al método __new __ () de la metaclase matriz (tipo) para realmente crear una nueva clase
  • asigna el atributo personalizado attr a la clase, con un valor de 100
  • Devuelve la clase recién creada

Ahora la otra mitad del vudú: definir una nueva clase Foo y especificar que su metaclase es metaclase la costumbre Meta, en lugar del tipo metaclase estándar. Esto se hace usando la metaclase palabra clave en la definición de clase de la siguiente manera:

>>> class Foo(metaclass=Meta):
... pass
...
>>> Foo.attr
100

Voila! Foo ha recogido el atributo attr automáticamente de la metaclase Meta. Por supuesto, cualquier otra clase que se definen de manera similar harán lo mismo:

>>> class Bar(metaclass=Meta):
... pass
...
>>> class Qux(metaclass=Meta):
... pass
...
>>> Bar.attr, Qux.attr
(100, 100)

De la misma manera que una clase funciona como una plantilla para la creación de objetos, unas funciones MetaClass como una plantilla para la creación de clases. Metaclases se refieren a veces como generadores de clases.

Comparar los dos ejemplos siguientes:

objeto de fábrica:

>>> class Foo:
... def __init__(self):
... self.attr = 100
...

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

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

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

clase de fábrica:

>>> class Meta(type):
... def __init__(
... cls, name, bases, dct
... ):
... cls.attr = 100
...
>>> class X(metaclass=Meta):
... pass
...
>>> X.attr
100

>>> class Y(metaclass=Meta):
... pass
...
>>> Y.attr
100

>>> class Z(metaclass=Meta):
... pass
...
>>> Z.attr
100

Es esto realmente necesario?

Tan simple como el ejemplo anterior es generador de clases, es la esencia de cómo funcionan las metaclases. Que permiten la personalización de instancias de clase.

embargo, esta es una gran cantidad de esfuerzo acaba de otorgar el attr atributo personalizado en cada clase recién creada. ¿Usted realmente necesita una metaclase sólo por eso?

En Python, hay por lo menos un par de otras maneras en que efectivamente lo mismo se puede lograr:

herencia simple:

>>> class Base:
... attr = 100
...

>>> class X(Base):
... pass
...

>>> class Y(Base):
... pass
...

>>> class Z(Base):
... pass
...

>>> X.attr
100
>>> Y.attr
100
>>> Z.attr
100

clase decoradora:

>>> def decorator(cls):
... class NewClass(cls):
... attr = 100
... return NewClass
...
>>> @decorator
... class X:
... pass
...
>>> @decorator
... class Y:
... pass
...
>>> @decorator
... class Z:
... pass
...

>>> X.attr
100
>>> Y.attr
100
>>> Z.attr
100

Conclusión

Como Tim Peters sugiere, metaclases puede fácilmente desviarse hacia el reino de ser una “solución en busca de un problema”. No suele ser necesaria la creación de encargo metaclases . Si el problema en cuestión se puede resolver de una manera más sencilla, lo que probablemente debería ser. Aún así, es beneficioso para entender metaclases para que pueda entender las clases de Python en general y puede reconocer cuando una metaclase realmente es la herramienta apropiada para su uso.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *