Tanuj
Tanuj

Reputation: 557

different values for class variables in each subclass in python

I have a base class which is never going to be instantiated. There are different subclasses of this base class. Each subclass defines certain class variables where the name is same across all subclasses but the value is going to be different. For example

class Base:
    def display(self): 
        print self.logfile, self.loglevel
class d1(Base):
    logfile = "d1.log"
    loglevel = "debug"
    def temp(self):
        Base.display(self)
class d2(Base):
    logfile = "d2.log"
    loglevel = "info"
    def temp(self):
        Base.display(self)

What is the right way to design this such that I can enforce that if tomorrow any new subclass is defined, the person implementing the subclass should provide some values to these class variables and not miss defining them ?

Upvotes: 7

Views: 4178

Answers (4)

JoeZuntz
JoeZuntz

Reputation: 1169

You could do this with a simple check in the constructor as ciphor has suggested, but you could also use the abc.abstractproperty decorator in your base class to ensure that a property like the one you want is defined.

Then the interpreter will check that the logfile is created when an instance is instantiated:

import abc
#It is almost always a good idea to have your base class inherit from object
class Base(object):  
    __metaclass__ = abc.ABCMeta
    @abc.abstractproperty
    def logfile(self):
        raise RuntimeError("This should never happen")

class Nice(Base):
    @property
    def logfile(self):
        return "actual_file.log"

class Naughty(Base):
    pass

d=Nice()  #This is fine
print d.logfile  #Prints actual_file.log
d=Naughty()  #This raises an error: 
#TypeError: Can't instantiate abstract class Base with abstract methods logfile

See http://docs.python.org/library/abc.html and probably more useful: http://www.doughellmann.com/PyMOTW/abc/ for more details.

One more note - when you have your subclasses call Base.display(self) in your original example it would make more sense to have them call self.display(). The method is inherited from the base, and this way avoids hard-coding the base class. If you have more sub-subclasses then it makes the inheritance chain cleaner, too.

Upvotes: 7

Anders Waldenborg
Anders Waldenborg

Reputation: 3035

One alternative that doesn't require instantiating the classes for the checking to take place is to create a metaclass:

class BaseAttrEnforcer(type):
    def __init__(cls, name, bases, d):
        if 'loglevel' not in d:
            raise ValueError("Class %s doesn't define loglevel attribute" % name)
        type.__init__(cls, name, bases, d)

class Base(object):
    __metaclass__ = BaseAttrEnforcer
    loglevel = None

class d1(Base):
    logfile = "d1.log"
    loglevel = "debug"

class d2(Base):
    logfile = "d2.log"
    loglevel = "info"

class d3(Base):
    logfile = "d3.log"
    # I should fail

Upvotes: 10

ciphor
ciphor

Reputation: 8288

Maybe you can add checking code in the init function of Base class, like this:

class Base:
    logfile = ""
    loglevel = ""
    def __init__(self):
        if len(self.logfile) == 0 or len(self.loglevel) == 0:
            print 'WARNING: logfile & loglevel must be set!'
    def display(self): 
        print self.logfile, self.loglevel
class d1(Base):
    logfile = "d1.log"
    loglevel = "debug"
    def temp(self):
        Base.display(self)
class d2(Base):
    logfile = "d2.log"
    loglevel = "info"
    def temp(self):
        Base.display(self)

Upvotes: 2

Savino Sguera
Savino Sguera

Reputation: 3572

This should work

>>> class Base(object):
...  def __init__(self):
...   if not hasattr(self, "logfile"):
...    raise Exception("not implemented")
... 
>>> class d1(Base):
...  logfile='logfile1.log'
... 
>>> class d2(Base):
...  pass
... 
>>> d1()
<__main__.d1 object at 0x7d0d0>
>>> d2()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __init__
not implemented

Upvotes: 7

Related Questions