Order in metaclass

Carlos Ribeiro carribeiro at gmail.com
Wed Oct 13 08:15:32 EDT 2004


On Tue, 12 Oct 2004 15:37:22 -0400, Nicolas Fleury
<nid_oizo at yahoo.com_remove_the_> wrote:
> In the following example:
> 
> class MyMetaclass(type): pass
> class MyBaseType(object): __metaclass__ = MyMetaclass
> class MyType(MyBaseType):
>      x = 4
>      y = 5
>      z = 6
> 
> Is there any way to modify MyMetaclass to keep the order of x,y,z somewhere?

Well, some people have already pointed to you about a recent
discussion on this topic. I was the one who started it all :-) What I
have learned is:

-- Due to the way the class statement is handled, it's not possible
(without really black magic) to guarantee ordering in a transparent
way. If you need to put attributes in order, you have either to
provide a list with all attributes in order, or use derive your
attributes from a base class that provide some ordering mechanism of
its own.

-- You have to include an extra member into your class to record the
order. I have chosen to include a list, named "_fields", with all
attribute _names_. Storing the attribute name is guaranteed way to
keep the ordering even when a descendant override an attribute, or
when the user modify its value.

I have written a small package called GenericTemplates, that works all
the magic needed. It's the basis for a much more ambitious package for
'declarative-style' data structures using class statements. It handles
nested classes also, and keeps the ordering of all attributes that
inherit from a AbstractAttribute class (it includes nested classes
that inherit from GenericTemplate, and also OrderedAttributes and
TypedAttributes). I have a page for it in portuguese only at:

http://www.pythonbrasil.com.br/moin.cgi/TemplatesGen%E9ricos

The code is as follows -- still with debug code included, and lots of comments:

"""
metatemplate.py

Template class that can be used to write complex data structures using 
nested classes. Template classes can store other template classes (nested)
or user-defined attributes (typed or untyped). The original definition 
order information is preserved, allowing for true templating use for
applications such as html templates, data entry forms, and configuration 
files.

(c) 2004 Carlos Ribeiro
carribeiro at gmail.com
http:///pythonnotes.blogspot.com

"""

import sys
from inspect import isclass, isdatadescriptor
from types import StringType, IntType, FloatType, ListType
import itertools

#----------------------------------------------------------------------
# Debug constants. I don't intend to remove them, even from production
# code, but I intend to use the logging module to print the messages

debug_generic_attribute = 0
debug_typed_attribute = 0
debug_auto_instantiation = 0

#----------------------------------------------------------------------
# AbstractAttribute is the ancestor of all classes that can be used
# in the metacontainer framework.

class AbstractAttribute(object):
    pass

#----------------------------------------------------------------------
# GenericAttribute is the ancestor of all simple elements that are
# used as attributes of user defined Container subclasses
#
# GenericAttributes are simpler than full containers. They're both
# derived from the same AbstractAttribute class, but GenericAttributes
# have only a single value associated with them.
#
# When referred from a instance, the __get__ method returns the value
# associated with the attribute. If called from the class, the __get__
# method returns the property itself.

class GenericAttribute(AbstractAttribute):
    """ Generic attributes for generic containers """
    def __init__(self, default = None):
        self._seqno = next_attribute_id()
        self.value = default
    def __repr__(self):
        return "<Attr '%s'>" % (self.__class__.__name__)
    def __get__(self, instance, owner):
        if debug_generic_attribute:
            print "GET self:[%s], instance:[%s], owner:[%s]" % \
                  (self, instance, owner)
        if instance:
            attrdict = instance.__dict__.setdefault('__attr__', {})
            return attrdict.get(self.name, self.value)
        else:
            return owner
    def __set__(self, instance, value):
        if debug_generic_attribute:
            print "SET self:[%s], instance:[%s], value:[%s]" % \
                  (self, instance, value)
        attrdict = instance.__dict__.setdefault('__attr__', {})
        attrdict[self.name] = value

class TypedAttribute(GenericAttribute):
    """ Typed attributes for generic containers """
    def __init__(self, default = None, mytype = None):
        self._seqno = next_attribute_id()
        self.value = default
        if mytype:
            if isclass(mytype):
                self.mytype = mytype
            else:
                raise TypeError("Argument <mytype> expects None "
                      "or a valid type/class")
        else:
            self.mytype = type(default)
    def __repr__(self):
        return "<TypedAttr '%s':%s>" % \
               (self.__class__.__name__, self.mytype.__name__)
    def __get__(self, instance, owner):
        if debug_typed_attribute:
            print "GET self:[%s], instance:[%s], owner:[%s]" % \
                  (self, instance, owner)
        if instance:
            attrdict = instance.__dict__.setdefault('__attr__', {})
            return attrdict.get(self.name, self.value)
        else:
            return self.value
    def __set__(self, instance, value):
        if debug_typed_attribute:
            print "SET self:[%s], instance:[%s], value:[%s]" % \
                  (self, instance, value)
        if not isinstance(value, self.mytype):
            # if it's a string, tries to convert to the correct
            # target type (this is needed because most things read
            # from files will be strings anyway)
            if isinstance(value, StringType):
                value = self.mytype(value)
            else:
                raise TypeError, "Expected %s attribute" % \
                      self.mytype.__name__
        attrdict = instance.__dict__.setdefault('__attr__', {})
        attrdict[self.name] = value

