Python - 最好有多个方法或许多可选参数?

时间:2022-09-01 11:28:21

I have a class which makes requests to a remote API. I'd like to be able to reduce the number of calls I'm making. Some of the methods in my class make the same API calls (but for different reasons), so I'ld like the ability for them to 'share' a cached API response.

我有一个类向远程API发出请求。我希望能够减少我正在拨打的电话数量。我的类中的一些方法进行相同的API调用(但由于不同的原因),所以我想它们能够“共享”缓存的API响应。

I'm not entirely sure if it's more Pythonic to use optional parameters or to use multiple methods, as the methods have some required parameters if they are making an API call.

我不完全确定使用可选参数或使用多个方法是否更Pythonic,因为这些方法在进行API调用时会有一些必需的参数。

Here are the approches as I see them, which do you think is best?

以下是我看到的方法,您认为最好吗?

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

Or this:

或这个:

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

Or is this more appropriate?

或者这更合适吗?

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

5 个解决方案

#1


5  

Normally when writing method one could argue that a method / object should do one thing and it should do it well. If your method get more and more parameters which require more and more ifs in your code that probably means that your code is doing more then one thing. Especially if those parameters trigger totally different behavior. Instead maybe the same behavior could be produced by having different classes and having them overload methods.

通常在编写方法时,可以认为方法/对象应该做一件事情,它应该做得好。如果你的方法得到越来越多的参数,这些参数需要你的代码中有越来越多的ifs,这可能意味着你的代码做了更多的事情。特别是如果这些参数触发完全不同的行为相反,也许通过使用不同的类并使它们具有重载方法可以产生相同的行为。

Maybe you could use something like:

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

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)

That way you can split the logic of how to lookup the response and the caching while also making it flexible for the user of the API to decide if they want the caching capabilities or not.

这样,您可以分离如何查找响应和缓存的逻辑,同时还使API的用户可以灵活地决定是否需要缓存功能。

#2


2  

There is nothing wrong with the method used in your class B. To make it more obvious at a glance that you actually need to include either item_id or cached_api_response, I would put the error checking first:

您的类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


1  

Ultimately this is a judgement that must be made for each situation. I would ask myself, which of these two more closely fits:

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

  1. Two completely different algorithms or actions, with completely different semantics, even though they may be passed similar information
  2. 两种完全不同的算法或动作,具有完全不同的语义,即使它们可能传递类似的信息
  3. A single conceptual idea, with consistent semantics, but with nuance based on input
  4. 一个概念性的概念,具有一致的语义,但基于输入的细微差别

If the first is closest, go with separate methods. If the second is closest, go with optional arguments. You might even implement a single method by testing the type of the argument(s) to avoid passing additional arguments.

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

#4


1  

This is an OO anti-pattern.

这是OO反模式。

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

    def do_something_else_with_api_response(self, response):
        ...

You have two methods on an instance and you're passing state between them explicitly? Why are these methods and not bare functions in a module?

你有一个实例上的两个方法,你明确地在它们之间传递状态?为什么这些方法而不是模块中的裸功能?

Instead, think about using encapsulation to help you by having the instance of the class own the api response.

相反,考虑使用封装通过让类的实例拥有api响应来帮助您。

For example:

例如:

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)

You have caching and any method which needs this response can run in any order without making multiple api requests because the first method that needs self.response will calculate it and every other will use the cached value. Hopefully it's easy to imagine extending this with multiple URLs or RPC calls. If you have a need for a lot of methods that cache their return values like response above then you should look into a memoization decorator for your methods.

您有缓存,任何需要此响应的方法都可以按任意顺序运行而不需要多个api请求,因为需要self.response的第一个方法将计算它,而其他每个方法都将使用缓存值。希望通过多个URL或RPC调用来扩展它很容易。如果你需要很多方法来缓存它们的返回值,比如上面的响应那么你应该查看你的方法的memoization装饰器。

#5


0  

