Over the years, I can't count how many times I wanted to do something like this in Python:
class A:
DEFAULT_MESSAGE = "Unspecified Country"
def do_it(message=DEFAULT_MESSAGE):
print(message)
class B(A):
DEFAULT_MESSAGE = "Mexico"
And have this result in:
> A().do_it()
Unspecified Country
> B().do_it()
Mexico
Without having to redefine the method:
class B(A):
DEFAULT_MESSAGE = "Mexico"
def do_it(message=DEFAULT_MESSAGE):
super().do_it(message)
Or do an arguably bad design pattern of setting the default message
to None
for the method, and instead reading the DEFAULT_MESSAGE
in the implementation of the method.
Is there a better, more DRY design-pattern that helps with this? Or is there some way to tell Python to re-evaluate the interfaces on parent class methods when defining a new, sub-class?
Here is a sketch of an approach using __init_subclass__
, note, this is highly dependent on your particular implementation, to get it to work more genreally, you would have to introspect the function signature, and maybe mark the functions you wish this to apply to somehow. But the idea is:
def _copy_func(f):
"""
There may be a better way to do this...
"""
import types
import functools
f_copy = types.FunctionType(f.__code__, f.__globals__, f.__name__, f.__defaults__)
f_copy = functools.update_wrapper(f_copy, f)
f_copy.__kwdefaults__ = f.__kwdefaults__ # for keyword-only default arguments
return f_copy
class A:
DEFAULT_MESSAGE = 'Unspecified Country'
def do_it(self, message=DEFAULT_MESSAGE):
print(message)
def __init_subclass__(cls, **kwargs):
"""
copy `do_it` and replace the default argument with the attribute
from the newly created class being initialized
"""
super().__init_subclass__(**kwargs)
# obviously, this relies on you knowing the name of the func ahead of time
do_it = _copy_func(cls.do_it)
# this relies on you knowing ahead of time the signature of the func
do_it.__defaults__ = (cls.DEFAULT_MESSAGE,)
cls.do_it = do_it
class B(A):
DEFAULT_MESSAGE = "Mexico"
A().do_it()
B().do_it()
This would all be very "magical" so I would probably not use this in production. But if this is really what you want, the above approach offers one solution.