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
(e-mail address removed)
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: (e-mail address removed)
mail: (e-mail address removed)