Reputation: 6038
Related: inspect.getmembers in order?
Background: I'm trying to build a tool that creates a Python file according to a certain specification. One option is to give, as input, a Python module that contains an abstract class declaration, and creates a base class that inherits that abstract class but also adds a default implementation to all abstract methods.
For example: say I have the following file, called Abstract.py
that contains the following:
class Abstract(object):
__metaclass__ = ABCMeta
@abstractmethod
def first(self):
pass
@abstractmethod
def second(self):
pass
So then the output of my tool would be a file called BaseClass.py
that contains:
class BaseClass(Abstract):
def first(self):
pass
def second(self):
pass
I want the methods in the BaseClass
to be in the same order as in Abstract
.
My question is: Is there a way to sort the methods according to their appearance without relying on built-in method comparison (which is based on memory address comparison)? I'm also hoping to avoid any kind of file-parsing, if possible.
Please note that I cannot create an instance of Abstract
so the solution mentioned in the above related question will not work for me.
Upvotes: 3
Views: 115
Reputation: 110476
At the time of class creation in Python2 (that is, when the interpreter just get pass the class body when running a file, which happens in sequence) - the Class itself is created as an object. At this point all variables and methods defined in the class body are passed to a call to "type" (which is the default metaclass) as a dictionary.
As you know, dictionaries in Python have no ordering, so ordinarily it is impossible in Python2. It is possible in Python 3 because metaclasses can implement a __prepare__
method which returns the mapping object that will be used to build the class body - so instead of an ordinary dict, __prepare__
could return an OrderedDict.
However, in your case, all relevant methods are decorated with @abstractmethod
- we can take advantage of that to not only annotate the methods as abstract, but also mark down the order in which they appear.
You can either wrap the abstractclass
decorator, or create another decorator and use both. I'd favor a new decorator that would do both things, in order to keep linecount down.
Also, you have to choose how will you keep the order of the methods and make use of it. Ordinarily iterating on the class's attributes will just iterate over a dictionary (rather a dictionary proxy), which is unorderd- so, you have to have keep a data structure were to keep the ordered methods available, and a way to record this given order. There are are some options there - but maybe, the most direct thing is to annotate the method order in the methods themselves, and retrieve them with a call to built-in sorted
with an appropriate key
parameter. Other means would require eithe a class decorator or a custom metaclass to work.
So here is an example of what I wrote about:
from abc import abstractmethod, ABCMeta
class OrderedAbstractMethod(object):
def __init__(self):
self.counter = 0
def __call__(self,func):
func._method_order = self.counter
self.counter += 1
return abstractmethod(func)
ordered_abstract_method = OrderedAbstractMethod()
class Abstract(object):
__metaclass__ = ABCMeta
@ordered_abstract_method
def first(self):
pass
@ordered_abstract_method
def second(self):
pass
@ordered_abstract_method
def third(self):
pass
@ordered_abstract_method
def fourth(self):
pass
print "Unordered methods: ",[method[0] for method in Abstract.__dict__.items() if not method[0].startswith("_") ]
# here it printed out - ['second', 'third', 'fourth', 'first']
print "Ordered methods: ", sorted([method for method in Abstract.__dict__.items() if not method[0].startswith("_") ], key= lambda m: m[1]._method_order)
Upvotes: 2