返回介绍

建议33:慎用变长参数

发布于 2024-01-30 22:19:09 字数 2886 浏览 0 评论 0 收藏 0

Python支持可变长度的参数列表,可以通过在函数定义的时候使用*args和**kwargs这两个特殊语法来实现(args和kwargs可以替换成任意你喜欢的变量名)。先来看两个可变长参数使用的例子。

1)使用*args来实现可变参数列表:*args用于接受一个包装为元组形式的参数列表来传递非关键字参数,参数个数可以任意,如下例所示。

def SumFun(*args):
  result = 0
  for x in args[0:]:
    result += x
  return result
print SumFun(2,4)
print SumFun(1,2,3,4,5)
print SumFun()

2)使用**kwargs接受字典形式的关键字参数列表,其中字典的键值对分别表示不可变参数的参数名和值。如下例中apple表示参数名,而fruit为其对应的value,可以是一个或者多个键值对。

def category_table(**kwargs):
  for name, value in kwargs.items():
    print '{0} is a kind of {1}'.format(name, value)
category_table(apple = 'fruit', carrot = 'vegetable',Python = 'programming language')
category_table(BMW = 'Car')

如果一个函数中同时定义了普通参数、默认参数,以及上述两种形式的可变参数,那么使用情况又是怎样的呢?来看一个简单的问题。

def set_axis(x,y, xlabel="x", ylabel="y", *args, **kwargs):
  pass

对于上面的函数下面几种调用方式哪些是不正确的呢?

set_axis(2,3, "test1", "test2","test3", my_kwarg="test3")     
①
set_axis(2,3, my_kwarg="test3")
set_axis(2,3, "test1",my_kwarg="test3")             
②
set_axis("test1", "test2", xlabel="new_x",ylabel = "new_y", my_kwarg="test3")
set_axis(2,"test1", "test2",ylabel = "new_y", my_kwarg="test3")

答案是:上面的所有调用方式都是合法的!实际上在4种不同形式的参数同时存在的情况下,会首先满足普通参数,然后是默认参数。如果剩余的参数个数能够覆盖所有的默认参数,则默认参数会使用传递时候的值,如标注①处的函数在调用的时候xlabel和ylabel的值分别为“test1”和“test2”;如果剩余参数个数不够,则尽最大可能满足默认参数的值,标注②中xlabel值为“test1”,而ylabel则使用默认参数y。除此之外其余的参数除了键值对以外所有的参数都将作为args的可变参数,kwargs则与键值对对应。那么,为什么要慎用可变长度参数呢?原因如下:

1)使用过于灵活。上面的示例set_axis()随随便便就能写出好几种不同形式的调用方式。在混合普通参数或者默认参数的情况下,变长参数意味着这个函数的签名不够清晰,存在多种调用方式。如果调用者需要花费过多的时间来研究你的方法该如何调用,显然这并不是一种值得提倡的方式,即使你认为它很炫,很高端大气上档次,但在团队合作开发的项目中,千万不要有这种想法,团队开发不是你的个人竞技场,代码编写从某种程度上说也是一种沟通,清晰准确是一个很重要的指标。另外变长参数可能会破坏程序的健壮性。

2)如果一个函数的参数列表很长,虽然可以通过使用*args和**kwargs来简化函数的定义,但通常这意味着这个函数可以有更好的实现方式,应该被重构。前面的例子中SumFun(*args)如果改为def Sum(seq),在函数调用之前将需要传递的参数保存在一个序列中,如seq = (1,2,3,4,5,6,7),再将序列seq作为参数传入Sum中也是一个不错的选择。同样如果使用**kwargs的目的仅仅是为了传入一个字典,这将是一个非常糟糕的选择。

3)可变长参数适合在下列情况下使用(不仅限于以下场景):

为函数添加一个装饰器。

>>> def mydecorator(fun):
...  def new(*args,**kwargs):
...       # ...
...       return fun(*args,**kwargs)
...  return new
...
>>>

如果参数的数目不确定,可以考虑使用变长参数。如配置文件test.cfg内容如下:

[Defaults]
name = test
version = 1.0
platform = windows
func(**kwargs)
用于读取一些配置文件中的值并进行全局变量初始化。
from ConfigParser import ConfigParser
conf = ConfigParser()
conf.read('test.cfg')
conf_dict = dict(conf.items('Defaults'))
def func(**kwargs):
  kwargs.update(conf_dict)
  global name
  name =  kwargs.get('name')
  global version
  version =  kwargs.get('version')
  global platform
  platform =  kwargs.get('platform')

用来实现函数的多态或者在继承情况下子类需要调用父类的某些方法的时候。

>>> class A(object):
...  def somefun(self,p1,p2):
...       pass
...
>>> class B(A):
...  def myfun(self,p3,*args,**kwargs):
...       super(B,self).somefun(*args,**kwargs)
...
>>>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文