Как вы можете добавить пользовательские атрибуты к объекту функции python в объявлении функции?

#python

#питон

Вопрос:

Я хочу создать богато аннотированную кодовую базу. Я вспомнил, что функции в Python являются объектами, поэтому они допускают произвольные атрибуты. Например:

 def foo(x:int, y:int)-gt;int:  '''This is a doc string'''  return x**2   y**2  foo.__notes__ = {  'Dependencies':['x', 'y'] }  foo.__notes__  gt;gt;gt; {'Dependencies': ['x', 'y']}  

Я хочу установить __notes__ атрибут из определения функции. Есть какие-нибудь идеи о том, как этого можно достичь?


ИЗМЕНИТЬ: Как указано в комментариях, __notes__ это плохое название для этого атрибута. _notes это более уместно.

Комментарии:

1. Ничто в определении не вычисляется до тех пор, пока вы фактически не вызовете функцию (а затем она вычисляется каждый раз, когда вы вызываете функцию).

2. Богато аннотированный, но для какой аудитории? Не для других программистов, потому что для этой цели у нас есть комментарии #. Не для автоматических генераторов документации, потому что для этой цели у нас есть строки документов и стандартный модуль pydoc. Какие свойства доступных инструментов вы считаете недостаточными?

3. @PaulCornelius Чтобы быть справедливым, строка документа-это просто способ установить строковый __doc__ атрибут для функции. Возможно, вам также понадобятся более богато набранные атрибуты.

4. @PaulCornelius — Я признаю, что отчасти это простое любопытство-посмотреть, можно ли это сделать. Я намерен использовать его для создания подсказок, псевдонимов, зависимостей от картографических данных, предположений о пропущенных значениях и т. Д. Я мог бы использовать для этого «большие» структуры данных (например, пользовательские классы), но подумал, что было бы интересно попробовать что-то новое. Сейчас у меня есть несколько вещей в __doc__ строке, и я извлекаю их с помощью регулярного выражения — это похоже на замену. Более того, я могу встраивать нестроковые объекты, такие как небольшие фреймы данных панд, в качестве примеров.

Ответ №1:

Вы можете использовать декоратор для добавления аннотации после определения функции.

 def add_note(k, v):  def _(f):  if not hasattr(f, '_notes'):  f._notes = {}  f._notes[k] = v  return f  return _   @add_note('Dependencies', ['x', 'y']) def foo(x: int, y: int) -gt; int:  return x**2   y**2  

Кроме того, имена dunder (например __notes__ ) зарезервированы для реализации; вы не должны изобретать свои собственные. Просто используйте обычное «частное» имя с префиксом один _ .

Комментарии:

1. Это выглядит очень чисто, спасибо. Можете ли вы придумать способ добавления объектов, объявленных внутри функции, в _notes атрибут?

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

3. Да, в целом я согласен. Я поговорю с вашим декоратором.

4. Я нахожу ваше использование подчеркиваний немного странным. Не могли бы вы объяснить? т. е. почему вы делаете def _(f): или return _ ?

Ответ №2:

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

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

Обратите внимание, что в моем примере ниже adder3 — который делает то, что вы хотите, и обновляет тело функции, не документируется с точки зрения инструмента самоанализа, пока он не будет выполнен хотя бы один раз.

adder2 с другой стороны, он настроен таким образом, что он корректирует свое поведение в соответствии со своей аннотацией, и эта аннотация всегда видна инструментам самоанализа.

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

  def adder1(x:int = 1, y: int = 2, z: int =3 )-gt;int:  '''This is a doc string'''  return x**2   y**2  def decorate(**kwds):  """ assign kwds as attributes to the function object """  def actual_decorator(func):  for k, v in kwds.items():  setattr(func, k, v)  return func  return actual_decorator  depends = {"x","y"} @decorate(_note=f"depends:{depends}", depends=depends) def adder2(x:int = 1, y: int = 2, z: int =3 )-gt;int:  # a function can totally refer to its name   # and use annotations to dynamically adjust its behavior  depends = adder2.depends  #sum up dependencies  li = []  for key in depends:  v = locals()[key] ** 2  li.append(v)   return sum(li)   def adder3(x:int = 1, y: int = 2, z: int =3 )-gt;int:  """ no note until function execution so this won't allow tools to inspect the module"""  _note = getattr(adder3,"_note",None)  if not _note:  adder3._note = "depends x,y"  return x**2   y**2  def adder4(x:int = 1, y: int = 2, z: int =3 )-gt;int:  """there is no guarantee that referring by its own name works"""  print("nnadder4.name:", adder4.__name__)  return (x**2   y**2) * 1000  adder4_saved = adder4 #swap name adder4 = adder2   for func in [adder1,adder2,adder3,adder4_saved]:  print(f"n{func.__name__} [note(before)={getattr(func,'_note',None)}] -gt; {func()} [note(after)={getattr(func,'_note',None)}]")    

выход:

  adder1 [note(before)=None] -gt; 5 [note(after)=None]  adder2 [note(before)=depends:{'y', 'x'}] -gt; 5 [note(after)=depends:{'y', 'x'}] adder3 [note(before)=None] -gt; 5 [note(after)=depends x,y]   adder4.name: adder2  adder4 [note(before)=None] -gt; 5000 [note(after)=None]