oop __getitem__ __dict__ - ¿Qué son las metaclases en Python?




7 Answers

Clases como objetos

Antes de comprender las metaclases, necesitas dominar las clases en Python. Y Python tiene una idea muy peculiar de qué clases son, tomadas del lenguaje Smalltalk.

En la mayoría de los idiomas, las clases son solo piezas de código que describen cómo producir un objeto. Eso también es cierto en Python:

>>> class ObjectCreator(object):
...       pass
...

>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

Pero las clases son más que eso en Python. Las clases también son objetos.

Sí, objetos.

Tan pronto como usa la class palabra clave, Python la ejecuta y crea un OBJETO. La instrucción

>>> class ObjectCreator(object):
...       pass
...

crea en memoria un objeto con el nombre "ObjectCreator".

Este objeto (la clase) es capaz de crear objetos (las instancias), y es por eso que es una clase .

Pero aún así, es un objeto, y por lo tanto:

  • puedes asignarlo a una variable
  • puedes copiarlo
  • puedes agregarle atributos
  • Puedes pasarlo como parámetro de función.

p.ej:

>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

Creando clases dinámicamente

Dado que las clases son objetos, puede crearlos sobre la marcha, como cualquier objeto.

Primero, puedes crear una clase en una función usando la class :

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # return the class, not an instance
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

Pero no es tan dinámico, ya que todavía tienes que escribir toda la clase.

Como las clases son objetos, deben ser generadas por algo.

Cuando usas la palabra clave de class , Python crea este objeto automáticamente. Pero como con la mayoría de las cosas en Python, te da una forma de hacerlo manualmente.

¿Recuerdas el type función? La buena función antigua que te permite saber qué tipo de objeto es:

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

Bueno, el type tiene una habilidad completamente diferente, también puede crear clases sobre la marcha. type puede tomar la descripción de una clase como parámetros y devolver una clase.

(Lo sé, es una tontería que la misma función pueda tener dos usos completamente diferentes según los parámetros que le pases. Es un problema debido a la compatibilidad con versiones anteriores en Python)

type funciona de esta manera:

type(name of the class,
     tuple of the parent class (for inheritance, can be empty),
     dictionary containing attributes names and values)

p.ej:

>>> class MyShinyClass(object):
...       pass

Se puede crear manualmente de esta manera:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

Notarás que usamos "MyShinyClass" como el nombre de la clase y como la variable para mantener la referencia de la clase. Pueden ser diferentes, pero no hay razón para complicar las cosas.

type acepta un diccionario para definir los atributos de la clase. Asi que:

>>> class Foo(object):
...       bar = True

Se puede traducir a:

>>> Foo = type('Foo', (), {'bar':True})

Y usado como una clase normal:

>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

Y, por supuesto, puedes heredar de él, así que:

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

sería:

>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True

Eventualmente querrá agregar métodos a su clase. Simplemente defina una función con la firma adecuada y asígnele un atributo.

>>> def echo_bar(self):
...       print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

Y puede agregar incluso más métodos después de crear dinámicamente la clase, al igual que agregar métodos a un objeto de clase creado normalmente.

>>> def echo_bar_more(self):
...       print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

Usted ve a dónde vamos: en Python, las clases son objetos, y puede crear una clase sobre la marcha, dinámicamente.

Esto es lo que hace Python cuando usa la class palabra clave, y lo hace usando una metaclase.

¿Qué son las metaclases (finalmente)?

Las metaclases son las "cosas" que crean clases.

Definís clases para crear objetos, ¿verdad?

Pero aprendimos que las clases de Python son objetos.

Bueno, las metaclases son las que crean estos objetos. Son las clases de las clases, puedes verlas de esta manera:

MyClass = MetaClass()
my_object = MyClass()

Has visto que el type te permite hacer algo como esto:

MyClass = type('MyClass', (), {})

Es porque el type función es de hecho una metaclase. type es la metaclase que Python usa para crear todas las clases detrás de escena.

Ahora se pregunta por qué diablos está escrito en minúsculas, y no en Type ?

