Before python 3.6 it was difficult to get attributes order from a class.
Let’s say, for instance, you want to create a listing class which contains columns to display :
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Column(object): pass class Listing(object): col1 = Column() col2 = Column() col3 = Column() col4 = Column() col5 = Column() col6 = Column() print( [ k for k in Listing.__dict__.keys() if '__' not in k ] ) |
With python 2.7 you will get :
1 |
['col6', 'col4', 'col5', 'col2', 'col3', 'col1'] |
With python 3.5 you will get :
1 |
['col3', 'col1', 'col4', 'col5', 'col2', 'col6'] |
And finally, with python 3.6, there is no problem any more : attributes key are already sorted : you will get :
1 |
['col1', 'col2', 'col3', 'col4', 'col5', 'col6'] |
Obviously, if you want to display column in the same order as they have been declared, you got a problem before python 3.6.
Fortunately, there are some solutions. For python 2.x, the idea is to add a counter in Column
class, then to order column attributes following this counter :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import inspect class Column(object): _creation_counter = 0 def __init__(self): self._creation_order = Column._creation_counter Column._creation_counter+=1 class ListingMeta(type): def __new__(meta, classname, bases, classDict): cls = type.__new__(meta, classname, bases, classDict) cls._columns = sorted(inspect.getmembers(cls,lambda o:isinstance(o,Column)),key=lambda i:i[1]._creation_order) return cls class Listing(object): __metaclass__ = ListingMeta col1 = Column() col2 = Column() col3 = Column() col4 = Column() col5 = Column() col6 = Column() print( [ i[0] for i in Listing._columns ] ) You will get : ['col1', 'col2', 'col3', 'col4', 'col5', 'col6'] |
You will get :
1 |
['col1', 'col2', 'col3', 'col4', 'col5', 'col6'] |
The only drawback is that you will get attribute order only for one base class, here Column
class.
With python3, you can now choose where will be stored members by defining __prepare__
method, for python < 3.6 choose an OrderedDict
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import collections import sys class Column: pass class OrderedClassMembers(type): @classmethod def __prepare__(self, name, bases): if sys.version_info < (3,6): return collections.OrderedDict() else: return super().__prepare__(self, name, bases) def __new__(self, name, bases, dct): dct['__attrlist__'] = [(k,v) for k,v in dct.items() if not k.startswith('__') ] return type.__new__(self, name, bases, dct) class Listing(metaclass=OrderedClassMembers): col1 = Column() col2 = Column() col3 = Column() col4 = Column() col5 = Column() col6 = Column() another_attribute = 'seen' def display_columns(self): pass print('allmembers =', [ i[0] for i in Listing.__attrlist__ ] ) print('columns =', [ i[0] for i in Listing.__attrlist__ if isinstance(i[1],Column) ] ) |
You will get:
1 2 |
allmembers = ['col1', 'col2', 'col3', 'col4', 'col5', 'col6', 'another_attribute', 'display_columns'] columns = ['col1', 'col2', 'col3', 'col4', 'col5', 'col6'] |
With this method, you will get all class members, not only some attributes having a specific base class.
Et voilà.