4

In the following code:

class A(object):
  VALUE = 1
  def __init__(self, value=VALUE):
    self.value = value

class B(A):
  VALUE = 2

i'd expect that B().value should be equal to 2, however:

B().value = 1

Is there an elegant way to define a class hierarchy where child classes can just declare class variables they want to override and have them be defaults for the instance variables? I still want to allow for these to be changed on a per-instance level, eg.

b = B(value=3)
SpongeBob
  • 145
  • 8

2 Answers2

10

This is another default arguments question. The point is that when you write

def foo(value=VALUE):

the code inside the function is compiled and made into a function object. It is at this time -- not at call time! -- that the default arguments are stored. So by the time you have defined B it is too late: the default value of foo is already set and changing VALUE won't have any effect.

If this seems a strange thing to do, suppose foo was a global function:

default = 3
def foo(x=default): pass

Then any other code, anywhere, could screw up foo by doing

global default
default = 4

This is arguably just as confusing.


To force the lookups to be done at runtime not compile time, you need to put them inside the function:

def foo(value=None):
    self.value = self.VALUE if value is None else value

or (not quite the same but prettier)

self.value = value or self.VALUE

(This is different because it will treat any 'falsy' value as a sentinel -- that is, 0, [], {} etc will all be overwritten by VALUE.)


EDIT: @mgilson pointed out another way of doing this:

def foo(**kwargs):
    self.value = kwargs.get("value", self.VALUE)

This is neater in that it doesn't require you to make a sentinel value (like None or object(), but it does change the argument specification of foo quite fundamentally since now it will accept arbitrary keyword arguments. Your call.

Community
  • 1
  • 1
Katriel
  • 120,462
  • 19
  • 136
  • 170
  • The only way to do that properly with allowance for `None` is using `**kwargs` and checking for `"value" in kwargs` though. – Jonas Schäfer Jul 20 '12 at 14:44
  • 3
    @JonasWielicki: you could use a sentinel `NOT_VALUE=object()` if `None` is allowed. You don't need `**kwargs` – jfs Jul 20 '12 at 14:45
  • Thanks @Sven, fail me. Agreed that `object()` sentinels are the way to go if (and only if) `None` is an acceptable value. – Katriel Jul 20 '12 at 14:47
  • PS I actually like the kwargs variant (because sentinels are icky) but allowing varargs obviously has wider consequences. – Katriel Jul 20 '12 at 14:49
  • 1
    This answer is much better than what I posted. Deleted mine and (+1) to you! (and I like the `kwargs` variant too). – mgilson Jul 20 '12 at 14:49
1

The default value should not be define in the func declaration line, otherwise, when the python reads this line, the default value will be focused, and whatever you change the VALUE later, the default value will not be changed.

You could write it as follows:

class A:
    VALUE = 1
    def __init__(self, value=None):
        if value is None:
            value = self.VALUE
        self.value = value

class B(A):
    VALUE = 2

print(A().value)
print(B().value)
aasa
  • 207
  • 1
  • 7