En Python, ¿qué son las metaclases y para qué las usamos?

5576
e-satis 19 sep. 2008 a las 10:10

15 respuestas

La mejor respuesta

Una metaclase es la clase de una clase. Una clase define cómo se comporta una instancia de la clase (es decir, un objeto) mientras que una metaclase define cómo se comporta una clase. Una clase es una instancia de una metaclase.

Mientras esté en Python puede usar callables arbitrarios para metaclases (como Jerub muestra), el mejor enfoque es convertirlo en una clase real. type es la metaclase habitual en Python. type es en sí mismo una clase, y es su propio tipo. No podrás recrear algo como type puramente en Python, pero Python hace un poco de trampa. Para crear tu propia metaclase en Python, realmente solo quieres subclasificar type.

Una metaclase se usa más comúnmente como una fábrica de clases. Cuando crea un objeto llamando a la clase, Python crea una nueva clase (cuando ejecuta la instrucción 'clase') llamando a la metaclase. En combinación con los métodos normales __init__ y __new__, las metaclases por lo tanto le permiten hacer 'cosas adicionales' al crear una clase, como registrar la nueva clase con algún registro o reemplazar la clase con algo completamente diferente.

Cuando se ejecuta la instrucción class, Python primero ejecuta el cuerpo de la instrucción class como un bloque de código normal. El espacio de nombres resultante (un dict) contiene los atributos de la futura clase. La metaclase se determina al observar las clases base de la clase a ser (las metaclases se heredan), al atributo __metaclass__ de la clase a ser (si existe) o la variable global __metaclass__ . Luego se llama a la metaclase con el nombre, las bases y los atributos de la clase para instanciarla.

Sin embargo, las metaclases en realidad definen el tipo de una clase, no solo una fábrica, para que pueda hacer mucho más con ellas. Puede, por ejemplo, definir métodos normales en la metaclase. Estos métodos de metaclase son como métodos de clase en el sentido de que se pueden invocar en la clase sin una instancia, pero tampoco son como los métodos de clase en que no se pueden invocar en una instancia de la clase. type.__subclasses__() es un ejemplo de un método en la metaclase type. También puede definir los métodos 'mágicos' normales, como __add__, __iter__ y __getattr__, para implementar o cambiar el comportamiento de la clase.

Aquí hay un ejemplo agregado de los bits y piezas:

def make_hook(f):
    """Decorator to turn 'foo' method into '__foo__'"""
    f.is_hook = 1
    return f

class MyType(type):
    def __new__(mcls, name, bases, attrs):

        if name.startswith('None'):
            return None

        # Go over attributes and see if they should be renamed.
        newattrs = {}
        for attrname, attrvalue in attrs.iteritems():
            if getattr(attrvalue, 'is_hook', 0):
                newattrs['__%s__' % attrname] = attrvalue
            else:
                newattrs[attrname] = attrvalue

        return super(MyType, mcls).__new__(mcls, name, bases, newattrs)

    def __init__(self, name, bases, attrs):
        super(MyType, self).__init__(name, bases, attrs)

        # classregistry.register(self, self.interfaces)
        print "Would register class %s now." % self

    def __add__(self, other):
        class AutoClass(self, other):
            pass
        return AutoClass
        # Alternatively, to autogenerate the classname as well as the class:
        # return type(self.__name__ + other.__name__, (self, other), {})

    def unregister(self):
        # classregistry.unregister(self)
        print "Would unregister class %s now." % self

class MyObject:
    __metaclass__ = MyType


class NoneSample(MyObject):
    pass

# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)

class Example(MyObject):
    def __init__(self, value):
        self.value = value
    @make_hook
    def add(self, other):
        return self.__class__(self.value + other.value)

# Will unregister the class
Example.unregister()

inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()

print inst + inst
class Sibling(MyObject):
    pass

ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__
2757
Cameron Savage 4 mar. 2019 a las 21:34

