Emma
Emma

Reputation: 55421

Making a variable non-inheritable in python

Is there a way to make a variable non-inheritable in python? Like in the following example: B is a subclass of A, but I want it to have its own SIZE value.

Could I get an Error to be raised (on __init__ or on getsize()) if B doesn't override SIZE?

class A:
   SIZE = 5
   def getsize(self): return self.SIZE

class B(A): pass

Edit: ... while inheriting the getsize() method...?

Upvotes: 3

Views: 2282

Answers (8)

D.Shawley
D.Shawley

Reputation: 59633

The only approach that I can add is to use hasattr(self.__class__, 'SIZE') in the implementation of getsize() and toss an exception if the attribute is not found. Something like:

class A:
   SIZE = 5
   def getsize(self):
     klass = self.__class__
     if hasattr(klass, 'SIZE') and 'SIZE' in klass.__dict__:
       return self.SIZE
     raise NotImplementedError('SIZE is not defined in ' + klass.__name__)

There is some magic still missing since the derived class could define a method named SIZE and getsize wouldn't detect it. You can probably do some type(klass.SIZE) magic to filter this out if you want to.

Upvotes: 1

Alex Martelli
Alex Martelli

Reputation: 882851

If metaclasses scare you (and I sympathize with that attitude!-), a descriptor could work -- you don't even have to make your custom descriptor (though that's easy enough), a plain good old property could work fine too:

class A(object):

  @property
  def SIZE(self):
    if type(self) is not A:
      raise AttributeError("Class %s MUST explicitly define SIZE!" % 
                            type(self).__name__)

  def getsize(self):
    return self.SIZE

Of course, this way you'll get the error only when an instance of a subclass of A which doesn't override SIZE actually tries to use self.SIZE (the metaclass approach has the advantage of giving the error earlier, when an errant subclass of A is created).

Upvotes: 2

A. Coady
A. Coady

Reputation: 57458

Another common idiom is to use NotImplemented. Think of it as the middle ground between metaclass enforcement and mere documentation.

class A:
   SIZE = NotImplemented

Now if a subclass forgets to override SIZE, the runtime errors will be immediate and obvious.

Upvotes: 0

quamrana
quamrana

Reputation: 39414

Another approach might be to get classes A and B to inherit from a third class instead of one from the other:

class X:
    def getsize(self):
        return self.SIZE
class A(X):
    SIZE = 5

class B(X): pass

a = A()
print a.getsize()
# Prints 5

b = B()
print b.getsize()
# AttributeError: B instance has no attribute 'SIZE'

Upvotes: 0

RichieHindle
RichieHindle

Reputation: 281875

Use a double-underscore prefix:

(Double-underscore solution deleted after Emma's clarification)

OK, you can do it like this:

class A:
    SIZE = 5
    def __init__(self):
        if self.__class__ != A:
            del self.SIZE

    def getsize(self):
        return self.SIZE

class B(A):
    pass

a = A()
print a.getsize()
# Prints 5

b = B()
print b.getsize()
# AttributeError: B instance has no attribute 'SIZE'

Upvotes: 8

Steef
Steef

Reputation: 34715

If you want to make absolutely sure that subclasses of A override SIZE, you could use a metaclass for A that will raise an error when a subclass does not override it (note that A is a new-style class here):

class ClassWithSize(type):
    def __init__(cls, name, bases, attrs):
        if 'SIZE' not in attrs:
            raise NotImplementedError('The "%s" class does not implement a "SIZE" attribute' % name)
        super(ClassWithSize, cls).__init__(name, bases, attrs)

class A(object):
    __metaclass__ = ClassWithSize

    SIZE = 5
    def getsize(self):
        return self.SIZE

class B(A):
    SIZE = 6

class C(A):
    pass

When you put the above in a module and attempt to import it, an exception will be raised when the import reaches the C class implementation.

Upvotes: 5

Mark Roddy
Mark Roddy

Reputation: 27986

It sounds like what you want is a private variable. In which case this is what you need to do:

class A:
    __SIZE = 5
    def getsize(self): 
        return self.__SIZE

    def setsize(self,newsize):
        self.__SIZE=newsize

class B(A): 
    pass

Upvotes: 0

quamrana
quamrana

Reputation: 39414

You can always just override it like this:

class B(A):  
  SIZE = 6

Upvotes: 0

Related Questions