Измените docstring метода класса в декораторе на основе атрибута класса

Начнем с этого:

class Example(object):

 change_docstring = True

 @add_to_docstring(" (additional content!)")
 def example_method(self):
 """Example docstring."""
 pass

То, что я пытаюсь сделать, это позволить декоратору @add_to_docstring добавить строку параметра в docstring метода, только если атрибут change_docstring имеет значение True. Я не хочу передавать что-либо еще в декоратор.

Это решение работает, но это не совсем то, что я ищу.

def add_to_docstring(text):

 def decorator(original_method):

 def wrapper(self):
 """wrapper docstring."""
 wrapper.__doc__ = original_method.__doc__

 if self.change_docstring:
 wrapper.__doc__ += text

 return original_method(self)

 return wrapper

 return decorator

Позволь мне объяснить.

Вышеупомянутое решение только изменяет docstring, если выполняется example_method. Докстор не изменяется при загрузке класса, метода и т.д.

>>> Example.example_method.__doc__
"wrapper docstring."
>>>
>>> Example().example_method()
>>> Example.example_method.__doc__
"Example docstring. (additional content!)"

Это то, что я хотел бы, чтобы выход команды был следующим:

>>> Example.example_method.__doc__
"Example docstring. (additional content!)"

Опять же, я не хочу передавать что-либо еще в декоратор.

Обновить

Для дополнительного пояснения это должно позволить декоратору изменить докштук метода, и чтобы это изменение отражалось в сгенерированной документации Sphinx. Сфинкс загружает все и собирает докстроны, но он ничего не делает.

Основываясь на выбранном решении, я добавил модульную переменную в модуль декораторов и выставил метод, чтобы отключить функцию изменения docstring в декораторах. Чтобы отключить функцию универсально, я тогда вызывал эту функцию отключения в своих файлах conf.py Sphinx следующим образом:

# import the decorators module
from some_modules import decorators
# disable the docstring change feature
decorators.disable_docstring_change()

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

2 ответа

Как упоминалось в Martijn Pieter, ответьте на "Доступ к переменным класса из понимания списка в определении класса", вы не можете получить доступ к атрибутам класса, если находитесь в новой области в классе. Этот ответ в основном фокусируется на выражениях понятий и выражений в классе, но то же самое относится к обычным функциям, включая декораторы.

Простым способом это сделать change_docstring глобальным и определить его непосредственно перед классом, чтобы вы могли легко установить его на основе класса по классам. Другой вариант - сделать его аргументом декоратора, но вы сказали, что предпочтете этого не делать. Вот небольшая демонстрация, которая работает как на Python 2 и 3.

def add_to_docstring(text):
 def decorator(original_method):
 def wrapper(self):
 return original_method(self)
 wrapper.__doc__ = original_method.__doc__
 if change_docstring:
 wrapper.__doc__ += text
 return wrapper
 return decorator

change_docstring = True
class Example(object):
 @add_to_docstring(" (additional content!)")
 def example_method(self):
 """Example docstring."""
 pass

change_docstring = False
class Other(object):
 @add_to_docstring(" (more content!)")
 def example_method(self):
 """Other docstring."""
 pass

print(Example.example_method.__doc__)
print(Other.example_method.__doc__)

вывод

Example docstring. (additional content!)
Other docstring.


Украсить и пометить методы

Нам не нужно много заботиться о функциях сигнатур, будь то связанных или несвязанных - мы просто добавляем атрибут с дополнительным текстом на объект функции, каким бы он ни был.

def add_to_docstring(text):
 def func(f):
 f.__add_to_docstring = text
 return f
 return func

Украсьте класс, чтобы указать, что мы хотим, чтобы тегированные методы выполнялись

Используя декоратор класса, мы можем указать, что хотим соблюдать тегированные методы и изменять докстроны. Мы просматриваем вызываемые объекты, проверяем, декорированы ли они объекты, которые содержат что-то, что нужно добавить в docstring, и внесите соответствующие изменения перед возвратом нового типа с различными функциями docstrings.

def change_docstrings(cls):
 for obj in vars(cls).values():
 if callable(obj) and hasattr(obj, '__add_to_docstring'):
 obj.__doc__ = (obj.__doc__ or '') + obj.__add_to_docstring
 del obj.__add_to_docstring
 return cls

Объединяя это вместе

@change_docstrings
class Example:
 @add_to_docstring('(cabbage!)')
 def example(self):
 """ something here """
 pass

Проверка Example.example.__doc__ мы получаем - ' something here (cabbage!)' И если вы удалите декоратор класса @change_docstrings вы не получите никаких изменений.

Обратите внимание, что это перемещает change_docstrings из класса и, независимо от того, украшаете вы или нет, однако он позволяет построить такую конструкцию, как:

unchanged_docstrings = Example
changed_docstrings = change_docstrings(Example)

licensed under cc by-sa 3.0 with attribution.