Aquí hay otro ejemplo de para qué se puede usar:

  • Puede usar metaclass para cambiar la función de su instancia (la clase).
class MetaMemberControl(type):
    __slots__ = ()

    @classmethod
    def __prepare__(mcs, f_cls_name, f_cls_parents,  # f_cls means: future class
                    meta_args=None, meta_options=None):  # meta_args and meta_options is not necessarily needed, just so you know.
        f_cls_attr = dict()
        if not "do something or if you want to define your cool stuff of dict...":
            return dict(make_your_special_dict=None)
        else:
            return f_cls_attr

    def __new__(mcs, f_cls_name, f_cls_parents, f_cls_attr,
                meta_args=None, meta_options=None):

        original_getattr = f_cls_attr.get('__getattribute__')
        original_setattr = f_cls_attr.get('__setattr__')

        def init_getattr(self, item):
            if not item.startswith('_'):  # you can set break points at here
                alias_name = '_' + item
                if alias_name in f_cls_attr['__slots__']:
                    item = alias_name
            if original_getattr is not None:
                return original_getattr(self, item)
            else:
                return super(eval(f_cls_name), self).__getattribute__(item)

        def init_setattr(self, key, value):
            if not key.startswith('_') and ('_' + key) in f_cls_attr['__slots__']:
                raise AttributeError(f"you can't modify private members:_{key}")
            if original_setattr is not None:
                original_setattr(self, key, value)
            else:
                super(eval(f_cls_name), self).__setattr__(key, value)

        f_cls_attr['__getattribute__'] = init_getattr
        f_cls_attr['__setattr__'] = init_setattr

        cls = super().__new__(mcs, f_cls_name, f_cls_parents, f_cls_attr)
        return cls


class Human(metaclass=MetaMemberControl):
    __slots__ = ('_age', '_name')

    def __init__(self, name, age):
        self._name = name
        self._age = age

    def __getattribute__(self, item):
        """
        is just for IDE recognize.
        """
        return super().__getattribute__(item)

    """ with MetaMemberControl then you don't have to write as following
    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age
    """


def test_demo():
    human = Human('Carson', 27)
    # human.age = 18  # you can't modify private members:_age  <-- this is defined by yourself.
    # human.k = 18  # 'Human' object has no attribute 'k'  <-- system error.
    age1 = human._age  # It's OK, although the IDE will show some warnings. (Access to a protected member _age of a class)

    age2 = human.age  # It's OK! see below:
    """
    if you do not define `__getattribute__` at the class of Human,
    the IDE will show you: Unresolved attribute reference 'age' for class 'Human'
    but it's ok on running since the MetaMemberControl will help you.
    """


if __name__ == '__main__':
    test_demo()

El metaclass es poderoso, hay muchas cosas (como la magia de los monos) que puedes hacer con él, pero ten cuidado de que esto solo lo sepas tú.

1
Carson Arucard 20 dic. 2019 a las 11:03

Nota, esta respuesta es para Python 2.x como se escribió en 2008, las metaclases son ligeramente diferentes en 3.x.

Las metaclases son la salsa secreta que hace que la 'clase' funcione. La metaclase predeterminada para un nuevo objeto de estilo se llama 'tipo'.

class type(object)
  |  type(object) -> the object's type
  |  type(name, bases, dict) -> a new type

Las metaclases toman 3 args. ' nombre ', ' bases ' y ' dict '

Aquí es donde comienza el secreto. Busque de dónde provienen el nombre, las bases y el dict en esta definición de clase de ejemplo.

class ThisIsTheName(Bases, Are, Here):
    All_the_code_here
    def doesIs(create, a):
        dict

Vamos a definir una metaclase que demostrará cómo la llama ' clase: '.

def test_metaclass(name, bases, dict):
    print 'The Class Name is', name
    print 'The Class Bases are', bases
    print 'The dict has', len(dict), 'elems, the keys are', dict.keys()

    return "yellow"

