Reputation: 64
I am inheriting from a 3rd party class and want to change the default value on one of the class's methods. I can think of a few ways of doing this, but none feel clean.
So, if the base class is
class Base:
def foo(self, a=1, b=2, c=3):
return a, b, c
I want a class MyClass(Base)
where b
defaults to 7
instead. So,
my_obj = MyClass()
my_obj.foo() # (1, 7, 3)
my_obj.foo(b=5) # (1, 5, 3)
my_obj.foo(2, 4) # (2, 4, 3)
my_obj.foo(8) # (8, 7, 3)
I've been able to achieve the desired behavior by manually modifying args and kwargs, but it doesn't feel clean:
class OptionOne(Base):
def foo(self, *args, **kwargs):
# b is the second argument, so it is an arg if and only if there are 2 or more args
if len(args) < 2:
# If b wasn't passed as an arg, update kwargs to default b to 7.
kwargs['b'] = kwargs.get('b', 7)
return super().foo(*args, **kwargs)
Try/excepting a type error seems a little neater, but it isn't that readable:
class OptionTwo(Base):
def foo(self, *args, **kwargs):
try:
return super().foo(b=7, *args, **kwargs)
except TypeError:
# If b was already specified (as an arg or kwarg)
# "TypeError: foo() got multiple values for keyword argument 'b'"
# will be raised, so we don't override b in this case.
return super().foo(*args, **kwargs)
Is there a cleaner way of doing this that I am missing?
Upvotes: 2
Views: 1265
Reputation: 532303
You can manually change the defaults after defining a new method. It's not great, since defaults are stored only as a tuple without any explicit reference to the parameters they apply to.
class OptionTwo(Base):
def foo(self, a, b, c):
return super().foo(a, b, c)
foo.__defaults__ = (Base.foo.__defaults__[:1]
+ (7,)
+ Base.foo.__defaults__[2:])
On the plus side, this is equivalent to setting the default values in the definition of foo
itself, but doesn't require you to actually know the old defaults.
You could also abstract this to a decorator if you like.
def update_defaults(src, i, v):
def _decorator(f):
f.__defaults__ = src.__defaults__[:i] + (v,) + src.__defaults[i+1:]
return f
return _decorator
class OptionTwo(Base):
@update_defaults(Base.__defaults__, 1, 7)
def foo(self, a, b, c):
return super().foo(a, b, c)
Upvotes: 0
Reputation: 2302
Maybe try something like the below. This is basically overwriting the default value of b
in the constructor of the subclass.
class Base:
def __init__(self, a=1, b=2, c=3):
self.a = a
self.b = b
self.c = c
def print_me(self):
print(self.a, self.b, self.c)
class MyClass(Base):
def __init__(self, a=1, b=7, c=3):
super(MyClass, self).__init__(a, b, c)
test1 = Base()
test1.print_me()
test2 = MyClass()
test2.print_me()
test3 = MyClass(0, 1)
test3.print_me()
test4 = MyClass(b=5)
test4.print_me()
Output:
1 2 3
1 7 3
0 1 3
1 5 3
Upvotes: 1