How to read class attributes in the same order as declared?

Eric picture Eric · Dec 16, 2010 · Viewed 13.7k times · Source

I am writing a metaclass that reads class attributes and store them in a list, but I want the list (cls.columns) to respect the declaration order (that is: mycol2, mycol3, zut, cool, menfin, a in my example):

import inspect
import pprint

class Column(object):
    pass

class ListingMeta(type):
    def __new__(meta, classname, bases, classDict):
        cls = type.__new__(meta, classname, bases, classDict)
        cls.columns = inspect.getmembers(cls, lambda o: isinstance(o, Column)) 
        cls.nb_columns = len(cls.columns)
        return cls

class Listing(object):
    __metaclass__ = ListingMeta
    mycol2 = Column()
    mycol3 = Column()
    zut = Column()
    cool = Column()
    menfin = Column()
    a = Column()

pprint.pprint(Listing.columns)

Result:

[('a', <__main__.Column object at 0xb7449d2c>),
 ('cool', <__main__.Column object at 0xb7449aac>),
 ('menfin', <__main__.Column object at 0xb7449a8c>),
 ('mycol2', <__main__.Column object at 0xb73a3b4c>),
 ('mycol3', <__main__.Column object at 0xb744914c>),
 ('zut', <__main__.Column object at 0xb74490cc>)]

This does not respect the declaration order of Column() attributes for Listing class. If I use classDict directly, it does not help either.

How can I proceed?

Answer

Thomas Perl picture Thomas Perl · Nov 24, 2014

In the current version of Python, the class ordering is preserved. See PEP520 for details.

In older versions of the language (3.5 and below, but not 2.x), you can provide a metaclass which uses an OrderedDict for the class namespace.

import collections 

class OrderedClassMembers(type):
    @classmethod
    def __prepare__(self, name, bases):
        return collections.OrderedDict()

    def __new__(self, name, bases, classdict):
        classdict['__ordered__'] = [key for key in classdict.keys()
                if key not in ('__module__', '__qualname__')]
        return type.__new__(self, name, bases, classdict)

class Something(metaclass=OrderedClassMembers):
    A_CONSTANT = 1

    def first(self):
        ...

    def second(self):
        ...

print(Something.__ordered__)
# ['A_CONSTANT', 'first', 'second']

This approach doesn't help you with existing classes, however, where you'll need to use introspection.