【问题标题】:Python - Better to have multiple methods or lots of optional parameters?Python - 最好有多种方法或大量可选参数?
【发布时间】:2011-08-22 17:12:44
【问题描述】:

我有一个向远程 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

【问题讨论】:

    标签: python api coding-style optional-parameters caching


    【解决方案1】:

    通常在编写方法时,人们可能会争辩说一个方法/对象应该做一件事并且应该做得很好。如果您的方法获得越来越多的参数,这些参数在您的代码中需要越来越多的 if,这可能意味着您的代码正在做的不止一件事。特别是如果这些参数触发完全不同的行为。相反,通过拥有不同的类并让它们重载方法,可能会产生相同的行为。

    也许你可以使用类似的东西:

    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 的用户灵活地决定他们是否需要缓存功能。

    【讨论】:

    • 感谢您的编辑,但通常我会建议使用委托而不是继承,因为它使类更灵活。
    • 如果这不是您的意图,很抱歉更改它。这只是一个 Python 问题,您的代码不是 Python,所以我做出了最好的猜测——随意回滚或更改它以反映您的意图。 (好答案,已经得到了我的 +1)
    【解决方案2】:

    您的class B 中使用的方法没有任何问题。为了让您一目了然地知道您实际上需要包含item_idcached_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】:

      最终,这是必须针对每种情况做出的判断。我会问自己,这两者中哪一个更适合:

      1. 两种完全不同的算法或动作,具有完全不同的语义,尽管它们可能传递相似的信息
      2. 一个单一的概念想法,语义一致,但基于输入的细微差别

      如果第一个最接近,请使用单独的方法。如果第二个最接近,请使用可选参数。您甚至可以通过测试参数的类型来实现单个方法以避免传递额外的参数。

      【讨论】:

        【解决方案4】:

        这是一个面向对象的反模式。

        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's 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 装饰器。

        【讨论】:

        • 我喜欢整体概念,但如果调用之间存在不同的参数,我发现从do_something 方法到_get_api_response 方法会有些困难。不过,仍然值得 +1。
        【解决方案5】:

        缓存的响应应该保存在实例中,而不是像一袋吃喝玩乐一样四处传播——如果你把它丢了怎么办?

        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 方法。

        【讨论】:

          猜你喜欢
          • 2016-02-29
          • 2013-10-30
          • 1970-01-01
          • 2016-12-26
          • 1970-01-01
          • 1970-01-01
          • 2012-07-06
          • 2011-12-10
          • 2011-12-12
          相关资源
          最近更新 更多