Recently I came across this problem where I had to list all the members of a class in the order in which they were declared. Normally, to list all the members of the class, one would use dir
to get all the attributes of the object. Following example demonstrates the working of dir
command:
classTestClass:B=2A=1defset_method(self):print('method1')defget_method(self):print('method2')if__name__=='__main__':print(dir(TestClass))print(TestClass.__dict__)# output['A','B','__class__','__delattr__','__dict__','__dir__','__doc__','__eq__','__format__','__ge__','__getattribute__','__gt__','__hash__','__init__','__le__','__lt__','__module__','__ne__','__new__','__reduce__','__reduce_ex__','__repr__','__setattr__','__sizeof__','__str__','__subclasshook__','__weakref__','get_method','set_method']{'__dict__':<attribute'__dict__'of'TestClass'objects>,'__weakref__':<attribute'__weakref__'of'TestClass'objects>,'set_method':<functionTestClass.set_methodat0x102133bf8>,'get_method':<functionTestClass.get_methodat0x102133c80>,'B':2,'A':1,'__doc__':None,'__module__':'__main__'}
We can see that the class members are not listed in the order in which they are declared. I started googling for the answers and found a solution here. Sample output with this solution is:
importcollectionsclassOrderedClassMembers(type):@classmethoddef__prepare__(self,name,bases):returncollections.OrderedDict()def__new__(self,name,bases,classdict):classdict['__ordered__']=[keyforkeyinclassdict.keys()ifkeynotin('__module__','__qualname__')]returntype.__new__(self,name,bases,classdict)classTestClass(metaclass=OrderedClassMembers):B=2A=1defset_method(self):print('method1')defget_method(self):print('method2')if__name__=='__main__':print(TestClass.__ordered__)# output['B','A','set_method','get_method']
To understand how this works, we first have to understand what a metaclass is. Metaclasses are basically classes that are used to create classes. In python, class is an object and it is basically an instance of a metaclass called type
. Let's look at some code:
In[1]:classA:...:pass...:In[2]:type(A)Out[2]:typeIn[3]:A.__class__Out[3]:typeIn[4]:isinstance(A,type)Out[4]:True
So, what we did while using OrderedClassMembers
was that we defined our own metaclass. We use the magic method called __prepare__
to achieve our goal. When we declare a class in python, a namespace for that particular class in prepared using a dict
object. However dict
fails to preserve order of the keys for the namespace. However, using __prepare__
method which was introduced in Python 3 (check PEP3115) we can define the structure that is used to create this namespace. Now when __new__
runs, we get an OrderedDict
instead of a normal dict
. In this dictionary, we add another key called __ordered__
which can than be used to get the desired result.
If you liked this post, you can share it with your followers or follow me on Twitter!