Default argument value from overridden method (preventing boilerplate)

戏子无情 提交于 2019-12-25 05:26:34

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!