class TestName(object, None, int, 1):
    __metaclass__ = test_metaclass
    foo = 1
    def baz(self, arr):
        pass

print 'TestName = ', repr(TestName)

# output => 
The Class Name is TestName
The Class Bases are (<type 'object'>, None, <type 'int'>, 1)
The dict has 4 elems, the keys are ['baz', '__module__', 'foo', '__metaclass__']
TestName =  'yellow'

Y ahora, un ejemplo que realmente significa algo, esto automáticamente hará que las variables en la lista de "atributos" se establezcan en la clase y se establezcan en Ninguno.

def init_attributes(name, bases, dict):
    if 'attributes' in dict:
        for attr in dict['attributes']:
            dict[attr] = None

    return type(name, bases, dict)

class Initialised(object):
    __metaclass__ = init_attributes
    attributes = ['foo', 'bar', 'baz']

print 'foo =>', Initialised.foo
# output=>
foo => None

Tenga en cuenta que el comportamiento mágico que Initialised gana al tener la metaclase init_attributes no se pasa a una subclase de Initialised.

Aquí hay un ejemplo aún más concreto, que muestra cómo puede subclasificar 'tipo' para hacer una metaclase que realiza una acción cuando se crea la clase. Esto es bastante complicado:

class MetaSingleton(type):
    instance = None
    def __call__(cls, *args, **kw):
        if cls.instance is None:
            cls.instance = super(MetaSingleton, cls).__call__(*args, **kw)
        return cls.instance

class Foo(object):
    __metaclass__ = MetaSingleton

a = Foo()
b = Foo()
assert a is b
386
ralh 6 nov. 2019 a las 07:57

Creo que la introducción de ONLamp a la programación de metaclases está bien escrita y ofrece una muy buena introducción al tema a pesar de tener ya 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 modelo para la creación de una instancia, una metaclase es un modelo 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 Django framework . Las clases de modelo utilizan un enfoque de metaclase para permitir un estilo declarativo de escribir nuevos modelos o clases de formulario. Mientras la metaclase está creando 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%.

113
Yet Another User 13 ago. 2018 a las 04:53

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

Por ejemplo, si observa modelos Django, su definición parece un un poco confuso Parece que solo estás 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 Persona se llenan con todo tipo de métodos útiles. Vea la fuente para obtener una metaclase increíble.

154
Antti Rasinen 19 sep. 2008 a las 06:45

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 es necesario usarla 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.

Según el modelo de objetos de Python, la clase es el objeto, por lo que la clase debe ser una instancia de otra clase determinada. Por defecto, una clase de Python es una instancia de la clase de tipo. Es decir, type es metaclase de la mayoría de las clases integradas y 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 argumentos de palabras clave en metaclase, indica que el intérprete de Python creará la Lista personalizada a través de 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.

20
binbjz 12 ene. 2018 a las 09:30

En la programación orientada a objetos, una metaclase es una clase cuyas instancias son clases. Así como una clase ordinaria define el comportamiento de ciertos objetos, una metaclase define el comportamiento de cierta clase y sus instancias. El término metaclase simplemente significa algo utilizado para crear clases. En otras palabras, es la clase de una clase. La metaclase se usa para crear la clase, de modo que, como el objeto es una instancia de una clase, una clase es una instancia de una metaclase. En python las clases también se consideran objetos.

3
Venu Gopal Tewari 9 jul. 2019 a las 05:45

Además de las respuestas publicadas, puedo decir que un metaclass define el comportamiento de una clase. Por lo tanto, puede establecer explícitamente su metaclase. Cada vez que Python obtiene una palabra clave class, comienza a buscar metaclass. Si no se encuentra, el tipo de metaclase predeterminado se usa para crear el objeto de la clase. Usando el atributo __metaclass__, puede configurar metaclass de su clase:

class MyClass:
   __metaclass__ = type
   # write here other method
   # write here one more method

print(MyClass.__metaclass__)

Producirá la salida de esta manera:

class 'type'

