Значение аргумента по умолчанию из переопределенного метода (предотвращение шаблона)

У меня есть базовый класс Base и два подкласса Foo и Bar. В Base я определяю функцию с необязательным аргументом. Этот аргумент либо задается, либо извлекается во время выполнения (не во время определения). Аргумент всегда извлекается одним и тем же способом, что приводит к тому, что подклассы имеют шаблонную строку кода (конечно, это одна строка, но это просто пример). Обычно это выглядело бы так:

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))

Теперь я придумал хак, который решает эту проблему, но я не считаю это надежным решением. Решение включает в себя определение частного метода, который обертывает переопределенный метод и предоставляет ему правильное значение. Оба метода переключаются в __init__, поэтому вызов/переопределение метода кажется «нормальным»:

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))

«Решение», которое я придумал, создает проблему, заключающуюся в том, что неясно, является ли имя необязательным, поскольку похоже, что оно не имеет значения по умолчанию.

В некоторых случаях Base является абстрактным базовым классом, а в некоторых — нет, поэтому я ищу решение, поддерживающее оба варианта.


person siebz0r    schedule 31.05.2013    source источник
comment
Почему не решение метаклассов?   -  person Mr_and_Mrs_D    schedule 22.11.2014


Ответы (2)


Как насчет использования декоратора? Это то, для чего они предназначены. Что-то типа:

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)

и оберните свои методы:

@get_at_runtime
def greet(self, name=None):
    print('Hello {name} this is Foo.'.format(name=name))
person Daniel Roseman    schedule 31.05.2013
comment
Таким образом, у вас все еще будет декоратор на Bar.great() и Foo.great(). Теперь декоратор - это шаблон, это просто перемещение проблемы imo. - person siebz0r; 31.05.2013

Привет, вы можете использовать метаклассы

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'

В этом случае любой класс, производный от Base, будет изменен, так что метод приветствия будет переименован в _greet, и будет создан метод приветствия, который будет запускать _greet.

person oleg    schedule 31.05.2013