问题
I have a base class Base
and two sub classes Foo
and Bar
. In Base
I define a function with an optional argument. This argument is either given or fetched at runtime (not at definition time). The argument is always fetched in the same exact way, which leads to subclasses having a boilerplate line of code (sure it's one line, but it's just an example). Normally this would look like this:
class Base(object):
def greet(self, name=None):
pass
class Foo(Base):
def greet(self, name=None):
name = name or get_name_at_runtime()
print('Hello {name} this is Foo.'.format(name=name))
class Bar(Base):
def greet(self, name=None):
name = name or get_name_at_runtime()
print('Hello {name} this is Bar.'.format(name=name))
Now I came up with a hack that solves this problem, but I don't find this to be a solid solution. The solution involves defining a private method that wraps the overridden method and supplies it with the correct value. Both methods are switched in the __init__
so calling/overriding the method feels 'normal':
class Base(object):
def __init__(self):
self.greet, self.__greet = self.__greet, self.greet
def greet(self, name):
pass
def __greet(self, name=None):
self.__greet(name or get_name_at_runtime())
class Foo(Base):
def greet(self, name):
print('Hello {name} this is Foo.'.format(name=name))
class Bar(Base):
def greet(self, name):
print('Hello {name} this is Bar.'.format(name=name))
The 'solution' I came up with poses the problem that it's not clear that name is optional since it looks like it doesn't have a default value.
In some cases Base
is an abstract base class and in some cases it's not, so I'm looking for a solution that supports both.
回答1:
How about using a decorator? This is the sort of thing they are meant for. Something like:
def get_at_runtime(func):
def wrapped(*args, **kwargs):
if kwargs.get('name', None) is None:
kwargs['name'] = get_name_at_runtime()
func(*args, **kwargs)
and wrap your methods:
@get_at_runtime
def greet(self, name=None):
print('Hello {name} this is Foo.'.format(name=name))
回答2:
Hi You can use metaclasses
class DefaultNameType(type):
def __new__(cls, name, bases, attrs):
if 'greet' in attrs:
attrs['_greet'] = attrs.pop('greet')
def greet(self, name=None):
name = name or self.get_name_at_runtime()
return self._greet(name)
attrs['greet'] = greet
print attrs
return super(DefaultNameType, cls).__new__(cls, name, bases, attrs)
class Base(object):
__metaclass__ = DefaultNameType
def get_name_at_runtime(self):
return 'foo'
def greet(self, name):
pass
class Foo(Base):
def greet(self, name):
print('Hello {name} this is Foo.'.format(name=name))
class Bar(Base):
def greet(self, name):
print('Hello {name} this is Bar.'.format(name=name))
def get_name_at_runtime(self):
return 'foo1'
In this case any class derived from Base
will be modified so that method greet will be renamed to _greet and greet method will be created which will run _greet
来源:https://stackoverflow.com/questions/16852860/default-argument-value-from-overridden-method-preventing-boilerplate