Y, por supuesto, puede crear su propio metaclass para definir el comportamiento de cualquier clase que se cree usando su clase.

Para hacerlo, su clase de tipo predeterminada metaclass debe heredarse ya que esta es la metaclass principal:

class MyMetaClass(type):
   __metaclass__ = type
   # you can write here any behaviour you want

class MyTestClass:
   __metaclass__ = MyMetaClass

Obj = MyTestClass()
print(Obj.__metaclass__)
print(MyMetaClass.__metaclass__)

La salida será:

class '__main__.MyMetaClass'
class 'type'
10
Andy 15 sep. 2018 a las 13:17

Otros han explicado cómo funcionan las metaclases y cómo encajan en el sistema de tipo Python. Aquí hay un ejemplo de para qué se pueden usar. En un marco de prueba que escribí, quería hacer un seguimiento del orden en que se definieron las clases, para poder luego instanciarlas en este orden. Me resultó más fácil hacer esto usando una metaclase.

class MyMeta(type):

    counter = 0

    def __init__(cls, name, bases, dic):
        type.__init__(cls, name, bases, dic)
        cls._order = MyMeta.counter
        MyMeta.counter += 1

class MyType(object):              # Python 2
    __metaclass__ = MyMeta

class MyType(metaclass=MyMeta):    # Python 3
    pass

Cualquier cosa que sea una subclase de MyType obtiene un atributo de clase _order que registra el orden en que se definieron las clases.

159
kindall 28 nov. 2016 a las 18:04

La versión tl; dr

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

El type() de una clase es su metaclase .

Para usar una metaclase:

class Foo(object):
    __metaclass__ = MyMetaClass

type es su propia metaclase. La clase de una clase es una metaclase: el cuerpo de una clase son los argumentos pasados a la metaclase que se utiliza para construir la clase.

Aquí puede leer sobre cómo usar metaclases para personalizar la construcción de clases.

39
noɥʇʎԀʎzɐɹƆ 5 dic. 2019 a las 16:27

type es en realidad un metaclass, una clase que crea otras clases. La mayoría de metaclass son las subclases de type. metaclass recibe la clase new como primer argumento y proporciona acceso al objeto de clase con los detalles que se mencionan a continuación:

>>> class MetaClass(type):
...     def __init__(cls, name, bases, attrs):
...         print ('class name: %s' %name )
...         print ('Defining class %s' %cls)
...         print('Bases %s: ' %bases)
...         print('Attributes')
...         for (name, value) in attrs.items():
...             print ('%s :%r' %(name, value))
... 

>>> class NewClass(object, metaclass=MetaClass):
...    get_choch='dairy'
... 
class name: NewClass
Bases <class 'object'>: 
Defining class <class 'NewClass'>
get_choch :'dairy'
__module__ :'builtins'
__qualname__ :'NewClass'

Note:

Note que la clase no fue instanciada en ningún momento; El simple acto de crear la clase desencadenó la ejecución de metaclass.

39
Chankey Pathak 29 ago. 2017 a las 05:23

Una clase, en Python, es un objeto, y al igual que cualquier otro objeto, es una instancia de "algo". Este "algo" es lo que se denomina Metaclase. Esta metaclase es un tipo especial de clase que crea los objetos de otra clase. Por lo tanto, metaclass es responsable de hacer nuevas clases. Esto permite al programador personalizar la forma en que se generan las clases.

Para crear una metaclase, generalmente se reemplaza los métodos nuevo () e init (). new () se puede reemplazar para cambiar la forma en que se crean los objetos, mientras que init () se puede reemplazar para cambiar la forma de inicializar el objeto. Metaclass se puede crear de varias maneras. Una de las formas es usar la función type (). La función type (), cuando se llama con 3 parámetros, crea una metaclase. Los parámetros son: -

  1. Nombre de la clase
  2. Tupla que tiene clases base heredadas por clase
  3. Un diccionario que tiene todos los métodos y variables de clase.