The cached response should be saved in the instance, not passed around like a bag of Skittles -- what if you dropped it?

缓存的响应应保存在实例中,而不是像一袋Skittles一样传递 - 如果丢弃它会怎么样?

Is item_id unique per instance, or can an instance make queries for more than one? If it can have more than one, I'd go with something like this:

每个实例的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 )

And if you may have to get the most current info about item_id, you can have a refresh_response method.

如果您可能必须获取有关item_id的最新信息,则可以使用refresh_response方法。

#1


5  

Normally when writing method one could argue that a method / object should do one thing and it should do it well. If your method get more and more parameters which require more and more ifs in your code that probably means that your code is doing more then one thing. Especially if those parameters trigger totally different behavior. Instead maybe the same behavior could be produced by having different classes and having them overload methods.

通常在编写方法时,可以认为方法/对象应该做一件事情,它应该做得好。如果你的方法得到越来越多的参数,这些参数需要你的代码中有越来越多的ifs,这可能意味着你的代码做了更多的事情。特别是如果这些参数触发完全不同的行为相反,也许通过使用不同的类并使它们具有重载方法可以产生相同的行为。

Maybe you could use something like:

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

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)

That way you can split the logic of how to lookup the response and the caching while also making it flexible for the user of the API to decide if they want the caching capabilities or not.

这样,您可以分离如何查找响应和缓存的逻辑,同时还使API的用户可以灵活地决定是否需要缓存功能。

#2


2  

There is nothing wrong with the method used in your class B. To make it more obvious at a glance that you actually need to include either item_id or cached_api_response, I would put the error checking first:

您的类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


1  

Ultimately this is a judgement that must be made for each situation. I would ask myself, which of these two more closely fits:

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

  1. Two completely different algorithms or actions, with completely different semantics, even though they may be passed similar information
  2. 两种完全不同的算法或动作,具有完全不同的语义,即使它们可能传递类似的信息
  3. A single conceptual idea, with consistent semantics, but with nuance based on input
  4. 一个概念性的概念,具有一致的语义,但基于输入的细微差别

If the first is closest, go with separate methods. If the second is closest, go with optional arguments. You might even implement a single method by testing the type of the argument(s) to avoid passing additional arguments.

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

#4


1  

This is an OO anti-pattern.

这是OO反模式。

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

    def do_something_else_with_api_response(self, response):
        ...

You have two methods on an instance and you're passing state between them explicitly? Why are these methods and not bare functions in a module?

你有一个实例上的两个方法,你明确地在它们之间传递状态?为什么这些方法而不是模块中的裸功能?

Instead, think about using encapsulation to help you by having the instance of the class own the api response.

相反,考虑使用封装通过让类的实例拥有api响应来帮助您。

For example:

例如:

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)

You have caching and any method which needs this response can run in any order without making multiple api requests because the first method that needs self.response will calculate it and every other will use the cached value. Hopefully it's easy to imagine extending this with multiple URLs or RPC calls. If you have a need for a lot of methods that cache their return values like response above then you should look into a memoization decorator for your methods.

您有缓存,任何需要此响应的方法都可以按任意顺序运行而不需要多个api请求,因为需要self.response的第一个方法将计算它,而其他每个方法都将使用缓存值。希望通过多个URL或RPC调用来扩展它很容易。如果你需要很多方法来缓存它们的返回值,比如上面的响应那么你应该查看你的方法的memoization装饰器。

#5


0  

The cached response should be saved in the instance, not passed around like a bag of Skittles -- what if you dropped it?

缓存的响应应保存在实例中,而不是像一袋Skittles一样传递 - 如果丢弃它会怎么样?

Is item_id unique per instance, or can an instance make queries for more than one? If it can have more than one, I'd go with something like this:

每个实例的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 )

And if you may have to get the most current info about item_id, you can have a refresh_response method.

如果您可能必须获取有关item_id的最新信息,则可以使用refresh_response方法。