Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enforcing Class Variables in a Subclass

I'm working on extending the Python webapp2 web framework for App Engine to bring in some missing features (in order to make creating apps a little quicker and easier).

One of the requirements here is that each subclass needs to have some specific static class variables. Is the best way to achieve this to simply throw an exception if they are missing when I go to utilise them or is there a better way?

Example (not real code):

Subclass:

class Bar(Foo):
  page_name = 'New Page'

page_name needs to be present in order to be processed here:

page_names = process_pages(list_of_pages)

def process_pages(list_of_pages)
  page_names = []

  for page in list_of_pages:
    page_names.append(page.page_name)

  return page_names
like image 709
A_Porcupine Avatar asked Feb 26 '14 15:02

A_Porcupine


People also ask

Do subclasses inherit class variables?

Classes called child classes or subclasses inherit methods and variables from parent classes or base classes. We can think of a parent class called Parent that has class variables for last_name , height , and eye_color that the child class Child will inherit from the Parent .

Can subclass access super class variables?

Subclasses inherit public methods from the superclass that they extend, but they cannot access the private instance variables of the superclass directly and must use the public accessor and mutator methods. And subclasses do not inherit constructors from the superclass.

Can a subclass inherit member variables and methods?

A subclass inherits all the members (fields, methods, and nested classes) from its superclass. Constructors are not members, so they are not inherited by subclasses, but the constructor of the superclass can be invoked from the subclass.

Can subclass inherit attributes?

A subclass “inherits” all the attributes (methods, etc) of the parent class. This means that a subclass will have everything that its “parents” have. You can then change (“override”) some or all of the attributes to change the behavior. You can also add new attributes to extend the behavior.


1 Answers

Python will already throw an exception if you try to use an attribute that doesn't exist. That's a perfectly reasonable approach, as the error message will make it clear that the attribute needs to be there. It is also common practice to provide reasonable defaults for these attributes in the base class, where possible. Abstract base classes are good if you need to require properties or methods, but they don't work with data attributes, and they don't raise an error until the class is instantiated.

If you want to fail as quickly as possible, a metaclass can prevent the user from even defining the class without including the attributes. The nice thing about a metaclass is that it's inheritable, so if you define it on a base class it is automatically used on any class derived on it.

Here's such a metaclass; in fact, here's a metaclass factory that lets you easily pass in the attribute names you wish to require.

def build_required_attributes_metaclass(*required_attrs):

    class RequiredAttributesMeta(type):
        def __init__(cls, name, bases, attrs):
            missing_attrs = ["'%s'" % attr for attr in required_attrs 
                             if not hasattr(cls, attr)]
            if missing_attrs:
                raise AttributeError("class '%s' requires attribute%s %s" %
                                     (name, "s" * (len(missing_attrs) > 1), 
                                      ", ".join(missing_attrs)))
    return RequiredAttributesMeta

Now to actually define a base class using this metaclass is a bit tricky. You have to define the attributes to define the class, that being the entire point of the metaclass, but if the attributes are defined on the base class they are also defined on any class derived from it, defeating the purpose. So what we'll do is define them (using a dummy value) then delete them from the class afterward.

class Base(object):
    __metaclass__ = build_required_attributes_metaclass("a", "b" ,"c")
    a = b = c = 0

del Base.a, Base.b, Base.c

Now if you try to define a subclass, but don't define the attributes:

class Child(Base):
    pass

You get:

AttributeError: class 'Child' requires attributes 'a', 'b', 'c'

N.B. I don't have any experience with Google App Engine, so it's possible it already uses a metaclass. In this case, you want your RequiredAttributesMeta to derive from that metaclass, rather than type.

like image 140
kindall Avatar answered Oct 12 '22 00:10

kindall