Otra forma de crear una metaclase consiste en la palabra clave 'metaclase'. Defina la metaclase como una clase simple. En los parámetros de la clase heredada, pase metaclass = metaclass_name

Metaclass se puede usar específicamente en las siguientes situaciones: -

  1. cuando se debe aplicar un efecto particular a todas las subclases
  2. Se requiere cambio automático de clase (en la creación)
  3. Por desarrolladores de API
1
Swati Srivastava 20 ene. 2020 a las 06:59

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 OrderedDict) para usar como espacio de nombres mientras se crea la clase. Debe devolver una instancia del espacio de nombres que elija. Si no implementa __prepare__, se utiliza un dict normal.

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

A una metaclase básica, sin 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 simple ejemplo:

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

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 posibles errores tipográficos junto con errores irritantes.

Una metaclase simple puede abordar ese problema:

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

Así es como se vería la metaclase (sin usar __prepare__ ya que no es necesaria):

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 bastante simple, también podría haberse logrado con un decorador de clase, pero presumiblemente 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
74
Ethan Furman 1 mar. 2016 a las 19:48

Las clases de Python son en sí mismas objetos, como por ejemplo, de su metaclase.

La metaclase predeterminada, que se aplica cuando se determinan las clases como:

class foo:
    ...

Las metaclases se utilizan para aplicar alguna regla a un conjunto completo de clases. Por ejemplo, suponga que está creando un ORM para acceder a una base de datos y desea que los registros de cada tabla sean de una clase asignada a esa tabla (basada en campos, reglas comerciales, etc.), un posible uso de metaclase es, por ejemplo, la lógica del grupo de conexiones, que comparten todas las clases de registros de todas las tablas. Otro uso es la lógica para admitir claves foráneas, que involucra múltiples clases de registros.

Cuando define metaclase, subclase tipo, y puede anular los siguientes métodos mágicos para insertar su lógica.

class somemeta(type):
    __new__(mcs, name, bases, clsdict):
      """
  mcs: is the base metaclass, in this case type.
  name: name of the new class, as provided by the user.
  bases: tuple of base classes 
  clsdict: a dictionary containing all methods and attributes defined on class

  you must return a class object by invoking the __new__ constructor on the base metaclass. 
 ie: 
    return type.__call__(mcs, name, bases, clsdict).

  in the following case:

  class foo(baseclass):
        __metaclass__ = somemeta

  an_attr = 12

  def bar(self):
      ...

  @classmethod
  def foo(cls):
      ...

      arguments would be : ( somemeta, "foo", (baseclass, baseofbase,..., object), {"an_attr":12, "bar": <function>, "foo": <bound class method>}

      you can modify any of these values before passing on to type
      """
      return type.__call__(mcs, name, bases, clsdict)


    def __init__(self, name, bases, clsdict):
      """ 
      called after type has been created. unlike in standard classes, __init__ method cannot modify the instance (cls) - and should be used for class validaton.
      """
      pass


    def __prepare__():
        """
        returns a dict or something that can be used as a namespace.
        the type will then attach methods and attributes from class definition to it.

        call order :

        somemeta.__new__ ->  type.__new__ -> type.__init__ -> somemeta.__init__ 
        """
        return dict()

    def mymethod(cls):
        """ works like a classmethod, but for class objects. Also, my method will not be visible to instances of cls.
        """
        pass

De todos modos, esos dos son los ganchos más utilizados. La metaclase es poderosa, y arriba no hay ninguna lista exhaustiva de usos para la metaclase.

25
Xingzhou Liu 13 jul. 2017 a las 08:18

Una metaclase es una clase que dice cómo (alguna) otra clase debería crearse.

Este es un caso en el que vi la metaclase como una solución a mi problema: tuve un problema realmente complicado, que probablemente podría haberse resuelto de manera diferente, pero decidí resolverlo usando 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()
53
3 revs, 2 users 99% 25 ene. 2016 a las 20:08
100003