views:

226

answers:

8

Hi,

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...?

+7  A: 

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'
RichieHindle
I wanted to inherit the method, but not the variable. It would be weird to copy and paste the same method to all the subclasses :(
Emma
... But then, even if I specify SIZE in B, it still raises the same AttributeError: B instance has no attribute 'SIZE'.
Emma
I guess there's just no elegant way to do it. Steef's solution works, but it looks kinda ...mmm... hacky.
Emma
@RichieHindle In your example class B does not need its own __init__ method. The code: b=B() calls the one from A anyway.
quamrana
@quamrana: Good point - fixed.
RichieHindle
A: 

You can always just override it like this:

class B(A):  
  SIZE = 6
quamrana
I just want to make sure that all subclasses of A override it. (That there is no way they can inherit the variable)
Emma
A: 

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
Mark Roddy
Hi Mark,No, that's not what I want.With your code,b = B()print b.getsize() # prints 5I don't want B to be able to access the __SIZE variable in A.
Emma
+4  A: 

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.

Steef
Thanks!I see... This should do the job, but it doesn't look particularly elegant...
Emma
So what you wanted is to enforce that subclasses set their own value for the class attribute and not allow them to delegate to the value set by class A?
Mark Roddy
Yeah, I must admit it's kind of hackish, messing with metaclasses has this eerie, black magic aura about it
Steef
@Emma: it doesn't look elegant because -- generally -- it's not necessary. We're all adults. Simply look at the subclass code and be sure it overrides SIZE. Much simpler and more elegant to look at the code.
S.Lott
@S.Lott Thanks! In my case there are lots of subclasses to check, that is why I didn't want to go through the code and check manually.
Emma
A: 

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'
quamrana
A: 

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.

Coady
+1  A: 

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).

Alex Martelli
+1  A: 

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.

D.Shawley