asherbret
asherbret

Reputation: 6038

Ordering class methods without instantiation

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

Answers (1)

jsbueno
jsbueno

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

Related Questions