Bueno, supongo que es una cuestión de coherencia con str , la clase que crea objetos de cadenas, y int la clase que crea objetos de enteros. type es solo la clase que crea objetos de clase.

Usted ve eso al verificar el atributo __class__ .

Todo, y lo digo todo, es un objeto en Python. Eso incluye ints, cadenas, funciones y clases. Todos ellos son objetos. Y todos ellos han sido creados a partir de una clase:

>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

Ahora, ¿cuál es el __class__ de cualquier __class__ ?

>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

Entonces, una metaclase es solo lo que crea objetos de clase.

Puedes llamarlo 'fábrica de clase' si lo deseas.

type es la metaclase incorporada que utiliza Python, pero por supuesto, puede crear su propia metaclase.

El atributo __metaclass__

En Python 2, puede agregar un atributo __metaclass__ cuando escribe una clase (vea la siguiente sección para la sintaxis de Python 3):

class Foo(object):
    __metaclass__ = something...
    [...]

Si lo haces, Python usará la metaclase para crear la clase Foo .

Cuidado, es complicado.

Primero escribe la class Foo(object) , pero el objeto de clase Foo no se ha creado en la memoria.

Python buscará __metaclass__ en la definición de la clase. Si lo encuentra, lo usará para crear la clase de objeto Foo . Si no lo hace, usará type para crear la clase.

Lea eso varias veces.

Cuando tu lo hagas:

class Foo(Bar):
    pass

Python hace lo siguiente:

¿Hay un atributo __metaclass__ en Foo ?

Si es así, cree en la memoria un objeto de clase (dije un objeto de clase, quédese conmigo aquí), con el nombre Foo usando lo que está en __metaclass__ .

Si Python no puede encontrar __metaclass__ , buscará una __metaclass__ en el nivel MÓDULO e intentará hacer lo mismo (pero solo para las clases que no heredan nada, básicamente las clases de estilo antiguo).

Luego, si no puede encontrar ninguna __metaclass__ , utilizará la propia metaclase de la Bar (el primer padre) (que podría ser el type predeterminado) para crear el objeto de la clase.

Tenga cuidado aquí de que el atributo __metaclass__ no se heredará, la metaclase del padre ( Bar.__class__ ) será. Si Bar usó un atributo __metaclass__ que creó Bar con type() (y no el type.__new__() ), las subclases no heredarán ese comportamiento.

Ahora la gran pregunta es, ¿qué puedes poner en __metaclass__ ?

La respuesta es: algo que puede crear una clase.

¿Y qué puede crear una clase? type , o cualquier cosa que las subclases o lo use.

Metaclases en Python 3

La sintaxis para establecer la metaclase se ha cambiado en Python 3:

class Foo(object, metaclass=something):
    [...]

es decir, el atributo __metaclass__ ya no se usa, a favor de un argumento de palabra clave en la lista de clases base.

Sin embargo, el comportamiento de las metaclases sigue siendo en gran medida el mismo .

Metaclases personalizados

El propósito principal de una metaclase es cambiar la clase automáticamente, cuando se crea.

Normalmente haces esto para las API, donde quieres crear clases que coincidan con el contexto actual.

Imagine un ejemplo estúpido, donde decide que todas las clases en su módulo deberían tener sus atributos escritos en mayúsculas. Hay varias formas de hacer esto, pero una forma es establecer __metaclass__ en el nivel del módulo.

De esta manera, todas las clases de este módulo se crearán usando esta metaclase, y solo tenemos que decirle a la metaclase que convierta todos los atributos en mayúsculas.

Afortunadamente, __metaclass__ puede ser realmente invocable, no es necesario que sea una clase formal (lo sé, algo con 'clase' en su nombre no tiene que ser una clase, imagínate ... pero es útil).

Entonces comenzaremos con un ejemplo simple, usando una función.

# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
    """
      Return a class object, with the list of its attribute turned
      into uppercase.
    """

    # pick up any attribute that doesn't start with '__' and uppercase it
    uppercase_attr = {}
    for name, val in future_class_attr.items():
        if not name.startswith('__'):
            uppercase_attr[name.upper()] = val
        else:
            uppercase_attr[name] = val

    # let `type` do the class creation
    return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # this will affect all classes in the module

class Foo(): # global __metaclass__ won't work with "object" though
    # but we can define __metaclass__ here instead to affect only this class
    # and this will work with "object" children
    bar = 'bip'

print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True

f = Foo()
print(f.BAR)
# Out: 'bip'

Ahora, hagamos exactamente lo mismo, pero usando una clase real para una metaclase:

# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
    # __new__ is the method called before __init__
    # it's the method that creates the object and returns it
    # while __init__ just initializes the object passed as parameter
    # you rarely use __new__, except when you want to control how the object
    # is created.
    # here the created object is the class, and we want to customize it
    # so we override __new__
    # you can do some stuff in __init__ too if you wish
    # some advanced use involves overriding __call__ as well, but we won't
    # see this
    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type(future_class_name, future_class_parents, uppercase_attr)

Pero esto no es realmente OOP. Llamamos a type directamente y no __new__ ni llamamos a los padres __new__ . Vamos a hacerlo:

class UpperAttrMetaclass(type):

    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        # reuse the type.__new__ method
        # this is basic OOP, nothing magic in there
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)

Es posible que hayas notado el argumento adicional upperattr_metaclass . No tiene nada de especial: __new__ siempre recibe la clase en la que está definido, como primer parámetro. Al igual que usted tiene self para los métodos comunes que reciben la instancia como primer parámetro, o la clase definitoria para los métodos de clase.

Por supuesto, los nombres que utilicé aquí son largos por razones de claridad, pero como self , todos los argumentos tienen nombres convencionales. Así que una metaclase de producción real se vería así:

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type.__new__(cls, clsname, bases, uppercase_attr)

Podemos hacerlo aún más limpio utilizando super , lo que facilitará la herencia (porque sí, puede tener metaclases, heredar de metaclases, heredar de tipo):

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

Eso es. Realmente no hay nada más sobre metaclases.

La razón detrás de la complejidad del código que utilizan las metaclases no se debe a las metaclases, sino porque usualmente se usan metaclases para hacer cosas retorcidas que dependen de la introspección, la manipulación de la herencia, vars como __dict__ , etc.

De hecho, las metaclases son especialmente útiles para hacer magia negra, y por lo tanto cosas complicadas. Pero por sí mismos, son simples:

  • interceptar una creación de clase
  • modificar la clase
  • devolver la clase modificada

¿Por qué usarías clases de metaclases en lugar de funciones?

Ya que __metaclass__ puede aceptar cualquier llamada, ¿por qué usaría una clase ya que obviamente es más complicado?

Hay varias razones para hacerlo:

  • La intención es clara. Cuando lees UpperAttrMetaclass(type) , sabes lo que va a seguir
  • Puedes usar OOP. Metaclase puede heredar de metaclase, anular métodos primarios. Las metaclases pueden incluso utilizar metaclases.
  • Las subclases de una clase serán instancias de su metaclase si ha especificado una clase de metaclase, pero no con una función de metaclase.
  • Puedes estructurar mejor tu código. Nunca use metaclases para algo tan trivial como el ejemplo anterior. Por lo general es para algo complicado. Tener la capacidad de hacer varios métodos y agruparlos en una clase es muy útil para hacer que el código sea más fácil de leer.
  • Puedes enganchar a __new__ , __init__ y __call__ . Lo que te permitirá hacer cosas diferentes. Incluso si normalmente puedes hacerlo todo en __new__ , algunas personas se sienten más cómodas usando __init__ .
  • Estas se llaman metaclases, maldita sea! ¡Debe significar algo!

¿Por qué usarías metaclases?

Ahora la gran pregunta. ¿Por qué usaría alguna característica oscura propensa a errores?

Bueno, normalmente no lo haces

