Использование строки документа из одного метода для автоматической перезаписи документа из другого метода - PullRequest
3 голосов
/ 16 сентября 2008

Проблема: у меня есть класс, который содержит шаблонный метод execute, который вызывает другой метод _execute. Подклассы должны перезаписывать _execute для реализации определенных функций. Эта функциональность должна быть документирована в строке документации _execute. Опытные пользователи могут создавать свои собственные подклассы для расширения библиотеки. Однако другой пользователь, имеющий дело с таким подклассом, должен использовать только execute, поэтому он не увидит правильную строку документации, если он использует help(execute).

Поэтому было бы неплохо изменить базовый класс таким образом, чтобы в подклассе строка документа execute автоматически заменялась строкой документа _execute. Есть идеи, как это можно сделать?

Я думал о метаклассах, чтобы сделать это полностью прозрачным для пользователя.

Ответы [ 5 ]

4 голосов
/ 16 сентября 2008

Что ж, если вы не против скопировать оригинальный метод в подкласс, вы можете использовать следующую технику.

import new

def copyfunc(func):
    return new.function(func.func_code, func.func_globals, func.func_name,
                        func.func_defaults, func.func_closure)

class Metaclass(type):
    def __new__(meta, name, bases, attrs):
        for key in attrs.keys():
            if key[0] == '_':
                skey = key[1:]
                for base in bases:
                    original = getattr(base, skey, None)
                    if original is not None:
                        copy = copyfunc(original)
                        copy.__doc__ = attrs[key].__doc__
                        attrs[skey] = copy
                        break
        return type.__new__(meta, name, bases, attrs)

class Class(object):
    __metaclass__ = Metaclass
    def execute(self):
        '''original doc-string'''
        return self._execute()

class Subclass(Class):
    def _execute(self):
        '''sub-class doc-string'''
        pass
2 голосов
/ 16 сентября 2008

Есть ли причина, по которой вы не можете напрямую переопределить функцию execute базового класса?

class Base(object):
    def execute(self):
        ...

class Derived(Base):
    def execute(self):
        """Docstring for derived class"""
        Base.execute(self)
        ...stuff specific to Derived...

Если вы не хотите делать выше:

Объекты метода не поддерживают запись в атрибут __doc__, поэтому вы должны изменить __doc__ в фактическом функциональном объекте. Поскольку вы не хотите переопределять один из базовых классов, вам нужно будет предоставить каждому подклассу собственную копию execute:

class Derived(Base):
    def execute(self):
        return Base.execute(self)

    class _execute(self):
        """Docstring for subclass"""
        ...

    execute.__doc__= _execute.__doc__

но это похоже на окольный способ переопределения execute ...

1 голос
/ 16 сентября 2008

Посмотрите на декоратор functools.wraps (); он делает все это, но я не знаю, если вы можете заставить его работать в правильном контексте

0 голосов
/ 16 сентября 2008

Я согласен, что самый простой, самый Pythonic способ достичь этого - просто переопределить execute в ваших подклассах и заставить его вызывать метод execute базового класса:

class Sub(Base):
    def execute(self):
        """New docstring goes here"""
        return Base.execute(self)

Это очень маленький код для выполнения того, что вы хотите; Единственным недостатком является то, что вы должны повторять этот код в каждом подклассе, который расширяет Base. Однако это небольшая цена за поведение, которое вы хотите.

Если вы хотите неаккуратный и многословный способ убедиться, что строка документации для исполнения генерируется динамически, вы можете использовать протокол дескриптора, который будет значительно меньше кода, чем другие предложения здесь. Это раздражает, потому что вы не можете просто установить дескриптор для существующей функции, что означает, что execute должен быть написан как отдельный класс с __call__ методом.

Вот код для этого, но имейте в виду, что мой приведенный выше пример намного проще и более Pythonic:

class Executor(object):
    __doc__ = property(lambda self: self.inst._execute.__doc__)

    def __call__(self):
        return self.inst._execute()

class Base(object):
    execute = Executor()

class Sub(Base):
    def __init__(self):
        self.execute.inst = self

    def _execute(self):
        """Actually does something!"""
        return "Hello World!"

spam = Sub()
print spam.execute.__doc__  # prints "Actually does something!"
help(spam)                  # the execute method says "Actually does something!"
0 голосов
/ 16 сентября 2008

Хорошо, строка документа хранится в __doc__, поэтому было бы не сложно переназначить ее на основе строки документа _execute по факту.

В основном:

</p> <pre> class MyClass(object): def execute(self): '''original doc-string''' self._execute() class SubClass(MyClass): def _execute(self): '''sub-class doc-string''' pass # re-assign doc-string of execute def execute(self,*args,**kw): return MyClass.execute(*args,**kw) execute.__doc__=_execute.__doc__ </pre> <p>

Необходимо выполнить повторное объявление Execute, чтобы строка документа была присоединена к версии execute для SubClass, а не для MyClass (что в противном случае мешало бы другим подклассам).

Это не очень аккуратный способ сделать это, но от POV пользователя библиотеки он должен дать желаемый результат. Затем вы можете обернуть это в мета-класс, чтобы облегчить его для подклассов.

...