#----------------------------------------------------------------------
# auxiliary functions

next_attribute_id = itertools.count().next

def getfields(dct):
    """
    takes a dictionary of class attributes and returns a decorated list
    containing all valid field instances and their relative position.

    """
    for fname, fobj in dct.items():
        if isinstance(fobj,GenericAttribute):
            yield (fobj._seqno, (fname, fobj))
        elif isclass(fobj) and issubclass(fobj,AbstractAttribute):
            yield (fobj._seqno, (fname, fobj))
        elif (fname[0] != '_'):
            # conventional attributes from basic types are just stored
            # as GenericAttributes, and put at the end of the list,
            # in alphabetical order
            if (isinstance(fobj,StringType) or
                isinstance(fobj,IntType) or
                isinstance(fobj,FloatType) or
                isinstance(fobj,ListType)):
                yield (sys.maxint, (fname, GenericAttribute(fobj)))
            else:
                yield (0, (fname, fobj))
        else:
            yield (0, (fname, fobj))

def makefieldsdict(dct, bases):
    # build the field list and sort it
    fields = list(getfields(dct))
    fields.sort()
    # undecorate the list and build a dict that will be returned later
    sorted_field_list = [field[1] for field in fields]
    field_dict = dict(sorted_field_list)
    # finds all attributes and nested classes that are containers
    attribute_list = [field for field in sorted_field_list
                      if (isinstance(field[1],AbstractAttribute) or
                          (isclass(field[1]) and
                           issubclass(field[1],AbstractAttribute)
                     ))]
    # check baseclasses for attributes inherited but not overriden
    # !!WARNING: this code does not checks correctly for multiple
    # base classes if there are name clashes between overriden
    # members. This is not recommended anyway.
    inherited = []
    for baseclass in bases:
        base_field_list = getattr(baseclass, '_fields', None)
        # looks for a valid _fields attribute in an ancestor
        if isinstance(base_field_list, ListType):
            fnames = [f[0] for f in attribute_list]
            for fname, fobj in base_field_list:
                # checks for overriden attributes
                if (fname in fnames):
                    # overriden - inherited list contains the new value
                    newobj = field_dict[fname]
                    inherited.append((fname, newobj))
                    # remove attribute and quick check field names list
                    attribute_list.remove((fname, field_dict[fname]))
                    fnames.remove(fname)
                else:
                    # copy the original entry into the inherited list
                    inherited.append((fname, fobj))
    field_dict['_fields'] = inherited + attribute_list
    return field_dict

#----------------------------------------------------------------------
# MetaTemplate metaclass
#
# Most of the hard work is done outside the class by the auxiliary
# functions makefieldsdict() and getfields()

class MetaTemplate(type):
    def __new__(cls, name, bases, dct):
        # creates the class using only the processed field list
        newdct = makefieldsdict(dct, bases)
        newclass = type.__new__(cls, name, bases, newdct)
        newclass._seqno = next_attribute_id()
        newclass.name  = name
        return newclass

#----------------------------------------------------------------------
# GenericTemplate superclass

class GenericTemplate(AbstractAttribute):
    __metaclass__ = MetaTemplate

    def __init__(self):
        """ instantiates all nested classes upon creation """

        # builds a copy of the field list. this is needed to allow
        # customizations of the instance not to be reflected in the
        # original class field list.
        self._fields = list(self.__class__._fields)

        # auto instantiates nested classes and attributes
        if debug_auto_instantiation:
            print "AutoInstantiation <%s>: fieldlist = %s" % \
                  (self.name, self._fields)
        for fname, fobj in self._fields:
            if isclass(fobj) and issubclass(fobj,Container):
                # found a nested class
                if debug_auto_instantiation:
                    print "AutoInstantiation <%s>: field[%s] is a "
                          "Container Subclass" % (self.name, fname)
                fobj = fobj()
                setattr(self, fname, fobj)
            elif isinstance(fobj, AbstractAttribute):
                # found an attribute instance
                if debug_auto_instantiation:
                    print "AutoInstantiation <%s>: field[%s] is an "
                          "Attribute Instance" % (self.name, fname)
                # removed: parent links are still being thought out,
                # and I'm not even sure if they're a good idea
                # setattr(fobj, 'parent', self)
                setattr(fobj, 'name', fname)
            else:
                if debug_auto_instantiation:
                    print "AutoInstantiation <%s>: field[%s] is "
                          "unknown" % (self.name, fname)

    def iterfields(self):
        for fname, fobj in self._fields:
            yield getattr(self, fname)

    def __repr__(self):
        return "<%s '%s'>" % (self.__class__.__name__, self.name,)

-- 
Carlos Ribeiro
Consultoria em Projetos
blog: http://rascunhosrotos.blogspot.com
blog: http://pythonnotes.blogspot.com
mail: carribeiro at gmail.com
mail: carribeiro at yahoo.com



More information about the Python-list mailing list