Pull to refresh

Как применять и декораторы, и документацию в Python

Reading time 2 min
Views 3.4K
(Этот материал — для начинающих пользователей.)

Декораторы в Питоне конфликтуют с функцией документации help — если применить декоратор, вывод функции help изменится. Чтобы увидеть, пишем простой декоратор:

  1. def deco(fn):
  2.     def z(*args, **kwargs):
  3.         return fn(*args, **kwargs)
  4.    
  5.     return z



И 2 функции, с и без декоратора:

  1. def x(c, d):
  2.     """Это X"""
  3.     pass
  4.  
  5. @deco
  6. def y(a, b = 20):
  7.     """Это Y"""
  8.     pass



Теперь проверяем:

Help on function x:

>>> help(x)
x(c, d)
    Это X

>>> help(y)
Help on function z:

z(*args, **kwargs)


Вместо y мы видим описание внутренней функции декоратора.

Если вы делаете свой модуль и хотите снова использовать его через некоторое время, то внутренняя документация будет очень полезной — чтобы узнать, как вызвать конкретную функцию, не надо будет читать исходный код модуля. Но если нужны декораторы, придётся искать какое-то решение…

У функций есть свойства func_doc, func_name, которые, в принципе, можно установить принудительно:

  1. def deco(fn):
  2.     def z(*args, **kwargs):
  3.         return fn(*args, **kwargs)
  4.     z.func_doc = fn.func_doc
  5.     z.func_name = fn.func_name
  6.     return z
  7.  
  8. @deco
  9. def y(a, b = 20):
  10.     """Это Y"""
  11.     pass



>>> help(y)
Help on function y:

y(*args, **kwargs) # а должно быть (a, b = 20)
    Это Y</code>


Но список аргументов всё равно остался от внутренней функции. (Ответ на замечание читателя kmike: декоратор functools.wraps делает то же самое с тем же результатом)

Есть 2 решения: «чистое» в виде модуля и «грязное» в виде хака от Алекса Мартелли:

Модуль decorator



  1. from decorator import decorator
  2. @decorator
  3. def deco(fn):
  4.     def z(*args, **kwargs):
  5.         return fn(*args, **kwargs)
  6.    
  7.     return z
  8.  
  9. @deco
  10. def y(a, b = 20):
  11.     pass



>>> help(y)
Help on function y:

y(a, b = 20)


Хак Алекса Мартелли


Суть хака в том, чтобы записать все декорируемые функции в массив lookaside, а функцию inspect.getargspec (которая выдаёт данные о функции для help) заменяем другой, которая выдаёт записанные в lookaside данные, либо вызывает исходную getargspec.

  1. import functools, inspect
  2.  
  3. realgas = inspect.getargspec
  4. lookaside = dict()
  5.  
  6. def fakegas(f):
  7.     if f in lookaside:
  8.         return lookaside[f]
  9.     return realgas(f)
  10.  
  11. inspect.getargspec = fakegas
  12.  
  13. def deco(fn):
  14.     @functools.wraps(fn)
  15.     def z(*args, **kwargs):
  16.         return fn(*args, **kwargs)
  17.     lookaside[z] = realgas(fn) # обратите внимание, что
  18.     return z
  19.  
  20. @deco
  21. def x(a, b=23):
  22.   """Это X"""
  23.   return a + b



help(x)

Help on function x in module __main__:

x(a, b=23)
    Some doc for x.
Tags:
Hubs:
+14
Comments 2
Comments Comments 2

Articles