Python. Лучше иметь несколько методов или множество дополнительных параметров?

У меня есть класс, который обрабатывает запросы к удаленному API. Я бы хотел, чтобы сократить количество звонков, которые я делаю. Некоторые из методов моего класса делают одни и те же вызовы API (но по разным причинам), поэтому мне понравилась возможность "делиться" с кэшированным ответом API.

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

Вот как я их вижу, как вы думаете, лучше?

class A:

  def a_method( item_id, cached_item_api_response = None):
     """ Seems awkward having to supplied item_id even 
         if cached_item_api_response is given
     """
     api_response = None 
     if cached_item_api_response:
         api_response = cached_item_api_response
     else:
         api_response = ... # make api call using item_id

     ... #do stuff

Или это:

class B:

    def a_method(item_id = None, cached_api_response = None):
     """ Seems awkward as it makes no sense NOT to supply EITHER
         item_id or cached_api_response
     """
     api_response = None 
     if cached_item_api_response:
         api_response = cached_item_api_response
     elif item_id:
         api_response = ... # make api call using item_id
     else:
         #ERROR

     ... #do stuff

Или это более уместно?

class C:
   """Seems even more awkward to have different method calls"""   

   def a_method(item_id):
      api_response = ... # make api call using item_id
      api_response_logic(api_response)

   def b_method(cached_api_response):
      api_response_logic(cached_api_response)

   def api_response_logic(api_response):
      ... # do stuff

Ответ 1

Обычно при написании метода можно утверждать, что метод/объект должен делать одно, и он должен делать это хорошо. Если ваш метод получает все больше и больше параметров, которые требуют больше и больше ifs в вашем коде, это, вероятно, означает, что ваш код делает больше, чем одно. Особенно, если эти параметры вызывают совершенно другое поведение. Вместо этого, возможно, такое же поведение может быть вызвано разными классами и методами перегрузки.

Возможно, вы могли бы использовать что-то вроде:

class BaseClass(object):
    def a_method(self, item_id):
        response = lookup_response(item_id)
        return response

class CachingClass(BaseClass):
    def a_method(self, item_id):
        if item_id in cache:
            return item_from_cache
        return super(CachingClass, self).a_method(item_id)

    def uncached_method(self, item_id)
        return super(CachingClass, self).a_method(item_id)

Таким образом, вы можете разделить логику поиска ответа и кэширования, а также сделать его гибким для пользователя API, чтобы решить, хотят ли они возможности кэширования или нет.

Ответ 2

Нет ничего плохого в методе, используемом в вашем class B. Чтобы сделать это более очевидным с первого взгляда, что вам действительно нужно включить либо item_id, либо cached_api_response, я бы сначала поставил проверку на ошибку:

class B:

    def a_method(item_id = None, cached_api_response = None):
        """Requires either item_id or cached_api_response"""

        if not ((item_id == None) ^ (cached_api_response == None)):
            #error

        # or, if you want to allow both,
        if (item_id == None) and (cached_api_response == None):
            # error

        # you don't actually have to do this on one line
        # also don't use it if cached_item_api_response can evaluate to 'False'
        api_response = cached_item_api_response or # make api call using item_id

        ... #do stuff

Ответ 3

В конечном счете это решение, которое должно быть сделано для каждой ситуации. Я бы спросил, какой из этих двух более подходит:

  • Два совершенно разных алгоритма или действия с совершенно другой семантикой, даже если они могут быть переданы аналогичной информации
  • Единая концептуальная идея с последовательной семантикой, но с нюансом на основе ввода

Если первое самое близкое, переходите к отдельным методам. Если вторая ближайшая, перейдите к необязательным аргументам. Вы даже можете реализовать один метод, протестировав тип аргумента (ов), чтобы избежать передачи дополнительных аргументов.

Ответ 4

Это анти-шаблон OO.

class API_Connection(object):
    def do_something_with_api_response(self, response):
        ...

    def do_something_else_with_api_response(self, response):
        ...

У вас есть два метода для экземпляра, и вы передаете состояние между ними явно? Почему эти методы и не голые функции в модуле?

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

Например:

class API_Connection(object):
    def __init__(self, api_url):
        self._url = api_url
        self.cached_response = None

    @property
    def response(self):
        """Actually use the _url and get the response when needed."""
        if self._cached_response is None:
            # actually calculate self._cached_response by making our
            # remote call, etc
            self._cached_response = self._get_api_response(self._url)
        return self._cached_response

    def _get_api_response(self, api_param1, ...):
        """Make the request and return the api response"""

    def do_something_with_api_response(self):
        # just use self.response
        do_something(self.response)

    def do_something_else_with_api_response(self):
        # just use self.response
        do_something_else(self.response)

У вас есть кеширование, и любой метод, который нуждается в этом ответе, может выполняться в любом порядке без создания нескольких запросов api, потому что первый метод, который нуждается в self.response, рассчитает его, а каждый другой будет использовать кешированное значение. Надеюсь, это легко представить, расширяя это с помощью нескольких URL-адресов или вызовов RPC. Если вам нужно много методов, которые кэшируют возвращаемые значения, такие как response выше, вы должны заглянуть в декоратор memoization для ваших методов.

Ответ 5

Кэшированный ответ должен быть сохранен в экземпляре, а не пропущен, как сумка Skittles - что, если вы уронили ее?

Является ли item_id уникальным для каждого экземпляра, или может ли экземпляр запрашивать несколько экземпляров? Если он может иметь более одного, я бы пошел с чем-то вроде этого:

class A(object):

    def __init__(self):
        self._cache = dict()

    def a_method( item_id ):
        """Gets api_reponse from cache (cache may have to get a current response).
        """
        api_response = self._get_cached_response( item_id )
        ... #do stuff

    def b_method( item_id ):
        """'nother method (just for show)
        """
        api_response = self._get_cached_response( item_id )
        ... #do other stuff

    def _get_cached_response( self, item_id ):
        if item_id in self._cache:
            return self._cache[ item_id ]
        response = self._cache[ item_id ] = api_call( item_id, ... )
        return response

    def refresh_response( item_id ):
        if item_id in self._cache:
            del self._cache[ item_id ]
        self._get_cached_response( item_id )

И если вам, возможно, понадобится получить самую последнюю информацию о item_id, вы можете иметь метод refresh_response.