Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: __subclasses__ order

I have some code that creates a list of (instances of) subclasses by calling the __subclasses__() function on a base class.

subclasses = [subclass() for subsclass in BaseClass.__subclasses__()]

In Python 2.7 the order of the subclasses in this list is always equal to the order of the import statements in my code (each subclass is defined in it's own Python file). But in Python 3.5 the order of subclasses in this list seems random. Can I use some kind of workaround to get the same behavior in Python 3.5?

See the class.__subclasses__ documentation.

like image 271
compie Avatar asked Mar 05 '23 20:03

compie


1 Answers

In Python 3.5, there is not, not by any trivial means. As of Python 3.4 the tp_subclasses data structure tracks subclasses via a dictionary (mapping the id() value of the subclass to a weak reference, see Python issue #17936 for why). Dictionaries are unordered.

In Python 3.6, the internal implementation of dictionaries was changed, and they now track insertion order. When you upgrade to Python 3.6, you'd get your subclasses in insertion order (order in which they are first seen by Python) again.

However, the fact that subclasses are tracked in a dictionary is an implementation detail and should not really be relied upon.

You could track the order by some other means. You could give your base class a metaclass that records subclasses in order, for example:

from itertools import count
from weakref import WeakKeyDictionary

class SubclassesInOrderMeta(type):
    _subclass_order = WeakKeyDictionary()
    _counter = count()

    def __ordered_subclasses__(cls):
        # sort subclasses by their 'seen' order; the sort key
        # puts classes that are missing from the _subclass_order
        # mapping at the end, defensively.
        order_get = type(cls)._subclass_order.get
        sort_key = lambda c: order_get(c, float('inf'))
        return sorted(cls.__subclasses__(), key=sort_key)

    def __new__(mcls, *args, **kwargs):
        cls = super(SubclassesInOrderMeta, mcls).__new__(mcls, *args, **kwargs)
        mcls._subclass_order[cls] = next(mcls._counter)
        return cls
like image 148
Martijn Pieters Avatar answered Mar 15 '23 18:03

Martijn Pieters