2008-09-16 4 views
2

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

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

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

+0

Есть ли веская причина, почему docstring для выполнения не может просто читать «делает ... а затем вызывает _execute, какие подклассы, как ожидается, будут переопределены, посмотрите на документацию для _execute для деталей "? Это похоже на самый Pythonic подход, если ожидается, что пользователи будут предоставлять базовые классы. – 2008-09-16 14:41:23

+0

Это просто неправильно, если пользователь вводит help (o.execute), он делает a) не получает информацию, которую он, вероятно, хотел, и b) ему предлагается посмотреть на какой-то сомнительный метод _execute, возможно, он даже не знал, что существует. Для наивного пользователя библиотеки это слишком много деталей реализации. – nikow 2008-09-27 17:27:49

ответ

4

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

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 
0

Ну, строка doc хранится в __doc__, поэтому было бы не слишком сложно повторно назначить его на основе 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 должен быть повторно объявлен, что строка документ прикрепляется к версии для выполнения SubClass и не для MyClass (который в противном случае мешать другим суб- классы).

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

+0

Дело в том, что я хочу, чтобы пользователи писали подклассы без написания кода шаблона для манипуляций с docstring. Любая идея, как справиться с метаклассами лучше, чем мое уродливое решение, упомянутое выше? – nikow 2008-09-16 13:46:41

2

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

+0

Мотивация заключается в том, что пользователи пишут свои собственные подклассы. Теперь я уточнил это в первоначальном вопросе. – nikow 2008-09-16 13:51:57

0

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

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

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

Если вы хотите неаккуратный и аккуратный способ убедиться, что docstring для выполнения динамически сгенерирован, вы можете использовать протокол дескриптора, который будет значительно меньше кода, чем другие предложения здесь. Это раздражает, потому что вы не можете просто установить дескриптор существующей функции, а это означает, что выполнение должно быть записано как отдельный класс с помощью метода __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!" 
1

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