Использование строки документа из одного метода для автоматической перезаписи строки документа из другого метода

StackOverflow https://stackoverflow.com/questions/71817

  •  09-06-2019
  •  | 
  •  

Вопрос

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

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

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

Это было полезно?

Решение

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

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

Другие советы

Есть ли причина, по которой вы не можете переопределить базовый класс 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...

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

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

В основном:

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__

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

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

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

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

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

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

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

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!"
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top