Las metaclases son una magia más profunda que el 99% de los usuarios nunca debe preocuparse. Si se pregunta si los necesita, no lo hace (las personas que realmente los necesitan saben con certeza que los necesitan y no necesitan una explicación de por qué).

Python Guru Tim Peters

El caso de uso principal de una metaclase es crear una API. Un ejemplo típico de esto es el Django ORM.

Te permite definir algo como esto:

class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

Pero si haces esto:

guy = Person(name='bob', age='35')
print(guy.age)

No devolverá un objeto IntegerField . int un int , e incluso puede tomarlo directamente de la base de datos.

Esto es posible porque models.Model define __metaclass__ y usa algo de magia que convertirá a la Person que acaba de definir con declaraciones simples en un enlace complejo a un campo de base de datos.

Django hace que algo complejo parezca simple al exponer una API simple y al usar metaclases, recreando el código de esta API para hacer el trabajo real entre bambalinas.

La última palabra

Primero, sabes que las clases son objetos que pueden crear instancias.

Bueno, de hecho, las clases son ellas mismas instancias. De metaclases

>>> class Foo(object): pass
>>> id(Foo)
142630324

Todo es un objeto en Python, y todos son instancias de clases o instancias de metaclases.

Excepto por el type .

type es en realidad su propia metaclase. Esto no es algo que se pueda reproducir en Python puro, y se hace haciendo trampa un poco en el nivel de implementación.

En segundo lugar, las metaclases son complicadas. Es posible que no desee utilizarlos para modificaciones de clase muy simples. Puedes cambiar de clase usando dos técnicas diferentes:

El 99% del tiempo que necesita una alteración de clase, es mejor usarlos.

Pero el 98% de las veces, no necesitas ninguna alteración de clase.

__hash__ __eq__

¿Qué son las metaclases y para qué las usamos?




Un uso para las metaclases es agregar nuevas propiedades y métodos a una instancia automáticamente.

Por ejemplo, si nos fijamos en los modelos Django , su definición parece un poco confusa. Parece como si solo estuvieras definiendo propiedades de clase:

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

Sin embargo, en tiempo de ejecución, los objetos Person se llenan con todo tipo de métodos útiles. Vea la source de algunos metaclassery asombrosos.




Creo que la introducción SOLO a la programación de metaclase está bien escrita y ofrece una muy buena introducción al tema a pesar de que ya tiene varios años.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html (archivado en https://web.archive.org/web/20080206005253/http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html )

En resumen: una clase es un plano para la creación de una instancia, una metaclase es un plano para la creación de una clase. Se puede ver fácilmente que en Python las clases también deben ser objetos de primera clase para habilitar este comportamiento.

Nunca he escrito uno, pero creo que uno de los mejores usos de las metaclases se puede ver en el marco de Django . Las clases modelo utilizan un enfoque de metaclase para habilitar un estilo declarativo de escritura de nuevos modelos o clases de formulario. Mientras la metaclase crea la clase, todos los miembros tienen la posibilidad de personalizar la clase en sí.

Lo que queda por decir es: si no sabe qué son las metaclases, la probabilidad de que no las necesite es del 99%.




Actualización de Python 3

Hay (en este punto) dos métodos clave en una metaclase:

  • __prepare__ y
  • __new__

__prepare__le permite proporcionar una asignación personalizada (como una OrderedDict) para usarla como espacio de nombres mientras se crea la clase. Debe devolver una instancia del espacio de nombres que elija. Si no se implementa se usa __prepare__un normal dict.

__new__ es responsable de la creación / modificación real de la clase final.

Una metaclase básica, de no hacer nada extra le gustaría:

class Meta(type):

    def __prepare__(metaclass, cls, bases):
        return dict()

    def __new__(metacls, cls, bases, clsdict):
        return super().__new__(metacls, cls, bases, clsdict)

Un ejemplo simple:

Digamos que desea que se ejecute un código de validación simple en sus atributos, como que siempre debe ser un into un str. Sin una metaclase, su clase se vería como:

class Person:
    weight = ValidateType('weight', int)
    age = ValidateType('age', int)
    name = ValidateType('name', str)

Como puede ver, debe repetir el nombre del atributo dos veces. Esto hace posible errores tipográficos junto con insectos irritantes.

Una metaclase simple puede resolver ese problema:

class Person(metaclass=Validator):
    weight = ValidateType(int)
    age = ValidateType(int)
    name = ValidateType(str)

Este es el aspecto que tendría la metaclase (no se usa __prepare__porque no es necesario):

class Validator(type):
    def __new__(metacls, cls, bases, clsdict):
        # search clsdict looking for ValidateType descriptors
        for name, attr in clsdict.items():
            if isinstance(attr, ValidateType):
                attr.name = name
                attr.attr = '_' + name
        # create final class and return it
        return super().__new__(metacls, cls, bases, clsdict)

Una muestra de ejecución de:

p = Person()
p.weight = 9
print(p.weight)
p.weight = '9'

produce:

9
Traceback (most recent call last):
  File "simple_meta.py", line 36, in <module>
    p.weight = '9'
  File "simple_meta.py", line 24, in __set__
    (self.name, self.type, value))
