Создание динамических строк документации в дескрипторе Python

Я пытаюсь динамически генерировать определения классов (для упаковки расширения C ++). Следующий дескриптор работает нормально, за исключением случаев, когда я пытаюсь получить доступ к строке документации для поля с помощью help (), он предоставляет документацию по умолчанию для дескриптора, а не для самого поля. Однако, когда я действительно помогу (имя класса), он извлекает строку документации, переданную дескриптору:

class FieldDescriptor(object):
    def __init__(self, name, doc='No documentation available.'):
        self.name = name
        self.__doc__ = doc

    def __get__(self, obj, dtype=None):
        if obj is None and dtype is not None:
            print 'Doc is:', self.__doc__
            return self
        return obj.get_field(self.name)

    def __set__(self, obj, value):
        obj.set_field(self.name, value)

class TestClass(object):
    def __init__(self):
        self.fdict = {'a': None, 'b': None}

    def get_field(self, name):
        return self.fdict[name]

    def set_field(self, name, value):
        self.fdict[name] = value

fields = ['a', 'b']
def define_class(class_name, baseclass):
    class_obj = type(class_name, (baseclass,), {})
    for field in fields:
        setattr(class_obj, field, FieldDescriptor(field, doc='field %s in class %s' % (field, class_name)))
    globals()[class_name] = class_obj


if __name__ == '__main__':
    define_class('DerivedClass', TestClass)
    help(DerivedClass.a)
    help(DerivedClass)
    v = DerivedClass()
    help(v.a)

"python test.py" выводит:

Doc is: field a in class DerivedClass
Help on FieldDescriptor in module __main__ object:

class FieldDescriptor(__builtin__.object)
 |  Methods defined here:
 |  
 |  __get__(self, obj, dtype=None)
 |  
 |  __init__(self, name, doc='No documentation available.')
 |  
 |  __set__(self, obj, value)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

Doc is: field a in class DerivedClass
Doc is: field b in class DerivedClass
Help on class DerivedClass in module __main__:

class DerivedClass(TestClass)
 |  Method resolution order:
 |      DerivedClass
 |      TestClass
 |      __builtin__.object
 |  
 |  Data descriptors defined here:
 |  
 |  a
 |      field a in class DerivedClass
 |  
 |  b
 |      field b in class DerivedClass
 |  
 |  ----------------------------------------------------------------------
 |  Methods inherited from TestClass:
 |  
 |  __init__(self)
 |  
 |  get_field(self, name)
 |  
 |  set_field(self, name, value)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from TestClass:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

Help on NoneType object:

class NoneType(object)
 |  Methods defined here:
 |  
 |  __hash__(...)
 |      x.__hash__()  hash(x)
 |  
 |  __repr__(...)
 |      x.__repr__()  repr(x)

Есть идеи, как получить descriptor.__doc__ за help(class.field)? И есть ли способ обойти это и иметь что-то вроде функции получения для документа вместо необходимости хранить строку документа в дескрипторе?

нравиться:

class FieldDescriptor(object):
    def __init__(self, name, doc='No documentation available.'):
        self.name = name
        self.__doc__ = doc

    def __get__(self, obj, dtype=None):
        if obj is None and dtype is not None:
            print 'Doc is:', self.__doc__
            return self
        return obj.get_field(self.name)

    def __set__(self, obj, value):
        obj.set_field(self.name, value)

    # This is what I'd like to have
    def __doc__(self, obj, dtype):
       return dtype.generate_docstring(self.name)

ОБНОВЛЕНИЕ: На самом деле я начал с этого определения __get__:

def __get__(self, obj, dtype=None):
    return obj.get_field(self.name)

Проблема заключалась в том, что когда я сказал:

help(DerivedClass.a)

Python выдал исключение, указывающее, что я пытался вызвать None.get_field. Таким образом, help() вызывает метод __get__ с obj=None и dtype=DerivedClass. Вот почему я решил вернуть экземпляр FieldDescriptor, когда obj = None и dtype! = None. Мое впечатление было help(xyz) попыток отобразить xyz.__doc__. По этой логике, если __get__ возвращает descriptor_instance, то descriptor_instance.__doc__ должен быть напечатан с помощью help (), что справедливо для всего класса [help(DerivedClass)], но не для одного поля [help(DerivedClass.a)].


person subhacom    schedule 06.04.2012    source источник
comment
Я уверен, что это все есть, но не могли бы вы уточнить, какие вызовы выдают неправильный вывод справки? Слишком много усилий, чтобы угадать, чего вы ожидали, прочитав код.   -  person alexis    schedule 06.04.2012
comment
Как указал jsbueno, это справка (DerivedClass.a), которая отображает документацию для дескриптора вместо документации для поля (сохраненной в дескрипторе .__ doc__).   -  person subhacom    schedule 06.04.2012
comment
@subhacom Вы когда-нибудь находили удовлетворительный ответ?   -  person Jérémie    schedule 04.07.2019
comment
@ Jérémie Насколько я помню и упоминал в моем комментарии к ответу jsbueno, похоже, это связано с тем, как работает встроенная реализация help python. В итоге я написал для этого специальную справочную функцию. Это была часть довольно сложного проекта, но код здесь: github.com/BhallaLab/moose-core/blob/master/python/moose/. Код интерфейса Python / C ++ находится в pymoose каталоге того же репо.   -  person subhacom    schedule 04.07.2019


Ответы (1)


Что происходит, так это то, что когда вы запрашиваете help(DerivedClass.a) - python вычисляет выражение в круглых скобках - объект, возвращаемый методом __get__ дескриптора - и ищет справку (включая строку документации) по этому объекту.

Один из способов добиться этой работы, включая создание динамической строки документации, состоит в том, чтобы ваш __get__ метод возвращал динамически сгенерированный объект, содержащий желаемую строку документа. Но этот объект сам должен быть подходящим прокси-объектом по отношению к исходному объекту, и это создаст некоторые накладные расходы на ваш код - и множество особых случаев.

В любом случае, единственный способ заставить его работать так, как вы хотите, - это изменить объекты, возвращаемые самим __get__, чтобы они вели себя так, как вам хотелось бы.

Я бы предположил, что если все, что вам нужно в справке, - это немного информации, как вы делаете, возможно, вы хотите, чтобы объекты, возвращаемые из вашего __get__, относились к классу, который определяет метод __repr__ (а не просто строку __doc__).

person jsbueno    schedule 06.04.2012
comment
__repr__ подход не работает. Вместо этого он печатает документацию класса, реализующего __repr__. Использование свойств вместо обычных дескрипторов работает, но страдает от статического хранения строки документации. Отслеживание функции справки с помощью pdb показало, что тесты в pydoc.help() не зависят от дескрипторов, определенных в Python, хотя они заботятся о свойствах и различных дескрипторах, определенных с помощью C API. В любом случае спасибо за ваш вклад, это побудило меня попробовать другие способы. - person subhacom; 07.04.2012