#python #python-3.x #oop
#python #python-3.x #ооп
Вопрос:
У меня есть класс, который предоставляет простой интерфейс к API. Я хочу определить метод в этом классе для каждого маршрута.
Поскольку большинство маршрутов одинаковы, большая часть функциональности может быть включена в более универсальную функцию, причем многие маршруты являются просто частично применяемой версией этой функции
class MyAPIWrapper:
def _get_results(self, route, **params):
# Do the important stuff here
results = ...
return results
def get_customers(self):
return self._get_results(self, 'customers')
def get_transactions(self):
return self._get_results(self, 'transactions')
# etc, etc, etc
Однако очевидно, что это все еще приводит к значительному количеству шаблонов в определении класса.
Одной из альтернатив является добавление нового метода, который добавляет метод каждого маршрута программно:
import functools
class MyAPIWrapper:
def __init__(self):
self._init_route_methods()
def _get_results(self, route, **params):
# Do the important stuff here
results = ...
return results
def _init_route_methods(self):
for route in ['customers', 'transactions', ...]:
route_fn = functools.partial(self. _get_results, route)
setattr(self, f'get_{route}', route_fn)
Преимущество этого заключается в сокращении количества шаблонов и упрощении добавления / удаления маршрутов. Однако добавление методов при инициализации кажется мне несколько неэлегантным.
Есть ли лучший и / или более идиоматичный способ сделать это?
Комментарии:
1. Это не методы. Это просто функциональные объекты, которые являются атрибутами экземпляра. Методы принадлежат классу . Но, похоже, это то, что вы хотите сделать, нет? Итак, что именно с этим не так? Но если вам нужны реальные методы, не выполняйте цикл в
__init__
, просто выполните цикл вне определения класса, добавив методы в класс2. А, я понимаю, что вы имеете в виду. Да, это было бы немного лучше (я действительно хочу, чтобы это были методы , а не атрибуты)
3. Да, однако атрибут экземпляра, который по сути является функцией, частично применяемой с экземпляром в качестве первого атрибута, по сути, тот же самый, если только вы не создаете много экземпляров (в этом случае вы избегаете присущего реальным методам аспекта шаблона с минимальным весом. Это, вероятно , было бы немного быстрее, обратите внимание, вызовы метода pyhton создают объект метода при каждом вызове
Ответ №1:
Вы можете быть удивлены, что это сработает:
class MyAPIWrapper:
def _get_results(self, route, **params):
# Do the important stuff here
return route
for route in ['customers', 'transactions']:
exec("""
def get_{0}(self):
return self._get_results('{0}')
""".strip().format(route))
MyAPIWrapper().get_customers() # return customers
MyAPIWrapper().get_transactions() # return transactions
Плюсы
- Хорошая читаемость
- Минимальное изменение кода
Минусы
Пожалуйста, обратите внимание, что это exec
требует немного больше накладных расходов, чем setattr(MyAPIWrapper,'get_%s'%route, ...)
, что имеет значение только в том случае, если вы собираетесь создавать миллионы методов в цикле.
Если вы хотите сделать то же самое со многими различными классами APIWrapper, рассмотрите возможность использования вместо этого декоратора класса.
Комментарии:
1. Это кажется совершенно прекрасным предложением, почему бы вам не предложить это?
2. Во время выполнения это будет медленнее, чем просто
setattr(MyAPIWrapper,'get_%s'%route, ...)
. Но это требует минимального изменения кода.3. Вероятно, это не проблема, они собираются создавать миллионы атрибутов в замкнутом цикле? Вероятно, нет. Единовременное несколько более медленное создание дюжины (или даже сотни) методов не окажет существенного влияния на производительность. Дополнительным преимуществом этого является сохранение всех приятных особенностей, таких как полные имена для функций, для лучшей отладки.
4. Хороший момент. Спасибо за комментарий. Я соответствующим образом отредактировал ответ.
5. Спасибо за ваш ответ. Это работает, и я (в основном) согласен с плюсами. Хотя, я думаю, вы теряете некоторые вещи, такие как подсветка синтаксиса и завершение кода, что немного ухудшает читаемость. Кроме того (и я знаю, что это не особенно убедительная критика, но), метапрограммирование со строками кажется мне ужасно похожим на «запах» плохого кода, и я думаю, что предпочитаю другой ответ. Я ошибаюсь, думая так?