TypeError: weight must be of type(s) <class 'int'> (got '9')

Nota : este ejemplo es lo suficientemente simple, también podría haberse logrado con un decorador de clase, pero probablemente una metaclase real estaría haciendo mucho más.

La clase 'ValidateType' para referencia:

class ValidateType:
    def __init__(self, type):
        self.name = None  # will be set by metaclass
        self.attr = None  # will be set by metaclass
        self.type = type
    def __get__(self, inst, cls):
        if inst is None:
            return self
        else:
            return inst.__dict__[self.attr]
    def __set__(self, inst, value):
        if not isinstance(value, self.type):
            raise TypeError('%s must be of type(s) %s (got %r)' %
                    (self.name, self.type, value))
        else:
            inst.__dict__[self.attr] = value



Una metaclase es una clase que dice cómo (otra) clase se debe crear.

Este es un caso en el que veía la metaclase como una solución a mi problema: tenía un problema realmente complicado, que probablemente podría haberse resuelto de manera diferente, pero elegí resolverlo utilizando una metaclase. Debido a la complejidad, es uno de los pocos módulos que he escrito donde los comentarios en el módulo superan la cantidad de código que se ha escrito. Aquí está...

#!/usr/bin/env python

# Copyright (C) 2013-2014 Craig Phillips.  All rights reserved.

