Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Forward declaration of classes?

Tags:

python

I have some classes looking like this:

class Base:   subs = [Sub3,Sub1]   # Note that this is NOT a list of all subclasses!   # Order is also important  class Sub1(Base): pass class Sub2(Base): pass class Sub3(Base): pass ... 

Now, this fails because Sub1 and Sub3 are not defined when Base.subs is. But obviously I can't put the subclasses before Base either. Is there a way to forward-declare classes in Python? I want to work with isinstance so the types in subs actually have to be the same as the later declared subclasses, it's not enough that they have the same name and other properties.

One workaround is to do: Base.subs = [Sub3,Sub1] after the subclasses have been defined, but I don't like having to split my class in that way.

Edit: Added information about order

like image 986
pafcu Avatar asked Nov 12 '10 07:11

pafcu


People also ask

What is forward declaration of a class?

Forward Declaration refers to the beforehand declaration of the syntax or signature of an identifier, variable, function, class, etc. prior to its usage (done later in the program). Example: // Forward Declaration of the sum() void sum(int, int); // Usage of the sum void sum(int a, int b) { // Body }

What can be forward declared?

The main rule is that you can only forward-declare classes whose memory layout (and thus member functions and data members) do not need to be known in the file you forward-declare it. This would rule out base classes and anything but classes used via references and pointers. Almost.

What is forwarding declaration?

In computer programming, a forward declaration is a declaration of an identifier (denoting an entity such as a type, a variable, a constant, or a function) for which the programmer has not yet given a complete definition.

Where do you put forward declaration?

Generally you would include forward declarations in a header file and then include that header file in the same way that iostream is included.


2 Answers

Here's essentially a hybrid version of @Ignacio Vazquez-Abrams' and @aaronasterling's answers which preserves the order of the subclasses in the list. Initially the desired subclass names (i.e. strings) are manually placed in the subs list in the desired order, then as each subclass is defined, a class decorator causes the corresponding string to be replaced with the actual subclass:

class Base(object):  # New-style class (i.e. explicitly derived from object).      @classmethod     def register_subclass(cls, subclass):         """ Class decorator for registering subclasses. """          # Replace any occurrences of the class name in the class' subs list.         # with the class itself.         # Assumes the classes in the list are all subclasses of this one.         # Works because class decorators are called *after* the decorated class'         # initial creation.         while subclass.__name__ in cls.subs:             cls.subs[cls.subs.index(subclass.__name__)] = subclass          return cls  # Return modified class.      subs = ['Sub3', 'Sub1']  # Ordered list of subclass names.   @Base.register_subclass class Sub1(Base): pass  @Base.register_subclass class Sub2(Base): pass  @Base.register_subclass class Sub3(Base): pass  print('Base.subs: {}'.format(Base.subs)) # Base.subs: [<class '__main__.Sub3'>, <class '__main__.Sub1'>] 

Update: Metaclasses

Exactly the same thing can also be done using a metaclass—which has the advantage that it eliminates the need to explicitly decorate each subclass as shown in my original answer above (which you accepted), however it makes it all happen automagically.

Note that even though the metaclass' __init__() is called for the creation of every subclass, it only updates the subs list if the subclass' name appears in it—so the initial Base class' definition of the contents of the subs list still controls what gets replaced in it (maintaining its order).

class BaseMeta(type):      def __init__(cls, name, bases, classdict):         if classdict.get('__metaclass__') is not BaseMeta:  # Metaclass instance?             # Replace any occurrences of a subclass' name in the class being             # created the class' sub list with the subclass itself.             # Names of classes which aren't direct subclasses will be ignored.             while name in cls.subs:                 cls.subs[cls.subs.index(name)] = cls          # Chain to __init__() of the class instance being created after changes.         # Note class instance being defined must be new-style class.         super(BaseMeta, cls).__init__(name, bases, classdict)   # Python 2 metaclass syntax. class Base(object):  # New-style class (derived from built-in object class).     __metaclass__ = BaseMeta     subs = ['Sub3', 'Sub1']  # Ordered list of subclass names.  # Python 3 metaclass syntax. #class Base(metaclass=BaseMeta): #    subs = ['Sub3', 'Sub1']  # Ordered list of subclass names.   # Note: No need to manually register the (direct) subclasses. class Sub1(Base): pass class Sub2(Base): pass class Sub3(Base): pass  print('Base.subs: {}'.format(Base.subs)) 

Output:

Base.subs: [<class '__main__.Sub3'>, <class '__main__.Sub1'>] 

It important to note that there's at least one subtle difference between these two answers—namely that the first will work with any class name that is registered via @Base.register_subclass(), whether or not its actually a subclass of Base (although that might be possible to change/fix.)

I'm pointing this out for a couple of reasons: First because in your comments you said that subs was a "bunch of classes in a list, some of which might be its subclasses", and more importantly, because that's not the case with the code in my update, which only works for Base subclasses since they effectively get "registered" automatically via the metaclass—but will leave anything else in the list alone. This could be considered a bug or a feature. ;¬)

Update: Python 3.6+

In Python 3.6 a new object method was added named __init_subclass__() which provides an even simpler way implement things that also doesn't require decorating all the subclasses or defining a metaclass:

#!/usr/bin/env python3.6  class Base:     subs = ['Sub3', 'Sub1']  # Ordered list of subclass names.      def __init_subclass__(cls, **kwargs):         super().__init_subclass__(**kwargs)         while cls.__name__ in cls.subs:             cls.subs[cls.subs.index(cls.__name__)] = cls   # Note: No need to manually register the subclasses. class Sub1(Base): pass class Sub2(Base): pass class Sub3(Base): pass  print('Base.subs: {}'.format(Base.subs)) 
like image 174
martineau Avatar answered Sep 29 '22 21:09

martineau


Write a decorator that adds it to the registry in Base.

class Base(object):   subs = []    @classmethod   def addsub(cls, scls):     cls.subs.append(scls)   ...  @Base.addsub class Sub1(Base):   pass  class Sub2(Base):   pass  @Base.addsub class Sub3(Base):   pass 
like image 33
Ignacio Vazquez-Abrams Avatar answered Sep 29 '22 22:09

Ignacio Vazquez-Abrams