3

I was hoping to make a list of all subclasses of a given class by having each subclass register itself in a list that a parent class holds, ie something like this:

class Monster(object):
    monsters = list()
class Lochness(Monster):
    Monster.monsters.append(Lochness)
class Yeti(Monster):
    Monster.monsters.append(Yeti)

This obviously doesn't work because the classes haven't been created yet when I want to add them to the list. And, it'd be much nicer if it were done automatically (like __subclass__)

I'm aware that __subclass__ has this functionality, but I was wondering (for my own edification) how you'd implement it yourself.

It seems like you'd want to create some sort of subclass of the metaclass which is creating everything to register it with Monster? Or is that completely off base

Viknesh
  • 505
  • 1
  • 4
  • 14

3 Answers3

11

Classes already register what subclasses are defined; call the class.__subclasses__() method to get a list:

>>> class Monster(object):
...     pass
... 
>>> class Lochness(Monster):
...     pass
... 
>>> class Yeti(Monster):
...     pass
... 
>>> Monster.__subclasses__()
[<class '__main__.Lochness'>, <class '__main__.Yeti'>]

.__subclasses__() returns a list of currently still alive subclasses. If you ever would clear all references to Yeti (del Yeti in the module, delete all instances, subclasses, imports, etc.) then it'd no longer be listed when you call .__subclasses__(). Note that in essence, .__subclasses__() is a CPython implementation detail, but the method is present in all Python versions that support new-style classes (2.2 and up, all the way to 3.x).

Otherwise, the canonical way to hook into class creation is to define a metaclass:

class MonstersMeta(type):
    def __new__(metaclass, name, bases, namespace):
        cls = super(MonstersMeta, metaclass).__new__(metaclass, name, bases, namespace)
        if issubclass(cls, Monster) and not cls is Monster:
            Monster.monsters.append(cls)
        return cls

class Monster(object):
    __metaclass__ = MonstersMeta
    monsters = []

class Lochness(Monster):
    pass

class Yeti(Monster):
    pass

Demo:

>>> class Monster(object):
...     __metaclass__ = MonstersMeta
...     monsters = []
... 
>>> class Lochness(Monster):
...     pass
... 
>>> class Yeti(Monster):
...     pass
... 
>>> Monster.monsters
[<class '__main__.Lochness'>, <class '__main__.Yeti'>]

or you can use a class decorator:

def registered_monster(cls):
    Monster.monsters.append(cls)
    return cls

class Monster(object):
    monsters = []

@registered_monster
class Lochness(Monster):
    pass

@registered_monster
class Yeti(Monster):
    pass

Demo:

>>> class Monster(object):
...     monsters = []
... 
>>> @registered_monster
... class Lochness(Monster):
...     pass
... 
>>> @registered_monster
... class Yeti(Monster):
...     pass
... 
>>> Monster.monsters
[<class '__main__.Lochness'>, <class '__main__.Yeti'>]

The difference being where you put the responsibility of registering monsters; with the base MonstersMeta type, or with explicit decorators.

Either way, the metaclass or the class decorator registers a permanent reference. You can use the weakref module if you really, really want to emulate the .__subclasses__() behaviour.

Community
  • 1
  • 1
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
2

Except from the obvious solution for this case to use type.__subclasses__(), you could use a decorator for similar problems:

class Monster(object):
    monsters = list()

def isMonster(cls):
    Monster.monsters.append(cls)
    return cls

@isMonster
class Lochness(Monster):
    pass

@isMonster
class Yeti(Monster):
    pass

print(Monster.monsters) # [<class '__main__.Lochness'>, <class '__main__.Yeti'>]
poke
  • 369,085
  • 72
  • 557
  • 602
1

Just to keep your code, add classes outside their definition:

class Monster(object):
    monsters = list()

class Lochness(Monster):
    pass
Monster.monsters.append(Lochness)

class Yeti(Monster):
    pass 
Monster.monsters.append(Yeti)

But, as said: if this is a common feature, create a Metaclass

Don
  • 16,928
  • 12
  • 63
  • 101