# This requires some explaining.  The point of this metaclass excercise is to
# create a static abstract class that is in one way or another, dormant until
# queried.  I experimented with creating a singlton on import, but that did
# not quite behave how I wanted it to.  See now here, we are creating a class
# called GsyncOptions, that on import, will do nothing except state that its
# class creator is GsyncOptionsType.  This means, docopt doesn't parse any
# of the help document, nor does it start processing command line options.
# So importing this module becomes really efficient.  The complicated bit
# comes from requiring the GsyncOptions class to be static.  By that, I mean
# any property on it, may or may not exist, since they are not statically
# defined; so I can't simply just define the class with a whole bunch of
# properties that are @property @staticmethods.
#
# So here's how it works:
#
# Executing 'from libgsync.options import GsyncOptions' does nothing more
# than load up this module, define the Type and the Class and import them
# into the callers namespace.  Simple.
#
# Invoking 'GsyncOptions.debug' for the first time, or any other property
# causes the __metaclass__ __getattr__ method to be called, since the class
# is not instantiated as a class instance yet.  The __getattr__ method on
# the type then initialises the class (GsyncOptions) via the __initialiseClass
# method.  This is the first and only time the class will actually have its
# dictionary statically populated.  The docopt module is invoked to parse the
# usage document and generate command line options from it.  These are then
# paired with their defaults and what's in sys.argv.  After all that, we
# setup some dynamic properties that could not be defined by their name in
# the usage, before everything is then transplanted onto the actual class
# object (or static class GsyncOptions).
#
# Another piece of magic, is to allow command line options to be set in
# in their native form and be translated into argparse style properties.
#
# Finally, the GsyncListOptions class is actually where the options are
# stored.  This only acts as a mechanism for storing options as lists, to
# allow aggregation of duplicate options or options that can be specified
# multiple times.  The __getattr__ call hides this by default, returning the
# last item in a property's list.  However, if the entire list is required,
# calling the 'list()' method on the GsyncOptions class, returns a reference
# to the GsyncListOptions class, which contains all of the same properties
# but as lists and without the duplication of having them as both lists and
# static singlton values.
#
# So this actually means that GsyncOptions is actually a static proxy class...
#
# ...And all this is neatly hidden within a closure for safe keeping.
def GetGsyncOptionsType():
    class GsyncListOptions(object):
        __initialised = False

    class GsyncOptionsType(type):
        def __initialiseClass(cls):
            if GsyncListOptions._GsyncListOptions__initialised: return

            from docopt import docopt
            from libgsync.options import doc
            from libgsync import __version__

            options = docopt(
                doc.__doc__ % __version__,
                version = __version__,
                options_first = True
            )

            paths = options.pop('<path>', None)
            setattr(cls, "destination_path", paths.pop() if paths else None)
            setattr(cls, "source_paths", paths)
            setattr(cls, "options", options)

            for k, v in options.iteritems():
                setattr(cls, k, v)

            GsyncListOptions._GsyncListOptions__initialised = True

        def list(cls):
            return GsyncListOptions

        def __getattr__(cls, name):
            cls.__initialiseClass()
            return getattr(GsyncListOptions, name)[-1]

        def __setattr__(cls, name, value):
            # Substitut option names: --an-option-name for an_option_name
            import re
            name = re.sub(r'^__', "", re.sub(r'-', "_", name))
            listvalue = []

            # Ensure value is converted to a list type for GsyncListOptions
            if isinstance(value, list):
                if value:
                    listvalue = [] + value
                else:
                    listvalue = [ None ]
            else:
                listvalue = [ value ]

            type.__setattr__(GsyncListOptions, name, listvalue)

    # Cleanup this module to prevent tinkering.
    import sys
    module = sys.modules[__name__]
    del module.__dict__['GetGsyncOptionsType']

    return GsyncOptionsType

# Our singlton abstract proxy class.
class GsyncOptions(object):
    __metaclass__ = GetGsyncOptionsType()



La versión tl; dr

La type(obj)función le da el tipo de un objeto.

El type()de una clase es su metaclase .

Para utilizar una metaclase:

class Foo(object):
    __metaclass__ = MyMetaClass



La función type () puede devolver el tipo de un objeto o crear un nuevo tipo,

por ejemplo, podemos crear una clase Hi con la función type () y no necesitamos usarlo de esta manera con la clase Hi (objeto):

def func(self, name='mike'):
    print('Hi, %s.' % name)

Hi = type('Hi', (object,), dict(hi=func))
h = Hi()
h.hi()
Hi, mike.

type(Hi)
type

type(h)
__main__.Hi

Además de usar type () para crear clases dinámicamente, puede controlar el comportamiento de creación de la clase y usar metaclase.

De acuerdo con el modelo de objetos de Python, la clase es el objeto, por lo que la clase debe ser una instancia de otra clase determinada. De forma predeterminada, una clase de Python es una instancia de la clase de tipo. Es decir, el tipo es metaclase de la mayoría de las clases integradas y la metaclase de clases definidas por el usuario.

class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

class CustomList(list, metaclass=ListMetaclass):
    pass

lst = CustomList()
lst.add('custom_list_1')
lst.add('custom_list_2')

lst
['custom_list_1', 'custom_list_2']

La magia tendrá efecto cuando pasamos los argumentos de palabras clave en metaclase, esto indica al intérprete de Python para crear la Lista personalizada mediante ListMetaclass. new (), en este punto, podemos modificar la definición de clase, por ejemplo, y agregar un nuevo método y luego devolver la definición revisada.




Related